jamchamb’s blog

Sidste sommer begyndte jeg at lave reverse engineering af Animal Crossing til GameCube for at undersøge muligheden for at lave mods til spillet. Jeg ønskede også at dokumentere processen for at skabe tutorials for folk, der er interesseret i ROM-hacking og reverse engineering.I dette indlæg udforsker jeg de udvikler-fejlfindingsfunktioner, der stadig er tilbage i spillet, og hvordan jeg opdagede en cheat combo, der kan bruges til at låse dem op.

new_Debug_mode

Mens jeg kiggede rundt på nogle resterende debug-symboler, lagde jeg mærke til funktioner og variabelnavne, der indeholdt ordet “debug”, og tænkte, at det ville være interessant at se, hvilke debug-funktioner der kunne være tilbage i spillet. Hvis der var nogle debugging- eller udviklerfunktioner, som jeg kunne aktivere, kunne det også hjælpe med processen med at oprette mods.

Den første funktion, jeg kiggede på, var new_Debug_mode.Den kaldes af entry-funktionen, som kører lige efter, at Nintendotrademark-skærmen er færdig. Det eneste, den gør, er at allokere en 0x1C94 byte-struktur og gemme dens pointer.

Når den bliver kaldt i entry, sættes en værdi på 0 ved offset 0xD4 i den allokerede struktur,lige før mainproc bliver kaldt.

Disassemblering af indgangsfunktionen

For at se, hvad der sker, når værdien er ikke-nul, patcherede jeg li r0, 0-instruktionen ved80407C8C til li r0, 1. De rå bytes for instruktionen li r0, 0 er 38 00 00 00,hvor den tildelte værdi er i slutningen af instruktionen, så du kan bare ændre dettetil 38 00 00 01 for at få li r0, 1. For en mere pålidelig måde at sammensætte instruktioner på, kan du bruge noget som kstool:

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

Du kan anvende denne patch i Dolphin-emulatoren ved at gå til fanen “Patches” i spillets sproperties og indtaste den på følgende måde:

Debug performance meter

Indstillingen af denne værdi til 1 fik en interessant graf til at dukke op i bunden af skærmen:

Debug performance meter

Det lignede en performance meter, hvor de små søjler i bunden af skærmen voksede og skrumpede. (Da jeg senere slog navnene på de funktioner, der tegner grafen, op, fandt jeg ud af, at de rent faktisk viser målinger for CPU- og hukommelsesforbrug.) Det var pænt, men ikke særlig nyttigt. Indstilling af værdien over 1 stoppede faktisk mytown fra at blive indlæst, så det virkede ikke som om der var meget andet at gøre med dette.

Zuru mode

Jeg begyndte at kigge rundt på andre referencer til debug-relaterede ting, og så noget, der hed “zuru mode”, dukke op et par gange. Forgreninger til kodeblokke, der havde debug-funktionalitet, kontrollerede ofte en variabel kaldet zurumode_flag.

game_move_first-funktion

I game_move_first-funktionen, der er afbilledet ovenfor, bliver zzz_LotsOfDebug (et navn, jeg har fundet på)kun kaldt, hvis zurumode_flag er ikke-nul.

Søger man efter funktioner, der er relateret til denne værdi, får man følgende:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

Op det første øjekast er de alle lidt uoverskuelige, idet de snurrer rundt med forskellige bits på offsets i en variabel ved navn osAppNMIBuffer.Her er et første kig på, hvad disse funktioner gør:

zurumode_init

  • Sæt zurumode_flag til 0
  • Kontroller nogle bits i osAppNMIBuffer
  • Lagrer en pointer til zurumode_callback-funktionen i padmgr-strukturen
  • Kald zurumode_update

zurumode_update

  • Kontroller nogle bits i osAppNMIBuffer
  • Kontroller betinget opdatering af zurumode_flag på grundlag af disse bits
  • Udskriv en formatstreng til OS-konsollen.

Denne slags ting er normalt nyttigefor at give kontekst til koden, men der var en masse tegn, der ikke kan udskrives, i strengen. Den eneste genkendelige tekst var “zurumode_flag” og “%d”.

zuru mode format string

Idet formodede, at det måske var japansk tekst med en multibyte-tegnkodning, kørte jeg strengen gennem et tegnkodningsdetektionsværktøj og fandt ud af, at den var Shift-JIS-kodet. Den oversatte streng betyder blot “zurumode_flag hasbeen changed from %d to %d”. Det giver ikke mange nye oplysninger, men det gør det at kende til brugen af Shift-JIS, da der er mange flere strenge i binære filer og strengtabeller, der anvender denne kodning.

zurumode_callback

  • Kald zerumode_check_keycheck
  • Kontroller nogle bits i osAppNMIBuffer
  • Påtrykker værdien af zurumode_flag et sted
  • Kald zurumode_update

zerumode_check_keycheck dukkede ikke op før på grund af den forskellige stavemåde.. hvad er det?

zerumode_check_keycheck

En enorm kompleks funktion, der laver mange flere bit-twiddling på værdier uden navne.På dette tidspunkt besluttede jeg at trække mig tilbage og kigge efter andre debug-relaterede funktioner ogvariabler, da jeg ikke engang var sikker på, hvad betydningen af zuru mode var. Jeg var heller ikke sikker på, hvad “key check” betød her. Kunne det være en kryptografisk nøgle?

Tilbage til debug

Omkring dette tidspunkt bemærkede jeg, at der var et problem med den måde, jeg indlæste debug-symbolerne i IDA på. foresta.map-filen på spildisken indeholder en masse adresser og navne til funktioner og variabler. Jeg havde ikke i første omgang bemærket, at adresserne for hver sektion startede ved nul, så jeg oprettede bare et simpelt script til at tilføje en navnepost for hver linje i filen.

Jeg oprettede nye nogle IDA-scripts til at rette op på indlæsningen af symbolkortet for de forskellige sektioner af programmet: .text, .rodata, .data og .bss. .text-sektionen er der, hvor alle funktionerne er,så jeg indstillede scriptet til automatisk at opdage funktioner på hver adresse, når jeg denne gang indstiller et navn.

For data-sektionerne indstillede jeg det til at oprette et segment for hvert binært objekt (f.eks. m_debug.o,som ville være kompileret kode for noget, der hedder m_debug), og indstillede plads og navne for hvert stykke data.Dette giver mig langt flere oplysninger, end jeg havde før, selv om jeg nu var nødt til manuelt at definere datatypen for hvert datastykke, da jeg indstillede hvert dataobjekt til at være et simpelt byte array. (Set i bakspejlet ville det have været bedre i det mindste at antage, at alle data med en størrelse, der er et multiplum af 4 bytes, indeholdt 32-bitinteger, da der er så mange af dem, og mange indeholder adresser til funktioner og data, der er vigtigefor opbygning af krydsreferencer.)

Mens jeg gennemgik det nye .bss-segment for m_debug_mode.o, så jeg nogle variabler som quest_draw_status ogevent_status. Disse er interessante, fordi jeg ønsker at få debug-tilstand til at vise nogle mere nyttige ting end ydelsesgrafen. Heldigvis var der krydsreferencer fra disse dataposter til et stort stykke kode, der kontrollerer debug_print_flg.

Med Dolphin-debuggeren satte jeg et breakpoint på den funktion, hvor debug_print_flg bliver kontrolleret (ved 8039816C) for at se, hvordan kontrollen fungerer. Breakpointet ramte aldrig.

Lad os kontrollere hvorfor: Denne funktion kaldes af game_debug_draw_last. Gæt, hvilken værdi der kontrolleres, før den betingetkaldes? zurumode_flag. Hvad pokker er det?

zurumode_flag check

Jeg satte et breakpoint på denne check (80404E18), og den brød straks sammen. Værdien af zurumode_flag var nul, så den ville normalt springe over at kalde denne funktion. Jeg NOPped ud af branch-instruktionen (erstattede den med en instruktion, der ikke gør noget) for at se, hvad der ville ske, når funktionen bliver kaldt.

I Dolphin-debuggeren kan du gøre dette ved at sætte spillet på pause, højreklikke på en instruktion og derefter klikke på “Insert nop”:

Dolphin-debuggeren NOPping

Der skete ikke noget. Derefter tjekkede jeg, hvad der sker inde i funktionen, og fandt en anden branch-anvisning, der kunne kortslutte forbi alle de interessante ting ved 803981a8. Jeg NOPpedede også den ud, og bogstavet “D” dukkede op i øverste højre hjørne af skærmen.

Debug mode-bogstavet D

Der var en masse mere interessant udseende kode i denne funktion ved 8039816C (jeg kaldte den zzz_DebugDrawPrint),men intet af det blev kaldt. Hvis du kigger på grafvisningen af denne funktion, kan du se, at der er en række branch statements, der springer over blokke i hele funktionen:

Branches in zzz_DebugDrawPrint

Ved at NOPpe flere af disse branch statements ud, begyndte jeg at se forskellige ting blive udskrevet på skærmen:

Mere debug ting bliver udskrevet

Det næste spørgsmål er, hvordan man kan aktivere disse debug-funktioner uden at ændre koden.Desuden vises zurumode_flag igen for nogle branch statements lavet i denne debug draw-funktion. jeg tilføjede en anden patch, så zurumode_flag altid sættes til 2 i zurumode_update, fordi det er susually sammenlignet specifikt med 2, når det ikke sammenlignes med 0.Efter genstart af spillet så jeg denne “msg. no”-meddelelse, der blev vist øverst til højre på skærmen.

meddelelsesnummervisning

Tallet 687 er post ID for den senest viste meddelelse. Jeg kontrollerede dette ved hjælp af en simpletable viewer, som jeg lavede tidligt, men du kan også kontrollere det med en fuld GUI-strengtabeleditor, som jeg lavede til ROM-hacking. Her er hvordan beskeden ser ud i editoren:

Besked 687 i string table editor

På dette tidspunkt stod det klart, at det ikke længere kunne undgås at finde ud af zuru mode; det er stiftet direkte ind i fejlfindingsfunktionerne i spillet.

Zuru-tilstand genbesøgt

Ved at vende tilbage til zurumode_init initialiseres et par ting:

  • 0xC(padmgr_class) sættes til adressen på zurumode_callback
  • 0x10(padmgr_class) sættes til adressen på padmgr_class selv
  • 0x4(zuruKeyCheck) sættes til den sidste bit af et ord, der er indlæst fra 0x3C(osAppNMIBuffer).

Jeg undersøgte, hvad padmgr betyder, og det er en forkortelse for “gamepad manager”. Det tyder på, at der kunne være en særlig tastekombination (knap), der skal indtastes på gamepadet for at aktivere zuru-tilstand, eller muligvis en særlig debugging-enhed eller udviklingskonsolfunktion, der kan bruges til at sende et signal for at aktivere den.

zurumode_init kører kun første gang spillet indlæses (et tryk på reset-knappen udløser den ikke).

Sætter vi et breakpoint ved 8040efa4, hvor 0x4(zuruKeyCheck) er sat, kan vi se, at under opstartuden at holde nogen taster nede, vil den blive sat til 0. Udskiftning af dette med 1 får en interessant ting til at ske:

Title screen with zuru mode

Bogstavet “D” dukker op i øverste højre hjørne igen (grønt i stedet for gult denne gang),og der er også noget build-info:

En patch til at få 0x4(zuruKeyCheck) altid sat til 1 ved start:

8040ef9c 38c00001

Det ser ud til at være den korrekte måde at få zuru mode initialiseret på. Derefter kan der være forskellige handlinger, som vi skal foretage for at få visse fejlfindingsoplysninger til at blive vist. Ved at starte spillet op og gå rundt og tale med en landsbyboer blev der ikke vist nogen af de tidligere nævnte visninger (ud over bogstavet “D” i hjørnet).

De sandsynlige mistænkte er zurumode_update og zurumode_callback.

zurumode_update

zurumode_update bliver først kaldt af zurumode_init, og bliver derefter gentagne gange kaldt afzurumode_callback.

Den kontrollerer den sidste bit i 0x3C(osAppNMIBuffer) igen og opdaterer derefter zurumode_flagpå baggrund af dens værdi.

Hvis bitten er nul, sættes flaget til nul.

Hvis det ikke er tilfældet, kører følgende instruktion med r5 som den fulde værdi af 0x3c(osAppNMIBuffer):

extrwi r3, r5, 1, 28

Dette udtrækker den 28. bit fra r5 og gemmer den i r3.Derefter lægges 1 til resultatet, så det endelige resultat altid er 1 eller 2.

zurumode_flag sammenlignes derefter med det foregående resultat, afhængigt af hvor mangeaf den 28. og sidste bit der er sat i 0x3c(osAppNMIBuffer): 0, 1 eller 2.

Denne værdi skrives til zurumode_flag. Hvis det ikke ændrede noget, slutter funktionen og returnerer den aktuelle værdi af flaget. Hvis den ændrer værdien, udføres en meget mere kompleks kæde af kodeblokke.

Der udskrives en meddelelse på japansk: Dette er den tidligere nævnte meddelelse “zurumode_flag er blevet ændret fra %d til %d”

Dernæst kaldes en række funktioner med forskellige argumenter afhængigt af, om flaget blev ændret til nul eller ej. Samlingen for denne del er kedelig, så pseudokoden for den ser således ud:

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

Bemærk, at hvis flaget er nul, får JC_JUTDbPrint_setVisible et argument på 0. Hvis flaget ikke er nul OG bit 25 eller bit 31 er sat i 0x3C(osAppNMIBuffer), får setVisible-funktionen et argument på 1.

Dette er den første nøgle til aktivering af zuru-tilstand: Den sidste bit i 0x3C(osAppNMIBuffer) skal sættes til 1 for at gøre debug-skærmene synlige og sætte zurumode_flag til en værdi, der ikke er nul.

zurumode_callback

zurumode_callback er på 8040ee74 og kaldes sandsynligvis af en funktion, der er relateret til gamepadet. Når der sættes et breakpoint på den i Dolphin-debuggeren, viser callstack’en, at den faktisk kaldes fra .

En af de første ting, den gør, er at køre zerucheck_key_check. Det er komplekst, men overordnet set ser det ud til at læse og derefter opdatere værdien af zuruKeyCheck. Jeg besluttede at se, hvordan denne værdi bruges i resten af callback-funktionen, før jeg gik videre til keycheck-funktionen.

Dernæst kontrollerer den nogle bits i 0x3c(osAppNMIBuffer) igen. Hvis bit 26 er sat, eller hvis bit 25 er sat, og padmgr_isConnectedController(1) returnerer ikke-nul, sættes den sidste bit i 0x3c(osAppNMIBuffer) til 1!

Hvis ingen af disse bits er sat, eller hvis bit 25 i det mindste er sat, men padmgr_isConnectedController(1) returnerer 0, kontrollerer den, om byte på 0x4(zuruKeyCheck) er 0. Hvis den er det, nulstiller den den sidste bit i den oprindelige værdi og skriver den tilbage til 0x3c(osAppNMIBuffer).Hvis ikke, så sætter den stadig den sidste bit til 1.

I pseudokode ser det sådan ud:

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, hvis bit 26 ikke er sat, så shorter den til at kalde zurumode_update og afslutter derefter.

Hvis den er sat, så hvis 0x4(zuruKeyCheck) ikke er nul, så indlæser den en formatstreng, hvor det ser ud til, at den vil udskrive: “ZURU %d/%d”.

Recap

Her er, hvad der sker:

padmgr_HandleRetraceMsg kalder zurumode_callback.Mit gæt er, at “handle retrace message” betyder, at den netop har scannet tastetryk på controlleren. Hver gang den scanner, kalder den muligvis en række forskellige callbacks.

Når zurumode_callback kører, kontrollerer den de aktuelle tastetryk (knaptryk).Dette ser ud til at kontrollere en bestemt knap eller en kombination af knapper.

Den sidste bit i NMI-bufferen opdateres baseret på specifikke bits i dens currentvalue samt værdien af en af de zuruKeyCheck bytes (0x4(zuruKeyCheck)).

Derpå kører zurumode_update og kontrollerer denne bit. Hvis den er 0, sættes zuru-tilstandsflaget til 0. Hvis den er 1, opdateres tilstandsflaget til 1 eller 2, afhængigt af om bit 28 er sat.

De tre måder at aktivere zuru-tilstand på er:

  1. Bit 26 er indstillet i 0x3C(osAppNMIBuffer)
  2. Bit 25 er indstillet i 0x3C(osAppNMIBuffer), og der er tilsluttet en controller til port 2
  3. 0x4(zuruKeyCheck) er ikke nul

osAppNMIBuffer

Spørgsmålet er, hvad osAppNMIBuffer var, Jeg startede med at søge på “NMI” og fandt henvisninger til “non-maskable interrupt” i forbindelse med Nintendo. Det viser sig, at hele variablenavnet også optræder i udviklerdokumentationen for Nintendo 64:

osAppNMIBuffer er en 64-byte buffer, som ryddes ved en kold nulstilling. Hvis systemet genstartes på grund af en NMI, er denne buffer uændret.

Grundlæggende er dette et lille stykke hukommelse, der er vedvarende på tværs af bløde genstarter. Et spil kan bruge denne buffer til at gemme, hvad det vil, så længe konsollen er tændt.Det oprindelige Animal Crossing-spil blev faktisk udgivet på Nintendo 64, så det giver mening, at noget som dette ville dukke op i koden.

Skifter man over til den binære boot.dol (alt ovenfor er fra foresta.rel), er der mange henvisninger til osAppNMIBuffer i main-funktionen. Et hurtigt kig viser, at der er en række kontroller, der kan resultere i, at forskellige bits i 0x3c(osAppNMIBuffer) bliver sat med OR-operationer.

Interessante OR-operandværdier at holde øje med ville være:

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

Husk, at bit 25, 26 og 28 er særligt interessante: 25 og 26 bestemmer, om zuru-tilstand er aktiveret, og bit 28 bestemmer niveauet for flaget (1 eller 2).Bit 31 er også interessant, men synes primært at blive opdateret på baggrund af værdierne af de andre.

Bit 26

Først: ved 800062e0 er der en ori r0, r0, 0x20-instruktion om bufferværdien ved 0x3c. Dette ville sætte bit 26, hvilket altid resulterer i, at zuru-tilstand er aktiveret.

Sætning af bit 26

For at bitten kan sættes, skal den 8. byte, der returneres fra DVDGetCurrentDiskID, være 0x99.Denne ID er placeret helt i begyndelsen af spilskiveaftrykket og indlæses op ved80000000 i hukommelsen. For en almindelig detailudgave af spillet ser ID’et således ud:

47 41 46 45 30 31 00 00 GAFE01..

Patching af den sidste byte af ID’et til 0x99 får følgende til at ske, når spillet startes op:

Game version ID 0x99

Og i OS-konsollen bliver følgende udskrevet:

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

Alle de andre patches kan fjernes, og bogstavet D vises også i øverste højre hjørne af skærmen igen, men ingen af de andre debug-displays er aktiveret.

Bit 25

Bit 25 bruges i forbindelse med udførelse af kontrol af port 2-controller. Hvad får den til at blive aktiveret?

Bit 25 og 28

Dette viser sig at have den samme kontrol, som anvendes for bit 28: versionen skal være større end eller lig med 0x90. Hvis bit 26 var sat (ID er 0x99), vil begge dissebits også være sat, og zuru-tilstand vil alligevel blive aktiveret.

Hvis versionen er mellem 0x90 og 0x98, bliver zuru-tilstand dog ikke straks aktiveret.Hvis vi husker kontrollen i zurumode_callback, vil den kun blive aktiveret, hvis bit 25 er sat, og padmgr_isConnectedController(1) returnerer ikke-nul.Når en controller er tilsluttet til port 2 (argumentet til isConnectedController er nul-indekseret), bliver zuru-tilstand aktiveret. Bogstavet D og build-info vises på titelskærmen, og…ved at trykke på knapperne på den anden controller styres debug-skærmene!

Nogle knapper gør også ting ud over at ændre displayet, f.eks. øger spillets hastighed.

zerucheck_key_check

Det sidste mysterium er 0x4(zuruKeyCheck). Det viser sig, at denne værdi bliver opdateret af den gigantiske komplekse funktion, der blev vist før:

zerumode_check_keycheck

Ved hjælp af Dolphin-debuggeren kunne jeg konstatere, at den værdi, der kontrolleres af denne funktion, er et sæt af bits, der svarer til tryk på knapper på den anden controller. Sporingen af knaptryk er gemt i en 16-bit værdi på 0x2(zuruKeyCheck). Når der ikke er nogen controller tilsluttet, er værdien 0x7638.

De 2 bytes, der indeholder flag for knaptryk på controller 2, indlæses og opdateres derefter nær begyndelsen af zerucheck_key_check. Den nye værdi overføres sammen med register r4 af padmgr_HandleRetraceMsg, når den kalder callback-funktionen.

key check end

Nede nær slutningen af zerucheck_key_check er der faktisk et andet sted, hvor 0x4(zuruKeyCheck) opdateres. Det fremgik ikke af listen over krydsreferencer, fordi det bruger r3 som basisadresse, og vi kan kun finde ud af, hvad r3 er, ved at se på, hvad den er sat til, hver gangdenne funktion er ved at blive kaldt.

I 8040ed88 skrives værdien af r4 til 0x4(zuruKeyCheck). Den er indlæst fra samme placering og derefter XORd med 1 lige før det. Dette skulle gøre det muligt at skifte værdien af byten (i virkeligheden kun den sidste bit) mellem 0 og 1. (Hvis den er 0, vil resultatet af XORing med 1 være 1.) Hvis den er 1, vil resultatet være 0. Slå op i sandhedstabellen for XOR for at se dette.)

key check end

Jeg bemærkede ikke denne adfærd, mens jeg kiggede på hukommelsesværdierne før, men jeg vil prøve at bryde på denne instruktion i debuggeren for at se, hvad der sker. Den oprindelige værdi er indlæst på 8040ed7c.

Og uden at røre ved nogen knapper på controllerne rammer jeg ikke dette breakpoint under titelskærmen. For at nå denne kodeblok skal værdien af r5 være 0xb før den forgreningsinstruktion, der kommer før den (8040ed74). Af de mange forskellige veje, der fører op til denne blok, er der en, der vil sætte r5 til 0xb før den, ved 8040ed68.

Setting r5 to 0xb

Bemærk, at for at nå den blok, der sætter r5 til 0xB, skal r0 have været lig med 0x1000 lige før. Hvis vi følger blokkene op ad kæden til begyndelsen af funktionen, kan vi se de begrænsninger, der er nødvendige for at nå denne blok:

  • 8040ed74: r5 skal være 0xB
  • 8040ed60: r0 skal være 0x1000
  • 8040ebe8: r5 skal være 0xA
  • 8040ebe4: r5 skal være mindre end 0x5B
  • 8040eba4: r5 skal være større end 0x7
  • 8040eb94: r6 skal være 1
  • 8040eb5c: r0 må ikke være 0
  • 8040eb74: r0 må ikke være 0
  • 8040eb74: Port 2-knapværdierne skal være ændret

Sporing af kodens vej

Her kommer vi til det punkt, hvor de gamle knapværdier indlæses og de nye værdier gemmes. Herefter er der et par operationer anvendt på de nye og gamleværdier:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

XOR-operationen markerer alle de bits, der er ændret mellem de to værdier. AND-operationen maskerer derefter det nye input for at fjerne alle bits, der ikke er indstillet i øjeblikket. Resultatet i r0 er de nye bits (tryk på knappen) i den nye værdi. Hvis den ikke er tom, er vi på rette vej.

For at r0 kan være 0x1000, skal den fjerde ud af de 16 knapsporbits lige være ændret.Ved at sætte et breakpoint efter XOR/AND-operationen kan jeg finde ud af, hvilketknaptryk der forårsager dette: det er START-knappen.

Det næste spørgsmål er, hvordan jeg får r5 til at starte som 0xA. r5 og r6 indlæses fra0x0(zuruKeyCheck) i begyndelsen af tastekontrolfunktionen og opdateres nær slutningen, når vi ikke afbryder kodeblokken, der skifter 0x4(zuruKeyCheck).

Der er et par steder lige før, hvor r5 bliver sat til 0xA:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
  • 8040ed34: r0 skal være 0x4000 (der blev trykket på B-knappen)
  • 8040ebe0: r5 skal være 0x5b
  • 8040eba4: r5 skal være større end 0x7
  • Samme som før herfra og fremover…

r5 skal starte på 0x5b

8040ed00
  • 8040ecfc: r0 skal være 0xC000 (A og B trykkes ned)
  • 8040ebf8: r5 skal være >= 9
  • 8040ebf0: r5 skal være mindre end 10
  • 8040ebe4: r5 skal være mindre end 0x5b
  • 8040eba4: r5 skal være større end 0x7
  • som før herfra og fremover…

r5 skal starte ved 9

8040ed50
  • 8040ed4c: r0 skal være 0x8000 (der blev trykket på A)
  • 8040ec04: r5 skal være mindre end 0x5d
  • 8040ebe4: r5 skal være større end 0x5b
  • 8040eba4: r5 skal være større end 0x7
  • Samme som før fra nu af…

r5 skal starte ved 0x5c

Det ser ud til, at der er en slags tilstand mellem knaptryk, og så skal der indtastes en vis rækkefølge af knapkombinationer, som slutter med START. Det ser ud til, at A og/eller B kommer lige før START.

Følger man kodevejen, der sætter r5 til 9, viser der sig et mønster: r5 er en inkrementerende værdi, der enten kan øges, når den korrekte værdi for knaptryk findes i r0, eller nulstilles til 0. De mærkeligste tilfælde, hvor det ikke er en værdi mellem 0x0 og 0xB, opstår, når der håndteres trin med flere knapper, f.eks. når der trykkes på A og B på samme tid. En person, der forsøger at indtaste denne kombination, vil normalt ikke trykke på begge knapper på nøjagtig samme tidspunkt, hvor pad traceet opstår, så den skal håndtere, at der trykkes på en af knapperne før den anden.

Fortsat med de forskellige kodeveje:

  • r5 sættes til 9, når der trykkes på RIGHT ved 8040ece8.
  • r5 sættes til 8, når der trykkes på C-stick højre ved 8040eccc.
  • r5 sættes til 7, når der trykkes på C-stick venstre ved 8040ecb0.
  • r5 sættes til 6, når der trykkes på LEFT ved 8040ec98.
  • r5 sættes til 5 (og r6 til 1), når der trykkes på DOWN ved 8040ec7c.
  • r5 sættes til 4, når der trykkes på C-stick op ved 8040ec64.
  • r5 sættes til 3, når der trykkes på C-stick ned ved 8040ec48.
  • r5 sættes til 2, når der trykkes på UP ved 8040ec30.
  • r5 sættes til 1 (og r6 til 1), når der trykkes på Z ved 8040ec1c.

Den aktuelle sekvens er:

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

Der kontrolleres endnu en betingelse før Z-kontrollen: mens den nyligt trykkede knap skal være Z, skal de aktuelle flag være 0x2030: den venstre og højre bumpers skal også være trykket (de har værdierne 0x10 og 0x20). Desuden er UP/DOWN/LEFT/RIGHT knapperne påD-padet, ikke analog stick.

Snydekoden

Den fulde combo er:

  1. Hold L+R-bumpers og tryk på Z
  2. D-UP
  3. C-DOWN
  4. C-UP
  5. D-D-DOWN
  6. D-LEFT
  7. C-LEFT
  8. C-RIGHT
  9. D-RIGHT
  10. A+B
  11. START

Det virker! Tilslut en controller til den anden port og indtast koden, og fejlfindingsdisplayet vil blive vist. Derefter kan du begynde at trykke på knapperne på den anden (eller endda tredje) controllerfor at begynde at gøre ting.

Denne kombination virker uden at patche spillets versionsnummer.Du kan endda bruge dette på en almindelig detailkopi af spillet uden nogen snydeværktøjereller konsolmods. Indtastning af kombinationen anden gang slår zuru-tilstanden fra igen.

Ved brug af koden på en rigtig GameCube

Meddelelsen “ZURU %d/%d” i zurumode_callback bruges til at udskrive status for denne kombination, hvis du indtaster den, når disk-ID’et allerede er 0x99 (formodentlig for at fejlfinde selve snydekoden). Det første tal er din aktuelle position i sekvensen, der passer til r5. Det andet er sat til 1, mens visse knapper i sekvensen holdes nede, disse kan svare til, når r6 er sat til 1.

De fleste af displayene forklarer ikke, hvad de er på skærmen, så for at finde ud af, hvad de gør, skal du finde de funktioner, der håndterer dem. For eksempel er den lange række af blå og redasterisks, der vises øverst på skærmen, pladsholdere til visning af status for forskellige forespørgsler. Når en quest er aktiv, vises der nogle tal, der angiver questens tilstand.

Den sorte skærm, der vises, når du trykker på Z, er en konsol til at udskrive fejlfindingsmeddelelser, men specielt til ting på lavt niveau som f.eks. hukommelsesallokering og heap-fejl eller andre dårlige exceptions. Opførslen af fault_callback_scroll tyder på, at det kan være forvisningen af disse fejl, før systemet genstartes. Jeg udløste ikke nogen af disse fejl, men jeg var i stand til at få den til at udskrive et par skraldetegn med nogle NOP’er. Jeg tror, at dette ville være meget nyttigt til at udskrive brugerdefinerede fejlfindingsmeddelelser senere:

JUTConsole garbage characters

Efter at have gjort alt dette, fandt jeg ud af, at det allerede er kendt at få debug-tilstand ved at patche versions-id’et til 0x99: https://tcrf.net/Animal_Crossing#Debug_Mode. (De har også nogle godenoter om, hvad de forskellige displays er, og flere ting man kan gøre ved hjælp af en controller i port 3.)Så vidt jeg kan se, er snydekombinationen dog ikke blevet offentliggjort endnu.

Det var alt for dette indlæg. Der er stadig nogle flere udviklerfunktioner, som jeg gerne vil udforske,såsom debug map screen og NES emulator select screen, og hvordan man kan aktivere dem uden at bruge patches.

Map select screen

Jeg vil også poste write ups om at vende dialog-, event- og quest-systemerne med henblik på at lave mods.

Update: Slides til det foredrag, jeg holdt om dette, kan findes her.