【拆包】如何拆卡牌资源以外的各类资源
9月29日
点火144 回复45
本文为作者原创内容,未经作者本人和营地同意不得转载

这几天排炉石太慢了,所以趁着间隙研究了一下拆包

零、准备工作

  • 下载AssetStudio.CLI或者类似的可以用命令行调用的拆包工具
  • 一门可以调用命令行的语言(最好支持多线程)

直接用AssetStudio的GUI,不用任何编程语言也可以,但是纯手动的走复杂的拆包流程会很痛苦。

一、前情提要

如何通过拆包定向查找素材? ——炉石传说拆包教学(1)
THorougH 2023-07-17
关于炉石拆包的流程和经验分享
穿山辛 2024-05-06

由于是打牌间隙写得,部分内容会写得比较糙,看不懂的时候可以回去翻翻,特别是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
然后找出所有对应的texture文件:
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。不过如果代码哪里写得有问题,我也懒得修,毕竟本来也只是写着练手玩的。

正巧排进酒馆了,就此搁笔。
 
 
 
 
打赏文章
全部评论 45条
按时间排序

还没有评论