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.
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
:
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:
Indstillingen af denne værdi til 1 fik en interessant graf til at dukke op i bunden af skærmen:
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
.
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 ipadmgr
-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”.
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?
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?
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”:
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.
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:
Ved at NOPpe flere af disse branch statements ud, begyndte jeg at se forskellige ting blive udskrevet på skærmen:
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.
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:
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 fra0x3C(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:
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_flag
på 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)
:
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:
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:
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:
- Bit 26 er indstillet i
0x3C(osAppNMIBuffer)
- Bit 25 er indstillet i
0x3C(osAppNMIBuffer)
, og der er tilsluttet en controller til port 2 -
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.
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:
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?
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:
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.
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.)
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
.
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ære0xB
- 8040ed60:
r0
skal være0x1000
- 8040ebe8:
r5
skal være0xA
- 8040ebe4:
r5
skal være mindre end0x5B
- 8040eba4:
r5
skal være større end0x7
- 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
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ære0x4000
(der blev trykket på B-knappen) -
8040ebe0
:r5
skal være0x5b
-
8040eba4
:r5
skal være større end0x7
- Samme som før herfra og fremover…
r5
skal starte på 0x5b
8040ed00
-
8040ecfc
:r0
skal være0xC000
(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 end0x5b
-
8040eba4
:r5
skal være større end0x7
- som før herfra og fremover…
r5
skal starte ved 9
8040ed50
-
8040ed4c
:r0
skal være0x8000
(der blev trykket på A) -
8040ec04
:r5
skal være mindre end0x5d
-
8040ebe4
:r5
skal være større end0x5b
-
8040eba4
:r5
skal være større end0x7
- 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 ved8040ece8
. -
r5
sættes til 8, når der trykkes på C-stick højre ved8040eccc
. -
r5
sættes til 7, når der trykkes på C-stick venstre ved8040ecb0
. -
r5
sættes til 6, når der trykkes på LEFT ved8040ec98
. -
r5
sættes til 5 (og r6 til 1), når der trykkes på DOWN ved8040ec7c
. -
r5
sættes til 4, når der trykkes på C-stick op ved8040ec64
. -
r5
sættes til 3, når der trykkes på C-stick ned ved8040ec48
. -
r5
sættes til 2, når der trykkes på UP ved8040ec30
. -
r5
sættes til 1 (ogr6
til 1), når der trykkes på Z ved8040ec1c
.
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:
- Hold L+R-bumpers og tryk på Z
- D-UP
- C-DOWN
- C-UP
- D-D-DOWN
- D-LEFT
- C-LEFT
- C-RIGHT
- D-RIGHT
- A+B
- 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.
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:
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.
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.