#author("2023-05-30T00:10:25+09:00","","")
#author("2023-05-30T00:12:20+09:00","","")
* GBFS [#hdb1f4ed]
GBAにはOSがない為、当たり前に使っている恩恵を受けることができません。ファイル管理システムもその1つで、fopenやfseek関数を呼び出せばファイルの中身にアクセスするという、単純なこともできません。プログラミング初心者にありがちなのは、自分のプロジェクト内で1つ1つのデータをポインタで管理してしまうことです。これでは規模が大きくなったら破綻します。そこでお奨めなのがGBFSです。ツールはdevkitProに標準装備されています。

** 原理 [#fc322335]
仕組みはとても簡単です。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:https://github.com/devkitPro/devkitarm-crtls/blob/master/gba_cart.ld]])に定義されています。このアドレスを利用して次の関数をいっしょに使います。

 // ExtendTinyFileSystem
 
 u8* EtfsReadu8(u32 offset)
 {
 	u8* p = (u8*)((u32)&__rom_end__ + offset);
 	return *p;
 }
 
 u16* EtfsReadu16(u32 offset)
 {
 	u16* p = (u16*)((u32)&__rom_end__ + offset);
 	return *p;
 }
 
 u32* EtfsReadu32(u32 offset)
 {
 	u32* p = (u32*)((u32)&__rom_end__ + offset);
 	return *p;
 }
 
 u8* EtfsGetPointer(void)
 {
 	return (u8*)((u32)&__rom_end);
 }

** 改良 [#wa26e1ea]
GBFSは、上記の原理を拡張して複数のファイルを参照可能にし、さらに詳細なファイル情報を追加したものです。仮に以下のようなファイルがあったとします。

 1.txt
 2.png
 3.bmp

これらをGBFSファイルにアーカイブ化するには以下のコマンドを入力します。

 gbfs.exe test.gbfs *.txt *.png *.bmp

test.gbfsのファイル構造は次のとおりです。

| GBFSヘッダ            |
| ファイル情報×3      |
| 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];			// 空き領域
 };

 // ファイル情報(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の処理忘れを疑ってください。バッチ処理を作っておくのもいいでしょう。
padbinコマンドは256バイト境界に合わせて連結させる為の処理です。この処理を忘れるとアドレスが誤って奇数位置で連結してしまう可能性があります。データがうまく取り出せない場合はpadbinの処理忘れを疑ってください。バッチを作っておくのもいいでしょう。

** GBFSとMode3の画像表示 [#v4aa1033]
公式にあるソースコードはモジュール化に失敗しておりユーザ側にGBFS情報を持つ必要があります。その為、ここでは自作したものを使用しました。

- gbfs.h
 #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で返却された場合、例外処理を作らなくてはいけません。

- 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"
 
 //---------------------------------------------------------------------------
 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;
 		}
 	}
 }

*** 動作画面 [#daa28698]
#ref(1.png,nolink)

** 履歴 [#e0740128]
- 2023/04/15
- 2007/09/25

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS