jamchamb’s blog

Im letzten Sommer begann ich mit dem Reverse Engineering von Animal Crossing für den GameCube, um die Möglichkeit zu erkunden, Mods für das Spiel zu erstellen. Außerdem wollte ich den Prozess dokumentieren, um Tutorials für Leute zu erstellen, die sich für ROM-Hacking und Reverse-Engineering interessieren.In diesem Beitrag untersuche ich die Entwickler-Debugging-Funktionen, die noch im Spiel vorhanden sind, und wie ich eine Cheat-Kombination entdeckt habe, mit der man sie freischalten kann.

Neuer_Debug-Modus

Als ich mir einige übriggebliebene Debug-Symbole ansah, bemerkte ich Funktionen und Variablennamen, die das Wort „Debug“ enthielten, und dachte, es wäre interessant zu sehen, welche Debug-Funktionen noch im Spiel vorhanden sein könnten. Wenn es irgendwelche Debugging- oder Entwicklerfunktionen gäbe, die ich aktivieren könnte, könnte das auch bei der Erstellung von Mods helfen.

Die erste Funktion, die ich mir ansah, war new_Debug_mode. Sie wird von der Funktion entry aufgerufen, die direkt nach dem Ende des Nintendotrademark-Bildschirms läuft. Alles, was sie tut, ist eine 0x1C94-Byte-Struktur zuzuweisen und ihren Zeiger zu speichern.

Nach dem Aufruf in entry wird in der zugewiesenen Struktur am Offset 0xD4 ein Wert von 0 gesetzt, kurz bevor mainproc aufgerufen wird.

Disassemblierung der Eingangsfunktion

Um zu sehen, was passiert, wenn der Wert nicht Null ist, habe ich die Anweisung li r0, 0 bei 80407C8C auf li r0, 1 gepatcht. Die Rohbytes für die Anweisung li r0, 0 sind 38 00 00 00, wobei der zugewiesene Wert am Ende der Anweisung steht, so dass man dies einfach in 38 00 00 01 ändern kann, um li r0, 1 zu erhalten. Für einen zuverlässigeren Weg, Anweisungen zusammenzustellen, könntest du etwas wie kstool verwenden:

$ kstool ppc32be "li 0, 1"li 0, 1 = 

Diesen Patch kannst du im Dolphin-Emulator anwenden, indem du auf die Registerkarte „Patches“ in den Spieleinstellungen gehst und ihn wie folgt eingibst:

Debug performance meter

Wenn man diesen Wert auf 1 setzt, erscheint am unteren Rand des Bildschirms eine interessant aussehende Grafik:

Debug performance meter

Es sah aus wie eine Leistungsanzeige, mit den kleinen Balken am unteren Rand des Bildschirms, die wuchsen und schrumpften. (Als ich später die Namen der Funktionen nachschlug, die den Graphen zeichnen, stellte ich fest, dass sie tatsächlich Metriken für CPU- und Speichernutzung anzeigen. Wenn man den Wert auf über 1 setzt, wird mytown nicht mehr geladen, also scheint es nicht viel zu tun zu geben.

Zuru-Modus

Ich fing an, mir andere Verweise auf debug-bezogene Dinge anzusehen, und sah, dass etwas namens „Zuru-Modus“ ein paar Mal auftauchte. Verzweigungen zu Codeblöcken mit Debug-Funktionalität überprüften oft eine Variable namens zurumode_flag.

Funktion

In der oben abgebildeten Funktion game_move_first wird zzz_LotsOfDebug (ein von mir erfundener Name) nur aufgerufen, wenn zurumode_flag ungleich Null ist.

Wenn man nach Funktionen sucht, die sich auf diesen Wert beziehen, findet man Folgendes:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

Auf den ersten Blick sind sie alle etwas undurchsichtig, da sie an verschiedenen Bits an Offsets in einer Variablen namens osAppNMIBuffer herumfummeln.Hier ein erster Blick darauf, was diese Funktionen tun:

zurumode_init

  • Setzen Sie zurumode_flag auf 0
  • Prüfen Sie einige Bits in osAppNMIBuffer
  • Speichern Sie einen Zeiger auf die Funktion zurumode_callback in der Struktur padmgr
  • Aufrufen Sie zurumode_update

zurumode_update

  • Prüfe einige Bits in osAppNMIBuffer
  • Aktualisiere unbedingt zurumode_flag basierend auf diesen Bits
  • Drucke einen Format-String auf der OS-Konsole aus.

Normalerweise ist so etwas nützlich, um dem Code einen Kontext zu geben, aber es gab eine Reihe von nicht druckbaren Zeichen in der Zeichenkette. Der einzige erkennbare Text war „zurumode_flag“ und „%d“.

zuru mode format string

In der Annahme, dass es sich um japanischen Text mit einer Multi-Byte-Zeichenkodierung handelt, habe ich die Zeichenkette durch ein Tool zur Erkennung von Zeichenkodierungen laufen lassen und herausgefunden, dass sie Shift-JIS-kodiert ist. Die übersetzte Zeichenfolge bedeutet lediglich „zurumode_flag hasbeen changed from %d to %d“. Das bringt zwar keine neuen Informationen, aber das Wissen um die Verwendung von Shift-JIS schon, da es in den Binärdateien und Stringtabellen viele weitere Zeichenfolgen gibt, die diese Kodierung verwenden.

zurumode_callback

  • Aufrufe zerumode_check_keycheck
  • Prüfe einige Bits in osAppNMIBuffer
  • Drucke Wert von zurumode_flag irgendwo
  • Aufrufe zurumode_update

zerumode_check_keycheck tauchten vorher wegen der unterschiedlichen Schreibweise nicht auf.

zerumode_check_keycheck

Eine riesige, komplexe Funktion, die noch viel mehr Bittricks mit Werten ohne Namen macht.An diesem Punkt beschloss ich, mich zurückzuziehen und nach anderen debug-bezogenen Funktionen und Variablen zu suchen, da ich mir nicht einmal sicher war, was die Bedeutung von zuru mode war. Ich war mir auch nicht sicher, was „key check“ hier bedeutete. Könnte es ein kryptographischer Schlüssel sein?

Zurück zum Debugging

Um diese Zeit bemerkte ich, dass es ein Problem mit der Art und Weise gab, wie ich die Debugsymbole in IDA lud. Die foresta.map-Datei auf der Spieldiskette enthält eine Reihe von Adressen und Namen für Funktionen und Variablen. Ich hatte zunächst nicht bemerkt, dass die Adressen für jeden Abschnitt bei Null anfingen, also habe ich ein einfaches Skript erstellt, um einen Namenseintrag für jede Zeile in der Datei hinzuzufügen.

Ich habe einige neue IDA-Skripte erstellt, um das Laden der Symbolkarte für die verschiedenen Abschnitte des Programms zu korrigieren:.text, .rodata, .data und .bss. Im .text-Abschnitt befinden sich alle Funktionen, also habe ich das Skript so eingestellt, dass es automatisch Funktionen an jeder Adresse erkennt, wenn ich diesmal einen Namen einstelle.

Für die Datenabschnitte habe ich es so eingestellt, dass es ein Segment für jedes Binärobjekt erstellt (z. B. m_debug.o, was kompilierter Code für etwas namens m_debug wäre) und Platz und Namen für jedes Datenstück einrichtet.Damit habe ich viel mehr Informationen als vorher, obwohl ich jetzt den Datentyp für jedes Datenobjekt manuell definieren musste, da ich jedes Datenobjekt als einfaches Byte-Array festgelegt habe. (Im Nachhinein wäre es besser gewesen, zumindest davon auszugehen, dass alle Daten mit einer Größe, die ein Vielfaches von 4 Bytes ist, 32-Bit-Ganzzahlen enthalten, da es so viele davon gibt und viele Adressen zu Funktionen und Daten enthalten, die für den Aufbau von Querverweisen wichtig sind.)

Beim Durchsehen des neuen .bss-Segments für m_debug_mode.o sah ich einige Variablen wie quest_draw_status undevent_status. Diese sind interessant, weil ich den Debug-Modus dazu bringen möchte, etwas Nützlicheres als das Leistungsdiagramm anzuzeigen. Glücklicherweise gab es Querverweise von diesen Dateneinträgen zu einem großen Codestück, das debug_print_flg überprüft.

Mit dem Dolphin-Debugger setzte ich einen Haltepunkt in der Funktion, in der debug_print_flg überprüft wird (bei 8039816C), um zu sehen, wie die Überprüfung funktioniert. Der Haltepunkt wurde nie erreicht.

Lassen Sie uns prüfen, warum: Diese Funktion wird von game_debug_draw_last aufgerufen. Raten Sie, welcher Wert vor dem bedingten Aufruf geprüft wird? zurumode_flag. Was zum Teufel ist das?

Zurumode_flag-Prüfung

Ich habe einen Haltepunkt auf diese Prüfung gesetzt (80404E18) und es hat sofort funktioniert. Der Wert von zurumode_flag war Null, so dass der Aufruf dieser Funktion normalerweise übersprungen würde. Ich habe die Verzweigungsanweisung NOPped (durch eine Anweisung ersetzt, die nichts tut), um zu sehen, was passiert, wenn die Funktion aufgerufen wird.

Im Dolphin-Debugger kann man dies tun, indem man das Spiel pausiert, mit der rechten Maustaste auf eine Anweisung klickt und dann auf „Nop einfügen“ klickt:

Dolphin-Debugger NOPping

Nichts passiert. Dann überprüfte ich, was innerhalb der Funktion geschah, und fand eine weitere Verzweigungsanweisung, die bei 803981a8 an allen interessanten Dingen vorbeiführen könnte. Ich löschte auch diese Anweisung, und der Buchstabe „D“ erschien in der oberen rechten Ecke des Bildschirms.

Debug-Modus-Buchstabe D

Es gab einen Haufen interessanteren Code in dieser Funktion bei 8039816C (ich nannte sie zzz_DebugDrawPrint), aber nichts davon wurde aufgerufen. Wenn Sie sich die grafische Darstellung dieser Funktion ansehen, können Sie sehen, dass es eine Reihe von Verzweigungsanweisungen gibt, die Blöcke in der gesamten Funktion überspringen:

Verzweigungen in zzz_DebugDrawPrint

Durch NOPping von mehr dieser Verzweigungsanweisungen konnte ich feststellen, dass verschiedene Dinge auf den Bildschirm gedruckt wurden:

Mehr Debug-Zeug wird gedruckt

Die nächste Frage ist, wie man diese Debug-Funktionen aktivieren kann, ohne den Code zu ändern.Außerdem erscheint zurumode_flag wieder für einige Verzweigungsanweisungen, die in dieser Debug-Draw-Funktion gemacht wurden. ich habe einen weiteren Patch hinzugefügt, so dass zurumode_flag immer auf 2 in zurumode_update gesetzt wird, weil es normalerweise speziell mit 2 verglichen wird, wenn es nicht mit 0 verglichen wird.Nach dem Neustart des Spiels sah ich diese „msg. no“-Meldung oben rechts auf dem Bildschirm.

Message number display

Die Nummer 687 ist die Eintrags-ID der zuletzt angezeigten Nachricht. Ich habe dies mit einem einfachen Tabellenbetrachter überprüft, den ich in einem frühen Stadium erstellt habe, aber Sie können es auch mit einem vollständigen GUI-Stringtabelleneditor überprüfen, den ich für das ROM-Hacking erstellt habe. So sieht die Meldung im Editor aus:

Meldung 687 im Stringtabellen-Editor

An diesem Punkt war klar, dass das Herausfinden des Zuru-Modus nicht mehr zu vermeiden war; es fügte sich direkt in die Debugging-Funktionen des Spiels ein.

Zuru-Mode revisited

Wenn man zu zurumode_init zurückkehrt, werden einige Dinge initialisiert:

  • 0xC(padmgr_class) wird auf die Adresse von zurumode_callback gesetzt
  • 0x10(padmgr_class) wird auf die Adresse von padmgr_class selbst gesetzt
  • 0x4(zuruKeyCheck) wird auf das letzte Bit eines von 0x3C(osAppNMIBuffer) geladenen Wortes gesetzt.

Ich habe nachgeschaut, was padmgr bedeutet, und es ist die Abkürzung für „Gamepad Manager“. Das deutet darauf hin, dass es eine spezielle Tastenkombination gibt, die auf dem Gamepad eingegeben werden muss, um den Zuru-Modus zu aktivieren, oder möglicherweise ein spezielles Debugging-Gerät oder eine Entwicklungskonsolenfunktion, die verwendet werden kann, um ein Signal zur Aktivierung zu senden.

zurumode_init läuft nur, wenn das Spiel zum ersten Mal geladen wird (das Drücken des Reset-Knopfes löst ihn nicht aus).

Wenn wir einen Haltepunkt bei 8040efa4 setzen, wo 0x4(zuruKeyCheck) gesetzt ist, können wir sehen, dass er während des Bootens, ohne irgendwelche Tasten zu drücken, auf 0 gesetzt wird. Ersetzt man diesen Wert durch 1, passiert etwas Interessantes:

Titelbildschirm mit Zuru-Modus

Der Buchstabe „D“ erscheint wieder in der oberen rechten Ecke (diesmal grün statt gelb), und es gibt auch einige Build-Informationen:

Ein Patch, um 0x4(zuruKeyCheck) beim Start immer auf 1 zu setzen:

8040ef9c 38c00001

Das scheint der richtige Weg zu sein, um den Zuru-Modus zu initialisieren. Danach kann es verschiedene Aktionen geben, die wir durchführen müssen, um bestimmte Debug-Informationen anzuzeigen. Wenn man das Spiel startet, herumläuft und mit einem Dorfbewohner spricht, wird keine der zuvor erwähnten Anzeigen angezeigt (außer dem Buchstaben „D“ in der Ecke).

Die wahrscheinlichsten Verdächtigen sind zurumode_update und zurumode_callback.

zurumode_update

zurumode_update wird zuerst von zurumode_init aufgerufen und wird dann wiederholt vonzurumode_callback aufgerufen.

Es prüft das letzte Bit von 0x3C(osAppNMIBuffer) erneut und aktualisiert dann zurumode_flagauf der Grundlage seines Wertes.

Wenn das Bit Null ist, wird das Flag auf Null gesetzt.

Wenn nicht, wird der folgende Befehl ausgeführt, wobei r5 der volle Wert von 0x3c(osAppNMIBuffer) ist:

extrwi r3, r5, 1, 28

Dies extrahiert das 28. Bit aus r5 und speichert es in r3.Dann wird 1 zu dem Ergebnis addiert, so dass das Endergebnis immer 1 oder 2 ist.

zurumode_flag wird dann mit dem vorherigen Ergebnis verglichen, je nachdem, wie viele der 28. und letzten Bits in 0x3c(osAppNMIBuffer) gesetzt sind: 0, 1 oder 2.

Dieser Wert wird in zurumode_flag geschrieben. Wenn sich nichts geändert hat, endet die Funktion und gibt den aktuellen Wert des Flags zurück. Wenn sie den Wert ändert, wird eine viel komplexere Kette von Codeblöcken ausgeführt.

Eine Meldung auf Japanisch wird ausgegeben: dies ist die bereits erwähnte Meldung „zurumode_flag has been changed from %d to %d“

Dann wird eine Reihe von Funktionen mit unterschiedlichen Argumenten aufgerufen, je nachdem, ob das Flag auf Null geändert wurde oder nicht. Die Assemblierung für diesen Teil ist mühsam, daher sieht der Pseudocode wie folgt aus:

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)}

Wenn das Flag auf Null gesetzt ist, erhält JC_JUTDbPrint_setVisible ein Argument von 0.Wenn das Flag nicht auf Null gesetzt ist UND Bit 25 oder Bit 31 in 0x3C(osAppNMIBuffer) gesetzt sind, erhält die Funktion setVisible ein Argument von 1.

Dies ist der erste Schlüssel zur Aktivierung des Zuru-Modus: Das letzte Bit von 0x3C(osAppNMIBuffer) muss auf 1 gesetzt werden, um die Debug-Anzeigen sichtbar zu machen und zurumode_flag auf einen Wert ungleich Null zu setzen.

zurumode_callback

zurumode_callback befindet sich bei 8040ee74 und wird wahrscheinlich von einer Funktion aufgerufen, die mit dem Gamepad zusammenhängt. Wenn man im Dolphin-Debugger einen Haltepunkt setzt, zeigt der Callstack, dass die Funktion tatsächlich von padmgr_HandleRetraceMsg aufgerufen wird.

Eines der ersten Dinge, die sie tut, ist die Ausführung von zerucheck_key_check. Es ist komplex, aber insgesamt scheint es den Wert von zuruKeyCheck zu lesen und dann zu aktualisieren. Ich beschloss zu sehen, wie dieser Wert im Rest der Callback-Funktion verwendet wird, bevor ich mich weiter mit der Keycheck-Funktion befasse.

Als nächstes werden einige Bits in 0x3c(osAppNMIBuffer) erneut überprüft. Wenn Bit 26 gesetzt ist, oder wenn Bit 25 gesetzt ist und padmgr_isConnectedController(1) einen Wert ungleich Null liefert, wird das letzte Bit in 0x3c(osAppNMIBuffer) auf 1 gesetzt!

Wenn keines dieser Bits gesetzt ist, oder wenn Bit 25 zumindest gesetzt ist, aber padmgr_isConnectedController(1) 0 liefert, wird geprüft, ob das Byte bei 0x4(zuruKeyCheck) 0 ist. Wenn ja, wird das letzte Bit im ursprünglichen Wert auf Null gesetzt und zurück nach 0x3c(osAppNMIBuffer) geschrieben.Wenn nicht, dann wird das letzte Bit trotzdem auf 1 gesetzt.

In Pseudocode sieht das so aus:

x = osAppNMIBufferif (BIT(x, 26) || (BIT(x, 25) && isConnectedController(1)) || zuruKeyCheck != 0) { osAppNMIBuffer = x | 1 // set last bit} else { osAppNMIBuffer = x & ~1 // clear last bit}

Wenn Bit 26 nicht gesetzt ist, wird der Aufruf von zurumode_update abgekürzt und dann beendet.

Wenn es gesetzt ist und 0x4(zuruKeyCheck) nicht Null ist, wird ein Formatstring geladen, in dem es aussieht, als würde es ausgedruckt: „ZURU %d/%d“.

Recap

So passiert es:

padmgr_HandleRetraceMsg Ruft die zurumode_callback auf.Meine Vermutung ist, dass „handle retrace message“ bedeutet, dass es gerade die Tasten des Controllers gescannt hat. Jedes Mal, wenn es scannt, kann es eine Reihe von verschiedenen Callbacks aufrufen.

Wenn zurumode_callback läuft, prüft es die aktuellen Tastendrücke.Dies scheint für eine bestimmte Taste oder Kombination von Tasten zu überprüfen.

Das letzte Bit im NMI-Puffer wird auf der Grundlage bestimmter Bits in seinem aktuellen Wert sowie dem Wert eines der zuruKeyCheck-Bytes (0x4(zuruKeyCheck)) aktualisiert.

Dann wird zurumode_update ausgeführt und überprüft dieses Bit. Wenn es 0 ist, wird das zuru-Modus-Flag auf 0 gesetzt. Wenn es 1 ist, wird das Modus-Flag auf 1 oder 2 aktualisiert, je nachdem, ob Bit 28 gesetzt ist.

Die drei Möglichkeiten zur Aktivierung des Zuru-Modus sind:

  1. Bit 26 ist in 0x3C(osAppNMIBuffer) gesetzt
  2. Bit 25 ist in 0x3C(osAppNMIBuffer) gesetzt, und ein Controller ist an Port 2 angeschlossen
  3. 0x4(zuruKeyCheck) ist nicht Null

osAppNMIBuffer

Ich habe mich gefragt, was osAppNMIBuffer ist, Ich begann mit der Suche nach „NMI“ und fand Verweise auf „non-maskable interrupt“ im Zusammenhang mit Nintendo. Es stellte sich heraus, dass der gesamte Variablenname auch in der Entwicklerdokumentation für das Nintendo 64 auftaucht:

osAppNMIBuffer ist ein 64-Byte-Puffer, der bei einem Kaltstart gelöscht wird. Wenn das System aufgrund eines NMI neu startet, bleibt dieser Puffer unverändert.

Grundsätzlich ist dies ein kleines Stück Speicher, das über Soft-Reboots hinweg bestehen bleibt. Ein Spiel kann diesen Puffer verwenden, um zu speichern, was immer es will, solange die Konsole eingeschaltet ist.Das ursprüngliche Animal Crossing-Spiel wurde auf dem Nintendo 64 veröffentlicht, also macht es Sinn, dass so etwas im Code auftaucht.

Wenn man zur boot.dol-Binärdatei wechselt (alles oben ist von foresta.rel), gibt es eine Menge Verweise auf osAppNMIBuffer in der main-Funktion. Ein kurzer Blick zeigt, dass es eine Reihe von Prüfungen gibt, die dazu führen können, dass verschiedene Bits von 0x3c(osAppNMIBuffer) mit ODER-Operationen gesetzt werden.

Interessante OR-Operandenwerte, auf die man achten sollte, wären:

  • Bit 31: 0x01
  • Bit 30: 0x02
  • Bit 29: 0x04
  • Bit 28: 0x08
  • Bit 27: 0x10
  • Bit 26: 0x20

Denken Sie daran, dass die Bits 25, 26 und 28 besonders interessant sind: 25 und 26 bestimmen, ob der Zuru-Modus aktiviert ist, und Bit 28 bestimmt die Höhe des Flags (1 oder 2).Bit 31 ist ebenfalls interessant, scheint aber in erster Linie auf der Grundlage der Werte der anderen aktualisiert zu werden.

Bit 26

Erstens: bei 800062e0 gibt es eine ori r0, r0, 0x20-Anweisung für den Pufferwert bei 0x3c. Dadurch wird Bit 26 gesetzt, was immer dazu führt, dass der Zuru-Modus aktiviert wird.

Setzen von Bit 26

Damit das Bit gesetzt werden kann, muss das achte Byte, das von DVDGetCurrentDiskID zurückgegeben wird, 0x99 sein.Diese ID befindet sich ganz am Anfang des Disk-Images des Spiels und wird bei 80000000 in den Speicher geladen. Bei einer regulären Verkaufsversion des Spiels sieht die ID wie folgt aus:

47 41 46 45 30 31 00 00 GAFE01..

Wenn man das letzte Byte der ID auf 0x99 setzt, passiert beim Starten des Spiels Folgendes:

Spieleversions-ID 0x99

Und in der Betriebssystemkonsole wird Folgendes ausgegeben:

06:43:404 HW\EXI_DeviceIPL.cpp:339 N: ZURUMODE2 ENABLE08:00:288 HW\EXI_DeviceIPL.cpp:339 N: osAppNMIBuffer=0x00000078

Alle anderen Patches können entfernt werden, und der Buchstabe D erscheint auch wieder in der rechten oberen Ecke des Bildschirms, aber keine der anderen Debug-Anzeigen ist aktiviert.

Bit 25

Bit 25 wird in Verbindung mit der Durchführung der Port-2-Controller-Prüfung verwendet. Was führt dazu, dass es aktiviert wird?

Bit 25 und 28

Es stellt sich heraus, dass es sich um die gleiche Prüfung handelt, die für Bit 28 verwendet wird: die Version muss größer oder gleich 0x90 sein. Wenn Bit 26 gesetzt war (ID ist 0x99), werden beide Bits ebenfalls gesetzt und der Zuru-Modus wird trotzdem aktiviert.

Wenn die Version zwischen 0x90 und 0x98 liegt, wird der Zuru-Modus jedoch nicht sofort aktiviert.Wie in zurumode_callback geprüft, wird er nur aktiviert, wenn Bit 25 gesetzt ist und padmgr_isConnectedController(1) einen Wert ungleich Null liefert.Sobald ein Controller an Port 2 angeschlossen ist (das Argument für isConnectedController ist null-indiziert), wird der Zuru-Modus aktiviert. Der Buchstabe D und die Build-Infos werden auf dem Titelbildschirm angezeigt, und…das Drücken von Knöpfen auf dem zweiten Controller steuert die Debug-Anzeigen!

Einige Knöpfe tun auch andere Dinge als die Anzeige zu ändern, wie zum Beispiel die Geschwindigkeit des Spiels zu erhöhen.

zerucheck_key_check

Das letzte Rätsel ist 0x4(zuruKeyCheck). Es stellt sich heraus, dass dieser Wert durch die zuvor gezeigte komplexe Funktion aktualisiert wird:

zerumode_check_keycheck

Mit Hilfe des Dolphin-Debuggers konnte ich feststellen, dass der von dieser Funktion geprüfte Wert ein Satz von Bits ist, die den Tastendrücken auf dem zweiten Controller entsprechen. Die Tastendruckspur wird in einem 16-Bit-Wert bei 0x2(zuruKeyCheck) gespeichert. Wenn kein Controller angeschlossen ist, lautet der Wert 0x7638.

Die 2 Bytes mit den Flags für die Tastendrücke des Controllers 2 werden geladen und dann in der Nähe des Anfangs von zerucheck_key_check aktualisiert. Der neue Wert wird mit dem Register r4 von padmgr_HandleRetraceMsg übergeben, wenn es die Callback-Funktion aufruft.

Tastenprüfungsende

Unten am Ende von zerucheck_key_check gibt es noch eine Stelle, an der 0x4(zuruKeyCheck) aktualisiert wird. Sie erscheint nicht in der Liste der Querverweise, weil sie r3 als Basisadresse verwendet, und wir können nur herausfinden, was r3 ist, wenn wir uns ansehen, auf was sie jedes Mal gesetzt wird, wenn diese Funktion aufgerufen wird.

Ab 8040ed88 wird der Wert von r4 in 0x4(zuruKeyCheck) geschrieben. Er wird von der gleichen Stelle geladen und dann kurz davor mit 1 XORd. Dadurch wird der Wert des Bytes (eigentlich nur das letzte Bit) zwischen 0 und 1 hin- und hergeschaltet. (Wenn es 0 ist, ist das Ergebnis der XOR-Verknüpfung mit 1 eine 1. Wenn es 1 ist, ist das Ergebnis 0. Schauen Sie in der Wahrheitstabelle für XOR nach, um dies zu sehen.)

Schlüsselprüfung Ende

Ich habe dieses Verhalten nicht bemerkt, als ich die Speicherwerte vorher beobachtete, aber ich werde versuchen, diese Anweisung im Debugger zu brechen, um zu sehen, was passiert. Der ursprüngliche Wert wird bei 8040ed7c geladen.

Ohne dass ich irgendwelche Tasten auf den Controllern berühre, treffe ich diesen Haltepunkt während des Titelbildschirms nicht. Um diesen Codeblock zu erreichen, muss der Wert von r5 0xb vor der davor liegenden Verzweigungsanweisung (8040ed74) liegen. Von den vielen verschiedenen Pfaden, die zu diesem Block führen, gibt es einen, der r5 auf 0xb setzt, und zwar bei 8040ed68.

Setzen von r5 auf 0xb

Beachte, dass r0 kurz vorher gleich 0x1000 gewesen sein muss, um den Block zu erreichen, der r5 auf 0xB setzt. Folgt man den Blöcken in der Kette bis zum Anfang der Funktion, so sieht man die Bedingungen, die notwendig sind, um diesen Block zu erreichen:

  • 8040ed74: r5 muss 0xB sein
  • 8040ed60: r0 muss 0x1000 sein
  • 8040ebe8: r5 muss 0xA
  • 8040ebe4 sein: r5 muss kleiner sein als 0x5B
  • 8040eba4: r5 muss größer sein als 0x7
  • 8040eb94: r6 muss 1 sein
  • 8040eb5c: r0 darf nicht 0 sein
  • 8040eb74: Port 2 button values must have changed

Tracing the code path

Hier erreichen wir den Punkt, an dem die alten Tastenwerte geladen und die neuen Werte gespeichert werden. Danach gibt es eine Reihe von Operationen, die auf die neuen und alten Werte angewendet werden:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

Die XOR-Operation markiert alle Bits, die sich zwischen den beiden Werten geändert haben. Die UND-Operation maskiert dann die neue Eingabe, um alle Bits zu löschen, die derzeit nicht gesetzt sind. Das Ergebnis in r0 ist die Menge der neuen Bits (Tastendrucke) im neuen Wert. Wenn es nicht leer ist, sind wir auf dem richtigen Weg.

Damit r0 0x1000 ist, muss sich das vierte der 16 Bits der Tastenspur gerade geändert haben.Durch Setzen eines Haltepunkts nach der XOR/AND-Operation kann ich herausfinden, welcher Tastendruck dies verursacht: es ist die START-Taste.

Die nächste Frage ist, wie man r5 dazu bringt, als 0xA zu beginnen. r5 und r6 werden zu Beginn der Tastenprüfungsfunktion aus0x0(zuruKeyCheck) geladen und gegen Ende aktualisiert, wenn wir den Codeblock verlassen, der 0x4(zuruKeyCheck) umschaltet.

Es gibt ein paar Stellen, kurz bevor r5 auf 0xA gesetzt wird:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
  • 8040ed34: r0 muss 0x4000 sein (B-Taste wurde gedrückt)
  • 8040ebe0: r5 muss 0x5b sein
  • 8040eba4: r5 muss größer sein als 0x7
  • Ab hier dasselbe wie vorher…

r5 muss bei 0x5b

8040ed00

  • 8040ecfc beginnen: r0 muss 0xC000 sein (A und B gedrückt)
  • 8040ebf8: r5 muss >= 9
  • 8040ebf0 sein: r5 muss kleiner als 10 sein
  • 8040ebe4: r5 muss kleiner sein als 0x5b
  • 8040eba4: r5 muss größer sein als 0x7
  • Ab hier dasselbe wie vorher…

r5 muss bei 9 beginnen

8040ed50
  • 8040ed4c: r0 muss 0x8000 sein (A wurde gedrückt)
  • 8040ec04: r5 muss kleiner sein als 0x5d
  • 8040ebe4: r5 muss größer sein als 0x5b
  • 8040eba4: r5 muss größer sein als 0x7
  • ab hier dasselbe wie vorher…

r5 muss bei 0x5c

Es scheint eine Art Zustand zwischen den Tastendrücken zu geben, und dann muss eine bestimmte Abfolge von Tastenkombinationen eingegeben werden, die mit START endet. Es scheint, als kämen A und/oder B kurz vor START.

Wenn man dem Codepfad folgt, der r5 auf 9 setzt, ergibt sich ein Muster: r5 ist ein inkrementeller Wert, der entweder erhöht werden kann, wenn der richtige Wert für den Tastendruck in r0 gefunden wird, oder auf 0 zurückgesetzt werden kann. Die seltsameren Fälle, in denen es sich nicht um einen Wert zwischen 0x0 und 0xB handelt, treten bei der Verarbeitung von Schritten mit mehreren Tasten auf, z. B. beim gleichzeitigen Drücken von A und B. Eine Person, die versucht, diese Kombination einzugeben, wird in der Regel nicht beide Tasten genau zur gleichen Zeit drücken, wenn der Pad-Trace auftritt, so dass eine der beiden Tasten vor der anderen gedrückt werden muss.

Weiter mit den verschiedenen Code-Pfaden:

  • r5 wird auf 9 gesetzt, wenn RECHTS bei 8040ece8 gedrückt wird.
  • r5 wird auf 8 gesetzt, wenn C-Stick rechts bei 8040eccc gedrückt wird.
  • r5 wird auf 7 gesetzt, wenn C-Stick links bei 8040ecb0 gedrückt wird.
  • r5 wird auf 6 gesetzt, wenn LINKS bei 8040ec98 gedrückt wird.
  • r5 wird auf 5 gesetzt (und r6 auf 1), wenn AB bei 8040ec7c gedrückt wird.
  • r5 wird auf 4 gesetzt, wenn C-Stick oben bei 8040ec64 gedrückt wird.
  • r5 wird auf 3 gesetzt, wenn C-Stick unten bei 8040ec48 gedrückt wird.
  • r5 wird auf 2 gesetzt, wenn UP bei 8040ec30 gedrückt wird.
  • r5 wird auf 1 gesetzt (und r6 auf 1), wenn Z bei 8040ec1c gedrückt wird.

Die aktuelle Sequenz ist:

Z, UP, C-DOWN, C-UP, DOWN, LEFT, C-LEFT, C-RIGHT, RIGHT, A+B, START

Vor der Z-Prüfung wird noch eine weitere Bedingung geprüft: während die neu gedrückte Taste Z sein muss, müssen die aktuellen Flags 0x2030 sein: die linken und rechten Bumper müssen ebenfalls gedrückt sein (sie haben die Werte 0x10 und 0x20). Außerdem sind die Tasten UP/DOWN/LEFT/RIGHT die Tasten des D-Pads, nicht des Analogsticks.

Der Cheat-Code

Die vollständige Combo lautet:

  1. L+R Stoßstangen halten und Z drücken
  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

Es funktioniert! Schließen Sie einen Controller an den zweiten Port an und geben Sie den Code ein, woraufhin die Debug-Anzeigen erscheinen. Danach kannst du die Tasten des zweiten (oder sogar dritten) Controllers drücken, um Dinge zu tun.

Diese Kombination funktioniert, ohne dass die Versionsnummer des Spiels gepatcht werden muss, und du kannst sie sogar bei einer regulären Kopie des Spiels ohne Cheat-Tools oder Konsolenmods verwenden. Wenn du die Kombination ein zweites Mal eingibst, wird der Zuru-Modus wieder ausgeschaltet.

Wenn du den Code auf einem echten GameCube verwendest

Die Meldung „ZURU %d/%d“ in zurumode_callback wird verwendet, um den Status dieser Kombination auszudrucken, wenn du sie eingibst, während die Disketten-ID bereits 0x99 ist (vermutlich um den Cheat-Code selbst zu debuggen). Die erste Zahl ist deine aktuelle Position in der Sequenz, die mit r5 übereinstimmt. Die zweite Zahl wird auf 1 gesetzt, wenn bestimmte Tasten in der Sequenz gedrückt werden; dies könnte der Fall sein, wenn r6 auf 1 gesetzt ist.

Die meisten Anzeigen erklären nicht, was sie auf dem Bildschirm sind, also muss man die Funktionen finden, die sie bedienen, um herauszufinden, was sie tun. Die lange Reihe von blauen und roten Rastern, die oben auf dem Bildschirm erscheinen, sind Platzhalter für die Anzeige des Status verschiedener Quests. Wenn eine Quest aktiv ist, erscheinen dort einige Zahlen, die den Status der Quest anzeigen.

Der schwarze Bildschirm, der erscheint, wenn du Z drückst, ist eine Konsole zum Drucken von Debug-Meldungen, aber speziell für Low-Level-Sachen wie Speicherzuweisungs- und Heap-Fehler oder andere schlechte Ausnahmen. Das Verhalten von fault_callback_scroll deutet darauf hin, dass diese Fehler angezeigt werden, bevor das System neu gebootet wird. Ich habe keinen dieser Fehler ausgelöst, aber ich konnte ihn dazu bringen, ein paar Müllzeichen mit einigen NOPs zu drucken. Ich denke, dass dies sehr nützlich wäre, um später benutzerdefinierte Debug-Meldungen zu drucken:

JUTConsole garbage characters

Nachdem ich das alles gemacht habe, habe ich herausgefunden, dass der Debug-Modus durch das Patchen der Versions-ID auf 0x99 bereits bekannt ist: https://tcrf.net/Animal_Crossing#Debug_Mode. (Es gibt auch einige gute Hinweise auf die verschiedenen Anzeigen und weitere Dinge, die man mit einem Controller in Port 3 tun kann.)Soweit ich weiß, wurde die Cheat-Kombination allerdings noch nicht veröffentlicht.

Das war’s für diesen Beitrag. Es gibt noch einige weitere Entwickler-Features, die ich gerne erforschen würde, wie z.B. den Debug-Map-Bildschirm und den NES-Emulator-Auswahlbildschirm, und wie man sie ohne Patches aktivieren kann.

Map-Auswahlbildschirm

Ich werde auch Berichte über die Umkehrung der Dialog-, Ereignis- und Quest-Systeme zum Zweck der Mod-Erstellung veröffentlichen.

Update: Die Folien für den Vortrag, den ich darüber gehalten habe, finden Sie hier.