Прошлым летом я приступил к реверс-инжинирингу игры Animal Crossing для GameCube. Я хотел исследовать возможность создания модов для этой игры. Кроме того, мне хотелось задокументировать процесс, чтобы создать туториалы для людей, заинтересованных в хакинге ROM-ов и обратной разработке. В этом посте я расскажу о отладочных функциях разработчика, которые остались в игре, а также поделюсь тем, как я обнаружил чит-комбо, с помощью которых их можно разблокировать.
new_Debug_mode
Изучая оставшиеся отладочные символы, я заметил имена функций и переменных, содержащих слово «debug», и решил, что будет интересно посмотреть, остался ли в игре какой-то отладочный функционал. Если мне удастся активировать функции отладки или разработки, это поможет мне и в процессе создания модов.
Первой функцией, на которую я обратил внимание, была new_Debug_mode
. Она вызывается функцией entry
, которая запускается сразу после завершения экрана с логотипом Nintendo. Всё, что она делает — это размещает байтовую структуру 0x1C94
и сохраняет указатель на неё.
После её вызова в entry
в размещённой структуре по смещению 0xD4
сразу перед вызовом mainproc
задаётся значение 0.
Чтобы посмотреть, что происходит, когда значение не равно нулю, я пропатчил инструкцию li r0, 0
по адресу 80407C8C
, заменив её на li r0, 1
. Сырые байты инструкции li r0, 0
— это 38 00 00 00
, где назначаемое значение находится в конце инструкции, поэтому я просто смог заменить байты на 38 00 00 01
и получить li r0, 1
. В качестве более надёжного способа сборки инструкций можно использовать что-нибудь типа kstool
:
$ kstool ppc32be "li 0, 1"
li 0, 1 = [ 38 00 00 01 ]
В эмуляторе Dolphin этот патч можно применить, перейдя во вкладку «Patches» в свойствах игры и введя его следующим образом:
После присвоения значения 1 в нижней части экрана появился интересный график:
Он выглядел как индикатор производительности: небольшие полосы внизу экрана то увеличивались, то уменьшались. (Позже, когда я посмотрел на названия функций, отрисовывающих этот график, обнаружил, что на самом деле они отображают метрики использования ЦП и памяти.)
Это было здорово, но не особо полезно. После присвоения значения 1 мой город перестал загружаться, поэтому здесь больше ничего нельзя было сделать.
Zuru mode
Я снова начал искать другие отсылки к функциям отладки, и несколько раз наткнулся на нечто под названием «zuru mode». Ветви блоков кода с функционалом отладки часто проверяли переменную zurumode_flag
.
zzz_LotsOfDebug
(название я придумал сам) в показанной выше функции game_move_first
вызывается только когда zurumode_flag
не равен нулю.
Поискав функции, связанные с этим значением, я обнаружил такие:
zurumode_init
zurumode_callback
zurumode_update
zurumode_cleanup
На первый взгляд их назначение загадочно, они жонглируют битами в смещениях переменной под названием osAppNMIBuffer
.
Вот как на первый взгляд выглядела работа этих функций:
zurumode_init
- Присваивает
zurumode_flag
значение 0 - Проверяет несколько битов в
osAppNMIBuffer
- Сохраняет указатель на функцию
zurumode_callback
в структуреpadmgr
- Вызывает
zurumode_update
zurumode_update
- Проверяет несколько битов в
osAppNMIBuffer
- В зависимости от значения этих битов обновляет
zurumode_flag
- Выводит строку формата в консоль ОС.
Подобное обычно полезно для придания контекста коду, но в строке было множество непечатаемых символов. Единственным распознаваемым текстом были «zurumode_flag» и "%d".
Предположив, что это может быть японский текст с многобайтовой кодировкой символов, я пропустил строку через инструмент распознавания кодировки и выяснил, что строка закодирована Shift-JIS. В переводе строка просто обозначала «Значение zurumode_flag изменилось с %d на %d». Это не даёт нам особо много новой информации, но зато теперь мы знаем, что используется Shift-JIS: в двоичных файлах и таблицах строк гораздо больше строк в этой кодировке.
zurumode_callback
- Вызывает
zerumode_check_keycheck
- Проверяет несколько битов в
osAppNMIBuffer
- Куда-то выводит значение
zurumode_flag
- Вызывает
zurumode_update
zerumode_check_keycheck
пока нам не встречалась из-за другого написания… что же это такое?
Огромная сложная функция, которая выполняет гораздо больше работы над битами со значениями без названий.
В этот момент я решил сделать шаг назад и изучить другие отладочные функции и переменные, потому что не был уверен в важности zuru mode. Кроме того, я не понимал, что здесь означает «key check». Возможно ли, что это криптографический ключ?
Обратно к отладке
Примерно в это время я заметил проблему с моим способом загрузки отладочных символов в IDA. Файл foresta.map
на диске игры содержит множество адресов и имён функций и переменных. Сначала я не увидел, что адреса для каждого раздела заново начинаются с нуля, поэтому написал простой скрипт, добавляющий запись имени для каждой строки файла.
Я написал новые скрипты IDA, чтобы исправить загрузку символьных таблиц для разных разделов программы: .text
, .rodata
, .data
и .bss
. В разделе .text
находятся все функции, поэтому я сделал так, чтобы на этот раз при задании имени скрипт автоматически распознавал функции по каждому адресу.
В разделах данных он теперь создавал сегмент для каждого двоичного объекта (например m_debug.o
, который должен был стать скомпилированным кодом для чего-то под названием m_debug
), и задавал пространство и названия для каждого фрагмента данных.
Это дало мне гораздо больше информации, однако пришлось вручную задавать тип данных для каждого фрагмента данных, потому что я задавал каждый объект данных как простой байтовый массив. (Оглядываясь назад, я понимаю, что лучше было бы предполагать, что кратные 4 байтам фрагменты содержали 32-битные целые числа, потому что их было много, и многие содержали адреса функций и данных, важных для построения перекрёстных ссылок.)
Изучая новый сегмент .bss
на наличие m_debug_mode.o
, я обнаружил несколько переменных вида quest_draw_status
и event_status
. Это интересно, потому что я хотел, чтобы в режиме отладки отображалась полезная информация, а не только график производительности. К счастью, из этих записей данных существовали перекрёстные ссылки на огромный фрагмент кода, проверяющий debug_print_flg
.
С помощью отладчика в эмуляторе Dolphin я установил точку останова в том месте функции, где проверялось debug_print_flg
(по адресу 8039816C
), чтобы понять, как работает эта проверка. Но программа так ни разу и не перешла в эту точку останова.
Проверим, почему так происходит: эта функция вызывается game_debug_draw_last
. Угадайте, какое значение проверяется перед её условным вызовом? zurumode_flag
! Какого чёрта происходит?
Я установил точку останова на этой проверке (80404E18
) и она сразу же сработала. Значение zurumode_flag
было равно нулю, поэтому при обычном выполнении программа пропустила бы вызов этой функции. Я вставил вместо инструкции ветвления NOP (заменил её инструкцией, которая ничего не делает), чтобы проверить, что происходит при вызове функции.
В отладчике Dolphin это можно сделать, поставив игру на паузу, нажав правой клавишей на инструкции и выбрав «Insert nop»:
Ничего не произошло. Затем я проверил, что происходит внутри функции, и обнаружил ещё одну конструкцию ветвления, которая обходила всё интересное, происходящее по адресу 803981a8
. Я тоже вставил вместо неё NOP, и в верхнем правом углу экрана появилась буква «D».
В этой функции по адресу 8039816C
(я назвал её zzz_DebugDrawPrint
), есть ещё куча интересного кода, но он не вызывается. Если посмотреть на эту функцию в виде графа, то можно увидеть, что там есть серия операторов ветвления, пропускающих блоки кода на протяжении всей функции:
Вставив NOP вместо нескольких других конструкций ветвления, я начал видеть на экране разные интересные вещи:
Следующий вопрос заключался в том, как активировать этот функционал отладки без изменения кода.
Кроме того, в некоторых конструкциях ветвления в этой функции отрисовки отладки снова встречается zurumode_flag
. Я добавил ещё один патч, чтобы в zurumode_update
флагу zurumode_flag
всегда присваивалось значение 2, потому что когда он не сравнивается с 0, то сравнивается конкретно со значением 2.
После перезапуска игры я увидел в правом верхнем углу экрана такое сообщение «msg. no».
Число 687 — это идентификатор записи самого последнего отображавшегося сообщения. Я проверил его с помощью программы просмотра таблиц, которую написал в самом начале анализа, но вы можете проверить его также с помощью редактора строковых таблиц с полным GUI, который я написал для хакинга ROM-ов. Вот как выглядит это сообщение в редакторе:
В этот момент стало понятно, что от исследования zuru mode уже было не отвертеться — он непосредственно связан с функциями отладки игры.
Снова возвращаемся к Zuru mode
zurumode_init
инициализирует несколько вещей:
0xC(padmgr_class)
присваивается значение адресаzurumode_callback
0x10(padmgr_class)
присваивается значение адреса самогоpadmgr_class
0x4(zuruKeyCheck)
присваивается значение последнего бита в слове, загруженном из0x3C(osAppNMIBuffer)
.
Я разобрался, что такое padmgr
, это сокращение от «gamepad manager». Это значит, что возможно существование особого сочетания клавиш (кнопок), которое можно ввести на геймпаде для активации zuru mode, или какого-то отладочного устройства или функции консоли разработчика, которую можно использовать для отправки сигнала для его активации.
zurumode_init
выполняется только при первой загрузке игры (при нажатии кнопки reset он не срабатывает).
Установив точку останова по адресу 8040efa4
, в котором происходит присвоение значения 0x4(zuruKeyCheck)
, мы можем увидеть, что при загрузке без нажатия клавиш присваивается значение 0. Если заменить его на 1, то происходит интересная вещь:
В правом верхнем углу снова появляется буква «D» (на этот раз зелёная, а не жёлтая), а также отображается некая информация сборки:
[CopyDate: 02/08/01 00:16:48 ]
[Date: 02-07-31 12:52:00]
[Creator:SRD@SRD036J]
Патч, всегда устанавливающий в начале для 0x4(zuruKeyCheck)
значение 1, выглядит так:
8040ef9c 38c00001
Похоже, что это правильный способ инициализации zuru mode. После этого могут понадобиться различные действия, чтобы добиться отображения определённой отладочной информации. Запустив игру, погуляв по ней и поговорив с деревенским жителем, мы не увидим никаких упомянутых выше сообщений (за исключением буквы «D» в углу).
Наиболее вероятными подозреваемыми являются zurumode_update
и zurumode_callback
.
zurumode_update
zurumode_update
впервые вызывается в zurumode_init
, а затем постоянно вызывается функцией zurumode_callback
.
Она снова проверяет последний бит 0x3C(osAppNMIBuffer)
и потом на основании этого значения обновляет zurumode_flag
.
Если бит равен нулю, то флагу присваивается значение нуля.
Если нет, то выполняется следующая инструкция, при этом полным значением 0x3c(osAppNMIBuffer)
является r5
:
extrwi r3, r5, 1, 28
Она извлекает 28-й бит из r5
и сохраняет его в r3
.
Затем к результату прибавляется 1, то есть конечный результат всегда равен 1 или 2.
Потом zurumode_flag
сравнивается с предыдущим результатом, зависящим от того, сколько из 28-х и последних битов задано в 0x3c(osAppNMIBuffer)
: 0, 1 или 2.
Это значение записывается zurumode_flag
. Если оно ничего не меняет, то функция завершает работу и возвращает текущее значение флага. Если оно изменяет значение, то выполняется гораздо более сложная цепочка блоков кода.
Выводится сообщение на японском: то самое «Значение zurumode_flag сменилось с %d на %d», о котором мы говорили выше.
Затем вызывается серия функций с разными аргументами, зависящими от того, стал ли флаг равным нулю, или нет. Ассемблерный код этой части однообразен, поэтому я покажу её псевдокод:
if (flag_changed_to_zero) {
JC_JUTAssertion_changeDevice(2)
JC_JUTDbPrint_setVisible(JC_JUTDbPrint_getManager(), 0)
} else if (BIT(nmiBuffer, 25) || BIT(nmiBuffer, 31)) {
JC_JUTAssertion_changeDevice(3)
JC_JUTDbPrint_setVisible(JC_JUTDbPrint_getManager(), 1)
}
Заметьте, что если флаг равен нулю, то JC_JUTDbPrint_setVisible
передаётся агрумент 0.
Если флаг не равен нулю И бит 25 или бит 31 заданы в 0x3C(osAppNMIBuffer)
, то функции setVisible
передаётся аргумент 1.
Это первый ключ к активации zuru mode: последний бит 0x3C(osAppNMIBuffer)
должен иметь значение 1, чтобы отобразить отладочную информацию и присвоить zurumode_flag
ненулевое значение.
zurumode_callback
zurumode_callback
находится по адресу 8040ee74
и вероятно вызывается функцией, связанной с геймпадом. После вставки точки останова в отладчике Dolphin стек вызовов показывает нам, что она и в самом деле вызывается из padmgr_HandleRetraceMsg
.
Одно из первых её действий — выполнение zerucheck_key_check
. Эта функция сложна, но похоже, что в целом она предназначена для считывания и обновления значения zuruKeyCheck
. Прежде чем переходить к функции keycheck, я решил проверить, как это значение используется в остальной части функции callback.
Затем она снова проверяет какие-то биты в 0x3c(osAppNMIBuffer)
. Если бит 26 задан, или если бит 25 задан и padmgr_isConnectedController(1)
возвращает ненулевое значение, то последнему биту в 0x3c(osAppNMIBuffer)
присваивается значение 1!
Если не задан ни один из этих битов, или бит 25 задан, но padmgr_isConnectedController(1)
возвращает 0, то функция проверяет, равен ли байт по адресу 0x4(zuruKeyCheck)
нулю. Если равен, то она обнуляет последний бит в исходном значении и записывает его обратно в 0x3c(osAppNMIBuffer)
. Если нет, то она всё равно присваивает последнему биту значение 1.
В псевдокоде это выглядит так:
x = osAppNMIBuffer[0x3c]
if (BIT(x, 26) || (BIT(x, 25) && isConnectedController(1)) || zuruKeyCheck[4] != 0) {
osAppNMIBuffer[0x3c] = x | 1 // set last bit
} else {
osAppNMIBuffer[0x3c] = x & ~1 // clear last bit
}
После этого, если бит 26 не задан, функция переходит к вызову zurumode_update
, а затем завершает работу.
Если бит задан, тогда если 0x4(zuruKeyCheck)
не равно нулю, то она загружает строку формата, в которой выводит следующее: «ZURU %d/%d».
Подведём промежуточный итог
Вот, что происходит:
padmgr_HandleRetraceMsg
вызывает zurumode_callback
. Я предполагаю, что это «handle retrace message» означает, что она просто сканирует нажатия клавиш контроллера. При каждом сканировании она может вызывать серию различных callback.
При выполнении zurumode_callback
она проверяет текущие нажатия клавиш (кнопок). Похоже, она проверяет конкретную кнопку или сочетание кнопок.
Последний бит в NMI Buffer обновляется в зависимости от конкретных битов в его текущем значении, а также от значения одного из байтов zuruKeyCheck
(0x4(zuruKeyCheck)
).
Затем выполняется zurumode_update
и проверяет этот бит. Если он равен 0, то флагу zuru mode присваивается значение 0. Если он равен 1, то флаг режима изменяется на 1 или 2, в зависимости от того, задан ли бит 28.
Существует три способа активации zuru mode:
- Бит 26 задан в
0x3C(osAppNMIBuffer)
- Бит 25 задан в
0x3C(osAppNMIBuffer)
и контроллер подключен к порту 2 0x4(zuruKeyCheck)
не равен нулю
osAppNMIBuffer
Заинтересовавшись тем, что значит osAppNMIBuffer
, я начал искать «NMI» и нашёл в контексте Nintendo ссылки на «non-maskable interrupt» (немаскируемое прерывание). Оказывается, имя этой переменной целиком упоминается в документации разработчика для Nintendo 64:
osAppNMIBuffer — это 64-байтный буфер, очищаемый при холодном перезапуске. Если система перезагружается из-за NMI, состояние этого буфера не меняется.
По сути, это небольшой фрагмент памяти, сохраняемый при «мягком» перезапуске (кнопкой reset). Игра может использовать этот буфер для хранения любых данных, пока консоль включена в сеть. Оригинальная Animal Crossing была выпущена на Nintendo 64, поэтому логично, что в коде должно было появиться нечто подобное.
Если мы перейдём к двоичному файлу boot.dol
(всё показанное выше находилось в foresta.rel
), то в его функции main
есть множество ссылок на osAppNMIBuffer
. При беглом просмотре видно, что есть серия проверок, которая может привести к заданию значений разных битов 0x3c(osAppNMIBuffer)
с помощью операций OR.
Интересными могут оказаться следующие значения операндов OR:
- Бит 31: 0x01
- Бит 30: 0x02
- Бит 29: 0x04
- Бит 28: 0x08
- Бит 27: 0x10
- Бит 26: 0x20
Мы помним, что биты 25, 26 и 28 особо интересны: 25 и 26 определяют, включён ли zuru mode, а бит 28 определяет уровень флага (1 или 2).
Бит 31 тоже интересен, но похоже, что он изменяется в зависимости от значений остальных.
Бит 26
Первым делом: по адресу 800062e0
есть инструкция ori r0, r0, 0x20
со значением буфера в 0x3c
. Она задаёт бит 26, что всегда приводит ко включению zuru mode.
Чтобы бит был задан, восьмой байт, возвращаемый от DVDGetCurrentDiskID
, должен быть равен 0x99
. Этот идентификатор расположен в самом начале образа диска игры, и загружается в память по адресу 80000000
. В обычном розничном релизе игры ID выглядит так:
47 41 46 45 30 31 00 00 GAFE01..
Заменив патчем последний байт идентификатора на 0x99
, мы получим при запуске игры следующую картину:
А в консоли ОС выводится следующее:
06:43:404 HWEXI_DeviceIPL.cpp:339 N[OSREPORT]: ZURUMODE2 ENABLE
08:00:288 HWEXI_DeviceIPL.cpp:339 N[OSREPORT]: osAppNMIBuffer[15]=0x00000078
Все другие патчи можно убрать, после чего в правом верхнем углу экрана снова появится буква D, но никакие отладочные сообщения больше не активируются.
Бит 25
Бит 25 используется совместно с выполнением проверки порта контроллера 2. Что приводит к его включению?
Оказывается, он должен использовать ту же проверку, что и для бита 28: версия должна быть больше или равна 0x90
. Если бит 26 задан (ID равен 0x99
), то оба этих бита также будут заданы, а zuru mode всё равно активируется.
Однако если версия находится в интервале от 0x90
до 0x98
, то zuru mode не включается мгновенно. Вспомним проверку, выполняемую в zurumode_callback
— режим будет включен, только если задан бит 25 и padmgr_isConnectedController(1)
возвращает ненулевое значение.
После подключения контроллера к порту 2 (аргумент isConnectedController
имеет нулевую индексацию) активируется zuru mode. На начальном экране появляются буква D и информация о сборке, а мы… можем управлять отображением отладки с помощью кнопок второго контроллера!
Некоторые кнопки выполняют действия, не только изменяющие отображение, но и, например, увеличивающие скорость игры.
zerucheck_key_check
Последней загадкой остаётся 0x4(zuruKeyCheck)
. Оказывается, что это значение обновляется огромной сложной функцией, которую я показывал выше:
С помощью отладчика эмулятора Dolphin мне удалось определить, что значение, проверяемое этой функцией — это набор битов, соответствующий нажатиям кнопок на втором контроллере.
Отслеживание нажатий кнопок хранится в 16-битном значении в 0x2(zuruKeyCheck)
. Когда контроллер не подключен, значение равно 0x7638
.
2 байта, содержащих флаги нажатий кнопок контроллера загружаются и затем обновляются в начале zerucheck_key_check
. Новое значение передаётся с регистром r4
функцией padmgr_HandleRetraceMsg
, когда она вызывает функцию callback.
Ближе к концу zerucheck_key_check
есть ещё одно место, где обновляется 0x4(zuruKeyCheck)
. Оно не появлялось в списке перекрёстных ссылок потому, что использует в качестве базового адреса r3
, и мы можем узнать значение r3
только посмотрев на то, какое значение ему присваивается перед вызовом этой функции.
По адресу 8040ed88
значение r4
записывается в 0x4(zuruKeyCheck)
. Прямо перед этим но записывается из того же места и затем XOR-ится с 1. Задача этой операции — переключать значение байта (а на самом деле — последнего бита) между 0 и 1. (Если значение 0, то результат
XOR с 1 будет 1. Если значение 1, то результат будет 0. См. таблицу истинности для XOR.)
Раньше, когда я изучал значения в памяти, я не замечал этого поведения, но попробую разбить эту инструкцию в отладчике, чтобы понять, что происходит. Исходное значение загружается по адресу 8040ed7c
.
Не касаясь кнопок контроллеров, я не попаду в эту точку останова на начальном экране. Чтобы попасть в этот блок кода, значение r5
должно стать равным 0xb
до инструкции ветвления, которая идёт перед точкой останова (8040ed74
). Из множества различных путей, ведущих к этому блоку, только один присваивает r5
значение 0xb
перед ней, по адресу 8040ed68
.
Заметьте, что для того, чтобы достичь блока, присваивающего r5
значение 0xB
, прямо перед этим значение r0
должно быть равно 0x1000
. Следуя по блокам вверх по цепочке до начала функции, мы можем увидеть все ограничения, необходимые для достижения этого блока:
- 8040ed74: значение
r5
должно быть равно0xB
- 8040ed60: значение
r0
должно быть равно0x1000
- 8040ebe8: значение
r5
должно быть равно0xA
- 8040ebe4: значение
r5
должно быть меньше0x5B
- 8040eba4: значение
r5
должно быть больше0x7
- 8040eb94: значение
r6
должно быть равно 1 - 8040eb5c: значение
r0
должно не быть равно 0 - 8040eb74: значения кнопок порта 2 должны измениться
Здесь мы достигли точки, в которой загружаются старые значения кнопок и сохраняются новые значения. Затем идёт пара операций, применяемая к новым и старым значениям:
old_vals = old_vals XOR new_vals
old_vals = old_vals AND new_vals
Операция XOR помечает все биты, изменившиеся между двумя значениями. Затем операция AND маскирует новый ввод, чтобы установить в состояние 0 все биты, которые в данным момент не заданы. Результатом в r0
является набор новых битов (нажатий кнопок) в новом значении. Если он не пуст, то мы на верном пути.
Чтобы r0
имела значение 0x1000
, должен измениться четвёртый из 16 битов отслеживания кнопок. Вставив точку останова после операции XOR/AND, я выяснил, что такое состояние вызывает кнопка START.
Следующий вопрос заключается в том, как получить r5
, чтобы она изначально была равна 0xA
. r5
и r6
загружаются из 0x0(zuruKeyCheck)
в начале функции проверки клавиш и обновляются ближе к концу, когда мы не попадаем в блок кода, включающий 0x4(zuruKeyCheck)
.
Есть несколько мест перед этим, где r5
присваивается значение 0xA
:
8040ed50
8040ed00
8040ed38
8040ed38
8040ed34
: значениеr0
должно быть равно0x4000
(нажата кнопка B)8040ebe0
: значениеr5
должно быть равно0x5b
8040eba4
: значениеr5
должно быть больше0x7
- дальше всё идёт как раньше…
r5
должна начинаться с 0x5b
8040ed00
8040ecfc
: значениеr0
должно быть равно0xC000
(нажаты A и B)8040ebf8
: значениеr5
должно быть >= 98040ebf0
: значениеr5
должно быть меньше 108040ebe4
: значениеr5
должно быть меньше0x5b
8040eba4
:r5
должно быть больше0x7
- дальше всё идёт как раньше…
r5
должна начинаться с 9
8040ed50
8040ed4c
: значениеr0
должно быть равно0x8000
(нажата кнопка A)8040ec04
: значениеr5
должно быть меньше0x5d
8040ebe4
: значениеr5
должно быть больше0x5b
8040eba4
: значениеr5
должно быть больше0x7
- дальше всё идёт как раньше…
r5
должна начинаться с 0x5c
Похоже, существует какое-то состояние между нажатиями кнопок, после чего необходимо ввести определённую последовательность комбо из кнопок, заканчивающееся нажатием на START. Похоже, что A и/или B должны идти прямо перед START.
Если проследить путь кода, задающего r5
значение 9, то возникает паттерн: r5
— это увеличивающееся значение, которое может или увеличиться, когда в r0
находится подходящее значение, или обнулиться. Самые странные случаи, когда это не значение в интервале от 0x0
до 0xB
, возникают при обработке шагов с несколькими кнопками, например, при одновременном нажатии A и B. Человек, пытающийся ввести это комбо, обычно не может нажать обе кнопки в точно в одинаковое время при отслеживании нажатий геймпада, поэтому приходится обрабатывать ту из кнопок, которая нажата первой.
Продолжаем исследовать разные пути кода:
r5
принимает значение 9, когда нажата RIGHT по адресу8040ece8
.r5
принимает значение 8, когда нажата правая кнопка C по адресу8040eccc
.r5
принимает значение 7, когда нажата левая кнопка C по адресу8040ecb0
.r5
принимает значение 6, когда нажата LEFT по адресу8040ec98
.r5
принимает значение 5 (а r6 принимает значение 1), когда нажата DOWN по адресу8040ec7c
.r5
принимает значение 4, когда нажата верхняя кнопка C по адресу8040ec64
.r5
принимает значение 3, когда нажата нижняя кнопка C по адресу8040ec48
.r5
принимает значение 2, когда нажата UP по адресу8040ec30
.r5
принимает значение 1 (аr6
принимает значение 1), когда нажата Z по адресу8040ec1c
.
Текущая последовательность имеет вид:
Z, UP, C-DOWN, C-UP, DOWN, LEFT, C-LEFT, C-RIGHT, RIGHT, A+B, START
Перед проверкой Z проверяется ещё одно условие: хотя новая нажатая кнопка должна быть Z, текущие флаги должны быть равны 0x2030
: должны быть также нажаты левый и правый бамперы (они имеют значения 0x10
и 0x20
). Кроме того, UP/DOWN/LEFT/RIGHT — это кнопки D-pad, а не аналогового стика.
Чит-код
Полное комбо имеет такой вид:
- Удерживаем бамперы L+R и нажимаем Z
- D-UP
- C-DOWN
- C-UP
- D-DOWN
- D-LEFT
- C-LEFT
- C-RIGHT
- D-RIGHT
- A+B
- START
Работает! Подключите контроллер ко второму порту и введите код, после чего появится информация отладки. После этого можно начать нажимать кнопки на втором (или даже третьем) контроллере, чтобы выполнять разные действия.
Это комбо будет работать без патчинга номера версии игры. Его можно даже использовать в обычной розничной копии игры без каких-либо читерских инструментов или модов консоли. Повторный ввод комбо отключает режим zuru mode.
Сообщение «ZURU %d/%d» в zurumode_callback
используется для вывода состояния этой комбинации, если вы вводите её, когда ID диска уже равен 0x99
(вероятно, в целях отладки самого чит-кода). Первое число — это ваша текущая позиция в последовательности, соответствующая r5
. Второе принимает значение 1, когда удерживаются определённые кнопки последовательности, они могут соответствовать тому, когда r6
присваивается значение 1.
Большинство сообщений не объясняет, что они делают на экране, поэтому чтобы понять их предназначение, необходимо найти обрабатывающие их функции. Например, длинная строка из синих и красных звёздочек в верхней части экрана — это заполнители для отображения состояния различных квестов. Когда квест активен, там появляются какие-то числа, сообщающие о состоянии квеста.
Чёрный экран, отображаемый при нажатии на Z — это консоль для вывода отладочных сообщений, а конкретно для низкоуровневых аспектов, таких как выделение памяти, ошибки кучи и другие плохие исключения. По поведению fault_callback_scroll
можно предположить, что она используется для отображения этих ошибок до перезапуска системы. Она не запускает никакие из этих ошибок, но может заставить их вывести пару мусорных символов с несколькими NOP. Думаю, в дальнейшем это будет очень полезно для вывода собственных сообщений отладки:
Проделав всё это, я выяснил, что попадание в режим отладки патчингом ID
версии на 0x99
уже известно другим людям: https://tcrf.net/Animal_Crossing#Debug_Mode. (Также по ссылке есть хорошие примечания о том, что обозначают различные сообщения, и рассказывается о других вещах, которые можно сделать с контроллером в порте 3.) Однако, насколько я знаю, чит-комбинацию пока никто не публиковал.
На этом всё. Есть и другие функции разработчика, которые я бы хотел исследовать, такие как отладочный экран карты и экран выбора эмулятора NES, и способы их активации без использования патчей.
Кроме того, я опубликую статьи о реверс-инжиниринге систем диалогов, событий и квестов с целью создания модов.
Автор: PatientZero