jamchambs blogg

Förra sommaren började jag reverse engineering av Animal Crossing för GameCube för att utforska möjligheten att skapa moddar för spelet. Jag ville också dokumentera processen för att skapa handledningar för personer som är intresserade av ROM-hacking och reverse engineering.I det här inlägget utforskar jag de felsökningsfunktioner för utvecklare som fortfarande finns kvar i spelet, och hur jag upptäckte en fuskkombination som kan användas för att låsa upp dem.

new_Debug_mode

När jag tittade runt på några överblivna debugsymboler såg jag funktioner och variabelnamn som innehöll ordet ”debug”, och tänkte att det skulle vara intressant att se vilka debugfunktioner som kan finnas kvar i spelet. Om det fanns några felsöknings- eller utvecklarfunktioner som jag kunde aktivera skulle det också kunna vara till hjälp vid skapandet av mods.

Den första funktionen jag tittade på var new_Debug_mode.Den anropas av entry-funktionen, som körs direkt efter att Nintendotrademark-skärmen är klar. Allt den gör är att allokera en 0x1C94 byte struktur och spara dess pekare.

När den anropas i entry sätts värdet 0 vid offset 0xD4 i den allokerade strukturen,precis innan mainproc anropas.

Disassembly of the entry function

För att se vad som händer när värdet inte är noll, lappade jag li r0, 0-instruktionen vid 80407C8C till li r0, 1. De råa bytena för instruktionen li r0, 0 är 38 00 00 00,där det tilldelade värdet finns i slutet av instruktionen, så du kan bara ändra detta till 38 00 00 01 för att få li r0, 1. För ett mer tillförlitligt sätt att sätta ihop instruktioner kan du använda något som kstool:

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

Du kan tillämpa den här patchen i Dolphin-emulatorn genom att gå till fliken ”Patches” i spelets egenskaper och skriva in den så här:

Debug performance meter

Som du sätter detta värde till 1 visas en intressant graf längst ner på skärmen:

Debug performance meter

Det såg ut som en prestandamätare, med de små staplarna längst ner på skärmen som växte och krympte. (När jag senare slog upp namnen på de funktioner som ritar grafen fann jag att de faktiskt visar mätvärden för CPU- och minnesanvändning.) Det var snyggt, men inte särskilt användbart. Att sätta värdet över 1 stoppade faktiskt mytown från att laddas, så det verkade inte som om det fanns mycket mer att göra med detta.

Zuru mode

Jag började titta runt på andra referenser till felsökningsrelaterade saker, och såg något som kallades ”zuru mode” dyka upp ett par gånger. Förgreningar till kodblock som hade felsökningsfunktionalitet kontrollerade ofta en variabel som hette zurumode_flag.

game_move_first-funktion

I game_move_first-funktionen på bilden ovan kallas zzz_LotsOfDebug (ett namn som jag hittade på)endast om zurumode_flag inte är noll.

Looking for functions related to this value yields the following:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

Om man ser det vid första anblicken så är de alla lite obskyra, de vrider runt olika bitar i offsets i en variabel som heter osAppNMIBuffer.Här är en första titt på vad dessa funktioner gör:

zurumode_init

  • Sätt zurumode_flag till 0
  • Kontrollera några bitar i osAppNMIBuffer
  • Lagra en pekare till zurumode_callback-funktionen i padmgr-strukturen
  • Kalla zurumode_update

zurumode_update

  • Kontrollera några bitar i osAppNMIBuffer
  • Anslutningsvis uppdatera zurumode_flag baserat på dessa bitar
  • Skriv ut en formatsträng till OS-konsolen.

Den här typen av saker är vanligtvis användbara för att ge ett sammanhang till koden, men det fanns ett gäng oskrivbara tecken i strängen. Den enda igenkännbara texten var ”zurumode_flag” och ”%d”.

zuru mode format string

I och med tanke på att det kunde vara japansk text som använde en teckenkodning med flera byte, körde jag strängen genom ett verktyg för teckenkodningsdetektering och fick reda på att den var Shift-JIS-kodad. Den översatta strängen betyder bara ”zurumode_flag hasbeen changed from %d to %d”. Det ger inte mycket ny information, men att veta om användningen av Shift-JIS gör det, eftersom det finns många fler strängar i binärfilerna och strängtabellerna som använder denna kodning.

zurumode_callback

  • Kallar zerumode_check_keycheck
  • Kontrollerar några bitar i osAppNMIBuffer
  • Prycker värdet av zurumode_flag någonstans
  • Kallar zurumode_update

zerumode_check_keycheck syntes inte tidigare på grund av den annorlunda stavningen.. vad är det?

zerumode_check_keycheck

En enorm komplex funktion som gör mycket mer bit-twiddling på värden utan namn.Vid det här laget bestämde jag mig för att backa tillbaka och leta efter andra felsökningsrelaterade funktioner och variabler, eftersom jag inte ens var säker på vad zuru-läget hade för betydelse. Jag var också osäker på vad ”key check” betydde här. Kunde det vara en kryptografisk nyckel?

Tillbaka till debug

Omkring den här tiden märkte jag att det fanns ett problem med hur jag laddade debugsymbolerna i IDA. Filen foresta.map på spelskivan innehåller en massa adresser och namn för funktioner och variabler. Jag hade inte lagt märke till att adresserna för varje sektion började vid noll, så jag satte bara upp ett enkelt skript för att lägga till en namnpost för varje rad i filen.

Jag satte upp nya några IDA-skript för att fixa symbolmappens laddning för de olika sektionerna i programmet: .text, .rodata, .data, och .bss. I avsnittet .text finns alla funktioner,så jag ställde in skriptet så att det automatiskt upptäcker funktioner på varje adress när jag ställer in ett namn den här gången.

För dataavsnitten ställde jag in det så att det skapar ett segment för varje binärt objekt (t.ex. m_debug.o,som skulle vara kompilerad kod för något som heter m_debug), och ställer in utrymme och namn för varje datastycke.Detta ger mig mycket mer information än vad jag hade tidigare, även om jag nu var tvungen att manuellt definiera datatypen för varje data, eftersom jag ställde in varje dataobjekt som en enkel byte array. (I efterhand hade det varit bättre att åtminstone anta att alla data med en storlek som är en multipel av 4 bytes innehöll 32-bitarsintegraler, eftersom det finns så många av dem och många innehåller adresser till funktioner och data som är viktiga för att bygga upp korshänvisningar.)

När jag tittade igenom det nya .bss-segmentet för m_debug_mode.o, såg jag några variabler som quest_draw_status ochevent_status. Dessa är intressanta eftersom jag vill få felsökningsläget att visa mer användbara saker än prestandadiagrammet. Lyckligtvis fanns det korshänvisningar från dessa dataposter till ett stort stycke kod som kontrollerar debug_print_flg.

Med hjälp av Dolphin debugger satte jag en brytpunkt på den funktion där debug_print_flg kontrolleras (vid 8039816C) för att se hur kontrollen fungerar. Brytpunkten träffade aldrig.

Låt oss kontrollera varför: denna funktion anropas av game_debug_draw_last. Gissa vilket värde som kontrolleras innan den villkorligt anropas? zurumode_flag. Vad fan är det?

zurumode_flag check

Jag satte en brytpunkt på den kontrollen (80404E18) och den bröts omedelbart. Värdet för zurumode_flag var noll, så den skulle normalt hoppa över att anropa denna funktion. Jag NOPped ut greninstruktionen (ersatte den med en instruktion som inte gör någonting) för att se vad som skulle hända när funktionen anropas.

I Dolphin debugger kan du göra detta genom att pausa spelet, högerklicka på en instruktion och sedan klicka på ”Insert nop”:

Dolphin debugger NOPping

Inget hände. Sedan kontrollerade jag vad som hände inne i funktionen och hittade en annan greninstruktion som kunde förkorta förbi alla intressanta saker vid 803981a8. Jag NOPpade även det och bokstaven ”D” dök upp i övre högra hörnet av skärmen.

Bokstaven D i felsökningsläge

Det fanns en hel del mer intressant kod i den här funktionen vid 8039816C (jag kallade den zzz_DebugDrawPrint), men inget av det blev anropat. Om du tittar på den grafiska vyn för den här funktionen kan du se att det finns en rad förgreningssatser som hoppar över block genom hela funktionen:

Förgreningar i zzz_DebugDrawPrint

Genom att NOPa ut fler av dessa grenförklaringar började jag se olika saker skrivas ut på skärmen:

Mer felsökningsgrejer skrivs ut

Nästa fråga är hur man kan aktivera dessa felsökningsfunktioner utan att modifiera koden.Dessutom dyker zurumode_flag upp igen för vissa grenförklaringar som gjorts i denna debug draw-funktion. jag lade till en annan patch så att zurumode_flag alltid sätts till 2 i zurumode_update, eftersom det vanligtvis jämförs specifikt med 2 när det inte jämförs med 0.Efter att ha startat om spelet såg jag det här meddelandet ”msg. no” visas uppe till höger på skärmen.

message number display

Talet 687 är entry ID för det senast visade meddelandet. Jag kontrollerade detta med hjälp av en simpletabellvisare som jag gjorde tidigt, men du kan också kontrollera det med en fullständig GUI-redigerare för strängtabeller som jag gjorde för ROM-hackning. Så här ser meddelandet ut i editorn:

Meddelande 687 i strängtabellseditorn

Det stod nu klart att det inte längre gick att undvika att ta reda på zuru-läget; det stack direkt in i spelets felsökningsfunktioner.

Zuru mode revisited

Vid återgång till zurumode_init initialiseras några saker:

  • 0xC(padmgr_class) sätts till adressen för zurumode_callback
  • 0x10(padmgr_class) sätts till adressen för padmgr_class själv
  • 0x4(zuruKeyCheck) sätts till sista biten i ett ord som laddas från 0x3C(osAppNMIBuffer).

Jag undersökte vad padmgr betyder och det är en förkortning för ”gamepad manager”. Detta tyder på att det kan finnas en särskild tangentkombination (knappkombination) som man måste ange på gamepaden för att aktivera zuru-läget, eller möjligen någon särskild felsökningsenhet eller utvecklingskonsolfunktion som kan användas för att skicka en signal för att aktivera det.

zurumode_init körs endast första gången spelet laddas (att trycka på återställningsknappen utlöser den inte).

Sätt en brytpunkt vid 8040efa4, där 0x4(zuruKeyCheck) är inställd, så kan vi se att under uppstartenutan att hålla nere några tangenter kommer den att sättas till 0. Att ersätta detta med 1 gör att en intressant sak händer:

Titelskärm med zuru-läge

Bokstaven ”D” dyker upp i det övre högra hörnet igen (grönt istället för gult den här gången),och det finns också lite build-information:

En patch för att 0x4(zuruKeyCheck) alltid ska sättas till 1 vid start:

8040ef9c 38c00001

Detta verkar vara det korrekta sättet att få zuru-läge initialiserat. Efter det kan det finnas olika åtgärder som vi måste vidta för att få viss felsökningsinformation att visas. Att starta spelet och gå runt och prata med en bybo visade ingen av de visningar som nämndes tidigare (förutom bokstaven ”D” i hörnet).

De troliga misstänkta är zurumode_update och zurumode_callback.

zurumode_update

zurumode_update anropas först av zurumode_init och blir sedan upprepade gånger anropad avzurumode_callback.

Den kontrollerar den sista biten i 0x3C(osAppNMIBuffer) igen och uppdaterar sedan zurumode_flag utifrån dess värde.

Om biten är noll, sätts flaggan till noll.

Om inte, körs följande instruktion med r5 som fullt värde för 0x3c(osAppNMIBuffer):

extrwi r3, r5, 1, 28

Detta extraherar den 28:e biten från r5 och sparar den i r3.Sedan adderas 1 till resultatet, så att slutresultatet alltid är 1 eller 2.

zurumode_flag jämförs sedan med det föregående resultatet, beroende på hur mångaav den 28:e och sista biten som är inställda i 0x3c(osAppNMIBuffer): 0, 1 eller 2.

Detta värde skrivs till zurumode_flag. Om det inte ändrade något avslutas funktionen och returnerar flaggans aktuella värde. Om den ändrar värdet utförs en mycket mer komplex kedja av kodblock.

Ett meddelande på japanska skrivs ut: detta är meddelandet ”zurumode_flag har ändrats från %d till %d ”som nämndes tidigare.

Därefter anropas en rad funktioner med olika argument beroende på om flaggan ändrats till noll eller inte. Monteringen för den här delen är tråkig, så pseudokoden ser ut så här:

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

Observera att om flaggan är noll får JC_JUTDbPrint_setVisible argumentet 0. Om flaggan inte är noll OCH bit 25 eller bit 31 är inställda i 0x3C(osAppNMIBuffer) fårsetVisible-funktionen argumentet 1.

Detta är den första nyckeln till att aktivera zuru-läget: den sista biten i 0x3C(osAppNMIBuffer) måste sättas till 1 för att göra felsökningsskärmarna synliga och sätta zurumode_flagtill ett värde som inte är noll.

zurumode_callback

zurumode_callback ligger på 8040ee74 och anropas troligen av en funktion som är relaterad till gamepaden. När man sätter en brytpunkt på den i Dolphin debugger visar callstack att den verkligen anropas från padmgr_HandleRetraceMsg.

En av de första sakerna den gör är att köra zerucheck_key_check. Det är komplicerat, men överlag verkar det som om den läser och sedan uppdaterar värdet på zuruKeyCheck. Jag bestämde mig för att se hur det värdet används i resten av callback-funktionen innan jag går vidare till nyckelkontrollfunktionen.

Nästan kontrollerar den några bitar i 0x3c(osAppNMIBuffer) igen. Om bit 26 är inställd, eller om bit 25 är inställd och padmgr_isConnectedController(1) returnerar icke-noll, så sätts den sista biten i 0x3c(osAppNMIBuffer) till 1!

Om ingen av dessa bitar är inställda, eller om bit 25 åtminstone är inställd men padmgr_isConnectedController(1) returnerar 0, så kontrollerar den om byte vid 0x4(zuruKeyCheck) är 0. Om den är det, så nollställer den den sista biten i det ursprungliga värdet och skriver tillbaka det till 0x3c(osAppNMIBuffer).Om inte, så sätter den fortfarande den sista biten till 1.

I pseudokod ser detta ut som:

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}

Efter det, om bit 26 inte är satt, så kortar den till att kalla zurumode_update och avslutar sedan.

Om det är satt, så om 0x4(zuruKeyCheck) inte är noll, så laddar den upp en formatsträng där det verkar som att den ska skriva ut: ”ZURU %d/%d”.

Recap

Här är vad som händer:

padmgr_HandleRetraceMsg anropar zurumode_callback.Min gissning är att ”handle retrace message” betyder att den just har skannat av tangenttryckningar på styrenheten. Varje gång den skannar kan den anropa en rad olika callbacks.

När zurumode_callback körs kontrollerar den de aktuella tangenttrycken (knapptryckningarna).Det verkar som om den kontrollerar en specifik knapp eller en kombination av knappar.

Den sista biten i NMI-bufferten uppdateras baserat på specifika bitar i dess currentvalue, samt värdet av en av zuruKeyCheck-bytena (0x4(zuruKeyCheck)).

Därefter körs zurumode_update och kontrollerar den biten. Om den är 0 kommer zuru-lägesflaggan att beset till 0. Om den är 1 uppdateras lägesflaggan till 1 eller 2 beroende på om bit 28 är inställd.

De tre sätten att aktivera zuru-läget är:

  1. Bit 26 är satt i 0x3C(osAppNMIBuffer)
  2. Bit 25 är satt i 0x3C(osAppNMIBuffer), och en styrenhet är ansluten till port 2
  3. 0x4(zuruKeyCheck) är inte noll

osAppNMIBuffer

Vad var osAppNMIBuffer, Jag började med att söka efter ”NMI” och hittade hänvisningar till ”non-maskable interrupt” i samband med Nintendo. Det visade sig att hela variabelnamnet också förekommer i utvecklardokumentationen för Nintendo 64:

osAppNMIBuffer är en 64-byte buffert som rensas vid en kall återställning. Om systemet startas om på grund av en NMI är den här bufferten oförändrad.

Det här är i princip en liten del av minnet som finns kvar vid mjuka omstarter. Ett spel kan använda den här bufferten för att lagra vad det vill så länge konsolen är påslagen.Det ursprungliga Animal Crossing-spelet släpptes faktiskt på Nintendo 64, så det är logiskt att något sådant här skulle dyka upp i koden.

Om man går över till den binära boot.dol-versionen (allt ovan är från foresta.rel) finns det många referenser till osAppNMIBuffer i main-funktionen. En snabb titt visar att det finns en rad kontroller som kan resultera i att olika bitar i 0x3c(osAppNMIBuffer) sätts med OR-operationer.

Interessanta OR-operandvärden att hålla utkik efter skulle vara:

  • Bit 31: 0x01
  • Bit 30: 0x02
  • Bit 29: 0x04
  • Bit 28: 0x08
  • Bit 27: Kom ihåg att bitarna 25, 26 och 28 är särskilt intressanta: 25 och 26 bestämmer om zuru-läget är aktiverat och bit 28 bestämmer flaggans nivå (1 eller 2).Bit 31 är också intressant, men verkar i första hand uppdateras baserat på värdena för de andra.
    Bit 26

    Först upp: vid 800062e0 finns en ori r0, r0, 0x20-instruktion på buffertvärdet vid 0x3c. Detta skulle sätta bit 26, vilket alltid resulterar i att zuru-läget är aktiverat.

    Sätta bit 26

    För att biten ska sättas måste den åttonde byte som returneras från DVDGetCurrentDiskID vara 0x99.Detta ID finns i början av spelskivans avbildning och laddas upp vid80000000 i minnet. För en vanlig detaljhandelsutgåva av spelet ser ID:

    47 41 46 45 30 31 00 00 GAFE01..

    Patchning av den sista byte av ID:t till 0x99 gör att följande händer när spelet startas:

    Game version ID 0x99

    Och i OS-konsolen skrivs följande ut:

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

    Alla andra patchar kan tas bort, och bokstaven D visas också i det övre högra hörnet av skärmen igen, men ingen av de andra felsökningsskärmarna är aktiverade.

    Bit 25

    Bit 25 används i samband med att kontrollen av styrenheten för port 2 utförs. Vad gör att den aktiveras?

    Bit 25 och 28

    Detta visar sig ha samma kontroll som används för bit 28: versionen måste vara större än eller lika med 0x90. Om bit 26 var inställd (ID är 0x99), kommer båda dessa bitar också att vara inställda, och zuru-läget kommer att aktiveras i alla fall.

    Om versionen är mellan 0x90 och 0x98 aktiveras dock inte zuru-läget omedelbart.Om vi minns kontrollen som gjordes i zurumode_callback, kommer det att aktiveras endast om bit 25 är inställd och padmgr_isConnectedController(1) returnerar icke-noll.Så snart en styrenhet är inkopplad i port 2 (argumentet till isConnectedController är noll-indexerat), aktiveras zuru-läget. Bokstaven D och byggnadsinformationen visas på titelskärmen, och… genom att trycka på knapparna på den andra handkontrollen styrs felsökningsskärmarna!

    Vissa knappar gör också andra saker än att ändra skärmen, t.ex. ökar spelets hastighet.

    zerucheck_key_check

    Det sista mysteriet är 0x4(zuruKeyCheck). Det visar sig att detta värde uppdateras av den jättelika komplexa funktion som visades tidigare:

    zerumode_check_keycheck

    Med hjälp av Dolphins felsökare kunde jag konstatera att det värde som kontrolleras av denna funktion är en uppsättning bitar som motsvarar knapptryckningar på den andra kontrollenheten. Spårningen av knapptryckningar lagras i ett 16-bitarsvärde på 0x2(zuruKeyCheck). När ingen styrenhet är inkopplad är värdet 0x7638.

    De 2 bytes som innehåller flaggor för knapptryckningar på styrenhet 2 laddas och uppdateras sedan i början av zerucheck_key_check. Det nya värdet skickas in medregister r4 av padmgr_HandleRetraceMsg när den anropar callback-funktionen.

    key check end

    Närmast i slutet av zerucheck_key_check finns det faktiskt ett annat ställe där 0x4(zuruKeyCheck) uppdateras. Det fanns inte med i listan över korshänvisningar eftersom det använder r3 som basadress, och vi kan bara ta reda på vad r3 är genom att titta på vad den sätts till varje gång denna funktion ska anropas.

    Vid 8040ed88 skrivs värdet av r4 till 0x4(zuruKeyCheck). Det laddas från samma plats och sedan XORd med 1 strax före det. Vad detta bör göra är att växla bytevärdet (egentligen bara den sista biten) mellan 0 och 1. (Om det är 0 blir resultatet avXORing av det med 1 1.) Om den är 1 blir resultatet 0. Slå upp sanningstabellen för XOR för att se detta.)

    key check end

    Jag märkte inte det här beteendet när jag tittade på minnesvärdena tidigare, men jag ska försöka bryta den här instruktionen i felsökaren för att se vad som händer. Det ursprungliga värdet laddas vid 8040ed7c.

    Och utan att röra några knappar på styrenheten träffar jag inte denna brytpunkt under titelskärmen. För att nå det här kodblocket måste värdet för r5 vara 0xb före den greninstruktion som kommer före den (8040ed74). Av de många olika vägar som leder fram till det blocket finns det en som sätter r5 till 0xb före det, vid 8040ed68.

    Sätta r5 till 0xb

    Bemärk att för att nå blocket som sätter r5 till 0xB måste r0 ha varit lika med 0x1000 strax innan. Genom att följa blocken uppåt i kedjan till funktionens början kan vi se de begränsningar som krävs för att nå detta block:

    • 8040ed74: r5 måste vara 0xB
    • 8040ed60: r0 måste vara 0x1000
    • 8040ebe8: r5 måste vara 0xA
    • 8040ebe4: r5 måste vara mindre än 0x5B
    • 8040eba4: r5 måste vara större än 0x7
    • 8040eb94: r6 måste vara 1
    • 8040eb5c: r0 får inte vara 0
    • 8040eb74: Port 2 button values must have changed

    Tracing the code path

    Här når vi den punkt där de gamla knappvärdena laddas och de nya värdena lagras. Därefter tillämpas ett par operationer på de nya och gamla värdena:

    old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

    XOR-operationen markerar alla bitar som har ändrats mellan de två värdena. AND-operationen maskerar sedan den nya ingången för att ta bort alla bitar som för närvarande inte är inställda. Resultatet i r0 är uppsättningen nya bitar (knapptryckningar) i det nya värdet. Om det inte är tomt är vi på rätt väg.

    För att r0 ska bli 0x1000 måste den fjärde av de 16 knappspårningsbitarna just ha ändrats.Genom att sätta en brytpunkt efter XOR/AND-operationen kan jag räkna ut vilken knapptryckning som orsakar detta: det är START-knappen.

    Nästkommande fråga är hur jag ska få r5 att börja som 0xA. r5 och r6 laddas från0x0(zuruKeyCheck) i början av tangentkontrollfunktionen och uppdateras mot slutet när vi inte använder kodblocket som växlar 0x4(zuruKeyCheck).

    Det finns några ställen strax innan där r5 sätts till 0xA:

    • 8040ed50
    • 8040ed00
    • 8040ed38
    8040ed38
    • 8040ed34: r0 måste vara 0x4000 (B-knappen trycktes ned)
    • 8040ebe0: r5 måste vara 0x5b
    • 8040eba4: r5 måste vara större än 0x7
    • Samma som tidigare hädanefter…

    r5 måste börja på 0x5b

    8040ed00
    • 8040ecfc: r0 måste vara 0xC000 (A och B tryckta)
    • 8040ebf8: r5 måste vara >= 9
    • 8040ebf0: r5 måste vara mindre än 10
    • 8040ebe4: r5 måste vara mindre än 0x5b
    • 8040eba4: r5 måste vara större än 0x7
    • Samma som tidigare hädanefter…

    r5 måste börja vid 9

    8040ed50
    • 8040ed4c: r0 måste vara 0x8000 (A trycktes in)
    • 8040ec04: r5 måste vara mindre än 0x5d
    • 8040ebe4: r5 måste vara större än 0x5b
    • 8040eba4: r5 måste vara större än 0x7
    • Samma som tidigare från och med nu…

    r5 måste börja vid 0x5c

    Det verkar som om det finns något slags tillstånd mellan knapptryckningar, och sedan måste en viss sekvens av knappkombinationer anges, som slutar med START. Det verkar som om A och/eller B kommer strax före START.

    Följer man kodvägen som sätter r5 till 9, framträder ett mönster: r5 är ett stigande värde som antingen kan ökas när det korrekta värdet för knapptryckning hittas i r0, eller återställas till 0. De konstigare fallen där det inte är ett värde mellan 0x0 och 0xB inträffar när man hanterar steg med flera knappar, t.ex. när man trycker på A och B samtidigt. En person som försöker mata in denna kombination kommer vanligtvis inte att trycka på båda knapparna vid exakt samma tidpunkt som pad trace uppstår, så den måste hantera att en av knapparna trycks in före den andra.

    Fortsätter med de olika kodvägarna:

    • r5 sätts till 9 när RIGHT trycks in vid 8040ece8.
    • r5 sätts till 8 när C-stick höger trycks på 8040eccc.
    • r5 sätts till 7 när C-stick vänster trycks på 8040ecb0.
    • r5 sätts till 6 när LEFT trycks på 8040ec98.
    • r5 sätts till 5 (och r6 till 1) när DOWN trycks på 8040ec7c.
    • r5 sätts till 4 när C-stick upp trycks på 8040ec64.
    • r5 sätts till 3 när C-stick ned trycks på 8040ec48.
    • r5 sätts till 2 när UP trycks på 8040ec30.
    • r5 sätts till 1 (och r6 till 1) när Z trycks på 8040ec1c.

    Den aktuella sekvensen är:

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

    Ett ytterligare villkor kontrolleras före Z-kontrollen: samtidigt som den nyss intryckta knappen måste vara Z, måste de aktuella flaggorna vara 0x2030: de vänstra och högra stötfångarna måste också vara intryckta (de har värdena 0x10 och 0x20). Dessutom är UP/DOWN/LEFT/RIGHT knapparna påD-pad, inte analoga pinnar.

    Fuskkoden

    Den fullständiga kombinationen är:

    1. Håll L+R-bumpers och tryck på 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

    Det fungerar! Anslut en styrenhet till den andra porten och ange koden, och felsökningsskärmarna kommer att visas. Därefter kan du börja trycka på knappar på den andra (eller till och med tredje) kontrollenheten för att börja göra saker.

    Den här kombinationen fungerar utan att du behöver patcha spelets versionsnummer.Du kan till och med använda den här på en vanlig detaljhandelskopia av spelet utan några fuskverktyg eller konsolmods. Om du anger kombinationen en andra gång stängs zuru-läget av igen.

    Användning av koden på en riktig GameCube

    Meddelandet ”ZURU %d/%d” i zurumode_callback används för att skriva ut statusen för den här kombinationen om du anger den när disk-ID:t redan är 0x99 (förmodligen för att felsöka fuskkoden i sig själv). Det första numret är din nuvarande position i sekvensen, som matchar r5. Det andra talet sätts till 1 när vissa knappar i sekvensen hålls nedtryckta, dessa kan motsvara när r6 sätts till 1.

    De flesta av displayerna förklarar inte vad de är på skärmen, så för att ta reda på vad de gör måste du hitta de funktioner som hanterar dem. Till exempel är den långa raden av blå och röda rutor som visas högst upp på skärmen platshållare för att visa statusen för olika uppdrag. När ett quest är aktivt kommer några siffror att visas där, vilket indikerar questens status.

    Den svarta skärmen som visas när du trycker på Z är en konsol för att skriva ut felsökningsmeddelanden, men specifikt för saker på låg nivå som minnesallokering och heapfel eller andra dåliga undantag. Beteendet hos fault_callback_scroll tyder på att den kan vara till för att visa dessa fel innan systemet startas om. Jag utlöste inget av dessa fel, men jag kunde få den att skriva ut ett par skräptecken med några NOPs. Jag tror att detta skulle vara väldigt användbart för att skriva ut egna felsökningsmeddelanden senare:

    JUTConsole garbage characters

    Efter att ha gjort allt detta, upptäckte jag att det redan är känt att man får felsökningsläget genom att patcha versions-ID till 0x99: https://tcrf.net/Animal_Crossing#Debug_Mode. (De har också några bra anteckningar om vad de olika displayerna är, och fler saker du kan göra med hjälp av en kontrollant i port 3.) Såvitt jag kan se har dock inte fuskkombinationen publicerats ännu.

    Det var allt för det här inlägget. Det finns fortfarande några fler utvecklarfunktioner som jag skulle vilja utforska, till exempel debug map screen och NES emulator select screen, och hur man aktiverar dem utan att använda patchar.

    Map select screen

    Jag kommer också att publicera skrivelser om hur man vänder på dialog-, event- och quest-systemen i syfte att göra moddar.

    Uppdatering: Slidorna för det föredrag jag höll om detta finns här.