Desde mi último artículo , para mi gran sorpresa, te interesó. Decidí complementar su resultado, una versión pirateada del juego "Contra (J) [T + Rus_Chronix]", con un poco de funcionalidad, al mismo tiempo que muestra "inyección de código" en NES. Esta vez haré que los jugadores comiencen el juego con el Spreadgun bombeado, para ingresar al juego debes seleccionar el ícono "S", seguido de "R".

Todos los interesados bienvenidos bajo cat.
Tradicionalmente
grabación de video aburrida y tediosa del proceso Y tradicionalmente planificamos la secuencia de acciones.
- Encuentra direcciones
- Descubra el valor de Spreadgun bombeado
- Descubre lo que escribe en estas direcciones al comienzo del juego.
- Reescribir ROM
- Easyway: cambia el valor del arma básica por el spreadgan bombeado
- Hardway: use una inyección de código completo si la forma fácil falla
- guardar el resultado en un nuevo archivo
Para buscar direcciones, utilizamos el método descrito anteriormente, pero recordando que las vidas estaban en direcciones vecinas, solo buscaremos armas del primer jugador con la esperanza de que el segundo jugador esté cerca. Al abrir la ventana "Ram watch" después del inicio del primer nivel, buscamos un valor desconocido. Creo que el arma base está establecida en 0, pero no estoy seguro.
Corremos a lo largo del nivel sin correr hacia adelante, disparamos en todas las direcciones y eliminamos los valores cambiantes. El arma aún no ha cambiado.
Usemos otra opción de ventana, o más bien, en el campo "Comparar con / por", resaltando la frecuencia de radio "Número de cambios", coloque en el campo 0. El tipo de comparación es, por supuesto, "Igual a". El arma aún no cambió.
Entonces, saltando de la opción "igual al valor anterior", a la opción "el número de cambios es 0", puede alcanzar aproximadamente 10,000 direcciones. Cuando más exámenes reduzcan la lista de direcciones demasiado o nada, uno puede avanzar lo suficiente como para eliminar la primera arma.
Una vez recogido, utilizamos inmediatamente el método de búsqueda "no igual al valor anterior", y reducimos aún más la lista buscando "el número de cambios es 1", el arma cambió exactamente una vez.
En el lugar donde recogemos nuestra primera arma, también aparece un amplificador de arma. Una vez seleccionado y recogido, puedes reducir el número de direcciones a 1, pero si no funciona, simplemente mata a tu personaje y así cambia el arma nuevamente. (No te olvides de la búsqueda después de cada cambio).
Con un amplificador de armas, existe el riesgo de que no cambie el valor del arma en sí, sino la bandera en otra parte de la memoria, pero esperemos que los autores del juego hayan guardado memoria e instrucciones. Y tuve la suerte de que la bonificación "R" cambió el valor del arma y la dirección estaba en la coordenada AA 16 . (Podría estar equivocado, pero monitoreé repetidamente el arma del héroe en diferentes versiones del juego Contra, como en todas partes esta dirección era AA 16 ).
También noté que la bonificación "R" aumentó el valor en la dirección en 16 10 o 10 16 , es decir, aumentó en 1 el primer dígito del número hexadecimal.
Después del reinicio en la ventana "Ram watch", se puede ver que el valor base es realmente 00 16 , la bonificación "M" aumentó el segundo dígito en 1 y la bonificación "R" el primero.
Puede optar por un spreadgan, definitivamente se reunirá a este nivel, o puede cambiar el valor de la dirección para ver qué números, qué armas crean. Empíricamente, descubrí que 01 16 es "Ametralladora", 02 16 es "Fuego", 03 16 es "Spreadgun" y 04 16 es "Láser". Cuando ingresa otros valores en el segundo dígito, se producen varios problemas técnicos.
Después de reiniciar el juego e ingresar el valor 1x 16 (donde "x" es una de las opciones aceptables) antes de seleccionar el bono "Rápido", puede descubrir que volver a seleccionar el bono no cambia nada.
Ahora puede reiniciar el juego, iniciar el juego para dos e intentar cambiar las direcciones adyacentes a AA 16 . (Hay dos de ellos, la búsqueda no será larga) Habiendo disparado al segundo jugador, rápidamente descubrí que las armas del segundo jugador realmente están almacenadas cerca en la dirección AB 16 . Y ahora que conocemos las direcciones que nos interesan y el valor que se debe poner allí, es hora de averiguar qué escribe en estas direcciones.
Lanzando un punto de interrupción en el registro de esta dirección, descubrí que la grabación ocurre allí varias veces y una de ellas después de la pantalla de inicio. El siguiente código hace esta entrada:
Dirección | Opcode | Mnemotécnico | Argumentos | Un | X |
---|
C307 | A2 28 | LDX | # $ 28 | ?? | ?? |
C309 | A9 00 | Lda | # $ 00 | ?? | 28 o 29 o ... o F0 |
C30B | 95 00 | STA | $ 00, X | 00 | 28 o 29 o ... o F0 |
C30d | E8 | Inx | | 00 | 28 o 29 o ... o F0 |
C30e | E0 F0 | CPX | # $ F0 | 00 | 29 o 30 o ... o F0 |
C310 | D0 F9 | Bne | $ C30B | 00 | 29 o 30 o ... o F0 |
Si lees cuidadosamente el juego aquí, pone a cero el rango de direcciones de 0028 16 a 00F0 16 , obviamente ambas direcciones de interés para nosotros en el rango. Entonces no habrá una manera fácil. Tendré que usar "Inyección de código" y la solución más simple que veo para averiguar de dónde venimos, redirigir la ejecución a un lugar libre de código y datos en la memoria, escribir allí mi versión del bucle que ocupa todo el rango, excepto las direcciones 00AA 16 y 00AB 16 y devolver el carro ejecución de vuelta. Por cierto, esta es la versión más clásica de la inyección. También puede suponer que llegamos aquí desde la instrucción JSR (Jump to SubRoutine), esto es fácil de verificar con la pila.
¿Cómo funciona la pila?En los procesadores 6502, la pila siempre se encuentra en el rango de direcciones 0100 16 - 01FF 16 para todas las computadoras basadas en este procesador, y crece de una dirección más grande a una más pequeña. Hay un registro separado que apunta a la parte superior de la pila, inicialmente es igual a FF 16 ya que un byte más significativo nunca cambia. El registro en sí mismo siempre indica el byte más alto desocupado con datos útiles.
El depurador del emulador no muestra el valor del registro "Stack Pointer", sino que muestra la dirección a la que se refiere el registro y en este momento es 01F2 16 , los cálculos simples muestran que los últimos datos en la pila son C3 16 y C2 16 , lo que podría conducir Pienso en la dirección C3C2 16 pero 6502 es un procesador del tipo "Little Endian" y, por lo tanto, al ejecutar instrucciones y almacenar la dirección en la memoria o en la pila, el byte menos significativo se escribe primero. Y si la dirección realmente está en la parte superior de la pila, esta es la dirección C2C3 16 . Y esta es la dirección del último argumento de la instrucción JSR, si, de nuevo, es una dirección en absoluto. Es muy fácil de verificar, solo mira lo que está escrito dos bytes arriba de la dirección C2C3 16 .
Dirección | Opcode | Mnemotécnico | Argumentos |
---|
C2C1 | 20 07 C3 | Jsr | $ C307 |
Como puede ver, esta es una instrucción JSR para C307 16 , lo que significa que la suposición de subrutina es correcta.
Ahora necesita escribir el código de inyección correctamente, encontrar un lugar adecuado para él, escribirlo en este lugar y redirigir la instrucción JSR a esta inyección.
Para esto, es muy conveniente usar un bloc de notas, tengo el código de Visual Studio para ello. Todos tienen su propio estilo de escritura, personalmente, soy el primero en escribir una instrucción JSR con su dirección, para saber dónde cambiar y un código de operación completo, para saber qué cambiar.
Después de un par de sangrías, duplico el código del bucle, ya se puede usar sin direcciones, pero es extremadamente útil ver mnemotécnicos con argumentos que no sean códigos de operación, y es útil tomar las instrucciones que siguen a este bucle junto con la dirección para saber dónde regresar de la inyección.
C2C1:20 07 C3 JSR $C307 A2 28 LDX #$28 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 F0 CPX #$F0 D0 F9 BNE $C30B C312:A2 07 LDX #$07
Un par de sangrías a continuación puede escribir el código de la inyección, de hecho, es un duplicado del ciclo con algunas adiciones.
C312:A2 07 LDX #$07 A2 28 LDX #$28 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 AA CPX #$AA D0 F9 BNE -7 A9 13 LDA #$13 95 00 STA $00,X E8 INX E0 AC CPX #$AC D0 F9 BNE -7 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 F0 CPX #$F0 D0 F9 BNE -7
Para mayor claridad, indiqué la sangría en lugar de la dirección de la ubicación del salto.
Si no pudiste leer este códigoAquí es el mismo ciclo, pero dividido en tres partes, en el primer ciclo comparamos el registro X con el valor AA 16 , ya que necesitamos poner a cero todas las direcciones a la dirección 00AA 16 , después de poner 13 16 en el registro A, el valor del spreadgan bombeado y escribir el segundo ciclo a la dirección 00AC 16 a partir de la cual el resto del rango debe ponerse a cero nuevamente. Volvemos a registrar A cero y cero el resto del rango.
Es imprescindible completar las instrucciones de retorno de la inyección al final.
E0 F0 CPX #$F0 D0 F9 BNE -7 4C 12 C3 JMP $C312
Ahora, por conveniencia, prefiero escribir los códigos de operación a continuación en el archivo.
4C 12 C3 JMP $C312 A2 28 A9 00 95 00 E8 E0 AA D0 F9 A9 13 95 00 E8 E0 AC D0 F9 A9 00 95 00 E8 E0 F0 D0 F9 4C 12 C3
Y después de contar los códigos de operación, es fácil descubrir que hay 32 de ellos. Por lo tanto, debe encontrar 20 16 direcciones desocupadas en la ROM. Como regla general, las direcciones desocupadas son espacios grandes de los mismos valores, a menudo ceros o FF 16. Es una pieza tan grande que debe encontrarse, debe tener al menos 35 10 direcciones para que haya algún margen.
En el "Editor Hex" encontré un rango de este tipo en B29E 16- BFFF 16 . Usar el comienzo de dichos sitios gratuitos para inyecciones puede ser peligroso, por lo tanto, le aconsejo que escriba un código de inyección al final. La dirección más conveniente para comenzar la inyección es BFE0 16 , pero esta es la dirección en la memoria de la consola, para averiguar dónde se encuentra en el archivo ROM, haga clic derecho sobre ella y seleccione "Ir aquí en el archivo ROM".
Ahora puede copiar y pegar todo el código de operación aquí (32 valores). Instrucciones finales de cambio de toque
C2C1:20 07 C3 JSR $C307
saltar a la dirección de inyección que tengo es BFE0 16 .
C2C1:20 E0 BF JSR $BFE0
Por supuesto, encontrar el verdadero lugar de instrucción en ROM.
La respuesta a la pregunta de los profesionales desatentos.La dirección de la instrucción JSR se coloca en la pila, por lo tanto, independientemente del lugar del salto, la misma dirección de retorno estará allí, y de la inyección volvemos al código con una instrucción JMP que no afecta a la pila de ninguna manera. Por lo tanto, la instrucción RTS funciona en el mismo lugar que sin inyección, y regresa al mismo lugar que sin inyección. Como resultado, esta inyección no rompe la pila.
PD: para bien, aún debes asegurarte de que después de la muerte los personajes vuelvan a nacer con una extensión extendida, pero estoy seguro de que con el conocimiento adquirido puedes manejarlo tú mismo. Dirigiré mis ojos a otra cosa. Motor de unidad por ejemplo.