blog de jamchamb

El verano pasado empecé a hacer ingeniería inversa de Animal Crossing para GameCube para explorar la posibilidad de crear mods para el juego. También quería documentar el procesopara crear tutoriales para la gente interesada en el hacking de la ROM y la ingeniería inversa.En este post exploro las características de depuración de los desarrolladores que aún quedan enel juego, y cómo descubrí un combo de trucos que se puede utilizar para desbloquearlos.

new_Debug_mode

Mientras miraba algunos símbolos de depuración sobrantes, me fijé en funciones y nombres de variables que contenían la palabra «debug», y pensé que sería interesante ver qué funcionalidad de depuración podría quedar en el juego. Si hubiera alguna función de depuración o de desarrollador que pudiera activar, también podría ayudar en el proceso de creación de mods.

La primera función a la que eché un vistazo fue new_Debug_mode.Es llamada por la función entry, que se ejecuta justo después de que termine la pantalla de Nintendotrademark. Todo lo que hace es asignar una estructura de 0x1C94 bytes y guardar su puntero.

Después de ser llamada en entry, se establece un valor de 0 en el offset 0xD4 de la estructura asignada, justo antes de llamar a mainproc.

Desmontaje de la función de entrada

Para ver qué ocurre cuando el valor es distinto de cero, he parcheado la instrucción li r0, 0 en80407C8C a li r0, 1. Los bytes crudos para la instrucción li r0, 0 son 38 00 00 00, donde el valor asignado está al final de la instrucción, así que puedes cambiar esto a 38 00 00 01 para obtener li r0, 1. Para una forma más fiable de ensamblar las instrucciones, podrías usar algo como kstool:

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

Puedes aplicar este parche en el emulador Dolphin yendo a la pestaña «Parches» de las propiedades del juego e introduciéndolo así:

Medidor de rendimiento de depuración

Ajustar este valor a 1 hizo que apareciera un interesante gráfico en la parte inferior de la pantalla:

Medidor de rendimiento de depuración

Parecía un medidor de rendimiento, con las pequeñas barras en la parte inferior de la pantalla creciendo y encogiéndose. (Más tarde, cuando busqué los nombres de las funciones que dibujan el gráfico, descubrí que, de hecho, muestran las métricas de uso de la CPU y la memoria). Al establecer el valor por encima de 1, mytown dejaba de cargarse, así que no parecía que hubiera mucho más que hacer con esto.

Modo zuru

Empecé a mirar otras referencias a cosas relacionadas con la depuración, y vi que aparecía algo llamado «modo zuru» unas cuantas veces. Las bifurcaciones a bloques de código que tenían funcionalidad de depuración a menudo comprobaban una variable llamada zurumode_flag.

función game_move_first

En la función game_move_first que aparece arriba, zzz_LotsOfDebug (un nombre que me he inventado)sólo se llama si zurumode_flag es distinto de cero.

Buscando funciones relacionadas con este valor se obtiene lo siguiente:

  • zurumode_init
  • zurumode_callback
  • zurumode_update
  • zurumode_cleanup

A primera vista, todos son un poco oscuros, jugando con varios bits en los desplazamientos de una variable llamada osAppNMIBuffer.He aquí un primer vistazo a lo que hacen estas funciones:

zurumode_init

  • Poner zurumode_flag a 0
  • Comprobar algunos bits en osAppNMIBuffer
  • Guardar un puntero a la función zurumode_callback en la estructura padmgr
  • Llamar a zurumode_update

zurumode_update

  • Comprueba algunos bits en osAppNMIBuffer
  • Actualiza condicionalmente zurumode_flag basándose en estos bits
  • Imprime una cadena de formato en la consola del SO.

Este tipo de cosas suele ser útil para dar contexto al código, pero había un montón de caracteres no imprimibles en la cadena. El único texto reconocible era «zurumode_flag» y «%d».

Cadena de formato de modo zuru

Suponiendo que podría ser un texto japonés que utilizaba una codificación de caracteres multibyte, pasé la cadena por una herramienta de detección de codificación de caracteres y descubrí que estaba codificada en Shift-JIS. La cadena traducida sólo significa «zurumode_flag hasbeen changed from %d to %d». Eso no proporciona mucha información nueva, pero saber sobre el uso de Shift-JIS sí, ya que hay muchas más cadenas en los binarios y tablas de cadenas que utilizan esta codificación.

zurumode_callback

  • Llama a zerumode_check_keycheck
  • Comprueba algunos bits en osAppNMIBuffer
  • Imprime el valor de zurumode_flag en alguna parte
  • Llama a zurumode_update

zerumode_check_keycheck no aparecía antes por la diferencia de ortografía.. ¿qué es?

zerumode_check_keycheck

Una enorme y compleja función que hace muchas más manipulaciones de bits en valores sin nombre.En este punto decidí retroceder y buscar otras funciones y variables relacionadas con la depuración, ya que ni siquiera estaba seguro de cuál era el significado de zuru mode. Tampoco estaba seguro de lo que significaba «key check» aquí. ¿Podría ser una clave criptográfica?

Volver a la depuración

Alrededor de este tiempo me di cuenta de que había un problema con la forma en que cargué los símbolos de depuración en IDA. El archivo foresta.map del disco del juego contiene un montón de direcciones y nombres de funciones y variables. No me había dado cuenta inicialmente de que las direcciones de cada sección empezaban de nuevo en cero, así que preparé un sencillo script para añadir una entrada de nombre para cada línea del archivo.

Preparé nuevos scripts de IDA para arreglar la carga del mapa de símbolos para las diferentes secciones del programa:.text, .rodata, .data y .bss. La sección .text es donde están todas las funciones, así que configuré el script para que detectara automáticamente las funciones en cada dirección al establecer un nombre esta vez.

Para las secciones de datos, lo configuré para crear un segmento para cada objeto binario (como m_debug.o, que sería código compilado para algo llamado m_debug), y configuré el espacio y los nombres para cada pieza de datos.Esto me da mucha más información de la que tenía antes, aunque ahora tuve que definir manualmente el tipo de datos para cada pieza de datos, ya que configuré cada objeto de datos para que fuera una simple matriz de bytes. (En retrospectiva, habría sido mejor asumir al menos que cualquier dato con un tamaño que es un múltiplo de 4 bytes contenía enteros de 32 bits, ya que hay muchos de ellos, y muchos contienen direcciones a las funciones y los datos que son importantes para la construcción de referencias cruzadas). Estos son interesantes porque quiero conseguir el modo de depuración para mostrar algunas cosas más útiles que el gráfico de rendimiento. Por suerte, había referencias cruzadas desde estas entradas de datos a un enorme trozo de código que comprueba debug_print_flg.

Usando el depurador Dolphin, establecí un punto de interrupción en la función donde se comprueba debug_print_flg (en 8039816C) para ver cómo funciona la comprobación. El punto de interrupción nunca llega.

Veamos por qué: esta función es llamada por game_debug_draw_last. ¿Adivina qué valor se comprueba antes de llamarla condicionalmente? zurumode_flag. ¿Qué diablos es?

Comprobación de la bandera del modo de funcionamiento

Puse un punto de interrupción en esa comprobación (80404E18) y se rompió inmediatamente. El valor de zurumode_flag era cero, por lo que normalmente omitiría la llamada a esta función. He eliminado la instrucción de bifurcación (la he sustituido por una instrucción que no hace nada) para ver qué pasaba cuando se llamaba a la función.

En el depurador de Dolphin se puede hacer esto poniendo en pausa el juego, haciendo clic con el botón derecho del ratón en una instrucción, y luego haciendo clic en «Insertar nop»:

Depurador de Dolphin NOPping

No pasó nada. Entonces comprobé lo que ha sucedido dentro de la función, y encontré otra sentencia de bifurcación que podría hacer un cortocircuito más allá de todas las cosas interesantes en 803981a8. Lo eliminé también, y la letra «D» apareció en la esquina superior derecha de la pantalla.

Modo de depuración letra D

Había un montón de código más interesante en esta función en 8039816C (la llamé zzz_DebugDrawPrint), pero nada de ello fue llamado. Si miras la vista del gráfico de esta función, puedes ver que hay una serie de sentencias de bifurcación que se saltan bloques a lo largo de toda la función:

Bifurcaciones en zzz_DebugDrawPrint

Al eliminar más de estas sentencias de bifurcación, empecé a ver que se imprimían cosas diferentes en la pantalla:

Se imprimen más cosas de depuración

La siguiente pregunta es cómo activar estas funciones de depuración sin modificar el código.Además, zurumode_flag aparece de nuevo para algunas declaraciones de bifurcación hechas en esta función de dibujo de depuración.He añadido otro parche para que zurumode_flag se ponga siempre a 2 en zurumode_update, porque normalmente se compara específicamente con 2 cuando no se está comparando con 0.Después de reiniciar el juego, vi este mensaje «msg. no» mostrado en la parte superior derecha de la pantalla.

visualización del número de mensaje

El número 687 es el ID de la entrada del mensaje mostrado más recientemente. He comprobado esto usando un visor de tablas simple que hice al principio, pero también puedes comprobarlo con un editor de tablas de cadenas con interfaz gráfica de usuario completo que hice para hackear la ROM. Esto es lo que parece el mensaje en el editor:

Mensaje 687 en el editor de tablas de cadenas

En este punto estaba claro que averiguar el modo zuru ya no era evitable; está ligado directamente a las características de depuración del juego.

Modo zuru revisitado

Volviendo a zurumode_init, se inicializan algunas cosas:

  • 0xC(padmgr_class) se pone a la dirección de zurumode_callback
  • 0x10(padmgr_class) se pone a la dirección del propio padmgr_class
  • 0x4(zuruKeyCheck) se pone al último bit de una palabra cargada desde 0x3C(osAppNMIBuffer).

He mirado lo que significa padmgr, y es la abreviatura de «gamepad manager». Esto sugiere que podría haber una combinación especial de teclas (botones) para introducir en el gamepad para activar el modo zuru, o posiblemente algún dispositivo de depuración especial o característica de la consola de desarrollo que podría ser utilizado para enviar una señal para activarlo.

zurumode_initSólo se ejecuta la primera vez que se carga el juego (al pulsar el botón de reinicio no se activa).

Estableciendo un punto de interrupción en 8040efa4, donde se establece 0x4(zuruKeyCheck), podemos ver que durante el arranquesin mantener pulsada ninguna tecla, se va a poner a 0. Reemplazar esto por 1 hace que ocurra algo interesante:

Pantalla de título con modo zuru

La letra «D» aparece de nuevo en la esquina superior derecha (verde en lugar de amarilla esta vez), y también hay algo de información de construcción:

Un parche para que 0x4(zuruKeyCheck) se ponga siempre a 1 en el arranque:

8040ef9c 38c00001

Esto parece que es la forma correcta de que se inicialice el modo zuru. Después de eso, puede haber diferentes acciones que tenemos que tomar con el fin de obtener cierta información de depuración para mostrar. Iniciar el juego y caminar alrededor y hablar con un aldeano no mostró anyof las pantallas mencionadas anteriormente (además de la letra «D» en la esquina).

Los sospechosos probables son zurumode_update y zurumode_callback.

zurumode_update

zurumode_update es llamado primero por zurumode_init, y luego es llamado repetidamente porzurumode_callback.

Comprueba de nuevo el último bit de 0x3C(osAppNMIBuffer) y luego actualiza zurumode_flagbasándose en su valor.

Si el bit es cero, la bandera se pone a cero.

Si no, se ejecuta la siguiente instrucción con r5 siendo el valor completo de 0x3c(osAppNMIBuffer):

extrwi r3, r5, 1, 28

Esto extrae el 28º bit de r5 y lo guarda en r3.A continuación, se añade 1 al resultado, por lo que el resultado final es siempre 1 o 2.

zurumode_flagSe compara entonces con el resultado anterior, dependiendo de cuántos de los 28 y últimos bits se establecen en 0x3c(osAppNMIBuffer): 0, 1 o 2.

Este valor se escribe en zurumode_flag. Si no cambia nada, la función termina y devuelve el valor actual de la bandera. Si cambia el valor, se ejecuta una cadena mucho más compleja de bloques de código.

Se imprime un mensaje en japonés: este es el mensaje «zurumode_flag ha sido cambiado de %d a %d «mencionado anteriormente.

Luego se llama a una serie de funciones con diferentes argumentos dependiendo de si la bandera fue cambiada a cero o no. El ensamblaje de esta parte es tedioso, por lo que el pseudocódigo de la misma tiene el siguiente aspecto:

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

Nótese que si la bandera es cero, a JC_JUTDbPrint_setVisible se le pasa un argumento de 0.Si la bandera no es cero Y el bit 25 o el bit 31 están establecidos en 0x3C(osAppNMIBuffer), a la funciónsetVisible se le pasa un argumento de 1.

Esta es la primera clave para activar el modo zuru: el último bit de 0x3C(osAppNMIBuffer)debe ponerse a 1 para hacer visibles las pantallas de depuración y poner zurumode_flaga un valor distinto de cero.

zurumode_callback

zurumode_callbackestá en 8040ee74 y probablemente es llamada por una función relacionada con el gamepad. Estableciendo un punto de interrupción en él en el depurador de Dolphin, la pila de llamadas muestra que efectivamente se llama desde padmgr_HandleRetraceMsg.

Una de las primeras cosas que hace es ejecutar zerucheck_key_check. Es complejo, pero en general parece leer y luego actualizar el valor de zuruKeyCheck. Decidí ver cómo se utiliza ese valor en el resto de la función de devolución de llamada antes de ir más allá en la función de comprobación de claves.

Luego comprueba algunos bits en 0x3c(osAppNMIBuffer) de nuevo. Si el bit 26 está activado, o si el bit 25 está activado y padmgr_isConnectedController(1) devuelve un valor distinto de cero, el último bit de 0x3c(osAppNMIBuffer) se pone a 1.

Si ninguno de esos bits está activado, o si el bit 25 está al menos activado pero padmgr_isConnectedController(1) devuelve 0, entonces comprueba si el byte en 0x4(zuruKeyCheck) es 0. Si lo es, entonces pone a cero el último bit del valor original y lo vuelve a escribir en 0x3c(osAppNMIBuffer).Si no, entonces sigue poniendo el último bit a 1.

En pseudocódigo esto se ve así:

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}

Después de eso, si el bit 26 no está puesto, hace un cortocircuito para llamar a zurumode_update y luego termina.

Si está puesto, entonces si 0x4(zuruKeyCheck) no es cero, carga una cadena de formato donde parece que va a imprimir: «ZURU %d/%d».

Recap

Esto es lo que ocurre:

padmgr_HandleRetraceMsgllama al zurumode_callback.Mi opinión es que «manejar el mensaje de retrace» significa que acaba de escanear presiones de teclasen el controlador. Cada vez que escanea, puede llamar a una serie de callbacks diferentes.

Cuando zurumode_callback se ejecuta, comprueba las pulsaciones de teclas (botones) actuales.Esto parece comprobar un botón específico o una combinación de botones.

El último bit del buffer NMI se actualiza en función de los bits específicos de su valor actual, así como del valor de uno de los bytes zuruKeyCheck (0x4(zuruKeyCheck)).

Entonces zurumode_update se ejecuta y comprueba ese bit. Si es 0, la bandera de modo zuru se pone a 0. Si es 1, la bandera de modo se actualiza a 1 o 2 en función de si el bit 28 está activado.

Las tres formas de activar el modo zuru son:

  1. El bit 26 está puesto en 0x3C(osAppNMIBuffer)
  2. El bit 25 está puesto en 0x3C(osAppNMIBuffer), y un controlador está conectado al puerto 2
  3. 0x4(zuruKeyCheck) no es cero

osAppNMIBuffer

Me preguntaba qué era osAppNMIBuffer, Empecé buscando «NMI», y encontré referencias a «interrupción no enmascarable» en el contexto de Nintendo. Resulta que todo el nombre de la variante también aparece en la documentación para desarrolladores de la Nintendo 64:

osAppNMIBuffer es un buffer de 64 bytes que se borra en un reinicio en frío. Si el sistema se reinicia debido a un NMI, este búfer no cambia.

Básicamente, este es un pequeño trozo de memoria que persiste a través de los reinicios suaves. Un juego puede utilizar este búfer para almacenar lo que quiera mientras la consola esté encendida.El juego original de Animal Crossing fue lanzado en realidad en Nintendo 64, por lo que tiene sentido que algo así aparezca en el código.

Cambiando al binario boot.dol (todo lo anterior es de foresta.rel), hay un montón de referencias a osAppNMIBuffer en la función main. Un vistazo rápido muestra que hay una serie de comprobaciones que pueden hacer que varios bits de 0x3c(osAppNMIBuffer) se pongan en marcha con operaciones OR.

Interesantes valores del operando OR a tener en cuenta serían:

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

Recuerda que los bits 25, 26 y 28 son especialmente interesantes: 25 y 26 determinan si el modo zuru está activado, y el bit 28 determina el nivel de la bandera (1 o 2).El bit 31 también es interesante, pero principalmente parece actualizarse en función de los valores de los otros.

Bit 26

En primer lugar: en 800062e0 hay una instrucción ori r0, r0, 0x20 sobre el valor del buffer en 0x3c. Esto establecería el bit 26, que siempre resulta en el modo zuru activado.

Establecer el bit 26

Para que el bit se establezca, el octavo byte devuelto desde DVDGetCurrentDiskID tiene que ser 0x99.Este ID se encuentra al principio de la imagen del disco del juego, y se carga en80000000 en la memoria. Para una versión normal del juego, el ID tiene el siguiente aspecto:

47 41 46 45 30 31 00 00 GAFE01..

Al parchear el último byte del ID a 0x99 se produce lo siguiente al iniciar el juego:

Identificación de la versión del juego 0x99

Y en la consola del sistema operativo se imprime lo siguiente:

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

Todos los demás parches pueden ser eliminados, y la letra D también aparece en la esquina superior derecha de la pantalla de nuevo, pero ninguna de las otras pantallas de depuración se activan.

Bit 25

El bit 25 se utiliza junto con la realización de la comprobación del controlador del puerto 2. ¿Qué hace que se active?

Bit 25 y 28

Este resulta tener la misma comprobación que se utiliza para el bit 28: la versión debe ser mayor o igual que 0x90. Si el bit 26 fue establecido (ID es 0x99), ambos bits también se establecerán, y el modo zuru se activará de todos modos.

Sin embargo, si la versión está entre 0x90 y 0x98, el modo zuru no se activa inmediatamente.Recordando la comprobación realizada en zurumode_callback, sólo se habilitará si el bit 25 está establecido y padmgr_isConnectedController(1) devuelve un valor distinto de cero.Una vez que un controlador está conectado al puerto 2 (el argumento a isConnectedController es cero-indexado), el modo zuru se activa. La letra D y la información de construcción se muestran en la pantalla de título, y… ¡presionando los botones del segundo controlador se controlan las pantallas de depuración!

Algunos botones también hacen cosas además de cambiar la pantalla, como aumentar la velocidad del juego.

zerucheck_key_check

El último misterio es 0x4(zuruKeyCheck). Resulta que este valor se actualiza mediante la gigantesca función compleja mostrada antes:

zerumode_check_keycheck

Usando el depurador de Dolphin, pude determinar que el valor comprobado por esta función es un conjunto de bits correspondientes a las pulsaciones de botón en el segundo controlador. El rastro de la pulsación de botones se almacena en un valor de 16 bits en 0x2(zuruKeyCheck). Cuando no hay ningún controlador conectado, el valor es 0x7638.

Los 2 bytes que contienen las banderas para las pulsaciones de botón del controlador 2 se cargan y luego se actualizan cerca del comienzo de zerucheck_key_check. El nuevo valor se pasa con el registro r4 por padmgr_HandleRetraceMsg cuando llama a la función de devolución de llamada.

Final de comprobación de teclas

Abajo, cerca del final de zerucheck_key_check, hay en realidad otro lugar donde se actualiza 0x4(zuruKeyCheck). No aparece en la lista de referencias cruzadas porque está usando r3 como dirección base, y sólo podemos saber qué es r3 mirando a qué se ajusta cada vez que se llama a esta función.

En 8040ed88 el valor de r4 se escribe en 0x4(zuruKeyCheck). Se carga desde la misma ubicación y luego se XORd con 1 justo antes de eso. Lo que esto debería hacer es alternar el valor del byte (en realidad, sólo el último bit) entre 0 y 1. (Si es 0, el resultado de XOR con 1 será 1.) Si es 1, el resultado será 0. Busque la tabla de verdad para XOR para ver esto.)

finalización de la comprobación de la clave

No noté este comportamiento mientras observaba los valores de la memoria antes, pero intentaré romper en esta instrucción en el depurador para ver lo que está sucediendo. El valor original se carga en 8040ed7c.

Sin tocar ningún botón de los controladores, no llego a este punto de ruptura durante la pantalla del título. Para llegar a este bloque de código, el valor de r5 debe ser 0xb antes de la instrucción de ramificación que le precede (8040ed74). De los muchos caminos diferentes que conducen a ese bloque, hay uno que establecerá r5 a 0xb antes de él, en 8040ed68.

Estableciendo r5 a 0xb

Note que para alcanzar el bloque que establece r5 a 0xB, r0 debe haber sido igual a 0x1000 justo antes. Siguiendo los bloques de la cadena hasta el principio de la función, podemos ver las restricciones necesarias para llegar a este bloque:

  • 8040ed74: r5 debe ser 0xB
  • 8040ed60: r0 debe ser 0x1000
  • 8040ebe8: r5 debe ser 0xA
  • 8040ebe4: r5 debe ser menor que 0x5B
  • 8040eba4: r5 debe ser mayor que 0x7
  • 8040eb94: r6 debe ser 1
  • 8040eb5c: r0 no debe ser 0
  • 8040eb74: Los valores de los botones del puerto 2 deben haber cambiado

Siguiendo la ruta del código

Aquí llegamos al punto en el que se cargan los valores antiguos de los botones y se almacenan los nuevos. Después hay un par de operaciones aplicadas a los valores nuevos y antiguos:

old_vals = old_vals XOR new_valsold_vals = old_vals AND new_vals

La operación XOR marcará todos los bits que han cambiado entre los dos valores. La operación AND entonces enmascara la nueva entrada para desmarcar los bits que no están actualmente establecidos. El resultado en r0 es el conjunto de nuevos bits (pulsaciones de botón) en el nuevo valor. Si no está vacío, estamos en el camino correcto.

Para que r0 sea 0x1000, el cuarto de los 16 bits de rastreo del botón debe haber cambiado.Estableciendo un punto de interrupción después de la operación XOR/AND puedo averiguar qué pulsación de botón causa esto: es el botón START.

La siguiente pregunta es cómo conseguir que r5 comience como 0xA. r5 y r6 se cargan desde0x0(zuruKeyCheck) al principio de la función de comprobación de teclas, y se actualizan cerca del final cuando no se pasa el bloque de código que activa 0x4(zuruKeyCheck).

Hay algunos lugares justo antes de que r5 se ponga en 0xA:

  • 8040ed50
  • 8040ed00
  • 8040ed38
8040ed38
  • 8040ed34: r0 debe ser 0x4000 (se ha pulsado el botón B)
  • 8040ebe0: r5 debe ser 0x5b
  • 8040eba4: r5 debe ser mayor que 0x7
  • lo mismo que antes de aquí en adelante…

r5 debe empezar en 0x5b

8040ed00
  • 8040ecfc: r0 debe ser 0xC000 (A y B pulsados)
  • 8040ebf8: r5 debe ser >= 9
  • 8040ebf0: r5 debe ser menor que 10
  • 8040ebe4: r5 debe ser menor que 0x5b
  • 8040eba4: r5 debe ser mayor que 0x7
  • lo mismo que antes de aquí en adelante…

r5 debe empezar en 9

8040ed50
  • 8040ed4c: r0 debe ser 0x8000 (se pulsó A)
  • 8040ec04: r5 debe ser menor que 0x5d
  • 8040ebe4: r5 debe ser mayor que 0x5b
  • 8040eba4: r5 debe ser mayor que 0x7
  • lo mismo que antes de aquí en adelante…

r5 debe empezar en 0x5c

Parece que hay algún tipo de estado entre las pulsaciones de los botones, y luego hay que introducir una cierta secuencia de combos de botones, que termina con START. Parece que A y/o B vienen justo antes de START.

Siguiendo la ruta del código que pone r5 a 9, surge un patrón: r5 es un valor incremental que puede incrementarse cuando se encuentra el valor correcto de pulsación de botón en r0, o restablecerse a 0. Los casos más extraños en los que no es un valor entre 0x0 y 0xB ocurren cuando se manejan pasos de varios botones, como pulsar A y B al mismo tiempo. Una persona que intenta introducir esta combinación normalmente no va a pulsar ambos botones en el mismo momento en que se produce el rastreo de la almohadilla, por lo que tiene que manejar cualquiera de los botones que se presiona antes que el otro.

Continuando con las diferentes rutas de código:

  • r5 se establece en 9 cuando se presiona la DERECHA en 8040ece8.
  • r5 se pone a 8 cuando se pulsa C-stick derecho en 8040eccc.
  • r5 se pone a 7 cuando se pulsa C-stick izquierdo en 8040ecb0.
  • r5 se pone a 6 cuando se pulsa IZQUIERDO en 8040ec98.
  • r5 se pone a 5 (y r6 a 1) cuando se pulsa ABAJO en 8040ec7c.
  • r5 se pone a 4 cuando se pulsa C-stick up en 8040ec64.
  • r5 se pone a 3 cuando se pulsa C-stick down en 8040ec48.
  • r5 se pone a 2 cuando se pulsa UP en 8040ec30.
  • r5 se pone a 1 (y r6 a 1) cuando se pulsa Z en 8040ec1c.

La secuencia actual es:

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

Se comprueba una condición más antes de la comprobación de Z: mientras el botón recién pulsado debe ser Z, las banderas actuales deben ser 0x2030: los parachoques izquierdo y derecho también deben estar pulsados (tienen valores de 0x10 y 0x20). Además, los botones ARRIBA/ABAJO/IZQUIERDA/DERECHA son del D-pad, no del stick analógico.

El código de trucos

El combo completo es:

  1. Mantén los parachoques L+R y pulsa Z
  2. D-UP
  3. C-DOWN
  4. C-UP
  5. D-DOWN
  6. D-.IZQUIERDA
  7. C-IZQUIERDA
  8. C-DERECHA
  9. D-DERECHA
  10. A+B
  11. INICIAR

¡Funciona! Conecta un controlador al segundo puerto e introduce el código, y aparecerá la pantalla de depuración. Después puedes empezar a pulsar los botones del segundo (o incluso del tercer) mando para empezar a hacer cosas.

Este combo funcionará sin necesidad de parchear el número de versión del juego.Incluso puedes usarlo en una copia normal del juego sin herramientas de trucos o mods de consola. Introducir la combinación por segunda vez desactiva el modo zuru.

Usando el código en una GameCube real

El mensaje «ZURU %d/%d» en zurumode_callback se utiliza para imprimir el estado de esta combinación si la introduces cuando el ID del disco ya es 0x99 (presumiblemente para depurar el propio código de trucos). El primer número es su posición actual en la secuencia, que coincide con r5. El segundo se pone a 1 mientras se mantienen pulsados ciertos botones de la secuencia, que podrían corresponder a cuando r6 se pone a 1.

La mayoría de las pantallas no explican lo que son en la pantalla, así que para averiguar lo que están haciendo tienes que encontrar las funciones que las manejan. Por ejemplo, la larga línea de discos azules y rojos que aparecen en la parte superior de la pantalla son marcadores de posición para mostrar el estado de las diferentes misiones. Cuando una búsqueda está activa algunos números aparecerán allí, indicando el estado de la búsqueda.

La pantalla negra que aparece cuando se presiona Z es una consola para imprimir mensajes de depuración, pero específicamente para cosas de bajo nivel como errores de asignación de memoria y de montón u otras excepciones malas. El comportamiento de fault_callback_scroll sugiere que puede ser para mostrar esos errores antes de que el sistema se reinicie. No he provocado ninguno de estos errores, pero he podido hacer que imprima un par de caracteres basura con algunos NOP. Creo que esto sería muy útil para imprimir mensajes de depuración personalizados más adelante:

JUTConsole garbage characters

Después de hacer todo esto, descubrí que conseguir el modo de depuración parcheando el ID de la versión a 0x99 ya es conocido: https://tcrf.net/Animal_Crossing#Debug_Mode. (También tienen algunas buenas notas sobre lo que las diversas pantallas son, y más cosas que usted puede hacer usando un controlador en el puerto 3.)Por lo que puedo decir, la combinación de trucos no se ha publicado todavía, sin embargo.

Eso es todo por este post. Todavía hay algunas características para desarrolladores que me gustaría explorar, como la pantalla de depuración del mapa y la pantalla de selección del emulador de NES, y cómo activarlas sin usar parches.

Pantalla de selección del mapa

También publicaré artículos sobre la inversión de los sistemas de diálogos, eventos y misiones con el fin de hacer mods.

Actualización: Las diapositivas de la charla que di sobre esto se pueden encontrar aquí.