GBAにはOSがない為、当たり前に使っているサービスの恩恵を受けることができません。ファイル管理システムもその1つで、fopenやfseek関数を呼び出せばファイルの中身にアクセスするという、単純なこともできません。初心者にありがちなのは、自分のプロジェクト内で1つ1つをポインタで管理してしまうことです。そして規模が大きくなった時に破綻してしまいます。そこでお奨めなのはGBFSです。ツールはdevkitProに入っています。
仕組みはとても簡単です。.gbaファイルの終端にファイルを繋げて読み込ませてしまおうというものです。例として.gbaファイルが0x1000のサイズだった場合、起動したときのアドレスは0800:0000h~0800:0FFFhになります。.gbaファイルの後ろにデータを追加したとき、その内容は0800:1000h以降に配置されるわけです。もちろんこのアドレスはROM領域に配置される為、リードオンリーであることは忘れないでください。
#define ADR_ROM_END (u8*)&__rom_end__ extern u8 __rom_end__;
「__rom_end__」は「C:\devkitPro\devkitARM\arm-none-eabi\lib\gba_cart.ld」(github)に定義されています。このアドレスを利用して次の関数をいっしょに使います。
// ExtendTinyFileSystem u8* EtfsReadu8(u32 offset) { u8* p = ADR_ROM_END + offset; return *p; } u16* EtfsReadu16(u32 offset) { u16* p = (u16*)ADR_ROM_END + offset; return *p; } u32* EtfsReadu32(u32 offset) { u32* p = (u32*)ADR_ROM_END + offset; return *p; } u8* EtfsGetPointer(void) { return ADR_ROM_END; }
GBFSは、上記の原理を拡張して複数のファイルを参照可能にし、さらに詳細なファイル情報を追加したものです。仮に以下のようなファイルがあったとします。
1.txt 2.png 3.bmp
これらをGBFSファイルにアーカイブ化するには以下のコマンドを入力します。
gbfs.exe test.gbfs *.txt *.png *.bmp
test.gbfsのファイル構造は次のとおりです。
// ヘッダ(32バイト) typedef struct { char sig[16]; // シグネチャ "PinEightGBFS" + 0x0d + 0x0a + 0x1a + 0x0a u32 size; // アーカイブの大きさ u16 dirOff; // ファイル一覧のオフセット(ROM終端からの相対) u16 fileCnt; // ファイルの登録数 u8 noUse[8]; // 空き領域 };
// ファイル情報(32*3バイト)(今回の例では3つ分存在します) typedef struct { char fname[24]; // ファイル名 u32 size; // サイズ u32 dataOff; // ファイルのオフセット(ROM終端からの相対) };
// 1.txtのバイナリデータ // 2.pngのバイナリデータ // 3.bmpのバイナリデータ
さらに次のコマンドで.gbaファイルと連結します。
padbin.exe 256 x.gba copy /b x.gba + x.gbfs x2.gba
padbinコマンドは256バイト境界に合わせて連結させる為の処理です。この処理を忘れるとアドレスが誤って奇数位置で連結してしまう可能性があります。ハードウェアの制約で、データがうまく取り出せない場合はpadbinの処理忘れを疑ってください。
公式にあるソースコードはモジュール化に失敗しておりユーザ側にGBFS情報を持つ必要があります。その為、ここでは自作したものを使用しました。
#ifndef __GBFS_H__ #define __GBFS_H__ #ifdef __cplusplus extern "C" { #endif #include "gba.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // ヘッダ(32バイト) typedef struct { char sig[16]; // シグネチャ "PinEightGBFS" + 0x0d + 0x0a + 0x1a + 0x0a u32 size; // アーカイブの大きさ u16 dirOff; // ファイル一覧のオフセット(ROM終端からの相対) u16 fileCnt; // ファイルの登録数 u8 noUse[8]; // 空き領域 } __PACKED ST_GBFS_HEADER; // ファイルリスト(32バイト) typedef struct { char fname[24]; // ファイル名 u32 size; // サイズ u32 dataOff; // ファイルのオフセット(ROM終端からの相対) } __PACKED ST_GBFS_LIST; typedef struct { ST_GBFS_HEADER* pHeader; ST_GBFS_LIST* pList; u32 pos; } ST_GBFS; //--------------------------------------------------------------------------- EWRAM_CODE void GbfsInit(void); IWRAM_CODE void* GbfsGetPointer(char* fname); IWRAM_CODE void* GbfsGetPointer2(u32 cnt); IWRAM_CODE void* GbfsGetSafePointer(char* fname); IWRAM_CODE void* GbfsGetSafePointer2(u32 cnt); EWRAM_CODE char* GbfsGetFileName(void); EWRAM_CODE u32 GbfsGetFileSize(void); EWRAM_CODE u32 GbfsGetArcSize(void); EWRAM_CODE u32 GbfsGetArcCnt(void); #ifdef __cplusplus } #endif #endif
ファイルのポインタを取り出す関数には2種類あり、Safe付きかどうかがあります。自分で用意する場合は100%存在するのでNULLの返却を許さず、Safeなしはユーザ側が用意し、NULLでも許容するというコードになっています。もちろんNULLで返却された場合、例外処理を作らなくてはいけません。
#include "gbfs.h" //--------------------------------------------------------------------------- // gba_cart.ld extern u8 __rom_end__; //--------------------------------------------------------------------------- ST_GBFS Gbfs; //--------------------------------------------------------------------------- EWRAM_CODE void GbfsInit(void) { _Memset(&Gbfs, 0x00, sizeof(ST_GBFS)); char* pHeader = (char*)(((u32)&__rom_end__ + 0xff) & 0xffffff00); if(_Strncmp(pHeader, "PinEightGBFS", sizeof("PinEightGBFS")-1) == 0) { Gbfs.pHeader = (ST_GBFS_HEADER*)pHeader; Gbfs.pList = (ST_GBFS_LIST*)(pHeader + Gbfs.pHeader->dirOff); } else { SystemError("[Err][GbfsInit] .gbfs File Not Found(pHeader = %x)", pHeader); } } //--------------------------------------------------------------------------- IWRAM_CODE void* GbfsGetPointer(char* fname) { s32 left = 0; s32 right = Gbfs.pHeader->fileCnt; s32 mid; s32 ret; while(left <= right) { mid = (left + right) / 2; ret = _Strncmp(fname, Gbfs.pList[mid].fname, 24); if(ret == 0) { Gbfs.pos = mid; return (u8*)Gbfs.pHeader + Gbfs.pList[mid].dataOff; } if(ret > 0) { left = mid + 1; } else { right = mid - 1; } } return NULL; } //--------------------------------------------------------------------------- IWRAM_CODE void* GbfsGetPointer2(u32 cnt) { if(cnt >= Gbfs.pHeader->fileCnt) { return NULL; } Gbfs.pos = cnt; return (u8*)Gbfs.pHeader + Gbfs.pList[cnt].dataOff; } //--------------------------------------------------------------------------- IWRAM_CODE void* GbfsGetSafePointer(char* fname) { void* p = GbfsGetPointer(fname); if(p == NULL) { SystemError("[Err][GBFSGetSafePointer] .gbfs File Not Found: %s\n", fname); } return p; } //--------------------------------------------------------------------------- IWRAM_CODE void* GbfsGetSafePointer2(u32 cnt) { void* p = GbfsGetPointer2(cnt); if(p == NULL) { SystemError("[Err][GBFSGetSafePointer2] .gbfs File Not Found: No.%d\n", cnt); } return p; } //--------------------------------------------------------------------------- EWRAM_CODE char* GbfsGetFileName(void) { return Gbfs.pList[Gbfs.pos].fname; } //--------------------------------------------------------------------------- EWRAM_CODE u32 GbfsGetFileSize(void) { return Gbfs.pList[Gbfs.pos].size; } //--------------------------------------------------------------------------- EWRAM_CODE u32 GbfsGetArcSize(void) { return Gbfs.pHeader->size; } //--------------------------------------------------------------------------- EWRAM_CODE u32 GbfsGetArcCnt(void) { return Gbfs.pHeader->fileCnt; }
#include "lib/gba.h" //--------------------------------------------------------------------------- void WaitForVsync(void) { while (*(volatile u16*)0x4000006 >= 160) {}; while (*(volatile u16*)0x4000006 < 160) {}; } //--------------------------------------------------------------------------- int main(void) { SetMode(MODE_3 | BG2_ENABLE); GbfsInit(); u16* pImg1 = GbfsGetSafePointer("image1.img"); u16* pImg2 = GbfsGetSafePointer("image2.img"); LZ77UnCompVram(pImg1, (void*)VRAM); u32 c = 0; bool is = FALSE; for(;;) { WaitForVsync(); // 約3秒後にpImg2を表示 if(c++ == 60*3 && is == FALSE) { LZ77UnCompVram(pImg2, (void*)VRAM); is = TRUE; } } }