"
Задача:
Довольно часто в играх
NES встречаются запакованные тайловые карты. Основной симптом этого - ненаходимость отдельных
надписей в РОМе. Один из тысяч примеров - игра
The Blue Marlin на
NES. На титульном экране есть множество надписей,
но та что мерцает(
PUSH START BUTTON) не находится в РОМе.
Здесь я собираюсь привести пример того как лично я нашел
ее. Т.е. я не утверждаю, что это оптимальный вариант (я почти уверен, что можно это сделать если не легче, то уж
точно быстрее), но для примера, я думаю, сойдёт и так.
Тулзы:Я лично пользовался модифицированным эмулем
FCEU под названием
FCEUXD SP 1.04, но подойдет любой эмулятор с отладчиком
[бряки они и в Африке бряки =)], ну еще можно хексредактор, хотя просмотрщик хекса в РОМе есть и в
FCEUXD SP.
Знания: Глубокое знание асма не обязательно [у меня самого его нет =)], но без азов можно ничего не понять.
Желательно, конечно, чтоб вы знали еще и что такое тайловые карты и работу процессора M6502 и т.д., но думаю,
это не является критичным требованием.
Всю необходимую инфу можно получить из доки Cah4e3'a, которую можно найти на его сайте и док Parasyt'a, которую можно
найти соответственно на его =)
Решение:
В первую очередь закрадывается подозрение, что надпись действительно спрайтовая(раз не находится сразу в такой простой
игре, где даже таблички стандартные). Чтобы убедиться - нужно посмотреть в спрайтовый буфер - специальная область
памяти приставки, куда записываются индексы и аттрибуты спрайтов (в том числе и координаты). Вся теоретическая
база по этой теме содержится в супердоке
Cah4e3'a. Я не стану описывать, как искать этот буфер - более понятно
и теоретически обоснованно это описано в доке Cah4e3'a. Плюс, во всех играх, с которыми я имел дело, буферы находились
в памяти по адресу
200h... Blue Marlin не исключение. Откроем клавишей F6 HexEditor, найдём адрес
200h.
Как мы видим, спрайты постоянно изменяются - оно и понятно - пузырьки воздуха на титульнике постоянно
поднимаются (ясно, что изменяют координату), плюс, возможно, тут есть перемешивание спрайтового
буфера (угадай, в какой доке это явление описано
) Чтобы точно убедиться, что наша надпись составлена не из
спрайтов - можно взять мощный , но абсолютно не функциональный дебаггер с простеньким эмулем
No$Nes
и нажать на
F5 появится окно
VRam viewer'a откроем вкладочку OAM Data - и увидим табличку всех спрайтов, которые
есть на экране в данную секунду. Да,
No$Nes бесполезен с точки зрения бряков, но этой функции я больше нигде не видел -
главное - наглядно. Красный квадрат отслеживает изменяющиеся спрайты, как видим нижняя часть экрана, где содержится
интересующая нас надпись не выделяется никогда, плюс в таблице спрайтов только графика пузырьков и никаких букв.
Кому интересно - могут заглянуть
VRam viewer'ом в игру
Teenage Mutant Ninja Turtles - Tournament Fightersв Options сразу видно, какая надпись составлена из спрайтов.
Итак - надпись не спрайтовая. Значит, поиск нужных процедур будет вестись по несколько иному пути...
берем нажимаем
Ctrl+F1 появляетя
PPU viewer находим буквы, из которых составлено предложение
press start button
Видим что-то вроде P
RESTABU...буквы, конечно, не повторяются, т.к. в коде будут использоваться одна буква, загруженная
несколько раз.
Вот тут нам впервые повезло, да еще как! буквы полностью загружены в память
PPU, без всяких изврашений, в читаемом
виде, самое главное теперь мы можем узнать индекс тайлов букв, которые загружены в
PPU
так, например
P= $70, R=$71 и так далее. Это для нас означает, что обязательно при загрузке этого экрана, вызывается
процедура, загружающая эти значения в память
PPU. Можно смело ставить
BreakPointWrite на адрес
$2368 в
PPUсрабатывает бряк вначале, когда в
PPU сохраняется ноль - это нас не интересует, жмем
RUN и
CODE:$C330:B9 00 03 LDA $0300,Y @ $030D = #$70
>$C333:8D 07 20 STA $2007 = #$00
чуть выше видим команду, которая загрузила в аккумулятор число
$70
как мы видим
$70 содержалось в ячейке
$030D в
RAM'e нас интересует, кто записал это значение туда, ставим бряк уже на
RАМ
и вновь загружаем наш титульник. Как мы видим, вначале эту ячейку активно используют для заполнения каких-то других
ячеек(может быть для вычисления значений и т.д.) нас это не интересует, т.к. все время код останавливается на команде
CODE:>$8031:9D 00 03 STA $0300,X @ $030D = #$A4 [В регистре А сейчас $40]
$8034:E8 INX
$8035:C6 02 DEC $0002 = #$18
.............................
Нас же интересует команда, которая загрузит
$70, жмём
RUN и замечаем, что это
$40 грузится очень много число раз
пока дойдем до заветной команды можно поседеть. Можно заняться глубокой трассировкой кода (или Condition breakpoints),
как бы сделали опытные
хакеры, но я, к сожалению, к таковым не отношусь... Я выбрал более долгий, зато намного более понятный способ :
использовал прикольную функцию
Trace Logger. ..что она делает? Просто записывает команды, выполненные процессором
в порядке их выполнения на экран или в файл - я предпочитаю последнее, т.к. можно пользоваться функцией поиска в любом
редакторе. Так вот просто берем и перед загрузкой нашего экрана жмем
Start Logging, предварительно назвав файл, в который
будет записываться команды процессора вместе со значениями всех регистров и, для особых извращенцев, с состоянием всех
флагов =). прекращаем запись, как только на экране появится наш титульник(буквы уже загружены, а в файл пишутся не
интересующие нас команды). У меня получился файл размером 40 мегабайт( неплохо за 2-то секунды =) )
открываем текстовым редактором, ищем команду, аналогичную
STA $0300,X @ $030D = #$A4, только меняем
A4 на
70
конечно, мы уже перепрыгнем на один цикл интересующую нас команду, т.е. сохраняться будет уже другое число
в аккумуляторе уже
$40),
а в ячейке
$030D УЖЕ содержится наше заветное
$70. Необходимо от команды, которую мы только что
нашли отлистать вверх до предыдущей записи в ячейку $030D .Просто вводим в поиск "
STA $0300,X @ $030D = #$" и ищем
в обратном направлении [вверх]. После
"#$" не стоит никакого числа, т.к. мы не знаем какое число содержалось в нашей ячейке
до того как в него было записано
$70. Итак, через наносекунду в ячейке будет
$70, если вы не забыли, это индекс буквы
'
P' в слове '
PRESS' и мы, наконец, внутри той процедуры, к которой так рвались. Она уже у нас на крючке - осталась малость - только разузнать, что она делает, а
дальше дело техники... Строго говоря, процедура началась еще раньше - когда фактически начинала заполнять тайловую карту
в память - зрячие люди могут видеть, что до этого писался индекс
$40, который, очевидно, означает пробел (ведь перед
словом '
PRESS' стоит пробел и даже не один...)
Итак, разрешите представить, наша процедура:
................................ ................................ ....
CODE:$8031:9D 00 03 STA $0300,X @ $030D = #$40 A:70 X:0D Y:02 P:nvUbdizc
$8034:E8 INX A:70 X:0D Y:02 P:nvUbdizc
$8035:C6 02 DEC $0002 = #$18 A:70 X:0E Y:02 P:nvUbdizc
$8037:D0 F1 BNE $802A A:70 X:0E Y:02 P:nvUbdizc
$802A:20 E8 80 JSR $80E8 A:70 X:0E Y:02 P:nvUbdizc
$80E8:A5 08 LDA $0008 = #$A0 A:70 X:0E Y:02 P:nvUbdizc
$80EA:0A ASL A:A0 X:0E Y:02 P:NvUbdizc
$80EB:90 04 BCC $80F1 A:40 X:0E Y:02 P:nvUbdizC
$80ED:06 09 ASL $0009 = #$10 A:40 X:0E Y:02 P:nvUbdizC
$80EF:B0 05 BCS $80F6 A:40 X:0E Y:02 P:nvUbdizc
$80F1:B1 0E LDA ($0E),Y @ $A433 = #$01 A:40 X:0E Y:02 P:nvUbdizc
$80F3:C8 INY A:01 X:0E Y:02 P:nvUbdizc
$80F4:D0 02 BNE $80F8 A:01 X:0E Y:03 P:nvUbdizc
$80F8:48 PHA A:01 X:0E Y:03 P:nvUbdizc
$80F9:20 FE 80 JSR $80FE A:01 X:0E Y:03 P:nvUbdizc
$80FE:E6 05 INC $0005 = #$6B A:01 X:0E Y:03 P:nvUbdizc
$8100:A5 05 LDA $0005 = #$6C A:01 X:0E Y:03 P:nvUbdizc
$8102:29 3F AND #$3F A:6C X:0E Y:03 P:nvUbdizc
$8104:D0 11 BNE $8117 A:2C X:0E Y:03 P:nvUbdizc
$8117:29 07 AND #$07 A:2C X:0E Y:03 P:nvUbdizc
$8119:D0 0C BNE $8127 A:04 X:0E Y:03 P:nvUbdizc
$8127:60 RTS A:04 X:0E Y:03 P:nvUbdizc
$80FC:68 PLA A:04 X:0E Y:03 P:nvUbdizc
$80FD:60 RTS A:01 X:0E Y:03 P:nvUbdizc
$802D:45 04 EOR $0004 = #$70 A:01 X:0E Y:03 P:nvUbdizc
$802F:85 04 STA $0004 = #$70 A:71 X:0E Y:03 P:nvUbdizc
$8031:9D 00 03 STA $0300,X @ $030E = #$40 A:71 X:0E Y:03 P:nvUbdizc
$8034:E8 INX A:71 X:0E Y:03 P:nvUbdizc
$8035:C6 02 DEC $0002 = #$17 A:71 X:0F Y:03 P:nvUbdizc
....................................................................
Этого куска достаточно чтобы разобраться, что она делает: обратим внимание на то, что далее в ячейку
$030E грузится
число
$71, т.е. уже следующим знаком будет буква с этим индексом. А это, ясное дело, буква '
R' ну и так далее, некоторые
буквы будут загружаться в ячейки несколько раз, как например буква '
S'. Теперь нам надо понять, как получают это число:
почти перед самой записью аккумулятор вытаскивают из стека и
XOR'ят с
$0004. Находим выше, что поднимаюм его в
стек командой '
$80F8:48 PHA' , а непосредственно перед поднятием в аккумулятор грузят значене из
$A433
Это число можно рассматривать как некий своеобразный поинтер, из которого получают это число. Теплится слабая надежда,
что на этом трассировка кода закончена (т.е. уже число в ячейке
$4A33 уже никак не вычисляют, и его можно будет
банально найти в РОМе). Чтобы найти это число в РОМе нам нужно знать, что по соседству - вновь ставим бряк на запись
в $030D и при загрузке титульного экрана, как только произойдет останов, открываем HexEditor, находим $4A33, как мы и
ожидали, там число
$01, которое используется для расчета индекса второй буквы, т.к. первую мы к этому времени уже
загрузили и сейчас расчитываем вторую. Итак, запоминаем цифры по порядку - это
$30 $01 $03 $01 $33 $33 $07 $01 $04
$05 $34 $36 $01 $47 $03 $0C $01 $39. Т.к. далее идут 'необычные' байты типа '
FF' можно сказать, что это конец
наших поинтеров, кроме того в фразе
'PRESS START BUTTON' вместе с пробелами ровно 8 знаков - как и цифр. Можно
ввести цифры поиском, можно перейти прямо так из
FCEUXD SP, нажав правую кнопку, но факт в том, что все эти цифры есть
в РОМе[по адресу
$a442] и их уже можно изменять в целях перевода!
Так, программой берется первое число(
$30) потом XOR'ится с предыдущим числом(было
$40) получаем заветное
$70!
[особо недоверчивые могут проверить в калькуляторе Windows =)]
Далее берется второй поинтер(
$01),XOR'ится с предыдущим числом(было $70) стало $71 и так далее...
теперь осталось только перерисовать буковки, расставить в нужном нам порядке поинтеры и закончатся наши мытарства
с этой надписью, а сколько их еще будет! =)
-=#Griever's Stuff#=- Н.Новгород 2005"