jamchamb’s blog

Vara trecută am început să fac inginerie inversă pentru Animal Crossing pentru GameCube pentru a explora posibilitatea de a crea moduri pentru acest joc. Am vrut, de asemenea, să documentez procesulpentru a crea tutoriale pentru persoanele interesate de ROM hacking și inginerie inversă. în acest post explorez caracteristicile de depanare a dezvoltatorului care sunt încă lăsate în joc și cum am descoperit o combinație de cheat care poate fi folosită pentru a le debloca.

new_Debug_mode

În timp ce mă uitam la unele simboluri de depanare rămase, am observat funcții și nume de variabile care conțineau cuvântul „depanare” și m-am gândit că ar fi interesant să văd ce funcționalitate de depanare ar putea fi lăsată în joc. Dacă ar exista vreo funcție de depanare sau de dezvoltator pe care aș putea să o activez, ar putea ajuta și în procesul de creare a modurilor.

Prima funcție la care m-am uitat a fost new_Debug_mode.Este apelată de funcția entry, care rulează imediat după ce se termină ecranul Nintendotrademark. Tot ceea ce face este să aloce o structură de 0x1C94 octeți și să salveze pointerul acesteia.

După ce este apelată în entry, o valoare de 0 este setată la offset 0xD4 în structura alocată,chiar înainte ca mainproc să fie apelată.

Dezasamblarea funcției de intrare

Pentru a vedea ce se întâmplă când valoarea este diferită de zero, am corectat instrucțiunea li r0, 0 de la80407C8C la li r0, 1. Octeții brute pentru instrucțiunea li r0, 0 sunt 38 00 00 00,unde valoarea atribuită se află la sfârșitul instrucțiunii, așa că nu trebuie decât să schimbați acest lucruîn 38 00 00 01 pentru a obține li r0, 1. Pentru un mod mai fiabil de asamblare a instrucțiunilor,ați putea folosi ceva de genul kstool:

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

Puteți aplica acest patch în emulatorul Dolphin mergând la fila „Patches” din sproprietățile jocului și introducându-l astfel:

Debug performance meter

Stabilirea acestei valori la 1 a făcut să apară un grafic cu aspect interesant în partea de jos a ecranului:

Debug performance meter

Arăta ca un contor de performanță, cu barele mici din partea de jos a ecranului crescând și micșorându-se. (Mai târziu, când am căutat numele funcțiilor care desenează graficul, am constatat că acestea afișează, de fapt, măsurători pentru utilizarea CPU și a memoriei.)Era frumos, dar nu deosebit de util. Setarea valorii peste 1 a oprit de fapt încărcarea mytown de la încărcare, așa că nu părea să mai fie multe de făcut cu acest lucru.

Zuru mode

Am început să mă uit în jur la alte referințe la lucruri legate de depanare și am văzut ceva numit „zuru mode” apărând de câteva ori. Ramificările către blocurile de cod care aveau funcționalitate de depanareverificau adesea o variabilă numită zurumode_flag.

funcția game_move_first

În funcția game_move_first ilustrată mai sus, zzz_LotsOfDebug (un nume inventat de mine)este apelată doar dacă zurumode_flag este diferită de zero.

În căutarea funcțiilor legate de această valoare rezultă următoarele:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

La prima vedere, toate acestea sunt puțin obscure, învârtind diverși biți la intervale de timp într-o variabilă numită osAppNMIBuffer.Iată o primă privire la ceea ce fac aceste funcții:

zurumode_init

  • Stabilește zurumode_flag la 0
  • Verifică niște biți din osAppNMIBuffer
  • Stochează un pointer la funcția zurumode_callback în structura padmgr
  • Apelează zurumode_update.

zurumode_update

  • Verifică unii biți din osAppNMIBuffer
  • Actualizează în mod condiționat zurumode_flag pe baza acestor biți
  • Imprimă un șir de format pe consola sistemului de operare.

Acest tip de lucru este de obicei util pentru a da context codului, dar în acest șir erau o mulțime de caractere neimprimabile. Singurul text recognoscibil era „zurumode_flag” și „%d”.

șirul de format al modului zuru

Gândind că ar putea fi un text japonez care folosește o codificare a caracterelor pe mai mulți octeți, am rulat șirul printr-un instrument de detectare a codificării caracterelor și am aflat că era codificat Shift-JIS. Șirul tradus înseamnă doar „zurumode_flag hasbeen changed from %d to %d”. Acest lucru nu oferă prea multe informații noi, dar cunoașterea utilizării Shift-JIS o face, deoarece există mult mai multe șiruri de caractere în fișierele binare și în tabelele de șiruri care utilizează această codificare.

zurumode_callback

  • Apelează zerumode_check_keycheck
  • Verifică niște biți în osAppNMIBuffer
  • Imprimă valoarea lui zurumode_flag undeva
  • Apelează zurumode_update

zerumode_check_keycheck nu a apărut înainte din cauza ortografiei diferite.. ce este?

zerumode_check_check_keycheck

O funcție complexă uriașă care face o mulțime de alte tatonări de biți pe valori fără nume. în acest moment am decis să mă retrag și să caut alte funcții și variabile legate de depanare, deoarece nici măcar nu eram sigur care era semnificația modului zuru. De asemenea, nu eram sigur ce însemna aici „key check”. Ar putea fi o cheie criptografică?

Înapoi la depanare

În acest moment am observat că exista o problemă cu modul în care am încărcat simbolurile de depanare în IDA. Fișierul foresta.map de pe discul de joc conține o grămadă de adrese și numepentru funcții și variabile. Nu observasem inițial că adresele pentru fiecare secțiune începeau de la zero, așa că am configurat un script simplu pentru a adăuga o intrare de nume pentru fiecare linie din fișier.

Am configurat noi câteva scripturi IDA pentru a repara încărcarea hărții de simboluri pentru diferitele secțiuni ale programului:.text, .rodata, .data și .bss. Secțiunea .text este cea în care se află toate funcțiile,așa că am setat scriptul să detecteze automat funcțiile de la fiecare adresă atunci când setează un nume de data aceasta.

Pentru secțiunile de date, l-am setat să creeze un segment pentru fiecare obiect binar (cum ar fi m_debug.o,care ar fi codul compilat pentru ceva numit m_debug), și am setat spațiul și numele pentru fiecare bucată de date.Acest lucru îmi oferă mult mai multe informații decât aveam înainte, deși acum a trebuit să definesc manual tipul de date pentru fiecare bucată de date, deoarece am setat ca fiecare obiect de date să fie o simplă matrice de octeți. (Privind retrospectiv, ar fi fost mai bine să presupunem cel puțin că orice date cu o dimensiune care este un multiplu de 4 octeți conținea numere întregi pe 32 de biți, deoarece sunt atât de multe dintre ele și multe conțin adrese către funcții și date care sunt importante pentru construirea de referințe încrucișate.)

În timp ce mă uitam prin noul segment .bss pentru m_debug_mode.o, am văzut câteva variabile precum quest_draw_status șievent_status. Acestea sunt interesante pentru că vreau ca modul de depanare să afișeze lucruri mai utile decât graficul de performanță. Din fericire, existau referințe încrucișate de la aceste intrări de date la o bucată uriașă de cod care verifică debug_print_flg.

Utilizând debuggerul Dolphin, am setat un punct de întrerupere pe funcția în care debug_print_flg este verificată (la 8039816C) pentru a vedea cum funcționează verificarea. Punctul de întrerupere nu a fost atins niciodată.

Să verificăm de ce: această funcție este apelată de game_debug_draw_last. Ghiciți ce valoare este verificată înainte de a o apela condiționat? zurumode_flag. Ce naiba este aceasta?

verificareazurumode_flag

Am setat un punct de întrerupere pe această verificare (80404E18) și s-a rupt imediat. Valoarea lui zurumode_flag a fost zero, așa că, în mod normal, ar sări peste apelarea acestei funcții. Am eliminat instrucțiunea de ramificare NOP (am înlocuit-o cu o instrucțiune care nu face nimic) pentru a vedea ce se va întâmpla când funcția va fi apelată.

În depanatorul Dolphin puteți face acest lucru punând jocul pe pauză, făcând clic dreapta pe o instrucțiune și apoi făcând clic pe „Insert nop”:

Dolphin debugger NOPping

Nu s-a întâmplat nimic. Apoi am verificat ce se întâmplă în interiorul funcției și am găsit o altă instrucțiune de bifurcare care ar puteacircuita scurt pe lângă toate lucrurile interesante la 803981a8. Am NOPped că, de asemenea, afară, și litera „D „a apărut în colțul din dreapta sus al ecranului.

Modul de depanare litera D

Exista o grămadă de cod mai interesant în această funcție la 8039816C (am numit-o zzz_DebugDrawPrint),dar nimic din el nu a fost chemat. Dacă vă uitați la vizualizarea grafică a acestei funcții, puteți vedea că există o serie de instrucțiuni de ramificare care sar peste blocuri de-a lungul întregii funcții:

Branșări în zzz_DebugDrawPrint

Prin NOP-ul mai multor declarații de ramificare, am început să văd că se tipăresc diferite lucruri pe ecran:

Se tipăresc mai multe lucruri de depanare

Întrebarea următoare este cum să activăm aceste funcții de depanare fără a modifica codul.De asemenea, zurumode_flag apare din nou pentru unele declarații de ramificare făcute în această funcție de depanare a desenului. am adăugat un alt patch astfel încât zurumode_flag să fie întotdeauna setat la 2 în zurumode_update, deoarece de obicei este comparat în mod specific cu 2 atunci când nu este comparat cu 0.După ce am repornit jocul, am văzut acest mesaj „msg. no” afișat în dreapta sus a ecranului.

afișare număr mesaj

Numărul 687 este ID-ul de intrare al celui mai recent mesaj afișat. Am verificat acest lucru folosind un vizualizator de tabele simple pe care l-am făcut la început, dar îl puteți verifica și cu un editor de tabele de șiruri cu interfață grafică completă pe care l-am făcut pentru hacking-ul ROM. Iată cum arată mesajul în editor:

Mesajul 687 în editorul de tabele de șiruri de caractere

În acest moment era clar că aflarea modului zuru nu mai putea fi evitată; aceasta a intrat direct în funcțiile de depanare ale jocului.

Modul zuru revizuit

Întorcându-ne la zurumode_init, acesta inițializează câteva lucruri:

  • 0xC(padmgr_class) este setat la adresa zurumode_callback
  • 0x10(padmgr_class) este setat la adresa lui padmgr_class însuși
  • 0x4(zuruKeyCheck) este setat la ultimul bit al unui cuvânt încărcat din 0x3C(osAppNMIBuffer).

Am verificat ce înseamnă padmgr și este o prescurtare pentru „gamepad manager”. Acest lucru sugerează că ar putea exista o combinație specială de taste (butoane) de introdus pe gamepad pentru a activa modul zuru sau, eventual, un dispozitiv special de depanare sau o caracteristică a consolei de dezvoltare care ar putea fi folosită pentru a trimite un semnal pentru a-l activa.

zurumode_init se execută numai prima dată când jocul este încărcat (apăsarea butonului de resetare nu îl declanșează).

Stabilind un punct de întrerupere la 8040efa4, unde 0x4(zuruKeyCheck) este setat, putem vedea că în timpul porniriifără a ține apăsată nicio tastă, acesta va fi setat la 0. Înlocuirea acestuia cu 1 face ca un lucru interesant să se întâmple:

Title screen with zuru mode

Letera „D” apare din nou în colțul din dreapta sus (verde în loc de galben de data aceasta),și există, de asemenea, unele informații despre construcție:

Un patch pentru ca 0x4(zuruKeyCheck) să fie întotdeauna setat la 1 la pornire:

8040ef9c 38c00001

Aceasta pare a fi modalitatea corectă de a obține modul zuru inițializat. După aceea,este posibil să existe diferite acțiuni pe care trebuie să le întreprindem pentru a obține afișarea anumitor informații de depanare. Pornind jocul și plimbându-ne și vorbind cu un sătean nu am afișat niciunul dintre afișajele menționate anterior (în afară de litera „D” din colț).

Suspecții probabili sunt zurumode_update și zurumode_callback.

zurumode_update

zurumode_updateeste apelat mai întâi de zurumode_init, iar apoi este apelat în mod repetat dezurumode_callback.

Verifică din nou ultimul bit din 0x3C(osAppNMIBuffer) și apoi actualizează zurumode_flagîn funcție de valoarea acestuia.

Dacă bitul este zero, steagul este setat la zero.

Dacă nu, se execută următoarea instrucțiune cu r5 fiind valoarea completă a lui 0x3c(osAppNMIBuffer):

extrwi r3, r5, 1, 28

Aceasta extrage al 28-lea bit din r5 și îl salvează în r3.Apoi se adaugă 1 la rezultat, astfel încât rezultatul final este întotdeauna 1 sau 2.

zurumode_flageste apoi comparat cu rezultatul anterior, în funcție de câțidin al 28-lea și ultimul bit sunt setați în 0x3c(osAppNMIBuffer): 0, 1 sau 2.

Această valoare este scrisă în zurumode_flag. Dacă nu a schimbat nimic, funcția se încheie și returnează valoarea curentă a indicatorului. Dacă schimbă valoarea,se execută un lanț mult mai complex de blocuri de cod.

Se tipărește un mesaj în japoneză: acesta este mesajul „zurumode_flag has been changed from %d to %d „menționat mai devreme.

Apoi se apelează o serie de funcții cu argumente diferite în funcție de faptul dacă stegulețul a fost schimbat la zero sau nu. Asamblarea pentru această parte este anevoioasă, așa că pseudocodul acesteia se prezintă astfel:

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

Observați că, dacă stegulețul este zero, JC_JUTDbPrint_setVisible primește un argument de 0.Dacă stegulețul nu este zero ȘI bitul 25 sau bitul 31 sunt setați în 0x3C(osAppNMIBuffer), funcțieisetVisible i se trece un argument de 1.

Aceasta este prima cheie pentru activarea modului zuru: ultimul bit din 0x3C(osAppNMIBuffer)trebuie să fie setat la 1 pentru ca afișajele de depanare să fie vizibile și să seteze zurumode_flagla o valoare diferită de zero.

zurumode_callback

zurumode_callback este la 8040ee74 și este probabil apelată de o funcție legată de gamepad. Punând un punct de întrerupere pe ea în debuggerul Dolphin, callstacks arată că este într-adevăr apelată de la padmgr_HandleRetraceMsg.

Unul dintre primele lucruri pe care le face este să ruleze zerucheck_key_check. Este complex, dar în general pare să citească și apoi să actualizeze valoarea lui zuruKeyCheck. Am decis să văd cum este folosită această valoare în restul funcției de rechemare înainte de a merge mai departe în funcția de verificare a tastelor.

În continuare verifică din nou câțiva biți din 0x3c(osAppNMIBuffer). Dacă bitul 26 este setat, sau dacă bitul 25 este setat și padmgr_isConnectedController(1) returnează non-zero, ultimul bit din 0x3c(osAppNMIBuffer)este setat la 1!

Dacă niciunul dintre acești biți nu este setat, sau dacă bitul 25 este cel puțin setat, dar padmgr_isConnectedController(1)returnează 0, atunci verifică dacă octetul de la 0x4(zuruKeyCheck) este 0. Dacă este,atunci va anula ultimul bit din valoarea originală și îl va scrie înapoi în 0x3c(osAppNMIBuffer).Dacă nu, atunci stabilește în continuare ultimul bit la 1.

În pseudocod acest lucru arată astfel:

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}

După aceea, dacă bitul 26 nu este setat, scurtcircuitează până la apelarea lui zurumode_update și apoi termină.

Dacă este setat, atunci dacă 0x4(zuruKeyCheck) nu este zero, încarcă un șir de format în care se pare că se va imprima: „ZURU %d/%d”.

Recap

Iată ce se întâmplă:

apelează zurumode_callback.Bănuiala mea este că „handle retrace message” înseamnă că tocmai a scanat tastele apăsate pe controler. De fiecare dată când scanează, este posibil să apeleze o serie de apeluri diferite.

Când zurumode_callback se execută, verifică apăsările curente de taste (butoane).Aceasta pare să verifice un anumit buton sau o combinație de butoane.

Ultimul bit din NMI Buffer este actualizat pe baza unor biți specifici din valoarea sa curentă, precum și pe baza valorii unuia dintre octeții zuruKeyCheck (0x4(zuruKeyCheck)).

Apoi zurumode_update rulează și verifică acel bit. Dacă este 0, stegulețul de mod zuru va fi beset la 0. Dacă este 1, stegulețul de mod este actualizat la 1 sau 2 în funcție de faptul dacă bitul 28 este setat.

Cele trei moduri de a activa modul zuru sunt:

  1. Bit 26 este setat în 0x3C(osAppNMIBuffer)
  2. Bit 25 este setat în 0x3C(osAppNMIBuffer), iar un controler este conectat la portul 2
  3. 0x4(zuruKeyCheck) nu este zero

osAppNMIBuffer

Întrebare ce a fost osAppNMIBuffer, Am început prin a căuta „NMI” și am găsit referiri la „non-maskable interrupt” în contextul Nintendo. Se pare că întregul variablename apare, de asemenea, în documentația dezvoltatorului pentru Nintendo 64:

osAppNMIBuffer este o memorie tampon de 64 de octeți care este ștearsă la o resetare la rece. Dacă sistemul se reporneștedin cauza unui NMI, acest buffer rămâne neschimbat.

În principiu, aceasta este o mică bucată de memorie care persistă în timpul repornirilor soft. Un joc poateutiliza această memorie tampon pentru a stoca orice dorește atâta timp cât consola este pornită.Jocul original Animal Crossing a fost lansat de fapt pe Nintendo 64, așa că este logic ca ceva de genul acesta să apară în cod.

Să trecem la binarul boot.dol (tot ce este mai sus este din foresta.rel),există o mulțime de referințe la osAppNMIBuffer în funcția main. O privire rapidă aratăcă există o serie de verificări care pot duce la setarea diverșilor biți din 0x3c(osAppNMIBuffer) cu operații OR.

Valorile interesante ale operanzilor OR la care trebuie să fim atenți ar fi:

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

Rețineți că biții 25, 26 și 28 sunt deosebit de interesanți: 25 și 26 determină dacă modul zuru este activat, iar bitul 28 determină nivelul indicatorului (1 sau 2).Bitul 31 este, de asemenea, interesant, dar în primul rând pare a fi actualizat pe baza valorilor celorlalți.

Bit 26

Primul: la 800062e0 există o instrucțiune ori r0, r0, 0x20 pe valoarea bufferului la 0x3c. Aceasta ar seta bitul 26, ceea ce duce întotdeauna la activarea modului zuru.

Setarea bitului 26

Pentru ca bitul să fie setat, al 8-lea octet returnat de la DVDGetCurrentDiskID trebuie să fie 0x99.Acest ID este situat chiar la începutul imaginii discului de joc și este încărcat la80000000 în memorie. Pentru o versiune obișnuită de vânzare cu amănuntul a jocului, ID-ul arată astfel:

47 41 46 45 30 31 00 00 GAFE01..

Patching-ul ultimului octet al ID-ului la 0x99 face ca la pornirea jocului să se întâmple următoarele:

Game version ID 0x99

Și în consola sistemului de operare, se tipărește următorul lucru:

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

Toate celelalte patch-uri pot fi eliminate, iar litera D apare din nou și în colțul din dreapta sus al ecranului, dar nu se activează niciunul dintre celelalte afișaje de depanare.

Bit 25

Bit 25 este utilizat împreună cu efectuarea verificării controlerului portului 2. Ce face ca acesta să fie activat?

Bit 25 și 28

Se pare că acesta are aceeași verificare folosită pentru bitul 28: versiunea trebuie să fie mai mare sau egală cu 0x90. Dacă bitul 26 a fost setat (ID-ul este 0x99), ambii biți vor fi, de asemenea, setați, iar modul zuru va fi activat oricum.

Dacă versiunea este între 0x90 și 0x98, totuși, modul zuru nu este activat imediat.Reamintind verificarea făcută în zurumode_callback, acesta va fi activat doar dacă bitul 25 este setatși padmgr_isConnectedController(1) returnează non-zero.Odată ce un controler este conectat la portul 2 (argumentul la isConnectedController este indexat cu zero),modul zuru este activat. Litera D și informațiile despre construcție sunt afișate pe ecranul de titlu, iar…apăsarea butoanelor de pe al doilea controler controlează afișajele de depanare!

Câteva butoane fac și alte lucruri în afară de schimbarea afișajului, cum ar fi creșterea vitezei jocului.

zerucheck_key_check

Ultimul mister este 0x4(zuruKeyCheck). Se pare că această valoare este actualizată de cătrefuncția complexă gigantică prezentată anterior:

zerumode_check_keycheck

Utilizând depanatorul Dolphin, am reușit să determin că valoarea verificată de această funcție este un setde biți care corespund apăsărilor de butoane de pe al doilea controler. Urma apăsării butoanelor este stocată într-o valoare pe 16 biți la 0x2(zuruKeyCheck). Când nu există nici un controler conectat, valoarea este 0x7638.

Cei 2 octeți care conțin stegulețele pentru apăsările de butoane ale controlerului 2 sunt încărcați și apoi actualizați aproape de începutul lui zerucheck_key_check. Noua valoare este transmisă curegistrul r4 de către padmgr_HandleRetraceMsg atunci când apelează funcția de rechemare.

cheie de verificare a tastelor sfârșit

Cu puțin mai jos, aproape de sfârșitul lui zerucheck_key_check, există de fapt un alt loc unde 0x4(zuruKeyCheck)este actualizat. Nu a apărut în lista de referințe încrucișate pentru că folosește r3 ca adresă de bază, iar noi ne putem da seama ce este r3 doar uitându-ne la ce este setată de fiecare dată când această funcție este pe cale să fie apelată.

La 8040ed88 valoarea lui r4 este scrisă în 0x4(zuruKeyCheck). Este încărcată din aceeași locație și apoi XORd cu 1 chiar înainte de aceasta. Ceea ce ar trebui să facă acest lucru este să comute valoarea octetului (de fapt doar ultimul bit) între 0 și 1. (Dacă este 0, rezultatulXOR-ului cu 1 va fi 1.) Dacă este 1, rezultatul va fi 0. Căutați tabelul de adevăr pentru XOR pentru a vedea acest lucru.)

key check end

Nu am observat acest comportament în timp ce urmăream valorile din memorie înainte, dar voi încerca să sparg această instrucțiune în debugger pentru a vedea ce se întâmplă. Valoarea originală este încărcată la 8040ed7c.

Fără să ating niciun buton de pe controllere, nu ating acest punct de întrerupere în timpul ecranului de titlu. Pentru a ajunge la acest bloc de cod, valoarea lui r5 trebuie să fie 0xb înainte de instrucțiunea de branșare care vine înaintea ei (8040ed74). Dintre numeroasele căi diferite care duc la acest bloc, există una care va stabili r5 la 0xb înainte de acesta, la 8040ed68.

Setting r5 to 0xb

Rețineți că pentru a ajunge la blocul care stabilește r5 la 0xB, r0 trebuie să fi fost egală cu 0x1000 chiar înainte. Urmând blocurile pe lanț până la începutul funcției, putem vedea constrângerile necesare pentru a ajunge la acest bloc:

  • 8040ed74: r5 trebuie să fie 0xB
  • 8040ed60: r0 trebuie să fie 0x1000
  • 8040ebe8: r5 trebuie să fie 0xA
  • 8040ebe4: r5 trebuie să fie mai mică decât 0x5B
  • 8040eba4: r5 trebuie să fie mai mare decât 0x7
  • 8040eb94: r6 trebuie să fie 1
  • 8040eb5c: r0 nu trebuie să fie 0
  • 8040eb74: Valorile butoanelor din portul 2 trebuie să se fi schimbat

Căutând traseul codului

Aici ajungem la punctul în care se încarcă vechile valori ale butoanelor și se stochează noile valori. După aceea, există câteva operații aplicate valorilor noi și vechi:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

Operația XOR va marca toți biții care s-au schimbat între cele două valori. Operația AND maschează apoi noua intrare pentru a dezactiva toți biții care nu sunt setați în prezent. Rezultatul din r0 este setul de biți noi (apăsări de butoane) în noua valoare. Dacă nu este gol, suntem pe drumul cel bun.

Pentru ca r0 să fie 0x1000, al 4-lea din cei 16 biți de urmărire a butoanelor trebuie să se fi schimbat.Prin setarea unui punct de întrerupere după operația XOR/AND îmi pot da seama ce apăsare de buton provoacă acest lucru: este butonul START.

Întrebarea următoare este cum să obținem ca r5 să înceapă ca 0xA. r5 și r6 sunt încărcate din0x0(zuruKeyCheck) la începutul funcției de verificare a tastelor și sunt actualizate aproape de sfârșit, atunci când nu se dă drumul la blocul de cod care comută 0x4(zuruKeyCheck).

Există câteva locuri chiar înainte de momentul în care r5 este setat la 0xA:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
    • 8040ed34: r0 trebuie să fie 0x4000 (butonul B a fost apăsat)
    • 8040ebe0: r5 trebuie să fie 0x5b
    • 8040eba4: r5 trebuie să fie mai mare decât 0x7
    • la fel ca înainte de aici încolo…

    r5 trebuie să înceapă la 0x5b

    8040ed00
    • 8040ecfc: r0 trebuie să fie 0xC000 (A și B apăsate)
    • 8040ebf8: r5 trebuie să fie >= 9
    • 8040ebf0: r5 trebuie să fie mai mică decât 10
    • 8040ebe4: r5 trebuie să fie mai mică decât 0x5b
    • 8040eba4: r5 trebuie să fie mai mare decât 0x7
    • la fel ca înainte de aici încolo…

    r5 trebuie să înceapă la 9

    8040ed50
      • 8040ed4c: r0 trebuie să fie 0x8000 (A a fost apăsat)
      • 8040ec04: r5 trebuie să fie mai mică decât 0x5d
      • 8040ebe4: r5 trebuie să fie mai mare decât 0x5b
      • 8040eba4: r5 trebuie să fie mai mare decât 0x7
      • la fel ca înainte de aici încolo…

      r5 trebuie să înceapă la 0x5c

      Se pare că există un fel de stare între apăsările de butoane, iar apoi trebuie introdusă o anumită secvență de combinații de butoane, care se termină cu START. Se pare că A și/sau B vin chiar înainte de START.

      Să urmărim calea codului care stabilește r5 la 9, apare un model: r5 este o valoare incrementală care poate fi fie crescută atunci când valoarea corectă de apăsare a butonului este găsită în r0,fie resetată la 0. Cazurile mai ciudate în care nu este o valoare între 0x0 și 0xB apar atunci când se gestionează pași cu mai multe butoane, cum ar fi apăsarea A și B în același timp. O persoană care încearcă să introducă această combinație, de obicei, nu va apăsa ambele butoane exact în același timp în care apare urma pad-ului, așa că trebuie să gestioneze apăsarea unuia dintre butoane înaintea celuilalt.

      Continuând cu diferitele trasee de cod:

      • r5 este setat la 9 atunci când este apăsat RIGHT la 8040ece8.
      • r5 este setat la 8 atunci când C-stick-ul drept este apăsat la 8040eccc.
      • r5 este setat la 7 atunci când C-stick-ul stâng este apăsat la 8040ecb0.
      • r5 este setat la 6 atunci când LEFT este apăsat la 8040ec98.
      • r5 este setat la 5 (și r6 la 1) atunci când DOWN este apăsat la 8040ec7c.
      • r5 este setat la 4 când C-stick-ul în sus este apăsat la 8040ec64.
      • r5 este setat la 3 când C-stick-ul în jos este apăsat la 8040ec48.
      • r5 este setat la 2 când UP este apăsat la 8040ec30.
      • r5 este setat la 1 (și r6 la 1) când Z este apăsat la 8040ec1c.

      Secvența curentă este:

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

      Încă o condiție este verificată înainte de verificarea lui Z: în timp ce butonul nou apăsat trebuie să fie Z, stegulețele curente trebuie să fie 0x2030: trebuie să fie apăsate și barele de protecție din stânga și din dreapta (acestea au valorile 0x10 și 0x20). De asemenea, butoanele UP/DOWN/LEFT/RIGHT sunt butoaneleD-pad, nu stick-ul analogic.

      Codul de trișare

      Combinația completă este:

  1. Apărați bumperele L+R și apăsați 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

Funcționează! Atașați un controler la al doilea port și introduceți codul, iar afișajele de depanare se vor afișa. După aceea, puteți începe să apăsați butoanele de pe al doilea (sau chiar al treilea) controlerpentru a începe să faceți lucruri.

Acest combo va funcționa fără a corecta numărul de versiune al jocului.Puteți folosi acest lucru chiar și pe o copie obișnuită de vânzare cu amănuntul a jocului fără instrumente de trișare sau moduri de consolă. Introducerea combinației a doua oară dezactivează din nou modul zuru.

Utilizarea codului pe un GameCube real

Mesajul „ZURU %d/%d” din zurumode_callback este folosit pentru a imprima starea acestei combinații dacă o introduceți când ID-ul discului este deja 0x99 (probabil pentru depanarea codului de înșelăciune în sine). Primul număreste poziția dvs. curentă în secvență, care se potrivește cu r5. Al doilea este setat la 1 în timp ce anumite butoanedin secvență sunt ținute apăsate, acestea ar putea corespunde atunci când r6 este setat la 1.

Majoritatea afișajelor nu explică ce sunt pe ecran, așa că, pentru a afla ce fac, trebuie să găsiți funcțiile care le gestionează. De exemplu, șirul lung de asteriscuri albastre și roșii care apar în partea de sus a ecranului sunt semne de poziție pentru afișarea stării diferitelor cereri. Atunci când o misiune este activă vor apărea acolo niște numere care indică starea misiunii.

Ecranul negru care apare atunci când apăsați Z esteo consolă pentru tipărirea mesajelor de depanare, dar în mod special pentru chestii de nivel scăzut, cum ar fi erori de alocare a memoriei și erori de heap sau alte excepții rele. Comportamentul lui fault_callback_scroll sugerează că poate fi pentrua afișa aceste erori înainte ca sistemul să fie repornit. Nu am declanșat niciuna dintre aceste erori, dar am reușit să o fac să tipărească câteva caractere de gunoi cu câteva NOP-uri. Cred că acest lucru ar fi foarte util pentru a imprima ulterior mesaje de depanare personalizate:

JUTConsole garbage characters

După ce am făcut toate acestea, am aflat că obținerea modului de depanare prin modificarea ID-ului versiunii la 0x99 este deja cunoscută: https://tcrf.net/Animal_Crossing#Debug_Mode. (Au, de asemenea, câteva note bune despre ce sunt diversele afișaje și mai multe lucruri pe care le poți face folosind un controler în portul 3.)Din câte îmi dau seama, combinația de trișare nu a fost publicată încă, totuși.

Acesta este tot pentru acest post. Mai sunt încă câteva caracteristici de dezvoltator pe care aș vrea să le explorez,cum ar fi ecranul de depanare a hărții și ecranul de selectare a emulatorului NES și cum să le activezi fără a folosi patch-uri.

Screen de selectare a hărții

Voi posta, de asemenea, scrieri despre inversarea sistemelor de dialoguri, evenimente și căutări în scopul de a face mods.

Update: Diapozitivele pentru discuția pe care am făcut-o pe această temă pot fi găsite aici.