jamchamb’s blog
No verão passado comecei a engenharia reversa Animal Crossing para o GameCube para explorar a possibilidade de criar mods para o jogo. Também queria documentar o processo de criação de tutoriais para pessoas interessadas em hacking e engenharia reversa de ROM. Neste post explorei os recursos de depuração do desenvolvedor que ainda restam no jogo, e como descobri um combo de trapaças que pode ser usado para desbloqueá-los.
new_Debug_mode
E enquanto olhava ao redor de alguns símbolos de debug sobrando, eu notei funções e nomes de variáveis que continham a palavra “debug”, e pensei que seria interessante ver qual funcionalidade de debug poderia ser deixada no jogo. Se houvesse alguma funcionalidade de depuração que eu pudesse activar, também poderia ajudar no processo de criação dos mods.
A primeira função que dei uma vista de olhos foi new_Debug_mode
. É chamada pela função entry
, que corre logo após o ecrã do Nintendotrademark terminar. Tudo que ela faz é alocar uma estrutura de bytes 0x1C94
e salvar seu ponteiro.
Após ser chamada em entry
, um valor de 0 é definido em offset 0xD4
na estrutura alocada, bem antes de mainproc
ser chamada.
Para ver o que acontece quando o valor é diferente de zero, eu corrigi a instrução li r0, 0
em80407C8C
para li r0, 1
. Os bytes brutos para a instrução li r0, 0
são 38 00 00 00
, onde o valor atribuído está no final da instrução, então você pode apenas alterar isso para 38 00 00 01
para obter li r0, 1
. Para uma forma mais confiável de montar instruções,você pode usar algo como kstool
:
Você pode aplicar este patch no emulador Dolphin indo até a aba “Patches” das propriedades do jogo e entrando assim:
Configurar este valor em 1 fez com que um gráfico interessante aparecesse na parte inferior da tela:
>
Parecia um medidor de performance, com as pequenas barras na parte inferior do screengrowing e shrinking. (Mais tarde quando olhei para os nomes das funções que desenham o gráfico, descobri que elas de fato exibem métricas para uso de CPU e memória). Ajustar o valor acima de 1 realmente impediu a minha cidade de carregar, então não parecia haver muito mais a ver com isso.
Zuru mode
Comecei a olhar em volta para outras referências a coisas relacionadas a depuração, e vi algo chamado “modo zuru” aparecer algumas vezes. Ramificações para blocos de código que tinham funcionalidade de depuração – muitas vezes verificaram uma variável chamada zurumode_flag
.
Na função game_move_first
acima, zzz_LotsOfDebug
(um nome que inventei)só é chamado se zurumode_flag
for não-zero.
Procura de funções relacionadas com este valor produz o seguinte:
zurumode_init
zurumode_callback
zurumode_update
zurumode_cleanup
À primeira vista, todos eles são um pouco obscuros, girando em torno de bits variáveis em offsets em uma variável chamada osAppNMIBuffer
.Aqui está uma primeira olhada no que estas funções fazem:
zurumode_init
- Conjunto
zurumode_flag
a 0 - Verifica alguns bits em
osAppNMIBuffer
- Armazena um ponteiro para a função
zurumode_callback
nopadmgr
estrutura - Chamada
zurumode_update
zurumode_update
- Verifica alguns bits em
osAppNMIBuffer
- Atualização condicional
zurumode_flag
com base nestes bits - Imprime uma string de formato para o console do SO.
Este tipo de coisa é normalmente útil para dar contexto ao código, mas havia um monte de caracteres não-imprimíveis na string. O único texto reconhecível era “zurumode_flag” e “%d”.
Guindo que poderia ser texto japonês usando uma codificação de caracteres de múltiplos bytes, eu corri a string através de uma ferramenta de detecção de codificação de caracteres e descobri que era Shift-JIS codificado. A string traduzida significa apenas “zurumode_flag hasbeen changed from %d to %d”. Isso não fornece muita informação nova, mas saber sobre o uso do Shift-JIS fornece, já que há muito mais strings nos binários e tabelas de strings que usam esta codificação.
zurumode_callback
- Calls
zerumode_check_keycheck
- Cheque alguns bits em
osAppNMIBuffer
- >Imprime valor de
zurumode_flag
em algum lugar - Calls
zurumode_update
zerumode_check_keycheck
não apareceram antes por causa das diferenças de formatação.. o que é isso?
Uma enorme função complexa que faz muito mais twiddling bit em valores sem nomes.Neste ponto eu decidi recuar e procurar por outras funções e variáveis relacionadas a depuração, pois eu nem tinha certeza qual era o significado do modo zuru. Também não tinha a certeza do que significava a “verificação da chave” aqui. Poderia ser uma chave criptográfica?
Back to debug
Around desta vez notei que havia um problema com a forma como carregava os símbolos de debug no IDA. O arquivo foresta.map
no disco do jogo contém um monte de endereços e nomes para funções e variáveis. Eu não tinha notado inicialmente que os endereços para cada seção começaram em zero, então eu apenas configurei um script simples para adicionar uma entrada de nome para cada linha no arquivo.
Eu configurei novos scripts IDA para corrigir o carregamento do mapa de símbolos para as diferentes seções do programa:.text
, .rodata
, .data
, e .bss
. A seção .text
é onde todas as funções estão,então eu configurei o script para detectar automaticamente funções em cada endereço ao definir um nome desta vez.
Para as seções de dados, eu configurei para criar um segmento para cada objeto binário (como m_debug.o
,que seria compilado código para algo chamado m_debug
), e configurei espaço e nomes para cada pedaço de dado.Isso me dá muito mais informação do que eu tinha antes, embora eu agora tivesse que definir manualmente o tipo de dado para cada pedaço de dado, já que eu defino cada objeto de dados como um simples byte array. (Em retrospectiva, teria sido melhor assumir pelo menos qualquer dado com um tamanho que seja um múltiplo de 4 bytes contendo 32-bitintegers, pois há tantos deles, e muitos contêm endereços para funções e dados que são importantes para a construção de referências cruzadas.)
Apesar de olhar através do novo segmento .bss
para m_debug_mode.o
, eu vi algumas variáveis como quest_draw_status
e event_status
. Estas são interessantes porque eu quero obter o modo de debug para exibir mais algumas coisas úteis do gráfico de desempenho. Por sorte, houve referências cruzadas dessas entradas de dados para um enorme pedaço de código que verifica debug_print_flg
.
Utilizando o depurador Dolphin, eu defini um ponto de quebra na função onde debug_print_flg
é verificado(em 8039816C
) para ver como a verificação funciona. O ponto de quebra nunca acerta.
Vejamos porque: esta função é chamada por game_debug_draw_last
. Adivinhe qual valor é verificado antes de chamá-lo condicionalmente? zurumode_flag
. Que diabos é isso?
Eu defini um ponto de quebra nessa verificação (80404E18
) e ele quebrou imediatamente. O valor de zurumode_flag
era zero, por isso normalmente saltaria a chamada a esta função. Eu NOPpedia a instrução branch (substituí-a por uma instrução que não faz nada) para ver o que aconteceria quando a função fosse chamada.
No depurador Dolphin você pode fazer isso pausando o jogo, clicando com o botão direito em uma instrução, e então clicando em “Insert nop”:
Nada aconteceu. Então eu verifiquei o que aconteceu dentro da função, e encontrei outra declaração de ramificação que poderia cortar o circuito para além de todas as coisas interessantes em 803981a8
. Eu NOPped que fora também, e a letra “D” apareceu no canto superior direito da tela.
Havia um monte de código de aparência mais interessante nesta função em 8039816C
(Eu a chamei de zzz_DebugDrawPrint
), mas nada disso estava sendo chamado. Se você olhar para a vista gráfica desta função, você pode ver que há séries de instruções de ramificação que saltam sobre blocos ao longo de toda a função:
>
>Por meio do NOP, comecei a ver coisas diferentes sendo impressas na tela:
A próxima pergunta é como ativar essas funções de debug sem modificar o código.Além disso, zurumode_flag
aparece novamente para algumas declarações de branch feitas nesta função de debug draw.Eu adicionei outro patch para que zurumode_flag
esteja sempre definido para 2 em zurumode_update
, porque, em geral, é comparado especificamente com 2 quando não está sendo comparado com 0.Depois de reiniciar o jogo, eu vi esta mensagem “msg. no” exibida no canto superior direito da tela.
The number 687 is entry ID of the most most most display message. Eu verifiquei isso usando um visualizador simples que fiz no início, mas você também pode verificar com um editor de tabela de strings GUI completo que eu fiz para hacking de ROM. Aqui está o aspecto da mensagem no editor:
Neste ponto ficou claro que descobrir o modo zuru não era mais evitável; ele foi diretamente para as características de depuração do jogo.
Zuru modo revisitado
Retornando para zurumode_init
, ele inicializa algumas coisas:
-
0xC(padmgr_class)
é definido para o endereço dezurumode_callback
-
0x10(padmgr_class)
é definido para o endereço depadmgr_class
ele mesmo -
0x4(zuruKeyCheck)
é definido para o último bit de uma palavra carregada de0x3C(osAppNMIBuffer)
.
>
Eu olhei para o que padmgr
significa, e é a abreviatura de “gamepad manager”. Isto sugere que poderia haver uma combinação especial de teclas (botões) para entrar no gamepad para ativar o modo zuru, ou possivelmente algum dispositivo especial de depuração ou recurso de console de desenvolvimento que poderia ser usado para enviar um sinal para ativá-lo.
zurumode_init
só corre na primeira vez que o jogo é carregado (premir o botão reset não o activa).
Configurando um breakpoint em 8040efa4
, onde 0x4(zuruKeyCheck)
está configurado, podemos ver que durante o arranque sem manter nenhuma tecla premida, vai ser configurado para 0. Substituindo isto por 1 causa uma coisa interessante:
A letra “D” aparece novamente no canto superior direito (verde ao invés de amarelo desta vez), e também há algumas informações de construção:
Um patch para ter 0x4(zuruKeyCheck)
sempre ajustado para 1 no início:
8040ef9c 38c00001
>
Esta parece ser a maneira correta de inicializar o modo zuru. Depois disso, pode haver diferentes ações que precisamos tomar para obter certas informações de depuração para exibir. Iniciando o jogo e andando por aí e falando com um aldeão não mostrou nenhuma das exibições mencionadas anteriormente (além da letra “D” no canto).
Os prováveis suspeitos são zurumode_update
e zurumode_callback
.
zurumode_update
zurumode_update
é primeiro chamado por zurumode_init
, e depois repetidamente chamado porzurumode_callback
.
Verifica novamente o último bit de 0x3C(osAppNMIBuffer)
e depois actualiza zurumode_flag
com base no seu valor.
Se o bit for zero, a bandeira é colocada a zero.
Se não, a seguinte instrução corre com r5
sendo o valor total de 0x3c(osAppNMIBuffer)
:
Este extrai o 28º bit de r5
e guarda-o em r3
.Então 1 é adicionado ao resultado, então o resultado final é sempre 1 ou 2,
zurumode_flag
é então comparado com o resultado anterior, dependendo de quantos dos 28º e últimos bits estão definidos em 0x3c(osAppNMIBuffer)
: 0, 1, ou 2,
Este valor é escrito em zurumode_flag
. Se não mudou nada, a função termina e retorna o valor atual do flag. Se ele alterar o valor,uma cadeia muito mais complexa de blocos de código é executada.
É impressa uma mensagem em japonês: esta é a mensagem “zurumode_flag has been changed from %d to %d “mencionada anteriormente.
Então uma série de funções são chamadas com argumentos diferentes dependendo se o flag foi alterado para zero ou não. A montagem para esta parte é tediosa, por isso o pseudo código da itloooks como este:
Note que se o flag é zero, JC_JUTDbPrint_setVisible
é dado um argumento de 0.Se o flag não é zero E o bit 25 ou bit 31 são definidos em 0x3C(osAppNMIBuffer)
, a funçãosetVisible
é passada um argumento de 1.
Esta é a primeira chave para ativar o modo zuru: o último bit de 0x3C(osAppNMIBuffer)
deve ser definido como 1 para tornar as telas de depuração visíveis e definir zurumode_flag
para um valor diferente de zero.
zurumode_callback
zurumode_callback
está em 8040ee74
e provavelmente é chamado por uma função relacionada ao gamepad. Definindo um ponto de parada nele no depurador Dolphin, os callstackshows que ele realmente é chamado de padmgr_HandleRetraceMsg
.
Uma das primeiras coisas que ele faz é executado zerucheck_key_check
. É complexo, mas no geral parece ler e depois actualizar o valor de zuruKeyCheck
. Eu decidi ver como esse valor é usado no resto da função de retorno antes de ir mais longe na função de keycheck.
Próximo, verifique alguns bits em 0x3c(osAppNMIBuffer)
novamente. Se o bit 26 estiver definido, ou se o bit 25 estiver definido mas padmgr_isConnectedController(1)
retorna não zero, o último bit em 0x3c(osAppNMIBuffer)
está definido para 1!
Se nenhum desses bits estiver definido, ou se o bit 25 estiver pelo menos definido mas padmgr_isConnectedController(1)
retorna 0, então ele verifica se o byte em 0x4(zuruKeyCheck)
é 0. Se for,então ele zera o último bit no valor original e o escreve de volta para 0x3c(osAppNMIBuffer)
.Se não, então ele ainda define o último bit para 1.
Em pseudo-código isto parece:
Depois disso, se o bit 26 não estiver definido, ele se encurta para chamar zurumode_update
e então termina.
Se estiver definido, então se 0x4(zuruKeyCheck)
não for zero, ele carrega uma string de formato onde parece que vai imprimir: “ZURU %d/%d”.
Recap
Aqui está o que acontece:
padmgr_HandleRetraceMsg
chama o zurumode_callback
.O meu palpite é que “handle retrace message” significa que acabou de digitalizar a mensagem pressionando a tecla do controlador. Cada vez que faz a varredura, pode chamar uma série de chamadas diferentes.
Quando zurumode_callback
é executado, ele verifica a tecla (botão) atual pressiona. Isto parece verificar por um botão específico ou uma combinação de botões.
O último bit do Buffer NMI é atualizado com base em bits específicos em seu valor atual, assim como o valor de um dos bytes zuruKeyCheck
(0x4(zuruKeyCheck)
).
Então zurumode_update
executa e verifica esse bit. Se for 0, o sinalizador de modo zuru será zerado para 0. Se for 1, o sinalizador de modo é atualizado para 1 ou 2 baseado se o bit 28 está definido.
As três maneiras de ativar o modo zuru são:
- Bit 26 é definido em
0x3C(osAppNMIBuffer)
- Bit 25 é definido em
0x3C(osAppNMIBuffer)
, e um controlador é conectado à porta 2 -
0x4(zuruKeyCheck)
não é zero
osAppNMIBuffer
Perguntando o que osAppNMIBuffer
foi, Comecei por procurar por “NMI”, e encontrei referências a “interrupção não-máscara” no contexto da Nintendo. Acontece que todo o nome variável também aparece na documentação do criador para a Nintendo 64:
osAppNMIBuffer é um buffer de 64 bytes que é limpo em um reset a frio. Se o sistema for reinicializado por causa de um NMI, este buffer permanece inalterado.
Basicamente, este é um pequeno pedaço de memória que persiste através de reinicializações suaves. Um jogo pode usar este buffer para armazenar o que quiser, desde que a consola esteja ligada. O jogo original Animal Crossing foi realmente lançado na Nintendo 64, por isso faz sentir que algo assim apareceria no código.
Comutar para o binário boot.dol
(tudo acima é de foresta.rel
), há muitas referências a osAppNMIBuffer
na função main
. Um rápido olhar mostra que existem séries de verificações que podem resultar em vários bits de 0x3c(osAppNMIBuffer)
getting set com operações OR.
Interesting OR operand values to look for would be:
- Bit 31: 0x01
- Bit 30: 0x02
- Bit 29: 0x04
- Bit 28: 0x08
- Bit 27: 0x10
- Bit 26: 0x20
Lembrar que os bits 25, 26 e 28 são especialmente interessantes: 25 e 26 determinam se o modo zuru está ativado, e o bit 28 determina o nível da bandeira (1 ou 2).O bit 31 também é interessante, mas principalmente parece ser atualizado com base nos valores dos outros.
Bit 26
Primeiro acima: em 800062e0
há uma instrução ori r0, r0, 0x20
sobre o valor do bufferat 0x3c
. Isto definiria o bit 26, o que sempre resulta em modo zuru estar habilitado.
Para o bit a ser configurado, o 8º byte retornado de DVDGetCurrentDiskID
tem que ser 0x99
.Este ID está localizado logo no início da imagem do disco do jogo, e é carregado em 80000000
na memória. Para um lançamento de varejo regular do jogo, o ID fica assim:
47 41 46 45 30 31 00 00 GAFE01..
Apontando o último byte do ID para 0x99
faz com que o seguinte aconteça ao iniciar o jogo:
E no console do SO, o seguinte é impresso:
06:43:404 HW\EXI_DeviceIPL.cpp:339 N: ZURUMODE2 ENABLE08:00:288 HW\EXI_DeviceIPL.cpp:339 N: osAppNMIBuffer=0x00000078
Todos os outros patches podem ser removidos, e a letra D também aparece no canto superior direito da tela novamente, mas nenhuma das outras telas de depuração são ativadas.
Bit 25
Bit 25 é usado em conjunto com a execução da verificação da porta 2 do controlador. O que o causa a ativação?
Esta verificação é a mesma usada para o bit 28: a versão deve ser mais ou igual a 0x90
. O bit 26 foi definido (o ID é 0x99
), ambos os bits também serão definidos, e o modo zuru será ativado de qualquer forma.
Se a versão estiver entre 0x90
e 0x98
, no entanto, o modo zuru não será imediatamente ativado. A letra D e a exibição de informações de compilação na tela de título, e…pressionando os botões no segundo controlador controla a exibição de depuração!
Alguns botões também fazem coisas além de alterar a exibição, como aumentar a velocidade do jogo.
zerucheck_key_check
O último mistério é 0x4(zuruKeyCheck)
. Acontece que este valor é atualizado pela função complexa gigante mostrada antes:
Usando o depurador Dolphin, eu pude determinar que o valor verificado por esta função é um setof bits correspondente ao pressionamento de botões no segundo controlador. O botão pressione trace é armazenado em um valor de 16 bits a 0x2(zuruKeyCheck)
. Quando não há nenhum controlador ligado,o valor é 0x7638
.
Os 2 bytes contendo bandeiras para o controlador 2 botões pressionados são carregados e então atualizados perto do início de zerucheck_key_check
. O novo valor é passado com o registrador r4
por padmgr_HandleRetraceMsg
quando ele chama a função callback.
Down perto do fim de zerucheck_key_check
, na verdade há outro lugar onde 0x4(zuruKeyCheck)
é atualizado. Não apareceu na lista de referências cruzadas porque está usando r3
como endereço base, e só podemos descobrir o que r3
é olhando para o que está definido para qualquer tempo que esta função está prestes a ser chamada.
Em 8040ed88
o valor de r4
está escrito em 0x4(zuruKeyCheck)
. É carregada a partir do mesmo local e depois XORd com 1 pouco antes disso. O que isto deve fazer é alternar o valor do byte (realmente apenas o último bit) entre 0 e 1. (Se for 0, o resultado deXOR com 1 será 1. Se for 1, o resultado será 0. Olhe a tabela de verdade para XOR para ver isto.)
Não notei este comportamento enquanto observava os valores da memória antes, mas vou tentar quebrar esta instrução no depurador para ver o que está acontecendo. O valor original é carregado em 8040ed7c
.
Sem tocar em nenhum botão dos controladores, eu não carrego neste ponto de quebra durante a tela do título. Para alcançar este bloco de código, o valor de r5
deve ser 0xb
antes da instrução do ramo que vem antes dele (8040ed74
). Dos muitos caminhos diferentes que levam a esse bloco, há um que irá definir r5
para 0xb
antes dele, em 8040ed68
.
Note que para chegar ao bloco que define r5
para 0xB
, r0
deve ter sido igual a 0x1000
pouco antes. Seguindo os blocos até o início da função, podemos ver as restrições necessárias para chegar a este bloco:
- 8040ed74:
r5
deve ser0xB
- 8040ed60:
r0
tem de ser0x1000
- 8040ebe8:
r5
tem de ser0xA
- 8040ebe4:
r5
tem de ser inferior a0x5B
- 8040eba4:
r5
tem de ser superior a0x7
- 8040eb94:
r6
tem de ser 1 - 8040eb5c:
r0
não deve ser 0 - 8040eb74: Os valores dos botões da porta 2 devem ter mudado
Até chegarmos ao ponto onde os valores dos botões antigos são carregados e os novos valores são armazenados. Depois há um par de operações aplicadas aos valores novos e antigos:
old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals
A operação XOR marcará todos os bits que tenham mudado entre os dois valores. A operação AND então mascara a nova entrada para desajustar quaisquer bits que não estejam configurados atualmente. O resultado em r0
é o conjunto de novos bits (botões pressionados) no valor de novidade. Se não estiver vazio, estamos no caminho certo.
Para que r0
seja 0x1000
, o 4º dos 16 bits de rastreamento de botão deve ter acabado de mudar.Ao definir um ponto de quebra após a operação XOR/AND eu posso descobrir o que o apertar do botão causa isto: é o botão START.
A próxima pergunta é como obter r5
para começar como 0xA
. r5
e r6
são carregados de0x0(zuruKeyCheck)
no início da função de verificação de teclas, e atualizados perto do final quando não usamos o bloco de código que alterna 0x4(zuruKeyCheck)
.
Existem alguns lugares um pouco antes onde r5
fica definido para 0xA
:
8040ed50
8040ed00
8040ed38
8040ed38
-
8040ed34
:r0
deve ser0x4000
(O botão B foi pressionado) -
8040ebe0
:r5
tem de ser0x5b
-
8040eba4
:r5
deve ser maior que0x7
- o mesmo de antes…
r5
deve começar em 0x5b
8040ed00
-
8040ecfc
:r0
tem de ser0xC000
(A e B premido) -
8040ebf8
:r5
tem de ser >= 9 -
8040ebf0
:r5
tem de ser inferior a 10 -
8040ebe4
:r5
tem de ser inferior a0x5b
-
8040eba4
:r5
deve ser maior que0x7
- o mesmo que antes a partir daqui…
r5
deve começar em 9
8040ed50
-
8040ed4c
:r0
deve ser0x8000
(A foi pressionado) -
8040ec04
:r5
tem de ser inferior a0x5d
-
8040ebe4
:r5
tem de ser maior que0x5b
-
8040eba4
:r5
deve ser maior do que0x7
- igual a partir daqui…
r5
deve começar em 0x5c
Parece que há algum tipo de estado entre as pressões dos botões, e depois uma certa sequência de combos de botões precisa de ser introduzida, terminando com START. Parece que A e/ou B comejust antes de START.
Segundo o caminho do código que define r5
a 9, surge um padrão: r5
é um valor incremental que pode ser aumentado quando o valor correto de pressão do botão é encontrado em r0
,ou resetado para 0. Os casos mais estranhos onde não é um valor entre 0x0
e 0xB
ocorrem quando se manipulam passos de múltiplos botões, como pressionar A e B ao mesmo tempo. Uma personalização para introduzir esta combinação normalmente não vai pressionar ambos os botões no momento exacto em que o tracejado do pad ocorre, por isso tem de lidar com um dos botões sendo pressionado antes do outro.
Continuando com os diferentes caminhos de código:
-
r5
é definido para 9 quando RIGHT é pressionado em8040ece8
. -
r5
está regulado para 8 quando o botão C é premido em8040eccc
. -
r5
está regulado para 7 quando o botão C é premido em8040ecb0
. -
r5
está regulado para 6 quando o botão LEFT é premido em8040ec98
. -
r5
está regulado para 5 (e r6 para 1) quando o botão DOWN é premido em8040ec7c
. -
r5
está regulado para 4 quando o botão C é premido em8040ec64
. -
r5
está regulado para 3 quando o botão C é premido em8040ec48
. -
r5
está regulado para 2 quando o botão UP é premido em8040ec30
. -
r5
está regulado para 1 (er6
para 1) quando o botão Z é premido em8040ec1c
.
A sequência actual é:
Z, UP, C-DOWN, C-UP, DOWN, LEFT, C-LEFT, C-RIGHT, RIGHT, A+B, START
É verificada mais uma condição antes da verificação Z: enquanto o botão recentemente premido deve ser Z, as bandeiras actuais devem ser 0x2030
: os pára-choques esquerdo e direito também devem ser premidos (têm valores de 0x10
e 0x20
). Além disso, os botões UP/DOWN/LEFT/RIGHT são os botõesD-pad, e não o stick analógico.
O código de batota
A combinação completa é:
- Pára-choques L+R e prima Z
- D-UP
- C-DOWN
- C-UP
- D-DOWN
- D-ESQUERDA
- C-LEFT
- C-REITA
- D-REITA
- A+B
- START
Funciona! Anexe um controlador à segunda porta e digite o código, e a tela de depuração aparecerá. Depois disso você pode começar a pressionar botões no segundo (ou até mesmo no terceiro) controllerto começar a fazer coisas.
Esta combinação funcionará sem a correção do número da versão do jogo.Você pode até usar isto em uma cópia de varejo regular do jogo sem nenhuma ferramenta de cheat ou mods de console. Entrando no combo uma segunda vez, o modo zuru é desligado.
A mensagem “ZURU %d/%d” em zurumode_callback
é usada para imprimir o status desta combinação se você inseri-lo quando o ID do disco já estiver 0x99
(presumivelmente para depuração do próprio código da trapaça). O primeiro número é a sua posição actual na sequência, correspondendo a r5
. O segundo é definido para 1 enquanto certos botões da sequência são mantidos pressionados, estes podem corresponder a quando r6
está definido para 1.
A maior parte dos ecrãs não explica o que estão no ecrã, por isso para descobrir o que estão a fazer tem de encontrar as funções que os manipulam. Por exemplo, a longa linha de blue e redasterisks que aparecem na parte superior da tela são marcadores de posição para exibir o status de diferentes pedidos. Quando uma quest está ativa alguns números aparecerão lá, indicando o estado da quest.
A tela preta que aparece quando você pressiona o console Z isa para imprimir mensagens de debug, mas especificamente para coisas de baixo nível, como alocação de memória e erros de heap ou outras exceções ruins. O comportamento de fault_callback_scroll
sugere que pode ser fordisplaying esses erros antes que o sistema seja reinicializado. Eu não acionei nenhum desses erros, mas fui capaz de fazer com que ele imprima alguns caracteres de lixo com alguns NOPs. Acho que isso seria muito útil para imprimir mensagens de depuração personalizadas mais tarde em:
Depois de fazer tudo isso, descobri que já é conhecido o modo de debug ao corrigir o ID da versão para 0x99
: https://tcrf.net/Animal_Crossing#Debug_Mode. (Eles também têm algumas boas notas sobre o que são os vários displays, e mais coisas que você pode fazer usando um controlador na porta 3.)Até onde eu posso dizer, a combinação de cheat ainda não foi publicada, no entanto.
É tudo por este post. Ainda existem mais algumas funcionalidades para desenvolvedores que eu gostaria de explorar, como a tela de depuração de mapas e a tela de seleção do emulador NES, e como ativá-los sem usar patches.
Eu também estarei postando write ups sobre reverter o diálogo, eventos e sistemas de busca com o propósito de fazer mods.
Atualizar: Os slides para a palestra que eu fiz sobre isso podem ser encontrados aqui.