Last summer, I started reverse engineering of the Animal Crossing game for GameCube. I wanted to explore the possibility of creating mods for this game. In addition, I wanted to document the process to create tutorials for people interested in ROM hacking and reverse engineering. In this post, I’ll talk about the developer’s debugging features that were left in the game, and I’ll also share how I found a cheat combo that can be unlocked.
new_Debug_mode
Studying the remaining debugging symbols, I noticed the names of the functions and variables containing the word “debug”, and decided that it would be interesting to see if some debugging functionality remained in the game. If I manage to activate the debugging or development functions, it will help me in the process of creating mods.
The first function I noticed was
new_Debug_mode
. It is called by the
entry
function, which starts immediately after the completion of the screen with the Nintendo logo. All that it does is that it places the byte structure
0x1C94
and stores a pointer to it.
After its call to the
entry
in the placed structure at offset
0xD4
immediately before the call,
mainproc
is set to 0.
To see what happens when the value is not zero, I patched the
li r0, 0
instruction at
80407C8C
, replacing it with
li r0, 1
. The raw bytes of the
li r0, 0
instruction are
38 00 00 00
, where the assigned value is at the end of the instruction, so I could just replace the bytes with
38 00 00 01
and get
li r0, 1
. As a more reliable way of assembling instructions, you can use something like
kstool
:
$ kstool ppc32be "li 0, 1"
li 0, 1 = [ 38 00 00 01 ]
In the Dolphin emulator, this patch can be applied by going to the “Patches” tab in the game properties and entering it as follows:
After assigning a value of 1, an interesting graph appeared at the bottom of the screen:
It looked like an indicator of performance: the small bars at the bottom of the screen increased and diminished. (Later, when I looked at the names of the functions that render this graph, I found that they actually display the CPU and memory usage metrics.)
It was great, but not very helpful. After assigning a value of 1, my city stopped loading, so nothing could be done here.
Zuru mode
I again began to look for other references to debugging functions, and several times I came across something called “zuru mode”. The branches of code blocks with debugging functionality often checked the
zurumode_flag
variable.
zzz_LotsOfDebug
(the name I came up with myself) in the above function
game_move_first
is called only when
zurumode_flag
not zero.
Looking for functions related to this value, I found these:
zurumode_init
zurumode_callback
zurumode_update
zurumode_cleanup
At first glance, their purpose is mysterious; they juggle bits in the offset of a variable called
osAppNMIBuffer
.
Here is how the functions of these functions looked at first glance:
zurumode_init
- Sets
zurumode_flag
to 0 - Checks several bits in
osAppNMIBuffer
- Stores the pointer to the
zurumode_callback
function in the padmgr
structure - Causes
zurumode_update
zurumode_update
- Checks several bits in
osAppNMIBuffer
- Depending on the value of these bits, updates
zurumode_flag
- Displays a format string in the OS console.
This is usually useful for giving context to the code, but there were a lot of non-printing characters in the string. The only recognizable text was "zurumode_flag" and "% d".
Assuming that it could be Japanese text with multibyte character encoding, I skipped the string through the encoding recognition tool and found out that the string is Shift-JIS encoded. In translation, the line simply meant "The value of zurumode_flag changed from% d to% d." This does not give us a lot of new information, but now we know that Shift-JIS is used: in binary files and tables of rows there are much more lines in this encoding.
zurumode_callback
- Calls
zerumode_check_keycheck
- Checks several bits in
osAppNMIBuffer
- Somewhere displays the value of
zurumode_flag
- Causes
zurumode_update
zerumode_check_keycheck
until we met because of a different writing ... what is it?
A huge complex function that does much more work on bits with values without names.
At this point, I decided to take a step back and explore other debugging functions and variables, because I was not sure about the importance of zuru mode. In addition, I did not understand what “key check” means here. Is it possible that this is a cryptographic key?
Back to debug
Around this time, I noticed a problem with my way of loading debug symbols in IDA. The
foresta.map
file on the game disk contains many addresses and names of functions and variables. At first, I did not see that the addresses for each section start anew from scratch, so I wrote a simple script that adds a name entry for each line of the file.
I wrote new IDA scripts to fix the loading of symbol tables for different sections of the program:
.text
,
.rodata
,
.data
and
.bss
. In the
.text
section there are all functions, so I made sure that this time, when specifying the name, the script automatically recognized the functions at each address.
In the data sections, he now created a segment for each binary object (for example,
m_debug.o
, which was supposed to be compiled code for something called
m_debug
), and specified the space and names for each piece of data.
This gave me a lot more information, but I had to manually set the data type for each data fragment, because I set each data object as a simple byte array. (Looking back, I understand that it would be better to assume that the multiple 4 byte fragments contained 32-bit integers, because there were a lot of them, and many contained addresses of functions and data important for building cross-references.)
Studying the new
.bss
segment for the presence of
m_debug_mode.o
, I discovered several variables of the form
quest_draw_status
and
event_status
. This is interesting because I wanted to display useful information in debug mode, not just a performance graph. Fortunately, of these data records, there were cross-references to a huge piece of code that checks
debug_print_flg
.
Using the debugger in the Dolphin emulator, I set a breakpoint at the function where
debug_print_flg
checked (at
8039816C
) in order to understand how this check works. But the program never went to this breakpoint.
Let's
game_debug_draw_last
why this happens: this function is called
game_debug_draw_last
. Guess what value is checked before its conditional call?
zurumode_flag
! What the hell is going on?
I set a breakpoint on this check (
80404E18
) and it immediately worked. The value of
zurumode_flag
was zero, so in normal execution the program would have missed calling this function. I inserted NOP instead of the branch instruction (replaced it with an instruction that does nothing) to check what happens when the function is called.
In the Dolphin debugger, this can be done by pausing the game, right-clicking on the instructions and selecting “Insert nop”:
Nothing has happened. Then I checked what was going on inside the function and found another branching construct that went around all the interesting things happening at
803981a8
. I also inserted a NOP instead, and the letter “D” appeared in the upper right corner of the screen.
In this function at
8039816C
(I called it
zzz_DebugDrawPrint
), there is a bunch of interesting code, but it is not called. If you look at this function in the form of a graph, you can see that there is a series of branch operators that pass code blocks throughout the entire function:
Having inserted NOP instead of several other branching constructions, I began to see various interesting things on the screen:
The next question was how to activate this debugging functionality without changing the code.
In addition, in some branch constructions,
zurumode_flag
is again found in this debug drawing function. I added one more patch so that the
zurumode_update
flag
zurumode_flag
always
zurumode_update
to
zurumode_flag
value 2, because when it is not compared with 0, it is compared specifically with value 2.
After restarting the game, I saw the following message “msg. no.
The number 687 is the record ID of the most recently displayed message. I checked it with a table viewer, which I wrote at the very beginning of the analysis, but you can also check it with the help of a
string table editor with a full GUI , which I wrote for hacking ROMs. This is what the message looks like in the editor:
At that moment, it became clear that the study of zuru mode could no longer get away - it is directly related to the debugging functions of the game.
Back to Zuru mode again.
zurumode_init
initializes several things:
0xC(padmgr_class)
assigned the value of the address zurumode_callback
0x10(padmgr_class)
assigned the value of the address itself padmgr_class
0x4(zuruKeyCheck)
assigned the value of the last bit in a word loaded from 0x3C(osAppNMIBuffer)
.
I figured out what
padmgr
, a short for gamepad manager. This means that there may be a special key combination (buttons) that you can enter on the gamepad to activate zuru mode, or some kind of debugging device or developer console function, which you can use to send a signal to activate it.
zurumode_init
is executed only when the game is first loaded (when the reset button is pressed, it does not work).
By setting a breakpoint at
8040efa4
, at which the value
0x4(zuruKeyCheck)
is assigned
0x4(zuruKeyCheck)
, we can see that when loaded without pressing a key, the value 0 is assigned. If you replace it with 1, an interesting thing happens:
The letter “D” appears again in the upper right corner (this time green, not yellow), and also some assembly information is displayed:
[CopyDate: 02/08/01 00:16:48 ]
[Date: 02-07-31 12:52:00]
[Creator:SRD@SRD036J]
A patch that always sets a value of 1 at the beginning for
0x4(zuruKeyCheck)
looks like this:
8040ef9c 38c00001
It seems that this is the right way to initialize zuru mode. After that, you may need various actions to achieve the display of certain debugging information. Having started the game, having walked along it and having spoken with a villager, we will not see any messages mentioned above (with the exception of the letter “D” in the corner).
The most likely suspects are
zurumode_update
and
zurumode_callback
.
zurumode_update
zurumode_update
first called in
zurumode_init
, and then constantly called by the
zurumode_callback
function.
It again checks the last bit
0x3C(osAppNMIBuffer)
and then updates the
zurumode_flag
based on this value.
If the bit is zero, then the flag is assigned the value zero.
If not, the following instruction is executed, with the full value of
0x3c(osAppNMIBuffer)
being
r5
:
extrwi r3, r5, 1, 28
It extracts the 28th bit from
r5
and saves it to
r3
.
Then 1 is added to the result, that is, the final result is always 1 or 2.
Then the
zurumode_flag
compared with the previous result, depending on how many of the 28 and last bits are specified in
0x3c(osAppNMIBuffer)
: 0, 1, or 2.
This value is written
zurumode_flag
. If it changes nothing, then the function terminates and returns the current value of the flag. If it changes the value, then a much more complex chain of code blocks is executed.
The message is displayed in Japanese: that same “The value of zurumode_flag changed from% d to% d”, which we talked about above.
Then a series of functions is called with different arguments depending on whether the flag has become zero or not. The assembly code of this part is monotonous, so I will show its pseudocode:
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) }
Note that if the flag is zero, then
JC_JUTDbPrint_setVisible
number 0 is passed to JC_JUTDbPrint_setVisible.
If the flag is not zero and bit 25 or bit 31 is set to
0x3C(osAppNMIBuffer)
, then argument 1 is passed to the
setVisible
function.
This is the first key to activate zuru mode: the last bit
0x3C(osAppNMIBuffer)
must be set to 1 in order to display debug information and set the
zurumode_flag
non-zero value.
zurumode_callback
zurumode_callback
is located at
8040ee74
and is probably called by a function associated with a gamepad. After inserting a breakpoint in the Dolphin debugger, the call stack shows us that it is actually called from
padmgr_HandleRetraceMsg
.
One of its first actions is executing
zerucheck_key_check
. This function is complex, but it seems that in general it is designed to read and update the value of
zuruKeyCheck
. Before moving on to the keycheck function, I decided to check how this value is used in the rest of the callback function.
Then it checks again some bits in
0x3c(osAppNMIBuffer)
. If bit 26 is set, or if bit 25 is set and
padmgr_isConnectedController(1)
returns a non-zero value, then the last bit in
0x3c(osAppNMIBuffer)
assigned the value 1!
If none of these bits are set, or bit 25 is set, but
padmgr_isConnectedController(1)
returns 0, the function checks whether the byte at
0x4(zuruKeyCheck)
is equal to zero. If equal, then it resets the last bit in the original value and writes it back to
0x3c(osAppNMIBuffer)
. If not, it still assigns the value 1 to the last bit.
In pseudocode, it looks like this:
x = osAppNMIBuffer[0x3c] if (BIT(x, 26) || (BIT(x, 25) && isConnectedController(1)) || zuruKeyCheck[4] != 0) { osAppNMIBuffer[0x3c] = x | 1
After that, if bit 26 is not set, the function proceeds to the
zurumode_update
call, and then exits.
If the bit is set, then if
0x4(zuruKeyCheck)
not zero, then it loads a format string in which it displays the following: “ZURU% d /% d”.
Let's summarize
This is what happens:
padmgr_HandleRetraceMsg
calls
zurumode_callback
. I assume that this “handle retrace message” means that it simply scans the keystrokes of the controller. With each scan, it can trigger a series of different callbacks.
When performing
zurumode_callback
it checks the current keystrokes (buttons). It looks like she is testing a specific button or combination of buttons.
The last bit in the NMI Buffer is updated depending on the specific bits in its current value, as well as on the value of one of the
zuruKeyCheck
bytes (
0x4(zuruKeyCheck)
).
Then
zurumode_update
is
zurumode_update
and checks this bit. If it is 0, the value 0 is assigned to the zuru mode flag. If it is 1, the mode flag is changed to 1 or 2, depending on whether bit 28 is set.
There are three ways to activate zuru mode:- Bit 26 is set to
0x3C(osAppNMIBuffer)
- Bit 25 is set to
0x3C(osAppNMIBuffer)
and the controller is connected to port 2 0x4(zuruKeyCheck)
not zero
osAppNMIBuffer
Interested in what
osAppNMIBuffer
means, I began to search for “NMI” and found in the context of Nintendo references to “non-maskable interrupt”. It turns out that the name of this variable is entirely mentioned in the developer documentation for Nintendo 64:
osAppNMIBuffer is a 64-byte buffer, cleared during a cold restart. If the system restarts due to NMI, the status of this buffer does not change.
In fact, this is a small fragment of memory that is saved during a “soft” restart (with the reset button). The game can use this buffer to store any data while the console is online. The original Animal Crossing was released on Nintendo 64, so it’s logical that something like this should appear in the code.
If we go to the binary
boot.dol
file (everything shown above was in
foresta.rel
), then in its
main
function there are many links to
osAppNMIBuffer
. A quick
0x3c(osAppNMIBuffer)
that there is a series of checks that can lead to the
0x3c(osAppNMIBuffer)
different
0x3c(osAppNMIBuffer)
bit
0x3c(osAppNMIBuffer)
using OR operations.
The following OR operand values can be interesting:
- Bit 31: 0x01
- Bit 30: 0x02
- Bit 29: 0x04
- Bit 28: 0x08
- Bit 27: 0x10
- Bit 26: 0x20
We remember that bits 25, 26 and 28 are particularly interesting: 25 and 26 determine whether zuru mode is on, and bit 28 determines the flag level (1 or 2).
Bit 31 is also interesting, but it seems that it changes depending on the values of the others.
Bit 26
First of all: at the address
800062e0
there is an
ori r0, r0, 0x20
instruction with a buffer value of
0x3c
. It sets bit 26, which always leads to the inclusion of zuru mode.
For the bit to be set, the eighth byte returned from the
DVDGetCurrentDiskID
must be
0x99
. This identifier is located at the very beginning of the game disk image, and is loaded into memory at the address
80000000
. In a regular retail game release, the ID looks like this:
47 41 46 45 30 31 00 00 GAFE01..
Replacing with the patch the last byte of the identifier to
0x99
, we get the following picture when starting the game:
And in the OS console, the following is displayed:
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
All other patches can be removed, after which the letter D will reappear in the upper right corner of the screen, but no debugging messages will no longer be activated.
Bit 25
Bit 25 is used in conjunction with checking the port of controller 2. What causes it to turn on?
It turns out that he should use the same check as for bit 28: the version must be greater than or equal to
0x90
. If bit 26 is set (ID is
0x99
), then both of these bits will also be set, and zuru mode is still activated.
However, if the version is in the range from
0x90
to
0x98
, then zuru mode does not turn on instantly. Recall the test performed in
zurumode_callback
— the mode will be enabled only if bit 25 is set
and padmgr_isConnectedController(1)
returns a non-zero value.
After connecting the controller to port 2 (the
isConnectedController
argument is zero indexed), zuru mode is activated. The letter D and the build information appear on the initial screen, and we ... can control the debug display using the buttons of the second controller!
Some buttons perform actions that not only change the display, but also, for example, increase the speed of the game.
zerucheck_key_check
The last mystery remains
0x4(zuruKeyCheck)
. It turns out that this value is updated by a huge complex function, which I showed above:
Using the Dolphin emulator debugger, I managed to determine that the value checked by this function is a set of bits corresponding to the button presses on the second controller.
Button tracking is stored in a 16-bit value in
0x2(zuruKeyCheck)
. When the controller is not connected, the value is
0x7638
.
The 2 bytes containing the controller keystroke flags are loaded and then updated at the beginning of the
zerucheck_key_check
. The new value is transferred with the
r4
register by the function
padmgr_HandleRetraceMsg
when it calls the callback function.
Toward the end of
zerucheck_key_check
there is another place where
0x4(zuruKeyCheck)
updated
0x4(zuruKeyCheck)
. It did not appear in the list of cross references because it uses
r3
as the base address, and we can find out the value of
r3
only by looking at what value is assigned to it before calling this function.
At the address 8040ed88
value is r4
written to 0x4(zuruKeyCheck)
. Right before that, but is recorded from the same place and then XOR-one from 1. The task of this operation is to switch the byte value (and in fact - the last bit) between 0 and 1. (If the value is 0, then theXOR result from 1 will be 1 If the value is 1, then the result will be 0. See the truth table for XOR.), , , , , .
8040ed7c
.
, . ,
r5
0xb
, (
8040ed74
). , ,
r5
0xb
,
8040ed68
.
Note that in order to reach the block that assigns the r5
value 0xB
, the value just before it r0
must be equal 0x1000
. Following the blocks up the chain before the beginning of the function, we can see all the restrictions necessary to achieve this block:- 8040ed74: the value
r5
should be equal0xB
- 8040ed60: the value
r0
should be equal0x1000
- 8040ebe8: value
r5
must be equal0xA
- 8040ebe4: value
r5
should be less0x5B
- 8040eba4: value
r5
should be greater0x7
- 8040eb94: value
r6
must be 1 - 8040eb5c: the value
r0
should not be 0 - 8040eb74: Port 2 button values should change
, . , :
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
:
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
, occur when processing steps with several buttons, for example, by simultaneously pressing A and B. A person trying to enter this combo usually cannot press both buttons at exactly the same time when tracking down the gamepad, so you have to handle the button that is pressed first.We continue to explore different code paths:r5
takes the value 9 when RIGHT is pressed at 8040ece8
.r5
takes the value 8 when the right C button at the address is pressed 8040eccc
.r5
takes the value 7 when the left C button at the address is pressed 8040ecb0
.r5
takes the value 6 when the LEFT at address is pressed 8040ec98
.r5
takes the value 5 (and r6 takes the value 1) when DOWN is pressed at 8040ec7c
.r5
takes the value 4 when the top button C at the address is pressed 8040ec64
.r5
takes the value 3 when the lower C button at the address is pressed 8040ec48
.r5
takes the value 2 when UP is pressed at 8040ec30
.r5
takes the value 1 (and r6
takes the value 1) when Z is pressed at 8040ec1c
.
The current sequence is:Z, UP, C-DOWN, C-UP, DOWN, LEFT, C-LEFT, C-RIGHT, RIGHT, A + B, STARTBefore checking Z, one more condition is checked: although the new pressed button must be Z, the current flags must be equal 0x2030
: the left and right bumpers must also be pressed (they have the values 0x10
and 0x20
). In addition, UP / DOWN / LEFT / RIGHT are D-pad buttons, not analog stick.Cheat code
A complete combo looks like this:- Hold the bumpers L + R and press Z
- D-UP
- C-DOWN
- C-UP
- D-DOWN
- D-LEFT
- C-LEFT
- C-RIGHT
- D-RIGHT
- A + B
- START
Works!
Connect the controller to the second port and enter the code, after which the debug information will appear. After that, you can start pressing the buttons on the second (or even third) controller to perform different actions.This combo will work without patching the version number of the game. It can even be used in a regular retail copy of the game without any cheat tools or console mods. Re-entering the combo disables zuru mode.The message “ZURU% d /% d” is zurumode_callback
used to display the state of this combination if you enter it when the disk ID is already equal 0x99
(probably to debug the cheat code itself). The first number is your current position in the sequence, the corresponding r5
. The second takes the value 1, when certain buttons of a sequence are held, they can correspond to when r6
a value of 1 is assigned.Most of the messages do not explain what they are doing on the screen, so in order to understand their purpose, it is necessary to find the functions that process them. For example, a long line of blue and red stars at the top of the screen are placeholders for displaying the status of various quests. When the quest is active, there appear some numbers indicating the status of the quest., Z — , , , .
fault_callback_scroll
, . , NOP. , :
Having done all this, I found out that getting into the debugging mode by patching the ID of theversion is 0x99
already known to other people: https://tcrf.net/Animal_Crossing#Debug_Mode . (The link also contains good notes on what the various messages mean, and tells about other things that can be done with the controller in port 3.) However, as far as I know, no one has yet published the cheat combination.That's all.
There are other developer features that I would like to explore, such as the debugging screen of the card and the NES emulator selection screen, and ways to activate them without using patches.In addition, I will publish articles on reverse engineering systems of dialogues, events and quests to create mods.