25-11-25
file: Contra (Japan).nes
md5: 0e40bc1b049c16c5d7246cc28399cb5d
sha1: 376836361f404c815d404e1d5903d5d11f4eff0e
cpu: 6502
mapper: 23 konami vrc2/vrc4 c
https://nescartdb.com/profile/view/3986/contra
Para este proyecto utilizaremos unicamente, FCEUX y Radare2 y se puede realizar desde cualquier sistema operativo.
Este documento explica como se descubrieron y modificaron los valores que se muestran en la siguente tabla.
| addrs | offset | Tipo | descripción |
|---|---|---|---|
| 0032 | - | RAM | Vidas |
| 00AA | - | RAM | Arma |
| 07:C2E9 | 1C2F9 | ROM | Asigna 2 vidas en ram |
| 07:C2EF | 1C2FF | ROM | Asigna 29 vidas en ram |
Las vidas la he buscado utilizando el buscador de memoria incluido en FCEUX 2.6.6, posteriormente he agregado un breakpoint de escritura en la dirección 0x0032 para encontrar la logica que asigna las vidas.
Quiero utilizar radare2 para parchear el juego, pero no tengo claro como realizar un cambio del banco de memoria y si realizo una busqueda en el binario no tendremos resultados si esta en otro banco. Lo mejor es trabajarlo en modo raw.
Abrimos el juego en modo raw.
r2 -a 6502 -w -n Contra\ \(Japan\).nes
Ahora vamos a buscar la siguiente instrucción 95 32 STA $32,X que luego de investigar en FCEUX sabemos que es la instrucción que utilizan para asignar la vida en la dirección 0x32.
deberia tener un resultado como el siguiente.
[0x00000000]> /x 95 32
0x0001c301 hit0_0 9532
0x0001c3b7 hit0_1 9532
disassembly la siguiente dirección, un poco antes de la que encontramos para ver donde se asigna el registro A que es el que se cargara en la dirección 0x32.
[0x00000000]> pd 10 @ 0x0001c2f0
0x0001c2f0 03 slo (0xc3, x)
0x0001c2f1 c3 dcp (0x85, x)
0x0001c2f2 851d sta 0x1d
0x0001c2f4 bd05c3 lda 0xc305,x
0x0001c2f7 8539 sta 0x39
0x0001c2f9 a902 lda 0x02
0x0001c2fb a424 ldy 0x24
┌─< 0x0001c2fd f002 beq 0x01c301
│ 0x0001c2ff a91d lda 0x1d
│ ;-- hit0_0:
└─> 0x0001c301 9532 sta 0x32,x
Aqui podemos ver donde se carga a memoria y donde se asigna el valor de las vidas a modificar, el codigo konami se valida con el beq, a nosotros nos interesa cambiar las vidas de 2 a 5 y lo realizamos con el siguente comando.
wx 05 @ 0x0001c2fa
Podemos salirnos con q en radare2 para probar nuestro cambio.
::: info Si queremos saber la dirección que muestra radare desde FCEUX solo tenemos que colocar el cursor sobre la dirección de memoria y dira la dirección en el offset.
:::
Para este proyecto decidí trabajar completamente offline, sin realizar búsquedas en internet. Como no conozco el identificador de cada arma en Contra, tuve que hacer la búsqueda manualmente, revisando los valores desde 00 hasta FF para localizar la dirección de memoria asociada al arma seleccionada y determinar su valor.
El método que me resultó más sencillo fue reemplazar una línea completa por el valor 00 e ir probando hasta identificar en qué línea se encontraba el arma, para luego localizar el byte exacto.
La siguente tabla muestra el valor correspondiente a la dirección 0xAA que corresponde al arma seleccionada.
| Valor | Letra | Descripción |
|---|---|---|
| 00 | - | Gun |
| 01 | M | Machine Gun Falcon |
| 02 | F | Fire Ball Falcon |
| 03 | S | Spread Gun Falcon |
| 04 | L | Laser Falcon |
Para el Rapid Fire Falcon es un caso especial, es simplemente activar el bit de Rapid Fire, sumando 0x10 al valor base del arma seleccionada.
tabla resultante.
| Valor | Letra | Descripción |
|---|---|---|
| 10 | - | Gun |
| 11 | M | Machine Gun Falcon |
| 12 | F | Fire Ball Falcon |
| 13 | S | Spread Gun Falcon |
| 14 | L | Laser Falcon |
Como ya tenemos la dirección de memoria 0xAA podemos activar un breakpoint de escritura en FCEUX y ver que rutina escribe en esa dirección. Asignamos el breakpoint y realizamos un hard reset y le damos continuar al debugger hasta que presionemos el boton start para empezar el juego y ver donde se llama la logica que asigna el arma.
El primer resultado es el que buscamos y es el siguiente:
CPU -> A: 00 X: AA
07:C303 ORA ($07, X)
07:C305 ORA ($00, X)
07:C307 LDX #$28 ; Asigna X = 0x28
07:C309 LDA #$00 ; Asigna A = 0
-> 07:C30B STA $00,X ; RAM[X] = A
07:C30D INX ; Incrementa X en 1.
07:C30E CPX #$F0 ; Compara X == 0xF0
07:C310 BNE $C30B ; Si X no es 0xF0 salta a C30B
La logica anterior es un bucle que asigna a 0 todos los valores de la ram desde 0x28 hasta 0xf0 es decir que esta incluido la vida que es la dirección 0x32 y el arma que es la dirección 0xAA .
¿Que problemas tenemos ahora?
No podemos modificar esta subrutina porque es la encargada de reiniciar la memoria al iniciar el juego. Además, se aprovecha de que el valor del arma por defecto es 0, de manera que al inicio ya está asignada esa arma. Sin embargo, ocurre algo diferente con la vida: esta se asigna a 0, lo que significa que, después de ejecutar esta subrutina, se ejecuta otra que asigna el valor correcto de vida. Por eso, el juego no comienza con 0 vidas.
Esta subrutina ya nos resulta familiar, pues fue el primer cambio que realizamos al inicio de este documento. ¿Y si aprovechamos la asignación de vida, que se ejecuta después del reinicio de memoria, para asignar también el arma en la dirección 0xAA? Eso es precisamente lo que vamos a hacer.
veamos esta subrutina desde radare2.
[0x00000000]> pd 16 @ 0x0001c2f9
┌─> 0x0001c2f9 a905 lda 0x05
╎ 0x0001c2fb a424 ldy 0x24
┌──< 0x0001c2fd f002 beq 0x01c301
│╎ 0x0001c2ff a91d lda 0x1d
└──> 0x0001c301 9532 sta 0x32,x
╎ 0x0001c303 ca dex
└─< 0x0001c304 10f3 bpl 0x01c2f9
0x0001c306 a9c8 lda 0xc8
0x0001c308 853c sta 0x3c
0x0001c30a 853e sta 0x3e
0x0001c30c a900 lda 0x00
0x0001c30e 853d sta 0x3d
0x0001c310 853f sta 0x3f
0x0001c312 60 rts
vemos solo los puntos importantes, para lo que queremos lograr, documento un poco el código
veamos esta subrutina desde radare2.
0x0001c2f9 a905 lda 0x05 ; A = 5
0x0001c2fb a424 ldy 0x24
; Valida el codigo konami.
┌──< 0x0001c2fd f002 beq 0x01c301
│ 0x0001c2ff a91d lda 0x1d ; A = 29 (Konami Code)
└──> 0x0001c301 9532 sta 0x32,x ; [0x32]Vidas = A
El código anterior asigna la cantidad de vidas según se utilice o no el código Konami. En nuestra modificación previa, si no se usa el código Konami se comienza con 5 vidas, y si se usa, se inicia con 29. Es importante recordar que en el juego la vida 0 cuenta, por lo que tener 5 vidas equivale realmente a tener 6.
En nuestro caso, no nos interesa conservar la lógica del código Konami; en su lugar, la sustituiremos para asignar el arma con la que deseamos que el juego inicie. Esto se puede lograr de la siguiente manera.
Iniciar el juego con el arma Machine Gun Falcon, su valor es 1.
0x0001c2f9 a901 lda 0x01 ; A = 1
0x0001c2fb a424 ldy 0x24
0x0001c2fd 95aa sta 0xaa,x ; [0xff]Arma = 1
0x0001c2ff a905 lda 0x05 ; A = 5
0x0001c301 9532 sta 0x32,x ; [0x32]Vidas = A = 5
Como asignamos los cambios a nuestro juego?
primero asignamos el codigo de la dirección 0x0001c2f9, que asigna A = 1
wx a901 @ 0x0001c2f9
segundo modificamos el salto para que ahora guarde el valor de A en la dirección de memoria del arma.
wx 95aa @ 0x0001c2fd
tercero, asignamos el valor de las vidas a A de nuevo en la dirección 0x0001c2ff
wx a905 @ 0x0001c2ff
para validar los cambios podemos utilizar el siguiente comando
[0x00000000]> pd 6 @ 0x0001c2f7
0x0001c2f7 8539 sta 0x39
0x0001c2f9 a901 lda 0x01
0x0001c2fb a424 ldy 0x24
0x0001c2fd 95aa sta 0xaa,x
0x0001c2ff a905 lda 0x05
0x0001c301 9532 sta 0x32,x
Con lo aplicado en los puntos anteriores esto deberia ser bastante sencillo, lo unico que tenemos que hacer para encontrar la logica encargada de asignar el arma tras nuestra muerte es agregar un breakpoint de escritura en la dirección 0xAA desde el emulador ya que ahi es donde se registra el arma asignada, posteriormente nos dejamos matar y vemos la dirección que aparece en el debugger.
deberiamos ver lo siguiente.
07:DA07 LDA A, #$00 ; A = 0
07:DA09 STA $AA, X ; [0xaa]Arma = 0 = Gun
Esta es la logica que asigna el arma cuando nos matan, la documentación de la derecha es agregada por mi como siempre, no es visible en el emulador, para saber cual es la dirección que tenemos que modificar en radare, ya que el emulador nos muestra que esta en el banco 7 y la dirección 0xda07, solo posicionamos el cursor sobre la dirección que queremos y anotamos el offset.
ahora vamos a radare.
[0x00000000]> pd 2 @ 0x1da17
0x0001da17 a900 lda 0x00
0x0001da19 95aa sta 0xaa,x
podemos ver que en la dirección 0x1da17 que encontramos con el emulador, tenemos el codigo a modificar, solo tenemos que cambier el valor que se asigna, con el valor que nosotros deseamos de la siguiente manera.
Asignar el Laser Falcon cuando nos maten, su valor es 4, podemos escribirlo con el siguiente comando.
wx a904 @ 0x0001da17
ya sabemos como validarlo, de igual manera lo explico de nuevo, simplemente vamos a ver el código de la subrutina de nuevo.
[0x00000000]> pd 2 @ 0x1da17
0x0001da17 a904 lda 0x04
0x0001da19 95aa sta 0xaa,x
Pues te dejo algunas tareas para practicar lo aprendido.
- Asigna vidas infinitas.
- Asigna que todos los power up asignen el mismo arma.
- Haz que tu jugador nunca muera aunque reciba disparos.