我想为 NES提取Shadowgate 中的字符串。我file
在图像上运行,然后strings
没有运气。我找到了一些关于 NES 卡带文件格式的信息。文档提到了“名称表”的使用。有没有办法反汇编这个文件并查看字符串?我试过
strings -e -T rom.bin
我也试过:
objdump -i rom.bin
该处理器看起来是 M6502 处理器,并且有可用的 Windows 反汇编程序。
我想为 NES提取Shadowgate 中的字符串。我file
在图像上运行,然后strings
没有运气。我找到了一些关于 NES 卡带文件格式的信息。文档提到了“名称表”的使用。有没有办法反汇编这个文件并查看字符串?我试过
strings -e -T rom.bin
我也试过:
objdump -i rom.bin
该处理器看起来是 M6502 处理器,并且有可用的 Windows 反汇编程序。
有多种方法可以在未知文件中定位字符串。您已经尝试过的一个:strings
。这将查找纯文本、未编码的 ASCII 文本:
字符串在二进制文件中查找 ASCII 字符串 [..] 字符串是 4 个(默认)或更多打印字符的任何序列,以换行符或 null 结尾。(
man strings
)
但是,这种幼稚的方法可能会失败的原因有很多。首先:并非世界上的每个文本都是 ASCII 编码的。事实上,使用二进制编辑器检查您的文件,您可以在偏移量 0x20010 处找到游戏中使用的字体的图形图像——8x16 像素的单色位图。如果您假设第一个字符(a '0')编号为零,则 'A' 位于位置 31 —— 绝对不是ASCII 文本。当然,文本绘制例程可能知道这一点,并根据此方案重新排序要打印的字符;但是,考虑到这个特定游戏的年代(1987 年),文本数据更有可能根据这种奇怪的编码进行存储。
然而,这本身应该不是问题。
谷歌搜索这个游戏提供了一些屏幕截图,你可以阅读一些可能出现的文本——“你记得的最后一件事”、“你的历史任务的词”等等——值得注意的一点是所有文本似乎都是大写的。
这有什么帮助?好吧,如果编码是远程“正常”,则“A”的字符代码可能是任何东西,但您可以安全地假设它code+1
是“B”、code+2
“C”等等。现在让我们假设文本“THE”出现在任何地方(安全假设)。从数据的第一个字节中减去“T”并注意差异。从下一个字节中减去这个差异并测试它是否是'H';如果是,请在下一个字节上测试相同的差异,看看它是否是“E”。三次是一种魅力(在这种情况下),并且由于字符串“THE”应该非常频繁地出现,您应该会看到许多具有相同差异的点击。然后你可以编写一个自定义例程来“转换”所有 数据字节根据此方案,并再次检查是否找到有用的字符串。
这对 Shadowgate 不起作用。
另一种选择是文本被故意混淆。一个流行的(因为fast)选项是将文本与常量进行异或。这样,当用十六进制查看器检查时,文本不容易看到,但可以很容易地显示出来。所以我做了和上面一样的,只是现在用异或运算而不是常数减法。它也没有用。
下一篇:鉴于 SG 是一个文本冒险,作者试图将尽可能多的文本塞进糟糕的 NES 内存中是理所当然的。在这样一个老游戏中找到真实世界的压缩(ZIP、LZW)是相当罕见的,压缩方案往往非常简单。毕竟,不仅 RAM 有限,CPU 速度也有限。如果每个字符都存储为 5 位序列会怎样?这将节省大量内存——每 8 个文本字符可以存储在 5 个字节中,压缩率为 62.5%。
为什么是“5位”?我们在这里谈论的是英文文本,加上一些标点符号,加上(可能)数字“0”到“9”。字母表本身有 26 个字符长,另外还有 6 个值用于其他任何东西——嘿,其中一个额外的代码可能意味着“下一个字符使用所有 8 位”。
根据我的测试字符串(在密码学中称为“crib”)每 5 位检查一次,我发现以下内容:
candidate at 0570, delta is 41 H_A\`THE[TROLL[
candidate at 0670, delta is 41 _H\`ATHE[TROLL[
candidate at 0878, delta is 41 `AN`QTHE[TROLL[
candidate at 09E3, delta is 41 FROM^THE[DEPTHS
candidate at 1380, delta is 41 E[OF[THEM_A[THI
candidate at 13F0, delta is 41 ]NX_ATHE[WORDS[
candidate at 14C0, delta is 41 PD^`QTHE[FLAME[
candidate at 1BBA, delta is 41 UDGE[THEM[BY_A_
candidate at 22E0, delta is 41 ]BX_ATHE[GLASS[
candidate at 230D, delta is 41 ID_A[THE^SIGN[O
candidate at 2375, delta is 41 S[ON[THEM_A\`AB
candidate at 2390, delta is 41 LLOW[THE^VISCOU
candidate at 2528, delta is 41 F]PX_THE[STONE[
candidate at 25E6, delta is 36 @CP=KTHE@?OFHBS
candidate at 27F8, delta is 41 YDP]ATHE[BARK[O
candidate at 2B1E, delta is 41 D_H\]THE[WATER[
.. 还有很多。您可以看到它有效,因为我还在测试字符串前后解码了几个字节,这也可以识别为“某物”。显示的“delta”是五位代码 (0..31) 和 ASCII 之间的差异,您可以看到它41
适用于大多数字符串(唯一的例外似乎是误报)。
为了确保这是正确的,我尝试了另一个婴儿床:(KING
这是一个幻想游戏):
candidate at 0661, delta is 41 Y[LOOKING[SPEAR
candidate at 23B4, delta is 41 [DRINKING[TAR_A
candidate at 2B5D, delta is 41 [DRINKING_A\`AA
candidate at 8E1B, delta is 43 \XVFDKINGDHEEVE
candidate at 146F9, delta is 34 JL54HKING48A4:D
这似乎也行得通:不是我期待的“国王”,但是 delta 为 41 的结果还是不错的,另一个 delta 是随机的。
但是以这种方式找到有用的字符串是相当幸运的,因为当然不能保证从第一个字节开始每读取 5 位就可以显示任何有用的东西。在显示的字符串之间可能还有很多其他字符串,但它们并不是从 5*8 位的倍数开始的。假设这是在位置#0没有文字,但有是在位置#1,然后我看不出它:
bits for byte 0,1
0000.0000 TTTT.T000 (T = Text character bits)
---
reading 1st 5 bits
1111.1??? ????.????
2nd 5 bits -- the wrong ones!
.... .111 11??.????
要正确解码所有字符串,您现在应采用以下路线:
[
似乎是一个简单的空间,但THEM_A\'AB
需要仔细观察)。