После изучения кода игры New GhostBusters 2 у меня появилась расшифрованная информация о привидениях по уровням.
Чтобы ей не пропадать зря, решил сделать редактор врагов и усложнить игру наподобии того, как делал с Battletoads, чтобы пройти её со своим мелким племянником.
Но писать на асме код, который мог бы считывать данные из другого места картриджа, которое еще надо найти свободное, это ад.
Но в современных эмуляторах есть встроенный скриптовый язык, позволяющий вмешаться в стандартный поток инструкций процессора. С помощью этого языка хакеры делали с играми вещи, напоминающие магию.
Но любая магия является просто сложной технологией, поэтому решил попробовать пошаманить со скриптами сам. Алгоритм загрузки данных о врагах комнаты в "Охотниках за привидениями"
выглядит примерно так:
Записи о комнатах храняться в виде списка из команд и номеров типов призраков. За командой следует переменной число аргументов, за номером типа координаты места появления (4 байта) и переменное число байт дополнительной информации (0 до 3 байт).
Игра узнает объем дополнительной информации из массива, в котором для каждого типа призрака есть набор из 3 флажков “имеет ли данный призрак доп. инфу заданного типа”. Такой информацией может быть, например, признак, что привидение неуязвимо (стулья в конце первого уровня и самовоспламеняющиеся свечи в последнем), подтип (головы в конце второго уровня) или направление поворота (импы в третьем уровне).
Таблица с описанием команд:Коды команд New GhostBusters 2
Код Описание
FF 0 Команда конца уровня.
FF 1 X Команда указателя на следующую комнату. Описание комнат уровня с номерами хранится отдельно.
FF 2 Чекпоинт. После проигрыша игра начнется не с начала уровня, а с последней сохраненной комнаты.
0D Мешок-приз.
0E Свечи из последнего уровня.
11 Сценка в конце 3-го уровня.
12 Стрелка прибора (появляется после отлова всех призраков. доп. байты - направление и номер комнаты)
13 Лизун
14 Ускоренный лизун
15 Лизун с телегой
1 Лизун-метатель еды
17 Зеленый пацан
18 Тень с бензопилой
19 Змея из стены
1A Пара и тостого и худого призраков (первый босс)
1B Прыгающий стул
1C Розовые крутяшки
1D Шахтер с дрелью
1E Шахтер с киркой
1F Шахтер с граблями
20 Шахтер на вагонетке
21 Мелкая голова из поезда
22 Лизун с лопатой
23 Тыквоголов
24 Летающий имп
25 Имп с гарпуном
26 Дух ниндзя с мечом
27 Дух ниндзя с сюрекенами
28 Тесто
29 Блин из канализации
2A Тролль с цепью
2B Лизун-метатель мусора
2C Лизун-метатель быстрый
2D Вращающаяся труба
2E Босс Ящеры
2F Босс Венс
30 Босс Виго
Проглядел доступную документацию по EmuLua
, нашел функцию memory.register
, про которую сказано, что она может установить произвольную функцию, отслеживающую изменения в ячейке памяти.
Если установить обработчики на шаги, помеченные на блок-схеме красными кружками с цифрами, то можно выставлять свои данные вместо игровых в аккумулятор вместо реальных данных и заглушать реальный счетчик, подсчитывая сколько байт уже было отдано, а в конце вернуть указатель на реальные данные.
Но такому простому способу препятствует то, что memory.register
а) работает только в режиме ослеживания записей. б) как следствие работает только в области изменяемой памяти (0x0000-0x8000), а данные уровня лежат в ROM. Хендлер на чтение из ROM установить нельзя.
Почему – не знаю, технически вроде не сложно вытащить такую возможность в скрипты, но факт, что таким способом подстановку данных сейчас сделать нельзя :(
Пришлось прочитать документацию по всем доступным функциям внимательнее, после чего обнаружилась функция memory.registerrun, устанавливающая функцию,выполняющуюся при выполнении кода по указанному в аргументе адресу.
Выяснил на практике, что если ставить обработчик memory.registerrun
, то он выполняется ДО выполнения команды по адресу, а если через memory.register
, то сразу ПОСЛЕ записи в ячейки.
Алгоритм внедрения модифицировал так - в роме добавил в интерпретатор еще одну команду, которая выставляет флажок в неиспользуемый игрой кусок памяти, а в скриптах сделал функцию, реагирующую на выставление флажка и делающую то же, что команда считывания в оригинальном роме. После тестов я заметил, что функция-обработчик вызывается недетерминированное количество раз (почему-то по 4 за одну запись в ячейку). Поэтому функция-обработчик обязана быть чистой и никаких счетчиков содержать не может :(
Выяснять, почему так, долго и сложно, поэтому просто убедился в том, что хотя бы memory.registerrun работает, как ожидается и решил использовать только её. Дальше попробовал так - навесить после всех инструкций, считывающие данные о комнате, свои функции-хендлеры на выполнение, которые блокируют действие этой инструкции (переписывают аккумулятор, память или регистр статуса) и дальше возвращаться к оригинальному потоку выполнения.
С таким подходом следует еще следить за тем, чтобы адрес, на который ставится обработчик находился в том же блоке памяти, иначе можно напороться на неправильную инструкцию. В качестве контрольной проверки может послужить сравнение значения ячейки, хранящей номер уровня с тем, который ожидается в скрипте. Ну и некоторые в некоторых сложных по возможному графу выполнения участках проще вклиниваться в несколько частей, собирая по ходу необходимые данные. Такой метод работает! Дальше оставалось только перехватить логику обработки чекпоинтов и game over, чтобы сбрасывать свои указатели данных. Еще в ходе исследования я заметил, что потенциально движок игры поддерживает до 8 призраков на экране, хотя в коде стоит ограничение на четырех.
Это ограничение было аккуратно вырезано, после чего игра стала более сложной и веселой. Единственный минус - видимо, за кадр не всегда успевают отрисоваться все объекты на экране, поэтому появляется мерцание. Ну на это уже можно и забить. Оставшиеся проблемы скрипта - я не делал поддержку перезапуска по нажатию кнопок power и reset приставки (это не поддерживается вообще) и загрузку/сохранение вместе с игровыми сейвами (просто неохота было). Так что сейвами пользоваться нельзя, а при перезагрузке игры надо вручную перезапустить скрипт. Вот и вся чёрная магия.
Видео геймплея:
Код скрипта:
http://www.everfall.com/paste/id.php?az548wj3t37t
Ссылка на архив (содержит эмулятор, игру на русском, скрипт и инструкции для запуска):
http://dl.dropbox.com/u/852723/NewGhostBusters_hard.zip