jamchamb’s blog

Múlt nyáron elkezdtem visszafejteni az Animal Crossingot a GameCube-ra, hogy felfedezzem a modok készítésének lehetőségét a játékhoz. Azt is akartam dokumentálni a folyamatot, hogy útmutatókat készítsek a ROM hacking és a reverse engineering iránt érdeklődők számára. ebben a bejegyzésben a fejlesztői hibakeresési funkciókat vizsgálom, amelyek még mindig maradtak a játékban, és hogyan fedeztem fel egy cheat kombót, amellyel feloldhatóak.

new_Debug_mode

Amíg körülnéztem néhány megmaradt debug szimbólum között,észrevettem olyan függvényeket és változó neveket, amelyek a “debug” szót tartalmazzák, és úgy gondoltam, érdekes lenne megnézni, milyen debug funkciók maradtak a játékban. Ha lennének olyan hibakeresési vagy fejlesztői funkciók, amelyeket aktiválhatnék, az segíthetne a modok készítésének folyamatában is.

Az első függvény, amelyet megnéztem, a new_Debug_mode volt.Ezt a entry függvény hívja, amely közvetlenül a Nintendotrademark képernyő befejezése után fut le. Mindössze annyit tesz, hogy allokál egy 0x1C94 bájtos struktúrát és elmenti a mutatóját.

Miután a entry-ban meghívják, az allokált struktúrában az 0xD4 offsetben 0 érték kerül beállításra,közvetlenül a mainproc meghívása előtt.

A belépési függvény szétszerelése

Hogy lássam, mi történik, ha az érték nem nulla, a li r0, 0 utasítást a 80407C8C-nél li r0, 1-re foltoztam. A li r0, 0 utasítás nyers bájtjai 38 00 00 00,ahol a hozzárendelt érték az utasítás végén van, így ezt egyszerűen 38 00 00 01-re változtathatjuk, hogy li r0, 1-t kapjunk. Az utasítások megbízhatóbb összeállításához használhatsz valami olyasmit, mint kstool:

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

Ezt a javítást a Dolphin emulátorban a játék sproperties “Patches” fülére lépve és a következőképpen adhatod meg:

Debug performance meter

Az érték 1-re állítása egy érdekes kinézetű grafikon megjelenését eredményezte a képernyő alján:

Debug performance meter

Ez úgy nézett ki, mint egy teljesítménymérő, a képernyő alján lévő kis sávok növekedtek és zsugorodtak. (Később, amikor utánanéztem a grafikont rajzoló függvények nevének, rájöttem, hogy valóban megjelenítik a CPU- és memóriahasználat mérőszámait.) Ez szép volt, de nem volt különösebben hasznos. Az érték 1 fölé állítása valójában megállította a mytown betöltődését, így nem úgy tűnt, mintha sok más tennivaló lenne ezzel.

Zuru mód

Elkezdtem körülnézni a hibakereséssel kapcsolatos egyéb hivatkozások között, és néhányszor láttam valami “zuru mód”-nak nevezett dolgot felbukkanni. A hibakeresési funkcióval rendelkező kódblokkok elágazásai gyakran ellenőriztek egy zurumode_flag nevű változót.

game_move_first függvény

A fenti game_move_first függvényben a zzz_LotsOfDebug (egy általam kitalált név)csak akkor hívódik meg, ha a zurumode_flag nem nulla.

Az ehhez az értékhez kapcsolódó függvények keresése a következőket eredményezi:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

Előső pillantásra ezek mind kicsit homályosak, egy osAppNMIBuffer nevű változóban lévő különböző biteket csavargatnak offseten.Íme egy első pillantás arra, hogy mit csinálnak ezek a függvények:

zurumode_init

  • A zurumode_flag-t 0-ra állítja
  • A osAppNMIBuffer néhány bitjét ellenőrzi
  • Tárol egy mutatót a zurumode_callback függvényre a padmgr struktúrában
  • Hívja a zurumode_update -t.

zurumode_update

  • Pár bitet ellenőrizni a osAppNMIBuffer-ben
  • Feltételesen frissíteni a zurumode_flag-t ezen bitek alapján
  • Kiírni egy formázott stringet az OS konzolra.

Az ilyesmi általában hasznos a kód kontextusának megadására, de a karakterláncban egy csomó nem nyomtatható karakter volt. Az egyetlen felismerhető szöveg a “zurumode_flag” és a “%d” volt.

zuru mode format string

Azt feltételezve, hogy japán szöveg lehet, amely több bájtos karakterkódolást használ, lefuttattam a karakterláncot egy karakterkódolás-felismerő eszközön, és kiderült, hogy Shift-JIS kódolású. A lefordított karakterlánc csak annyit jelent, hogy “zurumode_flag hasbeen changed from %d to %d”. Ez nem sok új információval szolgál, de a Shift-JIS használatának ismerete igen, mivel a binárisokban és a karakterlánc-táblázatokban sokkal több olyan karakterlánc van, amely ezt a kódolást használja.

zurumode_callback

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

zerumode_check_keycheck didn’t show up before because of the differentspelling.. mi ez?

zerumode_check_keycheck

Egy hatalmas összetett függvény, amely még sok bitet csavargat a név nélküli értékeken.Ezen a ponton úgy döntöttem, hogy visszavonulok, és más debuggal kapcsolatos függvényeket és változókat keresek, mivel még abban sem voltam biztos, hogy mi a jelentősége a zuru mode-nak. Azt sem tudtam, hogy mit jelent itt a “key check”. Lehet, hogy ez egy kriptográfiai kulcs?

Vissza a hibakereséshez

Ez idő tájt vettem észre, hogy probléma van azzal, ahogyan a hibakeresési szimbólumokat betöltöttem az IDA-ba. A foresta.map fájl a játék lemezén egy csomó címet és nevet tartalmaz függvények és változók számára. Kezdetben nem vettem észre, hogy az egyes szakaszok címei nulláról indulnak, így csak egy egyszerű szkriptet állítottam be, hogy minden egyes sorhoz hozzáadjak egy névbejegyzést a fájlban.

Új IDA szkripteket állítottam be, hogy kijavítsam a szimbólumtérkép betöltését a program különböző szakaszaihoz:.text, .rodata, .data és .bss. A .text szakaszban van az összes függvény,így ezúttal úgy állítottam be a szkriptet,hogy a név beállításakor automatikusan felismerje az egyes címeken lévő függvényeket.

Az adatszekciókhoz úgy állítottam be,hogy minden bináris objektumhoz (például m_debug.o,ami a m_debug nevű valami lefordított kódja lenne) hozzon létre egy szegmenst,és állítson be helyet és nevet minden egyes adatdarabhoz.Ezzel sokkal több információt kaptam, mint korábban, bár most már kézzel kellett definiálnom az adattípust minden egyes adatdarabhoz, mivel minden egyes adatobjektumot egyszerű bájttömbnek állítottam be. (Utólag jobb lett volna legalább feltételezni, hogy minden olyan adat, amelynek mérete 4 bájt többszöröse, 32 bitesintegereket tartalmaz, mivel nagyon sok van belőlük, és sok olyan függvény és adat címét tartalmazza, amely fontos a kereszthivatkozások felépítéséhez.)

Az m_debug_mode.o új .bss szegmensének átnézése közben láttam néhány változót, mint quest_draw_status és event_status. Ezek azért érdekesek, mert szeretném elérni, hogy a hibakeresési módban hasznosabb dolgokat jelenítsen meg, mint a teljesítménygrafikon. Szerencsére ezekből az adatbejegyzésekből kereszthivatkozások voltak egy hatalmas kódrészletre, amely ellenőrzi a debug_print_flg-t.

A Dolphin debugger használatával megszakítási pontot állítottam a függvényen, ahol a debug_print_flg-t ellenőrzik (a 8039816C-nél), hogy lássam, hogyan működik az ellenőrzés. A megszakítási pont soha nem talál.

Nézzük meg, hogy miért: ezt a függvényt a game_debug_draw_last hívja meg. Találd ki, milyen értéket ellenőriznek a feltételes meghívás előtt? zurumode_flag. Mi a fene ez?

zurumode_flag check

Elállítottam egy töréspontot erre az ellenőrzésre (80404E18) és azonnal megtört. A zurumode_flag értéke nulla volt, tehát normális esetben kihagyná ennek a függvénynek a meghívását. NOPped ki az elágazási utasítást (helyettesítettem egy olyan utasítással, ami nem csinál semmit), hogy lássam, mi történik, ha a függvényt mégis meghívják.

A Dolphin debuggerben ezt úgy teheti meg, hogy megállítja a játékot, jobb gombbal kattint egy utasításra, majd a “Insert nop”-ra kattint:

Dolphin debugger NOPping

Nem történt semmi. Ezután megnéztem, hogy mi történik a függvényen belül, és találtam egy másik elágazási utasítást, amelyik 803981a8-nál rövidre zárja az összes érdekes dolog mellett. Ezt is NOPped-oltam, és a képernyő jobb felső sarkában megjelent a “D” betű.

Debug mode letter D

A függvényben volt még egy csomó érdekesnek tűnő kód a 8039816C-nél (zzz_DebugDrawPrint-nak hívtam),de egyiket sem hívták meg. Ha megnézzük ennek a függvénynek a grafikus nézetét, láthatjuk, hogy egy sor elágazási utasítás van, amelyek átugranak blokkokat az egész függvényben:

Branches in zzz_DebugDrawPrint

Mivel több ilyen elágazási utasítást NOP-oltam ki, elkezdtem látni, hogy különböző dolgok kerülnek ki a képernyőre:

More debug stuff getting printed

A következő kérdés az, hogyan lehet aktiválni ezeket a debug funkciókat a kód módosítása nélkül.Továbbá a zurumode_flag újra megjelenik néhány elágazási utasításnál, amit ebben a debug draw függvényben csináltam. hozzáadtam egy másik javítást, hogy a zurumode_flag mindig 2-re legyen állítva a zurumode_update-ben, mert gyaníthatóan kifejezetten 2-vel hasonlítják össze, amikor nem a 0-val hasonlítják össze.A játék újraindítása után ezt a “msg. no” üzenetet láttam megjelenítve a képernyő jobb felső sarkában.

üzenetszám kijelzés

A 687-es szám a legutóbb megjelenített üzenet belépési azonosítója. Én ezt egy korán elkészített simpletable viewerrel ellenőriztem, de ellenőrizheted egy teljes GUI-s string table editorral is, amit a ROM hackeléshez készítettem. Így néz ki az üzenet a szerkesztőben:

A 687-es üzenet a string table editorban

Ez a pont egyértelművé tette, hogy a zuru mód kitalálása már nem volt kikerülhető; ez közvetlenül a játék hibakeresési funkcióiba tapadt bele.

Zuru mód újragondolva

A zurumode_init-hoz visszatérve inicializál néhány dolgot:

  • 0xC(padmgr_class) a zurumode_callback
  • 0x10(padmgr_class) címére állítja magát a padmgr_class címet
  • 0x4(zuruKeyCheck) a 0x3C(osAppNMIBuffer)-ből betöltött szó utolsó bitjére.

Utánanéztem, hogy mit jelent a padmgr, és ez a “gamepad manager” rövidítése. Ez arra utal, hogy lehet egy speciális billentyű (gomb) kombináció, amit be kell írni a gamepadon a zuru mód aktiválásához, vagy esetleg valamilyen speciális hibakereső eszköz vagy fejlesztőkonzol funkció, amivel jelet lehet küldeni az aktiválásához.

zurumode_init csak a játék első betöltésekor fut le (a reset gomb megnyomása nem váltja ki).

8040efa4-nál, ahol 0x4(zuruKeyCheck) van beállítva, egy megszakítási pont beállításával láthatjuk, hogy a bootolás soránminden billentyű lenyomása nélkül 0-ra fog állni. Ha ezt 1-re cseréljük, érdekes dolog történik:

Title screen with zuru mode

A jobb felső sarokban ismét megjelenik a “D” betű (ezúttal sárga helyett zöld),és van némi build info is:

Egy javítás, hogy a 0x4(zuruKeyCheck) mindig 1 legyen az indításkor:

8040ef9c 38c00001

Úgy tűnik, hogy ez a helyes módja a zuru mód inicializálásának. Ezután,különböző lépéseket kell tennünk annak érdekében, hogy bizonyos hibakeresési információk megjelenjenek. A játék elindítása és a járkálás, valamint a falusiakkal való beszélgetés nem mutatta meg a korábban említett kijelzőket (a sarokban lévő “D” betűn kívül).

A valószínűsíthető gyanúsítottak a zurumode_update és a zurumode_callback.

zurumode_update

zurumode_update először a zurumode_init hívja meg, majd ismételten meghívja azurumode_callback.

Újra ellenőrzi a 0x3C(osAppNMIBuffer) utolsó bitjét, majd annak értéke alapján frissíti a zurumode_flagt.

Ha a bit nulla, akkor a jelzőt nullára állítja.

Ha nem, akkor a következő utasítás fut le úgy, hogy r5 a 0x3c(osAppNMIBuffer) teljes értéke:

extrwi r3, r5, 1, 28

Ez kiveszi a r5 28. bitjét, és elmenti a r3-be.Ezután az eredményhez hozzáadódik 1, így a végeredmény mindig 1 vagy 2.

zurumode_flag Ezután összehasonlítjuk az előző eredménnyel, attól függően, hogy a 0x3c(osAppNMIBuffer)-ben a 28. és az utolsó bitből hány van beállítva: 0, 1 vagy 2.

Ezt az értéket írjuk a zurumode_flag-be. Ha nem változott semmi, afunkció véget ér, és visszaadja a flag aktuális értékét. Ha megváltoztatta az értéket,egy sokkal bonyolultabb kódblokkok láncolata hajtódik végre.

Egy japán nyelvű üzenet kerül kiírásra: ez a korábban említett “zurumode_flag has been changed from %d to %d “üzenet.

Ezután egy sor függvényt hívunk meg különböző argumentumokkal attól függően, hogy a flag nullára változott-e vagy sem. Ennek a résznek az összeállítása fárasztó, ezért az álkódja így néz ki:

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

Megjegyezzük, hogy ha a flag nulla, akkor a JC_JUTDbPrint_setVisible függvény 0 argumentumot kap.Ha a flag nem nulla ÉS a 0x3C(osAppNMIBuffer)-ben a 25. vagy 31. bit be van állítva, akkor asetVisible függvénynek 1 argumentumot adunk.

Ez az első kulcs a zuru mód aktiválásához: a 0x3C(osAppNMIBuffer) utolsó bitjét 1-re kell állítani ahhoz, hogy a hibakijelzők láthatóvá váljanak, és a zurumode_flag nem nulla értéket kapjon.

zurumode_callback

zurumode_callback a 8040ee74 pontban található, és valószínűleg egy, a gamepaddal kapcsolatos függvény hívja meg. A Dolphin debuggerben megszakítási pontot állítva rajta, a callstack megmutatja, hogy valóban a padmgr_HandleRetraceMsg-ből hívják.

Az egyik első dolog, amit csinál, az a zerucheck_key_check futtatása. Ez összetett, de összességében úgy tűnik, hogy beolvassa, majd frissíti a zuruKeyCheck értékét. Úgy döntöttem, hogy megnézem, hogy ez az érték hogyan kerül felhasználásra a visszahívási függvény többi részében, mielőtt továbbmegyek a billentyűellenőrző függvénybe.

A következőben ismét ellenőriz néhány bitet a 0x3c(osAppNMIBuffer)-ben. Ha a 26-os bit be van állítva, vagy ha a 25-ös bit be van állítva és a padmgr_isConnectedController(1) nem nullát ad vissza, akkor a 0x3c(osAppNMIBuffer) utolsó bitjét 1-re állítja!

Ha egyik bit sem állítva, vagy ha a 25-ös bit legalább be van állítva, de a padmgr_isConnectedController(1) 0-t ad vissza, akkor ellenőrzi, hogy a 0x4(zuruKeyCheck) bájt 0-e. Ha igen,akkor nullázza az eredeti érték utolsó bitjét és visszaírja a 0x3c(osAppNMIBuffer)-be.Ha nem, akkor az utolsó bitet továbbra is 1-re állítja.

Az álkódban ez így néz ki:

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}

Ezután, ha a 26-os bit nem áll, akkor rövidít a zurumode_update hívására, majd befejezi.

Ha áll, akkor ha 0x4(zuruKeyCheck) nem nulla, akkor betölt egy formátumsztringet, ahol megjelenik, hogy ki fogja nyomtatni: “ZURU %d/%d”.

Recap

Íme, mi történik:

padmgr_HandleRetraceMsg hívja a zurumode_callback.Az én tippem az, hogy a “handle retrace message” azt jelenti, hogy éppen beolvasta a billentyűnyomásokata vezérlőn. Minden egyes alkalommal, amikor letapogatja, meghívhat egy sor különböző visszahívást.

Mikor a zurumode_callback fut, ellenőrzi az aktuális billentyű (gomb) lenyomásokat.Ez úgy tűnik, hogy egy adott gombot vagy gombkombinációt ellenőriz.

Az NMI-puffer utolsó bitje frissül az aktuális értékben lévő meghatározott bitek, valamint az egyik zuruKeyCheck bájt (0x4(zuruKeyCheck)) értéke alapján.

Ezután a zurumode_update fut és ellenőrzi ezt a bitet. Ha 0, akkor a zuru üzemmódjelzőt 0-ra állítja. Ha 1, akkor az üzemmódjelzőt 1-re vagy 2-re frissíti aszerint, hogy a 28-as bit be van-e állítva.

A zuru mód aktiválásának három módja a következő:

  1. A 26-os bit be van állítva a 0x3C(osAppNMIBuffer)-ben
  2. A 25-ös bit be van állítva a 0x3C(osAppNMIBuffer)-ben, és egy vezérlő van csatlakoztatva a 2. porthoz
  3. 0x4(zuruKeyCheck) nem nulla

osAppNMIBuffer

Kérdés, hogy mi volt a osAppNMIBuffer, Az “NMI” kereséssel kezdtem, és a Nintendo kontextusában találtam utalásokat a “non-maskable interrupt”-ra. Kiderült, hogy a teljes változónév megjelenik a Nintendo 64 fejlesztői dokumentációjában is:

osAppNMIBuffer egy 64 bájtos puffer, amely hideg alaphelyzetbe állításkor törlődik. Ha a rendszer egy NMI miatt újraindul, ez a puffer nem változik.

Gyakorlatilag ez egy kis darab memória, amely megmarad a soft újraindítások során. Egy játék arra használhatja ezt a puffert, hogy bármit tároljon, amit akar, amíg a konzol be van kapcsolva.Az eredeti Animal Crossing játék valójában Nintendo 64-re jelent meg, így érthető, hogy valami ilyesmi megjelenik a kódban.

Az boot.dol binárisra váltva (minden fentebbi a foresta.rel-ből származik),sok hivatkozás van a osAppNMIBuffer-re a main függvényben. Egy gyors pillantás megmutatja,hogy van egy sor olyan ellenőrzés, amely a 0x3c(osAppNMIBuffer) különböző bitjeinek OR műveletekkel történő beállítását eredményezheti.

Érdekes VAGY operandus értékek, amelyekre figyelni kell:

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

Ne feledjük, hogy a 25, 26 és 28 bitek különösen érdekesek: 25 és 26 határozza meg, hogy a zuru mód engedélyezve van-e, a 28-as bit pedig a flag szintjét (1 vagy 2).A 31-es bit szintén érdekes, de elsősorban úgy tűnik, hogy a többiek értékei alapján frissül.

26-os bit

Először is: 800062e0-nál ori r0, r0, 0x20 utasítás van a 0x3c-nél lévő pufferértékre. Ez a 26-os bitet állítaná be, ami mindig a zuru üzemmód engedélyezését eredményezi.

A 26-os bit beállítása

A bit beállításához a DVDGetCurrentDiskID-ből visszakapott 8. bájtnak 0x99-nek kell lennie.Ez az azonosító a játék lemezkép legelején található, és a80000000-nél töltődik be a memóriába. A játék normál kiskereskedelmi kiadásánál az ID így néz ki:

47 41 46 45 30 31 00 00 GAFE01..

Az ID utolsó bájtjának 0x99-be történő beillesztése a játék indításakor a következőket eredményezi:

Game version ID 0x99

Az operációs rendszer konzolján pedig a következő jelenik meg:

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

A többi patch eltávolítható, és a képernyő jobb felső sarkában újra megjelenik a D betű is, de a többi hibakijelzés egyike sem aktiválódik.

Bit 25

A 25. bit a 2. port vezérlő ellenőrzésének elvégzésével együtt használható. Mitől aktiválódik?

25. és 28. bit

Kiderül, hogy a 28. bitnél használt ellenőrzés ugyanaz: a verziónak nagyobbnak vagy egyenlőnek kell lennie 0x90-nél. Ha a 26-os bit be volt állítva (az ID 0x99), akkor mindkét bit szintén be lesz állítva, és a zuru mód mindenképpen engedélyezve lesz.

Ha azonban a verzió 0x90 és 0x98 között van, a zuru mód nem lesz azonnal engedélyezve.A zurumode_callback-ben végzett ellenőrzésre emlékeztetve, csak akkor lesz engedélyezve, ha a 25-ös bit be van állítva, és a padmgr_isConnectedController(1) nem nullát ad vissza.Amint egy vezérlő be van dugva a 2-es portba (a isConnectedController argumentuma nulla indexű),a zuru mód aktiválódik. A D betű és a build info megjelenik a címképernyőn, és…a második kontroller gombjainak megnyomása vezérli a debug kijelzőket!

Néhány gomb a kijelző megváltoztatásán kívül más dolgokat is csinál, például növeli a játék sebességét.

zerucheck_key_check

Az utolsó rejtély a 0x4(zuruKeyCheck). Kiderült, hogy ezt az értéket a korábban bemutatott óriási komplex függvény frissíti:

zerumode_check_keycheck

A Dolphin debuggerét használva meg tudtam állapítani, hogy a függvény által ellenőrzött érték a második kontroller gombnyomásoknak megfelelő bitek halmaza. A gombnyomások nyomvonala egy 16 bites értékben van tárolva a 0x2(zuruKeyCheck) címen. Amikor nincs vezérlő csatlakoztatva,az érték 0x7638.

A 2 bájt, amely a 2. vezérlő gombnyomások zászlóit tartalmazza, betöltődik, majd frissül a zerucheck_key_check eleje közelében. Az új értéket a padmgr_HandleRetraceMsg átadja a r4 regiszterrel együtt, amikor meghívja a callback függvényt.

key check end

A zerucheck_key_check vége közelében van egy másik hely, ahol 0x4(zuruKeyCheck)frissül. Ez nem jelent meg a kereszthivatkozások listájában, mert r3-et használja alapcímként, és csak úgy tudjuk kitalálni, hogy mi a r3, ha megnézzük, hogy mire van beállítva minden alkalommal, amikor ez a függvény meghívásra kerül.

A 8040ed88-nél a r4 értéke a 0x4(zuruKeyCheck)-be íródik. Ugyanarról a helyről töltődik be, majd közvetlenül előtte XORdolódik 1-gyel. Ennek azt kell tennie, hogy a bájt értékét (valójában csak az utolsó bitet) 0 és 1 között váltogatja. (Ha 0, akkor az 1-gyel való XOR-olás eredménye 1 lesz. Ha 1, az eredmény 0 lesz. Nézd meg az XOR igazságtáblázatát, hogy lásd ezt.)

key check end

Nem vettem észre ezt a viselkedést a memóriaértékek megfigyelése közben, de megpróbálom betörni ezt az utasítást a debuggerben, hogy lássam, mi történik. Az eredeti érték 8040ed7cnél töltődik be.

A vezérlők bármelyik gombjának megérintése nélkül nem érem el ezt a töréspontot a címképernyő alatt. Ahhoz, hogy elérjük ezt a kódblokkot, a r5 értékének 0xb-nak kell lennie az előtte lévő elágazó utasítás előtt (8040ed74). A sok különböző útvonal közül, ami ehhez a blokkhoz vezet, van egy, ami r5-t 0xb-ra állítja előtte, a 8040ed68-nél.

Setting r5 to 0xb

Megjegyezzük, hogy ahhoz, hogy elérjük a blokkot, ami r5-t 0xB-ra állítja, r0-nak 0x1000-nek kell lennie közvetlenül előtte. Ha követjük a blokkokat a lánc elején a függvény elejéig, láthatjuk a blokk eléréséhez szükséges kényszereket:

  • 8040ed74: r5 legyen 0xB
  • 8040ed60: r0 legyen 0x1000
  • 8040ebe8: r5 legyen 0xA
  • 8040ebe4: r5 kisebb kell legyen, mint 0x5B
  • 8040eba4: r5 nagyobb kell legyen, mint 0x7
  • 8040eb94: r6 legyen 1
  • 8040eb5c: r0 nem lehet 0
  • 8040eb74: Port 2 button values must have changed

Tracing the code path

Here we reach the point where the old button values are loaded and the new valuesare stored. Ezután néhány műveletet alkalmazunk az új és a régiértékekre:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

Az XOR művelet minden olyan bitet megjelöl, amely a kétérték között megváltozott. Az AND művelet ezután elrejti az új bemenetet, hogy visszaállítsa az aktuálisan nem beállított biteket. Az eredmény a r0-ban az új bitek (gombnyomások) halmaza az új értékben. Ha ez nem üres, akkor jó úton járunk.

Ahhoz, hogy r0 0x1000 legyen, a 16 gomb nyomkövetési bitje közül a 4-iknek most kellett megváltoznia.Az XOR/AND művelet utáni töréspont beállításával ki tudom deríteni, hogy melyik gombnyomás okozza ezt: ez a START gomb.

A következő kérdés az, hogyan lehet elérni, hogy r5 0xA-nek induljon. r5 és r6 a 0x0(zuruKeyCheck)-ből töltődik be a billentyűellenőrző funkció elején, és a vége felé frissül, amikor nem lépünk be a 0x4(zuruKeyCheck)-t kapcsoló kódblokkba.

Van néhány hely közvetlenül azelőtt, ahol a r5 0xA-be kerül:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
  • 8040ed34: r0 legyen 0x4000 (a B gombot megnyomták)
  • 8040ebe0: r5 legyen 0x5b
  • 8040eba4: r5 nagyobbnak kell lennie, mint 0x7
  • innentől kezdve ugyanaz, mint eddig…

r5 0x5b

8040ed00
  • 8040ecfc: r0 legyen 0xC000 (A és B megnyomva)
  • 8040ebf8: r5 legyen >= 9
  • 8040ebf0: r5 kisebbnek kell lennie, mint 10
  • 8040ebe4: r5 kisebbnek kell lennie, mint 0x5b
  • 8040eba4: r5 nagyobbnak kell lennie, mint 0x7
  • innentől kezdve ugyanaz, mint eddig…

r5 9-nél kell kezdődnie

8040ed50
  • 8040ed4c: r0 legyen 0x8000 (A-t nyomtunk)
  • 8040ec04: r5 kisebbnek kell lennie, mint 0x5d
  • 8040ebe4: r5 nagyobbnak kell lennie, mint 0x5b
  • 8040eba4: r5 nagyobbnak kell lennie, mint 0x7
  • innentől kezdve ugyanaz, mint eddig…

r5 0x5c

Úgy tűnik, van valamilyen állapot a gombnyomások között, majd egy bizonyos gombkombináció-sorozatot kell beírni, ami STARTtal végződik. Úgy tűnik, hogy A és/vagy B közvetlenül a START előtt jön.

A kód útvonalát követve, amely a r5-t 9-re állítja, egy minta rajzolódik ki: r5 egy inkrementáló érték, amely vagy növelhető, amikor a megfelelő gombnyomás értékét megtaláljuk r0-ban,vagy visszaállítható 0-ra. A furcsább esetek, amikor ez nem egy 0x0 és 0xB közötti érték, akkor fordulnak elő, amikor több gombos lépéseket kezelünk, például amikor egyszerre nyomjuk meg az A és B gombot. Egy személy, aki megpróbálja beírni ezt a kombinációt, általában nem fogja mindkét gombot pontosan ugyanabban az időben megnyomni, amikor a pad nyomvonala megjelenik, ezért kezelnie kell bármelyik gomb megnyomását a másik előtt.

Folytatva a különböző kódutakat:

  • r5 9-re van állítva, amikor a Jobb gombot 8040ece8-nél megnyomják.
  • r5 8-ra áll, amikor a C-billentyűt jobbra nyomjuk a 8040eccc-nél.
  • r5 7-re áll, amikor a C-billentyűt balra nyomjuk a 8040ecb0-nél.
  • r5 6-ra áll, amikor a balra nyomjuk a 8040ec98-nél.
  • r5 5-re áll (és r6 1-re), amikor a lefelé nyomjuk a 8040ec7c-nél.
  • r5 4-re áll, amikor a C-billentyűt felfelé nyomjuk meg a 8040ec64-nél.
  • r5 3-ra áll, amikor a C-billentyűt lefelé nyomjuk meg a 8040ec48-nél.
  • r5 2-re áll, amikor a UP-ot nyomjuk meg a 8040ec30-nél.
  • r5 1-re áll (és r6 1-re), amikor a Z-t nyomjuk meg a 8040ec1c-nél.

Az aktuális sorrend a következő:

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

A Z ellenőrzés előtt még egy feltételt ellenőriznek: míg az újonnan megnyomott gombnak Z-nek kell lennie, az aktuális zászlóknak 0x2030-nek kell lenniük: a bal és jobb ütközőt is meg kell nyomni (ezek értéke 0x10 és 0x20). Továbbá az UP/DOWN/LEFT/RIGHT aD-pad gombok, nem pedig az analóg botok.

A cheat kód

A teljes kombó a következő:

  1. Hold L+R bumpers and press 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

Ez működik! Csatlakoztasson egy vezérlőt a második porthoz, és adja be a kódot, és a hibakijelzők megjelennek. Ezután elkezdheted nyomkodni a gombokat a második (vagy akár a harmadik) kontrolleren,hogy elkezdj dolgokat csinálni.

Ez a kombó a játék verziószámának patchelése nélkül is működik.akár a játék hagyományos kiskereskedelmi példányán is használhatod, bármilyen cheat tool vagy konzolmod nélkül. A kombó másodszori beírása kikapcsolja a zuru módot.

A kód használata egy valódi GameCube-on

A zurumode_callback-ben lévő “ZURU %d/%d” üzenet arra szolgál, hogy kiírja ennek a kombinációnak az állapotát, ha beírja, amikor a lemez azonosítója már 0x99 (feltehetően magának a cheat kódnak a hibakeresésére). Az első számaz aktuális pozíciód a szekvenciában, amely megfelel a r5-nek. A második 1-re van állítva, amíg bizonyos gombokat a szekvenciában lenyomva tartasz, ezek megfelelhetnek annak, amikor r6 1-re van állítva.

A legtöbb kijelző nem magyarázza meg, hogy mit jelentenek a képernyőn, így ahhoz, hogy kitaláld, mit csinálnak, meg kell találnod az őket kezelő függvényeket. Például a képernyő tetején megjelenő hosszú sor kék és piros raszterjelzés a különböző kérések állapotának megjelenítésére szolgáló helytartók. Amikor egy küldetés aktív, néhány szám jelenik meg ott, jelezve a küldetés állapotát.

A Z megnyomásakor megjelenő fekete képernyő egy konzol a hibakeresési üzenetek kiírására, de kifejezetten az olyan alacsony szintű dolgokra, mint a memóriafoglalási és heap hibák vagy más rossz kivételek. A fault_callback_scroll viselkedése azt sugallja, hogy a rendszer újraindítása előtti hibák megjelenítésére szolgálhat. Egyik ilyen hibát sem váltottam ki,de néhány NOP-mal el tudtam érni, hogy kiírjon néhány szemét karaktert. Azt hiszem, ez nagyon hasznos lenne a későbbiekben egyéni hibakeresési üzenetek nyomtatásához:

JUTConsole garbage characters

Azt követően, hogy mindezt megtettem, rájöttem, hogy a hibakeresési mód elérése a verzióazonosító 0x99-re való foltozásával már ismert: https://tcrf.net/Animal_Crossing#Debug_Mode. (Van néhány jó jegyzetük arról is, hogy mik a különböző kijelzők, és még több dolog, amit a 3-as portban lévő vezérlővel lehet csinálni.) Amennyire én tudom, a csaló kombinációt azonban még nem tették közzé.

Ez a bejegyzés ennyi. Van még néhány fejlesztői funkció, amit szeretnék felfedezni,mint például a debug map screen és a NES emulátor select screen, és hogyan lehet őket aktiválni patchek használata nélkül.

Map select screen

A párbeszéd, event és quest rendszerek megfordításáról is fogok írni a modok készítése céljából.

Update: Az erről tartott előadásom diáit itt találod.