spiiin: (2D)

Скрипт для поиска секретов в Утиных Историях 2. Отмечает квадратиками все игровые объекты, за счёт чего получается найти все секретные места.
https://github.com/spiiin/CadEditor/blob/master/Stuff/nes_lua/duck_tales_2_show_objects.lua
Tags:
spiiin: (Default)
После изучения кода игры 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

Ссылка на архив (содержит эмулятор, игру на русском, переведенную magicteam ), скрипт и инструкции для запуска):
http://dl.dropbox.com/u/852723/NewGhostBusters_hard.zip

- Если у вас нету картриджа с оригинальной игрой, то через 24 часа ROM вы обязаны удалить :)
- WARNING! Я не знаю Lua. По чему его лучше выучить?
Tags:

Profile

spiiin: (Default)
spiiin

September 2017

S M T W T F S
     1 2
34 567 89
101112131415 16
17181920212223
24252627282930

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Sep. 19th, 2017 11:39 am
Powered by Dreamwidth Studios