The Mystery of the Wizard's Castle REM Statement

The Mystery of the Wizard's Castle REM Statement

The Mystery of the REM Statement

在 1980 年的微型计算机游戏 The Wizard's Castle(为 Exidy Sorcerer 编写)中,第一行源代码显示为 10 REM"_(C2SLFF4。虽然它看起来像是一个拼写错误或乱码,但这一行实际上是一个伪装成 BASIC 注释的 Z80 机器码例程,用于模拟 RANDOMIZE 函数,为游戏的伪随机数生成器 (PRNG) 提供种子。

How the Machine Code was Hidden

在 Exidy Sorcerer 上,BASIC 解释器会对命令进行分词(tokenize),并将其作为链表存储在从地址 469 开始的 RAM 中。程序节点的结构包括下一个指针、行号、分词后的命令以及该行的文本。

对于程序的第一行:

  • 地址 469-470:下一个指针
  • 地址 471-472:行号 (10)
  • 地址 473:REM 标记 (0xC3)
  • 地址 474:REM 文本的开始

通过使用 USR() 函数——该函数通过在地址 260 和 261 处的 POKE 命令跳转到指定的机器码地址——游戏将 REM 语句的文本作为实际的 Z80 指令执行。具体来说,代码 POKE 260,218: POKE 261,1 告诉 USR() 函数跳转到地址 474,而这正是 REM 文本开始的地方。

Dissecting the Z80 Routine

最初尝试将 REM 文本作为 ASCII 进行反汇编的尝试失败了,因为原始杂志列表中的字符并非 ASCII 编码。当对磁带镜像中的实际字节进行分析时,该例程被揭示为:

LD A,R       ; Copy R register to accumulator
JR Z,-4      ; If result is 0, jump back to previous instruction
LD (F7FF),A  ; Copy accumulator into address F7FFh
RET          ; Return to BASIC

The PRNG Seeding Process

  1. Entropy Source: 该例程读取 R 寄存器,该寄存器在指令获取期间频繁递增,根据用户开始游戏的准确时间提供一个半随机值。
  2. Zero-Check: 它确保值不为零,以避免糟糕的 PRNG 种子。
  3. Memory Mapping: 该值存储在地址 0xF7FF,对应于内存映射屏幕文本的右下角字符位置。
  4. BASIC Retrieval: 然后 BASIC 代码使用 T = PEEK(-2049)(其中 -2049 是 0xF7FF)来读取此值,并使用 Q = RND(-(2*T+1)) 来为 RND() 函数设置种子。

由于 R 寄存器仅递增其低 7 位,这种技术限制了游戏只能生成 127 个可能的随机地牢。

The "Typing-In" Paradox

在 20 世纪 80 年代,程序员直接从杂志上输入代码。然而,如果将 The Wizard's Castle 中的 REM 语句作为字面 ASCII 字符输入,它是不可能起作用的。对于原始用户是如何实现这一点的,有两种可能的解释:

  1. Direct POKING: 作者可能输入了一个占位符 REM,然后使用 POKE 命令手动将非 ASCII 字节插入内存。
  2. Special Key Combinations: 研究表明,Exidy Sorcerer 拥有 Graphic+Shift 组合键,允许用户输入 0x80 到 0xFF 的标记和数值,这些数值会映射到列表中看到的字符。

Technical Insights from the Community

对该技术的分析突显了早期家用计算机的确定性特征,即固定的 RAM 位置和缺乏多处理能力使得 BASIC 和机器码能够无缝融合。

"Most type-in hybrid BASIC-machine language programs would have numerous DATA statements with opcodes and a loop to READ and POKE them... Commodore 64 BASIC's LOAD"WHATEVER",8 command would load a file of any size in the BASIC text area without validation." — @RiverCrochet

类似的技术也被用于其他平台,例如 Sinclair ZX81,在缺乏专用内存块或 DATA 语句的情况下,使用 REM 语句或其他被忽略的行来存储机器码。

Sources