记少女前线Live2d模型的提取(新版) +相关程序 2021年

早就想把少前妹纸的live2d提取出来放桌面上做壁纸了~直到有一天终于琢磨出来了,记少女前线Live2d模型的解包(新版) 2021Y

早就想把少前妹纸的live2d提取出来放桌面上做壁纸了~直到有一天终于琢磨出来了

记少女前线Live2d模型的提取(新版) +相关程序 2021年

衷心感谢up主kjkjkAIStudio

LINK:https://www.bilibili.com/video/BV1vK4y1D7wY?spm_id_from=333.788.b_636f6d6d656e74.151

经过无数次讨教与琢磨,终于将模型文件与动作文件搞出来了

全过程:(其实就是按up的方法照葫芦画瓢)

搞个安卓模拟器,下个少女前线,进到游戏里更新,更完后就是这样的

记少女前线Live2d模型的提取(新版) +相关程序 2021年

这就行了,退出,去文件管理

定位到Android/data/com.sunborn.girlsfrontline.cn/files/Android/New这个文件夹,里面全是游戏的资源文件,有spine和live2d等等

记少女前线Live2d模型的提取(新版) +相关程序 2021年

将New这个文件夹复制到模拟器与电脑的共享文件夹

记少女前线Live2d模型的提取(新版) +相关程序 2021年

还是有挺多文件的

重要:下载一个Asset Bundle Extractor和010 editor(下好了请无视,如果你有十六进制编辑器可以不用下010 editor)

需要输入密码:102030 获取资源!

[secret key="102030"] [/secret]

点开 AssetBundleExtractor ,左上角file,open,选择刚刚提取出来的文件。注意文件名要带有live2d

记少女前线Live2d模型的提取(新版) +相关程序 2021年
记少女前线Live2d模型的提取(新版) +相关程序 2021年

直接点是,随便命个名(我永远都是1)

然后点info

记少女前线Live2d模型的提取(新版) +相关程序 2021年

打开出来后有一大堆东西

记少女前线Live2d模型的提取(新版) +相关程序 2021年

点一点name,排个序,这样好找一些

找了一会后,注意MonoBehaviour model_moc,这就是我们要找的文件之一:live2d的模型文件,后缀名为moc3,然后我用来举例这个文件有两个模型,有一个是normal,有一个是destroy,俗称大破(更涩气),这两个文件要分开,最好标记一下,因为后面的文件也要一一对应的

记少女前线Live2d模型的提取(新版) +相关程序 2021年

点击export raw,保存(他保存的格式为dat)

记少女前线Live2d模型的提取(新版) +相关程序 2021年

p.s.可以用一下我的方法存文件,先建文件夹,这样方便很多,模型名/normal or destroy/包含moc3文件,motions文件夹(放动态文件)和textures文件夹(放贴图)

记少女前线Live2d模型的提取(新版) +相关程序 2021年

这时又看到texture文件,照样有normal和destroy,点击plugins,选择export to png,保存

记少女前线Live2d模型的提取(新版) +相关程序 2021年

这就完事了

然后找到你提取出来的模型文件,这时你会发现,后缀名是dat,并不是moc3,也许你会尝试直接改为moc3,但是打不开,这是要用010 editor编辑一下文件

记少女前线Live2d模型的提取(新版) +相关程序 2021年

如果你与正常的moc3文件对比一下,就会发现我们刚提取出来的模型文件在moc3字符前多出一些不明字符,给他删掉,保存

记得改文件后缀名,我们就完美得到少女前线的moc3模型文件了

接着打开live2d viewer EX(此应用steam有售)创建模型配置文件,后缀名为.model3.json。因为这个文件在加密包里是没有的,我们需要手动给他创建出来。当然,自己写文件也可以,我也会介绍如何从零开始创建模型配置文件,但个人推荐还是用 live2d viewer EX 方便一些,而且能实时看到模型效果~

打开EX工作室,选择live2d编辑器并导入刚刚弄好的moc3模型

记少女前线Live2d模型的提取(新版) +相关程序 2021年

优势出来了,傻瓜式创建模型配置文件,选择对应的贴图(不要选错,区分normal和destroy的贴图!)

记少女前线Live2d模型的提取(新版) +相关程序 2021年

这不就出来了?

记少女前线Live2d模型的提取(新版) +相关程序 2021年

destroy(大破版)

欢呼

第二种创建模型配置文件的方法:自己动手丰衣足食

新建一个文本文档,然后修改后缀为 .model3.json ,粘贴以下代码

{
  "Version": 3,


//以下路径需修改为自己的实际路径,moc为模型文件,textures为贴图文件
  "FileReferences": {
    "Moc": "ump9_normal.moc3",
    "Textures": [
      "textures/texture_00-CAB-b4639c0b22c12740e71d5b9d7c1a6de5-2881308979586228580.png"
    ],
    "PhysicsV2": {}
  },
  "Controllers": {
    "ParamHit": {},
    "ParamLoop": {},
    "KeyTrigger": {},
    "ParamTrigger": {},
    "AreaTrigger": {},
    "HandTrigger": {},
    "EyeBlink": {
      "MinInterval": 500,
      "MaxInterval": 6000,
      "Enabled": true
    },
    "LipSync": {
      "Gain": 5.0
    },
    "MouseTracking": {
      "SmoothTime": 0.15,
      "Enabled": true
    },
    "AutoBreath": {
      "Enabled": true
    },
    "ExtraMotion": {
      "Enabled": true
    },
    "Accelerometer": {
      "Enabled": true
    },
    "Microphone": {},
    "Transform": {},
    "FaceTracking": {
      "Enabled": true
    },
    "HandTracking": {},
    "ParamValue": {},
    "PartOpacity": {},
    "ArtmeshOpacity": {},
    "ArtmeshColor": {},
    "ArtmeshCulling": {
      "DefaultMode": 0
    },
    "IntimacySystem": {}
  },
  "Options": {}
}

注意要修改贴图和模型文件的路径,不会可以看下图

记少女前线Live2d模型的提取(新版) +相关程序 2021年

至此,模型搞好了,接下来是动作文件了!(最难)

还是需要感谢UPkjkjkAIStudio,在他的视频里有很详细的提取原理,这里不再过多赘述

还是打开 AssetBundleExtractor ,大约在moc文件附近能找到动作文件,如图

记少女前线Live2d模型的提取(新版) +相关程序 2021年
记少女前线Live2d模型的提取(新版) +相关程序 2021年

为什么我不把红圈上面的destroy.fade和normal.fade圈起来呢?因为经过无数实验,那两个不是动作文件,所以忽略

assets bundle里找到mono behaviour xxxx.fade文件就是
type为motion fade.CubismFadeMotionData

点击export dump,选择导出文件类型为UABE json dump

记少女前线Live2d模型的提取(新版) +相关程序 2021年
记少女前线Live2d模型的提取(新版) +相关程序 2021年

要注意动作文件也有normal和destroy之分

导出来了,难道这就是真正的动作文件了吗?想多了,后缀名都不一样,正常的文件后缀名是.motion3.json的,此时就要用up主的思路来写一个解密小软件

Visual Studio安排上,下图文件结构

记少女前线Live2d模型的提取(新版) +相关程序 2021年

程序读取转换实现(Program.cs)

using System;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;


namespace CubismFadeMotionDataToJson
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("CubismFadeMontionDataToJson 版本0.1b1");
            Console.WriteLine("功能:用UABE以json形式从assess bundle导出cubismfademotiondata类的数据,可以用此程序转换为一般的live2d动作文件");
            Console.WriteLine("仅支持cubism live2d sdk3 motion json的导出");
            Console.WriteLine("警告:处于实验性阶段,出现问题很正常");
            Console.WriteLine("按任意键开始");
            Console.ReadKey();

            if (Directory.Exists("dst"))
            {
                string[] existFilename = Directory.GetFiles("dst");
                foreach (string i in existFilename)
                    File.Delete(i);
            }
            else
            {
                Directory.CreateDirectory("dst");
            }
            string[] filenames = Directory.GetFiles("src");
            MotionDataConverter converter = new MotionDataConverter();
            foreach (string i in filenames)
            {
                Console.WriteLine(string.Format("转换:[0]......", i));
                try
                {

                    var json = File.ReadAllText(i);
                    var infinity = JsonConvert.PositiveInfinity;
                    var newContent = json.Replace("1.#INF", infinity.ToString());
                    var obj = JObject.Parse(newContent);
                    var content = converter.Convert(obj).ToString();
                    File.WriteAllText("dst/" + Path.GetFileName(i),content);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    Console.WriteLine("转换失败");

                }
                
            }
            Console.WriteLine("全部转换成功,现在开始改名");
            RenameMacro();
            Console.WriteLine("successful");
            Console.ReadKey();

        }
        static void RenameMacro()
        {
            string[] filenames = Directory.GetFiles("dst");
            for (int i = 0; i < filenames.Length; ++i)
            {
                string name = Path.GetFileName(filenames[i]);
                int chrPos = name.LastIndexOf('-'); // find last '-'
                if (chrPos == -1)
                    continue;
                name = name.Remove(chrPos,name.Length - chrPos);
                name += ".motion3.json";
                File.Move(filenames[i], "dst/" + name);

            }
        }
    }
}

关键逻辑(解密代码,即MotionDataConverter.cs)

using Newtonsoft.Json.Linq;
using System;


namespace CubismFadeMotionDataToJson
{
    
    public class MotionDataConverter

    {
        public MotionDataConverter()
        {

        }
        public JObject Convert(JObject cubismFadeMotionData)
        {
            JObject result = new JObject();
            WriteHead(result);

            WriteCurves(result, (JObject)cubismFadeMotionData.GetValue("0 MonoBehaviour Base"));
            return result;
        }
        class Keyframe
        {
            public float time, value, inSlope, outSlope;
        }
        void WriteHead(JObject dstData)
        {
            dstData.Add("Version", 3);
            JObject meta = new JObject();
            meta.Add("Duration", 0.0f);
            meta.Add("Pps", 0.0f);
            meta.Add("Loop",true);
            meta.Add("AreBeziersRestricted",true);
            meta.Add("CurveCount", 0.0f);
            meta.Add("TotalSegmentCount", 0.0f);
            meta.Add("TotalPointCount", 0.0f);
            meta.Add("UserDataCount", 0.0f);
            meta.Add("TotalUserDataSize", 0.0f);
            dstData.Add("Meta", meta);
        }
        void WriteCurves(JObject dstData, JObject srcData)
        {
            JArray curves = new JArray();
            string[] parameterIds = GetParameterIds(srcData);

            float[] parameterFadeInTimes = GetParameterFadeInTimes(srcData);


            float[] parameterFadeOutTimes =  GetParameterFadeOutTimes(srcData);



            JArray animationCurves = (JArray)((JObject)srcData.GetValue("0 vector ParameterCurves")).GetValue("1 Array Array");
            
            for (int i = 0; i < parameterIds.Length; ++i)
            {
                if (string.IsNullOrEmpty(parameterIds[i]))
                    continue;
                JObject curve = new JObject();
                curve.Add("Target", "Parameter");
                curve.Add("Id", parameterIds[i]);
                if (parameterFadeInTimes[i] >= 0.0f)
                    curve.Add("FadeInTimes", parameterFadeInTimes[i]);
                if (parameterFadeOutTimes[i] >= 0.0f)
                    curve.Add("FadeOutTimes", parameterFadeOutTimes[i]);

                curve.Add("Segments", ConvertKeyFramesToCurvesSegments((JObject)((JObject)animationCurves[i]).GetValue("0 AnimationCurve data")) );
                curves.Add(curve);
                
            }
            dstData.Add("Curves", curves);
        }
        string[] GetParameterIds(JObject srcData)
        {
            JArray array = (JArray)((JObject)srcData.GetValue("0 vector ParameterIds")).GetValue("1 Array Array");
            string[] result = new string[array.Count];
            for (int i = 0; i < result.Length; ++i)
                result[i] = (string)((JObject)array[i]).GetValue("1 string data");
            return result;
        }
        
            
        float[] GetParameterFadeInTimes(JObject srcData)
        {
            JArray array = (JArray)((JObject)srcData.GetValue("0 vector ParameterFadeInTimes")).GetValue("1 Array Array");
            float[] result = new float[array.Count];
            for (int i = 0; i < result.Length; ++i)
                result[i] = (float)((JObject)array[i]).GetValue("0 float data");
            return result;
        }
        float[] GetParameterFadeOutTimes(JObject srcData)
        {
            JArray array = (JArray)((JObject)srcData.GetValue("0 vector ParameterFadeOutTimes")).GetValue("1 Array Array");
            float[] result = new float[array.Count];
            for (int i = 0; i < result.Length; ++i)
                result[i] = (float)((JObject)array[i]).GetValue("0 float data");
            return result;
        }
        
        JArray ConvertKeyFramesToCurvesSegments(JObject animationCurves)
        
        {
            
            JArray result = new JArray();

            JArray curveArray = (JArray)((JObject)animationCurves.GetValue("0 vector m_Curve")).GetValue("1 Array Array");
            if (curveArray.Count == 0)
                return result;

            Keyframe[] keyframe = ConvertJsonToArray(curveArray);
            //first 2 keyframe must be segment
            result.Add(keyframe[0].time);
            result.Add(keyframe[0].value);
            for (int j = 1; j < keyframe.Length; ++j)
            {
                //judge keyfraame type
                if (j + 1 < keyframe.Length && keyframe[j].inSlope != 0.0f && keyframe[j].outSlope == 0.0f && keyframe[j +1].inSlope == 0.0f &&keyframe[j+1].inSlope == 0.0f)
                {
                    result.Add(3.0f); //type:inverseStepped
                    result.Add(keyframe[j + 1].time);
                    result.Add(keyframe[j + 1].value);
                    ++j; //inversestepped create 2 keyframe
                }
                else if (float.IsPositiveInfinity(keyframe[j].inSlope))
                {
                    result.Add(2.0f);
                    result.Add(keyframe[j].time);
                    result.Add(keyframe[j].value);
                }
           
                
                else if (keyframe[j - 1].outSlope == keyframe[j].inSlope)
                {
                    result.Add(0.0f);
                    result.Add(keyframe[j].time);
                    result.Add(keyframe[j].value);
                }
                else
                {
                    result.Add(1.0f);
                    float tangentLength = Math.Abs(keyframe[j - 1].time - keyframe[j].time) * 0.333333f;
                    result.Add(0.0f);
                    
                    result.Add(keyframe[j - 1].outSlope * tangentLength + keyframe[j - 1].value);
                    result.Add(0.0f);
                    result.Add(keyframe[j].value - keyframe[j].inSlope * tangentLength);
                    result.Add(keyframe[j].time);
                    result.Add(keyframe[j].value);
                }
                    

            }
            return result;
        }
        Keyframe[] ConvertJsonToArray(JArray array)
        {
            Keyframe[] result = new Keyframe[array.Count];
            for (int i = 0; i < array.Count; ++i)
            {
                JObject obj = (JObject)((JObject)array[i]).GetValue("0 Keyframe data");
                result[i] = new Keyframe();
                result[i].time = (float)obj.GetValue("0 float time");
                result[i].value = (float)obj.GetValue("0 float value");
                var test = obj.GetValue("0 float inSlope");
                float max;
                if(float.TryParse(test.ToString(), out max))
                {
                    result[i].inSlope = (float)obj.GetValue("0 float inSlope");
                }
                else
                {
                    result[i].inSlope = float.MaxValue;
                }
                
                result[i].outSlope = (float)obj.GetValue("0 float outSlope");
            }
            return result;

        }
    }
}

实现:将提取出来的动作文件放入src文件夹,程序将其转换成真正的motion3.json位于dst文件夹内

记少女前线Live2d模型的提取(新版) +相关程序 2021年

需要输入密码:102030 获取资源!

[secret key="102030"] [/secret]

转换程序,将文件后缀改为txt

解压密码146586742786875288767857857869986788775246452

当初为了抄up这玩意,真是琢磨了好久。。。

我们可以看看转换后的文件与转换前的文件对比,下图是转换前

记少女前线Live2d模型的提取(新版) +相关程序 2021年

下图是转换后

记少女前线Live2d模型的提取(新版) +相关程序 2021年

用live2d viewer EX 打开后一切正常~

当然,还要修改一下模型配置文件,将动作文件导进去,这里也不说了

至此,少前的liv2d提出来了,这下桌面又生动起来了~~~

原创文章,作者:Rosmontics,如若转载,请注明出处:https://rosmontis.com/archives/74

(0)
RosmonticsRosmontics网站管理员
上一篇 2021年10月16日 下午8:55
下一篇 2021年10月17日 下午5:21

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

评论审核已启用。您的评论可能需要一段时间后才能被显示。