メガドラ開発:LUAを使う

 私はメガドラゲームの開発ではBizHawk(EmuHawkって書いてあるときもあるんですがどっちなんでしょうね?)を主につかっています。BizHawkではLuaが使えますので、デバッグで利用していくと便利そうです。最近使い始めたので、メモ程度に情報を残しておきます。

使い方はメインメニューからTools/Lua Consoleを選びます。

LuaConsoleの画面

 基本的にはあらかじめLuaスクリプトを書いておいて、このウィンドウで実行することになります。とりあえず簡単なLuaスクリプトを書いてみます。

function test()
	gui.drawString( 0, 0, "Hello World" )
end

while true do

	test()

	emu.frameadvance()
end

 上記コードをtest.luaとして保存しLua Consoleから開きます(ゲーム実行中にフォルダアイコンを選択してLuaファイルを選択)。開くと自動的に実行されます。正常に動作していれば、画面左上にHello Worldが表示されるはずです。
 このエミュレータにおいてLuaはコード内に無限ループを入れておかないとそのまま終了されてしまうようです。その際にはemu.frameadvance()を入れておかないとエミュレーター側に処理を戻さなくなりフリーズしたような状態となりますので注意してください。
 コードを書き換えたときは、Lua ConsoleウィンドウのScript/Refresh(F5)で更新できます。
 使用できる関数はLua ConsoleのメニューにあるHelp/Lua Functions List(F1)で表示することができます。

Lua Functions List

 Returnは戻り値。Libraryは関数の前にピリオドを挟んで記述します。先ほどのgui.drawStringやemu.frameadvanceのような感じで。Parametersは引数です。

memory.read_u16_be( long addr, string domain )

 メモリを読み取る関数です。関数名のu16はunsigned short(16bit)の意味です。ここをu8, u16, u24, u32にすれば読み込むサイズも変更できます。もちろん符号付でも大丈夫です(例:s16)。関数名最後の_beはビッグエンディアンという意味です。メガドラは68Kですのでビッグエンディアンですね。このエミュはいろんなハードに対応しているためリトルエンディアンと両対応しています。引数のアドレスはそのままですが、ドメインは読み取るメモリの種類です。Tool/Hex Editorでメモリ内を閲覧できますが、このウィンドウのOption/Memory Domainsからメモリの種類を変更できます。で、この名前がそのまま引数の文字列として使えます。具体的には以下のような感じで。

  • 68K RAM

  • Z80 RAM

  • MD CART

  • SRAM

  • CRAM

  • VSRAM

  • VRAM

  • M68K BUS

 それぞれアドレスの最大値が異なりますので注意してください。SGDKでポインタにアドレスを入れる場合32ビットとなりますが、この時0xE0FF0000といった感じで先頭にE0が着くのですが、このままだとM68K BUSのアドレスとしては使用できません。0xE0000000引くか、下位2byteのみ取得して68K RAMで取得した方が良いかもしれません。

gui.drawBox( X1, Y1, X2, Y2, LineColor, BoxColor )

 画面に矩形を描画します。X1,Y1は左上座標、X2,Y2には右下の座標を指定します。LineColor,BoxColorはLua Colorで指定します。色の順番がARGBなので間違えないよう。16進数表記の場合は0xFF001122みたいな感じで。ヒットコリジョンなんかを表示するのに良さそうですね。

gui.drawString( X, Y, string )

 画面に文字列を描画します。Luaでは文字列の連結はピリオド2つです。

gui.drawString( 0, 0, "Player pos X:" .. pl_pos_x )

 アドレスなんかを表示したい場合は16進数にしたいですね。その場合はstring.formatを使用します

gui.drawString( 0, 0, string.format( "ADDR:%x", pAddr ))

 C言語のフォーマット指定子と同じですね。%xで16進数、%dなら10進数です。もちろん%08X(先頭を0で埋めつつ8桁表示、大文字使用)といった表記も可能です。全然関係ないですが、luaの紹介サイトを見ているとビットシフトが使えるようになったと記載されていましたが、BizHawkのLuaはバージョンが古いのか利用できませんでした…。

emu.frameCount()

 ゲーム初期化時にはメモリの状態が不定のため、最初のフレームからLuaを実行することになるとおかしな挙動となることがあります(実行中にゲームをリセットしたときなど)。そこでこの関数でゲームの経過フレーム数を取得して最初の数フレームはスキップした方が良さそうです。

while true do
	if emu.framecount() > 0 then
		testFunc()
	end

	emu.frameadvance()
end

他に良い方法もあるかもしれません。いろいろ試してみてください。

最後に

 今回の記事は自分も勉強中のことをまとめているため、雑多な感じとなっています。場合によっては、後日追記や修正をちょこちょこと入れるかもしれません。
 現実問題、Luaでデバッグを行うにはゲーム内の様々な要素のアドレスを知る必要があります。ビルド時に出力されるsymbol.txt内に各データのアドレスが記載されていますが、コードをちょっと変更するとアドレスもちょくちょく変わってしまうため面倒臭いことこの上ないです…。とりあえずデバッグ用の情報をまとめた構造体を作っておき、このアドレスから有用な情報をLua側で利用できるようにしていますが、それでも構造体のアドレスは調べないといけないのが面倒です。SGDKのデバッグ情報をエミュレーターが拾ってくれると良いのですが…。

追記:symbol.txtを読んでみる

かなりベタの方法になってしまったが、symbol.txtを読んでアドレスを毎回読む方法を模索してみた。

function read_symbol()

	local f = io.open('D:/project/test/out/symbol.txt', 'r')
	local adr_pl_pos_y

	for line in f:lines() do
	    -- 変数名で検索してアドレスを抽出
		if string.find( line, "gPl_pos_y" ) ~= nil then
			-- 3文字目から8文字目までを文字列として取得(アドレスの下位6byte)
			local adr = string.sub( line, 3, 8 )
			-- 16進数の文字列を数値に変換
			adr_pl_pos_y = tonumber(adr,16)
		end
	end
end

本当は読み込んだROMイメージのパスでも取得できれば良いのだけど、そういったコマンドはなさそうなので絶対パスで一本釣りしちゃいました。文字列検索して変数名がヒットしたらアドレス値を取得、16進数文字列を数値に変換って感じです。もうちょいスマートな方法が見つかったらいずれ更新したい。

この記事が気に入ったらサポートをしてみませんか?