Using the code on a real GameCube

Прошлым летом я приступил к реверс-инжинирингу игры 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.

game_move_first function

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".

zuru mode format string

Предположив, что это может быть японский текст с многобайтовой кодировкой символов, я пропустил строку через инструмент распознавания кодировки и выяснил, что строка закодирована Shift-JIS. В переводе строка просто обозначала «Значение zurumode_flag изменилось с %d на %d». Это не даёт нам особо много новой информации, но зато теперь мы знаем, что используется Shift-JIS: в двоичных файлах и таблицах строк гораздо больше строк в этой кодировке.

zurumode_callback


  • Вызывает zerumode_check_keycheck
  • Проверяет несколько битов в osAppNMIBuffer
  • Куда-то выводит значение zurumode_flag
  • Вызывает zurumode_update

zerumode_check_keycheck пока нам не встречалась из-за другого написания… что же это такое?

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! Какого чёрта происходит?

zurumode_flag check

Я установил точку останова на этой проверке (80404E18) и она сразу же сработала. Значение zurumode_flag было равно нулю, поэтому при обычном выполнении программа пропустила бы вызов этой функции. Я вставил вместо инструкции ветвления NOP (заменил её инструкцией, которая ничего не делает), чтобы проверить, что происходит при вызове функции.

В отладчике Dolphin это можно сделать, поставив игру на паузу, нажав правой клавишей на инструкции и выбрав «Insert nop»:

Dolphin debugger NOPping

Ничего не произошло. Затем я проверил, что происходит внутри функции, и обнаружил ещё одну конструкцию ветвления, которая обходила всё интересное, происходящее по адресу 803981a8. Я тоже вставил вместо неё NOP, и в верхнем правом углу экрана появилась буква «D».

Debug mode letter D

В этой функции по адресу 8039816C (я назвал её zzz_DebugDrawPrint), есть ещё куча интересного кода, но он не вызывается. Если посмотреть на эту функцию в виде графа, то можно увидеть, что там есть серия операторов ветвления, пропускающих блоки кода на протяжении всей функции:

Branches in zzz_DebugDrawPrint

Вставив NOP вместо нескольких других конструкций ветвления, я начал видеть на экране разные интересные вещи:

More debug stuff getting printed

Следующий вопрос заключался в том, как активировать этот функционал отладки без изменения кода.

Кроме того, в некоторых конструкциях ветвления в этой функции отрисовки отладки снова встречается zurumode_flag. Я добавил ещё один патч, чтобы в zurumode_update флагу zurumode_flag всегда присваивалось значение 2, потому что когда он не сравнивается с 0, то сравнивается конкретно со значением 2.

После перезапуска игры я увидел в правом верхнем углу экрана такое сообщение «msg. no».

message number display

Число 687 — это идентификатор записи самого последнего отображавшегося сообщения. Я проверил его с помощью программы просмотра таблиц, которую написал в самом начале анализа, но вы можете проверить его также с помощью редактора строковых таблиц с полным GUI, который я написал для хакинга ROM-ов. Вот как выглядит это сообщение в редакторе:

Message 687 in the string table editor

В этот момент стало понятно, что от исследования 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, то происходит интересная вещь:

Title screen with zuru mode

В правом верхнем углу снова появляется буква «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:

  1. Бит 26 задан в 0x3C(osAppNMIBuffer)
  2. Бит 25 задан в 0x3C(osAppNMIBuffer) и контроллер подключен к порту 2
  3. 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.

Setting bit 26

Чтобы бит был задан, восьмой байт, возвращаемый от DVDGetCurrentDiskID, должен быть равен 0x99. Этот идентификатор расположен в самом начале образа диска игры, и загружается в память по адресу 80000000. В обычном розничном релизе игры ID выглядит так:

47 41 46 45 30 31 00 00 GAFE01..

Заменив патчем последний байт идентификатора на 0x99, мы получим при запуске игры следующую картину:

Game version ID 0x99

А в консоли ОС выводится следующее:

06:43:404 HW\EXI_DeviceIPL.cpp:339 N[OSREPORT]: ZURUMODE2 ENABLE
08:00:288 HW\EXI_DeviceIPL.cpp:339 N[OSREPORT]: osAppNMIBuffer[15]=0x00000078


Все другие патчи можно убрать, после чего в правом верхнем углу экрана снова появится буква D, но никакие отладочные сообщения больше не активируются.

Бит 25

Бит 25 используется совместно с выполнением проверки порта контроллера 2. Что приводит к его включению?

Bit 25 and 28

Оказывается, он должен использовать ту же проверку, что и для бита 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). Оказывается, что это значение обновляется огромной сложной функцией, которую я показывал выше:

zerumode_check_keycheck

С помощью отладчика эмулятора Dolphin мне удалось определить, что значение, проверяемое этой функцией — это набор битов, соответствующий нажатиям кнопок на втором контроллере.

Отслеживание нажатий кнопок хранится в 16-битном значении в 0x2(zuruKeyCheck). Когда контроллер не подключен, значение равно 0x7638.

2 байта, содержащих флаги нажатий кнопок контроллера загружаются и затем обновляются в начале zerucheck_key_check. Новое значение передаётся с регистром r4 функцией padmgr_HandleRetraceMsg, когда она вызывает функцию callback.

key check end

Ближе к концу zerucheck_key_check есть ещё одно место, где обновляется 0x4(zuruKeyCheck). Оно не появлялось в списке перекрёстных ссылок потому, что использует в качестве базового адреса r3, и мы можем узнать значение r3 только посмотрев на то, какое значение ему присваивается перед вызовом этой функции.

По адресу 8040ed88 значение r4 записывается в 0x4(zuruKeyCheck). Прямо перед этим но записывается из того же места и затем XOR-ится с 1. Задача этой операции — переключать значение байта (а на самом деле — последнего бита) между 0 и 1. (Если значение 0, то результат
XOR с 1 будет 1. Если значение 1, то результат будет 0. См. таблицу истинности для XOR.)

key check end

Раньше, когда я изучал значения в памяти, я не замечал этого поведения, но попробую разбить эту инструкцию в отладчике, чтобы понять, что происходит. Исходное значение загружается по адресу 8040ed7c.

Не касаясь кнопок контроллеров, я не попаду в эту точку останова на начальном экране. Чтобы попасть в этот блок кода, значение r5 должно стать равным 0xb до инструкции ветвления, которая идёт перед точкой останова (8040ed74). Из множества различных путей, ведущих к этому блоку, только один присваивает r5 значение 0xb перед ней, по адресу 8040ed68.

Setting r5 to 0xb

Заметьте, что для того, чтобы достичь блока, присваивающего 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 должны измениться

Tracing the code path

Здесь мы достигли точки, в которой загружаются старые значения кнопок и сохраняются новые значения. Затем идёт пара операций, применяемая к новым и старым значениям:

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 должно быть >= 9
  • 8040ebf0: значение r5 должно быть меньше 10
  • 8040ebe4: значение 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, а не аналогового стика.

Чит-код


Полное комбо имеет такой вид:

  1. Удерживаем бамперы L+R и нажимаем Z
  2. D-UP
  3. C-DOWN
  4. C-UP
  5. D-DOWN
  6. D-LEFT
  7. C-LEFT
  8. C-RIGHT
  9. D-RIGHT
  10. A+B
  11. START

Работает! Подключите контроллер ко второму порту и введите код, после чего появится информация отладки. После этого можно начать нажимать кнопки на втором (или даже третьем) контроллере, чтобы выполнять разные действия.

Это комбо будет работать без патчинга номера версии игры. Его можно даже использовать в обычной розничной копии игры без каких-либо читерских инструментов или модов консоли. Повторный ввод комбо отключает режим zuru mode.

Using the code on a real GameCube

Сообщение «ZURU %d/%d» в zurumode_callback используется для вывода состояния этой комбинации, если вы вводите её, когда ID диска уже равен 0x99 (вероятно, в целях отладки самого чит-кода). Первое число — это ваша текущая позиция в последовательности, соответствующая r5. Второе принимает значение 1, когда удерживаются определённые кнопки последовательности, они могут соответствовать тому, когда r6 присваивается значение 1.

Большинство сообщений не объясняет, что они делают на экране, поэтому чтобы понять их предназначение, необходимо найти обрабатывающие их функции. Например, длинная строка из синих и красных звёздочек в верхней части экрана — это заполнители для отображения состояния различных квестов. Когда квест активен, там появляются какие-то числа, сообщающие о состоянии квеста.

Чёрный экран, отображаемый при нажатии на Z — это консоль для вывода отладочных сообщений, а конкретно для низкоуровневых аспектов, таких как выделение памяти, ошибки кучи и другие плохие исключения. По поведению fault_callback_scroll можно предположить, что она используется для отображения этих ошибок до перезапуска системы. Она не запускает никакие из этих ошибок, но может заставить их вывести пару мусорных символов с несколькими NOP. Думаю, в дальнейшем это будет очень полезно для вывода собственных сообщений отладки:

JUTConsole garbage characters

Проделав всё это, я выяснил, что попадание в режим отладки патчингом ID
версии на 0x99 уже известно другим людям: https://tcrf.net/Animal_Crossing#Debug_Mode. (Также по ссылке есть хорошие примечания о том, что обозначают различные сообщения, и рассказывается о других вещах, которые можно сделать с контроллером в порте 3.) Однако, насколько я знаю, чит-комбинацию пока никто не публиковал.

На этом всё. Есть и другие функции разработчика, которые я бы хотел исследовать, такие как отладочный экран карты и экран выбора эмулятора NES, и способы их активации без использования патчей.

Map select screen


Кроме того, я опубликую статьи о реверс-инжиниринге систем диалогов, событий и квестов с целью создания модов.

Комментарии (4)


  1. Barafu_Albino_Cheetah
    14.06.2018 19:59

    А где научиться вот так ковырять софт? Кодить на С я умею, а дальше не могу продвинуться: все учебники сходу начинаются с непонятных слов и магических преобразований.


    1. perfect_genius
      14.06.2018 21:37

      Касперски?


  1. sintech
    14.06.2018 20:37

    Спасибо за подробный разбор. А можно уточнить, отладочные символы и комментарии были в бинарниках релизной версии игры?


    1. PatientZero Автор
      14.06.2018 21:58

      Это перевод, но судя по тексту, да, остались в релизной версии.