Skip to content

lowlevel-1989/krontra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Contra

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.

Radare2 y NES

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.

:::

Buscando las armas por defecto

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

Buscando logica para la asignación de arma al inicio de la partida

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

Como conservar el arma luego de la muerte?

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

Quieres intentarlo?

Pues te dejo algunas tareas para practicar lo aprendido.

  1. Asigna vidas infinitas.
  2. Asigna que todos los power up asignen el mismo arma.
  3. Haz que tu jugador nunca muera aunque reciba disparos.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors