メガドラ開発:バッテリーバックアップ
SGDKによるメガドラ開発でバッテリーバックアップ(SRAM)に対応する方法についてメモを残しておきます。今回は自分でも正確に把握しきれていません。不十分な内容であることをご了承ください。
概要説明
SGDKでバッテリーバックアップを利用する場合、まずはロムヘッダーをバッテリーバックアップ用の設定に変更します。主に保存方法や、アドレスなどです。あとは専用の関数でByte単位またはWord単位でデータを記録・読み込みを実行していきます。ただ、実際にやってみると込み入った事情があるようで困惑することになります…。
ロムヘッダーの記述
ロムヘッダーはメガドラのゲームロムの冒頭数十Byteを使用して記述するロムの仕様をまとめたデータです。その中にSRAMに関する設定があります。ロムヘッダー全体の説明は以下のサイト(Plutiedev)が参考になります。
上記サイトのEXTRA MEMORYの項が該当します。SGDKではsrc/boot/rom_head.cを開きます。
#include "genesis.h"
__attribute__((externally_visible))
const ROMHeader rom_header = {
#if (ENABLE_BANK_SWITCH != 0)
"SEGA SSF ",
#elif (MODULE_MEGAWIFI != 0)
"SEGA MEGAWIFI ",
#else
"SEGA MEGA DRIVE ",
#endif
"(C)SGDK 2024 ",
"SAMPLE PROGRAM ",
"SAMPLE PROGRAM ",
"GM 00000000-00",
0x000,
"JD ",
0x00000000,
#if (ENABLE_BANK_SWITCH != 0)
0x003FFFFF,
#else
0x000FFFFF,
#endif
0xE0FF0000,
0xE0FFFFFF,
"RA",
0xF820,
0x00200000,
0x0020FFFF,
" ",
"DEMONSTRATION PROGRAM ",
"JUE "
};
上のコードはサンプルのものです。この中では"RA", 0xF820, 0x00200000, 0x0020FFFF, 辺りが該当箇所です。"RA"は固定で変更しません(0x2020に設定されている市販のゲームもありますが、そのように設定するとSGDKではrom_headerの冒頭にズレがでてしまうようです)。その次の0xF820はデータの取り扱いに関する設定のようです。Plutiedevによると、
0xA020 セーブ不可 16bit
0xB020 セーブ不可 8bit(偶数アドレス)
0xB820 セーブ不可 8bit(奇数アドレス)
0xE020 セーブ可 16bit
0xF020 セーブ可 8bit(偶数アドレス)
0xF820 セーブ可 8bit(奇数アドレス)
その次の0x00200000, 0x0020FFFF, はSRAMの開始アドレス、終端アドレスとなります。先のサイトでは開始アドレスが0x00200001と設定されております。自分が調べた範囲では市販のバッテリーバックアップ対応ゲームは以下のような設定が多かったです(トア、シャイニングシリーズ、A大戦略など)。
"RA", 0xF820, 0x00200001, 0x00203FFFF,
また、この問題に関連しているのかわかりませんが、SGDK/inc/sram.hには以下のような記述もあります。
* This unit provides methods to read from or write to SRAM.<br>
* By default we suppose SRAM is 8bit and connected to odd address.<br>
* You can change to even address by changing SRAM_BASE from 0x200001 to 0x200000 and rebuild the library.<br>
偶数アドレスを使用する場合はSRAM_BASEを変更してリビルドしてね、ってことのようですね。一般的な使用法としては0xF820で8bit奇数アドレスで設定して、開始アドレスは0x00200001とすれば良さそうです。
ゲーム側での使用法
準備が整ったら、実際にゲームでロード、セーブを行ってみます。読み書きを実行するときは一度割り込み処理を停止したうえで、セーブロードを行う方が良いようです。
SYS_disableInts();
SRAM_enableRO();
u16 data = SRAM_readByte(2);
SRAM_disable();
SYS_enableInts();
上記は読み込みを行っているのでSRAM_enableRO()を使用しています(Read Only)。引数はByteオフセットです。書き込みをする場合は以下のような感じで…
SYS_disableInts();
SRAM_enable();
SRAM_writeByte(2, data);
SRAM_disable();
SYS_enableInts();
同じですね。SRAM_writeByte以外にもWordやLong版もあります。
容量の大きいゲームでの問題点
SGDKを使用すると簡単にSRAMへの読み書きができますが、大容量のゲームを開発する際には仕組みを理解しておく必要があります。
通常、SRAMにアクセスするときには0x200000へSRAMがリマップされます。これはメガドライブ自体の仕様のようです。以前の記事でも書きましたが、0x000000 - 0x1FFFFFまではユーザープログラムが入り、0x300000 - 0x3FFFFFはSGDKによる自動的なバンク切り替えが行われます。そしてSRAMは0x200000-0x20FFFFにリマップされるわけです。このことから、そのエリアにプログラムがあると問題になりそうです。まぁ、プログラムが2MByteを超えることはないと思いますが…(MDSDRVを0x200000に配置してる場合は注意してください!)。また、すでにそのエリアにバンクを手動で設定してる場合にも問題が起きると考えられます。
このような事情から、SRAMへアクセスする前後ではバンクの状態の変化に注意し、アクセス中では極力不要な処理とは平行しない方が無難のようです。