公司要立项做海贼王项目了, 这么香的IP听着就有点兴奋。前期就拿别人家的游戏资源来研究了,所以这就是为什么破解《航海王-燃烧之血》的缘由了。几天下来,感觉破解游戏这事儿,还真得需要点儿黑客的潜质。在一堆二进制文件中找出规律,实在不是一件容易的事儿。

解压缩:

进入游戏资源目录, 看一看到一堆.cpk文件, 我们可以使用CPK File Builder解压成scz压缩格式的文件。

对于一个角色来说,你会得到三个文件*.npk.scz, *.npki.scz, *.npkv.scz三种压缩格式文件。使用KMS配合着cmp_scz.bms可以解压出对于的.npk, .npki, .npkv三种格式的文件。

.npk存储着如mesh, texture的索引
.npki 储存真正的vertex buffer & indices buffer
.npkv 储存texture buffer

有一种文件储存格式SDR, 其中npk是sdr的集合。其中:

.npk 是.sdr文件的集合
.npki 是.sdri的文件集合
.npkv 是.sdrv文件集合

上面提到的工具和下面使用到的脚本都可以在我的github工程中提取到。

提取Mesh

在上一步骤中拿到npk文件之后, 我们拖到HEXEDIT工具中查看。

npk解析

npk使用的索引sdr格式标记的, 关于这些标记可以在他们官网的查询到。 下面列出部分使用到的标记含义:

$TXR (纹理) texure
这包含有关纹理资源的信息。具有RSI子条目,其中包含大量与纹理分辨率、格式和mipmap相关的信息。

$SCN (场景) scene
似乎与三维模型有关。具有RSI子条目。

$MSH (网格)mesh
包含三维模型的网格信息。具有RSI子条目

$VTX (顶点)vertex
包含三维模型的顶点信息。具有RSI子条目。

$MAT (材质)material
包含三维模型的材质信息。具有RSI子条目

$RSI (资源信息)resources info
似乎包含与它前面的任何条目相关的附加数据。它的布局和结构似乎会根据所包含的数据类型而改变。

$CT0 (内容终止符)content terminal
这标志着SRD文件的结束,并且也出现在大多数具有RSI子条目的其他条目类型之后。
它是一个特殊的条目类型,除了条目类型字符串之外还有一个完全空的标题(零填充)。

SRD文件中的所有条目都与16个字节对齐。这意味着,如果一个条目只有10个字节长,那么在下一个条目之前会有额外的6个字节的填充。

如果一个条目的长度是125字节,那么随后将有3个字节的填充。条目和子条目将接受此填充。使用hexedit打开npk文件, 可以清楚看到上述的标记。

上述标记也只在npk文件中, npkv和npki记得都是buffer, 而不是标记。

npk解析

如图所示,在$VTX之后的字符*.npki 20个字节之后表示的是子网格顶点个数6D-00-00-00(绿色部分), 反向字节排序0x006D = 109即十进制是109。

在$RSI之后的28个字节,我们有顶点vertex起始地址60-D7-01,并且在它下面,在13个字节之后,表示indices索引起始地址E0-FC-01, 十六进制表示即为0x1fce0。

在其一个字节之后我们有面部索引块长度38 04,即0x0438除以2,我们得到indices count为0x21c = 540(转换为十进制)。

要获取UV起始地址,我们必须将蓝色字节添加到顶点起始地址,它们并不总是在$VTX部分中的相同位置,但在顶点块长度之后它们前面是 20-00-00-00,

而是在$VTX部分的30-00-00-00的12字节之后:0x1d760 + 0x2210 UV起始地址。

对于FVFsize,我们将顶点块长度除以顶点计数: 0x1470 / 0x6d = 0x30 = 48(转换为十进制)。

要获得UVB的大小,我们需要从顶点地址后的紫色1字节中的值中减去蓝色值,并将差值除以顶点计数:(0x2578 - 0x2210)/ 0x6d = 0x08 = 8(转换为十进制)。

注意这个值:有些是8和一些16, 一定要跟着公式来, 不要直接写8。

Vertices start address: 0x1d760				
vertices count: 0x6d = 109 // 十进制
Face indices start address: 0x1fce0			
FI count: 0x438/2 = 0x21c = 540 // 十进制
FVFsize: 0x1470 / 0x6d = 0x30 = 48 // 十进制
UV start address: 0x1d760 + 0x2210 = 0x1f970		
Size UVB: 0x2578  0x2210 / 0x6d = 8

解析之后, 把相关的参数输入到hex2obj之后,点击mesh按钮,可以得到预览到mesh

在hex2obj,你可以得到一个test.obj,这个就是生成的mesh了。

如果你讨厌复杂的进制转换和公式计算(长时间看一堆二进制,实在让人眼晕), 你可以使用github工程中parse_model.py脚本,自动的把所有的参数都输出在一个文件中就可以了。

# 进入你的工作目录
cd work_dir
# 给py传入参数xx是npk的文件名
python parse_model.py xx.npk > ch001.txt

在你的终端运行上述脚本(需要本地有python环境), 就会在本地生成一个ch001.txt文本文件, 这里会打印出所有的算好的参数, 你直接填到hex2obj中去就好了。脚本的逻辑就是遍历整个二进制文件,找出所有配对的VTX和CT0, 然后在两者之间找到vertex和indices的起始地址并且计算出相关的数量和uv相关的参数, 直接输出就可以了。

生成的ch001.txt文本格式如下:

mesh count: 107 

********* 0 **********
start: 2e60, end: 2f90
vertex addr: 000
face   addr: 1abe0
face   cnt: 5568
vertex cnt: 1141
FVF   addr: 16490
size   UVB: 16

********* 1 **********
start: 2fa0, end: 30c0
vertex addr: 1d760
face   addr: 1fce0
face   cnt: 540
vertex cnt: 109
FVF   addr: 1f970
size   UVB: 8

********* 2 **********
start: 30d0, end: 31f0
vertex addr: 2120
face   addr: 226a0
face   cnt: 540
vertex cnt: 109
FVF   addr: 22330
size   UVB: 8

提取Texture

和提取Mesh类似, 你需要知道txture在npk中的索引,并且找到相关的参数。

在npk中,texture是使用标志是$TXR.

中间嵌套标记符$RSI

如上所示, 距离$TXR标志16字节的地方始终是四个字节(01-00-00-00), 再偏移6个字节就是图片显示的长和宽。这里长和宽使用的小端的十六进制数字表示。

接着蓝色的部分表示图片的压缩格式,不同的图片格式对应不同的解析方式和压缩率。下面Python脚本就展示了不同format对应的压缩格式和压缩率:

# Raw formats.
if fmt in [0x01, 0x02, 0x05, 0x1A]:
  decoder = "raw"

  # 32-bit BGRA
  if fmt == 0x01:
    bytespp = 4
  
  # 16-bit BGR
  elif fmt == 0x02:
    bytespp = 2
  
  # 16-bit BGRA
  elif fmt == 0x05:
    bytespp = 2
  
  # 8-bit indexed
  elif fmt == 0x1A:
    bytespp = 4

elif fmt in [0x0F, 0x11, 0x14, 0x16, 0x1C]:
  decoder = "bcn"
  
  # DXT1
  if fmt == 0x0F:
    bytespp = 8
  
  # DXT5
  elif fmt == 0x11:
    bytespp = 16
  
  # BC5
  elif fmt == 0x14:
    bytespp = 16
  
  # BC4
  elif fmt == 0x16:
    bytespp = 8
  
  # BC7 / DX10
  elif fmt == 0x1C:
    bytespp = 16

在$RSI标志16字节之后始终使用固定标识(06-05)起始, 接着3个字节之后,即图中青色标出的部分表示mipmap的个数。接着每一行对应的是每个mipmap在.npkv文件中的起始地址和raw texture buffer的长度。

在所有mipmap之后就是图片的名字了, 比如图中所示的黄色部分标记图片名就是beard_c.tga。

基于上面整理的信息, 我们写了一个python脚本,用来提取游戏中的图片。 这里用到了pillow, 是python中一个强大的图形处理库, 对于不了解的可以看我之前的一篇文章Python Pillow做一个强大的图形工具

python传入前两个参数即npk和npkv文件路径, 第三个参数是输出路径

# 进入你的工作目录
cd work_dir
# 导出
python parse_texture.py \
 /Users/penghuailiang/Documents/projects/tools/character_000.npk \
 /Users/penghuailiang/Documents/projects/tools/character_000.npkv \
 /Users/penghuailiang/Documents/projects/tools/out/ 

运行脚本之后, 在指定的目录生成的图片如下:

参考:

[1] SRD文件介绍以及标记
[2] One Piece Burning Blood models .npk | .npki
[3] One Piece Burning Blood 3d Models
[4] Danganronpa-Tools