DeSmuMEにてLuaスクリプトを導入し、解析orTAS制作を手助けする

この記事はニンテンドーDS向けエミュレータの一つである「DeSmuME」（以降Desmume）にLuaを導入し、解析やTAS制作を手助ける方法を知る記事です。

:::note warn
注意
この記事ではエミュレータの導入については取り扱いません。導入については前回の記事を参照ください。
→https://qiita.com/gestoC_moti/items/ac1863f25a225066a043
:::

Desmumeの機能の`memory search`や`View memory`で目的メモリ番地を特定し、`memory watch`でメモリ値を監視することができる。しかしこれはメモリ値を監視するだけで、四則演算等の処理をかけることはできず解析で困ることが多々ある。

そこでDesmumeの機能として`Luaスクリプト`を使用することができるのでそれを活用して解析を手助ける。

# Luaスクリプトとはなんぞや

`Luaスクリプト` （これ以降`Lua`）は1993年[^1]に開発されたスクリプト言語で現在も開発が進んでいる。動的型付け言語であるものの実行は早く、C言語をホストプログラムとして組込むことを想定した言語であるためC言語系と相性が良い[^2]（他言語でも組み込み可）

現行バージョンは`5.4.x`になるが、今回用いるdesmumeは`5.1`に対応しているので、`5.2`以降の機能は使えない。
例:`bit32`ライブラリは使用できない。代わりに`bit`ライブラリを使用する必要がる。

リファレンスマニュアル（英語）は以下の通りである。
https://www.lua.org/manual/5.1/
日本語で読みたい場合は有志が翻訳したものがたくさんあったはずだが、軒並みSSLエラーや404notfoundが返ってくるためWebArchiveなどで探す必要がある。（現代の機械翻訳の精度はまあまあ高いのでそれで読んでしまうのは多いにありではある。）

# Luaスクリプト　環境構築
`Lua`をDesmumeで動かそうとLuaスクリプトを読み込ませても実は動かない。最初の`.zip`にはLuaのDLLファイルが含まれていないためだ。そこでDLLの入手から行う。
（あなたがもし`lua51-64bit.dll`を持っているのであれば手順１は飛ばしてください。動画編集ソフトである`Aviutl`を導入しているのであればAviutlのプラグインで入手している可能性があります。[^3]）

:::note info
導入したDesmumeが32bit版のものであれば、32bit版のlua51.dllを入手してください。
:::


手順１（安全性が高い[少し面倒]）

:::note info
公式サイトからダウンロードする方法です。過去のバージョンになるので少し面倒です。　ダウンロード後はほとんどのファイルが不必要なものです。
:::

Luaの公式サイトである以下のページから`LuaBinaries 5.1.5 - Release 1`の`lua5_1_5_Win64_dll8_lib.zip`をクリックする。
https://luabinaries.sourceforge.net/download.html
その後SOURCEFORGEに飛ぶので`5.1.5`→`Windows Libraries`→`Dynamic`でディレクトリ移動をし、`lua-5.1.5_Win64_dll17_lib.zip`をダウンロードする。

:::note info
解凍後DLL以外は削除してかまいません。
:::

手順１（安全性が低い[とても簡単]）

:::note warn
必要なファイルだけをダウンロードするだけでとても簡単ですが、そのサイトを信用するかは自己責任です。
:::

好きな検索エンジンで`lua51 dll 64bit`と検索をして`lua51-64bit.dll`などのバージョンが`5.1`で`64bit版`を入手してください。dllをダウンロードするためそのサイトを信用するかは自己責任です。
<br>
手順２
手順１にて入手した64bit版lua51.dllを`lua51.dll`にリネームを行い、Desmumeがあるディレクトリにファイルを置く。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3673555/c1f8bd88-4dcb-e5f1-072f-476ce47159d2.png)
<br>
これで環境構築は行えました。まずはテストプログラムを動かして導入できているかを確認します。
メモ帳を開き以下のコードを書きます。メモ帳以外のコードエディタを使用している場合は言語をLuaに設定しコードを書き始めてください。

:::note 
筆者はメモ帳でプログラミングする人間なのでその前提で記事を書きます。特に理由がなければvscodeあたりでも使ってください。
vscodeブラウザ版→https://vscode.dev/
:::

``` lua:test.lua
local function Fn()

gui.text(0,10,string.format("Hello World!"))

end
gui.register(Fn)
```
標準入出力を使わない上に関数を使用しているなど突っ込みたいことが山ほどあるでしょうが、理由はあとで説明します。

名前を付けて保存から`test.lua`を保存します。Luaスクリプトの拡張子は`.lua`です。保存先はDesmumeを置いている同ディレクトリにluaフォルダを作成しその中に保存するのがオススメです。

:::note warn
どこに保存しても構いませんが、パスに2byte文字を含めてはなりません。
:::

Desmumeを起動し、`Tools`->`Lua Scripting`->`New Lua Script Window ...`を選択し以下のウィンドウを表示させる。
<img width="256" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3673555/c7e92dfa-3af1-799a-1e53-3df09c678951.png">
`Browse...`から先ほど保存させた`test.lua`を選択する。
ウィンドウのOutput Consoleに`script returned but is still running registered functions`が出力されるか、下記の図のように下画面上部にHello World!が出力されていることを確認してください。
この時点でlua51.dllに関するエラー文がOS側から出力された場合は導入手順１から見直してください。
<img width="256" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3673555/5029a5a0-70ed-4dce-4958-21ed4d22a9de.png">

# Luaを使ってメモリ値を表示させるプログラムを書く前に必要な知識
ifやwhileなどの基本的な文の書き方は先に述べたリファレンスマニュアルを参照してください。ここではDesmumeで表示させる場合の基礎知識を述べます。
<br>
### 文字列主力先
Desmumeでluaを使う際に指定できる文字列出力先は３つで、
`stdout`
`output console`
`guiAPI`
の３つです。
`stdout`はLuaScriptウィンドウのstdoutチェックボックスから有効化するとそちらに出力するようになります。Windowsのコンソールに出力されます。コンソールウィンドウはTools -> Console -> Enableから表示できます。エミュレーターのあらゆる出力がされるのでメモリ値管理などには向いていません。
`output console`はLuaScriptウィンドウに出力されます。エラー文を実装した場合などに出力をする場合もありますが、リアルタイムでのメモリ値表示には向いていません。
`guiAPI`はDesmumeで用意されたAPIで先ほどの`test.lua`のように画面に重ねて表示ができるものになります。メモリ値の表示には一般的にはこのAPIを利用することが多いでしょう。

### 関数のループ
メインの関数はループさせることが望ましいです。ループをさせないとどのようになるか、以下のコードを実行して確認しましょう。

``` lua:bad.lua
gui.text(0,10,string.format("Hello World!"))
```

実行をすると約１秒ほど表示された後に文字が消えます。表示させる続ける場合には実行をループさせる必要があります。主に以下のループ方式があります。

``` lua:loop.lua
-- ここに宣言
while true do
    -- ここにループさせたい素敵なコードを書く
emu.frameadvance()
end
```

``` lua:recommend.lua
--ここに宣言
function fn()
    --ここにループさせたい素晴らしいコード書く
end

gui.register(fn)
```
ループさせたい場所に`gui.text(0,10,string.format("Hello World!"))`を追加して実行してみると、どちらも`Hello World!`が表示され続ける。最初の形式が一般的ですが、Desmumeの場合他のエミュレーターと異なる方法で動作しているので、２番目の方法でループさせる必要があります。[^4]


### ループの際に呼び出すレジスタ関数
Desmumeに限らず、luaがサポートされているどの関数を呼び出すかを把握している必要があります。

### emu.frameadvance()
while true do --- endの間に入れて動作させます。厳密にはレジスタ関数ではなくフレームを進めるAPIを呼び出しているだけです。この関数を使うメリットは確実に全てのフレームで更新されることです。
ただしDesmumeでは決して使ってはなりません。フレームカウンターは増えていきますが、描画更新されないため全く役に立たない関数です。またこれを入れたluaを動作させながらのTAS制作はdesyncを誘発する可能性があるので決して使用してはなりません。

``` lua:loop.lua
-- ここに宣言
while true do
    -- ここにループさせたい素敵なコードを書く
emu.frameadvance()
end
```

### gui.register(function)
この関数はエミュレータが描画を更新するたびに呼び出されます。全てのエミュレータで全フレーム毎に呼び出される保証はありません。一時停止などを行わない場合は重大な情報を見逃したり、メモリ値をトリガーに作動するコードを書いた場合に上手く認識できない場合があります。本来はフレーム毎に呼び出される関数ですが、Desmumeの場合は常に呼び出されるため非常に便利な関数であり、Desmumeの場合はこれを使うべきです。

``` lua:recommend.lua
--ここに宣言
function fn()
    --ここにループさせたい素晴らしいコードを書く
end
gui.register(fn)
```

### emu.registerbefore(function)
この関数はエミュレータが描画をする直前に呼び出される関数です。一時停止をすると呼び出されず文字列出力しないため、Desmumeの場合はbot制作に向いている関数ですが、メモリ表示には向いていません。

``` lua:forbot.lua
--ここに宣言
local function Fn()
  --ここにbotのための良いコードを書く
end
emu.registerbefore(Fn)
```

### emu.registerafter(function)
この関数はエミュレータが描画をした直後に呼び出される関数です。`emu.registerbefore()`と同じく一時停止をすると呼び出されないため使い方に注意です。

``` lua:forbot.lua
--ここに宣言
local function Fn()
  --ここにbotのためのすごいコードを書く
end
emu.registerafter(Fn)
```

# メモリ値の表示
メモリ値の表示には`memory search`で予め探しておいたメモリ番地が必要です。
流れとしては`memory search`でメモリ番地の特定→`memory`ライブラリで読み込み→`gui.text`で表示が流れです。前回の記事ではNewスーパーマリオブラザーズDSでのX座標とY座標のメモリ番地を特定しているのでこれを画面に表示させるサンプルコードは以下の通りです。

:::note info
memory searchとは何ぞやとなった場合は前回の記事後半部を参照ください。
→https://qiita.com/gestoC_moti/items/ac1863f25a225066a043
:::

``` lua:NSMB_XY.lua
function fn()
local X,Y

X = memory.readdword(0x0208AD20)
Y = memory.readdword(0x0208AD24)

gui.text(0,10,"X: "..X)
gui.text(0,20,"Y: "..Y)

end
gui.register(fn)
```

表示されている値は何も変換をしていないので大きな数字が表示されていますが、これが基本形です。

### memoryライブラリ
memoryライブラリには31個の関数が登録されていますが、ここでは読み込みでよく使うものを説明します。[^5]
<br>
`memory.readbyte()`：引数に指定したメモリ番地の1byteのメモリ値を戻り値として返します。
`memory.readword()`：引数に指定したメモリ番地の2byte(half word)のメモリ値を戻り値として返します。
`memory.readdword()`：引数に指定したメモリ番地の4byte(word)のメモリ値を戻り値として返します。

### guiライブラリ
guiライブラリは15個の関数が登録されていますが、ここでもよく使われるものを説明します。[^6]
<br>
`gui.register()`：引数に指定した関数を画面描画毎に呼び出しその結果が戻り値して返ってきます。ループして使用（上の文を参照）
`gui.text(int X,int Y, string msg[, type color1, type color2])`：テキストを描画する関数です。引数の'int X'には表示させたいX座標,`int Y`には表示させたいY座標を指定します。`color1` `color2`はオプションで省略可能。指定時は`0xRRGGBBAA`の16進数で指定するか、`blue`などの名前で指定します。 `color1`は主な色で省略した場合は白が、`color2`は輪郭の色で省略した場合は黒が指定されます。
`gui.pixel(int x, int y, type color)`：1pixelを描画する関数です。
`gui.line(int x1, int y1, int x2, int y2, type color)`：X1Y1座標からX2Y2座標まで1pixelの線を描画する関数です。
`gui.box(int x1, int y1, int x2, int y2, type FillColor, type BorderColor)`：X1Y1座標からX2Y2座標まで塗りつぶされたボックスを表示します。`FillColor`に塗りつぶす色を指定し、`BorderColor`に枠線を指定します。`BorderColor`は省略可能で省略した場合、`FillColor`が枠線に使用され、`FillColor`の半透明色で塗りつぶされます。

:::note info
`int X`などは具体的な数値も指定できますが、関数なのでローカル変数やグローバル変数も指定できます。
:::

:::note info
`string msg`はstring.formatを使用して指定ができるため変数の表示や書式指定が可能です。
サンプルコードは`test.lua`(最初にHello World!を表示させたコード)です。
:::

:::note warn
色指定に関して
Desmumeを含むほとんどのエミュレータでは80red glitchと言って0x7FFFFFFFF以上の値を指定すると描画が行われなくなるバグがあります。色の指定時には注意が必要です。
:::

#
これでLua Scriptを動かすことができるようになりました。次回以降はDesmumeでLua Scriptを活用するための基礎知識、Desmumeに登録されているAPIの説明を行います。



[^1]:wikipedia Lua　1994年かも？ライセンスは1994~
https://ja.wikipedia.org/wiki/Lua (2024/01/29 アクセス)
[^2]:DesmumeはC++で動いているソフト
[^3]:Aviutlは32bitソフトなので入っているlua51.dllは32bit版である場合がほとんどです。動かなかったら大人しく手順１のとおりにダウンロードしてください。
[^4]:もし今ROMを用意していてROMを読み込んだ状態でluaを動かせるのであれば１番目と２番目をもう一度比べてみてください。１番目のほうでは奇妙なことが起こるでしょう。エミュレートされた画面が更新されないのはDesmumeが抱えるバグであり２番目を使わなければならない理由の一つです。
[^5]:https://qiita.com/gestoC_moti/items/6cdc7a6ba0bb65627856
[^6]:こちらも頑張って書きます。書いたらここに記事のURLが来る。