试图撕掉这个游戏的精灵

逆向工程 二进制 二进制格式 二进制编辑
2021-06-16 11:12:33

我目前正在尝试从名为 Cookie Shop - Create Your Dream Shop 的 Nintendo DS 游戏中提取精灵,但它们都在 _LZ.bin 文件中。

当我解压缩它的压缩包时,我可以看到很多文件标题为 spr.bin 和 pal.bin 图1:_LZ.bin解压后的文件列表

我知道这些文件是精灵及其相应的调色板,但我没有打开它们的想法。

我尝试使用 CrystalTile2,但结果出来的都是乱码,调色板和精灵都是乱码。

这里分别是调色板和精灵的十六进制。 图 2:调色板十六进制 图 3(1):精灵十六进制 图 3(2):精灵十六进制(续)

现在这就是 NO$GBA 中精灵和调色板的样子。 图 4:输出精灵 图 5:输出调色板

调色台 OBP C(从左到右,RGB 格式) 001010 0F0604 130B09 191311 1D1816 1A0C10 1C1013 1F1518 1F1C1D 1F1F1F 0F18080F0F0A0F0A0F0A0F0D0A10F0A10F0A10F10F10F10D0F10F10D0

此外,最大颜色十六进制是 1F,而不是 FF。如果我假设,001010 用作透明度过滤器。

我还发现了游戏中从未使用过的精灵,我去更改角色的名称,但在我按下继续后,它最终冻结了游戏。

是否可以提取精灵?因为我想用它来创建一个精灵表,以及使用这些角色创建一个故事。

2个回答

两个文件都以 4 个字节的大数字开头;显然是后面数据的总大小。我忽略它,因为文件大小似乎是固定的。(此外,对于图像,其他重要数据,其宽度和高度,不存储在文件中。)

调色板

调色板是每通道 5 位的 BGR,最高位未使用。这两个字节以小端顺序存储。因此,转换为真彩色 RGB 是一个位移问题;你最终得到 16 个 RGB 颜色三元组。

图片

图像存储在 16 x 16 块中,每块 8 x 8 像素,每两个连续的像素被打包成一个字节,右像素在前。每个像素值的范围为 0..15,并立即映射到调色板。

要解压单个 8x8 块,只需要

for y in range(8):
    for x in range(4):
        print (img[4*y+x]])

这产生了一系列 2 像素数据。我发现在下面将每两个半字节立即解压缩为 2 个独立的像素更方便;然后就是在右 x 和 y 轴上循环以将整个图像重新组合成一个连贯的、线性的、128x128 像素位图。

下面的代码然后将每个像素的 RGB 值存储在真彩色 PNG 图像中;或者,您也可以将其保存为调色板 PNG 图像(或您喜欢的任何其他图像格式)。

Python 3.x 代码

此代码需要pypng. 将路径的固定部分调整为您的文件夹结构——它应该以斜线结尾。另存为cookie2png.py并从命令行调用

python cookie2png.py Rose/bu_strawberry_LZ.bin\bu_strawberry_anger

即,去掉部分_pal.bin_spr.bin这样脚本就可以自己找到它们。

import sys,png
from struct import unpack

path = "/Sprites/Character files/"
# base = 'bu_strawberry2'
base = sys.argv[1]

with open (path+base+'_pal.bin', 'rb') as p:
    pal = p.read()

pal = unpack('<I16H', pal)[1:]
pal = [bin(p)[2:].zfill(15) for p in pal]
rgb = [(int(p[10:15]+p[10:13],2),int(p[5:10]+p[5:8],2),int(p[0:5]+p[0:3],2)) for p in pal]
print ('rgb palette', *['%02x%02x%02x' % (r,g,b) for r,g,b in rgb])

with open (path+base+'_spr.bin', 'rb') as i:
    img = i.read()

# Strip header
img = img[4:]
# Convert nibbles to bytes
img = [[b & 0x0f,b >> 4] for b in img]
# Unpack list
img = [b for a in img for b in a]

# Linearize image
target = []
for y in range(16):
    for yy in range(8):
        for x in range(16):
            for xx in range(8):
                target.append(rgb[img[16*64*y+64*x+8*yy+xx]])

# Convert from palettized into True color
target = [color for rgb in target for color in rgb]

# Split into rows, required by pypng
target = [target[i:i + 3*128] for i in range(0, len(target), 3*128)]

w = png.Writer(128, 128, greyscale=False, bitdepth=8)
with open(path+base+'.png', 'wb') as f:
    w.write(f, target)

结果正如预期的那样。这是您的bu_strawberry,以及一些有趣的其他人:

草莓 苦涩 bu_crunchy bu_maccha bu_opera

这些.bin文件以字节为单位保存精灵的数据。您可以轻松转储每个.bin文件并将其解释为精灵数据。

回到我编写 NES 模拟器时,它处理精灵的方式非常过时。它有一个墨盒中完整精灵列表的“左侧”,它位于0x0000-0x1000,然后是一个“右侧”,分别位于0x1000-0x1FFF

在范围内的示例0x0000-0x0010分别位于某个精灵的左侧(每边长 16 字节)。您可以将该部分中的这些字节与0x1000-0x1010精灵的右侧组合起来

无论如何,足够的题外话。我只是认为由于结构的相似性,这对您来说会很有趣。

您需要提取每个.bin文件中的每个字节并将每个字节存储在一个单独的std::uint8_t数组中。然后你可以使用类似 SFML 的东西将像素渲染为原始数据。我过去常常sf::Texture::loadFromMemory这样做。

如果您想将纹理原始撕裂为图像文件。您必须将每个字节中的字节转换.bin为纹理,然后将该纹理作为.bmp文件输出