わどるDAILY

つれづれと

mGBAでLuaScriptを書いた話

タイトル通り。


mGBAはGBA用のエミュレータで、多くのTAS用エミュレータと同じくLuaによるスクリプトが導入されています。

通常であれば使う機会はほぼ無いのですが、わけあってmGBA用にLuaを書いたので備忘録として共有します。

Luaを書いた経緯

こちらの記事でも語ってますが、「星のカービィ 鏡の大迷宮」というソフトで、通信プレイ環境での敵のHPメモリを見ることになったのがきっかけです。

例えば、TAS用エミュレータであるBizHawkでは簡単にメモリを見ることが出来ますが、通信プレイ環境を構築するのは非常に困難です。というかボクが出来ない。

mGBAは通信プレイ環境が簡単に構築でき、かつメモリビューアも備わっているため上記の条件を満たすにはうってつけのエミュでした。

さらに、2022年5月にScripting機能が追加されたため、Luaによるスクリプトを動作させることも可能になりました。

自分の調べたいメモリが動的で普通にメモリビューアで見るのは難しいものだったため、自前でスクリプトを書く運びとなりました。

BizHawkのLuaとの違い

Luaスクリプトを書くという点ではBizHawk含めたTAS用エミュレータと共通していますが、同じように動かすには一工夫必要になります。

また、エミュレータLuaと言えば画面内に色んな情報が表示されるイメージがありますが、2023年10月現在ではGUI関連のAPIが備わっていないため、基本はテキストベースでの表示となります。

リアルタイムで情報表示をすると言った使い方ならテキストバッファを使うことが多くなるでしょうので、そのためのコードの書き方を記します。

Scripting画面


Scripting画面は大きく分けてコンソールテキストバッファに分かれます。

コンソールはコンソール出力画面とコマンドラインがそれぞれあります。

コンソールへの文字列出力はconsole:log()でも使ってください。


テキストバッファはテキストバッファオブジェクトを生成し、その中でテキスト操作をした結果を表示します。

左のバッファリストには生成したテキストバッファオブジェクトの表示名が並びます。

情報表示スクリプトの書き方

mGBAで用意されているAPIはこちらの公式ページにあります:

Scripting API - mGBA

サンプル:テキストバッファへの表示

-- テキストバッファオブジェクトの生成
myBuffer = console:createBuffer("buffer")

-- テキストバッファに文字列を表示
myBuffer:print("Hello World!")
  • 結果


  • テキストバッファに表示するには、一度テキストバッファを生成し、その中で文字列を表示する必要があります。
    • 上記のスクリプトの場合、オブジェクト名はmyBufferですが、リスト上の表示名はbufferになります。この2つは異なりますので注意しましょう。
  • print()はString型しか受け付けないため、それ以外の変数に関してはtostring()などで型変換しておきましょう。

サンプル:カービィの現在位置の表示

-- カービィ(1P)の現在位置表示
kirbyInfo = console:createBuffer("kirbyInfo")

-- frameコールバックで呼び出す関数
function updateBuffer()
    local kirbyRoom = emu.memory.wram:read16(0x020FE6) -- カービィの部屋番号を取得
    local kirbyX = emu.memory.wram:read8(0x020F21) + emu.memory.wram:read16(0x020F22) * 256 -- カービィのX座標を取得
    local kirbyY = emu.memory.wram:read8(0x020F25) + emu.memory.wram:read16(0x020F26) * 256 -- カービィのY座標を取得

    -- String型に変換
    local kirbyRoomStr = tostring(kirbyRoom)
    local kirbyXStr = tostring(kirbyX)
    local kirbyYStr = tostring(kirbyY)

    -- テキストバッファに出力
    kirbyInfo:clear()
    kirbyInfo:print("Room: " .. kirbyRoomStr)
    kirbyInfo:moveCursor(14,0)
    kirbyInfo:print("X: " .. kirbyXStr)
    kirbyInfo:moveCursor(28,0)
    kirbyInfo:print("Y: " .. kirbyYStr)
end

-- フレームが終了した時にupdateBufferを実行
callbacks:add("frame", updateBuffer)
  • 結果


  • メモリの読み込みはemu:read8()等を使います。
    • メモリドメインを指定する場合は上記のようにemu.memory.wram:read8()と表記します。
  • テキストバッファは等幅文字のため、見やすさを考えるならmoveCursorメソッドなどで横のテキスト位置を固定にすると良いでしょう。
    • ちなみにカーソルを列移動するadvanceメソッドは何故かクラッシュしてしまうので非推奨。
  • フレーム毎に処理を実行するにはcallbacksを利用します。
    • 他のエミュだとループの最後にemu.frameadvance()などを置く記述がほとんどなため少し戸惑うかもしれません。

おわりに

以上のコードはあくまで一例になります。

冒頭でも触れた通り、mGBAでスクリプトを書く機会はほとんど無いと思われます。

とはいえ、通信プレイ上での検証など局所的に必要になる場面が出てくるため、その時の資料として活用いただければと思います。(ただでさえ資料が少ないので)

mGBAも開発が現在進行形で進んでいるため、今後Scriptingにアップデートがなされる可能性も大いにあるでしょう。