三国志アナザー~星将の願い~ (三国志another/幻想名将录)解包教程(也许)

三国志アナザー~星将の願い~ 停更了 Spine Cocos2D Astc-Beeplay

赏析环节。给个7.5分,还是挺好看的。第一次处理cocos引擎游戏,真是一路折腾:exploding_head::exploding_head::exploding_head:,感觉收获颇丰。

游戏资源

依旧来自Qoo[Download] 三国志アナザー 星将の願い - QooApp Game Store

【公式】三国志アナザー~星将の願い~(三国another) (@another3594) / X

官网半天找不到,大概也是半截入土了。

资产路径

都在APK里面,这里忘记看了,所以安装了。应该也是APK里面有分块APK,找到下面这个。

在以下这个路径里面

这里的native就是所有的资源文件,import和index.jsc没什么用

config.json很重要,是用来还原文件名的。这里先解压出来。

key (可以跳过)

这里得到的key并不重要,因为后续也用不到,只是用来反编译jsc使用。如果只需要获取spine,这里可以跳过

参考

关于Cocos2dx-js游戏的jsc文件解密(二) - 吾爱破解 - 52pojie.cn

群英风华录解密有教的吗 - 讨论 - Live2DHub

关于Cocos2dx打包游戏的jsc文件解密(一) - 知乎

cocos引擎,xxtea加密

libcocos2djs.so搜索Cocos Game 后面跟着的第一个串就是keywf-game-card

77 66 2D 67 61 6D 65 2D 63 61 72 64 00 00 00 00 #自动填充到16 bytes
wf-game-card

或者IDA里面搜索xxtea相关的函数也能找到。

验证了论坛里的一个说法applicationDidFinishLaunching,大多都是在这里可以找到xxtea的key

AppDelegate::applicationDidFinishLaunching(void)

还原astc为png

从散落的文件里看到了atlas文件,所以是spine无疑了。然后bin文件就是二进制skel骨骼,直接改为skel后缀就能用了。

绝大部分文件都是astc,压缩且加密,没法直接转换,解压astc发现文件头变成了beeplay,说明第一步是解压,然后再去掉beeplay进行XOR解密

逆向libcocos2djs.so搜索astc以及image相关函数

cocos2d::Image::initWithImageData

发现检测文件头7bytes,就是检测beeplay签名

if ( !memcmp(cocos2d::Image::initWithImageData(unsigned char const*,long)::ENCRYPT_SIGNATURE, p[0], 7u) )
{
    v8 = v4 - 7;
    v9 = (unsigned __int8 *)malloc(v4 - 7);
    memcpy(v9, v7 + 7, v4 - 7);
    if ( v4 - 7 >= 1 )
    {
        if ( v8 < 0x20 )
        {
            v10 = 0;
            do
                LABEL_15:
            v9[v10++] ^= 0x17u;
            while ( v8 != v10 );
            goto LABEL_16;
        }
        v11 = 0;
        v10 = v8 & 0xFFFFFFFFFFFFFFE0LL;
        v12.n128_u64[0] = 0x1717171717171717LL;
        v12.n128_u64[1] = 0x1717171717171717LL;
        do
        {
            v13 = (int8x16_t *)&v9[v11];
            v14 = *(int8x16_t *)&v9[v11];
            v15 = *(int8x16_t *)&v9[v11 + 16];
            v11 += 32;
            *v13 = veorq_s8(v14, v12);
            v13[1] = veorq_s8(v15, v12);
        }
        while ( v10 != v11 );
        if ( v8 != v10 )
            goto LABEL_15;
    }
}

这里大概就是说把beeplay文件头去掉后,直接对所有的字节XOR 0x17

伪astc文件  
  └─ GZIP解压
       └─ beeplay ASTC
             └─ 去 7 字节头
                  └─ XOR 解密
                      └─ 标准 ASTC
                          └─ astcenc 解码 = 正常astc文件

astc处理为正常格式后,还需要分类,

上面提到了很多种图片格式,这里统一处理为png格式的(spine的贴图是png的),这里只会处理astc

文件,skel 和 atlas不会处理,可以放心使用。后文会给出脚本。

资产分类/名称还原

所有文件的名称都是没什么规律的,没法进行分类,所以也没法通过名称找到同一个spine的三个文件。尽管可以尝试用atlas里面记载的图片名称和尺寸进行匹配,但是skel的匹配却别无他法。所以最优解法还是找到映射表。

根据上文找到的key,可以通过jsc反编译器jsc解加密工具-Orange.zip - 蓝奏云来查看源码index.jsc

这里得到index.jsc,目前看来没什么用。推测是引擎自己使用的逻辑,有兴趣可以自行了解一下。

理论上来说通过config.json应该就可以搞定了,但是这里uuid里记载的都是22位的,实际文件基本都是36位,有少数是9位的。在config里面都是22位的根本找不到,应该还做了其他处理。

通过抓包发现,下载的本地manifest里面记载的也是22位数据,同时远程的manifest也是22位数据,所以说要处理的话只能在本地处理了。要去找到游戏内部的处理逻辑。

# 热更新
http://hotter-hxmjljp.wengames.com/japan/cardjapan-update/1.0.17.73/ios_bundle/assets/resources/import/51/5127c5e3-3e3e-4150-b8bf-904386ffe7fc.json?md5=b120aa6f54754ce2834e617c2ddc4e7f
# 本地manifest
http://hotter-hxmjljp.wengames.com/japan/cardjapan-update/1.0.17.73/ios_bundle/project.manifest

经过一天的瞎折腾,终于找到了答案:

不懂就问:UUID的压缩算法是怎么样的呢? - Creator 3.x - Cocos中文社区

有什么方法能从cocos creator构建的游戏里还原出live2d或spine动画资源? - 讨论 - Live2DHub

config.json里记载的uuid是22位的被压缩的uuid,而文件名称都是36位的uuid (不是hash)

处理顺序:读取config.json建表(path的key对应uuid的index)dictionary<uuid22, path>,遍历所有文件(uuid36 → uuid22 → path)找到对应path,重命名并移动。后文会给出处理脚本。


处理有点慢,要个5s吧。但是处理完发现还有大约900张图片是没有分类的,因为config里面根本没有对应的路径,只能找到uuid。

小人spines/panel/ 立绘spines/beauty 预览图icon/heroHead

到这里基本上就搞定了,接下来就是把spine相关文件的名称改为模型名称。

从马后炮的角度来看,其实path里面记载的路径比如"3": ["icon/heroHead/11068", 1],

最后一部分11068其实是文件名,icon/heroHead才是路径。这样可以理解为什么会有这样的"spines/panel/SG_SHU_OR_pangtong/SG_SHU_OR_pangtong"后面两段重复的路径了。

发现还缺了挺多东西的,不知道是不是鉴权,下面这几个角色有一大半没有。

从头像数量来看,应该有211个的spine,config的路径里面spines/beauty/spine/开头的有168个,分类后实际42个。

我找了一个没有的角色头像,同时也是未被分类的,计算出来的path应该是8790,而这里刚好没有。这就产生了一个疑点。

image-20260118130301744

搜索武则天,在官网也能找到对应的立绘,但是本地只能找到小人,config里面也只有小人的相关文件,并没有立绘的。

实测游戏内可以看到立绘,而且是动态的。

在游戏里面切换立绘十分流畅,几乎可以肯定都是本地资源。

又经过了一番探索,发现了问题的端倪,APK里的只有500MB,而游戏体积占大头的一定是资产,所以这1.69G里面肯定有资产。

/data/data/包名/这个路径下,remote目录

这里还有个很重要的表cacheList.json,一并保存下来。

打开remote发现有两个特别大的json文件,果不其然是config。为什么会有两个?

猜测因为把native和import放一起了,所有既有atsc bin atlas 还有json文件。

但是这俩玩意里面很多重复的条目,这个也无从分辨,不知是怎么考虑的,当前的策略是第一个config为主体,如何没有查到,就查另一个config。不过从结果来看,用那个size更大的config好像就行了,因为这个查不到的话,另一个也还是查不到。

注意到文件的命名都不是36位的uuid,而是类似17686595069780的一串数字。这里cacheList.json就发挥作用了,这里又是一层映射关系,url → uuid36

然后接下来的处理方式跟之前一致。

总结

  1. APK资源:Qoo下载APK,解压找到split_install_time_assets.apk,再解压找到 assets > assets > resources 这个目录保存native文件夹和config.json (configAPK),native应该有400+MB。
  2. Cache资源:在上文提到路径,保存remote目录和cacheList.json。将文件按照大小排序,保存最大的那个json17686595437760.json (configCommon 4528 KB),然后按照文件类型排序,取出其中的atsc atlas bin文件,json以及其他类型的不要,保存到cache目录。
  3. native和cache目录统一放在Res目录下

形成以下工作目录结构

.
├─ ERRORRes
├─ Res
│  └─ native
│  └─ cache
├─ SortedRes
├─ cacheList.json
├─ configAPK.json
└─ configCommon.json

先分类,再处理。分别使用以下两个脚本来分类和还原。具体使用方法看注释或者问AI

  1. 分类:把脚本里面的输入目录改为工作目录,同时修改这三个json的路径

    .Scripts/SanGuoAnother/SortFiles.py at main · violet-wdream/.Scripts

  2. 还原:分类完毕后,把还原脚本里的输入路径改为你想要还原的部分(比如.\SortedRes\spines\beauty\spine),不建议全部还原,因为文件太多了,astcenc处理速度有点慢。
    .Scripts/SanGuoAnother/Astc2Png (SanGuoAnother).py at main · violet-wdream/.Scripts

部分文件无法分类放在了ERRORRes,需要手动处理。我看了下都是些乱七八糟的玩意,无伤大雅。

总共172个,有个模型缺了atlas,应该是这个nvzhulihui_pifu2

4 个赞

我没记错的话之前帮别人解过一个Hgame就是这个换皮




看spine挺像的界面也是
不过这个Hgame的sign是cocosgame好像是太久了记不清了不过可以留意一下
翻了一下邮件大概是2025 02 27时候解的游戏下载链接G18
不过已经死了 :)

还有这个工具的宣传也是使用这个游戏的spine

1 个赞

注意力惊人 :nerd_face:

这好像是 幻想名将录 加密也一样

是的,还有个群英风华录也是这个公司做的,换皮流水线了属于是
ClipWindowsGIF