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),並將其作為鏈結串列 (linked list) 儲存在從位址 469 開始的 RAM 中。程式節點的結構包括下一個指標 (next pointer)、行號、標記化的指令以及該行的文本。

對於程式的第一行:

  • 位址 469-470:下一個指標
  • 位址 471-472:行號 (10)
  • 位址 473:REM 標記 (0xC3)
  • 位址 474:REM 文本的開始

透過使用 USR() 函數——該函數會跳轉到透過 POKE 指令在位址 260 和 261 指定的機器碼位址——遊戲會將 REM 敘述的文本作為實際的 Z80 指令執行。具體來說,程式碼 POKE 260,218: POKE 261,1 會告訴 USR() 函數跳轉到位址 474,而那正是 REM 文本開始的地方。

Dissecting the Z80 Routine

最初嘗試將 REM 文本作為 ASCII 進行反組譯的嘗試失敗了,因為原始雜誌清單中的字元並非 ASCII 編碼。當對來自磁帶映像檔的實際位元組進行分析時,該常式被揭露為:

LD A,R       ; 將 R 暫存器複製到累加器
JR Z,-4      ; 如果結果為 0,則跳回前一個指令
LD (F7FF),A  ; 將累加器複製到位址 F7FFh
RET          ; 返回 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

在 1980 年代,程式設計師直接從雜誌中輸入程式碼。然而,如果《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 與機器碼無縫融合。

"大多數輸入型混合 BASIC-機器語言程式會包含許多帶有操作碼 (opcodes) 的 DATA 敘述,以及一個用於 READ 和 POKE 它們的迴圈... Commodore 64 BASIC 的 LOAD"WHATEVER",8 指令會在不進行驗證的情況下,將任何大小的檔案載入到 BASIC 文本區域。" — @RiverCrochet

類似的技術也被用於其他平台,例如 Sinclair ZX81,在缺乏專用記憶體區塊或 DATA 敘述的情況下,使用 REM 敘述或其他被忽略的行來儲存機器碼。

Sources