jamchamb’s blog

Vorige zomer ben ik begonnen met reverse engineering van Animal Crossing voor de GameCube om de mogelijkheid van het maken van mods voor het spel te onderzoeken. Ik wilde ook het proces documenteren om tutorials te maken voor mensen die geïnteresseerd zijn in ROM hacking en reverse engineering. In dit bericht verken ik de developer debugging features die nog in het spel zitten, en hoe ik een cheat combo heb ontdekt die gebruikt kan worden om ze te ontgrendelen.

new_Debug_mode

Terwijl ik aan het rondkijken was naar wat overgebleven debug symbolen, viel mijn oog op functies en variabele namen die het woord “debug” bevatten, en ik dacht dat het interessant zou zijn om te zien welke debug functionaliteit er nog in het spel zou kunnen zitten. Als er debug of developer functies waren die ik kon activeren, zou dat ook kunnen helpen bij het maken van mods.

De eerste functie waar ik naar keek was new_Debug_mode.Hij wordt aangeroepen door de entry functie, die loopt direct nadat het Nintendotrademark scherm is afgelopen. Het enige wat het doet is een 0x1C94 byte structuur toewijzen en de pointer opslaan.

Nadat het wordt aangeroepen in entry, wordt een waarde van 0 gezet op offset 0xD4 in de toegewezen structuur, vlak voordat mainproc wordt aangeroepen.

Demontage van de entry-functie

Om te zien wat er gebeurt als de waarde niet nul is, heb ik de instructie li r0, 0 op80407C8C naar li r0, 1 gepatcht. De ruwe bytes voor de instructie li r0, 0 zijn 38 00 00 00, waarbij de toegekende waarde aan het eind van de instructie staat, dus je kunt dit gewoon veranderen in 38 00 00 01 om li r0, 1 te krijgen. Voor een meer betrouwbare manier om instructies te assembleren, zou je iets als kstool:

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

kunnen gebruiken. Je kunt deze patch toepassen in de Dolphin emulator door naar de “Patches” tab van de speleigenschappen te gaan en het als volgt in te voeren:

Debug prestatiemeter

Debug prestatiemeter

Het leek op een prestatiemeter, waarbij de balkjes onderaan het scherm groeiden en krompen. (Toen ik later de namen opzocht van de functies die de grafiek tekenen, ontdekte ik dat ze in feite metingen weergeven voor CPU en geheugengebruik.)Dit was netjes, maar niet bijzonder nuttig. Het instellen van de waarde boven 1 stopte het laden van mytown, dus het leek er niet op dat er veel anders mee te doen was.

Zuru mode

Ik begon rond te kijken naar andere verwijzingen naar debug-gerelateerde dingen, en zag een paar keer iets opduiken dat “zuru mode” heette. Takken naar codeblokken die debug-functionaliteit hadden, controleerden vaak een variabele genaamd zurumode_flag.

game_move_first-functie

In de hierboven afgebeelde game_move_first-functie wordt zzz_LotsOfDebug (een naam die ik heb verzonnen) alleen aangeroepen als zurumode_flag niet nul is.

Het zoeken naar functies met betrekking tot deze waarde levert het volgende op:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

Op het eerste gezicht zijn ze allemaal een beetje onduidelijk, ze draaien rond verschillende bits op offsets in een variabele met de naam osAppNMIBuffer.Hier is een eerste blik op wat deze functies doen:

zurumode_init

  • Stel zurumode_flag op 0
  • Controleer enkele bits in osAppNMIBuffer
  • Sla een pointer naar de zurumode_callback functie in de padmgr structuur
  • Roep zurumode_update op

zurumode_update

  • Controleer enkele bits in osAppNMIBuffer
  • Conditioneer zurumode_flag op basis van deze bits
  • Print een format string naar de OS console.

Dit soort dingen is meestal nuttig om context aan de code te geven, maar er waren een heleboel onuitdrukbare tekens in de string. De enige herkenbare tekst was “zurumode_flag” en “%d”.

zuru mode format string

In de veronderstelling dat het Japanse tekst zou kunnen zijn met een multi-byte tekencodering, heb ik de string door een tekencoderingsdetectietool gehaald en kwam ik erachter dat het Shift-JIS-gecodeerd was. De vertaalde string betekent alleen “zurumode_flag hasbeen changed from %d to %d”. Dat levert niet veel nieuwe informatie op, maar de kennis over het gebruik van Shift-JIS wel, aangezien er veel meer strings in de binaries en stringtabellen zijn die deze encoding gebruiken.

zurumode_callback

  • Calls zerumode_check_keycheck
  • Check some bits in osAppNMIBuffer
  • Prints value of zurumode_flag somewhere
  • Calls zurumode_update

zerumode_check_keycheck kwam eerder niet tevoorschijn vanwege de verschillende schrijfwijze.Wat is het?

zerumode_check_keycheck

Een enorme complexe functie die nog veel meer bit twiddling doet op waarden zonder namen. Op dit punt besloot ik om terug te trekken en te zoeken naar andere debug-gerelateerde functies en variabelen, omdat ik niet eens zeker wist wat de betekenis van zuru mode was. Ik wist ook niet zeker wat “key check” hier betekende. Zou het een cryptografische sleutel kunnen zijn?

Terug naar debug

Omstreeks deze tijd merkte ik dat er een probleem was met de manier waarop ik de debug symbolen in IDA laadde. Het foresta.map bestand op de game disc bevat een hoop adressen en namen voor functies en variabelen. Het was me in eerste instantie niet opgevallen dat de adressen voor elke sectie bij nul begonnen, dus heb ik een simpel script gemaakt om een naam toe te voegen voor elke regel in het bestand.

Ik heb een aantal nieuwe IDA scripts gemaakt om het laden van de symbol map voor de verschillende secties van het programma op te lossen:.text, .rodata, .data, en .bss. De .text sectie is waar alle functies zijn, dus ik heb het script ingesteld om automatisch functies te detecteren op elk adres bij het instellen van een naam deze keer.

Voor de data secties, heb ik het ingesteld om een segment te maken voor elk binair object (zoals m_debug.o, dat zou gecompileerde code zijn voor iets dat m_debug heet), en ruimte en namen in te stellen voor elk stuk data.Dit geeft me veel meer informatie dan ik eerst had, hoewel ik nu handmatig het datatype voor elk stukje data moest definiëren, omdat ik elk data-object instelde als een eenvoudige byte-array. (Achteraf gezien zou het beter zijn geweest om tenminste aan te nemen dat alle gegevens met een grootte die een veelvoud van 4 bytes is, 32-bitintegers bevatten, omdat er zoveel van zijn, en veel bevatten adressen van functies en gegevens die belangrijk zijn voor het opbouwen van kruisverwijzingen.)

Terwijl ik door het nieuwe .bss segment voor m_debug_mode.o keek, zag ik enkele variabelen zoals quest_draw_status enevent_status. Deze zijn interessant omdat ik debug modus wil krijgen om wat meer bruikbare dingen weer te geven dan de prestatie grafiek. Gelukkig waren er kruisverwijzingen van deze data entries naar een groot stuk code dat debug_print_flg controleert.

Gebruik makend van de Dolphin debugger, heb ik een breekpunt gezet op de functie waar debug_print_flg wordt gecontroleerd (bij 8039816C) om te zien hoe de controle werkt. Het breekpunt is nooit geraakt.

Laten we eens kijken waarom: deze functie wordt aangeroepen door game_debug_draw_last. Raad eens welke waarde wordt gecontroleerd voordat deze functie voorwaardelijk wordt aangeroepen? zurumode_flag. Wat is dat nou weer?

zurumode_flag check

Ik heb een breekpunt ingesteld op die check (80404E18) en het brak meteen. De waarde van zurumode_flag was nul, dus het zou normaal gesproken het aanroepen van deze functie overslaan. Ik heb de branch instructie NOPpped (vervangen door een instructie die niets doet) om te zien wat er zou gebeuren als de functie wel wordt aangeroepen.

In de Dolphin debugger kun je dit doen door het spel te pauzeren, rechts te klikken op een instructie, en dan te klikken op “Insert nop”:

Dolphin debugger NOPping

Er gebeurde niets. Toen controleerde ik wat er in de functie gebeurde, en vond een andere vertakking die een kortsluiting kon maken langs alle interessante dingen bij 803981a8. Ik heb dat ook verwijderd, en de letter “D” verscheen in de rechterbovenhoek van het scherm.

Debug-modus letter D

Er stond nog veel meer interessant uitziende code in deze functie op 8039816C (ik heb het zzz_DebugDrawPrint genoemd), maar niets daarvan werd aangeroepen. Als u naar de grafische weergave van deze functie kijkt, kunt u zien dat er een reeks vertakkingsstatements is die blokken door de hele functie overslaan:

Vertakkingen in zzz_DebugDrawPrint

Door meer van deze vertakkingen te negeren, zie ik dat er andere dingen op het scherm worden afgedrukt:

Er worden meer debug-dingen afgedrukt

De volgende vraag is hoe ik deze debug-functies kan activeren zonder de code te wijzigen.Ook verschijnt zurumode_flag weer voor sommige branch statements gemaakt in deze debug draw functie.Ik heb nog een patch toegevoegd zodat zurumode_flag altijd op 2 wordt gezet in zurumode_update, omdat het meestal specifiek met 2 wordt vergeleken als het niet met 0 wordt vergeleken.Na het herstarten van het spel, zag ik deze “msg. no” melding rechtsboven in het scherm.

message number display

Het nummer 687 is entry ID van het meest recent weergegeven bericht. Ik heb dit gecontroleerd met een simpletable viewer die ik in het begin gemaakt heb, maar je kunt het ook controleren met een volledige GUI string tabel editor die ik gemaakt heb voor ROM hacking. Hier zie je hoe het bericht eruit ziet in de editor:

Bericht 687 in de string tabel editor

Op dit punt was het duidelijk dat het uitzoeken van de zuru-modus niet langer te vermijden was; het ging rechtstreeks over de debugging-functies van het spel.

Zuru mode revisited

Terugkerend naar zurumode_init, initialiseert het een paar dingen:

  • 0xC(padmgr_class) is ingesteld op het adres van zurumode_callback
  • 0x10(padmgr_class) is ingesteld op het adres van padmgr_class zelf
  • 0x4(zuruKeyCheck) is ingesteld op het laatste bit van een woord geladen uit 0x3C(osAppNMIBuffer).

Ik heb gekeken wat padmgr betekent, en het is een afkorting voor “gamepad manager”. Dit suggereert dat er een speciale toetsencombinatie op de gamepad kan zijn om de zuru-modus te activeren, of mogelijk een speciaal debug-apparaat of ontwikkelconsole-functie die kan worden gebruikt om een signaal te sturen om het te activeren.

zurumode_init loopt alleen de eerste keer dat het spel wordt geladen (op de reset-knop drukken activeert het niet).

Als we een breekpunt instellen op 8040efa4, waar 0x4(zuruKeyCheck) is ingesteld, kunnen we zien dat tijdens het opstarten zonder een toets ingedrukt te houden, het op 0 wordt gezet. Door dit te vervangen door 1 gebeurt er iets interessants:

Titelscherm met zuru-modus

De letter “D” verschijnt weer in de rechterbovenhoek (groen in plaats van geel deze keer), en er is ook wat build info:

Een patch om 0x4(zuruKeyCheck) altijd op 1 te zetten bij het opstarten:

8040ef9c 38c00001

Dit lijkt de juiste manier om zuru mode geïnitialiseerd te krijgen. Daarna kunnen er verschillende acties zijn die we moeten ondernemen om bepaalde debug informatie op het scherm te krijgen. Het spel opstarten en rondlopen en praten met een dorpeling heeft geen van de eerder genoemde displays laten zien (behalve de letter “D” in de hoek).

De waarschijnlijke verdachten zijn zurumode_update en zurumode_callback.

zurumode_update

zurumode_update wordt eerst aangeroepen door zurumode_init, en wordt vervolgens herhaaldelijk aangeroepen doorzurumode_callback.

Hij controleert nogmaals het laatste bit van 0x3C(osAppNMIBuffer) en werkt vervolgens zurumode_flag bij op basis van de waarde daarvan.

Als het bit nul is, wordt de vlag op nul gezet.

In het andere geval wordt de volgende instructie uitgevoerd met r5 als de volledige waarde van 0x3c(osAppNMIBuffer):

extrwi r3, r5, 1, 28

Dit haalt het 28e bit uit r5 en slaat het op in r3.Vervolgens wordt 1 toegevoegd aan het resultaat, zodat het eindresultaat altijd 1 of 2 is.

zurumode_flag wordt dan vergeleken met het vorige resultaat, afhankelijk van hoeveel van de 28e en laatste bits zijn gezet in 0x3c(osAppNMIBuffer): 0, 1, of 2.

Deze waarde wordt weggeschreven naar zurumode_flag. Als er niets verandert, eindigt de functie en wordt de huidige waarde van de vlag teruggegeven. Als de waarde wel wordt gewijzigd, wordt een veel complexere reeks codeblokken uitgevoerd.

Er wordt een bericht in het Japans afgedrukt: dit is het eerder genoemde bericht “zurumode_flag is gewijzigd van %d in %d”

Daarna wordt een reeks functies aangeroepen met verschillende argumenten, afhankelijk van of de vlag al dan niet in nul is gewijzigd. De assemblage voor dit deel is omslachtig, dus de pseudocode ervan ziet er als volgt uit:

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

Merk op dat als de vlag nul is, JC_JUTDbPrint_setVisible een argument van 0 krijgt.Als de vlag niet nul is EN bit 25 of bit 31 zijn gezet in 0x3C(osAppNMIBuffer), dan krijgt desetVisible-functie een argument van 1 doorgegeven.

Dit is de eerste sleutel tot het activeren van de zuru mode: het laatste bit van 0x3C(osAppNMIBuffer) moet op 1 worden gezet om de debug displays zichtbaar te maken en zurumode_flag op een niet-nul waarde te zetten.

zurumode_callback

zurumode_callback staat op 8040ee74 en wordt waarschijnlijk aangeroepen door een functie die gerelateerd is aan de gamepad. Als je een breekpunt instelt in Dolphin debugger, laat de callstack zien dat het inderdaad wordt aangeroepen door padmgr_HandleRetraceMsg.

Een van de eerste dingen die het doet is zerucheck_key_check uitvoeren. Het is complex, maar in het algemeen lijkt het de waarde van zuruKeyCheck te lezen en dan bij te werken. Ik besloot te kijken hoe die waarde wordt gebruikt in de rest van de callback functie voordat ik verder ging met de keycheck functie.

Daarna controleert het weer een aantal bits in 0x3c(osAppNMIBuffer). Als bit 26 is gezet, of als bit 25 is gezet en padmgr_isConnectedController(1) retourneert niet nul, dan wordt het laatste bit in 0x3c(osAppNMIBuffer) gezet op 1!

Als geen van deze bits is gezet, of als bit 25 is gezet maar padmgr_isConnectedController(1) retourneert 0, dan wordt gecontroleerd of de byte op 0x4(zuruKeyCheck) 0 is. Als dat zo is, dan wordt het laatste bit in de oorspronkelijke waarde op nul gezet en teruggeschreven naar 0x3c(osAppNMIBuffer).Zo niet, dan wordt het laatste bit alsnog op 1 gezet.

In pseudo-code ziet dit er als volgt uit:

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}

Na dat, als bit 26 niet is gezet, gaat het kort naar het aanroepen van zurumode_update en eindigt dan.

Als het wel is gezet, dan laadt het, als 0x4(zuruKeyCheck) niet nul is, een format string waar het lijkt dat het gaat afdrukken: “ZURU %d/%d”.

Recap

Dit is wat er gebeurt:

padmgr_HandleRetraceMsg roept de zurumode_callback op.Mijn gok is dat “handle retrace message” betekent dat het zojuist toetsendrukkenon de controller heeft gescand. Elke keer dat het scant, kan het een reeks verschillende callbacks aanroepen.

Wanneer zurumode_callback wordt uitgevoerd, controleert het de huidige toetsaanslagen.Dit lijkt te controleren op een specifieke knop of combinatie van knoppen.

De laatste bit in de NMI Buffer wordt bijgewerkt op basis van specifieke bits in de huidige waarde, evenals de waarde van een van de zuruKeyCheck bytes (0x4(zuruKeyCheck)).

Dan zurumode_update loopt en controleert die bit. Als het 0 is, wordt de zuru-modusvlag op 0 gezet. Als het 1 is, wordt de modusvlag bijgewerkt naar 1 of 2, afhankelijk van of bit 28 is gezet.

De drie manieren om de zuru-modus te activeren zijn:

  1. Bit 26 is gezet in 0x3C(osAppNMIBuffer)
  2. Bit 25 is gezet in 0x3C(osAppNMIBuffer), en een controller is aangesloten op poort 2
  3. 0x4(zuruKeyCheck) is niet nul

osAppNMIBuffer

Vroeg me af wat osAppNMIBuffer was, Ik begon met zoeken naar “NMI”, en vond verwijzingen naar “non-maskable interrupt” in de context van Nintendo. Het blijkt dat de hele variablenaam ook voorkomt in de documentatie voor ontwikkelaars van de Nintendo 64:

osAppNMIBuffer is een buffer van 64 bytes die wordt gewist bij een koude reset. Als het systeem opnieuw opstart vanwege een NMI, blijft deze buffer ongewijzigd.

Basically, this is a small piece of memory that persists across soft reboots. Een spel kan deze buffer gebruiken om op te slaan wat het maar wil, zolang de console aanstaat. Het originele Animal Crossing spel werd uitgebracht op de Nintendo 64, dus het is logisch dat iets als dit in de code opduikt.

Overschakelend naar de boot.dol binary (alles hierboven is van foresta.rel), zijn er veel verwijzingen naar osAppNMIBuffer in de main functie. Een snelle blik laat zien dat er reeksen controles zijn die kunnen resulteren in verschillende bits van 0x3c(osAppNMIBuffer) die worden gezet met OR operaties.

Interessante OR operand waarden om op te letten zouden zijn:

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

Bits 25, 26 en 28 zijn bijzonder interessant: 25 en 26 bepalen of de zuru mode is ingeschakeld, en bit 28 bepaalt het niveau van de vlag (1 of 2).Bit 31 is ook interessant, maar lijkt voornamelijk te worden bijgewerkt op basis van de waarden van de anderen.

Bit 26

Eerst: op 800062e0 is er een ori r0, r0, 0x20 instructie op de bufferwaarde op 0x3c. Dit zou bit 26 zetten, wat er altijd toe leidt dat de zuru-modus wordt ingeschakeld.

Setting bit 26

Om de bit te kunnen zetten, moet de 8e byte die terugkomt van DVDGetCurrentDiskID 0x99 zijn.Deze ID bevindt zich helemaal aan het begin van de game disc image, en wordt op80000000 in het geheugen geladen. Voor een normale retail-uitgave van het spel ziet de ID er als volgt uit:

47 41 46 45 30 31 00 00 GAFE01..

Patchen van de laatste byte van de ID naar 0x99 zorgt ervoor dat bij het opstarten van het spel het volgende gebeurt:

Game version ID 0x99

En in de OS-console wordt het volgende afgedrukt:

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

Alle andere patches kunnen worden verwijderd, en de letter D verschijnt ook weer in de rechterbovenhoek van het scherm, maar geen van de andere debug-displays wordt geactiveerd.

Bit 25

Bit 25 wordt gebruikt in combinatie met het uitvoeren van de poort 2 controller check. Waardoor wordt deze geactiveerd?

Bit 25 en 28

Dit blijkt dezelfde controle te hebben die voor bit 28 wordt gebruikt: de versie moet groter zijn dan of gelijk zijn aan 0x90. Als bit 26 was gezet (ID is 0x99), zullen deze beide bits ook worden gezet, en zal de zuru mode toch worden ingeschakeld.

Als de versie echter tussen 0x90 en 0x98 ligt, wordt de zuru mode niet meteen ingeschakeld.Herinnerend aan de controle in zurumode_callback, zal deze alleen worden ingeschakeld als bit 25 is gezet en padmgr_isConnectedController(1) niet nul retourneert.Zodra een controller is aangesloten op poort 2 (het argument voor isConnectedController is op nul gezet), wordt de zuru mode geactiveerd. De letter D en de build info verschijnen op het titelscherm, en…het drukken op knoppen op de tweede controller bestuurt de debug displays!

Sommige knoppen doen ook dingen naast het veranderen van de display, zoals het verhogen van de snelheid van het spel.

zerucheck_key_check

Het laatste mysterie is 0x4(zuruKeyCheck). Het blijkt dat deze waarde wordt bijgewerkt door de gigantische complexe functie die eerder werd getoond:

zerumode_check_keycheck

Met behulp van de Dolphin debugger kon ik vaststellen dat de waarde die door deze functie wordt gecontroleerd een set bits is die overeenkomt met het indrukken van knoppen op de tweede controller. Het drukknop spoor is opgeslagen in een 16-bit waarde op 0x2(zuruKeyCheck). Als er geen controller is aangesloten, is de waarde 0x7638.

De 2 bytes met de flags voor de controller 2 knopdrukken worden geladen en dan geupdate aan het begin van zerucheck_key_check. De nieuwe waarde wordt samen met register r4 door padmgr_HandleRetraceMsg doorgegeven wanneer het de callback-functie oproept.

toetscontrole einde

Achter het einde van zerucheck_key_check, is er nog een plaats waar 0x4(zuruKeyCheck) wordt bijgewerkt. Deze stond niet in de lijst met kruisverwijzingen, omdat r3 wordt gebruikt als basisadres, en we kunnen er alleen achter komen wat r3 is door te kijken naar de waarde ervan op het moment dat deze functie wordt aangeroepen.

Op 8040ed88 wordt de waarde van r4 weggeschreven naar 0x4(zuruKeyCheck). Het is geladen vanaf dezelfde locatie en dan XORd met 1 net daarvoor. Dit zou moeten betekenen dat de waarde van de byte (eigenlijk alleen het laatste bit) wisselt tussen 0 en 1. (Als het 0 is, zal het resultaat van de XOR met 1 1 zijn. Als het 1 is, is het resultaat 0. Zoek de waarheidstabel voor XOR op om dit te zien.)

key check end

Ik heb dit gedrag niet eerder opgemerkt bij het bekijken van de geheugenwaarden, maar ik zal proberen deze instructie te breken in de debugger om te zien wat er gebeurt. De originele waarde wordt geladen op 8040ed7c.

Zonder knoppen op de controllers aan te raken, raak ik dit breekpunt niet tijdens het titelscherm. Om dit codeblok te bereiken, moet de waarde van r5 0xb zijn voor de vertakkingsinstructie die er voor komt (8040ed74). Van de vele verschillende paden die naar dat blok leiden, is er een die r5 op 0xb zal zetten vóór het blok, op 8040ed68.

Setting r5 to 0xb

Merk op dat om het blok te bereiken dat r5 op 0xB zet, r0 gelijk moet zijn aan 0x1000 net ervoor. Als we de blokken volgen tot aan het begin van de functie, zien we de beperkingen die nodig zijn om dit blok te bereiken:

  • 8040ed74: r5 moet zijn 0xB
  • 8040ed60: r0 moet zijn 0x1000
  • 8040ebe8: r5 moet zijn 0xA
  • 8040ebe4: r5 moet kleiner zijn dan 0x5B
  • 8040eba4: r5 moet groter zijn dan 0x7
  • 8040eb94: r6 moet 1 zijn
  • 8040eb5c: r0 mag niet 0 zijn
  • 8040eb74: Port 2 button values must have changed

Tracing the code path

Hier bereiken we het punt waar de oude button values worden geladen en de nieuwe values worden opgeslagen. Daarna worden er een paar bewerkingen op de nieuwe en oude waarden toegepast:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

De XOR-bewerking markeert alle bits die tussen de twee waarden zijn gewijzigd. De AND-bewerking maskeert vervolgens de nieuwe ingang om alle bits die momenteel niet zijn ingesteld, uit te schakelen. Het resultaat in r0 is de set van nieuwe bits (druk op de knop) in de nieuwe waarde. Als deze niet leeg is, zijn we op de goede weg.

Om r0 0x1000 te laten zijn, moet de 4e van de 16 button trace bits zojuist zijn veranderd.Door een breakpoint te zetten na de XOR/AND operatie kan ik achterhalen welke button press dit veroorzaakt: het is de START button.

De volgende vraag is hoe r5 te laten beginnen als 0xA. r5 en r6 worden geladen vanuit0x0(zuruKeyCheck) aan het begin van de toets controle functie, en geupdate aan het eind wanneer we het code blok dat 0x4(zuruKeyCheck) omschakelt niet starten.

Er zijn een paar plaatsen vlak voor waar r5 wordt ingesteld op 0xA:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
  • 8040ed34: r0 moet 0x4000 zijn (B-knop werd ingedrukt)
  • 8040ebe0: r5 moet 0x5b
  • 8040eba4: r5 moet groter zijn dan 0x7
  • hetzelfde als voorheen vanaf hier…

r5 moet beginnen bij 0x5b

8040ed00
  • 8040ecfc: r0 moet 0xC000 zijn (A en B ingedrukt)
  • 8040ebf8: r5 moet >= 9
  • 8040ebf0: r5 moet kleiner zijn dan 10
  • 8040ebe4: r5 moet kleiner zijn dan 0x5b
  • 8040eba4: r5 moet groter zijn dan 0x7
  • hetzelfde als voorheen vanaf hier…

r5 moet beginnen bij 9

8040ed50
  • 8040ed4c: r0 moet 0x8000 zijn (A werd ingedrukt)
  • 8040ec04: r5 moet kleiner zijn dan 0x5d
  • 8040ebe4: r5 moet groter zijn dan 0x5b
  • 8040eba4: r5 moet groter zijn dan 0x7
  • hetzelfde als voorheen vanaf hier…

r5 moet beginnen bij 0x5c

Het lijkt erop dat er een soort toestand is tussen het indrukken van knoppen, en dat dan een bepaalde volgorde van knoppencombinaties moet worden ingevoerd, eindigend met START. Het lijkt erop dat A en/of B net voor START komen.

Volgt men het codepad dat r5 op 9 zet, dan komt er een patroon naar voren: r5 is een oplopende waarde die ofwel kan worden verhoogd wanneer de juiste waarde voor het indrukken van de knop is gevonden in r0, of kan worden teruggezet op 0. De vreemdere gevallen waarin het geen waarde is tussen 0x0 en 0xB komen voor bij het verwerken van stappen met meerdere knoppen, zoals het indrukken van A en B op hetzelfde moment. Een persoon die probeert deze combo in te voeren zal meestal niet op beide knoppen drukken op exact hetzelfde moment dat de pad trace optreedt, dus het moet omgaan met de ene knop ingedrukt voor de andere.

Vervolg met de verschillende code paden:

  • r5 is ingesteld op 9 wanneer RECHTS wordt ingedrukt op 8040ece8.
  • r5 staat op 8 als de C-stick rechts wordt ingedrukt bij 8040eccc.
  • r5 staat op 7 als de C-stick links wordt ingedrukt bij 8040ecb0.
  • r5 staat op 6 als LINKS wordt ingedrukt bij 8040ec98.
  • r5 staat op 5 (en r6 op 1) als OMLAAG wordt ingedrukt bij 8040ec7c.
  • r5 is ingesteld op 4 wanneer de C-stick omhoog wordt gedrukt bij 8040ec64.
  • r5 is ingesteld op 3 wanneer de C-stick omlaag wordt gedrukt bij 8040ec48.
  • r5 is ingesteld op 2 wanneer UP wordt ingedrukt bij 8040ec30.
  • r5 is ingesteld op 1 (en r6 op 1) wanneer Z wordt ingedrukt bij 8040ec1c.

De huidige volgorde is:

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

Er wordt nog een voorwaarde gecontroleerd voor de Z-controle: terwijl de nieuw ingedrukte knop Z moet zijn, moeten de huidige vlaggen 0x2030 zijn: de linker en rechter bumpers moeten ook worden ingedrukt (ze hebben waarden van 0x10 en 0x20). Ook, de UP/DOWN/LEFT/RIGHT zijn deD-pad knoppen, niet analoge stick.

De cheat code

De volledige combo is:

  1. Houd L+R bumpers ingedrukt en druk op Z
  2. D-UP
  3. C-DOWN
  4. C-UP
  5. D-DOWN
  6. D-LINKS
  7. C- LINKS
  8. C-RIGHT
  9. D-RIGHT
  10. A+B
  11. START

Het werkt! Sluit een controller aan op de tweede poort en voer de code in, en de debug-displays zullen verschijnen. Daarna kun je knoppen indrukken op de tweede (of zelfs derde) controller om dingen te gaan doen.

Deze combo zal werken zonder het versienummer van het spel te patchen.Je kunt dit zelfs gebruiken op een gewone retail kopie van het spel zonder cheat tools of console mods. Als je de combo een tweede keer invoert, wordt de zuru-modus weer uitgeschakeld.

De code gebruiken op een echte GameCube

De “ZURU %d/%d”-boodschap in zurumode_callback wordt gebruikt om de status van deze combinatie af te drukken als je deze invoert terwijl de disk-ID al 0x99 is (waarschijnlijk om de cheatcode zelf te debuggen). Het eerste getal is uw huidige positie in de reeks, overeenkomend met r5. Het tweede getal wordt op 1 gezet als bepaalde knoppen in de reeks ingedrukt worden gehouden, deze kunnen overeenkomen met wanneer r6 op 1 staat.

De meeste displays leggen niet uit wat ze zijn op het scherm, dus om erachter te komen wat ze doen moet je de functies vinden die ze behandelen. Bijvoorbeeld, de lange lijn van blauwe en rode rasterisks die verschijnen aan de bovenkant van het scherm zijn plaatshouders voor het tonen van de status van verschillende zoektochten. Als een quest actief is zullen er nummers verschijnen die de status van de quest aangeven.

Het zwarte scherm dat verschijnt als je op Z drukt is een console voor het afdrukken van debug berichten, maar specifiek voor low level dingen zoals geheugen allocatie en heap fouten of andere slechte uitzonderingen. Het gedrag van fault_callback_scroll suggereert dat het zou kunnen zijn voor het weergeven van deze fouten voordat het systeem opnieuw wordt opgestart. Ik heb geen van deze fouten getriggerd, maar ik kon er wel voor zorgen dat het een paar vuilniskarakters afdrukte met wat NOP’s. Ik denk dat dit erg handig zou zijn om later aangepaste debug-berichten af te drukken:

JUTConsole garbage characters

Nadat ik dit allemaal gedaan had, kwam ik erachter dat het verkrijgen van debug-modus door het patchen van de versie-ID naar 0x99 al bekend is: https://tcrf.net/Animal_Crossing#Debug_Mode. (Ze hebben ook een aantal goede aantekeningen over wat de verschillende displays zijn, en meer dingen die je kunt doen met behulp van een controller in poort 3.)Voor zover ik kan nagaan, is de cheat-combinatie nog niet gepubliceerd, hoewel.

Dat is alles voor deze post. Er zijn nog enkele ontwikkelaarsfuncties die ik wil onderzoeken, zoals het debug-mapscherm en het NES-emulator-selectiescherm, en hoe je die kunt activeren zonder patches te gebruiken.

Map-selectiescherm

Ik zal ook nog schrijfsels plaatsen over het omkeren van de dialoog-, event- en questsystemen om mods te kunnen maken.

Update: De dia’s van de toespraak die ik hierover heb gehouden, kun je hier vinden.