Project SEKAI 逆向(4): pjsk AssetBundle 反混淆 + PV 动画导入
- 分析variant:世界計劃 2.6.1 (Google Play 台服)
1. 数据提取
pjsk资源采用热更新模式;本体运行时之外,还会有3~4G左右的资源 (**注:**不定量,见下一篇)
- 尝试从本机提取资源
没有magic UnityFS
,考虑ab文件有混淆
2. 加载流程分析
- 进dnSpy直接搜assetbundle找相关Class
- 进ida看impl,可以很轻松的找到加载ab的嫌疑流程
- 最后直接调用了unity的
LoadFromStream
,Sekai.AssetBundleStream
实现了这样的Stream:
可以注意到
加载时根据
_isInverted
flag 决定是否进行反混淆操作如果有,则先跳过4bytes,之后5bytes按位取反
最后移交
InvertedBytesAB
继续处理- 注意到
n00
应为128,v20
为读取offset
- 注意到
这里考虑offset=0情况,那么仅前128字节需要处理
跟进InvertedBytesAB
可见,这里即跳过4bytes后,每 8bytes,取反前5bytes
综上,解密流程分析完毕;附脚本:
import sys
import os
def decrypt(infile, outfile):
with open(infile, 'rb') as fin:
magic = fin.read(4)
if magic == b'\x10\x00\x00\x00':
with open(outfile,'wb') as fout:
for _ in range(0,128,8):
block = bytearray(fin.read(8))
for i in range(5):
block[i] = ~block[i] & 0xff
fout.write(block)
while (block := fin.read(8)):
fout.write(block)
else:
print('copy %s -> %s', infile, outfile)
fin.seek(0)
with open(outfile,'wb') as fout:
while (block := fin.read(8)):
fout.write(block)
if len(sys.argv) == 1:
print('usage: %s <in dir> <out dir>' % sys.argv[0])
else:
for root, dirs, files in os.walk(sys.argv[1]):
for fname in files:
file = os.path.join(root,fname)
if (os.path.isfile(file)):
decrypt(file, os.path.join(sys.argv[2], fname))
3. 提取资源
- 文件处理完后,就可以靠https://github.com/Perfare/AssetStudio查看资源了:
- 不过版本号很好找,这里是
2020.3.21f1
:
- 加载可行,如图:
4. AssetBundleInfo?
在数据目录里发现了这个文件,同时在Sekai_AssetBundleManager__LoadClientAssetBundleInfo
中:
用的是和API一样的密钥和封包手段,解开看看
注: 工具移步 https://github.com/mos9527/sssekai;内部解密流程在文章中都有描述
python -m sssekai apidecrypt .\AssetBundleInfo .\AssetBundleInfo.json
5. 资源使用?
- 角色模型数很少
猜测这里的资源被热加载;在blender直接看看已经有的mesh吧:
bind pose有问题,修正FBX导出设置可以解决;不过暂且不往这个方向深究
- 同时也许可以试试导入 Unity?
https://github.com/AssetRipper/AssetRipper/ 可以做到这一点,尝试如下:
- 拖进 Editor
- 注意shader并没有被拉出来,暂时用standard替补
- face/body mesh分开;需绑定face root bone(Neck)到body (Neck)
using UnityEngine;
public class BoneAttach : MonoBehaviour
{
public GameObject src;
public GameObject target;
void Start()
{
Update();
}
void Update()
{
target.transform.position = src.transform.position;
target.transform.rotation = src.transform.rotation;
target.transform.localScale = src.transform.localScale;
}
}
- 注意到blendshape/morph名字对不上
爬了下issue:这里的数字是名称的crc32(见 https://github.com/AssetRipper/AssetRipper/issues/954)
- 拿blendshape名字做个map修复后,动画key正常
- 加上timeline后的播放效果
不知道什么时候写之后的,暂时画几个饼:
- 资源导入Blender + toon shader 复刻
- 资源导入 Foundation
- 脱离游戏解析+下载资源
SEE YOU SPACE COWBOY…
References
https://github.com/AssetRipper/AssetRipper/