Mikelan98, Trifindo
Compatible con:
Los videojuegos de Pokémon en Nintendo DS están hechos en 3D, en tanto que utilizan modelos 3D para representar los mapas por los que movemos al jugador. Sin embargo, la quinta generación se lleva el mérito de ser la que más aprovecha las tres dimensiones en cuanto a jugabilidad; esto se debe a la perspectiva mucho más profunda, a unos modelos más voluminosos y, sobre todo, a jugar con la cámara en multitud de escenarios.
Prácticamente podemos decir, salvo algunas excepciones, que el 3D está bastante desaprovechado en los videojuegos de cuarta generación, aunque está ahí y todavía puede usarse (a diferencia de otras plataformas en las que utilizar 3D implicaría cambiar completamente el motor del videojuego). Los modelos y elementos de los mapas dependen completamente del estilo personal de cada desarrollador, mientras que la perspectiva de cámara se encuentra codificada en el Overlay 1 y puede modificarse con relativa facilidad. Pero, ¿qué podemos hacer para mover la cámara en nuestros mapas? Existen muchas formas de implementar algo así en la ROM, pero en cualquier caso necesitamos incorporar código ASM desde cero.
El primer paso para investigar cómo podemos mover la cámara del juego en los mapas es buscar en la memoria RAM los valores que definen los parámetros de la cámara. Para encontrarlos, podemos aprovechar la utilidad de Cheats de DeSmuME y realizar una búsqueda comparativa de valores en la RAM (por ejemplo, moviendo horizontalmente al personaje y buscando valores mayores o menores).
Si lo hacemos correctamente, DeSmuME nos reportará varias coincidencias: tres de ellas pertenecerán a una región de memoria que denominaremos “caja de cámara” y que guarda todos los valores necesarios que definen la cámara del juego.
En la caja de cámara podemos encontrar la posición de la cámara, la posición del target (hacia dónde apunta la cámara) y el vector de cámara. Todos estos parámetros son suficientes para generar cualquier movimiento de cámara, si bien también podemos modificar otros valores con efectos distintos. Las propiedades de cada parámetro son las mismas en Pokémon Platino y en Pokémon HeartGold y SoulSilver.
Ahora bien, no es trivial crear funciones para modificar estos parámetros. Hay que tener en cuenta que estos valores tienen que ser modificados gradualmente, frame a frame, y que no basta con modificarlos reemplazándolos directamente por un valor (lo que haría que la cámara se teletransportase a otro punto). Hay que tener en cuenta esto a la hora de diseñar todas las subrutinas, lo que nos obliga a encontrar alguna subrutina a la que enganchar nuestro código y que se ejecute, como mínimo, una vez cada frame.
La primera opción para integrar los movimientos de cámara en el mapa es utilizar triggers y scripts para llamar a las subrutinas que van a mover las cámaras. Aunque este sistema tiene la ventaja de que no hay que añadir nada que no exista ya (excepto las subrutinas para modificar los valores de la cámara), presenta varios inconvenientes. Por un lado, cada vez que entramos a un trigger, el juego se congela durante unos pocos frames, lo que puede resultar visualmente incómodo. Así mismo, se involucran demasiados elementos en la ROM (mapas, eventos y scripts).
Cualquier otra opción implicaría, a priori, diseñar una nueva especie de formato para determinar los movimientos de cámara. Lo más lógico es que este formato estuviera incluido dentro los archivos de mapas, junto con los archivos de colisiones, los archivos de placas de sonido o los archivos de terreno. Aunque es una opción mucho más complicada que utilizar triggers, una vez estén programadas las subrutinas necesarias será mucho más fácil diseñar los movimientos de cámara para cada mapa.
Si queremos seguir esta última estrategia, es necesario conocer muy bien cómo se cargan los datos de los mapas en la RAM. Cada una de las secciones se carga en direcciones de memoria distintas, es decir, el archivo del mapa no se mantiene en su conjunto en la RAM. Si buscamos secuencias de bytes del archivo de mapa, lo primero que podemos ver en la RAM es el archivo de colisiones, que siempre tiene un tamaño fijo de 0x800 bytes (2048 bytes, correspondientes a 32 tiles x 32 tiles x 2 capas de permisos). Modificando el valor en el header del mapa correspondiente al tamaño del archivo de colisiones no se modifica el tamaño de esta sección, por lo que podemos concluir que este valor es ignorado por el juego, y a las colisiones se les asigna siempre un tamaño fijo de 0x800 bytes. Detrás del archivo vienen escritos distintos punteros, a direcciones de memoria que contienen otras secciones del mapa.
Uno de ellos es el puntero al archivo BDHC, al final del cual vamos a incorporar nuestro archivo de cámaras (se podría incorporar también en los archivos de placas de sonido, pero entonces habría que buscar una estrategia distinta para Pokémon Platino, que no posee esta sección, y no habría compatibilidad entre ambos). Nuestro formato, por tanto, debe diseñarse como un archivo de placas. Insertar nuestro archivo de cámaras como una sección independiente del archivo de mapa sería extremadamente difícil y obligaría a modificar el código que lee todas las demás secciones (debido, sobre todo, a cómo se distribuyen los punteros de los que hemos hablado antes en la memoria).
Para comprobar la posibilidad de añadir datos al final de los archivos BDHC, y de que estos datos luego se puedan encontrar en la memoria RAM junto al archivo de terreno, se probó a modificar un archivo de mapa y unirle una secuencia aleatoria de bytes al final de este (es decir, al final de la sección del BDHC). Sin embargo, en el dump de memoria del juego no se encontró la secuencia de bytes, por lo que estos datos no estaban siendo cargados a la RAM. Esto se debe a que el tamaño de BDHC que va a ser copiado en la RAM está definido por el header del archivo de mapas y el header del propio BDHC. Es necesario incrementar el tamaño del BDHC, en el header del mapa, tantos bytes como tenga el archivo de cámaras que estemos uniendo detrás del BDHC. Igualmente, en el header del BDHC hay que incrementar el tamaño de la Parte U en [bytes del archivo de cámaras / 2], de modo que nuestro archivo de cámara quede incorporado en la Parte U del BDHC. En este caso sí que podemos ver los datos en el dump de la memoria RAM.
Es necesario indicarle al juego dónde comienza nuestro archivo de cámaras dinámicas (a partir de ahora llamado DCAM) dentro del archivo BDHC. Así mismo, debemos indicarle el tamaño de la sección DCAM para incrementar ese valor al tamaño de la parte U y que así se pueda leer junto a esta parte (ya que incrementar manualmente el tamaño de la parte U en el header del BDHC es muy tedioso y puede acabar provocando complicaciones). Para ello utilizaremos los 4 bytes de la firma del archivo BDHC, es decir, los 4 primeros bytes del archivo que contienen la cadena de texto "BDHC". Los dos primeros bytes serán reservados para el tamaño del DCAM, mientras que los dos siguientes serán para la posición relativa del DCAM dentro del contenido del archivo BDHC. Esto nos limita a que el tamaño del BDHC no pueda superar los 65535 bytes (64 KB), aunque es una limitación hipotética, ya que los archivos de terreno más complejos rara vez llegan a los 3 KB. La sección DCAM tampoco podría superar los 64 KB, aunque rara vez va a sobrepasar 1 KB. También hay que tener en cuenta que, en la RAM, al archivo BDHC se le elimina el header, por lo que la posición del archivo DCAM dentro del BDHC hay que contarla a partir del primer byte de la Parte P (justo después del header).
Con un diseño de los archivos establecido, solo queda encontrar una subrutina original en la ROM que cumpla con los siguientes requisitos:
Como requisito previo, también se buscó que la subrutina original trabajase con el puntero de los datos del mapa, de modo que pudieran aprovecharse para nuestra subrutina. Sin embargo, más tarde se descubrió un método para obtener el puntero desde cero en cualquier subrutina a partir de la estructura FIELDSYS_WORK, por lo que ya no fue necesario.
Lo ideal y lógico sería utilizar la propia subrutina de lectura de los BDHCs para usar un branch link (BL) a nuestro código de lectura de los DCAM. Sin embargo, si utilizamos un depurador (como IDA Pro) y colocamos breakpoints en esta subrutina, vemos que no cumple con algunos requisitos. La subrutina que lee el archivo de colisiones sí cumple con todos los requisitos en HeartGold y SoulSilver, así que inicialmente se enlazó el código con ella. Concretamente, en el offset 0x020547F4 (IPKS) en un BL que fue modificado para que apuntase hacia otro sitio donde se encontraba nuestro código. Sin embargo, cuando se probó en Pokémon Platino, la subrutina no se ejecutaba a cada frame, haciéndola inservible.
Finalmente, se escogió la subrutina que se encarga de actualizar la posición de la cámara a cada momento en función de la posición del jugador: una parte de esta función sólo se ejecuta en el overworld, cumpliendo con todos los requisitos propuestos. Sin embargo, al ser una subrutina tan distante a las que inicialmente se plantearon (la de lectura de BDHC y la de lectura de colisiones) el divmap termina siendo impredecible a la hora de ejecutar nuestra subrutina y debe ser recalculado de nuevo. El divmap es un número que identifica en qué mapa estamos, dentro de los 4 posibles mapas que se cargan a la vez en la memoria del juego. Por tanto, solo puede tomar 4 posibles valores distintos (de 0 a 3), tal y como se ve en el esquema siguiente.
El glitch del tweaking en la Cuarta Generación (DP/Pt/HGSS) consiste precisamente en combinar movimientos caóticos lo suficientemente rápidos como para que el divmap no pueda actualizarse de forma correcta y se lean los datos correspondientes a otros mapas.
Como no podemos utilizar el divmap que lee por defecto la rutina de colisiones, tenemos que diseñar nosotros una subrutina que calcule el divmap correcto en el que deberíamos estar, en función de la posición del jugador. Haciendo esto podemos leer correctamente el archivo de cámaras correspondiente al mapa en donde se encuentra el jugador, de entre los 4 mapas cargados en la RAM en cada momento.
En el apartado visual, hay que tener en cuenta que, al modificar la cámara, las normales de los modelos 3D de los mapas van a interaccionar de forma distinta con la cámara. Debido a esto, es común ver cómo algunos polígonos, e incluso sprites, se oscurecen o aclaran un poco conforme se mueve la cámara. De momento no hay forma de solucionar esto, ya que sería extremadamente complejo modificar las normales de un modelo 3D en tiempo real.
Para incorporar esta nueva funcionalidad a la ROM, es necesario expandir el ARM9, tal y como es explica en este tutorial.
El formato final que se ha diseñado para los archivos DCAM se detalla a continuación. Existen dos tipos de placas: aquellas que efectúan una animación de cámara definida en un intervalo de tiempo, y aquellas que modifican la cámara en función de la posición del jugador dentro de la placa. Este último tipo se divide a su vez en dependencia horizontal o vertical.
Lo primero que debe hacer nuestra subrutina, como ya hemos mencionado, es obtener el divmap correcto para que se pueda leer el archivo DCAM correcto correspondiente al mapa en el que estemos. Para ello nos basta con conocer la posición del jugador cada vez que ejecutemos la rutina, aunque han sido necesarios algunos arreglos de código en los casos en los que el jugador se encuentra en el centro de los mapas (ya que el mapa actual cambia de divmap en esos tiles).
Lo siguiente es leer correctamente el archivo DCAM, a partir de los headers que hemos modificado, e incrementar o disminuir los valores de la caja de cámara. En las placas que tienen un movimiento de cámara que depende de la posición, la gradualidad del movimiento de cámara la tenemos de la gradualidad del movimiento del personaje moviéndose por la placa. En las placas que tienen una animación independiente de la posición del jugador, por el contrario, la gradualidad hay que obtenerla limitando el incremento de los valores de la caja de cámara a cada frame. Podemos observar fácilmente con IDA Pro que nuestra subrutina se ejecuta más de una vez por frame, por lo que necesitamos una subrutina auxiliar que actúe como una función booleana, y que nos indique si ha trascurrido 1 frame desde la última vez que se ejecutó.
Así mismo, la subrutina necesita de un pequeño espacio en la memoria RAM para almacenar algunos parámetros (incrementos aplicados a cada valor de la caja de cámara, ángulos aplicados con anterioridad, valores originales de la caja de cámara…). Este espacio se encuentra al final de la subrutina, como un espacio relleno de 00. Esta región de la subrutina no debe alterarse ni ocuparse con otros valores, pues resultaría en un comportamiento inpredecible por parte de la cámara.
A continuación se puede consultar la tabla que indica qué reemplazos hay que efectuar y dónde. Debido a su tamaño, la subrutina de lectura de DCAM se encuentra como un archivo a parte y no como texto.
Así mismo, es necesario modificar ligeramente las subrutinas de lectura de los archivos BDHC para que lean correctamente el segmento DCAM y lo incorporen a la memoria.
Los archivos BDHC que tienen incorporado su DCAM (denominados BDHCam) se pueden elaborar manualmente mediante edición hexadecimal, pero la herramienta PDSMS tiene soporte para generar estos archivos, de modo que se puede hacer de forma más fácil y con referencias visuales. Por otro lado, la importación de los BDHCam en los archivos de mapa se puede hacer con SDSME o DSPRE como si fuesen un archivo BDHC. No es necesario hacer ninguna otra modificación. Los archivos BDHCam son intercompatibles entre HeartGold/SoulSilver (IPKX/IPGX) y Platino (CPUX).
Si se opta por crear un BDHCam manualmente mediante edición hexadecimal, es recomendable que el DCAM quede alineado en 4 bytes, por lo que muchas veces es necesario un padding de 2 bytes (FF FF). Estos bytes también deben contabilizarse para incrementar el tamaño de la sección DCAM en el header del archivo.
Si vas a utilizar esta investigación en tu proyecto, es necesario dar créditos a Mikelan98 y Trifindo. Puedes citar esta investigación copiando la siguiente referencia y colocándola en los créditos del post o página web de tu proyecto:
Mikelan98, Trifindo: Cámaras dinámicas (pokehacking.com/r/20110900) |
Se prohibe la copia total o parcial de esta investigación en otros sitios sin el consentimiento previo de los autores.