1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作Game Boy AdvanceソフトをSteamworks対応 — 未使用I/O領域でつなぐDewpoint Advance

1
Posted at

私はこれまで、個人的な趣味で Game Boy Advance(GBA)向けのソフトを何本か開発してきました。

今回、それらを Steam で販売するために、GBA ROM 上の C コードから Steam の実績・ランキング・UGC を利用できる仕組み「Dewpoint Advance」を開発しました。

Dewpoint Advance を使うと、自作 GBA ソフトに次のような機能を実装できます。

  • リーダーボードを取得してゲーム画面内に表示
  • リーダーボードに添付された UGC データ(リプレイ)をダウンロードして再生

clip_256x160.gif

単に Steam オーバーレイを表示するのではなく、取得したランキングやリプレイを 240×160ピクセルのGBA画面内で直接表示・再生できる のが特徴です。

この仕組みは、ホストアプリケーション側から GBA の「未使用I/O領域への読み書き」をエミュレータ経由でフックし、Steamworks APIの呼び出しなどへ変換することで実現しています。

ROM をエミュレータで動かして Steam 向けに配信する事例は存在します。しかし、GBA 側の ARM コードを起点として Steamworks の機能をゲーム内へ直接統合した事例は、おそらくこれが史上初 だと思っています。

私が調査した範囲では、メモリ監視やブレイクポイントのような仕組みで外的にSteam連携している事例しか見つかりませんでした。

ただし、この点は私のリサーチ不足という可能性もあります。

類似事例をご存じでしたら、是非コメントなどで教えてください🙇

本記事では、Dewpoint Advanceを開発した動機、アーキテクチャ、具体的な使い方を解説します。

モチベーション

一般的にゲーム機エミュレータというと、「過去のゲームを動かすためのもの」かもしれません。

それ自体を否定するつもりはありません。

しかし、私はゲーム機エミュレータを、Unity や Unreal Engine などと同じ 「ゲームエンジンの一種」 ともみなしています。

Unity や Unreal Engine などの「現代のゲームエンジン」は、確かに素晴らしいです。

しかし、私にとっては、タイルマップやスプライトを中心としたキャラクターパターンベースのグラフィックスシステムのほうが、ゲームを開発しやすいという実感があります。

また、開発環境が軽い点も魅力です。

Unreal Engine や Unity では、開発環境に数 TB 程度のストレージが必要になるケースもあり、プロジェクトをひとつ作るだけでも膨大なストレージを消費します。(参考記事)

しかし、GBA の開発環境(devkitPro)であれば、2 GB もあれば十分です。

コードベースでの実装制御が容易なので、AI エージェントを活用したコーディングにも適しています。(フルアセンブリ言語ではトークン消費がきついかもしれませんが、C などの高級言語が使えれば、比較的少ないトークン消費で快適に開発できる印象です)

Discord の gbadev では「生成AI禁止」というルールがありますが、裏を返せばそれは 「gbadevは生成AIとの相性が良すぎる(AI slop過多?)」 ということなのかもしれません。


何より、GBA は数あるゲームコンソールの中でも、最もゲームを開発しやすい名機だと私は考えています。

GBA の主な特徴:

  • 完成された VDP 方式: ポリゴンではなく、2D スプライトを用いる映像処理方式の最終到達点
  • モダンな開発環境: 32 ビット ARM バイナリが動作するため、GCC を利用できる
  • ちょうどよいリソースサイズ:
    • 288 KB の WRAM と 96 KB の VRAM を搭載
    • 画面サイズ(240×160)が小さいため、「作画コスト」を抑えられる

ただし、単純に「GBA ソフトをベタ移植のまま Steam で販売できる」だけでは芸がありません。

そもそも、私が開発した GBA ソフトは、Steam で配信中のソフト(VGS-Zero で制作)のおまけとして、すでに ROM ファイルを配信済みです。

そのため、まったく同じものを Steam で配信するのは無意味です。

そこで、アチーブメントやリーダーボードなどの「Steam ならではの要素」を追加するのが良いのではないかと考え、GBA のコードから Steam API を呼び出せるアーキテクチャ を考案し、それを用いたゲームを販売してみることにしました。

Dewpoint Architecture

Dew point(露点)とは、水蒸気が水滴に変わり始める温度のことです。

若干「意味深」な名称かもしれませんが、単純に Steam(水蒸気)から連想する言葉を、Valve から怒られないであろう形で付けてみただけです。おそらく、深い意味はありません。

このアーキテクチャは、主に次の3つのコンポーネントで構成されます。

  • Dewpoint Advance SDK:GBA コードで実行できるライブラリ
  • Dewpoint Bridge:GBA コードとネイティブコード間のインターフェース
  • Dewpoint Runtime:ネイティブコードで実行されるランタイム処理

現状、最小構成として、

  • Steam Achievement
  • Steam Leaderboard (※リプレイデータの添付に対応)

に対応する処理を、GBA の ARM32 コードで直接記述できます。

将来的には、Steam Lobby や Steam Networking Socket にも対応し、オンライン・マルチプレイを実現してみるのも面白いかもしれません。

このほかにも、

  • ゲームプロセスを終了する API
  • 画面モード(フルスクリーン・ウィンドウ)を切り替える API
  • 接続ゲームコントローラー種別(PC-Keyboard/XBOX/PS/Switch)を取得するAPI

など、PC ゲーム特有のインターフェースを提供しています。

これにより、GBA ソフトでありながら、Steam のプラットフォーム特性に適合したゲームを開発できます。

Dewpoint Advance SDK

SDK の仕組みは非常にシンプルです。

GBA の mmap I/O に含まれる未使用アドレス領域の一部を、Dewpoint Bridge 用に割り当てました。

GBA の I/O アドレス空間に含まれる未使用領域のうち、実機および mGBA での検証結果をもとに、0x04801000〜0x04801FFF を Dewpoint Bridge 用の独自アドレス領域として使用しています。

そして、Dewpoint Runtime がエミュレータ・コアの I/O をフックし、Steamworks SDK を用いた処理などのホスト・ネイティブ処理を実行する構造になっています。

Dewpoint Bridge

以下では、実際の Dewpoint Advance のコードの一部を用いて、Dewpoint Bridge の仕組みを解説します。

Dewpoint Advance SDK の実装

#include "dpa.h"

#define DPMID (('D' << 24) | ('E' << 16) | ('W' << 8) | 'P')

enum DpaIndex {
    DpaIndexId = 0,        // [I] 有効な場合 DPMID を返す
    // 中略
};

static volatile uint32_t* _dpa = (volatile uint32_t*)0x04801000;

int dpa_is_enabled(void)
{
    return _dpa[DpaIndexId] == DPMID;
}

Dewpoint Advance SDK には、「Dewpoint Bridge が利用できるか」を確認するAPI関数 dpa_is_enabled があります。

dpa_is_enabled は、0x04801000 から4バイトの固定値を読み取り、DPMID と一致するかを評価しています。

Dewpoint Runtime の実装

Dewpoint Runtime は GBA エミュレータの I/O フックを利用し、0x04801000 への読み込みアクセスが発生したときに DPMID を返します。

// mGBA のレジスタ入力フック
uint32_t DewpointRuntime::readRegister(uint32_t index)
{
    switch (index) {
        case DpaIndexId: return DPMID; // ← DPMID を返す
// <以下省略>

これにより、GBA 側のコードは「Dewpoint Runtime が利用できる環境」と「そうでない環境」(実機やほかのエミュレータ)を切り分けて処理できます。

※実機では、0x04801000 から読み取った値が DPMID と一致しないことを検証済みです。

ユーザープログラムの実装例

例えば、ゲームのリザルト画面で

  • スコアをリーダーボードへ送信
  • スコアが1000点を超えた時に実績「beginner」をアンロック

という要件は、次のように記述できます。

void show_result_screen(int score)
{
    // `board0` へ UGC を添付せずにスコアを送信
    dpa_leaderboard_send(0, score, 0); 

    // 実績をアンロック
    if (score > 1000) {
        dpa_achievement_unlock("beginner");
    }

    // リザルト画面表示処理(省略)
}

補足事項: dpa_leaderboard_send や dpa_achievement_unlock は、内部的に dpa_is_enabled を用いて「DPAが実行可能な環境」でのみ動作するため、このコードはGBA実機環境では 正常に無視 されます。

UGCデータを使ったリプレイ対応例

Steamのリーダーボードには、スコア(数値)に「UGCデータ」を添付できます。

数字だけのスコアでは、「どうやってそのスコアを取得したのか」が分からないため、私はスコアランキングにはリプレイが必要だと考えています。

そこで、Dewpoint Advance は、UGCデータを連携させたリーダーボードを標準でサポートしました。

リプレイデータの作成方法

リプレイデータは、毎フレームのキー入力を記憶することで作成できます。

しかし、GBAのWRAMは「リプレイ対応」をするにはやや不十分なサイズです。

そこで、Dewpoint Advance では ホストアプリケーション側のメモリ空間にUGCデータを作成するAPI を提供しています。

API Description
dpa_ugc_clear 共通 UGC バッファをクリア
dpa_ugc_append 共通 UGC バッファに 4 bytes のデータを追加
dpa_ugc_size 共通 UGC バッファのサイズを取得
dpa_ugc_download 共通 UGC バッファへのダウンロードを開始
dpa_ugc_read 共通 UGC バッファから 4 bytes 読み込む

リプレイデータの作成処理シーケンス

ゲームプレイ開始時に dpa_ugc_clear でクリアをした後、毎フレーム dpa_ugc_append で入力キー情報を記憶します。

リプレイデータの送信

dpa_leaderboard_send の第三引数に 1 を指定すれば、作成された UGC データをリーダーボードへ添付できます。

// `board0` へ `UGCを添付したスコア` を送信
dpa_leaderboard_send(0, score, 1); 

リプレイデータの再生

dpa_ugc_download でリーダーボードに紐づくUGCデータ(リプレイ)をSteam Cloudからダウンロードできます。

dpa_ugc_download はリーダーボードのランキングエントリ単位でダウンロードをリクエストする仕様です。

/**
 * @brief UGCデータを共通UGCバッファへダウンロード開始リクエスト
 * @param entry ダウンロード対象のリーダーボード・エントリ
 */
void dpa_ugc_download(LeaderboardEntry* entry);

正常にダウンロードできたら、メインループの 入力キー情報を取得 を dpa_ugc_read で読み出したデータに差し替えることで、リプレイを再生することができます。

/**
 * @brief 共通UGCバッファからデータを読み込む
 * @return UGCデータ (指定indexにデータが無い場合は -1 を返す)
 */
int32_t dpa_ugc_read(int index);

※乱数シードを確実に一致させる仕組みが別途必要です。

実際に販売してみた

Dewpoint Advance のシステムテストを兼ねて、

  • Steam クラウドセーブに対応
  • Steam アチーブメントに対応
  • Steam リーダーボードに対応(リプレイ対応)
  • 画面モード(フルスクリーン・ウィンドウ)の切り替えに対応
  • 終了メニュー(EXIT)を追加

などの 主要な特徴 を全て実装した Battle Marine Advance! をSteamで販売してみることにしました。

👉 https://store.steampowered.com/app/4975370

本書執筆時点(2026.07.22)ではまだ「近日発売」の状態です。

Battle Marine Advance! は、「有料で販売できること」の検証も兼ねて無料配信ではなく $1 で販売しています。

たとえ1円であっても、「有料」で販売すると「事業主としての責任」が発生するため、有料販売でテストすることが肝要だと考えています。

商用配信にあたっては、実際の配布物と依存関係を確認し、必要に応じて専門家または mGBA の作者と相談することを強く推奨します。

Battle Marine Advance! の販売は Dewpoint Advance の実運用例ですが、個別製品のライセンス適合性や法的安全性を保証するものではありません。

細かい補足事項:

  • GBA、Game Boy、および Game Boy Advance は、任天堂が日本(またはその他の地域)で保有する登録商標であることを J-PlatPat で確認しました。そのため、ゲームのタイトル(商標)に「for GBA」などを付けたい場合は、任天堂からの許諾が必要になるものと考えられます。
  • 「Advance」は一般的な英単語であり、任天堂の商標を示す表現としてではなく、独自名称の一部として「Dewpoint Advance」と「Battle Marine Advance!」で使用しています。これらの名称について、既存商標との混同可能性は低いものと私は判断しました。
  • Game Boy または Game Boy Advance の BIOS 機能(MP2k など)は使用しないでください。互換 BIOS で提供されていても、使用しないほうが安全です。
    • 例えば、将来的に mGBA より再現度が高く、より緩いライセンス体系の GBA エミュレータが登場したとき、Dewpoint Advance の組み込みエミュレータを変更する可能性があります。
    • しかし、mGBA が提供する互換 BIOS に依存すると、コアの移行が難しくなります。
    • また、GBA 対応ソフトはハードウェア機能だけでもゲームを実装しやすいため、BIOS 機能に依存すべき理由は少ないものと考えられます(BIOS 機能を使用しない devkitPro 用の SDKも存在します)。
  • 実機の場合、ROM ファイルのヘッダー領域に「Game Boy Advance」と書かれた画像データ(圧縮ビットマップ)を配置しなければゲームを起動できません。しかし、エミュレータではその制約を技術的に回避できるため、Dewpoint Advance では、パッケージを生成する際に配信データのその領域を自動的にマスク(0x00 で塗りつぶし)することで、任天堂が著作権を有する画像データの同梱による著作権と商標権の侵害行為 を回避しています。

生成AIの活用

生成AI(Claude Code または Codex)を用いれば、devkitPro + Dewpoint Advance に対応した GBA コード(C/C++)を即座に実装することができます。

実際、Battle Marine Advance! を dewpoint-advance へ対応する実装はすべて生成AI(Codex)に任せました。

以下、実際に私が使用した Codex への指示プロンプトを例示します。

リプレイ対応処理の実装指示(Codex)

GBA版モジュール(./gba/src/*)で dewpoint-advance の SDK を用いてリーダーボードへスコアとリプレイデータを送信する処理を実装してください。

ゲームプレイを開始時に dpa_ugc_clear でリプレイデータをリセットしてから毎フレームキー入力データを dba_ugc_append する方式でリプレイデータを記録します。(ただし、startボタン = ポーズ & ポーズ中の操作は記憶しない)
ゲームプレイ終了時に dpa_leaderboard_send でスコアとリプレイ(ugc)データを対象のリーダーボード(※)へ送信してください。
※リーダーボードは board0 (normal mode) と board1 (master mode) の 2種類があります。

デバッグ用にタイトル画面で cursor == 0 の時に L/R ボタンを押しながら START ボタンを押すことで、直前のリプレイデータを再生できるものとします。
デバッグ用のリプレイ再生は「直前のプレイ」であって、dpa_ugc_clear しなければ残っているはずです。(dpa_ugc_size がゼロならデータ無しとして実行しない想定)

リプレイ再生モードでは、獲得ランクの増加(アチーブメントのアンロック)、ハイスコアの更新、スコア送信はしないでください。
なお、リプレイ再生モードでは基本的にユーザー入力を受け付けませんが、ポーズ & ポーズ中の操作に限り許容されるものとします。

リーダーボードの実装指示(Codex)

gba側モジュールにリーダーボード画面を実装して、titleからの遷移を実装してください。(現状、titleではリーダーボードを選択するとexitしています)

画面仕様は次の通りです:

    LEADERBOARD - NORMAL
-----------------------------
   1 YAMADAYAMADA 9999999990
   2 HOGE               9000
   4 HOGE               8000
   5 HOGE               7000
   6 HOGE               6000
   7 HOGE               5000
   8 HOGE               4000
   9 HOGE               3000
  10 HOGE               2000
  11 HOGE               1000
              :
  98 HOGE                 30
  99 HOGE                 30
 100 HOGE                 20

 ??? MY NAME              10
-----------------------------
    [A] REPLAY / [B] BACK

- BG0 にカーソルを描画 (キャラクタパターン0x9F: 青い塗りつぶし)
- BG1 にテキストを描画
- ダウンロード中は画面中央に `LOADING_` を表示
- リーダーボードの読み込みが ready になったら dpa_leaderboard_get/dpa_leaderboard_getmで取得したランキング情報を表示
- ランキングは1画面に16行表示(最大101レコード)
- 上下でカーソル移動
- 自分のスコアがある場合、初期カーソル位置は自分のスコア
- 左右/LRでボード切り替え(NORMAL / MASTER をトグル)
- 初期表示ボードは直前にプレイしたモード
- NAME: 13文字目以降は切る, 英小文字→英大文字, 0x20~0x2F 以外の記号とASCIIコード以外の文字は全て ? に置き換える
- SCORE: 1桁目は常に `0` でスコア値は 2桁目以降
- Bボタンでタイトル画面へ戻る
- Aボタンで決定したら window_draw + vdp_print_bg で BG2/3 に `DOWNLOADING_` のダイアログを表示してUGCをダウンロード(完了したらリプレイ再生へ遷移)

余談ですが、私は Codex や Claude Code へ依頼する時に敬語を使っています。

相手は人間ではないから不要な配慮かもしれませんが、命令口調ばかり使っていると Slack などで誤って非人動的な発言をしてしまいそうなので、敢えて 人間に依頼する時と同じ口調 での指示を心がけています。

エミュレータ・コア

Dewpoint Advance のエミュレータ・コアには、信頼と実績の mGBA を採用しました。

商用利用するのでれば、エミュレータ・コアも自作した方が安全です。

しかし、以前 Z80 エミュレータを自作したとき、完成までに数年を要しました。

エミュレータの開発は非常に大変です。

現在であれば、Claude Code や Codex の活用によってエミュレータの開発難易度は下がったと思いますが、それでも完成までには相当な時間を要するものと想定できます。

そこで、今回はサードパーティー製の OSS を利用することにしました。

サードパーティー製の OSS を利用するうえで、最も注意しなければならないのが「ライセンスへの適合」です。

そして、mGBA のOSSライセンスは MPL 2.0(Mozilla Public License 2.0) です。

ライセンスへの適合

ある意味、この点が Dewpoint Advance の肝となる部分かもしれません。

mGBA を利用するうえで最大の懸念点は、ライセンス違反かと思われます。

mGBA に限らず、OSS エミュレータのライセンス体系には複雑なものが多くあります。

mGBA を安全に商用利用したい場合、mGBA の作者に相談するのが最善です。

この点については、mGBA の README でも言及されています。

mGBA の README より抜粋:

If you are a game publisher and wish to license mGBA for commercial usage, please email licensing@mgba.io for more information.

一方で、README には mGBA は MPL 2.0 であること も記載されています。

MPL 2.0 は商用利用が認められている「自由」なライセンスなので、ライセンス上の要件を満たせるのであれば、商用利用する場合でも 作者との個別契約は必須とは限らない と解釈できます。

MPL 2.0 への適合

MPL 2.0 は、ファイル単位で公開義務が伝播するコピーレフトライセンスです。

そのため、プログラム単位で公開義務が伝播するコピーレフトライセンス(GPL など)と比べれば、商用利用のハードルは低いと考えられます。

MPL 2.0 の要件は、対象コードを含むファイルとその変更を、配布物の受領者がソースコードとして入手できるようにすることなどです。

Dewpoint Advance は、mGBA のコードを含むリポジトリを OSS として公開しています。そのため、Dewpoint Advance の利用者は、次の内容をストア説明文などのユーザーが確認できるドキュメントに明記することで、ライセンス要件に適合できるものと考えました。

  1. Dewpoint Advance を利用している旨
  2. MPL 2.0 が適用されたmGBAのソースコードが含まれる旨
  3. MPL 2.0 が適用されたソースコードは、GitHub の suzukiplan/dewpoint-advance から入手できる旨

使用した suzukiplan/dewpoint-advance のコミットハッシュも記載しておくと、より安全です。また、Dewpoint Advance や mGBA の改造が必要な場合は、suzukiplan/dewpoint-advance のフォークを使用することもできます。

(記載例)

## License

- The application uses Dewpoint Advance
- Dewpoint Advance includes MPL 2.0-licensed code (mGBA)
- The source code disclosed under the MPL 2.0 license is available in the `suzukiplan/dewpoint-advance` repository on GitHub

Steam のストア説明文には URL を記載できないため、上記のような書き方にする必要があります。(URLを記載するとストア審査でリジェクトされます)

ただし、あくまでもこれは、法律の素人である私の個人的な見解です。

実際のライセンス適合性については、国際的な知的財産権(特に OSS 分野)に詳しい法律の専門家(弁護士など)に相談したうえで、慎重に判断することを強くおすすめします。

余談

Dewpoint MD なども面白いかもしれませんね!

このメカニズムはGBAに限らず色々と応用ができるものと想定しています。

1
0
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?