Игра на BASIC из 80-х прятала машинный код внутри комментария. Объяснить это удалось только через 40 лет

Строка «10 REM"_(C2SLFF4» открывает The Wizard's Castle на Exidy Sorcerer — бессмыслица для любого человека. На самом деле это машинный код Z80, помещённый ровно по тому адресу памяти, где его ждал вызов функции, потому что парсер комментариев в BASIC оказался самым простым способом протащить байты мимо типографики журналов 1980-х.

Вкратце

Загадочный комментарий REM, открывающий подземельную игру на BASIC 1980-х, оказался сырым машинным кодом Z80, а не текстом — размещённым точно по адресу, откуда его должен был выполнить вызов функции USR().

Трюк эксплуатировал то, как интерпретатор BASIC на Exidy Sorcerer хранил исходник в памяти — байты комментария попадали ровно туда, где нужен был исполняемый код, замаскированные под нечитаемую ремарку.

Полезная нагрузка делала кое-что изящное: читала постоянно растущий регистр R процессора Z80 как дешёвый источник случайности для сида RNG игры — за десятилетия до аппаратных чипов случайности.

Бессмыслица, которую никогда не задумывали читать как текст

Загадочный комментарий REM из The Wizard's Castle
Загадочный комментарий REM из The Wizard's Castle

Первая строка The Wizard's Castle, подземельной игры 1980-х для микрокомпьютера Exidy Sorcerer, гласит 10 REM"_(C2SLFF4 — оператор-ремарка BASIC, тип строки, явно предназначенный для человекочитаемых комментариев, которые интерпретатор игнорирует во время выполнения. Только эта строка нечитаема, и в сколько-нибудь значимом смысле она и не игнорируется: байты внутри этого оператора REM — исполняемый машинный код Z80, а не текст, и от их корректного выполнения зависит вся работа игры.

Микрокомпьютер Exidy Sorcerer
Микрокомпьютер Exidy Sorcerer

Эксплуатация того, как интерпретатор хранит собственный исходник

Механизм опирается на деталь внутренней работы интерпретатора BASIC на Sorcerer: когда BASIC токенизирует программу, он хранит исходник (включая буквальное содержимое комментариев REM) прямо в памяти, в фиксированной, предсказуемой раскладке. Автор игры вычислил, что содержимое оператора REM попадёт точно по адресу памяти 474, ровно туда, куда следующий вызов USR(0) в программе был настроен перейти и выполнить как функцию. Последовательность POKE в начале программы строит небольшой трамплин, указывающий на адрес 474, затем вызов USR(0) прыгает туда и начинает выполнять всё, что оказалось в этой памяти, а это, по замыслу, и есть «комментарий» из строки 10. Непечатаемые, не-ASCII байты внутри оператора REM (значения вроде 0xED и 0xF7, встречающиеся в анализе), вовсе не символы; это настоящие инструкции ассемблера Z80, скомпилированные вручную и протащенные в место, которое собственный токенизатор BASIC гарантированно оставлял нетронутым.

Что именно делает скрытый код, когда он выполняется: читает регистр R процессора Z80: внутренний регистр, автоматически увеличивающийся с каждой выполненной инструкцией CPU, как побочный эффект обычной работы, отфильтровывает нулевые значения и записывает результат по определённому адресу видеопамяти, используя его как дешёвый, довольно непредсказуемый сид для генератора псевдослучайных чисел игры. На домашнем железе 1980-х без выделенного источника энтропии считывание регистра, постоянно меняющегося просто от работы CPU, было по-настоящему изящным способом получить случайность, не стоивший ни единого байта явного кода RNG: генерация сида целиком спрятана в комментарии, который никто не должен был читать иначе как комментарий.

Почему это должен был быть именно комментарий, а не видимый код

Более глубокая причина, почему этот трюк вообще существует, а не просто байты машинного кода, записанные автором как обычный оператор DATA, кроется в том, как игры на BASIC 1980-х реально доходили до читателей: напечатанные текстовыми листингами в компьютерных журналах, вручную набранные тысячами читателей на собственных машинах. Ввести сырые не-ASCII байты машинного кода с печатной страницы журнала обычной клавиатурой было почти невозможно: не было чистого способа представить непечатаемые значения байтов символами, которые читатель мог бы набрать обратно. Трюк с оператором REM обходит это целиком: какую бы искажённую последовательность символов ни напечатал журнал, пока читатель набирал её обратно точно (вероятно, используя специальные комбинации клавиш Graphic+Shift, поддерживаемые Sorcerer, или вручную вводя байты через POKE после набора символов-заглушек) — раскладка памяти токенизатора гарантированно доставляла эти байты по адресу 474 в любом случае. Это решение, целиком построенное вокруг ограничений печати и набора своей эпохи: изящное именно потому, что должно было пережить копирование от руки, чужими людьми, со страницы журнала.

Получайте такие разборы каждый день

Главные истории Hacker News — на email, в Telegram или RSS.

Это последняя новость выпуска 23 июля 2026.

Перейти в архив выпусков