GBAにはLinuxのようなOSがない為、当たり前に使っているような恩恵を受けることもできません。その1つはファイル管理システムの存在です。C言語にはfopenやfseekなどの関数を使用すると中身のバイナリにアクセスするわけですけれどGBAはそうはいきません。その為、自分でプロジェクトに結び付けることをすると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]; // 空き領域
} __PACKED ST_GBFS_HEADER;
// 登録したファイルリスト(今回の例では3つ分存在します)
typedef struct {
char fname[24]; // ファイル名
u32 size; // サイズ
u32 dataOff; // ファイルのオフセット(ROM終端からの相対)
} __PACKED ST_GBFS_LIST;
// バイナリデータ(1.txt、2.png、3.bmp)
これを次のようなコマンドで.gbaファイルと連結します。
padbin.exe 256 x.gba copy /b x.gba + x.gbfs x2.gba
padbinコマンドは256バイト境界に合わせて追加させる為の処理です。
これはアドレスが誤って奇数位置で連結しないようにする為の処置となっています。
忘れるとデータがうまく取り出せなくなるので注意してください。
公式にあるソースコードはモジュール化に失敗しており(ユーザ側に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でも許容するというコードになっています。
#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"
#include "res.h"
//---------------------------------------------------------------------------
void WaitForVsync(void)
{
while (*(volatile u16*)0x4000006 >= 160) {};
while (*(volatile u16*)0x4000006 < 160) {};
}
//---------------------------------------------------------------------------
int main(void)
{
SetMode(MODE_3 | BG2_ENABLE);
GbfsInit();
u16* pImg = GbfsGetSafePointer("image.img");
LZ77UnCompVram(pImg, (void*)VRAM);
for(;;)
{
WaitForVsync();
}
}#ref(): File not found: "clip_1.png" at page "doc.11"