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.

Desmontagem da função de entrada

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:

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

Você pode aplicar este patch no emulador Dolphin indo até a aba “Patches” das propriedades do jogo e entrando assim:

Metro de performance de depuração

Configurar este valor em 1 fez com que um gráfico interessante aparecesse na parte inferior da tela:

Metro de performance de depuração>

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.

game_move_first function

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 no padmgr 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”.

zuru mode format string

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?

zerumode_check_keycheck

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?

 verificação da bandeira de zurumode_flag

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”:

Dolphin debugger NOPping

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.

Debug mode letter D

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:

Branches em zzz_DebugDrawPrint

>

>Por meio do NOP, comecei a ver coisas diferentes sendo impressas na tela:

Mais coisas debug sendo impressas

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.

message number display

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:

Message 687 no editor de tabelas de string

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 de zurumode_callback
  • 0x10(padmgr_class) é definido para o endereço de padmgr_class ele mesmo
  • 0x4(zuruKeyCheck) é definido para o último bit de uma palavra carregada de 0x3C(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:

Tela de título com modo zuru

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

extrwi r3, r5, 1, 28

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:

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

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:

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}

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:

  1. Bit 26 é definido em 0x3C(osAppNMIBuffer)
  2. Bit 25 é definido em 0x3C(osAppNMIBuffer), e um controlador é conectado à porta 2
  3. 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.

Configurando o bit 26

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:

ID da versão do jogo 0x99

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?

Bit 25 e 28

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:

zerumode_check_keycheck

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.

key check end

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

key check end

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.

Configurando r5 para 0xb

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 ser 0xB
  • 8040ed60: r0 tem de ser 0x1000
  • 8040ebe8: r5 tem de ser 0xA
  • 8040ebe4: r5 tem de ser inferior a 0x5B
  • 8040eba4: r5 tem de ser superior a 0x7
  • 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

Traçando o caminho do código

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 ser 0x4000 (O botão B foi pressionado)
  • 8040ebe0: r5 tem de ser 0x5b
  • 8040eba4: r5 deve ser maior que 0x7
  • o mesmo de antes…

r5 deve começar em 0x5b

8040ed00
  • 8040ecfc: r0 tem de ser 0xC000 (A e B premido)
  • 8040ebf8: r5 tem de ser >= 9
  • 8040ebf0: r5 tem de ser inferior a 10
  • 8040ebe4: r5 tem de ser inferior a 0x5b
  • 8040eba4: r5 deve ser maior que 0x7
  • o mesmo que antes a partir daqui…

r5 deve começar em 9

8040ed50
  • 8040ed4c: r0 deve ser 0x8000 (A foi pressionado)
  • 8040ec04: r5 tem de ser inferior a 0x5d
  • 8040ebe4: r5 tem de ser maior que 0x5b
  • 8040eba4: r5 deve ser maior do que 0x7
  • 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 em 8040ece8.
  • r5 está regulado para 8 quando o botão C é premido em 8040eccc.
  • r5 está regulado para 7 quando o botão C é premido em 8040ecb0.
  • r5 está regulado para 6 quando o botão LEFT é premido em 8040ec98.
  • r5 está regulado para 5 (e r6 para 1) quando o botão DOWN é premido em 8040ec7c.
  • r5 está regulado para 4 quando o botão C é premido em 8040ec64.
  • r5 está regulado para 3 quando o botão C é premido em 8040ec48.
  • r5 está regulado para 2 quando o botão UP é premido em 8040ec30.
  • r5 está regulado para 1 (e r6 para 1) quando o botão Z é premido em 8040ec1c.

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 é:

  1. Pára-choques L+R e prima Z
  2. D-UP
  3. C-DOWN
  4. C-UP
  5. D-DOWN
  6. D-ESQUERDA
  7. C-LEFT
  8. C-REITA
  9. D-REITA
  10. A+B
  11. 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.

Usando o código em um GameCube real

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:

JUTConsole garbage characters

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.

Tela de seleção de mapa

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.