ここでは、GNUツールで作ったGBAプログラムを白ロムに各方法を指南します。
白ロムに関してはFLASH2ADVANCE等をお買い求めください。
ロードアドレス
UNIX USERの記事に書かれているプログラムは、GBAの外部RAMにロードされることを前提に作られています。今回はGBAプログラムを白ロムに書き込むことが目的なのでプログラムやリンカスクリプトにも修正を加える必要があります。
まず、リンカに関して、カートリッジROMのアドレスは0x08000000からマッピングされています。このうち、0x08000000~0x080000BFまでのアドレスは後ほど説明するGBAヘッダ領域となるので、コード領域(.textセクション)の開始アドレスは0x080000C0になります。定数領域(.rodataセクション)は、コード領域の直後においておけばよいでしょう。
データ領域(.data,.bssセクション)は、外部RAM、あるいは内部RAMにおくことになりますが、ここで注意が必要になります。例えば外部RAMにおく場合、参照するアドレスは0x02000000~であるわけですが、生成するバイナリイメージにおいて、これらの"初期値"に関してはROMに置かれることになるので、そのアドレスはコード領域の直後である必要があります。ここで出てくるのがリンカスクリプトの"ロードアドレス"と呼ばれる概念です。プログラム内で参照されるアドレスは"仮想メモリアドレス"と呼ばれるものです。ここで今回使われるROMにおかれる(binaryイメージに出力される)領域は"ロードアドレス"となります。つまり、UNIX USERの記事のように全てRAMにおかれる場合は
仮想メモリアドレス = ロードアドレス
となるのですが、今回の場合は別々に指定する必要があるのです。
私が使っているリンカスクリプトの例を示します。
OUTPUT_ARCH(arm)
SECTIONS {
.text 0x80000C0 : { *(.text) }
.rodata : { *(.rodata*); _erdata = .; }
.data 0x2000000 : AT(ADDR(.text) + SIZEOF(.text) + SIZEOF(.rodata))
{ _data = . ; *(.data); _edata = . ; }
.bss : AT(ADDR(.text) + SIZEOF(.text) + SIZEOF(.rodata) + SIZEOF(.data))
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
ここで、:より前に書かれているアドレスが仮想メモリアドレスで、:以降に書かれているAT()がロードアドレスになります。
RAM領域の初期化
前節でも説明したとおり、ROMから起動した状態ではデータ領域の値はまだROMに入ったままなのでこれをRAM領域へ動かす作業を、ユーザープログラムを動かす前に行う必要があります。イメージとしてはこのような感じです。
具体的にはCのmain関数の先頭に以下の処理を追加します。
extern char _erdata,_data, _edata, _bstart, _bend;
char *src = &_erdata;
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst < &_edata) {
*dst++ = *src++;
}
/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0;
ここでextern指定されている_erdata,_data, _edata, _bstart, _bendというのは、前節のリンカスクリプトで定義した定数_erdata,_data, _edata, _bstart, _bendを差しています。GNUツールではこのようにリンカスクリプトで定義した定数をC言語でも使えるようにできているのです。
GBA Headerの追加
ここまででROMに書き込む際のプログラム側の整備は終わりました。でもそれだけではROMに書き込んで動作させることはできません。Gameboy Advanceでは、binaryイメージの先頭にGBA Headerと呼ばれるコードを追加する必要があります。
GBA Headerの内容は以下のとおりです。
Let's Emu!:http://www.geocities.co.jp/Playtown-Yoyo/6130/tech_gba.htm
から抜粋
GBA Headerの追加を自動化するツールを用意いたしました。[[ソース置き場]]よりダウンロードしてください。なお、本プログラムにはNintendoロゴデータやメーカーコード
といったデータは搭載されておりません。各自の責任においてデータを書き込んでください。(簡単な方法はGBAカートリッジを吸い出してその内容を見ることですが)
[0000-00BFh (192Byte)] | 説明 |
---|---|
開始アドレス 0000-0003h (4Byte) | プログラムの開始アドレス。ARMコマンド 「B(開始アドレス)」。通常は 00C0h にジャンプするようになっている。 |
Nintendo ロゴデータ 0004-009Fh (156Byte) | ゲーム起動時のNintendoの文字/ロゴのデータ領域。 |
ゲームタイトル 00A0-00ABh (12Byte) | ゲームのタイトル。 |
ゲームコード 00AC-00AFh (4Byte) | ゲームごとに割り当てられるゲームコード。その内訳: 00ACh 一部の特殊カートリッジを除き、今のところ 「A」 や 「B」 。以後、「C」と続くことになる。 00AD-00AEh ゲーム名の頭文字から取ったりした任意の2文字。 00AFh 地域コード。「C」(中国) 、「D」(ドイツ) 、「E」(アメリカ) 、「F」(フランス) 、「H」(オランダ?) 、「I」(イタリア) 、「J」(日本) 、「K」(韓国) 、「P」(ヨーロッパ) 、「S」(スペイン) 、「U」(マルチ?) 、「X」(?) 、「Y」(?) 。 |
メーカーコード 00B0-00B1h (2Byte) | 発売元(メーカー)ごとに割り当てられるメーカーコード。 |
修正コード 00B2h (1Byte) | 必ず修正値の 96h 。 |
メインユニットコード 00B3h (1Byte) | ソフトを動作させるのに必要なハードウェアの識別コード。今のところ 00h 。 |
ディバイスタイプ 00B4h (1Byte) | ゲームのセーブパックにインストールされているディバイスのタイプ。1M/8Mビットの フラッシュDACS (Debugging And Communication System) を搭載したジョイキャリーカートリッジ用。 |
予約エリア 00B5-00BBh (7Byte) | すべて00hの未使用エリア。 |
ROMバージョン 00BCh (1Byte) | ゲームのバージョン。00hはバージョン1.0、01hはバージョン1.1となる。 |
コンプレメントチェック 00BDh (1Byte) | チェックサムの一種で、ヘッダー部分のデータが改変されていないかを判断できる。00A0h から 00BCh のデータの合計の補正に 19h を足した数値。 |
予約エリア 00BE-00BFh (2Byte) | 当初はチェックサム用の領域だったが、00h の未使用エリア。 |
デバッグおよび実行
デバッグはエミュレータで行いましょう。いちいちROMに書き込んで行うとFLASH ROMの書き込み制限をすぐに越してしまいます。
BoycottAdvance:http://boycottadvance.emuunlim.com/
等、デバッグ機能のついたエミュレータもあるのでそこで動作確認を行ったうえでROMに書き込むべきでしょう。
さあ、いよいよROMに書き込むときがやってきました。オプティマイズのUSBブートケーブルを使っている方はbtconsやdevmanを使ってカセットに書き込めば終わりです。そこらへんのやり方はオプティマイズのサイト内で詳しい説明があるので特にここで説明する必要も無いでしょう。
ソースコード
/*
* RAWバイナリからGBAブートのためのヘッダを追加するツール
* makeGBA.c
* Original Programmed by kacky All Rights Reserved
*/
#include <stdio.h>
/* GBAヘッダ バイトオーダーはリトルエンディアン */
struct GBAHeader {
unsigned char startaddress[4];
unsigned char logocode[156];
unsigned char title[12];
unsigned char gamecode[4];
unsigned char makercode[2];
unsigned char revisioncode[1];
unsigned char mainunitcode[1];
unsigned char devicetype[1];
unsigned char reserved[7];
unsigned char romversion[1];
unsigned char complecheck[1];
unsigned char reserved2[2];
};
struct GBAHeader header =
{
{ 0x2E,0x00,0x00,0xEA }, /* スタートアドレスへのジャンプ命令 */
{ }, /* ロゴコード */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* タイトル */
{ 'A', 0x00, 0x00, 'J'}, /* ゲームコード(A type JPN) */
{ }, /* メーカーコード(任天堂) */
{ 0x96 }, /* 修正値 (0x96固定) */
{ 0x00 }, /* メインユニットコード(0x00) */
{ 0x00 }, /* デバイスタイプ(0x00) */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* 予約エリア */
{ 0x00 }, /* ロムのバージョン(0x00) */
{ 0x00 }, /* チェックバイト */
{ 0x00, 0x00 }, /* 予約エリア */
};
/* Gameboy Advance binary イメージに GBA Headerを付加するユーティリティ
* 使い方
* makeGBA 入力ファイル名 出力ファイル名 タイトル
* 入力ファイルにbinaryイメージを指定するとタイトル名をつけたGBAヘッダを
* 出力ファイル名で出力します
* 例: makeGBA hoge.bin hoge.gba HOGEHOGE
* 注:タイトルは 11byte 以内に設定すること */
int main(int argc,char *argv[])
{
unsigned char checkbyte = 0x00;
unsigned char *bytearray; /* ヘッダをバイト列とみなす */
unsigned char buf[512]; /* 読み込みバッファ */
FILE *in,*out;
int i;
if(argc < 4){
fprintf(stderr,"few arguments usage makeGBA inputfile outputfile title\n");
return -1;
}
strcpy(header.title,argv[3]); /* タイトルをヘッダにコピー */
header.gamecode[1] = argv[3][0]; /* 頭文字をヘッダにコピー */
header.gamecode[2] = argv[3][1];
/* チェックコードを計算 */
bytearray = (unsigned char *)&header.title;
for(i=0xA0;i<0xBC;i++){
checkbyte += *bytearray++;
}
checkbyte += 0x19;
checkbyte = ~checkbyte + 1;
header.complecheck[0] = checkbyte;
/* 出力ファイルをオープン */
if((out = fopen(argv[2],"w")) == NULL){
fprintf(stderr,"Cannot open file %s.\n",argv[2]);
return -1;
}
/* ヘッダを書き込む */
fwrite(&header,0xC0,1,out);
/* 入力ファイルをオープン */
if((in = fopen(argv[1],"r")) == NULL){
fprintf(stderr,"Cannot open file %s.\n",argv[1]);
fclose(out);
return -1;
}
while(!feof(in)){
i = fread(buf,1,512,in); /* 512byte ずつ読み込む */
fwrite(buf,i,1,out); /* 読み込んだものを出力 */
}
fclose(in);
fclose(out);
printf("GBA make successfully.\n");
return 0;
}