记少女前线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)
上一篇 2021年10月16日
下一篇 2021年10月17日
alt

相关推荐

发表回复

登录后才能评论

评论列表(17条)

  • 匿名的头像
    匿名 2021年12月25日 下午6:20

    问下动作的代码打在哪里

  • 匿名的头像
    匿名 2021年12月26日 上午12:22

    大佬,知道为什么创建模型适配文件点确定的时候会弹出读取模型失败的原因吗,下午弄的好好的,晚上就这样了

  • 匿名的头像
    匿名 2022年5月4日 上午12:01

    动作弄出来后,就嘴巴不会动,其他动作正常...

  • パネライ ルミノール デイライト 中古的头像

    I was suɡgested this blog by my cousin. I'm not
    sure ѡhether thiis ρost is writgten by him as nobody else know suhch detailed about mʏ trouble.
    You arе incrediƄle! Thanks!

    Feel free to visit my blog post; パネライ ルミノール デイライト 中古

  • Aasen的头像
    Aasen 2023年8月3日 下午3:18

    实践了下,如果使用Cubism附带的Viewer的话,motion文件转motion3.json时需要将Meta的FPS参数设置为合适的值,以及Segments中Identifier的数值需要是不带小数点的整型,否则Viewer播放motion文件时会无法正常播放。。。也许是ViewerEX的功能更全面些,能自动纠错吗emmm

TG通知群
小程序
小程序
分享本页
返回顶部