ホームチュートリアルプログラム
1-セットアップ 4-サウンド 7-FIFO インベーダー
2-フレームバッファ 5-SRAM 8-割り込み DSEmu
3-キー入力 6-ファイルシステム 9-マイク  
10-拡張回転バックグラウンド

Homebrew任天堂DS開発 Part 5 - SRAM

任天堂ゲームボーイアドバンスのカートリッジには、ゲームデータの保存に使用される SRAM と呼ばれる領域があります。 GBAプログラムでここにデータを書き込むことができます、そして、それは、ゲームボーイアドバンスをオフにしても記憶されたままになります。

この領域は任天堂DSから書き込みそして読み出すことができます、このことはHomebrewプログラムが、マシンのスイッチをオン・オフする間、データをこの領域に格納し永続的に残しておくことが可能となります。また、ユーザーがプログラムで使用するデータを'アップロードすることも可能となります。 彼らは彼らのフラッシュカートリッジソフトウェアのデータをこの領域に 'セーブ' することも、'ロード' することも可能となります。

例えば、この領域に書き込むデータはゲームのハイスコアテーブルかもしれません。 また、メモ帳プログラムはユーザーがフラッシュカートリッジからテキストをダウンロードできるように、このSRAM領域にテキストを格納するかもしれません。 そして、PC上でそれを編集した後、新しいテキストをこのソフトウェアにアップロードすることができます。

このチュートリアルでは、どのようにこの領域を読み書きするかを解説します。

GBA カートリッジのメモリ レイアウト

NDSTech Wiki で ARM9のメモリレイアウト が公開されています。GBA カートリッジ ROM はアドレス 0x08000000 に位置しており、SRAM 領域は 0x0A000000 にあります。SRAM はサイズが 64KB です。

GBA カートリッジメモリは特定のプロセッサにマップしなければなりません。 一時点では ARM7 と ARM9 の片方だけがここにアクセスできます。 現在どのプロセッサがメモリをマップできるかを制御するために、WAIT_CRレジスタを使用します。

ARM7 がGBAカートリッジメモリにアクセスする場合は、このレジスタのビット 7 はオンにセットします。ARM9 がアクセスする場合にはオフにセットします。

以下が、どのように操作を行うかのサンプルコードです。

  /* カードデータのローカルコピーの保存場所 */
  static char card_id[5];

  /* ビット7をオフにすることにより、ARM9 が GBA カートリッジメモリをアクセスできるようにする */
  WAIT_CR &= ~0x80;

  /* これで安全に GBA カートリッジメモリにアクセスできる */
  memcpy(card_id, (char*)0x080000AC, 4);

カートリッジID

A GBADEV フォーラムでの Darkain からの投稿 で、Homebrewコードが挿入されているカートリッジを認識しないで SRAM 領域に書き込むときの問題点が提示されました。 Wifimeを使用して homebrew コードをダウンロードしたなら、homebrewコードをインストールしたフラッシュカートリッジがないかもしれません。 また、それは市販カートリッジの重要なゲームのセーブデータかもしれません。 ダウンロードされた homebrew プログラムは許可なくそのデータを消すべきではないでしょう。

このことが起こるのを防ぐために、Darkain はすべてのカートリッジが持つ識別子に 'PASS' が含まれているかどうかをチェックすべきだと提案しています。 このコードは現在すべての homebrew コードで使用されており、それは homebrew コードを実行するために使用しているオリジナルの 'PaddMe' 方法に関連しています。 この識別子が存在するかどうかをチェックするのは非常に簡単であり、私はSRAMに書く前にチェックするというフォーラムの投稿者の忠告に従うことをお勧めします。

カートリッジメモリがいったんマップされると、私達はこの識別子を読むことができます。ヌルターミネイトではない4バイトの文字列で 0x080000AC に識別子はあります。 いかのサンプルでは、カートリッジメモリからローカル変数にコピーしています。

  static char card_id[5] = { 0,0,0,0,0 };

  static void memcpy(char* dest, char const* src, int size) {
    while(size--) 
      *dest++ = *src++;
  }

  void main() {
    [...]

    /* 4文字のカートリッジROMの内容をローカル変数にコピーする。
       すべてのhomebrew ROMでは 'PASS' であるべきです。
       識別子は、0x080000AC に位置している。 */
    memcpy(card_id, (char*)0x080000AC, 4);
  
    [...]
  }

is_homebrew_cartridge 関数は、私が使用する 'PASS' をチェックするコードです。

  /* カートリッジIDが 'PASS' なら true でリターン */
  static int is_homebrew_cartridge() {
    return 
      card_id[0] == 'P' &&
      card_id[1] == 'A' &&
      card_id[2] == 'S' &&
      card_id[3] == 'S';
  }

  void some_func() {
    if(is_homebrew_cartridge()) {
      write_to_sram();
    }
  }

SRAM の読み書き

一度マップされると、SRAM はメモリアドレス 0x0A000000 に位置します。 libnds にはこの領域をポイントするための SRAM マクロが定義されています。

#define SRAM          ((uint8*)0x0A000000)

マクロは理由があって、8ビットタイプだと指定されています。すべての SRAM への読み書きは8ビット単位で行わう必要があります。 16ビットや32ビットでの読み込みは動作しないでしょう。 そのため、以前に示したサンプルでは memcpy ルーチンを使用しており、これは1時点では1バイト=8ビット単位で動作するからです。

このメモリ領域に書き込みはどんなフォーマットでもかまいません。あなたはテキスト・バイナリデータなどを格納できます。 あたまたま別のプログラムで保存されたSRAMデータを読み込む可能性もあるので、領域の先頭にデータ構造を特定するためのいくつかのデータを格納することは名案であるかもしれません

このチュートリアルではヌルターミネート文字列を読み書きしているだけです。 'A'キーを押すとSRAMから文字列を読み取りローカル変数 sram_data に格納し、スクリーンに表示します。

    if(READ_KEYS & KEY_A) {
      /* SRAM から表示用のローカル変数にコピー */
      memcpy(sram_data, (char*)SRAM, sizeof(sram_data) - 1);
    }

'B' キーが押されると静的なストリングをローカル変数からコピーし、SRAMに格納します。

    if(READ_KEYS & KEY_B) {
      /* カートリッジがhomebrew ROMの時は、テキストストリングを SRAM にコピー */
      const char text[] = "Hello from SRAM!";
      const char error[] = "Not Homebrew! Copy failed.";
      const char success[] = "Copy succeeded.";
      if(is_homebrew_cartridge()) {
	memcpy((char*)SRAM, text, sizeof(text));
	memcpy(copy_status, success, sizeof(success));
      }
      else
	memcpy(copy_status, error, sizeof(error));
    }

デモのビルド

SRAM への読み書きは DevKitPRO リリース 13 以上が必要です。 それ以前のバージョンでは、SRAM にこの方法でアクセスすることを防ぐために SRAM アクセス許可を設定していました。

完全なサンプルプログラムは 'sram_demo1' です。ARM9 のコードは arm9_main.cpp にあります。 それは チュートリアル1 と同様に情報を表示するためにコンソールルーチンを使用します。 ARM7 のコード arm7_main.cpp は、ndslib の標準の ARM7 テンプレートコードと同じで、私は一切修正していません。すべてをビルドするための Makefile ファイルです。

完全なソースコードは sram_demo1.zip で、エミュレータまたは実機で動作する sram_demo1.ndssram_demo1.nds.gba ファイルをダウンロードできます

テスト

SRAM への書き込みおよび読み込みを検証する方法は以下の通りです。

  1. はじめて sram_demo1 を実行するとき, SRAM に何らかのテキストをコピーするように 'B' ボタンを押します。 'Copy Succeeded' メッセージが表示されるはずです。今度は、SRAM の内容を読み込んでスクリーンに表示するように 'A' ボタンを押します。'Hello from SRAM!'と表示亜sれます。DSをリブートします。
  2. 2回目の実行では、まず、'A' ボタンを押します。前回の実行で SRAM に書き込まれた、'Hello from SRAM!' が表示されるはずです。これで、SRAM の内容がリブートをまたがることが検証できました。
  3. フラッシュカートリッジソフトウェアを使用して、SRAM の内容を PC にダウンロードします。 他のテキストを表示するために、それを編集します。サーブデータをカートリッジに戻し、sram_demo1 を再実行します。 'A' ボタンを押すと、あなたがアップロードしたテキストが表示されるはずです。

結論

このチュートリアルでは、どのように SRAM またはGBAフラッシュリンカーカートリッジのゲームセーブ領域に読み書きを行うかを解説しました。 しかし、私はこのことに関してまだ不確実なことがあります。

私がこのチュートリアルのための情報を多く知った GBADEV フォーラムの投稿者に感謝申し上げます。

いつものように、いろいろなコメントを提案は歓迎します。 以下の私の連絡先を見てください。