Doc.9 擬似ファイルシステムGBFS †
GBAには元々fopenや、fseekなどのファイル関数がありません。
その為、1つ1つをポインタでやりとりしてて管理が面倒とか起こりえます。
そんな時にお奨めなのがGBFSです。
公式サイトはhttp://www.pineight.com/gba/#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__」は「gba_cart.ld」に定義されています。
このアドレスを利用して、次のような関数といっしょに使うのがいいと思います。
// 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;
}
マルチブート用には「gba_mb.ld」内に定義されています。
コンパイル時の指定には注意してください。
・gba_mb.ld(259行目)
__rom_end__ = __ewram_overlay_end;
・gba_cart.ld(271行目)
__rom_end__ = __pad_lma + SIZEOF(.pad);
改良 †
GBFSは、上記の原理を拡張して複数のファイルを参照を可能にし、さらに詳細なファイル情報を追加したものです。
仮に以下のようなファイルがあったとします。
1.txt
2.png
3.bmp
これらをGBFSファイルにするには、以下のコマンドを入力します。
gbfs.exe test.gbfs *.txt *.png *.bmp
test.gbfsのファイル構造は次のとおりとなっています。
- ヘッダ
- 登録したファイルリスト
- 1.txt、2.png、3.bmpのバイナリデータ
// ヘッダ(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を使ってMode3の画像を表示する例 †
公式にあるソースコードはモジュール化に失敗しており(ユーザ側にGBFS情報を持つ必要があり)
あまり良いとは言えないです。ここでは自作したものを使用します。
ファイルのポインタを取り出す関数には2種類あり、Safe付かどうかがあります。
自分で自前に用意する場合は100%存在するのでNULLの返却を許さず、
Safeなしはユーザが用意し、NULLでも許容するというコードになっています。
- gbfs.c
#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;
}
- main.c
#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();
}
}
動作画面 †
履歴 †