这几天排炉石太慢了,所以趁着间隙研究了一下拆包
零、准备工作
- 下载AssetStudio.CLI或者类似的可以用命令行调用的拆包工具
- 一门可以调用命令行的语言(最好支持多线程)
直接用AssetStudio的GUI,不用任何编程语言也可以,但是纯手动的走复杂的拆包流程会很痛苦。
一、前情提要
由于是打牌间隙写得,部分内容会写得比较糙,看不懂的时候可以回去翻翻,特别是th的那篇文章,讲得比我这里细致。(顺便催更拆包教学2)
二、AssetStudio.CLI的使用
调用的标准格式为:AssetStudio <input_path> <output_path> [options]
其中input_path可以是文件,也可以是文件夹,output_path则必须是文件夹
注:对于路径里面有空格等情况,可以用""将其包裹起来,其他地方的字符串也是同理
options很多,本文并不会全部提及,而且我使用的CLI和你用的可能存在版本差异,如想深入研究可以进CLI的github界面看readme文件,或者AssetStudioCli -?来获取相关信息
- --game 大概是针对特定游戏做出来的设定,必须填,我一般用的--game Normal
- --types 允许筛选出特定类型,支持"A|B"这样筛选多个类型,常见类型比如MonoBehaviour(文本信息)、Texture2D(图片)、AudioClip(音乐)等,不过我用的这个CLI似乎有点毛病,types选择Texture2D后不能正常导出
- --names 通过资源文件的名字进行筛选,支持正则,不区分大小写
- --containers 通过container进行筛选,对于炉石来说,container即guid
注:看源代码会发现,names会在加载资源之前就做好判断,containers则是加载资源之后再进行移除,所以速度上来说names更快一些。
- --group_assets 有ByContainer BySource ByType None四种模式,默认ByType
- ByContainer导出到output/guid/文件,如果一个文件中有很多需要资源导出,推荐使用这个方案
- BySource会导出到output/xxx.unity3d_exports/CAB-xxx/文件,想要确定一个资源是出自哪个文件的时候可以用这个模式
- ByType会导出到output/type/文件,没啥用
- None则是导出到output/文件,需要注意,input为文件夹的时候,对于同名文件,CLI并不会做好保护,有时导出文件会被覆盖。
- map
- --map_name 将会导出到output/map_name[.map_type]
- --map_op 选择导出模式,我直接选择的ALL
- --map_type 导出格式,我这里选择的JSON
map文件最重要的性质是可以导出pathID,这是cli导出pathID的唯一方案。
三、一些前置概念
上文中不加说明的提到了一些概念,这里进行一下补充。
3.1 guid(global unique id)
一串32位的十六进制数,从命名来判断应该是全局唯一的,也即AssetStudio中的Container
在各种MonoBehaviour文件中常常这样出现,即一个「文件名:guid」:
"m_detailsTexture": "Default_Texture_DetailsFallback.tif:cc27f02843038f942ab18443d5b08305",
其中,文件名并不准确,基本可以当作没用。
在asset_manifest.unity3d的base_assets_catalog文件中,存在着这样的对应关系:
m_assets[n].guid->m_bundleNames[ m_assets[n].bundleId ]
guid -> 对应的unity3d文件名
对于一些国服特供的内容则需要在asset_manifest_zhcn.unity3d找到asset_catalog_locale_zhCN文件:
m_assets[n].baseGuid->m_assets[n].guid->m_bundleNames[ m_assets[n].bundleId ]
原本的guid -> 新的guid -> 对应的unity3d文件名
如此找到新的guid和对应文件。
3.2 pathid
一串int64的数字,应该不保证全局唯一性,不过数字够大也几乎可以认为是唯一的。
在各种MonoBehaviour文件中常常这样出现:
"m_Script": {"m_FileID": 0,"m_PathID": 485247164769422224},
其中FileID=0似乎代表就在同一个文件中,不过我通常没有搭理这个。
举例来说,如果上述的文本信息来自initial_bgs_global-c05a2d0e-prefab-0.unity3d中
那么想要找的东西总会在以下文件中,把下面的文件全拆出来,然后生成map文件,接着根据PathID即可找到对应的guid,group_assets 选择ByContainer模式,根据guid即可获取文件。
如果想找的是图片,那么图片总会出现在prefab/texture结尾的文件中,可以排除掉material之类的文件。
需要注意前面的部分,有些是_base_,有些是_bgs_,这几个文件都有可能,所以搜索相关文件时最好只搜含有c05a2d0e的文件
3.3 多语言
"m_collectionName": {"m_locValues": ["Classic","Klassisch","Clásico","Clásico","Classique","Classico","クラシック","기본","Klasyczne","Clássico","Классика","คลาสสิค","经典","經典"],"m_locId": 2824665},
在更早期的版本中,这里是"m_collectionName":{"m_currentLocaleValue":"经典"}
想要更好的兼容性可以考虑两者都兼顾一下。
四、一些示例
4.1 水印
"Records": [{"m_ID": 1001,"m_isCollectible": 1,"m_isCoreCardSet": 0,"m_legacyCardSetEvent": 164,"m_contentLaunchEvent": 307,"m_isFeaturedCardSet": 0,"m_standardEvent": 10000140,"m_craftableWhenWild": 0,"m_cardWatermarkTexture": "ICCIcon.tif:461f6ce4699d457429c62796700085ed","m_setFilterEvent": 203,"m_filterIconTexture": "Filter_Icons.tif:87d111b8cf28af64ca1a5d50ce478c31","m_filterIconOffsetX": 0.75,"m_filterIconOffsetY": 0.0,"m_releaseOrder": 1009,"m_isExpansionCardSet": 1},...]
其中的m_ID即set id,不过并没有特别好的方案获取到扩展包名字和set id的对应关系,就按下不表。
在dbf.unity3d->CARD_SET里面可以找到m_cardWatermarkTexture,其guid对应的即是水印了
遍历提取出所有的guid,找到对应的文件,会发现主要就集中在少数几个文件中,不妨将所有文件按照groups_assets="ByContainer"解压缩,然后遍历去找output/guid下面的文件,即可提取出来。
4.2 卡背
"Records": [{"m_ID": 0,"m_data1": 0,"m_source": 0,"m_enabled": 1,"m_sortCategory": 1,"m_sortOrder": 1,"m_prefabName": "Card_Back_Default.prefab:581a152092244499aa68d46c804051f1","m_name": {"m_locValues": [...,"怀旧经典","經典"],"m_locId": 20881},"m_description": {"m_locValues": [...,"经典款,总会给你带来最初的感觉。","經典設計,成就永恆。"],"m_locId": 20882},"m_isRandomCardBack": 0,"m_collectionManagerPurchaseProductId": 0},...,]
在dbf.unity3d->CARD_BACK可以找到上面这一段,有名字,有描述,这很不错,导出后可以改文件名,使其包含这些信息。
不过m_prefabName对应的文件就不是很舒服了,里面全是fileID和pathID:
{"m_GameObject": {"m_FileID": 0,"m_PathID": -742154249242800108},"m_Enabled": 1,"m_Script": {"m_FileID": 0,"m_PathID": -7755206838764419031},...,"m_CardBackTexture": {"m_FileID": 9,"m_PathID": 2050936170184355078},....}
当然,pathID只是麻烦了一点,并非不能解决。
首先先看看m_prefabName找出来的文件都放在哪儿的:
essential_base_global-prefab-0.unity3d
initial_base_global-3b2513bf-prefab-0.unity3d
initial_base_global-34be6ac6-prefab-0.unity3d
initial_base_global-34be6ac6-prefab-1.unity3d
essential_base_global-texture-0.unity3d
essential_base_global-texture-1.unity3d
essential_base_global-texture-2.unity3d
initial_base_global-3b2513bf-texture-0.unity3d
initial_base_global-34be6ac6-texture-0.unity3d
initial_base_global-34be6ac6-texture-1.unity3d
initial_base_global-34be6ac6-texture-2.unity3d
initial_base_global-34be6ac6-texture-3.unity3d
initial_base_global-34be6ac6-texture-4.unity3d
initial_base_global-34be6ac6-texture-5.unity3d
initial_base_global-34be6ac6-texture-6.unity3d
initial_base_global-34be6ac6-texture-7.unity3d
按照guid分类提取出来,并建立map文件。
由于整个过程是逐个文件进行的,map文件也会建立多个,最好是提取一个文件后立刻解析并删除对应的map文件。
之后根据map文件,由pathID找到对应guid,即可回归到类似水印的提取过程中去。
4.3 酒馆表情
"Records": [{"m_ID": 1,"m_enabled": 1,"m_rarityId": 2,"m_collectionShortName": {"m_locValues": [...,"欢乐的牛头人","快樂牛頭人"],"m_locId": 2833718},"m_description": {"m_locValues": [...,"选择你的英雄,在对战中发送表情。","在對戰中選擇你的英雄即可使用表情符號。"],"m_locId": 2853032},"m_animationPath": "Default_Emotes_2.controller:1cc6b65bfe961db489aac3784148c257","m_xOffset": 0.0,"m_zOffset": -0.079,"m_isDefault": 1,"m_isAnimating": 0,"m_borderType": 0},
同卡背一样,这一次也有名字和描述,挺不错的。
但是这次的m_animationPath没能导出任何东西。
使用GUI到对应的文件查看,发现preview和dump一样,看来是真的导出不了。
而且这里面也没有找到pathid的线索
不过,还是可以像之前卡背的思路一样,提取所有相关的prefab文件和texture文件,然后全部导出
这次根据m_animationPath前面的文件名来进行判断,比如Default_Emotes_2.png即是欢乐的牛头人。
不过后面那个文件名并不准确,有些时候会有乌龙,比如下图这里就多打了一个_final
又比如下图这里多打了一个空格1
这种情况只能自己灵活处理一下了,好在大部分时候都是对的
五、结语
相关代码链接:https://pan.baidu.com/s/1-N6Y4KpzK2Z7TENT2l-zSg?pwd=1234
想直接用可以运行bat。不过如果代码哪里写得有问题,我也懒得修,毕竟本来也只是写着练手玩的。