「N64 エミュレーターをもとに作ったのですか?」「ゲームを作り直したのですか?」とよく聞かれますが、どちらでもありません。Marchwind 64 は静的再コンパイル(static recompilation、略して recomp)という方法を使っています。原作カートリッジのプログラムを関数ごとに C 言語へ翻訳し、それを現代のコンパイラで Windows・macOS・Linux・Android のネイティブプログラムにしています。
エミュレーション、リメイク、再コンパイル
- エミュレーターは、パソコンを N64 に見立てて動かします。ゲームの実行中に原作の MIPS 命令を一つずつ解釈し、N64 の CPU・グラフィックスチップ・オーディオチップをまねます。ゲーム自体は手を加えませんが、すべての命令がこの層を通ります。
- リメイクは原作をもとに新しくゲームを作ることです。画面もルールもストーリーも作り直すため、原作と完全に同じにするのは困難です。
- 再コンパイルはその中間です。ゲームのロジックは原作そのもので、あらかじめネイティブコードに翻訳しておきます。戦闘計算、敵の思考、イベントスクリプト、セーブ形式は原作どおりに動き、実行時に CPU をまねる必要もないため、原作の関数の境目に新しい機能を付け足すこともできます。
たとえるなら、エミュレーターはその場で一文ずつ訳す同時通訳、再コンパイルは本を丸ごと翻訳してから出版すること、リメイクは同じ物語で別の本を書くことです。
カートリッジからネイティブプログラムへ
- コードを見つける。 原作のプログラムは 18 の部分に分かれています。1 つは常にメモリにあり、17 は必要に応じてカートリッジから読み込まれます(タイトル、マップ、戦闘、インターミッション画面など。同じメモリを使い回す部分もあります)。それぞれのカートリッジ上の位置と実行時のアドレスを特定し、合計 3,407 の関数を洗い出しました。
- C に翻訳する。 オープンソースの N64Recomp で、各関数の MIPS 命令を同じ動作をする C コードに変換します。原作のメモリ配置と計算方法はそのまま保たれます。
- システム層を置き換える。 原作は N64 のシステムライブラリ(スレッド、メッセージキュー、タイマー、コントローラー、セーブ)に依存しています。N64ModernRuntime がこれらをパソコンのスレッドやファイルで実装するので、翻訳されたコードは N64 の上でないことを意識せずに動きます。
- 画面と音。 N64 の画面は、グラフィックスチップに送る「ディスプレイリスト」で描かれます。ゲームは今もこれを作り、RT64 が Direct3D 12・Vulkan・Metal で現代の GPU に描画します。ワイド画面や高解像度テクスチャへの差し替えもここで行います。オーディオチップで動いていた音声プログラムも、ネイティブコードに再コンパイルしています。
- コンパイルする。 生成した C コードと自前のコードを、各プラットフォームのコンパイラでまとめてビルドします。
ゲームの画像・音・テキストは今もご自身の ROM から読み込むため、日本版の ROM を用意していただく必要があります。
原作の上に機能を足す
原作の関数がふつうの C 関数になったことで、特定の関数を引き継いだり、前後に処理を挟んだりできます。現在その箇所は 134 です。主な用途は次のとおりです。
- ネイティブ画面。 会話、インターミッションのメニュー、改造、強化パーツ、セーブ、設定などの画面を RmlUi で描き直し、中国語・英語・日本語、高解像度の文字、タッチ操作に対応しています。ゲームの状態は原作のコードが管理したままで、原作の画面にもいつでも戻せます。
- 翻訳。 セリフはすべてテキストファイルとして同梱し、画面やデータのテキスト(人名、機体、武器、精神コマンドなど)は用語表で管理して、実行時に言語ごとに差し替えます。プレイヤーが自分で書き換えることもできます。
- HD アート。 顔グラフィック、背景、マップ、立ち絵、戦闘演出などを描画時に高解像度の画像へ差し替えます。ゲームのロジックには手を加えません。
- 遊びやすさ。 複数のセーブ枠とオートセーブ、早送りとスキップ、戦闘演出の中断、キー割り当て、フィルターとベゼル、切り替え可能な原作の不具合修正など。
- 開発ツール。 内蔵のデバッグインターフェースで、AI ツールがゲームの状態を読み、ボタンを押し、スクリーンショットを撮れます。自動テストや不具合の再現に使っています。
自分たちで書いた部分は C++ で約 3.6 万行(ランタイム、画面、翻訳、HD、プラットフォーム対応)、ほかに Python のツールが約 4 万行あり、スクリプト解析、翻訳パイプライン、アートのパッケージ化、テスト、リリースを担っています。
いくつかの具体例
会話。 ストーリーは今も原作のイベントスクリプトで進みます。誰が話すか、どのセリフが次に来るか、いつボタンを待つかは原作のコードが決めています。スクリプトがセリフを表示する瞬間に、そのセリフの番号と話し手を読み取り、番号をもとにセリフファイルから現在の言語の訳文を取り出して、同梱の HarmonyOS Sans フォントで高解像度に組んで描画します。原作の日本語フォントは数千の固定字形しかありませんが、この方法ならその制約を受けず、一文ごとに改行し直してページ送りを減らすこともできます。
インターミッション画面。 原作のシナリオ間メニューは、ディスパッチテーブルで画面を切り替えています。その入口で、ネイティブ版がある画面なら新しい画面に入力と描画を引き継ぎ、プレイヤーが決定したら結果を原作のコードに返します。たとえば機体の改造では、資金の消費や能力値の上昇は原作の処理を通るので、価格や上限は原作と同じです。
セーブ。 原作のセーブの読み書きは、すべてカートリッジのセーブメモリ(SRAM)を転送する一つの関数を通ります。私たちはその転送先だけを別のファイルに向けています。32 KiB のカートリッジセーブはそのまま残るため、ares・Project64・mupen64plus・RetroArch などのエミュレーターとセーブをやり取りできます。追加のセーブ枠やオートセーブは別ファイルで、中身は原作が書き出すのと同じバイト列です。チェックや復元も原作のコードが行います。
戦闘演出のスキップ。 原作には設定で戦闘アニメをオフにする機能があり、オフのときは別の経路で戦闘を処理します。演出を中断したときは原作のこの「アニメなし」の経路に切り替えるので、ダメージ、命中、経験値はアニメをオフにしたときとまったく同じで、飛ばしたフレームが結果に影響することはありません。
ワイド画面。 RT64 は 3D の画面を横に広く描けますが、ゲーム自体の 2D 要素(ステータスバー、カーソル、テキストボックス)は 4:3 に合わせて配置されています。これらの要素ごとにワイド画面での位置を設定し、16:9 いっぱいに表示してもインターフェースがずれないようにしています。
HD アートの組み込み方は「HD アートの制作工程」をご覧ください。
原作と同じであることの確かめ方
- エミュレーターとの比較。 原作の挙動を確かめたいときは、同じ ROM と同じセーブを ares エミュレーターで動かし、画面ごとに比べます。
- まずコードを読む。 戦闘の計算式、キー割り当て、隠し要素などは原作のコードから調べて文書にしてから、どう組み込むか、どう直すかを決めます。原作の不具合修正はすべてオフにできるスイッチにしています。
- 自動テスト。 約 450 のテストでデータの書き出し、翻訳ファイル、画面の文言などを確認しています。さらにオフラインのレイアウト検査で、ゲームを起動せずに全画面を各言語・各画面サイズで組み、枠からはみ出す文字を探します。
- ゲームの自動操作。 内蔵のデバッグインターフェースを通じて、スクリプトや AI ツールがゲームを起動し、ボタンを押し、スクリーンショットを撮り、メモリの状態を読めます。プレイヤーから報告された問題の再現に使っています。
まだできないこと
再コンパイルは原作のロジックをすべて残すので、テンポもそのまま残ります。ゲームのロジックは毎秒 30 フレーム固定で、待ち時間やアニメ、スクロールはすべてフレーム単位で数えているため、単純に 60 フレームにはできません。将来、表示の側でフレームを補間するしかありません。同じく、敵の行動や戦闘結果など原作のロジックが決めることには、初期設定でオンになっていてオフにもできる修正を除いて、手を加えないようにしています。
ひとつのコードで 5 つのプラットフォーム
macOS・Windows・Linux・Steam Deck・Android は同じコードを使い、描画にはそれぞれ Metal・Direct3D 12・Vulkan を使います。コミットのたびに、GitHub で Windows・Linux・Android 版が自動でビルドされます。
謝辞
これらのオープンソースプロジェクトがなければ、Marchwind 64 は生まれませんでした。N64Recomp と N64ModernRuntime(再コンパイルとランタイム)、RT64(グラフィックス)、RmlUi(インターフェース)、librashader(RetroArch のフィルター)、そして Zelda 64: Recompiled をはじめとする先行プロジェクトの知見に感謝します。ソースコードは GPL-3.0 で GitHub に公開しています。