Хакинг Dandy игр. На примере Road Fighter

3 Сен
2011

Я хочу рассказать про принцип хакинга игрушек для игровой приставки Dandy (она же Nintendo Entertainment System, она же Famicom, она же 100500 китайских клонов, далее по тексту просто NES).
Хакинг (а точнее модинг, но в эмусцене более распространён термин «ROMхакинг») игры будет заключаться в частичном дизассемблировании кода игры и написания своего небольшого кода. Принципиально РОМхакинг почти не отличается от обычного «крэкинга» программ или написания трейнеров для игр.
Жертвой будет небольшая игрушка, завсегдатая китайских катриджей многоигровок 9999999 in 1, игра Road Fighter.

Это достаточно простая гонка, занимает мало объёма и идеально подходит для экспериментов. В этой игре есть возможность перемещать автомобиль только влево или вправо по экрану. Гэймплэйно это оправдано, но хочется чего-то больше. Для начала я введу возможность перемещать наш гоночный автомобиль вперёд и назад.
Итак, что для это нам надо:
  • Эмулятор NES со встроенным отладчиком ( либо тяжёлая артиллерия в виде IDA по вкусу). В качестве эмулятора рекомендую FCEUX-2 последней версии (http://fceux.com)
  • Сам РОМ файл игры
  • Любимый Hex редактор
  • Компилятор под процессор MSC6502 (необязательно, но желательно)
Ром файл и компилятор можно будет взять по ссылке в конце статьи.
Когда инструментами и самим препарируемым запаслись, не помешало бы разобраться в некоторых тонкостях архитектуры приставки NES. Документации как по процессору, так и по приставке достаточно, поэтому кратко затрону только основные моменты. Следующий абзац текста вполне можно пропустить.
Процессор 6502 является 8-ми битным, но имеет 16-ти битную шину адреса и может работать с диапазоном адресов 0h-FFFFh. При этом на RAM отводятся первые 7FFh байт (2КБ), далее идут некоторые специфичные вещи, а на сам код отводится диапазон 8000h-FFFFh (32КБ). Если в указанный объём код игры не помещается, используется специальный механизм – «мапперы». Мапперы имеют возможность переключать участки кода в адресном пространстве процессора. То есть, временно подменять один участок кода другим, тем самым подгружая нужные данные… Мапперы оперируют блоками памяти («банками») по 16 КБ. Технически мапперы являются микросхемами на плате катриджа, и в самом ROM файле отражения не имеют.
Шаг номер один. Расширяем РОМ файл.
Итак, запускаем эмулятор FCEUX, загружаем нашего пациента и заходим в меню Help->Message Log.
Видим примерно следующее:
PRG ROM: 1 x 16KiB
CHR ROM: 1 x 8KiB
..
Mapper #: 0
..

Ром Road Fighter имеет объём 24КБ, из них 16КБ отводится на PRG-ROM (код и данные) и 8КБ на CHR-ROM (графика), а также использует нулевой маппер (т.е. маппер отсутствует). Как видно, РОМ можно легко расширить добавив ещё один банк памяти на 16КБ. Это нужно для того, что бы было куда добавлять наш код.
Для начала откроем встроенный Hex Editor из меню Debug. Переходим на адрес $8000, а потом на адрес $C000 и видим что они содержат одинаковые данные. Причём данные отзеркаливаются в диапазон $8000-$C000 (в этом легко убедится если поставить брикпоинт на этот диапазон, он ни разу не сработает). Теперь открываем РОМ в любом хекс редакторе и правим заголовок, он имеет следующий формат:
0-3 Строка "NES^Z" "^Z" = 0x1A
4 Количество 16КБ банков PRG.
5 Количество 8КБ банков CHR.
6 бит 7-4 Эти 4 бита - младшие биты номера типа маппера ROM.
- бит 3 1 для 4-х экранного VRAM.
- бит 2 1 для 512-ти байтного тренера по адресам $7000-$71FF.
- бит 1 1 для энергонезависимого SRAM по адресам $6000-$7FFF.
- бит 0 1 для вертикального отражения, 0 для горизонтального отражения.
7 бит 7-4 Старшие 4 бита номера типа маппера ROM.
- бит 3-1 Резерв, должны быть 0!
- бит 0 1 для картриджей систем VS.
8 Количество 8КБ банков RAM. Для совместимости с предыдущими
- версиями формата .NES, использовать одну страницу 1x8КБ RAM когда
- этот байт равен 0.
9 бит 7-1 Резерв, должны быть 0.
- бит 0 1 для картриджей PAL, иначе для NTSC.
10-15 Резерв, должны быть 0!

Выставляем количество PRG банков равным двум. Далее вставляем (но не перезаписываем!) в файл пустой блок размером 16КБ (4000h) сразу после заголовка. Теперь файл должен весить 40КБ.
Шаг номер два. Ищем данные.
Теперь нам нужно найти ячейку в ОЗУ отвечающую за координату Y автомобиля и ячейку с кодом нажатой клавиши. Это можно сделать с помощью встроенного в эмулятор инструмента RAM Search (меню Tools). В нашем же случае будет достаточно запустить эмуляторный HEX Editor, и просмотреть первые 100h байт глазами. Процессор имеет специальные «короткие инструкции» для быстрого обращения в этот диапазон, поэтому часто используемые данные находятся именно здесь. Если одновременно открыть главное окно эмулятора и окно Hex Editor’а, и понажимать «влево\вправо» (только не забудьте настроить управление), то можно увидеть, что соответственно меняются несколько ячеек. Нам нужны 07h – код клавиши и 65h – координата X. Логично предположить что координата Y находится рядом с координатой X, меняем ячейку 66h на произвольное число и вуаля – автомобиль переместился вдоль оси Y. Также на время тестов можно заморозить через контекстное меню хекс редактора ячейке C8h, в ней хранится количество топлива.
Шаг номер три. Делаем врезку кода и ставим заглушку.
Зная адреса нужных переменных можно уже написать свой код, но пока мы не знаем откуда его вызывать. Ставим брикпоинт на запись, на ячейку 65h (через контекстное меню в хексе или через отладчик), перемещаемся в игре влево\вправо и должно открыться окно отладчика со следующим кодом:
01:D52B:E6 65 INC $0065 = #$64
01:D52D:60 RTS
01:D52E:C6 65 DEC $0065 = #$64
01:D530:60 RTS

Отладчик изредка криво дизасмить команды, поэтому если прокрутить окно отладчика вверх, некоторые команды будут отображены неправильно. Прокрутив код немного вверх, либо пошагово дойдя до RTS, мы выйдем на адрес, откуда происходит вызов процедуры проверки кода клавиши и последующее движение автомобиля:
01:D4D0:20 D9 D4 JSR $D4D9
01:D4D3:20 E1 D4 JSR $D4E1
01:D4D6:4C E9 D4 JMP $D4E9

Заменим JSR $D4D9 на свой вызов процедуры — JSR $8000. Для этого достаточно нажать на полоску слева от адреса команды, и вбить нужную команду в инлайновом ассемблере. Используя пошаговую отладку перейдём на адрес $8000, и поставим заглушку в виде вырезанного JSR и последующего RTS из нашей процедуры:
00:8000:20 D9 D4 JSR $D4D9
00:8003:60 RTS

Шаг номер четыре. Пишем код и вставляем его в РОМ.
Создаём текстовый файл RoadFighter.asm и вписываем в него такой простенький код:
$=$8000
LDA $07
AND #$04
BEQ checkUp
BNE backward:
checkUp:
LDA $0007
AND #$08
BEQ exitInject
forward:
LDA $66
CMP #$50 ; ставим ограничение на изменение переменной
BMI exitInject
DEC $0066
JMP exitInject
backward:
LDA $66
CMP #$B0
BPL exitInject
INC $0066
exitInject:
JSR $D4D9
RTS

Теперь запускаем компилятор с параметрами “-l roadFighter”. На выходе будет файл листинга и бинарник. Открываем бинарник и копируем его содержимое в РОМ Road Fighter по смещению 10h.
Шаг номер пять. Фини-та-ля.
Если всё сделано правильно, то запустив модифицированный РОМ в эмуляторе, мы сможем перемещать нашу гоночную машинку вперёд и назад, тем самым удобнее сталкивать безобидных и не очень машинок на трассе (а за их уничтожение даются очки!).
Архив с компилятором, исходником, оригинальным ромом и модифицированным ромом – RoadFighterModSource.zip(народ)
RoadFighterModSource.zip(megaupload)
По материалам Хабрахабр.



загрузка...

Комментарии:

Наверх