Игра на 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 игры — за десятилетия до аппаратных чипов случайности.
Бессмыслица, которую никогда не задумывали читать как текст

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

Эксплуатация того, как интерпретатор хранит собственный исходник
Механизм опирается на деталь внутренней работы интерпретатора 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.
Перейти в архив выпусков