No verão passado, comecei a engenharia reversa Animal Crossing para o GameCube. Eu queria explorar a possibilidade de criar mods para este jogo. Além disso, queria documentar o processo para criar tutoriais para pessoas interessadas em invadir ROMs e engenharia reversa. Neste post, falarei sobre os recursos de depuração para desenvolvedores que permaneceram no jogo e também compartilharei como descobri combos de truques que podem ser usados para desbloqueá-los.
new_Debug_mode
Estudando os símbolos de depuração restantes, notei os nomes das funções e variáveis que contêm a palavra "debug" e decidi que seria interessante ver se alguma funcionalidade de depuração permanecia no jogo. Se eu conseguir ativar as funções de depuração ou desenvolvimento, isso me ajudará no processo de criação de mods.
A primeira função que notei foi
new_Debug_mode
. É chamado pela função de
entry
, que inicia imediatamente após a tela do logotipo da Nintendo terminar. Tudo o que ela faz é colocar a estrutura de byte
0x1C94
e salvar um ponteiro nela.
Depois de ser chamado na
entry
na estrutura hospedada no deslocamento
0xD4
imediatamente antes de chamar o
mainproc
valor 0 é definido.
Para ver o que acontece quando o valor não é zero,
80407C8C
a instrução
li r0, 0
em
80407C8C
, substituindo-a por
li r0, 1
. Os bytes brutos da instrução
li r0, 0
são
38 00 00 00
onde o valor atribuído está no final da instrução, então eu poderia substituir os bytes por
38 00 00 01
e obter
li r0, 1
. Como uma maneira mais confiável de criar instruções, você pode usar algo como o
kstool
:
$ kstool ppc32be "li 0, 1"
li 0, 1 = [ 38 00 00 01 ]
No emulador Dolphin, esse patch pode ser aplicado acessando a guia "Patches" nas propriedades do jogo e inserindo-o da seguinte maneira:
Depois de atribuir o valor 1, um gráfico interessante apareceu na parte inferior da tela:
Parecia um indicador de desempenho: as pequenas barras na parte inferior da tela aumentavam ou diminuíam. (Mais tarde, quando examinei os nomes das funções que desenham esse gráfico, descobri que elas realmente exibem métricas de uso de CPU e memória.)
Foi ótimo, mas não particularmente útil. Depois de atribuir o valor 1, minha cidade parou de carregar, para que nada mais pudesse ser feito aqui.
Modo Zuru
Novamente, comecei a procurar outras referências a funções de depuração e várias vezes me deparei com algo chamado "modo zuru". As ramificações dos blocos de código com funcionalidade de depuração costumavam verificar a variável
zurumode_flag
.
zzz_LotsOfDebug
(o nome que eu mesmo
game_move_first
) na função
game_move_first
mostrada acima é chamado apenas quando
zurumode_flag
não
zurumode_flag
igual a zero.
Procurando funções associadas a esse valor, encontrei estas:
zurumode_init
zurumode_callback
zurumode_update
zurumode_cleanup
À primeira vista, seu objetivo é misterioso, eles manipulam bits nos desvios de uma variável chamada
osAppNMIBuffer
.
Aqui está como o trabalho dessas funções olhou à primeira vista:
zurumode_init
- Define
zurumode_flag
como 0 - Verifica vários bits no
osAppNMIBuffer
- Salva um ponteiro para a função
zurumode_callback
na estrutura padmgr
- Chamadas
zurumode_update
zurumode_update
- Verifica vários bits no
osAppNMIBuffer
- Dependendo do valor desses bits, o
zurumode_flag
atualiza - Imprime uma string de formato no console do SO.
Isso geralmente é útil para contextualizar o código, mas havia muitos caracteres não imprimíveis na linha. O único texto reconhecível foi "zurumode_flag" e "% d".
Supondo que poderia ser texto em japonês com codificação de caracteres multibyte, passei a string pela ferramenta de reconhecimento de codificação e descobri que a string foi codificada com Shift-JIS. Na tradução, a linha simplesmente significava "O valor de zurumode_flag mudou de% d para% d". Isso não nos fornece muitas informações novas, mas agora sabemos que o Shift-JIS é usado: em arquivos binários e tabelas de linhas, há muito mais linhas nessa codificação.
zurumode_callback
- Chamadas
zerumode_check_keycheck
- Verifica vários bits no
osAppNMIBuffer
- O valor
zurumode_flag
está sendo zurumode_flag
- Chamadas
zurumode_update
zerumode_check_keycheck
até nos conhecermos por causa de uma ortografia diferente ... o que é?
Uma função enorme e complexa que trabalha muito mais em bits com valores sem nome.
Nesse ponto, decidi dar um passo atrás e estudar outras funções e variáveis de depuração, porque não tinha certeza da importância do modo zuru. Além disso, não entendi o que “verificação de chave” significa aqui. É possível que essa seja uma chave criptográfica?
Voltar à depuração
Nessa época, notei um problema com minha maneira de carregar símbolos de depuração na IDA. O arquivo
foresta.map
no disco do jogo contém muitos endereços e nomes de funções e variáveis. No começo, não vi que os endereços de cada seção recomeçam do zero, então escrevi um script simples que adiciona uma entrada de nome para cada linha do arquivo.
Escrevi novos scripts da IDA para corrigir o carregamento de tabelas de símbolos para diferentes seções do programa:
.text
,
.rodata
,
.data
e
.bss
. A seção
.text
contém todas as funções, então eu fiz isso para que, desta vez, quando eu definir o nome, o script reconheceria automaticamente as funções em cada endereço.
Nas seções de dados, ele agora criou um segmento para cada objeto binário (por exemplo,
m_debug.o
, que deveria ser o código compilado para algo chamado
m_debug
) e definiu o espaço e os nomes de cada parte dos dados.
Isso me deu muito mais informações, mas tive que definir manualmente o tipo de dados para cada parte dos dados, porque defini cada objeto de dados como uma matriz de bytes simples. (Olhando para trás, entendo que seria melhor supor que fragmentos de 4 bytes continha números inteiros de 32 bits, porque havia muitos e muitos endereços contidos de funções e dados importantes para a criação de referências cruzadas.)
Estudando o novo segmento
.bss
para
m_debug_mode.o
, encontrei várias variáveis no formato
quest_draw_status
e
event_status
. Isso é interessante porque eu queria que informações úteis, não apenas um gráfico de desempenho, fossem exibidas no modo de depuração. Felizmente, nesses registros de dados, havia referências cruzadas para um grande pedaço de código verificando
debug_print_flg
.
Usando um depurador no emulador Dolphin, defino um ponto de interrupção no local da função em que
debug_print_flg
verificado (no
8039816C
) para entender como essa verificação funciona. Mas o programa nunca passou para esse ponto de interrupção.
Vamos
game_debug_draw_last
por que isso acontece: essa função é chamada por
game_debug_draw_last
. Adivinha qual valor é verificado antes de sua chamada condicional?
zurumode_flag
! Que diabos está acontecendo?
Defina um ponto de interrupção nessa verificação (
80404E18
) e funcionou imediatamente. O valor de
zurumode_flag
era zero, portanto, na execução normal, o programa teria perdido a chamada para esta função. Em vez disso, inseri uma instrução de ramificação NOP (substituí-a por uma instrução que não faz nada) para verificar o que acontece quando a função é chamada.
No depurador Dolphin, isso pode ser feito pausando o jogo, clicando com o botão direito do mouse nas instruções e selecionando “Insert nop”:
Nada aconteceu. Depois, verifiquei o que estava acontecendo dentro da função e descobri outra construção ramificada que contornava tudo de interessante que acontecia em
803981a8
. Também inseri o NOP e a letra “D” apareceu no canto superior direito da tela.
Nesta função no
8039816C
(eu chamei de
zzz_DebugDrawPrint
), ainda há um monte de código interessante, mas não é chamado. Se você olhar para esta função na forma de um gráfico, poderá ver que há uma série de operadores de ramificação que ignoram os blocos de código em toda a função:
Depois de inserir o NOP em vez de várias outras construções ramificadas, comecei a ver várias coisas interessantes na tela:
A próxima pergunta era como ativar essa funcionalidade de depuração sem alterar o código.
Além disso, em algumas construções de ramificação,
zurumode_flag
ocorre novamente nessa função de desenho de depuração.
zurumode_update
outro patch para que em
zurumode_update
sinalizador
zurumode_update
sempre
zurumode_flag
atribuído ao valor 2, porque quando não se compara com 0, é comparado especificamente com o valor 2.
Depois de reiniciar o jogo, vi no canto superior direito da tela uma mensagem "msg. não "
O número 687 é o identificador de registro da mensagem exibida mais recentemente. Eu o verifiquei usando o programa visualizador de tabelas que escrevi no início da análise, mas você também pode verificá-lo usando o
editor de tabelas de strings com uma GUI completa , que escrevi para invadir ROMs. Aqui está a aparência da postagem no editor:
Nesse ponto, ficou claro que o estudo do modo zuru não era mais retirado - está diretamente relacionado às funções de depuração do jogo.
Voltar ao modo Zuru novamente
zurumode_init
inicializa várias coisas:
0xC(padmgr_class)
recebe o valor do endereço zurumode_callback
0x10(padmgr_class)
recebe o valor do endereço da própria padmgr_class
0x4(zuruKeyCheck)
recebe o valor do último bit na palavra carregada de 0x3C(osAppNMIBuffer)
.
Eu descobri o que
padmgr
o
padmgr
, uma abreviação de "gamepad manager". Isso significa que pode haver uma combinação especial de teclas (botões) que podem ser inseridas no gamepad para ativar o modo zuru, ou algum tipo de dispositivo de depuração ou função do console do desenvolvedor que pode ser usado para enviar um sinal para ativá-lo.
zurumode_init
é executado apenas na primeira inicialização do jogo (quando o botão de reset é pressionado, ele não funciona).
Depois de definir um ponto de interrupção no endereço
8040efa4
, no qual o valor
0x4(zuruKeyCheck)
é atribuído
0x4(zuruKeyCheck)
, podemos ver que, ao carregar sem pressionar as teclas, o valor é definido como 0. Se você substituí-lo por 1, algo interessante acontece:
A letra “D” aparece novamente no canto superior direito (desta vez é verde, não amarelo), e algumas informações de montagem também são exibidas:
[CopyDate: 02/08/01 00:16:48 ]
[Date: 02-07-31 12:52:00]
[Creator:SRD@SRD036J]
Um patch que sempre define
0x4(zuruKeyCheck)
como 1 no início é assim:
8040ef9c 38c00001
Esta parece ser a maneira correta de inicializar o modo zuru. Após isso, várias ações podem ser necessárias para obter a exibição de certas informações de depuração. Iniciando o jogo, dando uma volta nele e conversando com um morador, não veremos nenhuma das mensagens mencionadas acima (exceto a letra "D" no canto).
Os suspeitos mais prováveis são
zurumode_update
e
zurumode_callback
.
zurumode_update
zurumode_update
chamado primeiro em
zurumode_init
e depois constantemente chamado por
zurumode_callback
.
Ele verifica novamente o último bit
0x3C(osAppNMIBuffer)
e, com base nesse valor, atualiza
zurumode_flag
.
Se o bit for zero, o sinalizador será definido como zero.
Caso contrário, a seguinte instrução é executada, com o valor completo
0x3c(osAppNMIBuffer)
sendo
r5
:
extrwi r3, r5, 1, 28
Extrai o 28º bit de
r5
e o armazena em
r3
.
Em seguida, 1 é adicionado ao resultado, ou seja, o resultado final é sempre 1 ou 2.
Em seguida,
zurumode_flag
comparado com o resultado anterior, dependendo de quantos dos 28º e últimos bits estão configurados para
0x3c(osAppNMIBuffer)
: 0, 1 ou 2.
Este valor é gravado em
zurumode_flag
. Se não mudar nada, a função sai e retorna o valor atual do sinalizador. Se ele mudar o valor, será executada uma cadeia muito mais complexa de blocos de código.
Uma mensagem é exibida em japonês: o mesmo "valor zurumode_flag alterado de% d para% d", sobre o qual falamos acima.
Em seguida, uma série de funções é chamada com argumentos diferentes, dependendo se o sinalizador se tornou igual a zero ou não. O código assembler desta parte é monótono, então mostrarei seu pseudocódigo:
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) }
Observe que, se o sinalizador for zero, o argumento 0 será passado para JC_JUTDbPrint_setVisible.
Se o sinalizador não for igual a
0x3C(osAppNMIBuffer)
E o bit 25 ou 31 são definidos como
0x3C(osAppNMIBuffer)
, então
setVisible
passado o argumento 1.
Esta é a primeira chave para ativar o modo zuru: o último bit
0x3C(osAppNMIBuffer)
deve ser definido como 1 para exibir informações de depuração e definir
zurumode_flag
valor diferente de zero.
zurumode_callback
zurumode_callback
está localizado em
8040ee74
e provavelmente é chamado por uma função relacionada ao gamepad. Após inserir um ponto de interrupção no depurador Dolphin, a pilha de chamadas nos mostra que na verdade está sendo chamada de
padmgr_HandleRetraceMsg
.
Uma de suas primeiras ações foi executar
zerucheck_key_check
. Essa função é complexa, mas parece que em geral foi projetada para ler e atualizar o valor de
zuruKeyCheck
. Antes de passar para a função de verificação de tecla, decidi verificar como esse valor é usado no restante da função de retorno de chamada.
Em seguida, ele novamente verifica alguns bits em
0x3c(osAppNMIBuffer)
. Se o bit 26 estiver definido ou se o bit 25 estiver definido e
padmgr_isConnectedController(1)
retornar um valor diferente de zero, o último bit em
0x3c(osAppNMIBuffer)
como 1!
Se nenhum desses bits estiver definido ou o bit 25 estiver definido, mas
padmgr_isConnectedController(1)
retornar 0, a função verificará se o byte no endereço
0x4(zuruKeyCheck)
é igual a zero. Se igual, ele redefine o último bit no valor original e o grava de volta em
0x3c(osAppNMIBuffer)
. Caso contrário, ele ainda define o último bit como 1.
No pseudo-código, fica assim:
x = osAppNMIBuffer[0x3c] if (BIT(x, 26) || (BIT(x, 25) && isConnectedController(1)) || zuruKeyCheck[4] != 0) { osAppNMIBuffer[0x3c] = x | 1
Depois disso, se o bit 26 não estiver definido, a função continuará chamando
zurumode_update
e será encerrada.
Se o bit estiver definido, se
0x4(zuruKeyCheck)
não
0x4(zuruKeyCheck)
igual a zero, ele carregará uma string de formato na qual exibirá o seguinte: “ZURU% d /% d”.
Para resumir o subtotal
Aqui está o que acontece:
padmgr_HandleRetraceMsg
chama
zurumode_callback
. Eu suponho que essa "mensagem de retração de identificador" significa que simplesmente verifica as teclas digitadas no controlador. Com cada varredura, isso pode causar uma série de retornos de chamada diferentes.
Quando
zurumode_callback
executado
zurumode_callback
ele verifica os pressionamentos de tecla atuais (botões). Parece que ela está verificando um botão específico ou uma combinação de botões.
O último bit no NMI Buffer é atualizado dependendo dos bits específicos em seu valor atual, bem como do valor de um dos bytes
zuruKeyCheck
(
0x4(zuruKeyCheck)
).
Então
zurumode_update
é
zurumode_update
e verifica esse bit. Se for 0, o sinalizador do modo zuru será definido como 0. Se for 1, o sinalizador de modo mudará para 1 ou 2, dependendo se o bit 28 estiver definido.
Existem três maneiras de ativar o modo zuru:- O bit 26 está definido como
0x3C(osAppNMIBuffer)
- O bit 25 está definido como
0x3C(osAppNMIBuffer)
e o controlador está conectado à porta 2 0x4(zuruKeyCheck)
não 0x4(zuruKeyCheck)
zero
osAppNMIBuffer
Interessado no que significa
osAppNMIBuffer
, comecei a procurar por "NMI" e encontrei links no contexto da Nintendo como "interrupção não mascarável". Acontece que o nome dessa variável é totalmente mencionado na documentação do desenvolvedor do Nintendo 64:
o osAppNMIBuffer é um buffer de 64 bytes que é limpo após uma reinicialização a frio. Se o sistema reiniciar devido à NMI, o estado desse buffer não será alterado.
De fato, esse é um pequeno pedaço de memória salva durante uma reinicialização "suave" (com o botão de reinicialização). O jogo pode usar esse buffer para armazenar quaisquer dados enquanto o console estiver na rede. O Animal Crossing original foi lançado no Nintendo 64, por isso é lógico que algo semelhante deveria aparecer no código.
Se formos para o arquivo binário
boot.dol
(tudo mostrado acima estava em
foresta.rel
), sua função
main
possui muitos links para
osAppNMIBuffer
. Uma rápida olhada mostra que há uma série de verificações que podem levar à
0x3c(osAppNMIBuffer)
valores de diferentes bits
0x3c(osAppNMIBuffer)
usando operações OR.
Os seguintes valores do operando OR podem ser interessantes:
- Bit 31: 0x01
- Bit 30: 0x02
- Bit 29: 0x04
- Bit 28: 0x08
- Bit 27: 0x10
- Bit 26: 0x20
Lembramos 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 flag (1 ou 2).
O bit 31 também é interessante, mas parece que muda dependendo dos valores dos outros.
Bit 26
Primeiro: no endereço
800062e0
há uma instrução
ori r0, r0, 0x20
com um valor de buffer de
0x3c
. Ele define o bit 26, que sempre ativa o modo zuru.
Para que o bit seja definido, o oitavo byte retornado de
DVDGetCurrentDiskID
deve ser
0x99
. Este identificador está localizado no início da imagem de disco do jogo e é carregado na memória em
80000000
. Em um lançamento de varejo regular do jogo, o ID fica assim:
47 41 46 45 30 31 00 00 GAFE01..
Substituindo o último byte do identificador por um
0x99
para
0x99
, obtemos a seguinte imagem ao iniciar o jogo:
E o seguinte é exibido no console do SO:
06:43:404 HW\EXI_DeviceIPL.cpp:339 N[OSREPORT]: ZURUMODE2 ENABLE
08:00:288 HW\EXI_DeviceIPL.cpp:339 N[OSREPORT]: osAppNMIBuffer[15]=0x00000078
Todos os outros patches podem ser removidos, após o que a letra D aparece novamente no canto superior direito da tela, mas não há mais mensagens de depuração ativadas.
Bit 25
O bit 25 é usado em conjunto com a verificação da porta do controlador 2. O que faz com que ele seja ligado?
Acontece que ele deve usar a mesma verificação do bit 28: a versão deve ser maior ou igual a
0x90
. Se o bit 26 estiver definido (o ID é
0x99
), os dois bits também serão definidos e o modo zuru ainda será ativado.
No entanto, se a versão estiver no intervalo de
0x90
a
0x98
, o modo zuru não será ativado instantaneamente. Lembre-se da verificação realizada em
zurumode_callback
- o modo será ativado apenas se o bit 25 estiver definido
e padmgr_isConnectedController(1)
retornar um valor diferente de zero.
Após o controlador ser conectado à porta 2 (o argumento
isConnectedController
possui zero indexação), o modo zuru é ativado. A letra D e as informações sobre a montagem aparecem na tela inicial, e nós ... podemos controlar a exibição da depuração usando os botões do segundo controlador!
Alguns botões executam ações que não apenas alteram a exibição, mas também, por exemplo, aumentam a velocidade do jogo.
zerucheck_key_check
O último mistério permanece
0x4(zuruKeyCheck)
. Acontece que esse valor é atualizado com uma enorme função complexa que mostrei acima:
Usando o depurador do emulador Dolphin, consegui determinar que o valor verificado por esta função é um conjunto de bits correspondente ao pressionamento do botão no segundo controlador.
O rastreamento de cliques no botão é armazenado em um valor de 16 bits em
0x2(zuruKeyCheck)
. Quando o controlador não está conectado, o valor é
0x7638
.
2 bytes contendo sinalizadores das teclas pressionadas do controlador são baixados e atualizados no início de
zerucheck_key_check
. O novo valor é passado com o registro
r4
função
padmgr_HandleRetraceMsg
quando chama a função de retorno de chamada.
Perto do final de
zerucheck_key_check
há outro local em que
0x4(zuruKeyCheck)
atualizado
0x4(zuruKeyCheck)
.
Ele não apareceu na lista de referências cruzadas porque é usado como o endereço base r3
, e podemos descobrir o valor r3
apenas olhando para qual valor é atribuído a ele antes de chamar esta função.No endereço, o 8040ed88
valor é r4
gravado em 0x4(zuruKeyCheck)
. Logo antes disso, mas ele é gravado no mesmo local e, em seguida, XORs a partir de 1. A tarefa desta operação é alternar o valor do byte (e de fato o último bit) entre 0 e 1. (Se o valor for 0, o resultado deXOR de 1 será 1). Se o valor for 1, o resultado será 0. Consulte a tabela verdade para XOR.)Anteriormente, quando estudei os valores na memória, não percebi esse comportamento, mas tentarei quebrar essa instrução no depurador para entender o que está acontecendo. O valor original é carregado em 8040ed7c
.Sem tocar nos botões do controlador, não chegarei a esse ponto de interrupção na tela inicial. Para entrar nesse bloco de código, o valor r5
deve se tornar igual 0xb
antes da instrução de ramificação anterior ao ponto de interrupção ( 8040ed74
). Dos muitos caminhos diferentes que levam a esse bloco, apenas um atribui um r5
valor 0xb
à sua frente no endereço 8040ed68
.Observe que, para alcançar o bloco que atribui o r5
valor 0xB
, o valor r0
deve ser igual logo antes dele 0x1000
. Após os blocos da cadeia até o início da função, podemos ver todas as restrições necessárias para atingir esse bloco:- 8040ed74: o valor
r5
deve ser igual0xB
- 8040ed60: o valor
r0
deve ser igual0x1000
- 8040ebe8: o valor
r5
deve ser igual0xA
- 8040ebe4: o valor
r5
deve ser menor0x5B
- 8040eba4: o valor
r5
deve ser maior0x7
- 8040eb94: o valor
r6
deve ser 1 - 8040eb5c: o valor
r0
não deve ser 0 - 8040eb74: os valores do botão da porta 2 devem mudar

Aqui chegamos ao ponto em que os valores dos botões antigos são carregados e os novos valores são salvos. Em seguida, vêm algumas operações aplicadas aos valores novos e antigos: A operação XOR marca todos os bits que foram alterados entre os dois valores. Em seguida, a operação AND mascara a nova entrada para definir todos os bits que não estão configurados no momento para o estado 0. O resultado é um conjunto de novos bits (pressionar o botão) no novo valor. Se não estiver vazio, estamos no caminho certo. Para fazer a diferença , o quarto dos botões de rastreamento de 16 bits deve mudar. Depois de inserir um ponto de interrupção após a operação XOR / AND, descobri que o botão START aciona esse estado. A próxima pergunta é como fazê- lo ser inicialmente igualold_vals = old_vals XOR new_vals
old_vals = old_vals AND new_vals
r0
r0
0x1000
r5
0xA
. r5
e r6
são carregados desde 0x0(zuruKeyCheck)
o início da função de teste principal e atualizados mais perto do fim, quando não entramos no bloco de código que o inclui 0x4(zuruKeyCheck)
.Existem vários lugares antes disso onde o r5
valor é atribuído 0xA
:8040ed38
8040ed34
: o valor r0
deve ser igual 0x4000
(o botão B é pressionado)8040ebe0
: o valor r5
deve ser igual0x5b
8040eba4
: o valor r5
deve ser maior0x7
- então tudo corre como antes ...
r5
deve começar com 0x5b
8040ed00
8040ecfc
: o valor r0
deve ser igual 0xC000
(A e B são pressionados)8040ebf8
: o valor r5
deve ser> = 98040ebf0
: o valor r5
deve ser menor que 108040ebe4
: o valor r5
deve ser menor0x5b
8040eba4
: r5
deve ser mais0x7
- então tudo corre como antes ...
r5
deve começar às 98040ed50
8040ed4c
: o valor r0
deve ser igual 0x8000
(botão A pressionado)8040ec04
: o valor r5
deve ser menor0x5d
8040ebe4
: o valor r5
deve ser maior0x5b
8040eba4
: o valor r5
deve ser maior0x7
- então tudo corre como antes ...
r5
deve começar com 0x5c
Parece que existe algum tipo de estado entre as teclas, após o qual você precisa inserir uma certa sequência de combos a partir dos botões, terminando com a tecla START. Parece que A e / ou B devem ir logo antes de INICIAR.Se você rastrear o caminho do código que define o r5
valor como 9, surge um padrão: r5
- é um valor crescente que pode aumentar quando um r0
valor adequado é encontrado ou zero. Os casos mais estranhos, onde não é um valor na faixa de 0x0
até0xB
, ocorre ao processar etapas com vários botões, por exemplo, quando A e B. são pressionados simultaneamente.Uma pessoa que tenta entrar nesse combo geralmente não pode pressionar os dois botões exatamente ao mesmo tempo enquanto acompanha o gamepad, portanto, é necessário processar o botão pressionado primeiro.Continuamos a explorar diferentes caminhos de código:r5
assume o valor 9 quando RIGHT é pressionado no endereço 8040ece8
.r5
assume o valor 8 quando o botão direito C no endereço é pressionado 8040eccc
.r5
assume o valor 7 quando o botão esquerdo C é pressionado no endereço 8040ecb0
.r5
assume o valor 6 quando ESQUERDA é pressionado no endereço 8040ec98
.r5
assume o valor 5 (e r6 assume o valor 1) quando DOWN é pressionado no endereço 8040ec7c
.r5
assume o valor 4 quando o botão superior C no endereço é pressionado 8040ec64
.r5
assume o valor 3 quando o botão inferior C no endereço é pressionado 8040ec48
.r5
assume o valor 2 quando UP é pressionado no endereço 8040ec30
.r5
assume o valor 1 (e r6
assume o valor 1) quando Z é pressionado no endereço 8040ec1c
.
A sequência atual é:Z, CIMA, C-BAIXO, C-UP, BAIXO, ESQUERDA, C-ESQUERDA, C-DIREITA, DIREITA, A + B, INÍCIOAntes de verificar Z, mais uma condição é verificada: embora um novo botão deva ser pressionado Z, os sinalizadores atuais devem ser iguais 0x2030
: os pára-choques esquerdo e direito também devem ser pressionados (eles têm valores de 0x10
e 0x20
). Além disso, ACIMA / ABAIXO / ESQUERDA / DIREITA são botões D-pad, não um stick analógico.Batota código
A combinação completa fica assim:- Segure os para-choques L + R e pressione Z
- D-up
- C-DOWN
- C-up
- D-down
- D-esquerda
- C-esquerda
- C-DIREITA
- D-DIREITA
- A + B
- INICIAR
Isso funciona! Conecte o controlador à segunda porta e insira o código, após o qual as informações de depuração serão exibidas. Depois disso, você pode começar a pressionar os botões no segundo (ou até terceiro) controlador para executar várias ações.Essa combinação funcionará sem corrigir o número da versão do jogo. Pode até ser usado em uma cópia regular do jogo, sem nenhuma ferramenta de trapaça ou mods de console. Voltar a entrar em combos desativa o modo zuru.A mensagem "ZURU% d /% d" é zurumode_callback
usada para exibir o status dessa combinação se você a inserir quando o ID do disco já for igual 0x99
(provavelmente para depurar o próprio código de fraude). O primeiro número é sua posição atual na sequência, correspondente r5
. O segundo pega o valor 1, quando certos botões da sequência são mantidos, eles podem corresponder a quando o r6
valor é atribuído 1.A maioria das mensagens não explica o que está fazendo na tela; portanto, para entender sua finalidade, é necessário encontrar funções que as processem. Por exemplo, uma longa linha de estrelas azuis e vermelhas na parte superior da tela são espaços reservados para exibir o status de várias missões. Quando a missão está ativa, alguns números aparecem lá, informando o status da missão.A tela preta exibida ao pressionar Z é um console para exibir mensagens de depuração, especificamente para aspectos de baixo nível, como alocação de memória, erros de heap e outras exceções ruins. Por comportamento, fault_callback_scroll
pode-se supor que ele seja usado para exibir esses erros antes que o sistema reinicie. Ele não inicia nenhum desses erros, mas pode causar a impressão de alguns caracteres ilegíveis com vários NOPs. Eu acho que no futuro será muito útil para exibir suas próprias mensagens de depuração:Depois de fazer tudo isso, descobri que entrar no modo de depuração corrigindo o ID daversão 0x99
já é conhecido por outras pessoas: https://tcrf.net/Animal_Crossing#Debug_Mode . (Também existem boas notas no link que indicam várias mensagens e conversam sobre outras coisas que podem ser feitas com o controlador na porta 3.) No entanto, até onde eu sei, ninguém publicou a combinação de truques ainda.Isso é tudo.
Há outros recursos de desenvolvedor que eu gostaria de explorar, como a tela de depuração do cartão e a tela de seleção do emulador NES e como ativá-los sem usar patches.Além disso, publicarei artigos sobre engenharia reversa de sistemas de diálogo, eventos e missões com o objetivo de criar mods.