blog jamchamb

Zeszłego lata rozpocząłem inżynierię wsteczną Animal Crossing dla GameCube, aby zbadać możliwości tworzenia modów do gry. Chciałem również udokumentować ten proces, aby stworzyć tutoriale dla ludzi zainteresowanych hackowaniem ROM-u i inżynierią wsteczną. W tym poście badam funkcje debugowania deweloperskiego, które wciąż pozostały w grze, i jak odkryłem cheat combo, które może być użyte do ich odblokowania.

new_Debug_mode

Podczas rozglądania się za pozostałymi symbolami debugowania, zauważyłem funkcje i nazwy zmiennych, które zawierały słowo „debug” i pomyślałem, że byłoby interesujące zobaczyć jakie funkcje debugowania mogą pozostać w grze. Jeśli byłyby jakieś funkcje debugowania lub deweloperskie, które mógłbym aktywować, mogłoby to również pomóc w procesie tworzenia modów.

Pierwszą funkcją, której się przyjrzałem, była new_Debug_mode.Jest ona wywoływana przez funkcję entry, która uruchamia się zaraz po zakończeniu ekranu Nintendotrademark. Wszystko, co robi, to alokacja 0x1C94 bajtowej struktury i zapisanie jej wskaźnika.

Po jej wywołaniu w funkcji entry, wartość 0 jest ustawiana na offsecie 0xD4 w alokowanej strukturze, tuż przed wywołaniem funkcji mainproc.

Disassembly of the entry function

Aby zobaczyć, co się dzieje, gdy wartość jest niezerowa, załatałem instrukcję li r0, 0 na80407C8C do li r0, 1. Surowe bajty dla instrukcji li r0, 0 to 38 00 00 00, gdzie przypisana wartość znajduje się na końcu instrukcji, więc możesz po prostu zmienić to na 38 00 00 01, aby uzyskać li r0, 1. Aby uzyskać bardziej niezawodny sposób składania instrukcji, możesz użyć czegoś takiego jak kstool:

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

Możesz zastosować tę poprawkę w emulatorze Dolphin, przechodząc do zakładki „Patches” we właściwościach gry i wpisując ją w następujący sposób:

Debug performance meter

Ustawienie tej wartości na 1 spowodowało pojawienie się ciekawie wyglądającego wykresu na dole ekranu:

Debug performance meter

Wyglądało to jak miernik wydajności, z małymi słupkami na dole ekranu rosnącymi i malejącymi. (Później, gdy sprawdziłem nazwy funkcji, które rysują ten wykres, odkryłem, że w rzeczywistości wyświetlają one wskaźniki użycia procesora i pamięci). Ustawienie wartości powyżej 1 faktycznie powstrzymało mytown przed załadowaniem się, więc nie wyglądało na to, że można z tym zrobić coś więcej.

Tryb zuru

Zacząłem rozglądać się za innymi odniesieniami do rzeczy związanych z debugowaniem i zobaczyłem, że coś nazwanego „trybem zuru” pojawiło się kilka razy. Rozgałęzienia do bloków kodu, które miały funkcjonalność debugowania, często sprawdzały zmienną o nazwie zurumode_flag.

funkcja game_move_first

W funkcji game_move_first przedstawionej powyżej, zzz_LotsOfDebug (nazwa, którą wymyśliłem) jest wywoływana tylko wtedy, gdy zurumode_flag jest niezerowa.

Szukanie funkcji związanych z tą wartością daje następujące wyniki:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

Na pierwszy rzut oka wszystkie te funkcje są trochę niejasne, kręcą się wokół różnych bitów na offsetach w zmiennej o nazwie osAppNMIBuffer.Oto pierwsze spojrzenie na to, co robią te funkcje:

zurumode_init

  • Set zurumode_flag to 0
  • Check some bits in osAppNMIBuffer
  • Store a pointer to the zurumode_callback function in the padmgr structure
  • Call zurumode_update.

zurumode_update

  • Sprawdź niektóre bity w osAppNMIBuffer
  • Zaktualizuj bezwarunkowo zurumode_flag na podstawie tych bitów
  • Wypisuj ciąg formatu do konsoli systemu operacyjnego.

Tego rodzaju rzeczy są zwykle przydatne do nadania kontekstu kodowi, ale w łańcuchu znajdowała się masa niedrukowalnych znaków. Jedynym rozpoznawalnym tekstem było „zurumode_flag” i „%d”.

łańcuch formatu trybu zuru

Podejrzewając, że może to być tekst japoński używający wielobajtowego kodowania znaków, przepuściłem łańcuch przez narzędzie do wykrywania kodowania znaków i okazało się, że jest zakodowany Shift-JIS. Przetłumaczony ciąg oznacza po prostu „zurumode_flag hasbeen changed from %d to %d”. To nie dostarcza wiele nowych informacji, ale wiedza o użyciu Shift-JIS już tak, ponieważ jest wiele więcej łańcuchów w binariach i tablicach łańcuchów, które używają tego kodowania.

zurumode_callback

  • Wywołania zerumode_check_keycheck
  • Sprawdzenie niektórych bitów w osAppNMIBuffer
  • Wydruk wartości zurumode_flag gdzieś
  • Wywołania zurumode_update

zerumode_check_keycheck nie pojawiały się wcześniej z powodu innej pisowni.. co to jest?

zerumode_check_keycheck

Ogromna złożona funkcja, która robi dużo więcej bitów na wartościach bez nazw.W tym momencie zdecydowałem się wycofać i poszukać innych funkcji i zmiennych związanych z debugowaniem, ponieważ nie byłem nawet pewien jakie znaczenie ma tryb zuru. Nie byłem też pewien co oznacza tutaj „key check”. Czy może to być klucz kryptograficzny?

Powrót do debugowania

Mniej więcej w tym czasie zauważyłem, że istnieje problem ze sposobem, w jaki załadowałem symbole debugowania do IDA. Plik foresta.map na płycie z grą zawiera mnóstwo adresów i nazw funkcji i zmiennych. Początkowo nie zauważyłem, że adresy dla każdej sekcji zaczynały się od zera, więc skonfigurowałem prosty skrypt, aby dodać wpis nazwy dla każdej linii w pliku.

Skonfigurowałem nowe skrypty IDA, aby poprawić ładowanie mapy symboli dla różnych sekcji programu: .text, .rodata, .data i .bss. W sekcji .text znajdują się wszystkie funkcje, więc ustawiłem skrypt tak, aby automatycznie wykrywał funkcje pod każdym adresem podczas ustawiania nazwy tym razem.

Dla sekcji danych, ustawiłem go tak, aby tworzył segment dla każdego obiektu binarnego (takiego jak m_debug.o, który byłby skompilowanym kodem dla czegoś o nazwie m_debug), i ustawiał przestrzeń i nazwy dla każdego kawałka danych.Daje mi to znacznie więcej informacji niż miałem wcześniej, chociaż teraz musiałem ręcznie zdefiniować typ danych dla każdego kawałka danych, ponieważ ustawiłem każdy obiekt danych jako prostą tablicę bajtów. (Z perspektywy czasu lepiej byłoby przynajmniej założyć, że wszelkie dane o rozmiarze będącym wielokrotnością 4 bajtów zawierają 32-bitowe liczby całkowite, ponieważ jest ich tak wiele, a wiele z nich zawiera adresy funkcji i danych, które są ważne przy tworzeniu odsyłaczy.)

Przeglądając nowy segment .bss dla m_debug_mode.o, zobaczyłem kilka zmiennych takich jak quest_draw_status ievent_status. Są one interesujące, ponieważ chcę, aby tryb debugowania wyświetlał bardziej przydatne rzeczy niż wykres wydajności. Na szczęście istniały odsyłacze z tych danych do dużego fragmentu kodu, który sprawdza debug_print_flg.

Używając debuggera Dolphin, ustawiłem punkt przerwania na funkcji, gdzie debug_print_flg jest sprawdzane (na 8039816C), aby zobaczyć jak działa sprawdzanie. Punkt przerwania nigdy nie został trafiony.

Sprawdźmy dlaczego: ta funkcja jest wywoływana przez game_debug_draw_last. Zgadnij, jaka wartość jest sprawdzana przed jej warunkowym wywołaniem? zurumode_flag. Co to jest do cholery?

zurumode_flag check

Ustawiłem breakpoint na tym sprawdzeniu (80404E18) i od razu się zepsuło. Wartość zurumode_flag wynosiła zero, więc normalnie pominęłoby to wywołanie tej funkcji. NOPped out the branch instruction (zastąpiłem go instrukcją, która nic nie robi), aby zobaczyć, co się stanie, gdy funkcja zostanie wywołana.

W debugerze Dolphin możesz to zrobić przez wstrzymanie gry, kliknięcie prawym przyciskiem myszy na instrukcję, a następnie kliknięcie „Insert nop”:

Dolphin debugger NOPping

Nic się nie stało. Następnie sprawdziłem, co dzieje się wewnątrz funkcji i znalazłem inną instrukcję rozgałęzienia, która mogła skrócić obwód obok wszystkich interesujących rzeczy na 803981a8. NOPped to również, i litera „D” pojawiła się w prawym górnym rogu ekranu.

Tryb debugowania litera D

W tej funkcji było więcej interesująco wyglądającego kodu w 8039816C (nazwałem go zzz_DebugDrawPrint), ale żaden z nich nie został wywołany. Jeśli spojrzysz na widok wykresu tej funkcji, możesz zobaczyć, że jest tam szereg instrukcji rozgałęzień, które pomijają bloki w całej funkcji:

Branches in zzz_DebugDrawPrint

By NOPping out more of these branch statements, I started to see different things get printed to the screen:

More debug stuff getting printed

Następnym pytaniem jest, jak aktywować te funkcje debugowania bez modyfikowania kodu.Ponadto, zurumode_flag pojawia się ponownie dla niektórych instrukcji rozgałęzień wykonanych w tej funkcji debug draw.Dodałem kolejną poprawkę, aby zurumode_flag był zawsze ustawiony na 2 w zurumode_update, ponieważ jest on zwykle porównywany specjalnie z 2, gdy nie jest porównywany z 0.Po ponownym uruchomieniu gry zobaczyłem taki komunikat „msg. no” wyświetlany w prawym górnym rogu ekranu.

wyświetlanie numeru wiadomości

Liczba 687 to ID wpisu ostatnio wyświetlanej wiadomości. Sprawdziłem to używając prostej przeglądarki tabel, którą zrobiłem na początku, ale możesz również sprawdzić to za pomocą pełnego edytora GUI tabel łańcuchowych, który zrobiłem dla ROM hacking. Oto jak wygląda wiadomość w edytorze:

Wiadomość 687 w edytorze tabel łańcuchowych

W tym momencie stało się jasne, że rozgryzienie trybu zuru nie było już możliwe do uniknięcia; wplątało się bezpośrednio w funkcje debugowania gry.

Zuru mode revisited

Powracając do zurumode_init, inicjalizuje on kilka rzeczy:

  • 0xC(padmgr_class) jest ustawiony na adres zurumode_callback
  • 0x10(padmgr_class) jest ustawiony na adres samego padmgr_class
  • 0x4(zuruKeyCheck) jest ustawiony na ostatni bit słowa załadowanego z 0x3C(osAppNMIBuffer).

Sprawdziłem co oznacza padmgr, i jest to skrót od „gamepad manager”. To sugeruje, że może istnieć specjalna kombinacja klawiszy (przycisków) do wprowadzenia na gamepadzie, aby aktywować tryb zuru, lub być może jakieś specjalne urządzenie debugujące lub funkcja konsoli deweloperskiej, która może być użyta do wysłania sygnału, aby go aktywować.

zurumode_initdziała tylko przy pierwszym załadowaniu gry (naciśnięcie przycisku reset nie powoduje jej uruchomienia).

Ustawiając punkt przerwania na 8040efa4, gdzie 0x4(zuruKeyCheck) jest ustawione, widzimy, że podczas bootowania bez przytrzymania jakichkolwiek klawiszy, będzie ustawione na 0. Zamiana tego na 1 powoduje, że dzieje się ciekawa rzecz:

Ekran tytułowy z trybem zuru

W prawym górnym rogu znów pojawia się litera „D” (tym razem zielona zamiast żółtej), jest też trochę informacji o buildzie:

Poprawka, aby 0x4(zuruKeyCheck) było zawsze ustawione na 1 przy starcie:

8040ef9c 38c00001

Wygląda na to, że jest to poprawny sposób na zainicjowanie trybu zuru. Po tym, mogą być różne działania, które musimy podjąć w celu uzyskania pewnych informacji debugowania do wyświetlenia. Uruchomienie gry, chodzenie i rozmowa z mieszkańcem wioski nie pokazało żadnego z wcześniej wymienionych wyświetlaczy (oprócz litery „D” w rogu).

Prawdopodobnie podejrzanymi są zurumode_update i zurumode_callback.

zurumode_update

zurumode_update jest najpierw wywoływany przez zurumode_init, a następnie wielokrotnie przez zurumode_callback.

Sprawdza ponownie ostatni bit 0x3C(osAppNMIBuffer), a następnie aktualizuje zurumode_flagna podstawie jego wartości.

Jeśli bit wynosi zero, flaga jest ustawiana na zero.

Jeśli nie, wykonywana jest następująca instrukcja, w której r5 jest pełną wartością 0x3c(osAppNMIBuffer):

extrwi r3, r5, 1, 28

Wyodrębnia ona 28 bit z r5 i zapisuje go do r3.Następnie do wyniku dodawana jest 1, więc końcowy wynik to zawsze 1 lub 2.

zurumode_flagPotem jest porównywany z poprzednim wynikiem, w zależności od tego, ile 28. i ostatnich bitów jest ustawionych w 0x3c(osAppNMIBuffer): 0, 1 lub 2.

Ta wartość jest zapisywana do zurumode_flag. Jeśli nic to nie zmieniło, funkcja kończy działanie i zwraca aktualną wartość flagi. Jeśli jednak zmieniła wartość, wykonywany jest znacznie bardziej złożony łańcuch bloków kodu.

Wypisywany jest komunikat w języku japońskim: jest to wspomniany wcześniej komunikat „zurumode_flag has been changed from %d to %d”

Następnie wywoływana jest seria funkcji z różnymi argumentami w zależności od tego, czy flaga została zmieniona na zero czy nie. Montaż tej części jest żmudny, więc pseudokod wygląda tak:

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

Zauważ, że jeśli flaga jest zerowa, to funkcja JC_JUTDbPrint_setVisible otrzymuje argument 0. Jeśli flaga nie jest zerowa i bit 25 lub bit 31 są ustawione w 0x3C(osAppNMIBuffer), to funkcjasetVisible otrzymuje argument 1.

To jest pierwszy klucz do aktywacji trybu zuru: ostatni bit 0x3C(osAppNMIBuffer)musi być ustawiony na 1, aby wyświetlacze debugowania były widoczne i ustawić zurumode_flagna niezerową wartość.

zurumode_callback

zurumode_callback znajduje się w 8040ee74 i jest prawdopodobnie wywoływany przez funkcję związaną z gamepadem. Ustawiając na niej punkt przerwania w debugerze Dolphina, callstackshows pokazuje, że rzeczywiście jest wywoływana z padmgr_HandleRetraceMsg.

Jedną z pierwszych rzeczy, które robi jest uruchomienie zerucheck_key_check. Jest to skomplikowane, ale ogólnie wydaje się, że odczytuje, a następnie aktualizuje wartość zuruKeyCheck. Postanowiłem zobaczyć, jak ta wartość jest używana w reszcie funkcji wywołania zwrotnego, zanim przejdę dalej do funkcji sprawdzania klucza.

Następnie ponownie sprawdza niektóre bity w 0x3c(osAppNMIBuffer). Jeśli bit 26 jest ustawiony, lub jeśli bit 25 jest ustawiony i padmgr_isConnectedController(1) zwraca wartość niezerową, to ostatni bit w 0x3c(osAppNMIBuffer) jest ustawiany na 1!

Jeśli żaden z tych bitów nie jest ustawiony, lub jeśli bit 25 jest przynajmniej ustawiony, ale padmgr_isConnectedController(1) zwraca wartość 0, to sprawdza, czy bajt w 0x4(zuruKeyCheck) ma wartość 0. Jeśli tak, to wyzeruje ostatni bit w oryginalnej wartości i zapisze go z powrotem do 0x3c(osAppNMIBuffer).Jeśli nie, to nadal ustawia ostatni bit na 1.

W pseudokodzie wygląda to tak:

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}

Po tym, jeśli bit 26 nie jest ustawiony, to skraca się do wywołania zurumode_update, a następnie kończy.

Jeśli jest ustawiony, to jeśli 0x4(zuruKeyCheck) nie jest zerem, to ładuje ciąg formatu, w którym wygląda na to, że wydrukuje: „ZURU %d/%d”.

Recap

Oto co się dzieje:

padmgr_HandleRetraceMsgwywołuje zurumode_callback.Moje przypuszczenie jest takie, że „handle retrace message” oznacza, że właśnie przeskanował key presseson kontrolera. Za każdym razem, gdy skanuje, może wywołać serię różnych wywołań zwrotnych.

Gdy zurumode_callback działa, sprawdza bieżące naciśnięcia klawiszy (przycisków).To wydaje się sprawdzać dla konkretnego przycisku lub kombinacji przycisków.

Ostatni bit w buforze NMI jest aktualizowany w oparciu o określone bity w jego bieżącej wartości, a także wartość jednego z zuruKeyCheck bajtów (0x4(zuruKeyCheck)).

Kiedy zurumode_update uruchamia się i sprawdza ten bit. Jeśli wynosi on 0, flaga trybu zuru jest ustawiana na 0. Jeśli wynosi 1, flaga trybu jest aktualizowana na 1 lub 2 w zależności od tego, czy bit 28 jest ustawiony.

Trzy sposoby aktywacji trybu zuru to:

  1. Bit 26 jest ustawiony w 0x3C(osAppNMIBuffer)
  2. Bit 25 jest ustawiony w 0x3C(osAppNMIBuffer), a kontroler jest podłączony do portu 2
  3. 0x4(zuruKeyCheck) nie jest zerem

osAppNMIBuffer

Zastanawiałem się, czym jest osAppNMIBuffer, Zacząłem od szukania „NMI”, i znalazłem odniesienia do „non-maskable interrupt” w kontekście Nintendo. Okazuje się, że cała ta zmienna nazwa pojawia się również w dokumentacji deweloperskiej dla Nintendo 64:

osAppNMIBuffer to 64-bajtowy bufor, który jest czyszczony podczas zimnego resetu. Jeśli system uruchomi się ponownie z powodu NMI, ten bufor pozostanie niezmieniony.

Podsumowując, jest to mały kawałek pamięci, który utrzymuje się podczas miękkich restartów. Gra może używać tego bufora do przechowywania czegokolwiek chce, dopóki konsola jest włączona.Oryginalna gra Animal Crossing została wydana na Nintendo 64, więc to ma sens, że coś takiego może pojawić się w kodzie.

Przechodząc do binarnego boot.dol (wszystko powyżej jest z foresta.rel), jest wiele odniesień do osAppNMIBuffer w funkcji main. Szybkie spojrzenie pokazuje, że istnieje seria kontroli, które mogą spowodować, że różne bity 0x3c(osAppNMIBuffer) zostaną ustawione za pomocą operacji OR.

Interesujące wartości operandów OR, na które należy zwrócić uwagę, to:

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

Pamiętaj, że bity 25, 26 i 28 są szczególnie interesujące: 25 i 26 określają, czy tryb zuru jest włączony, a bit 28 określa poziom flagi (1 lub 2).Bit 31 jest również interesujący, ale głównie wydaje się być aktualizowany na podstawie wartości pozostałych.

Bit 26

Po pierwsze: w 800062e0 znajduje się instrukcja ori r0, r0, 0x20 na wartości bufora w 0x3c. To ustawiłoby bit 26, co zawsze powoduje włączenie trybu zuru.

Ustawianie bitu 26

Aby bit został ustawiony, 8. bajt zwrócony z DVDGetCurrentDiskID musi być 0x99.Ten identyfikator znajduje się na samym początku obrazu płyty z grą i jest ładowany do pamięci pod adresem80000000. Dla zwykłego wydania detalicznego gry, ID wygląda następująco:

47 41 46 45 30 31 00 00 GAFE01..

Dopasowanie ostatniego bajtu ID do 0x99 powoduje, że podczas uruchamiania gry dzieje się co następuje:

Identyfikator wersji gry 0x99

A w konsoli systemu operacyjnego wypisywana jest następująca informacja:

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

Wszystkie inne patche mogą zostać usunięte, a litera D pojawia się ponownie w prawym górnym rogu ekranu, ale żaden z pozostałych ekranów debugowania nie jest aktywny.

Bit 25

Bit 25 jest używany w połączeniu z wykonywaniem kontroli kontrolera portu 2. Co powoduje jego włączenie?

Bit 25 i 28

Okazuje się, że jest to ta sama kontrola, co w przypadku bitu 28: wersja musi być większa lub równa 0x90. Jeśli bit 26 był ustawiony (ID jest 0x99), oba te bity również będą ustawione, i tryb zuru zostanie włączony tak czy owak.

Jeśli jednak wersja jest pomiędzy 0x90 a 0x98, tryb zuru nie jest natychmiast włączany.Przypominając sprawdzenie dokonane w zurumode_callback, zostanie on włączony tylko jeśli bit 25 jest ustawiony i padmgr_isConnectedController(1) zwraca wartość niezerową.Gdy tylko kontroler zostanie podłączony do portu 2 (argument do isConnectedController jest indeksowany zerowo), tryb zuru zostanie włączony. Litera D i informacja o budowie wyświetlane są na ekranie tytułowym, a… wciskanie przycisków na drugim kontrolerze steruje wyświetlaczami debug!

Niektóre przyciski robią też rzeczy poza zmianą wyświetlania, takie jak zwiększanie prędkości gry.

zerucheck_key_check

Ostatnią zagadką jest 0x4(zuruKeyCheck). Okazuje się, że wartość ta jest aktualizowana przez gigantycznie złożoną funkcję pokazaną wcześniej:

zerumode_check_keycheck

Korzystając z debuggera Dolphina, udało mi się ustalić, że wartością sprawdzaną przez tę funkcję jest zestaw bitów odpowiadających naciśnięciom przycisków na drugim kontrolerze. Ślad po naciśnięciu przycisku jest przechowywany w 16-bitowej wartości w 0x2(zuruKeyCheck). Gdy nie ma podłączonego kontrolera, wartość ta wynosi 0x7638.

Dwa bajty zawierające flagi naciśnięć przycisków kontrolera 2 są ładowane, a następnie aktualizowane w pobliżu początku zerucheck_key_check. Nowa wartość jest przekazywana wraz z rejestrem r4 przez padmgr_HandleRetraceMsg, gdy wywołuje funkcję callback.

key check end

Na końcu zerucheck_key_check jest jeszcze jedno miejsce, w którym 0x4(zuruKeyCheck) jest aktualizowany. Nie pojawiło się ono na liście odsyłaczy, ponieważ używa adresu r3 jako adresu bazowego, a my możemy się dowiedzieć, czym jest r3 tylko patrząc na to, na co jest ustawiany za każdym razem, gdy ta funkcja ma zostać wywołana.

W 8040ed88 wartość r4 jest zapisywana do 0x4(zuruKeyCheck). Jest ona ładowana z tego samego miejsca, a następnie XORd z 1 tuż przed tym. To, co powinno zrobić, to przełączenie wartości bajtu (tak naprawdę tylko ostatniego bitu) pomiędzy 0 i 1. (Jeśli jest to 0, wynikiem XORowania z 1 będzie 1. Jeśli jest to 1, wynikiem będzie 0. Zobacz tabelę prawdy dla XOR, aby to zobaczyć.)

key check end

Nie zauważyłem tego zachowania podczas oglądania wartości pamięci wcześniej, ale spróbuję przełamać tę instrukcję w debugerze, aby zobaczyć, co się dzieje. Oryginalna wartość jest ładowana pod adresem 8040ed7c.

Bez dotykania jakichkolwiek przycisków na kontrolerach, nie uderzam w ten punkt przerwania podczas ekranu tytułowego. Aby dotrzeć do tego bloku kodu, wartość r5 musi być 0xb przed instrukcją rozgałęziającą, która się przed nią znajduje (8040ed74). Spośród wielu różnych ścieżek prowadzących do tego bloku jest jedna, która ustawi r5 na 0xb przed nim, na 8040ed68.

Setting r5 to 0xb

Zauważ, że aby dotrzeć do bloku, który ustawia r5 na 0xB, r0 musi być równe 0x1000 tuż przed nim. Podążając po blokach w górę łańcucha do początku funkcji, możemy zobaczyć ograniczenia niezbędne do osiągnięcia tego bloku:

  • 8040ed74: r5 musi być 0xB
  • 8040ed60: r0 musi być 0x1000
  • 8040ebe8: r5 musi być 0xA
  • 8040ebe4: r5 musi być mniejsze niż 0x5B
  • 8040eba4: r5 musi być większe niż 0x7
  • 8040eb94: r6 musi być 1
  • 8040eb5c: r0 nie może być 0
  • 8040eb74: Port 2 button values must have changed

Tracing the code path

Tutaj dochodzimy do punktu, w którym stare wartości przycisków są wczytywane, a nowe zapisywane. Następnie wykonujemy kilka operacji na nowych i starych wartościach:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

Operacja XOR zaznacza wszystkie bity, które zmieniły się pomiędzy dwoma wartościami. Następnie operacja AND zamaskuje nowe wejście, aby wyłączyć wszystkie bity, które nie są aktualnie ustawione. Wynik w r0 jest zbiorem nowych bitów (naciśnięć przycisków) w nowej wartości. Jeśli nie jest pusty, to jesteśmy na dobrej drodze.

Aby r0 był 0x1000, czwarty z 16 bitów śledzenia przycisków musiał się właśnie zmienić.Ustawiając punkt przerwania po operacji XOR/AND, mogę dowiedzieć się, które naciśnięcie przycisku to powoduje: jest to przycisk START.

Następne pytanie brzmi, jak sprawić, by r5 zaczął działać jako 0xA. r5 i r6 są ładowane z0x0(zuruKeyCheck) na początku funkcji sprawdzania klawiszy, a aktualizowane pod koniec, gdy nie wyrzucamy bloku kodu przełączającego 0x4(zuruKeyCheck).

Jest kilka miejsc tuż przed tym, gdzie r5 zostaje ustawiony na 0xA:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
  • 8040ed34: r0 musi być 0x4000 (przycisk B został wciśnięty)
  • 8040ebe0: r5 musi być 0x5b
  • 8040eba4: r5 musi być większe niż 0x7
  • tak samo jak poprzednio od tego miejsca…

r5 musi zaczynać się od 0x5b

8040ed00
  • 8040ecfc: r0 musi być 0xC000 (A i B wciśnięte)
  • 8040ebf8: r5 musi być >= 9
  • 8040ebf0: r5 musi być mniejszy niż 10
  • 8040ebe4: r5 musi być mniejszy niż 0x5b
  • 8040eba4: r5 musi być większe niż 0x7
  • tak samo jak poprzednio stąd…

r5 musi zaczynać się od 9

8040ed50
  • 8040ed4c: r0 musi być 0x8000 (A zostało wciśnięte)
  • 8040ec04: r5 musi być mniejsze niż 0x5d
  • 8040ebe4: r5 musi być większe niż 0x5b
  • 8040eba4: r5 must be greater than 0x7
  • same jak poprzednio od tego momentu…

r5 must start at 0x5c

Wygląda na to, że jest jakiś stan pomiędzy naciśnięciami przycisków, a następnie trzeba wprowadzić pewną sekwencję kombinacji przycisków, kończącą się STARTEM. It seems like A and/or B comejust before START.

Podążając za ścieżką kodu, która ustawia r5 na 9, wyłania się wzór: r5 jest wartością rosnącą, którą można albo zwiększyć, gdy w r0 zostanie znaleziona właściwa wartość naciśnięcia przycisku, albo zresetować do 0. Dziwniejsze przypadki, gdy nie jest to wartość z przedziału 0x0 i 0xB, występują przy obsłudze kroków z wieloma przyciskami, takich jak naciśnięcie A i B w tym samym czasie. Osoba próbująca wprowadzić tę kombinację zazwyczaj nie zamierza nacisnąć obu przycisków dokładnie w tym samym czasie, gdy pojawia się ślad padu, więc musi obsłużyć jeden z przycisków wciśnięty przed drugim.

Kontynuując z różnymi ścieżkami kodu:

  • r5 jest ustawione na 9, gdy PRAWO jest wciśnięte w 8040ece8.
  • r5 jest ustawiony na 8 gdy C-stick prawy jest wciśnięty na 8040eccc.
  • r5 jest ustawiony na 7 gdy C-stick lewy jest wciśnięty na 8040ecb0.
  • r5 jest ustawiony na 6 gdy LEWY jest wciśnięty na 8040ec98.
  • r5 jest ustawiony na 5 (i r6 na 1) gdy DÓŁ jest wciśnięty na 8040ec7c.
  • r5 jest ustawiony na 4, gdy C-stick up jest naciśnięty na 8040ec64.
  • r5 jest ustawiony na 3, gdy C-stick down jest naciśnięty na 8040ec48.
  • r5 jest ustawiony na 2, gdy UP jest naciśnięty na 8040ec30.
  • r5 jest ustawiony na 1 (i r6 na 1), gdy Z jest naciśnięty na 8040ec1c.

Aktualna sekwencja to:

Z, GÓRA, C-DÓŁ, C-GÓRA, DÓŁ, LEWO, C-LEWO, C-PRAWO, PRAWO, A+B, START

Jeszcze jeden warunek jest sprawdzany przed sprawdzeniem Z: podczas gdy nowo wciśniętym przyciskiem musi być Z, aktualnymi flagami muszą być 0x2030: lewy i prawy zderzak muszą być również wciśnięte (mają wartości 0x10 i 0x20). Ponadto, UP/DOWN/LEFT/RIGHT to przyciski D-pada, a nie analogowego drążka.

Kod cheatu

Pełne combo to:

  1. Przytrzymaj zderzaki L+R i naciśnij Z
  2. D-UP
  3. C-DOWN
  4. C-UP
  5. D-DOWN
  6. D-.LEWO
  7. C-LEWO
  8. C-PRAWO
  9. D-PRAWO
  10. A+B
  11. START

To działa! Podłącz kontroler do drugiego portu i wpisz kod, a na wyświetlaczu pojawi się debug. Po tym możesz zacząć wciskać przyciski na drugim (lub nawet trzecim) kontrolerze, aby zacząć robić rzeczy.

Ta kombinacja zadziała bez patchowania numeru wersji gry. Możesz nawet użyć tego na zwykłej detalicznej kopii gry bez żadnych narzędzi cheatujących lub modów konsolowych. Wprowadzenie kombosy po raz drugi wyłącza tryb zuru.

Użycie kodu na prawdziwym GameCube

Wiadomość „ZURU %d/%d” w zurumode_callback jest używana do wydrukowania statusu tej kombinacji, jeśli wprowadzisz ją, gdy ID dysku jest już 0x99 (przypuszczalnie do debugowania samego kodu cheatu). Pierwsza liczba to twoja aktualna pozycja w sekwencji, odpowiadająca r5. Druga jest ustawiona na 1, gdy niektóre przyciski w sekwencji są przytrzymane, co może odpowiadać sytuacji, gdy r6 jest ustawione na 1.

Większość wyświetlaczy nie wyjaśnia, czym są na ekranie, więc aby dowiedzieć się, co robią, musisz znaleźć funkcje, które je obsługują. Na przykład, długa linia niebieskich i czerwonych znaków, które pojawiają się na górze ekranu, to symbole zastępcze do wyświetlania statusu różnych zadań. Kiedy zadanie jest aktywne, pojawią się tam pewne liczby, wskazujące stan zadania.

Czarny ekran, który pojawia się po naciśnięciu Z, jest konsolą do drukowania komunikatów debugowania, ale szczególnie dla rzeczy niskiego poziomu, takich jak błędy alokacji pamięci i sterty lub inne złe wyjątki. Zachowanie fault_callback_scroll sugeruje, że może on służyć do wyświetlania tych błędów przed ponownym uruchomieniem systemu. Nie udało mi się wywołać żadnego z tych błędów, ale udało mi się sprawić, że wydrukował kilka śmieciowych znaków z kilkoma NOPami. Myślę, że byłoby to bardzo przydatne do późniejszego drukowania niestandardowych komunikatów debugowania:

JUTConsole garbage characters

Po zrobieniu tego wszystkiego, dowiedziałem się, że uzyskanie trybu debugowania poprzez zmianę ID wersji na 0x99 jest już znane: https://tcrf.net/Animal_Crossing#Debug_Mode. (Mają też kilka dobrych notatek na temat różnych wyświetlaczy i więcej rzeczy, które możesz zrobić używając kontrolera w porcie 3.)O ile mogę powiedzieć, kombinacja oszukańcza nie została jeszcze opublikowana.

To wszystko w tym poście. Jest jeszcze kilka funkcji deweloperskich, które chciałbym zbadać, takich jak ekran debugowania mapy i ekran wyboru emulatora NES, oraz jak je aktywować bez użycia łatek.

Ekran wyboru mapy

Będę również zamieszczał wpisy o odwróceniu systemów dialogowych, zdarzeń i questów w celu tworzenia modów.

Uaktualnienie: Slajdy z wykładu, który przeprowadziłem na ten temat można znaleźć tutaj.