EVE 游戏文件分析(一)

cutter / dosbox / klogg / imhex / eve

有段时间没开新的分析系列了,这个是我最近想到的一个游戏,EVE burst error。

前言

这个游戏说实在的我没玩过,因为在我的年代,没有中文版, 而游戏令人称道的地方是剧情,那这个就很尴尬了,我大学念完也只会五十音。 而且这个系列是从第二作开始有中文游戏的,这就更加尴尬。看相关介绍, 这个系列被一遍又一遍移植到各种平台,让我更加确认,这个游戏应该是不错。 而且很多资料会提到,原作者「剣乃 ゆきひろ」在开发后就从 C‘sware 离职了, 后续所有的移植、改编,都和原作者无关。这反而让我对原作的兴趣越来越大。

然后似乎很多玩家对「夜行侦探」这个译名很有意见,我还没有玩过,所以无法做出判断, 不过听人劝,吃饱饭,而且读英文对我也不是困难的事情,我就还称之为 EVE。

之前几个系列,收获了一些经验,让我稍微有些膨胀,我觉得,如果搞懂游戏的资源, 那么基本上,我应该可以借助翻译软件看懂故事,更进一步,也许能做一个简陋的汉化版, 那我们就以此为小目标好了,这个目标说实在的对我着实不小。

有了兴趣就去做,这是我的作风,所以我研究了一段时间, 之前的 llm.rs 系列 可能要坑一段时间了,别太担心,我应该会填平的, 不过,想看这个系列的朋友,也不用太开心,我写这篇文,其实是因为, 这个坑我也只是挖开而已,先总结下我目前的成果。后面因为有读者想看另一个游戏, 我可能会开始搞那个,不过那个会不会也变成坑,可能也说不准了。

工具

现在我们不像大富翁那会儿是个没头苍蝇了,捡几个顺手的工具吧:

  • dosbox-x 2024.03.01:我最习惯用的 dosbox,很方便调出 debugger,而且支援 pc98。
  • klogg 22.06.0.1289:这次的 cpu log 不到 10G,不用它的话,请告诉我还有哪个可以。
  • cutter 2.3.4:这个略微有些打我脸,我之前觉得,ghidra 最好用,虽然 UI 很别扭,不过不晓得是不是历史原因,或者是我还不太会用 ghidra,ghidra 识别的效果并没有好过 cutter,而 cutter 相比 ghidra 有没有那么难用,所以这次我的主力是 cutter。
  • ImHex 1.33.2:我们用这个查看游戏文件的内容,因为它可以用结构体标注文件内容。

AGS

因为 EVE 是使用 EVE.BAT 启动,这里面实际打开的程序是 AGS.EXE,这可能是个游戏引擎, 引擎可能是borland c++ 写的,没有找到什么资料。可能是 FAIRYTALECocktail Soft 常用的 AGS(Advanced Game Script Interpreter),不能确定。

启动游戏后的文件打开顺序

这个使用 dosbox-x,启动游戏前下断点 bpint 21 3d,进入断点时 d ds dx, 即可看到文件名:

  1. EVE.EFC
  2. rogo.gdt (后面是 C’s ware 的 logo)
  3. system.dat
  4. menu2.gdt (推测是新游戏菜单)
  5. menu5.gdt (推测是选角菜单)
  6. a01.cc
  7. system.dat
  8. eve_29.M(后面是 9* 12/3)
  9. waku.gdt
  10. 001.gdt(断点时还没有背景,只有框)
  11. eve_21.M(第一句话后)
  12. 001.gdt (一段剧情后)
  13. 001.gdt

日本人的英语我们就暂且不吐槽了,我们第一个要看的文件,大概率是 rogo.gdt。 因为打开了多个 gdt 文件,而且第一个文件不长,一般第一个文件也都是设定, 应该没装什么值得注意的东西。

GDT 文件

老方法,bpint 21 3dd ds dx 查看文件名,一直到 rogo.gdt 文件打开, logl 1ffffff 记日志。

说起来,我看名字感觉可能是脚本,因为游戏中公司的 logo 有动画, 而且怎么看都会想起来 godot engine。不过现在看来,大概率是图形文件:

前四字节估计是头,然后是文件长度,后面花花绿绿的是几个单独读出来的参数。 然后跳过了 24(18h)个字节,接下来记录了四个长度, 从 30h 开始,是文件正式的内容。

这四个长度,很容易让我想到之前的四平面, 那么我猜这是个 16 色的游戏,只是还不知道调色板在哪里。

而读取才是刚刚开始,程序保存了一个 256 长度的函数表:

index(hex) count(hex) bx cutter offset
0 20 3b34 7bb4
20 20 3b55 7BD5
40 20 3d4c 7DCC
60 20 3d6a 7dea
80 20 3d88 7E08
a0 10 3da6 7E26
b0 10 3dce 7e4e
c0 10 3df6 7e76
d0 10 3e1e 7e9e
e0 10 3e46 7ec6
f0 1 3b18 7b98
f1 8 3b0c 7b8c
f9 1 3b24 7ba4
fa 1 3b77 7bf7
fb 1 3b86 7C06
fc 1 3be1 7C61
fd 1 3c11 7C91
fe 1 3cb1 7D31
ff 1 3e6d 7EED

bx 对应了程序跳转的位置,我举个例子,可能会比较好理解, 比如如果程序读到了 10h,就会执行 3b34h 处的程序, 读到了 e9h,就会去执行 3b24h 处的程序。

我为什么又写了一列 cutter offset, 是因为 dosbox-x 印出来的日志中的 segment:offset,和 cutter 有差,比如在 log 中:

1427:00000137  call 00000166 ($+2c)                                    E8 2C 00              EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F70 DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:00000166  cmp  byte cs:[00DE],00          cs:[00DE]=8C14          2E 80 3E DE 00 00     EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:0000016C  jne  0000016F ($+1)             (down)                  75 01                 EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010
1427:0000016F  test word cs:[00A4],4000        cs:[00A4]=CF00          2E F7 06 A4 00 00 40  EAX:00000028 EBX:00000DC6 ECX:00000000 EDX:0000434E ESI:00000001 EDI:000000A0 EBP:00000F78 ESP:00000F6E DS:1427 ES:1427 FS:0000 GS:0000 SS:218C CF:0 ZF:0 SF:0 OF:0 AF:0 PF:1 IF:1 TF:0 VM:0 FLG:00007216 CR0:00000010

而在 cutter 中,这段是这样:

0000:41e6      2e803ede0000           cmp     byte cs:[0xde], 0 ; compare two operands
0000:41ec      7501                   jne     0x41ef ; jump short if not equal/not zero (zf=0)
0000:41ee      c3                     ret ; return from subroutine. pop 4 bytes from esp and jump there.
0000:41ef      2ef706a4000040         test    word cs:[0xa4], 0x4000 ; set eflags after comparing two registers (AF, CF, OF, PF, SF, ZF)

0166h 行对应的是 cutter 的 41e6h 行,我们忽略前面的段位置 1427h, 两者偏差 4080h,所以我要手工算一下,才能在 cutter 中找到这段代码, 很头疼,不知道两边怎么对齐。

休息一下

看上去,要写完这 19 个函数,我们起码才能读完一个平面,简单过了一遍, 前面几个比较好写,后面几个计数为 1 的,反而是比较复杂, 不过这就是我目前研究到的内容了,下次我们再完成这 19 个函数吧。