一行神秘注释里的机器码秘密
10 REM"_(C2SLFF4

在80年代Exidy Sorcerer平台的BASIC游戏The Wizard's Castle中,第一行代码10 REM"_(C2SLFF4看似毫无意义的注释,实则暗藏玄机。我和Josh、Chris通过逆向分析发现,这段字符在内存中恰好对应Z80机器码,被USR()函数调用执行。更有趣的是,它配合PEEK(-2049)读取屏幕右下角字符,为伪随机数生成器提供种子,确保每次游戏的地牢布局不同。这个巧妙的设计不仅避开了当时BASIC缺乏RANDOMIZE命令的限制,还展现了早期程序员在有限资源下的极致创意。
这行注释里的字符实际上就是Z80机器码,被USR()函数当作可执行代码调用。
HN 评论区
51- nneonneo
嗯,根据 http://bitsavers.informatik.uni-stuttgart.de/pdf/exidy/DP500... 中 Table F-2 上的(手写!)笔记,看起来按下 Graphic+ 键可以输入 0x80 到 0xBF 之间的 BASIC 令牌,而按下 Graphic+Shift+ 键则可以访问 0xC0 到 0xC6。由此推断,Graphic+Shift+ 键应该能访问整个 0xC0 到 0xFF 空间,但其中大多数按键都没有文档记录。
基于此,我在想是否值得尝试以下操作:
10 REM [Graphic+Shift+=] [_] [(] [Graphic+Shift+NumpadPlus] [2] [Graphic+Shift+NumpadEquals] [Graphic+Shift+Numpad6] [Graphic+Shift+0]
注意,你可能需要一个具有精确键盘模拟功能的模拟器——或者一台真机——才能输入这些内容。不过,使用 http://www.liaquay.co.uk/sorcerer 提供的模拟器,我确认了 Graphic+Shift+0 产生了 201(显示为 F4),而 Graphic+Shift+= 产生了 255(显示为 S),所以我认为这种方法应该可行。
- RiverCrochet
> 我想知道这在其他平台上是否也能行?还是说它们会直接崩溃?其他人可以在 Commodore 64 或其他设备上试试看。
我不记得在 Commodore 64 上见过太多这类东西。大多数混合 BASIC 和机器语言的输入程序都会有大量的 DATA 语句包含操作码,以及一个循环来 READ 并 POKE 它们(通常位于 RAM 前 2K 的磁带缓冲区区域,或者在 49152/$C000 处的 4K 区域)。
Commodore 64 BASIC 的 LOAD"WHATEVER",8 命令会将任何大小的文件加载到 BASIC 文本区域,而不进行任何验证或格式检查。因此,许多商业 C64 软件通常只有一行 "10 SYS2063"(或类似)存根,它会直接跳转到附加在该 BASIC 存根之后的实际代码中。但是 LIST 命令不会显示存根之后的任何内容——我想“下一行”字节被设置为 $0000 以表示程序结束。
- sedatk
这就是为什么一些计算机杂志开始引入工具,让用户能够无歧义地输入程序。
这是我最近制作的一个例子:一个 Amstrad CPC 程序,它既是有效的 BASIC 代码又是机器码,以两种方式运行(RUN 或 CALL &170 —— BASIC 程序的加载地址)都会产生相同的输出:https://github.com/ssg/yello
- JSR_FDED
看,这就是为什么我从来都不理解 LISP 用户为何自豪地宣称“代码即数据,数据即代码”……我们在 BASIC 里已经这样做了几十年了!
- NetMageSCW
这让我对那些被遗忘的历史感到难过。我从未用过 Sorcerer,但我一眼就能看出那段代码是做什么的。