Volver
Capítulo 1: Elementos Básicos de la SNES

Por Dark-N: hernaldog@gmail.com
http://darknromhacking.com


Versiones

  • 1.4:
       -Se agrega explicación del registro P necesaria para los próximos capítulos
  • 1.3:
       -Versión revisada por Kaosoft y pasada a html.
  • 1.0:
       -Versión original del tutorial en .doc.


    Introducción

    Esta guía la había estado pensando hacer desde hace mucho tiempo. Si no es por Magno que me ha ayudado en todo, no hubiese sido posible hacerla. Lo mismo que a Jonas. ¡Gracias a los dos!

    En este primer capítulo se estudiarán los elementos básicos de la SNES. Pero partamos con una pregunta básica:

    ¿Qué es el 65816?

    Es un microprocesador hecho por Western Design, el cual es básicamente una actualización de 16 bits del conocido microprocesador 6502 (usado en la NES y en las viejas computadoras como el Commodore 64, etc.) y se usa en los sistemas Apple IIgs.
    El 65816 es el usado por la SNES/Super Famicom. El procesador tiene registros internos de 16 bits y bus de datos de 8 bits.

    ¿Que es la Super Famicom?

    Es la versión japonesa de la SNES (Super Nintendo Entertainment System) americana. El hardware en sí es el mismo y los cartuchos de SF (con una pequeña modificación externa) se pueden jugar en la SNES y viceversa. Lo que las diferencia principalmente es la apariencia.


    Elementos Básicos de la SNES

    Dentro de la SNES tenemos los siguientes componentes:

  • Una CPU con Registros Internos:

       -A: Acumulador
       -X: Índice X
       -Y: Índice Y
       -D: Registro de Página Directa
       -S: Stack Register (Registro de Pila)
       -PBR: Program Bank Register (Banco de Programa)
       -DBR: Data Bank Register (Banco de Programa)
       -PC: Contador de Programa
       -P: Status Register (Registro de Banderas/Registro de Estado)

  • Una Memoria que se subdivide en:

       1. Memoria ROM: aquí esta el código del juego, y los datos iniciales como Tiles (comprimidas o no), música, fonts, etc.
       2. Memoria RAM: aquí se guardan datos temporales como tiles o fonts que se descomprimen en tiempo de ejecución (runtime) si la rom esta comprimida.
       3. Memoria de Video o VRAM: se usa para mostrar en pantalla las tiles, font, maps, sprites, etc.

    NOTA: El orden 1,2,3 es el que siguen los datos para ser mostrados en pantalla.

  • Registros Externos al Procesador: Son puertos mapeados de memoria fijos de Entrada/Salida usados para enviar datos al PPU. Estos registros están asociados a ciertos bancos y direcciones, es decir, de cierta dirección de la memoria principal a otra. Por ejemplo: El registro de los controles o joypad ocupa de la dirección $4000 a la $41FF.

    Los registros externos son:
       -Registros OAM (Object Attribute Memory): Información de sprites en la pantalla; elige una ubicación en VRAM donde el carácter y la medida de los sprites en la pantalla son guardados.
       -Registros de Color.
       -Registros de Transferencia/Dirección de VRAM.
       -Registros de Video: Controla el tamaño de los tiles de background.
       -Registros Contadores/IRQ/VBL/NMI.
       -Registros Windowing: Usados para cortar porciones de pantalla, por ejemplo en el Mario World cuando aparece el círculo de entrada al empezar a jugar.
       -Registros de Joypad/Joystick: Ocupados para manejar los controles, setear número de ellos y los botones.
       -Registros DMA (Direct Memory Access): usados para copiar datos como tiles, de ROM a la VRAM (la pantalla) de forma "rápida" sin pasar por RAM.

    NOTA: Por ahora no hay que entender estos complicados registros, los explicaré en su debido momento.


    Registros Internos: A, X/Y y P

    Estos 4 registros son los esenciales para trabajar con asm al hackear una rom.

    A: Registro Acumulador

    Es el acumulador se usa para cálculo y almacenamiento general. El acumulador puede estar en modo de 8 ó 16 bits. Si está en 8 bits -1 byte-, sólo puede contener valores de 0 a 255 (00 a FF hex) y en 16 bits -2 bytes- valores de 0 a 65535 (0000 a FFFF hex).

    Para saber si el acumulador está en 8 o en 16 bits, hay que chequear el bit nº 5 o "m" del registro de banderas P (este registro se verá con más detalle en otro capítulo). Si el bit 5 llamado m está seteado (es decir m = 1), el acumulador está en 8 bits y si el bit 5 está re-seteado (m = 0) el acumulador está en 16 bits.

    Ejemplos:
    LDA #$AC ; carga A con el valor AC hex. Los valores en ASM SNES se escriben con #$VALOR.
    LDA #$25 ; carga A con el valor 25 hex.
    LDA $1234 ; carga A con el valor que se encuentre dentro de la dirección $1234. Las direcciones en ASM SNES se escriben con $DIRECCION.
    STA $0ABC ; mete lo de A dentro de la dirección $0ABC.

    X,Y: Registros Índices

    Los registros X y Y están asociados al acumulador. Puedes guardar valores dentro de estos registros y luego realizar operaciones matemáticas.
    Estos registros son usados para indexar localizaciones de memoria. ¿Cómo es eso? Si se quiere leer de una dirección de memoria y de allí para abajo (se desea recorrer para revisar cada contenido), se necesita un índice que se vaya aumentando. Es como recorrer un arreglo (si no saben que es un arreglo, revisen documentación de casi cualquier lenguaje de alto nivel como C/C++/PHP/VB6 o vena en google algún manual) .

    Estos dos registros también pueden ser de 8 ó 16 bits, dependiendo del flag (bandera) X del registro P. Si X = 1, entonces los registros (X y Y) estarán en modo de 8 bits. Si X = 0, están en 16 bits.

    LDX #$AC ; carga X con el valor AC hex.
    LDY #$20 ; carga Y con el valor 20 hex.
    LDY $10 ; carga en Y lo que hay en la dirección $10.
    LDA $1234, Y ; caso típico del uso del índice Y, ya que con esto cargamos en A lo que haya en la dirección $1234+Y, si Y vale 8 por ejemplo, cargamos en A lo que haya en $1234+$8 = $123C.

    Sacando Datos de la Memoria

    El acumulador o los índices se pueden llenar con valores de alguna dirección. Por ejemplo:

    LDA $30 ; carga el acumulador con el valor que está dentro de la dirección 30 hex (las direcciones de la SNES siempre están en hexadecimal, así que de ahora en adelante, si hablo de dirección, es en HEX).

    LDX $4A ; carga X con el valor que está dentro de la dirección $4A.

    Introduciendo Datos a la Memoria

    Se puede llenar una casilla de memoria con los valores del acumulador o de los índices.

    STA $3AFF ; guarda lo que está en A en la dirección 3AFF.
    STY $05C0 ; guarda lo que está en Y en la dirección 05C0.
    STX $BB ; guarda lo que está en X en la dirección 00BB.


    Registro de Banderas P

    El registro P es de 8 bits, cada bit representa un Flag (un bit con valor 1 o 0) o Banderas de Estado de la Snes:



    n: Negative (Negativo)
    v: Overflow (Bit de Desbordamiento)
    m: Memory/Accumulator Select (Bit Acumulador)
    x: Index Register select (Bit de Registros índices X e Y)
    d: Decimal
    i: Interrupt(Bit de la Máscara de Interrupción)
    z: Zero (Bit Cero)
    c: Carry (Bit de Acarreo)
    e: Emulation (Emulación)

    n: Negative (Negativo)

    Se pone a 1 si el resultado de la última operación aritmética, lógica o de manipulación de bits fue negativo.

    Ejemplo:
    LDY #$0002 ; se carga Y con 0x0002
    DEY ;Y=0001
    DEY ;Y=0000 -> solo para saber, aquí Z pasa a 1 ya que Y llegó a 0.
    DEY ;Y=FFFF -> aquí N pasa a 1, ya que si al 0 le restamos 1 nos da...FFFF pero se activa N.

    v: Overflow (Bit de Desbordamiento)

    Es parecido al de Acarreo pero actúa con números en complemento a dos y se activa cuando se pasa del mayor numero (el +127 en 1 byte) al menor (-127 en 1 byte).

    m: Memory/Accumulator Select (Bit Acumulador)

    Este bit deja el Acumulador en 8 o 16 bits. Si este bit esta seteado (es decir m = 1) el acumulador esta en 8 bit y si el bit 5 esta re-seteado (m = 0) el acumulador está en 16 bit. Para dejar solo el acumulador en modo de 8-bits usa SEP #$20.

    Esto es así ya que ya que 20 hex es igual a 00100000 en binario y si comparamos 00100000 con el Registro de Flag queda:

    00100000
    nvmxdizc, es decir m = 1

    En resumen:
    Para dejar solo el acumulador en modo de 8 bits usa SEP #$20.
    Para dejar solo el acumulador en modo de 16 bits usa REP #$20.
    Para dejar el registro X/Y y A en modo de 8 bits usa: SEP #$30
    Para dejar el registro X/Y y A en modo de 16 bits usa: REP #$30

    Entonces lo correcto al hacer un LDA #$1234, es hacer justo antes un REP #$20.
    Lo mismo si se quiere hacer un LDA #$05 es hacer antes un SEP #$20.

    x: Index Register select (Bit de Registros índices X e Y)

    Este bit deja permite dejar los registros X e Y en 8 o 16 bits.

    Para dejar el registro X/Y en modo de 8 bits usa SEP #$10.
    Para dejar el registro X/Y en modo de 16 bits usa REP #$10.
    Para dejar el registro X/Y y A en modo de 8 bits usa: SEP #$30.
    Para dejar el registro X/Y y A en modo de 16 bits usa: REP #$30.

    Explicación de porque ese "10":
    REP #$10 ; 10 hex = 10000 bin = bit 5 (X) índices X/Y en modo 16 bit.
    SEP #$10 ; 10 hex = 10000 bin = bit 5 (X) índices X/Y en modo 8 bit.

    d: Decimal

    Permite setear el uso de valores decimales en vez de hexadecimales. Por defecto esta en 0, es decir en modo Hexadecimal:
    d=0 => Modo Hexadecimal
    d=1 => Modo Decimal

    i: Interrupt (Bit de la Máscara de Interrupción)

    La máscara de interrupción I se pone a 1 mediante hardware o software para deshabilitar (enmascarar) todas las posibles fuentes de interrupción, tanto exteriores como interiores.

    z: Zero (Bit Cero)

    Se pone a 1 si el resultado de la última operación aritmética, lógica o de manipulación de bits fue nulo (cero).

    Ejemplo 1:
    LDA #$0000 ; esto hace que Z pase a 1.
    LDX #$0000 ; esto hace que Z pase a 1.

    Ejemplo 2:

    LDY #$0002 ; se carga Y con 0x0002
    DEY ;Y=1
    DEY ;Y=0 -> aquí Z pasa a 1

    c: Carry (Bit de Acarreo)

    Hay veces en que una operación hace que un número se desborde y esto se debe registrar de alguna forma. Cuando un número se desborda el flag de acarreo se pone a 1. El carry puede afectar a algunas operaciones de Suma y Resta, es decir, no afecta a las operaciones de INC y DEC, en cambio si a las de ADC y SBC.

    Para dejar el Carry en 0 se usa la instrucción CLC.
    Para dejar el Carry en 1 se usa la instrucción SEC.

    ¿Pero para que dejar el Carry en 1 o en 0?
    Bueno, aunque esto lo veremos en capítulo 3 y 4, diré que si queremos hacer esta operación matemática: X = A+4, en verdad es un seudo-código que representa la suma de registros. Donde A es el acumulador con valor inicial 2 y X es el registro X vacío.
    Entonces haremos esto:

    ADC #$04 ;sumamos el valor 0x4 a A, aquí el Operando es 0x4
    TAX ; transferimos(copiamos) lo de A hacia X, supuestamente tendríamos que X = A = 2 + 4 = 6, X vale 6 hex.
    Pero aquí hay algo mal ya que ADC es "add with carry", es decir "Sumar con Carry", en otras palabras la "fórmula" para ADC es:

    ADC Operando => A = A + Operand + Carry

    Entonces el Carry AFECTA la Suma, ¿se dan cuenta? A ver, asumamos que el carry esta en 1, entonces si hacemos la misma operación que antes:

    ADC #$04
    TAX

    ¿Cómo queda A finalmente?
    La respuesta es A = 4 + 2 +1 = 7, entonces X = 7. ¿se dan cuenta ahora como afecta a la suma?
    Entonces lo que hay que hacer es dejar el carry en 0 ANTES de la suma, entonces vemos como quedaría:

    CLC
    ADC #$04
    TAX

    ¡Listo! Así nos queda la operación X = A + 4.

    También el Carry afecta a la Resta y su "fórmula" es:

    SBC Operando => A = A – Operand + Carry – 1
    Entonces ustedes pueden ver como se escribiría esta operación: X = A – 3.

    Sigamos adelante entonces.

    e: Emulation (Bit de Emulación)

    Si E = 1, entonces el procesador esta en modo de Emulación 6502 (emula el procesador de NES), Pero si esta en E = 0 el procesador esta en modo Nativo, es decir, en modo SNES.

    Para cambiar a Modo Nativo (E=0) hay que hacer:
    CLC ; limpia el flag de acarreo a 0.
    XCE ; intercambia el bit de emulación con el de acarreo.

    Para cambiar a Modo de Emulación (E=1):
    SEC ; bit de acarreo en 1.
    XCE ; intercambia el bit de emulación con el de acarreo


    Memoria

    La memoria es la encargada de almacenar:

  • Conjunto de instrucciones: Aquí está el código del juego, donde se puede hackear --> Usamos Memoria ROM.
  • Datos fijos que se usan en el juego, como tiles, sonidos --> Usamos Memoria ROM.
  • Datos temporales que usa el juego, como tiles, trozos de código que descomprime, etc. --> Usamos la RAM.
  • Datos finales, listos para ser mostrados en pantalla --> Usamos memoria VRAM.

    La memoria RAM (Random Access Memory, Memoria de Acceso Aleatorio) almacena los datos a procesar. Generalmente es un tipo de memoria volátil (su contenido se pierde cuando se apaga la computadora o la consola en nuestro caso), pero depende del tipo de memoria RAM.

    La memoria RAM en la SNES se subdivide en:

  • WRAM (Working RAM) de 124 KBytes, que es la misma RAM interna de la SNES. Es la que importa, ya que la WRAM se compone de 3 Sub-RAMs que son: LowRAM, HighRAM y Expanded RAM. Estas tres RAMs nos guardarán los datos temporalmente al momento de extraer un texto comprimido, por ejemplo.

  • VRAM (Video RAM) de 64 KBytes, que es la memoria de video que se accede por los registros y es encargada de mostrar los gràficos finales en pantalla.

  • SRAM (Save RAM) de 526 o 512 KBytes, que es la memoria estática que está dentro de un cartucho de SNES; tiene el tamaño un poco mayor que una pila de esas de botón. En ella es en donde se guarda una copia de la RAM en el momento que se salva la partida jugando con una SNES real. Es más o menos como los archivos que contienen las partidas guardadas (no SaveStates) de los emuladores. El contenido de la SRAM no se pierde al apagar la consola, debido a que se encuentra en el cartucho mismo.
    Cuando abres un juego con cualquier emulador, se crea un archivo .srm con el mismo nombre del archivo de la ROM y que contiene lo mismo que contendrá esa memoria RAM de un cartucho de SNES. En realidad se presenta sólo en juegos que tienen opciones para continuar, al estilo de los RPG y ZELDAs. Cuando salvas (usando SaveStates con ZSNES), te queda un archivo .zst, puesto que estos mantienen la copia EXACTA de los dos bancos de RAM y de toda la VRAM en el momento de guardar.

    La memoria ROM (Read Only Memory, Memoria de Sólo Lectura) es una memoria permanente en la que no se puede escribir (viene pregrabada por el fabricante de juegos). Los programas almacenados aquí no se pierden al apagar la consola y tampoco se pueden modificar mientras ésta (la consola) está corriendo. Esta es memoria no volátil.
    Los únicos que modificamos la ROM somo nosotros, los romhackers :)
    La memoria principal la representaremos como un conjunto de direcciones hexadecimales donde cada dirección tiene un dato asociado.


    En la imagen se ve un modelo típico y muy básico de una Memoria de SNES. Se ve una dirección que está compuesta por un BANCO y la DIRECCIÓN. Al conjunto Banco:Dirección se le conoce como Offset. El banco parte de 00 hasta FF y la dirección de 0000 hasta FFFF.

    Abajo se muestra un diagrama más concreto sobre la memoria:


    En la memoria principal se encuentra la ROM y RAM. Estas direcciones varían si la ROM es LoROM (LowROM) o HiROM(HighROM). Las LoRom fueron pensadas para juegos livianos, con poca data y las HiRom fueron hechas para rom con mucha data como algunos RPGs.
    Todas las ROMs que conocemos están dentro de uno de estos 2 grupos. Las LoROM son más livianas, ya que contienen menos información.
    Cada banco en LoROM es de 32 KB, y en HiROM es de 64 KB.
    Como dije más arriba, en la memoria ROM se escribe el código o programa principal de una ROM, es decir que una ROM (un juego cualquiera .smc, .fig, etc.), es la copia exacta del chip ROM de un cartucho de SNES (en este caso). En la RAM se guardan sólo datos temporales. La RAM, como los Registros Internos y Externos, los provee la arquitectura de la SNES, no están dentro del cartucho.


    Mapa de Memoria

    Es una tabla donde se indican los bancos y direcciones de RAM, ROM y Registros Externos. Hay varios documentos en inglés en Zophar's Domain; recomiendo ver uno llamado "SNESMem.txt" hecho por ]SiMKiN[. Sólo pondré aquí lo más importante por ahora, las direcciones de RAM y ROM para una HiROM y LoROM:





    Estos bancos están definidos por la arquitectura de la SNES, no los inventé porque si. Y como dije, es una versión muy simplificada de un mapa que está en Zophar's Domain. Lo que importa es que sepas cómo se "ve" en la memoria la RAM y ROM para entender cuando yo explique algo así: "según esta dirección se accede a la RAM..." o "sacamos este dato de la ROM".



    Gusto a Poco

    1. Si tenemos la instrucción LDA $7EB200, ¿de dónde sacamos datos?
    R: De la RAM, ya que tenemos 7E:B200, y 7E es la dirección de la RAM.

    2. Si tenemos la instrucción STA $CC0150, ¿dónde lo almacenamos?
    En la ROM, pero la memoria ROM varía de posición dependiendo de si es HiROM o LoROM, en este caso la dirección es $0150 y la LoROM no accede en direcciones menores de $8000, sólo desde $8000 a $FFFF para cada banco, por lo que STA $CC0150 lo que hace es almacenar el dato del acumulador en la ROM de una HiROM.

    3. ¿En un programa real es posible hacer STA $1234?
    R: Depende, ya que no se sabe el banco, si fuera un banco de la ROM, como por ejemplo el banco 01: STA $011234, NO seria posible ya que en la ROM no es posible escribir. Pero si fuera un banco de la RAM, como 7E: STA $7E1234, SI sería posible ya que aquí si es posible escribir.


    Volver
    blog comments powered by Disqus
    2003 - 2018    La Web de Dark-N