与现代计算机不同,从光谱上看,文件系统的概念并非如此。 这意味着从每种类型的媒体下载都需要单独的实现,并且在大多数情况下,不能仅将程序从磁带复制到磁盘。 如果程序加载器是用BASIC编写的,则可以通过相当简单的修订使其适用于TR-DOS。 但是,由于在许多游戏中(无论是品牌游戏还是黑客游戏),装载程序都是用机器代码编写的,有时包含复制保护,这一事实使情况变得复杂。

尽管存在一个“魔术按钮”,可以简单地完全转储计算机内存并可以将程序以某种方式保存到软盘中,但专家们认为,可以在保留原始启动映像和其他属性的同时创建游戏的磁盘版本。
在本文中,我将告诉您如何在Pac-Man游戏的示例(即原始的Pac-Man.tzx图像)上进行这种修改。
工具
尽管过去所有这些工作都是直接在ZX Spectrum上完成的(没有其他选项),但我将使用模拟器和命令行实用程序来修改游戏。 主要原因是,尤其是刚开始时,适应过程包含大量的尝试和错误,并且如果它是自动化的,则过程会轻松得多。 所有相同的操作都可以直接在Spectrum上完成。
在第一部分中,我们将使用以下工具:
- 保险丝仿真器,用于调试和测试。
- SkoolKit可拆卸。
在引导加载程序中禁用启动
由于下载的图像和数据文件没有标题块(带有文件名和类型的17个字节),因此这意味着装入程序是用机器代码编写的。 您需要找到这些代码的位置以及从哪个地址启动它们。
有几种查看引导程序代码的方法:
最简单的方法是开始下载程序,等待引导加载程序启动,然后按Space
键将其停止。 在许多情况下,这是可行的,但在Pacman的情况下(与其他许多情况一样),这会导致重置。
下一种方法是使用MERGE ""
而不是LOAD ""
加载程序。 与LOAD
不同, MERGE
忽略程序自动运行。 如果是吃豆人,则通过MERGE
引导会导致计算机死机,并带有典型的左屏幕偏移。 这是由于MERGE
而不是逐行执行程序,而是尝试完整地解析它并将其与已加载的程序合并。 但是,如果程序的机器代码块违反了程序的语法,则将导致崩溃。
如果您不愿意动脑子,可以将磁带图像从TZX转换为TAP,并使用Fuse随附的listbasic
实用程序:
$ tzx2tap Pac-Man.tzx $ listbasic Pac-Man.tap 1 RANDOMIZE USR (PEEK 23635+256*PEEK 23636+91)
地址23635
( $5C53
)对应于PROG
系统变量,该变量包含BASIC区域的起始地址。 因此,引导加载程序的入口点相对于BASIC区域偏移了91个字节。
在Desativando BASIC程序自动执行中介绍了查看引导加载程序的另一种方法。 在Fuse调试器中,您需要设置一个断点br 2053
,加载程序,并且当下载完成并且代码执行停止时,执行set 23619 128
。 这将阻止程序启动,并允许您退出BASIC。
引导程序拆卸
知道入口点相对于BASIC区域的偏移,您可以计算其绝对地址。 在没有加载TR-DOS的ZX Spectrum 48K的情况下,BASIC区域从23755
( $5CCB
)开始。 因此,引导加载程序将从23755 + 91 = 23846
( $5D26
)开始。
首先,只需在起始地址处设置一个断点,然后查看机器代码即可。 在Fuse中,您可以制作br 23846
并开始下载程序。 引导加载程序开始执行后,仿真器将停止:

在加载程序非常简单的情况下,只需查看中间面板中的反汇编代码并了解正在加载的内容。 通常,无头文件的下载代码如下所示:
LD IX, $8000 ; LD DE, $4000 ; LD A, $FF ; CALL $0556 ; LD-BYTES JP $8000 ;
在代码执行更复杂的情况下,您需要了解步骤并做笔记。 SkoolKit实用程序套件非常适合于此。 如果您设定了目标,则可以借助它的帮助将游戏解析为最后一个螺丝(消息,精灵,声音)。 文档中详细介绍了如何完成此操作。
简而言之,请执行以下操作:
- 使用
tap2sna.py
或仿真器功能制作Pac-Man.z80
计算机内存的快照。 - 创建一个
Pac-Man.ctl
控制文件,其中包含用于拆卸的初始说明:
i 16384 Ignore for now c $5D26 Loader
- 运行反汇编:
sna2skool.py -H -c Pac-Man.ctl Pac-Man.z80 > Pac-Man.skool
。 - 在学习代码时,请向控制文件中添加新的说明和注释。
- 重复直到完全开悟。
结果,在第一遍之后,我们得到以下信息(我的评论,地址被省略):
ORG $5D26 ; 23846, ; DI IM 1 ; LD D, IYh ; LD E, IYl ; LD B, $25 ; EX DE, HL ; LD DE, $0019 ; ADD HL, DE ; HL $5C53 ( PROG) LD E, (HL) ; PROG DE IX INC HL ; LD D, (HL) ; LD IXh, D ; LD IXl, E ; LD A, (IX+$7F) ; ( $7F- ; PROG) LD HL, $0035 ; ($35 PROG) ADD HL, DE ; PUSH HL ; XOR (HL) ; LD (HL), A ; INC HL ; DJNZ $5D43 ; AND (HL) ; RET NZ ; ; DEFB $77
引导加载程序解密
真正重要的是解密后的引导加载程序位于PROG + $35
。 这意味着,如果我们在br 23808
上放置一个断点,那么此时解密将完成,我们将看到解密的引导程序:

该程序已经与上述典型案例更加相似。 将值$4000
( 16384
)加载到寄存器IX
和DE
,完成其他操作,并将控制权转移到$055A
的ROM例程(这比LD-BYTES
的标准入口点低几个字节)。 看来这种方法实现了某种复制保护,因为 标准程序不会加载此文件,有些复制者不理解该文件。
程序入口点
仍然需要弄清楚加载后如何调用该程序。 在此使用的不是正常的CALL LD-BYTES
和JP
,而是LD SP, XXXX
和JP LD-BYTES
。 第一个(通常)选项的工作方式如下:
CALL
将软件计数器( PC
)的当前值压入堆栈。- 控制权传递给被调用的例程。
- 从子例程(
RET
)返回时,该值将从堆栈中删除,并转换为调用程序。
为什么在这里做不同的事情? 事实是,Pac-Man与ZX Spectrum 16K兼容,并且绝对占据了所有RAM(请参见上面的文件大小)。 因此,在加载时,无论位于何处,程序都会同时覆盖自身和堆栈。 如果我们想使用堆栈从ROM切换到引导加载程序,然后通过JP
调用下载的程序,则在下载完成时, JP
所在的那一刻将没有内存地址,也没有指令本身。
而是,堆栈指针移到存储区,在加载后,该程序的入口点的地址就会出现,处理器(不会注意到欺骗)会通过新的指针将其从堆栈中删除,并移至指定的地址。
完整的反汇编结果可以在github上的项目存储库中查看。
合计
通过研究引导加载程序,我们发现了以下内容:
- 长度为16384字节的无头文件在16384(在屏幕区域中下载,通常在下载过程中很明显)下载。
- 下载结束时,堆栈指针位于
$5D7C
,控制权已转移到该位置。
在以下各部分中,我将讨论如何准备要写入磁盘的文件以及如何在汇编器中编写整体文件加载器。
相关链接:
- 个人档案“ TRUB Spectrumist” 。
- 反向工程ZX Spectrum(Z80)游戏 。
- Beta版的适应游戏48 。