Registrarse

[3DS] Ingeniería inversa de los recursos de Pokémon The Band of Thieves & 1000 Pokémon (Nintendo 3DS)

Manurocker95

Doctorando en Ingeniería Biomédica & Game Dev
Miembro insignia
¡Buenas una vez más! Vengo a traer otro proyecto que tengo de hacía tiempo de Ingeniería inversa y que dejé en el tintero documentandolo. Bien, se trata de Pokémon The Band of Thieves & 1000 Pokémon (とうぞくと1000びきのポケモン), de Nintendo 3DS. Para el que no lo conozca, es un videojuego de acción y estrategia desarrollado por Marvelous AQL y publicado por The Pokémon Company, lanzado exclusivamente en Japón el 5 de junio de 2014 para Nintendo 3DS. Se distribuyó gratuitamente a través de la Nintendo eShop durante un periodo limitado, hasta el 30 de septiembre de ese mismo año, sin llegar a publicarse oficialmente en Europa ni América.

Como ya he hecho en otros proyectos de ingeniería inversa, esta investigación se ha realizado a través de un parser e importer personalizado para Unity Engine, con el objetivo de interpretar directamente los archivos originales del juego y reconstruir sus recursos dentro del editor. Para ello, ha sido necesario estudiar las cabeceras binarias, los offsets, las estructuras internas y los diferentes métodos de codificación utilizados por NintendoWare CTR, incluyendo las particularidades del hardware gráfico PICA200 de Nintendo 3DS.

Dicho esto, vamos a entrar en la parte técnica y ver cómo están organizados los archivos del juego y qué hemos podido descubrir de cada uno de ellos. Por un lado, tenemso que echar un ojo a los archivos que tenemos en Romfs de la ROM:

1. Estructura General

A diferencia de otros títulos de Pokémon para Nintendo 3DS, The Band of Thieves & 1000 Pokémon (desde ahora PTBOT) no utiliza los formatos habituales de los juegos principales de la saga, sino que se apoya principalmente en NintendoWare CTR, el conjunto de bibliotecas y formatos gráficos, sonoros y de interfaz proporcionado por Nintendo para el desarrollo de videojuegos en esta consola. Esto significa que buena parte de sus recursos internos están organizados en formatos como CGFX, BCLIM, BCFNT, BCSTM o DARC, cada uno con sus propias estructuras binarias, tablas de referencias y sistemas de almacenamiento:

Código:
Extensión / recurso  Firma o estructura                     Función
-------------------  -------------------------------------  ------------------------------------------
.nwmdl               CGFX → DATA / DICT / CMDL              Modelos y materiales
.nwanm, .nwanmm      CGFX → CANM                            Animación esquelética / material
.nwptl               CGFX                                   Recursos de partículas
.nwlyt               DARC                                   Archivo de diseño UI con miembros internos
.bclim               CLIM al final + imag                   Texturas de interfaz
.bcfnt               CFNT / FINF / TGLP                     Fuentes y atlas de glifos
.bcstm               CSTM / INFO / SEEK / DATA              Audio de streaming
.btxt                Sin magic fijo; cabecera y entradas    Bancos de texto japonés
Recursos de sonido   CSAR                                   Archivo de sonido
Base de datos        ICDB                                   Datos internos
Binarios de shader   DVLB                                   Código gráfico
Iconos en bruto      Dimensiones inferidas del tamaño       Gráficos de iconos
Como se puede ver, y como ya he mencionado anteriormente, estamos ante formatos completamente diferentes a los que solemos encontrar en los juegos principales de Pokémon para Nintendo 3DS, como Pokémon X/Y o Pokémon Sol/Luna. En realidad, los recursos de PTBOT se parecen mucho más a los utilizados por otros títulos desarrollados con las bibliotecas NintendoWare CTR, que proporcionaban a los estudios una serie de formatos y herramientas estandarizados para trabajar con el hardware de la consola.

¿Y por qué se utilizan estos formatos? Básicamente, porque NintendoWare CTR facilitaba considerablemente el desarrollo para Nintendo 3DS. En lugar de tener que diseñar un sistema propio para almacenar modelos, animaciones, texturas, sonidos o interfaces, los desarrolladores podían aprovechar las herramientas oficiales de Nintendo, que ya estaban preparadas para las características y limitaciones de la consola. Por ejemplo, CGFX permite organizar modelos, materiales y animaciones, mientras que BCLIM almacena imágenes en formatos compatibles con el procesador gráfico PICA200, incluyendo técnicas de compresión y organización de píxeles adaptadas al hardware.

Esto también explica por qué encontramos tantos formatos diferentes dentro de la RomFS: cada uno está especializado en un tipo de recurso y responde a unas necesidades concretas. No significa que PTBOT utilice un motor de Nintendo como tal, sino que sus desarrolladores aprovecharon las bibliotecas y herramientas de NintendoWare CTR para construir su propio juego.

Desde el punto de vista de la ingeniería inversa, esto tiene una ventaja interesante: parte del conocimiento obtenido al estudiar estos formatos puede reutilizarse para investigar otros juegos de Nintendo 3DS que también empleen NintendoWare CTR, aunque cada título pueda introducir sus propias modificaciones o estructuras adicionales.

2. Contenedores CGFX: modelos y animaciones

Sabiendo que NintendoWare CTR utiliza el contenedor CGFX para almacenar recursos gráficos, podemos comenzar a analizar su estructura interna. Lo primero que encontramos es una cabecera identificada por la misma firma CGFX, que nos permite reconocer el archivo y obtener información sobre su organización. A partir de esta cabecera podemos localizar el bloque DATA, que contiene una serie de referencias a diccionarios DICT. Estos diccionarios funcionan como índices: cada entrada identifica un recurso mediante su nombre y una referencia a su posición dentro del archivo.

Lo interesante es que estas referencias se almacenan habitualmente como offsets relativos, es decir, desplazamientos calculados desde una posición concreta de la estructura, en lugar de direcciones absolutas. Por tanto, para localizar correctamente cada recurso, necesitamos interpretar el desplazamiento y calcular su posición real dentro del archivo.

Una vez resueltas estas referencias, podemos establecer una representación simplificada de la organización interna de CGFX:

Código:
CGFX
└── DATA
    ├── DICT: Models ──────────► CMDL
    │                            ├── SOBJ (shapes / meshes)
    │                            ├── Skeleton / bones
    │                            └── Material references
    ├── DICT: Textures ────────► TXOB
    ├── DICT: Materials ───────► MTOB
    ├── DICT: SkeletalAnims ───► CANM
    └── Otros diccionarios
¿Por qué se organiza de esta manera?

Principalmente, porque un modelo 3D no está formado únicamente por sus vértices y polígonos. También necesita materiales, texturas, información de huesos y, en muchos casos, animaciones. Al separar estos elementos en estructuras independientes y relacionarlos mediante referencias, el formato permite organizar múltiples recursos dentro de un mismo contenedor sin duplicar necesariamente toda su información.

Por ejemplo, un objeto CMDL representa un modelo, pero para reconstruirlo necesitamos interpretar sus objetos de geometría SOBJ, sus referencias a materiales MTOB y las texturas TXOB que correspondan. Si además dispone de un esqueleto, debemos reconstruir la jerarquía de huesos y sus transformaciones. Algo similar ocurre con las animacionesCANM, que almacenan información sobre los elementos animados, las curvas, los fotogramas clave y sus métodos de interpolación. Estos datos deben interpretarse y asociarse correctamente con los objetos correspondientes para reproducir el movimiento original.

Por tanto, el proceso de importación no consiste simplemente en extraer un modelo del archivo CGFX, sino en recorrer sus diccionarios, resolver las referencias internas y reconstruir las relaciones entre los diferentes recursos para convertirlos en objetos que Unity o el motor de juegos o render, pueda utilizar.

3. Texturas PICA200 y orden Morton

Siguiendo la premisa mencionada, las texturas se organizan en bloques de 8 × 8 píxeles mediante una distribución Morton (tiled/swizzled), optimizada para el procesador gráfico PICA200 de Nintendo 3DS. Además, algunas utilizan compresión ETC1 o ETC1A4 para reducir el consumo de memoria, quedando de la siguiente manera:

Código:
Código PICA  Formato   Bits por píxel
-----------  --------  --------------
0            RGBA8     32
1            RGB8      24
2            RGBA5551  16
3            RGB565    16
4            RGBA4     16
5            LA8       16
6            HILO8     16
7            L8        8
8            A8        8
9            LA4       8
10           L4        4
11           A4        4
12           ETC1      4
13           ETC1A4    8
BCLIM: texturas con cabecera al final

Otra peculiaridad de los recursos gráficos de PTBOT la encontramos en los archivos .bclim, utilizados principalmente para almacenar texturas de interfaz. A diferencia de otros formatos, estos archivos almacenan los datos gráficos al principio y la cabecera al final, concretamente en los últimos 0x28 bytes del archivo. Esta cabecera contiene las firmas CLIM e imag, además de información como las dimensiones de la imagen, su formato y el tamaño de los datos gráficos.

Código:
Offset    Tipo     Campo

+0x00     char[4]  CLIM
+0x04 u16 BOM (0xFEFF)
+0x0C u32 Tamaño total del archivo
+0x14 char[4] imag
+0x1C u16 Ancho visible
+0x1E u16 Alto visible
+0x20 u32 Formato CLIM
+0x24 u32 Tamaño de los texels
Además, las dimensiones utilizadas para almacenar las texturas pueden ser mayores que las dimensiones visibles, generalmente ajustándose a potencias de dos y respetando un mínimo de 8 × 8 píxeles. Esto permite adaptar los datos a la organización de memoria del procesador gráfico PICA200, aunque parte de la textura no llegue a mostrarse en pantalla.

Esta estructura se puede observar en al menos 535 recursos BCLIM encontrados dentro de la RomFS del juego.

4. Archivos de interfaz DARC / NWLYT

En este juego, determinados ".nwlyt" son contenedores DARC en little-endian. Su cabecera ocupa 0x1C bytes y contiene la información necesaria para localizar las tablas de entradas y los datos.

Código:
Campo         Tipo / tamaño  Descripción
------------  -------------  ----------------------
Magic         char[4]        `darc`
BOM           u16            `0xFEFF`
Header size   u16            `0x1C`
Version       u32            Versión del contenedor
File size     u32            Tamaño total
Table offset  u32            Inicio de tabla
Table size    u32            Longitud de tabla
Data offset   u32            Inicio de datos
Cada entrada de tabla ocupa 12 bytes. El primer u32 combina un offset de nombre de 24 bits con el indicador de directorio 0x01000000. Los dos campos siguientes representan (offset, size) para archivos o (parentIndex, endIndex) para directorios. Los nombres se leen de un pool UTF-16LE.

Código:
darc (.nwlyt)
├── Header (0x1C)
├── Entry table (12 bytes × N)
├── UTF-16LE name pool
└── File data
    ├── timg/*.bclim
    ├── *.bclyt
    ├── *.bclan
    └── Otros miembros
Hay que tener encuenta aun así que extraer imágenes de un archivo no equivale a reconstruir completamente el layout y sus animaciones.

5. Bancos de texto BTXT

Los archivos data/jpn/text/*.btxt son especialmente interesantes porque no tienen una firma ASCII fija. El parser valida la forma de su cabecera y las tablas internas.

Código:
Offset  Tipo          Significado observado
------  ------------  ---------------------------------------------------
0x00    u32           0
0x04    u32           Valor no identificado (0xFF121024 / 0xFF100527)
0x08    u16           Número de entradas
0x0A    u16           Segundo contador, igual al anterior
0x0C    entry[8] × N  Tabla de entradas
Cada entrada de 8 bytes:

Código:
Offset relativo  Tipo  Campo
---------------  ----  -------------------------------------------------------------
+0x00            u32   Flag (observado como 1)
+0x04            u8    MetricA (métrica similar a anchura; significado incompleto)
+0x05            u8    LineCount (líneas separadas por LF)
+0x06            u8    MetricC (desconocido)
+0x07            u8    Índice de un byte
La representación de texto es UTF-16 y puede incluir unidades de control. PTBOT conserva tres variantes: Raw (texto original), Markup (controles legibles y reversibles) y Plain (presentación simplificada). Los controles de ruby se representan como {ruby:base|reading} y {ruby1:base|reading} para los códigos U+1700 y U+1701; los controles desconocidos se conservan como {XXXX}.

6. Fuentes, audio y recursos adicionales

Fuentes: PTBOT utiliza archivos BCFNT identificados mediante la firma CFNT, que contienen estructuras como FINF y TGLP. Estas permiten almacenar información tipográfica y atlas de glifos, donde se encuentran las representaciones gráficas de los caracteres utilizados por el juego.

Audio: Los archivos BCSTM utilizan la firma CSTM y se organizan en bloques como INFO, SEEK y DATA. Estos almacenan la configuración del sonido, la información necesaria para su reproducción y los datos de audio, incluyendo características como los puntos de repetición (loop). También encontramos recursos CSAR, utilizados para organizar archivos y referencias de sonido.

Otros recursos: Además de los formatos anteriores, encontramos estructuras como ICDB, relacionadas con datos internos del juego, y DVLB, utilizadas para almacenar código de shaders destinado al procesador gráfico PICA200. También existen iconos almacenados directamente como datos gráficos, sin necesidad de utilizar un contenedor convencional.

7. Resultados

Como una imagen vale más que mil palabras, aquí está el resultado de toda esta investigación:

npc_01_app_L.png
npc_01_atk_L.png
npc_01_std_L.png


Y nada, eso es prácticamente todo. Si alguien quiere juguetear con lso archivos, no tenéis que bajaros la ROM y empezar a sacar modelos y sprites.
 
Arriba