NightFox & Co - Desarrollo de Homebrew para NDS
Blog dedicado a los proyectos presentes y futuros desarrollados para la pequeña Nintendo DS
Aficiones: desarrollo, homebrew, nintendo ds, programación, videojuegos
Día 1. Carga de fondos “tileados” desde FAT.
Introduccion
Bueno, pasemos a las cosas serias. Como doy por supuesto que ya sabemos programar mínimamente bien en C (cuidado, que yo no soy ni de lejos un lumbreras), pasaremos a las cosas que se salen de lo normal en la DS, como la carga y manejo de fondos, sprites, interface de entrada y sonido. Lo demás como es C de toda la vida, pues casi no tiene sentido que lo explique yo aquí, habiendo cientos de tutoriales (mucho mejores de los que podría yo realizar) sobre programación en C.
El tema que tocare hoy es referente a la carga de fondos, usando uno de los modos nativos que soporta el motor 2D de la DS, los gráficos tileados. Los gráficos tileados no son más que gráficos que han sido previamente convertidos en un formato especial, compuesto de 3 partes, para ahorrar espacio. En el caso de la DS, estos gráficos se componen en el archivo de tiles, donde la imagen se ha cortado en bloques de 8x8 pixeles (1 tile) y se han guardado todos juntos. El siguiente archivo necesario es el de mapa de tiles (MAP) que indica a la DS donde colocar cada tile del otro archivo en la pantalla. Esto es útil de cara a ahorrar memoria (la DS tiene poco mas de 512kb de memoria de video para ambas pantallas), ya que si en nuestro fondo se repite mucho un tile (un ladrillo por ejemplo), el grafico de este ladrillo solo se guarda 1 vez y se carga 1 sola vez en la VRAM y luego el archivo de mapa le indica a la DS cuantas veces debe de colocar ese ladrillo en pantalla y donde, con lo cual, si en pantalla tenemos 20 ladrillos, en memoria solo habrá cargado 1, pero la ds gracias al sistema de tiles nos mostrara los 20, con el consecuente ahorro de recursos. La tercera y última parte del sistema de tileado es el archivo de paleta, que indica que colores usara nuestro fondo. Debéis de tener en cuenta que este tipo de fondos serán siempre a 256 colores (color indexado) para poder usarlos.
La última release del devKitPro incluye una fabulosa utilidad llamada GRIT, la cual convertirá nuestros archivos de imagen en los 3 archivos que necesitamos para que la DS “lo entienda”.
La mayoría de ejemplos sobre gráficos tileados se basan en integrar los gráficos una vez convertidos dentro de la propia ROM generada. Cosa buena, en una sola ROM todo solucionado y funcionando. Cosa mala, como tu proyecto ocupe mas de 4mb, olvídate de que funcione, dado que esa es la RAM que tiene disponible la DS para trabajar. Así que para evitar problemas futuros, más vale hacer las cosas ya bien desde el principio y aprender a cargar los fondos, sprites y demás desde la FAT (sistema de archivos de vuestra flashcard) y tener solo en la ROM el ejecutable, con lo cual tendremos más flexibilidad a la hora de trabajar.
Después de esta pequeña introducción, hoy vamos a aprender a:
1. Inicializar el sistema FAT y cargar archivos desde ella a la RAM
2. Inicializar el motor 2D de la DS y cargar en la VRAM un fondo
Ademas también os enseñare a convertir los graficos al formato “tileado” con la utilidad GRIT.
Codigo.
Este es el código de la lección de hoy:
//
// Leccion 1
// Carga de fondos "tileados" desde FAT
// Ejemplo por NightFox
//
// Includes NDS
#include <nds.h>
// Includes FAT
#include <fat.h>
// Includes comunes
#include <stdio.h>
#include <string.h>
#include <unistd.h>
// Main
int main(void) {
// Inicializa el sistema de interrupciones
irqInit();
// y habilita el vblank para poder usar swiWaitForVblank()
irqEnable(IRQ_VBLANK);
// Inicializa el sistema FAT
consoleDemoInit(); // Inicializa la consola de texto
iprintf("Inicializando FAT.\n");
if (fatInitDefault()) { // Intenta inicializar la FAT
// Conseguido, continua
} else {
// Fallo. Deten el programa
iprintf("Fallo en la inicializacion.\n");
iprintf("Programa detenido.\n");
// Bucle infinito. Fin del programa
while(1) {
swiWaitForVBlank();
}
}
// Define los Buffers para almacenar los graficos
char* BUFFER_TILES; // Buffer para alamacenar los tiles
char* BUFFER_MAP; // Buffer para alamacenar el mapa
char* BUFFER_PAL; // Buffer para almacenar la paleta
// Inicializa los buffers (asi evitamos cargar los datos vete a saber tu donde)
BUFFER_TILES = NULL;
BUFFER_MAP = NULL;
BUFFER_PAL = NULL;
// Variable para almacenar el path y el nombre de archivo
char filename[256];
// Carga el archivo de TILES en la RAM
long tiles_size = 0; // Alamcenamos el tamaño del archivos de tiles
FILE* tiles_file; // Referencia al archivo de tiles
sprintf(filename, "leccion01/fondo.img"); // Guarda el path + archivo
tiles_file = fopen(filename, "rb"); // Y abre el archivo (modo lectura)
if (tiles_file) { // Si el archivo existe
// Obten el tamaño del archivo
fseek(tiles_file, 0, SEEK_END);
tiles_size = ftell(tiles_file);
rewind(tiles_file);
// Reserva el espacio en RAM
BUFFER_TILES = (char*) calloc (tiles_size, sizeof(char));
if (BUFFER_TILES == NULL) { // Si no hay suficiente RAM libre
iprintf("RAM insuficiente.\n");
iprintf("Archivo: %ld bytes\n", tiles_size);
while(1) {
swiWaitForVBlank();
}
}
// Lee el archivo y ponlo en la RAM
fread (BUFFER_TILES, 1, tiles_size, tiles_file);
} else { // Archivo no encontrado
iprintf("Archivo no encontrado.\n");
while(1) {
swiWaitForVBlank();
}
}
fclose(tiles_file); // Cierra el archivo
swiWaitForVBlank(); // Espera al cierre del archivo
// Carga el archivo de MAP en la RAM
long map_size = 0; // Alamcenamos el tamaño del archivos de tiles
FILE* map_file; // Referencia al archivo de tiles
sprintf(filename, "leccion01/fondo.map"); // Guarda el path + archivo
map_file = fopen(filename, "rb"); // Y abre el archivo (modo lectura)
if (map_file) { // Si el archivo existe
// Obten el tamaño del archivo
fseek(map_file, 0, SEEK_END);
map_size = ftell(map_file);
rewind(map_file);
// Reserva el espacio en RAM
BUFFER_MAP = (char*) calloc (map_size, sizeof(char));
if (BUFFER_MAP == NULL) { // Si no hay suficiente RAM libre
iprintf("RAM insuficiente.\n");
iprintf("Archivo: %ld bytes", map_size);
while(1) {
swiWaitForVBlank();
}
}
// Lee el archivo y ponlo en la RAM
fread (BUFFER_MAP, 1, map_size, map_file);
} else { // Archivo no encontrado
iprintf("Archivo no encontrado.\n");
while(1) {
swiWaitForVBlank();
}
}
fclose(map_file); // Cierra el archivo
swiWaitForVBlank(); // Espera al cierre del archivo
// Carga el archivo de PAL en la RAM
long pal_size = 0; // Alamcenamos el tamaño del archivos de tiles
FILE* pal_file; // Referencia al archivo de tiles
sprintf(filename, "leccion01/fondo.pal"); // Guarda el path + archivo
pal_file = fopen(filename, "rb"); // Y abre el archivo (modo lectura)
if (pal_file) { // Si el archivo existe
// Obten el tamaño del archivo
fseek(pal_file, 0, SEEK_END);
pal_size = ftell(pal_file);
rewind(pal_file);
// Reserva el espacio en RAM
BUFFER_PAL = (char*) calloc (pal_size, sizeof(char));
if (BUFFER_PAL == NULL) { // Si no hay suficiente RAM libre
iprintf("RAM insuficiente.\n");
iprintf("Archivo: %ld bytes", pal_size);
while(1) {
swiWaitForVBlank();
}
}
// Lee el archivo y ponlo en la RAM
fread (BUFFER_PAL, 1, pal_size, pal_file);
} else { // Archivo no encontrado
iprintf("Archivo no encontrado.\n");
while(1) {
swiWaitForVBlank();
}
}
fclose(pal_file); // Cierra el archivo
swiWaitForVBlank(); // Espera al cierre del archivo
// Debug (Informa del tamaño de los archivos)
iprintf("Tiles: %ld bytes\n", tiles_size);
iprintf("Map: %ld bytes\n", map_size);
iprintf("Pal: %ld bytes\n", pal_size);
iprintf("Carga ok.\n");
// Inicializa el motor 2D en "Modo 0" en la pantalla principal y habilita el fondo nº0
videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE);
// Define el banco de VRAM "A" para usarlo con el fondo
vramSetBankA(VRAM_A_MAIN_BG);
// Habilita el fondo nº0
// BgType_Text8bpp indica que usaremos un fondo "Tileado" a 256 colores
// BgSize_T_256x256 indica que el tamaño del fondo es de 256x256
// Alamcena la ID exclusiva del fondo en "fondo"
int fondo = bgInit(0, BgType_Text8bpp, BgSize_T_256x256, 0,1);
// Usa el memcpy para copiar los datos de los Buffers en RAM a la VRAM
// bgGetGfxPtr(fondo) - Obten el puntero donde almacenar los TILES en VRAM
// bgGetMapPtr(fondo) - Obten el puntero donde almacenar el MAP en VRAM
memcpy(bgGetGfxPtr(fondo), BUFFER_TILES, tiles_size);
memcpy(bgGetMapPtr(fondo), BUFFER_MAP, map_size);
memcpy(BG_PALETTE, BUFFER_PAL, pal_size);
// Bucle (repite para siempre)
while(1) {
swiWaitForVBlank(); // Espera al sincronismo vertical
}
return 0;
}
He comentado el código lo suficiente como que sea auto explicativo (espero), de todas maneras si tenéis dudas, me dejáis un comentario en el blog.
La primera parte del código inicializa lo básico de la DS, interrupciones y demás. A continuación intentamos iniciar y acceder al sistema FAT de nuestra flashcard, de lograrlo, el programa continua. En caso de no poder ser, nos informa del error y detiene la ejecución del programa. A continuación declaramos e inicializamos 3 Buffers de memoria para poder almacenar en RAM los 3 archivos de los que se compone nuestro fondo. Acto seguido, procedemos a la carga de los archivos en si, esta parte de código, intenta abrir el archivo, devolviendo error si no se encuentra, obtiene el tamaño de dicho archivo y lo almacena en una variable, muy importante para más tarde poder dimensionar el buffer en RAM y su posterior copia en VRAM y finalmente, lee el archivo en el buffer correspondiente. He puesto un par de rutinas extras para verificar si ha sido correcta la carga del archivo en RAM y evitar disgustos.
La ultima parte simplemente inicializa el motor 2D de la DS y le dice que queremos iniciar una capa de fondo en la pantalla superior, usando la capa 0 (la más alta) con un fondo de 256x256 pixeles a 256 colores. Llegados a este punto, debéis saber que el hardware de la DS, sin hacer “trampas” solo soporta fondos desde 256x256 pixeles hasta 512x512 pixeles y sus variantes (512x256, 256x512). Para cargar fondos más grandes, ya tendremos de rascar código, pero eso será otro día).
El resultado del programa del ejemplo nos mostrara en la pantalla superior el fondo de muestra que hemos cargado y un texto de debug en la inferior, indicándonos el resultado de la inicialización del sistema FAT y el tamaño en bytes de los 3 archivos cargados.
Recordar en usar el MAKEFILE incluido en mi ejemplo, que ha sido modificado para usar las librerías FAT.
Uso de la utilidad GRIT.
Aquí aprenderemos a convertir un archivo BMP indexado a 256 colores a los 3 archivos que necesitaremos para nuestro ejemplo. La utilidad GRIT se ejecuta desde línea de comandos, así que para simplificar la cosa, he creado un archivo BAT que le pasa los parámetros más comunes para la conversión de los archivos BMP a TILES y de paso renombra los archivos generados y limpia los más necesarios. En la carpeta GRIT del ejemplo tenéis todo lo necesario.
Para convertir el archivo “fondo.bmp” a los 3 archivos necesarios, simplemente ejecutar desde línea de comandos:
convert fondo
Esto generara en la misma carpeta 3 archivos, “fondo.img”, que contiene los tiles, “fondo.map”, que contiene el mapa y “fondo.pal” que contiene la paleta.
Si queréis trastear vosotros con las posibilidades de esta utilidad, ejecutar la aplicación GRIT.EXE y veréis todos los parámetros disponibles. Mi BAT usa los siguientes:
grit.exe [archivo] -g -m -p -gB8 –ftb
Podéis también ver como está hecho el BAT simplemente abriendo el archivo “convert.bat”
Prueba del ejercicio.
Si el ejemplo ha compilado bien (debería si has usado el material adjunto a este tutorial), solo queda probarlo en la DS. Por algún bug en el emulador IDEAS, de momento no es posible probarlo en el emulador, así que tocara probarlo con la consola y el flashcard.
Crea una carpeta en la raíz de tu flashcard llamada “leccion01” y copia dentro el binario compilado (leccion01.nds) y parchéalo con DLDI si es necesario (en los flashcards mas reciente, no lo es) y los 3 archivos del fondo (fondo.img, fondo.map y fondo.pal).
Si todo es correcto, aparecerá el fondo en la pantalla superior (abre en tu PC el archivo fondo.bmp para compararlo) y en la pantalla inferior texto informando de la carga y tamaño de los archivos. Te animo a probar este ejemplo con tu propio fondo, el único punto a respetar es que sean BMP de 256x256 pixeles indexados a 256 colores.
Y esto es todo por hoy, espero que os haya gustado el tutorial, haya sido claro y de provecho para vosotros.
Aquí tenéis el enlace con todo el material necesario para esta lección
Proyecto en Visual C++: http://www.mediafire.com/?uctiqhmwfmd
GRIT con ejemplos: http://www.mediafire.com/?nnrz4mrmdgz
Tambien podeis descargar el WORD de este tutorial aquí: http://www.mediafire.com/?tynmyyzouhy
Un cordial saludos a todos
NightFox
Blog dedicado a los proyectos presentes y futuros desarrollados para la pequeña Nintendo DS
Aficiones: desarrollo, homebrew, nintendo ds, programación, videojuegos

17 Comentarios:
Portada El Bosque Dormido (20 Marzo)
21 de Marzo de 2009 • 01:14 — LoganKellerJoder, pedazo de entrada y sin comentarios... no culpo a nadie ya que pocos sabrán de que estas hablando... pero da pena ver trabajo sin alguna recompensa. Por esta entrada tan currada, te casco la medallica ^^ A ver si cojo la DS y le saco algo de provecho que esta muerta de risa en el armario xD
¡Saludos y sigue así!
Muchisimas gracias por tu
21 de Marzo de 2009 • 02:09 — KnightFoxWow
21 de Marzo de 2009 • 03:10 — MarkovEl código que muestras arriba me recordó que el próximo semestre voy a aprender sobre ello. ( Ya ví la primera parte sobre lo básico en C++) y de verdad es muy interesante. Por eso entiendo más o menso, lástiam que no tenga Ds en mano. Sino me pondría a escudriñarla.
Un saludo.
Gracias por el tutorial. Hay
19 de Abril de 2009 • 16:52 — Invitado (invitado)El problema es que tienes
19 de Abril de 2009 • 17:41 — KnightFoxEl problema es que tienes que configurar por separado el motor de renderizado de la DS, es decir, el modo de video y los bancos de VRAM por separado. (si, tienes razon hay que ser g*li*ollas para diseñarlo asi, pero bueno). Si al motor le dices que vas usar el modo LCD (escritura directa al framebuffer) pero no se lo cuentas a los banco de memoria, pues te quedas igual y no funciona.
En cuanto a la documentacion es la que hay y googleando mucho, encuentras algun ejemplo de alguna alma caritativa que explica todo lo que los señores del DevkitArm dejan al aire (suponen que tenemos poderes increible de adivinacion o que intuimos como funcionan las cosas sin ejemplos).
Aunque no te lo creas, me he tenido que pegar con esa poca documentacion buscar por el google como un loco y mucho ensayo y error... asi que imagina para en un mes sacar lo que he sacado...
Saludos
Nightfox
Muchas gracias. Creo que ya
20 de Abril de 2009 • 22:44 — Invitado (invitado)No se yo, con las ultimas
21 de Abril de 2009 • 01:10 — KnightFoxTambien te doy la razon, intentar programar en DS, con la documentacion que hay, es un acto de masoquismo. A ver si con mis "tutoriales" arrojo algo de luz al tema.
Saludos
NightFox
Como puedo emular esto sin tener NDS?
24 de Abril de 2009 • 13:00 — Maxi_Newbie (invitado)Si te bajas el ultimo
24 de Abril de 2009 • 14:15 — KnightFoxSi te bajas el ultimo proyecto, veras que hay una carpeta que se llama Ideas. Dentro tienes un archivo que se llama "make_ideas_rom". Lo ejecutas y te parcheara la ROM para funcionar con Ideas i FAT. En la misma carpeta ya hay el emulador ideas configurado para usar el driver DLDI de R4 y poder tener acceso a FAT.
Saludos
NightFox
programacion
22 de Octubre de 2009 • 08:39 — fanecillohola me gustaria que me contestaras a algunas preguntas si es posible ...
tengo conocimientos muy basicos de C++ (estudio electronica) y por tu
experiencia crees que alguien sin conocimientos de C++ y tal podria llegar a
entender de todo esto ? esque ami el primer programa todabia el de hola
mundo pero este se me keda muy lejos y dificil bueno muchas gracias ^^
el programa es el que tu tienes como leccion01 ... entonces seguramente sera
por que no entendere muy bien el programa verdAD?
este error a que se debve ?
>------ Operación Generar iniciada: proyecto: daska, configuración: Release Win32 ------
1>Ejecutando acciones de proyecto de archivo MAKE
1>main.cpp
1>arm-eabi-g++ -MMD -MP -MF /c/nds/daska/daska/build/main.d -g -Wall -O2 -march=armv5te -mtune=arm946e-s -fomit-frame-pointer -ffast-math -mthumb -mthumb-interwork -I/c/nds/daska/daska/include -I/c/nds/daska/daska/build -I/c/devkitPro/libnds/include -I/c/devkitPro/libnds/include -I/c/nds/daska/daska/build -DARM9 -fno-rtti -fno-exceptions -c /c/nds/daska/daska/source/main.cpp -o main.o
1>linking daska.elf
1>main.o: In function `main':
1>c:/nds/daska/daska/source/main.cpp(32): undefined reference to `fatInitDefault'
1>collect2: ld returned 1 exit status
1>make[1]: *** [/c/nds/daska/daska/daska.elf] Error 1
1>make: *** [build] Error 2
1>El registro de generación se guardó en el "file://c:\nds\daska\daska\Release\BuildLog.htm"
1>daska - 0 errores, 0 advertencias
========== Generar: 1 correctos, 0 incorrectos, 0 actualizados, 0 omitidos ==========
se que seguramente sera que algo no entiendo y tal ... es una pena la verdad pero bueno por lo menos e conseguido sacar operaciones textos por pantalla y cosillas asi
nunka e utilizado este compilador o editor de programas y algunos conceptos basicos puede que se me escapen ...
es mi primer dia y espero que no sea el ultimo UU
y un ultimo deseo podria ser posible algun link donde expliken las funciones que utilizas en el programa a un nivel un poco de novatillo ....
Asegurate quetienes la
22 de Octubre de 2009 • 10:23 — NightFox (invitado)me e descargado esa carpeta
22 de Octubre de 2009 • 20:29 — fanecillome e descargado esa carpeta que pone nflib y le e dado a examples y el compile y me sale el mismo fallo .. donde tengo que poner la libreria esta que e descargado ?
tengo que poner en una carpeta donde tengo tu plantilla que me e descargado y sobre ella trastear asi tendre la libreria que me falta ? " siento si pregunto demasiado UU"
con la carpeta para hacer que visual c++ coja la libreria de palib aplication no se podria hacer lo mismo para que cojiera tu libreria ? una plantilla como cuando haces con la palib aplication ?
Si los ejemplos de la
22 de Octubre de 2009 • 22:01 — NightFox (invitado)devkpro este no lo tengo
22 de Octubre de 2009 • 22:20 — fanecillodevkpro este no lo tengo instalado tengo el visual c++ con la lib esta de ,nds .... eske no me aclaro muy bien no tendras un archivo con todo para instalar y listo ....UU siento si soy mucha molestia
el hola mundo y tal si que e podido crearlo ^^
Has seguido este tutorial de
22 de Octubre de 2009 • 22:26 — NightFox (invitado)si pero el ARMR25 donde esta
22 de Octubre de 2009 • 23:19 — fanecillosi pero el ARMR25 donde esta ? le doy a view all files y descargo el R25 ? abiendo mas actuales iwalmente ?
lo que voy hacer es desinstalar todo y volver a instalar y te comento " lo e echo en dos dias como unas 10 o 15 veces... y el uniko tutorial del que me fio tendra que ser el tuyo que porlomeneos contestas muchisimas gracias por ayudar a un pobre desgraciado como yo XDDDD"
todo desistalado ahora sigo paso por paso y te comento la instalacion si no es mucha molestia
descargo visual c++ 2005 " no 2008 " express
Service Pack 1
corre que ya me toka installar el arm R25 XD XD ^^XDDD
bueno exactamente ke archivo bajo del arm r25 me salen
newlib-1.15.0.tar.gz 10.2 MiB Tue Jun 19 2007 15:44 732 newlib-1.16.0.tar.gz 11.5 MiB Mon Jan 21 2008 23:37 606 newlib-1.17.0.tar.gz 12.2 MiB Fri Jan 30 2009 05:31 518 newlib-1.17.1.tar.gzespera que tengo ligeras lagunas si se llama Microsoft Visual Studio 8 en tu instalacion eske ai que descargar el2008 ??
en la instalacion del 2008 sale ya el sp para 2008 entonces ace falta que descarge la otrA ?
Tranquilo, a ver si este
23 de Octubre de 2009 • 14:32 — NightFox (invitado)