三国志アナザー~星将の願い~ (三国志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 解码 = 普通图片(png jpg webp等)

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的路径

    SortFiles.py

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

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

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

4 个赞

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




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

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

1 个赞

注意力惊人 :nerd_face:

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

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

群英风华录解密脚本
.Scripts/CocosFileProcess/BeeplayAndAstc2Png.py at main · violet-wdream/.Scripts
仅供参考

大佬你好,我看了下国服的也可以用这种方法,但是国服的PNG文件有两种情况:一种是png直接加密,一种是改成了.pkm,可以帮忙看看吗 :folded_hands:

新建文件夹.zip (3.9 MB)

删除签名,异或0x17

艹,这不跟上面的解密一样吗?那还问个锤子

很抱歉麻烦大佬了,感谢大佬的指点,我弄了一个脚本可用来处理国服的.png文件。

但是.pkm文件处理不了,pkm文件主要在战斗小人骨骼里,如果只提取人物皮肤骨骼则不需理会

python脚本.zip (2.2 KB)

pkm没加密可以用texturepacker查看你可以去试试

我弄的这个脚本也可以处理pkm文件,处理后可以通过texturepacker打开,但是通道被分离了,可以写一个脚本来合并

国服提取方法:

提取文件: APK /assets/assets/resources/native

/data/user/0/com.bkty.hxmjl.mi/files/gamecaches/cacheList.json
/data/user/0/com.bkty.hxmjl.mi/files/gamecaches/remote/ 取最大的一个json文件 重命名为 configCommon.json
然后按照文件类型排序,取出其中的atsc atlas bin文件,json以及其他类型的不要,保存到cache目录
/data/user/0/com.bkty.hxmjl.mi/files/app-assets/assets/resources/native 覆盖native文件夹
/data/user/0/com.bkty.hxmjl.mi/files/app-assets/assets/resources/config.json 重命名为 configAPK.json

   工作目录:
├─ ERRORRes
├─ Res
│  └─ native
│  └─ cache
├─ SortedRes
├─ cacheList.json
├─ configAPK.json
└─ configCommon.json

先用楼主给的第一个脚本分类文件,然后用我发的脚本解密,png文件解密后可正常查看,pkm文件可以用texturepacker打开,然后导出为png文件,但是通道被分离了,可以让AI写脚本合并

大概内容:后缀为.png的图片上半部分是带颜色的内容,下半部分是黑白内容,通道被分离了,帮我写一个python脚本来合并通道,合并后的图片覆盖原图片,然后用记事本打开后缀为.atlas的文件,根据第一个size的数值来处理合并后的图片,按照size里面像素高度的数值,从下方开始裁剪对应的图片

因为我这边显示texturepacker无法静默打开,所有没办法将“用texturepacker打开pkm文件然后保存为png图片”加入脚本,也就无法实现批量处理

幻想名将录_Spine整理工具.zip (1.7 MB)
以前写的,不知道能不能用了

步子迈太大容易累劈叉,看看就行了选择性看有帮助的没啥可吵的不过看样子确实大佬提供了一些有用的东西但是你没去看。。。算了多说又吵起来了 :sweat_smile:

4a679daabe894daf8a7b5786375ac806

:tieba_23:
不是…我以为文档里下面的东西不会用到的

在你准备要通过电子邮件、新闻群组或者聊天室提出技术问题前,请先做到以下事情:

1. 尝试在你准备提问的论坛的旧文章中搜索答案。
2. 尝试上网搜索以找到答案。
3. 尝试阅读手册以找到答案。
4. 尝试阅读常见问题文件(FAQ)以找到答案。
5. 尝试自己检查或试验以找到答案。
6. 向你身边的强者朋友打听以找到答案。
7. 如果你是程序开发者,请尝试阅读源代码以找到答案。

描述目标而不是过程

如果你想弄清楚如何做某事(而不是报告一个 Bug),在开头就描述你的目标,然后才陈述重现你所卡住的特定步骤。

经常寻求技术帮助的人在心中有个更高层次的目标,而他们在自以为能达到目标的特定道路上被卡住了,然后跑来问该怎么走,但没有意识到这条路本身就有问题。结果要费很大的劲才能搞定。

处理无礼的回应

很多黑客圈子中看似无礼的行为并不是存心冒犯。相反,它是直截了当,一针见血式的交流风格,这种风格更注重解决问题,而不是使人感觉舒服而却模模糊糊。

如果你觉得被冒犯了,试着平静地反应。如果有人真的做了出格的事,邮件列表、新闻群组或论坛中的前辈多半会招呼他。如果这没有发生而你却发火了,那么你发火对象的言语可能在黑客社区中看起来是正常的,而将被视为有错的一方,这将伤害到你获取信息或帮助的机会。

另一方面,你偶尔真的会碰到无礼和无聊的言行。与上述相反,对真正的冒犯者狠狠地打击,用犀利的语言将其驳得体无完肤都是可以接受的。然而,在行事之前一定要非常非常的有根据。纠正无礼的言论与开始一场毫无意义的口水战仅一线之隔,黑客们自己莽撞地越线的情况并不鲜见。如果你是新手或外人,避开这种莽撞的机会并不高。如果你想得到的是信息而不是消磨时光,这时最好不要把手放在键盘上以免冒险。

(有些人断言很多黑客都有轻度的自闭症或亚斯伯格综合症,缺少用于润滑人类社会正常交往所需的神经。这既可能是真也可能是假的。如果你自己不是黑客,兴许你认为我们脑袋有问题还能帮助你应付我们的古怪行为。只管这么干好了,我们不在乎。我们喜欢我们现在这个样子,并且通常对病患标记都有站得住脚的怀疑。)

如何避免扮演失败者

在黑客社区的论坛中,你以本指南所描述的或类似的方式,可能会有那么几次搞砸了。而你会在公开场合中被告知你是如何搞砸的,也许攻击的言语中还会带点夹七夹八的颜色。

这种事发生以后,你能做的最糟糕的事莫过于哀嚎你的遭遇、宣称被言语攻击、要求道歉、高声尖叫、憋闷气、威胁诉诸法律、向其雇主报怨、不去关马桶盖等等。相反地,你该这么做:

熬过去,这很正常。事实上,它是有益健康且合理的。

社区的标准不会自行维持,它们是通过参与者积极而公开地执行来维持的。不要哭嚎所有的批评都应该通过私下的邮件传送,它不是这样运作的。当有人评论你的一个说法有误或者提出不同看法时,坚持声称受到个人攻击也毫无益处,这些都是失败者的态度。

也有其它的黑客论坛,受过高礼节要求的误导,禁止参与者张贴任何对别人帖子挑毛病的消息,并声称如果你不想帮助用户就闭嘴。 结果造成有想法的参与者纷纷离开,这么做只会使它们沦为毫无意义的唠叨与无用的技术论坛。

夸张的讲法是:你要的是“友善”(以上述方式)还是有用?两个里面挑一个。

记着:当黑客说你搞砸了,并且(无论多么刺耳)告诉你别再这样做时,他正在为关心他的社区而行动。对他而言,不理你并将你从他的生活中滤掉更简单。如果你无法做到感谢,至少要表现得有点尊严,别大声哀嚎,也别因为自己是个有戏剧性超级敏感的灵魂和自以为有资格的新来者,就指望别人像对待脆弱的洋娃娃那样对你。

有时候,即使你没有搞砸(或者只是在他的想像中你搞砸了),有些人也会无缘无故地攻击你本人。在这种情况下,抱怨倒是真的会把问题搞砸。