SNES逆汗解析・改造入門

アセンブラに関する資料は見かけるものの、 具体的な解析手法を公開しているサイトはほとんど見ないので作ってみました。 まー私自身大した知識や技術があるわけではないので、逆汗初心者向けです。

この文章について

この文章はSNESのROMを対象にして逆汗解析をしたり改造をしたりするためには
作業をどのように進めていけば良いのか、を具体的に説明することを目的としています。
前提的知識としては ゲイムのお部屋の「2から始める改造講座」をなんとなく理解できるくらいを想定して書いています。
わからない単語があれば、e-Words を探せば大抵載ってます。

§1 基礎知識(の中の基礎)

1.1 逆汗解析とは何ぞや

コンピュータはCPUによって動作しているわけですが、そのCPUには固有の機械語があります。
また、機械語の命令に一対一対応している言語をアセンブリ言語と呼びます。
プログラムというものは人間のわかりやすい言語(高級言語)でソースコードを書いて
それを機械語へ変換(コンパイル)するとプログラムとしてコンピュータ上で動作するようになります。

で、ここでの本題である逆汗解析とは
「機械語をアセンブリ言語に変換(逆アセンブル)し、アセンブリ言語でプログラムを理解してしまおう」 というものです。

1.2 16進数とか2進数とか

普段我々が日常的に使っているのは10進数です。0から9までの10個の数で1桁を表すので10進数。
しかしコンピュータを扱う上では16進数と2進数が重要です。
特にプログラム改造をやる上では知らないと話しにならないような事項なのでしっかり理解しておきましょう。

2進数(以下、2進数の値には(b)をつける)は0と1で構成されていて、16進数(同様に(h)をつける)は0~9にA,B,C,D,E,Fを加えた16個の数によって構成されています。
2進数で1101(b)は10進数で言うと13、16進数で言えばD(h)です。この計算はどのように行っているかというと、
2進数の各桁の値と基数(10進数なら10、2進数なら2)を桁数乗したものを掛けて足しあわせたものです。1101(b)で言えば

1101
=  1  *  2^3  +  1  *  2^2  +  0  *  2^1  +  1  *  2^0  (h)
=  1  *   8   +  1  *  4    +  0  *  2    +  1  *  1    (h)
=  D(h)

というようになります。このように、8,4,2,1のような各桁に相当する値で分解すれば良い訳です。
さて、16進数と2進数の関係で重要なのが、「1桁の16進数は4桁の2進数で表せられる」 ということです。
何故なら2^4=16なわけですから。

ところで、ファイルをバイナリエディタで開いてみれば分かる通り、ファイルを構成する単位はバイト(Byte)です。
1バイトは2桁の16進数なので8桁の2進数ということになります。2進数の各桁の値のことをビット(Bit)と呼ぶので、 1バイト=8ビット です。
ビットをOn/Offと考えると、1バイトで8個の状態を表すことができます。これはRPGの状態異常なんかによく使われています。
例えば、あるキャラの状態を表す値が4D(h)だったとすると

4D(h)
=  0  *  80  +  1  *  40  +  0  *  20  +  0  *  10  +  1  *  8  +  1  *  4  +  0  *  2  +  1  *  1  (h)
=  0            1            0            0            1           1           0           1        (b)
=  1001101(b)

というように表すことができます。最も高い桁をbit7、最も低い桁をbit0と呼ぶので4D(h)の状態ではbit6、bit3、bit2、bit0がOnになっており、
これが毒だったり麻痺だったりするわけです。
バイトをビットに分解して考えることはプログラム改造では基本事項なので覚えておきましょう。

1.3 65C816アセンブリについて

"1.1"では「CPUには固有の機械語がある」と書きました。
SNESのCPUは65C816と呼ばれるものですが(*)、もちろん65C816にも固有の機械語がありアセンブリ言語(65C816アセンブリ)があります。
SNESのROMを逆汗解析をするには、当然、65C816アセンブリについて理解していなければなりません。
(*)正確には65C816はMPUです。

65C816アセンブリに関する資料は、このサイトのトップページのリンクから辿れば見つけられるはずです。
資料を精読しなくてもこれ以降の文章は理解できるように書くつもりですが、なるべく資料には目を通しておくと良いでしょう。
以下、65C816アセンブリに関して知っておくべき情報をまとめておきます。

1.3.1 レジスタとそのbit切り替えフラグ

Resistとは「保持する」という意味で、Resisterはつまり「何かを保持する存在」のこと。
アセンブリ言語で何を保持するかって言ったら値に決まってます。
そんなわけで、 レジスタは値を保持しています。大雑把に言えば変数みたいなもんです。

とりあえず覚えておいてほしいレジスタはA(アキュムレータ)やX、Y(インデックスレジスタ)、DB(データバンクレジスタ)、P(ステータスレジスタ)。
この中で A、X、Yレジスタだけは保持できるバイト数(8bitなら1バイト、16bitなら2バイト)をフラグによって切り替えることができます
AレジスタならばMフラグ、インデックスレジスタはXフラグです。
んで、このフラグの切り替えは↓に出てくる「REP」「SEP」によって行います。
経験上Aレジスタの切り替えがほとんどなので、 AレジスタはREPで2バイト、SEPで1バイト と覚えておけば事足ります。

1.3.2 ニーモニック

ニーモニックとはアセンブリ言語の各命令のことです。65c816 Referenceのニーモニックは65c816 Reference(ここで)網羅されています。
とりあえず必要なニーモニックは、

OP の関数
LDA、LDX、LDY 各レジスタに値を読み込む
STA、STX、STY 各レジスタの値を書き込む
JSL、RTL 24bit(3バイト)アドレス指定の関数呼び出しと呼び出し終了
JSR、RTS アドレスの下位16bit(2バイト)指定関数呼び出しと呼び出し終了。上位8bitのバンクはDBレジスタを使用
REP、SEP ステータスフラグの切り替え(*1)
TDC Aレジスタを初期化(*1)
EA 何もしない

あたりでしょうか。詳しくは資料で探してください。
ちなみに、ニーモニックをアセンブルした機械語の命令をオペレーションコード、あるいはOPコードと呼びます。
(*1)ほんとは「Dレジスタの値をAレジスタに書き込む」でDレジスタに値が入ってないと初期化になる

1.3.3 アドレッシングモード

アセンブリ言語では、メモリアドレスを指定する方法が様々あります。これらの表記方法を総称してアドレッシングモードと呼びます。
これはかなり重要なんですが、まぁ、dis65816のコメントを見ればなんとなくわかるでしょうからここでは説明しません。

1.4 メモリマッピングとメモリ/ROMアドレス

"1.3.2"で「バンク」という単語が出てきましたが、これはXX:YYYYというアドレスがあったとしたらXXを指す言葉です。

それはさておき、SNESのメモリ領域は00:0000-FF:FFFFまででこの中にROMがロードされます。
メモリ中のROMの位置はROMの種類(LoROMとかHiROM)によって異なります。
Tactics OgreはLoROMであります。
Memory Mapping(これ) とか参考にどうぞ。
ここではLoROMの場合のメモリアドレスと実際のROMアドレスの変換方法を書いておきます。

ROMデータはメモリの各バンクの8000~FFFFに入ってます
ROMの0000~FFFFまでだったら、メモリでは2バンク分必要ということになります
(バンク$00h~$3Fhまでの話だけど、まぁ特に気にする必要はない)

で、具体的にどうやって変換するかと言うと

メモリアドレスがXXYYYYだったとしたら
XX-80してこの値が偶数だったらYYYY-8000、奇数だったらそのまま
次にXX-80の値を2で割る
これで出せます

例を示すと、88C75Bなら
88-80=8は偶数なのでC75B-8000=475B
8/2=4なのでROMアドレスは4475B

逆変換はxxyyyyだとしたら
xxを2倍して80を足す
yyyy8000以上だったら xx \* 2 + 80 + 17FFF以下だったら xx \* 2 + 80 のまま yyyy + 8000

よくわかんなかったら FuSoYa's Niche で手に入れられる Lunar Address を使えば手っ取り早いでしょう。

§2 必要なTOOL

2.1 SNES用デバッガ

Geiger's Snes9x Debugger」略してGSDを使います。

2.1.1 SNES ROM

ただしヘッダ有り、もしくはインターリーブ形式のROMはサポートしていないそうです。
(ヘッダ有りのROMを読み込もうとするとヘッダを削るかどうかのダイアログが出てきます。)

2.1.2 デバッグコンソールの説明

Functions Description
Next Op 次に実行する命令を表示する。
Step Into 次の命令を1つだけ実行する。サブルーチンが呼び出された場合、サブルーチンのアドレスへ飛ぶ
Step Over 次の命令を実行する。サブルーチンが呼び出された場合、そのサブルーチンが終了するまで実行し続ける。
Step Out 次の命令を現在のサブルーチンが終了するまで実行し続ける。
Skip 次の命令を1つだけ無視する。
Clear Text デバッグコンソールに表示されているログを消去する。
Disassemble 開始/終了アドレスを指定してディスアセンブルし表示する。
Run ブレークポイントにヒットするまで実行し続ける。
Reset ROM をリセットする。
Frame Adv 指定したフレーム数だけ実行する。(*1)
Vector Info CPUとAPUのベクターを表示する。(*2)
Sprite Status EMUが表示しているスプライトに関する情報を表示する。
APU State APU に関する情報を表示する。
Sample Addr APUの保持するサンプルのアドレスを表示する。
Show Hex Hex Editorを呼び出す。
Dump RAM 開始アドレスとバイト数を指定してRAMを。binで出力する。
Dump Palette パレットを表示する。
What's Used ROMが現在使用している表示機能(VRAMとかBGとか)を表示する。
What's Missing
Trace From ログ出力を開始するアドレス等を指定する。
Reset Debug デバッグ情報を消去する。
BreakPoints ブレークポイントを指定する。
Exec 指定したアドレスにある命令が実行される直前でブレーク
Read 指定したアドレスの値が読み込まれる直前でブレーク
Write 指定したアドレスに値が書き込まれる直前でブレーク
Logging ログ出力するCPU、APU、SA1、Sound DSPの選択
Special Tracing ログ出力するDMA、HDMA、VRAM、DSP-1、Unknown Registersの選択
CPU Trace Options ログ出力する際のオプション
Trace Once 一度実行した命令は出力しない。
Split ログを分割出力する。
Tabbed Output タブ区切りで出力する。
Misc Options その他のオプション
Tilde FF チルダキーでターボモードにできるか否か(日本語キーボードだと'キー?)。
Alt Menu Behavior Emu本体のメニューバーを表示しない。
Auto Usage Map Usageマップを自動出力する。
Usage Maps Usageと言うのは、読み込んだアドレスはデータとして、
実行したアドレスはサブルーチンとして記録しておくログみたいなもんです(多分)
Open Usage .Usageを開き、現在のUsageのログと置き換える。
Merge Usage .Usageを開き、現在のUsageのログに追加する。
Save Usage 現在のUsageのログを保存する。
Gen Offsets Usageのログをtxtファイルで出力する。
Hex Editor Show Hexで呼び出される少し便利なメモリエディタ。
スクロールしてるとたまに強制終了するので気をつけましょう。
Viewing:
ROM LoROMなら80:8000~BF:FFFF、HiROMならC0:0000~FF:FFFFのROM領域を表示
RAM 7E:0000~7F:FFFFのRAM領域を表示
VRAM 0000~FFFFのVRAM領域を表示
ARAM 0000~FFFFのARAM領域を表示
Freeze RAM領域で選択した範囲の値を変更できなくする。
Save ROM ROM領域で書き換えた値をROMファイルに反映する。
Open TBL .tblファイルを開く(書式不明)

(*1): 本家Snes9xのデフォルト動作速度だと60fps(1フレームで約0.016秒)のはずなのだが、
「Frame Adv」で30framesで動かしてみると約1秒くらいになる(=つまり30fpsってこと)。
GSDのデフォルト動作速度が30fpsなのか計算がおかしいだけなのかはよくわからない。

(*2): APU:Audio Processor Unitというのは音源用に搭載されているマイクロコンピュータでSony製のSPC700のこと。
しかし、画像も音声関係も全く手をつけてないので詳しいことが書けません・・・

2.2

ここでは「dis65816」と「TRaCER」を使います。
1.3.1で書きましたが、Aレジスタは扱う値が1バイトか2バイトで変化します。
Aレジスタに数値を読み込ませる場合、指定する数値も1バイトか2バイトで変化させなければなりません。
ここが逆アセンブラがコードを読み間違える原因の一つです。
これに対処するためには、 逆アセンブラでAレジスタを1バイトで読ませた時の結果と
2バイトで読み込ませたときの結果の二つを用意しておいて、両方を見比べましょう

2.2.1 dis65816

dis65816はREP/SEPによってMフラグを切り替えながらコードを読み込んでくれて、
さらに、結果の分割出力、コメント追加など色々便利なので、こちらをメインにして使っていくと良いでしょう。
dis65816の使い方は、 ROM名を"rom.smc"に変えてから"dis65816 rom.smc" で実行すればOK。
LoROMならばdis65816_2.exeの方で起動すると、アドレス指定がROMアドレスと同じになります。

2.2.2 TRaCER

TRaCERの方は結果を分割できないので、手直し用に使うと良いと思います。
dis65816は基本的にM、Xフラグ共に16bitで読み込み、TRaCERは逆に8bitで読み込むので。
TRaCERの起動は基本的に "tracer -o -i ROM名" で、ROMがHiROMなら"-h"、
ROMにSMC/SWCヘッダがついているようであれば"-s"のオプションを加えると良いでしょう。

2.3 バイナリエディタ

ROMを書き換えるために使うわけですが、それだけでなく、デバッガで取った巨大なログファイルを加工する際にも使います。
Stirling」と「OOO」がお勧めです。Stirlingの方は「環境設定」->「キーアサイン」で「上書き保存」、「元に戻す」、「やり直し」、
「マーク登録/解除」、「次のマーク位置」、「前のマーク位置」のキー設定をしておくと幸せになれます。

§3 解析対象に関する情報収集

解析をする上で情報は多いに越したことはありません。
ゲームについてよく知ってるほど解析しやすい ですから。
そんなわけで、そのゲームの改造コード、データベースやダメージ算出式などを載せているサイトは非常に役に立ちます。
まぁ、ほんとは逆汗解析結果を公開しているサイトを見つけるのが一番良いのですが・・・

有名なゲームの場合、2chで立ってるスレのテンプレ、またはテンプレサイトを探すと楽に見つけられますが、
大抵の場合、Googleで検索してれば見つかります。Googleの「検索オプション」を利用したり、
キーワードを""で一纏めにしたり、正規表現を駆使して活用しましょう。

§4 逆汗解析

4.1 デバッグしないで解析

4.1.0 基本的なデータサーチ

これから逆汗解析の実践に入るわけですが、その前におさらいみたいなもんとしてデバッグも逆汗もしないでサーチする方法からやってみましょう。
調べる題材は「Tactics Ogre」で、武器の攻撃力(武器STR)データがあるアドレスを探してみます。
サーチの順番としては、 「装備のRAMアドレス(改造コード)→武器のID→武器STRROMアドレス」 でいきます。
まず装備のRAMアドレスからですがゲーム上の編成画面で装備をつけたり変えたり外したりしてサーチすれば見つかるでしょう
(この時点でわからないと後の文章はついてけないと思いマス)。
んで、一人目の左上装備が7EB6C6、右上装備が7EB6C7、左下装備が7EB70C、右下装備が7EB70Dであるとわかります。
ショートソードがID:02なのでその前後のミニマムダガー(ID:01)とバルダーダガー(ID:03)の攻撃力を調べると武器STRはそれぞれ「12 15 25」です。
これを16進数に変えると「0C 0F 19」となり、これをバイナリエディタでサーチしてみると候補が3つほど出てきます。
書き換えてみればわかりますが1番最初に出てくる4572Eが武器STRのアドレスのようですが、
データアドレスの先頭はID:00から始まる ので(ID:01から始まる場合も稀にあります)
正解は4572Dです。これでとりあえず目標達成です。

ゲームによって違いますが、今回の方法でうまくいかないような場合は、ショートソードのSTRの隣の値がショートソードのINTであるような、
ID毎でデータがまとめられている 可能性が高いのでSTRの次にINT、AGI・・・を並べてサーチすれば良いと思います(ゲーム上のステータスの並びと違う可能性もある)。
それでもだめな場合は、 ゲーム上の表記とバイナリでの値が必ずしも同じでない 場合(+50されてたり/10されてたり)や
データ自体が圧縮されてる 可能性があります(武器の攻撃力のように頻繁にアクセスするようなデータが圧縮されてることはまず無いと思いますが)。

4.1.1 データアドレスから直接サーチ

ではこれから65816とスーファミのハードウェアの知識を利用した解析をやっていきましょう。逆汗する目的として計算式の算出や改変などが挙げられると思うので、
TOのダメージ計算を見ていくことにします。4.1.0で武器STRアドレスを調べたので、ダメージ計算時にこのデータを読み込んでいる場所を探してみましょう。
一応、検証用の ステートセーブファイル を置いておきます。TOはLoROMなので1.4で述べたようにアドレス変換すると、ROMアドレスである4572Dはメモリアドレス88D72Dとなります。
また、SNESのROMは リトルエンディアン 方式なので実際に検索するのは「2D D7 88」です。
バイナリ検索すると何個か出てきますが書き換えて調べると1番最初に出てくる44DB9が武器STRを読み込んでいるところであるとわかります。
ではこの部分をdis65816_2(LoROM用のdis65816)の逆汗結果で見てみましょう。

044DAF DA          PHX
044DB0 22 44 D1 88 JSL $045144 ->  ; $045144
044DB4 B0 07       BCS #$07        ; ? -> $044DBD
044DB6 FA          PLX
044DB7 7B          TDC
044DB8 BF 2D D7 88 LDA $04572D "X" ; "A" = $04572D+"X" ; 武器STRデータ
044DBC 6B          RTL

ここではあまり深く解説しません(詳細は4.3.1)が、とりあえず「44DAFから始まるサブルーチンは武器STRを読み込んでいる」ということがわかりました。

4.1.2 サブルーチン呼出元のサーチ

さらに調べるために、今度は4.1.1のサブルーチンがどこで呼び出されているかを調べましょう。
このサブルーチンはRTLで終了しているので、呼び出される場合はJSL(22)で呼び出される はずです。
なので、「22 AF CD 88」を「EA EA EA EA」(nop(EA)は何もしない命令)で書き換えてみると11個ほど出てきます。
そろそろ1個ずつ調べるのがめんどくさいのでちょっとしたテクニックを使ってみます。
まずゲーム上の戦闘画面で攻撃キャラのダメージ値を覚えておきます(武器を持ってないと意味が無いので装備しておきましょう)。 次にStiringで全く書き換えをしていない状態(=これ以上元に戻せない)で 「22 AF CD 88」を「EA EA EA EA」 に置換し、最後のところでマークを付けておいてEmuで変化を調べます。
明らかにダメージが減っていることがわかるので次に5回「元に戻す」をやってまたマークを付けてEmuで変化を調べます。
ダメージが元に戻っているので、次は「やり直し」を3回やってまたEmuで変化を調べてください。
ダメージは元に戻ったままなので、あとは1回ずつ調べていくと、最後の6F83Bのところで再びダメージが減少します。
ということで「6F83Bで武器STR読み込みサブルーチンを呼び出している」ということがわかります。

一番最後に置換した場所なので1個ずつ調べていけば11回かかったわけですが、今回の方法だと4回調べるだけで済みました。
このように 「半分元に戻す→半分やり直しor元に戻す→...」 を繰り返していけば、全体の置換回数がNだとすれば2^n≒Nとなるn回だけ調べれば済むことになります。
置換回数が1000を超えるくらいになるとこの方法は非常に効果的です (まぁ何回元に戻すかを数えるのは手間なので、バンクの半分元に戻すorやり直しをすれば良いでしょう)。
また、 「プログラムはROMの10:0000くらいまでで、残りは画像や音楽などのデータであること」
大抵の場合、 10:0000以内にあるデータの近くにはそのデータを扱うサブルーチンが存在する」 ということを念頭においておけばさらに少ない手数で調べられるでしょう。

さて、今見つけた6F83Bのところををまた逆汗結果で見てみましょう。

06F7F6 A5 9F       LDA $9F       ; "A" = $9F
(略)
06F832 FA          PLX
06F833 C9 02 F0    CMP #$F002
06F836 4A          LSR
06F837 C9 01 F0    CMP #$F001
06F83A 46 22       LSR $22
06F83C AF CD 88 A8 LDA $1408CD   ; "A" = $1408CD
06F840 84 02       STY $02       ; $02 = "Y"
06F842 A4 0C       LDY $0C       ; "Y" = $0C
06F844 8A          TXA
06F845 D9 7E 1A    CMP $1A7E "Y"
06F848 D0 04       BNE #$04      ; ? -> $06F84E
06F84A C6 04       DEC $04       ; $04 --
06F84C F0 21       BEQ #$21      ; ? -> $06F86F
06F84E D9 7F 1A    CMP $1A7F "Y"
06F851 D0 04       BNE #$04      ; ? -> $06F857
06F853 C6 04       DEC $04       ; $04 --
06F855 F0 1D       BEQ #$1D      ; ? -> $06F874
06F857 D9 A6 1A    CMP $1AA6 "Y"
06F85A D0 04       BNE #$04      ; ? -> $06F860
06F85C C6 04       DEC $04       ; $04 --
06F85E F0 19       BEQ #$19      ; ? -> $06F879
06F860 D9 A7 1A    CMP $1AA7 "Y"
06F863 D0 04       BNE #$04      ; ? -> $06F869
06F865 C6 04       DEC $04       ; $04 --
06F867 F0 15       BEQ #$15      ; ? -> $06F87E
06F869 A4 02       LDY $02       ; "Y" = $02
06F86B 84 00       STY $00       ; $00 = "Y"
(略)
06F883 A5 00       LDA $00       ; "A" = $00
(略)
06F892 60          RTS

6F83Bのところを見るとおや?と思うでしょう。6F83Bからの命令がありません。これは明らかにdis65816の読み間違いです。
2.2で述べたように、TRaCERからの結果で手直しすると

06F7F6     A5 9F       LDA $9F       ; "A" = $9F
(略)
06F832     FA          PLX
C6/F833:   C9 02       CMP #$02
C6/F835:   F0 4A       BEQ $F881
C6/F837:   C9 01       CMP #$01
C6/F839:   F0 46       BEQ $F881
C6/F83B:   22 AF CD 88 JSR $88CDAF   ; 武器STR読み込み呼出
C6/F83F:   A8          TAY
06F840     84 02       STY $02       ; $02 = "Y"
06F842     A4 0C       LDY $0C       ; "Y" = $0C
06F844     8A          TXA
06F845     D9 7E 1A    CMP $1A7E "Y"
06F848     D0 04       BNE #$04      ; ? -> $06F84E
06F84A     C6 04       DEC $04      ; $04 --
06F84C     F0 21       BEQ #$21      ; ? -> $06F86F
06F84E     D9 7F 1A    CMP $1A7F "Y"
06F851     D0 04       BNE #$04      ; ? -> $06F857
06F853     C6 04       DEC $04       ; $04 --
06F855     F0 1D       BEQ #$1D      ; ? -> $06F874
06F857     D9 A6 1A    CMP $1AA6 "Y"
06F85A     D0 04       BNE #$04      ; ? -> $06F860
06F85C     C6 04       DEC $04       ; $04 --
06F85E     F0 19       BEQ #$19      ; ? -> $06F879
06F860     D9 A7 1A    CMP $1AA7 "Y"
06F863     D0 04       BNE #$04      ; ? -> $06F869
06F865     C6 04       DEC $04       ; $04 --
06F867     F0 15       BEQ #$15      ; ? -> $06F87E
06F869     A4 02       LDY $02       ; "Y" = $02
06F86B     84 00       STY $00       ; $00 = "Y"
(略)
06F883     A5 00       LDA $00       ; "A" = $00
(略)
06F892     60          RTS

(※TRaCERはHiROMモードで読み込ませているのでアドレスの先頭がCになっています。
LoROMモード読み込ませないのはアドレスに+8000されるためROMアドレスと比較しづらいのとdis65816の結果にあわせるためです。)

これで正しくなりました。さて、このサブルーチンをもう少し見てみましょう。武器STR読み込み呼出の近くに次のような命令があります。

06F845   D9 7E 1A    CMP $1A7E "Y"
06F84E   D9 7F 1A    CMP $1A7F "Y"
06F857   D9 A6 1A    CMP $1AA6 "Y"
06F860   D9 A7 1A    CMP $1AA7 "Y"

1A7E1A7F1AA61AA7というアドレスは7E1A7E7E1A7F7E1AA67E1AA7と同じです。
というのも RAMの0000-1FFFまでの領域はバンクが$00-$3F$7Eでミラーリングされており
さらに $00-$6Fの内容は全て$80-$EFと同じ だからです(ExROMだと若干違う)。んでこのアドレスは「戦闘時の装備」です。
この辺りで何の処理をしているかというと、詳しくは説明しませんがヒート・メルトウェポン等の武器STRの補助効果の処理です。
ともかく、これで「6F7F6から武器STR取得サブルーチン開始」ということがわかりました。

4.2 デバッガ解析

4.2.1 サブルーチン呼出元サーチ

これまでデバッグをしないで解析してきましたが、ここからはデバッガ(GSD)を使って解析しましょう。
まずは4.1.2で調べた武器STR取得サブルーチン6F7F6の呼出元を調べてみます。 まず始めに「Breakpoints」で8DF7F6でブレークポイント(以下BP)を設定し「Exec」にチェックを入れておいてください。
次にゲームに戻って攻撃を開始すると途中で停止するはずです。そして「Step Out」を押して現在のサブルーチンを終了させます。そうすると

$8D/9305     AA  TAX     A:000F X:0002 Y:0002 P:envMxdizc

このように表示が出てくると思います。4.1.2のようなやり方でサブルーチン呼出元を調べなくても、デバッガを使えばこれで済んでしまいます。
あら便利!ということで、69305の周辺を逆汗結果で見てみましょう。

0692D1    E9 92 F1     SBC #$F192     ; "A" -= #$F192
0692D4    92 68        STA $68        ; $68 = "A"
0692D6    93 4C        STA $4C "Y"    ; $4C+"Y" = "A"
0692D8    93 68        STA $68 "Y"    ; $68+"Y" = "A"
0692DA    93 24        STA $24 "Y"    ; $24+"Y" = "A"
0692DC    93 9A        STA $9A "Y"    ; $9A+"Y" = "A"
0692DE    93 9A        STA $9A "Y"    ; $9A+"Y" = "A"
0692E0    93 9A        STA $9A "Y"    ; $9A+"Y" = "A"
0692E2    93 B3        STA $B3 "Y"    ; $B3+"Y" = "A"
0692E4    93 9A        STA $9A "Y"    ; $9A+"Y" = "A"
0692E6    93 9A        STA $9A "Y"    ; $9A+"Y" = "A"
0692E8    93 7B        STA $7B "Y"    ; $7B+"Y" = "A"
0692EA    AF DF D2 7E  LDA $7ED2DF    ; "A" = $7ED2DF
0692EE    AA           TAX
0692EF    80 11        BRA #$11       ; ? -> $069302
0692F1    7B           TDC
0692F2    AF DF D2 7E  LDA $7ED2DF    ; "A" = $7ED2DF
0692F6    AA           TAX
0692F7    A4 0C        LDY $0C        ; "Y" = $0C
0692F9    B9 AF 17     LDA $17AF "Y"  ; "A" = $17AF+"Y"
0692FC    22 28 CF 88  JSL $044F28    ; -> $044F28
069300    90 06        BCC #$06       ; ? -> $069308
069302    20 F6 F7     JSR $F7F6      ; -> $06F7F6 ;武器STR取得呼出
069305    AA           TAX
069306    86 00        STX $00        ; $00 = "X"
069308    A6 0C        LDX $0C        ; "X" = $0C
06930A    C2 21        REP #$21
06930C    22 AF F0 8D  JSL $06F0AF    ; -> $06F0AF
069310    18           CLC
069311    65 00        ADC $00        ; "A" += $00
069313    85 00        STA $00        ; $00 = "A"
069315    22 83 F1 8D  JSL $06F183    ; -> $06F183
069319    4A           LSR
06931A    65 00        ADC $00        ; "A" += $00
06931C    85 00        STA $00        ; $00 = "A"
06931E    E2 20        SEP #$20
069320    20 EA 93     JSR $93EA      ; -> $0693EA
069323    60           RTS

このようになってます。これを詳しく見る前に692D1より少し上を見てみると、次のような命令があります。

C6/92B3:     FC D1 92     JSR ($92D1,X)
C6/92B9:     FC DD 92     JSR ($92DD,X)

dis65816の表記だとわかりずらいのでTRaCERの方から持ってきましたが、
これは 692D1, 692DD以降並んでいる2バイトのアドレスリストからサブルーチンのジャンプ先を決定する」 ということです。
JMP ($xxxx,X)」(7C)でもそうですが、アドレスリストがどこまでであるかはどこにも明記されておらず、 プログラマが勝手に決めて良いことになっているのでわかりずらいです。
また 逆アセンブラはアドレスリストであるかどうかの判断はしないので、ここがまた逆アセンブラが読み間違える原因 でもあります。
では692D1, 692DD以降を2バイトアドレスリストとして手直しすると

0692D1    E9 92                         ; -> $0692E9
0692D3    F1 92                         ; -> $0692F1
0692D5    68 93                         ; -> $069368
0692D7    4C 93                         ; -> $06934C
0692D9    68 93                         ; -> $069368
0692DB    24 93                         ; -> $069324
0692DD    9A 93                         ; -> $06939A
0692DF    9A 93                         ; -> $06939A
0692E1    9A 93                         ; -> $06939A
0692E3    B3 93                         ; -> $0693B3
0692E5    9A 93                         ; -> $06939A
0692E7    9A 93                         ; -> $06939A
0692E9    7B             TDC
0692EA    AF DF D2 7E    LDA $7ED2DF    ; "A" = $7ED2DF
0692EE    AA             TAX
0692EF    80 11          BRA #$11       ; ? -> $069302
0692F1    7B             TDC
0692F2    AF DF D2 7E    LDA $7ED2DF    ; "A" = $7ED2DF
0692F6    AA             TAX
0692F7    A4 0C          LDY $0C        ; "Y" = $0C
0692F9    B9 AF 17       LDA $17AF "Y"  ; "A" = $17AF + "Y"
0692FC    22 28 CF 88    JSL $044F28    ;   -> $044F28
069300    90 06          BCC #$06       ; ? -> $069308
069302    20 F6 F7       JSR $F7F6      ;   -> $06F7F6    ; 武器STR取得呼出
069305    AA             TAX
069306    86 00          STX $00        ; $00 = "X"
069308    A6 0C          LDX $0C        ; "X" = $0C
06930A    C2 21          REP #$21
06930C    22 AF F0 8D    JSL $06F0AF    ;   -> $06F0AF
069310    18             CLC
069311    65 00          ADC $00        ; "A" += $00
069313    85 00          STA $00        ; $00 = "A"
069315    22 83 F1 8D    JSL $06F183    ;   -> $06F183
069319    4A             LSR
06931A    65 00          ADC $00        ; "A" += $00
06931C    85 00          STA $00        ; $00 = "A"
06931E    E2 20          SEP #$20
069320    20 EA 93       JSR $93EA      ;   -> $0693EA
069323    60             RTS

これで正しくなりました。

4.2.2 サブルーチン解析

692E9からのサブルーチンについてもう少し詳しく見ていきましょう。まずサブルーチンを解析する上で重要なのが 「引数と戻り値が何であるか」 ということです。
特に戻り値の方。少なくとも戻り値さえわかれば、そのサブルーチンのコード全てを見なくても何とかなってしまう場合が多々あります。
4.1.2の武器STR取得サブルーチンを見てみると

06F883  A5 00       LDA $00     ; "A" = $00

これ以降Aレジスタに対しての処理が全くないので、これはAレジスタがこのサブルーチンの戻り値であるということです(当然、値は武器STR)。その後

069302  20 F6 F7    JSR $F7F6   ; -> $06F7F6 ; 武器STR取得呼出
069305  AA          TAX
069306  86 00       STX $00     ; $00 = "X"

これは何をやっているかと言えば、武器STRを取得しておいて$00にその値を書き込んでいます。その後

06930C  22 AF F0 8D JSL $06F0AF ; -> $06F0AF
069310  18  CLC
069311  65 00       ADC $00     ; "A" += $00
069313  85 00       STA $00     ; $00 = "A"

069315  22 83 F1 8D JSL $06F183 ; -> $06F183
069319  4A          LSR
06931A  65 00       ADC $00     ; "A" += $00
06931C  85 00       STA $00     ; $00 = "A"

6F0AFのサブルーチンで取得した値と$00(武器STR)を足し合わせ(ADC $00はAレジスタ+$00→Aレジスタ)、
さらに6F183で取得した値の半分(LSRはAレジスタ/2)を足してるようです。
では6F0AF6F183で何を取得しているのでしょうか。またもや細かい説明を省きますが

06F0B5  BD C6 18    LDA $18C6 "X"  ; "A" = $18C6+"X"
06F189  BD 3E 19    LDA $193E "X"  ; "A" = $193E+"X"

18C6は「戦闘中のユニットSTR」、193Eは「戦闘中のユニットDEX」です。 ということで、少なくとも「武器STR+STR+DEX/2」という式が算出できました。
確認のためにデバッガで調べてみましょう。8D931EにBPを仕掛けて、HITしたら「Show Hex」でHex Editorを起動し、
「Viewing」の「RAM」を選択して7E:0000($00に相当)の値を見れば良いです。 んで実際「39h(57)」(ショートソードSTR:15+攻撃側STR:29+攻撃側DEX:26/2=57)になりました。
さらに

069320  20 EA 93    JSR $93EA      ; -> $0693EA

の処理を調べるために8D9323にBPを仕掛けて値がどう変化するか調べましょう。調べてみると$00の値が「33h」になりました。
どうやってこの値が出てきたかは今のとこ不明ですが、保留しておきます。この後69323でサブルーチンが終了し692B6に戻るのでそちらの処理も見てみましょう。

06929A    8B       PHB
(略)
0692AC    EB       XBA
C6/92AD:  A9 00    LDA #$00
C6/92AF:  EB       XBA
0692B0    0A       ASL
0692B1    AA       TAX
0692B2    DA       PHX
0692B3    FC D1 92 JSR $92D1 "X" ; -> $0692D1 ;  武器STR+攻撃側STR+攻撃側DEX/2とさらに何かの処理
0692B6    FA       PLX
0692B7    D4 00    PEI $00
0692B9    FC DD 92 JSR $92DD "X" ; -> $0692DD
0692BC    C2 20    REP #$20
0692BE    68       PLA
0692BF    38       SEC
0692C0    E5 00    SBC $00       ; "A" -= $00
0692C2    85 00    STA $00       ; $00 = "A"
(略)
0692D0    6B       RTL

まず

0692B2    DA       PHX
0692B3    FC D1 92 JSR $92D1 "X" ; -> $0692D1
0692B6    FA       PLX
0692B7    D4 00    PEI $00
0692B9    FC DD 92 JSR $92DD "X" ; -> $0692DD

先ほど692B3692E9のサブルーチンを呼び出したわけですが、
「サブルーチンの呼出と終了前後でスタックポインタを変化させてはならない」 という鉄則(呼出元に戻れなくなる為)があるので、
692B2でXレジスタの値をスタックにプッシュし、 692B6でプッシュした値をXレジスタにプルしているので692B2692B7のXレジスタの値は同じ(共に0)です。
ということで692B9でどのサブルーチンが呼び出されるかというと「6939A」です。6939Aのサブルーチンの処理は詳しく述べませんが、
先ほどの692E9と同じように「(防具VIT+防御側VIT+防御側STR/2)に何らかの処理(693EA)をした値を$00に返す」というものです。 この後の処理は

0692BE    68        PLA
0692BF    38        SEC
0692C0    E5 00     SBC $00     ; "A" -= $00
0692C2    85 00     STA $00     ; $00 = "A"

692B7$00(攻撃側の値)をスタックにプッシュしそれをAレジスタにプルして、
692C0でAレジスタから$00(防御側の値)を引いて(SBC $00はAレジスタ-$00→Aレジスタ)その値を$00に書き込んでいます。
やはりこの$00もこのサブルーチンの戻り値のようです。
さて、692B3692B9でどのサブルーチンを呼び出すかはXレジスタによるわけですが、ではそのXレジスタの値はどこから来たのでしょうか。

0692AC    EB       XBA            ; AH ⇔ AL
C6/92AD:  A9 00    LDA #$00       ; #$00 → AL
C6/92AF:  EB       XBA            ; AH ⇔ AL
0692B0    0A       ASL            ; AL * 2
0692B1    AA       TAX            ; AL → X
0692B2    DA       PHX
0692B3    FC D1 92 JSR $92D1 "X"  ; -> $0692D1

(※AL:Aレジスタ下位8bit、AH:Aレジスタ上位8bit)

↑のコメント見ればわかるとおり「Aレジスタの下位8bit*2」がXレジスタに入れられるようです。 これより前にAレジスタに対する処理は無いので「Aレジスタの下位8bitがこのサブルーチンの引数」であるということが言えます。 ではその引数がどっから来ているかを見てみましょう。
6929Aのサブルーチン終了後698BCに戻ります。

069844    8B           PHB
(略)
0698B4    AF E0 D2 7E  LDA $7ED2E0   ; "A" = $7ED2E0
0698B8    22 9A 92 8D  JSL $06929A   ; -> $06929A ;  ステータスダメージ計算
0698BC    22 79 EC 8D  JSL $06EC79   ; -> $06EC79
0698C0    20 31 97     JSR $9731     ; -> $069731
0698C3    20 CC 93     JSR $93CC     ; -> $0693CC
0698C6    22 20 F6 8D  JSL $06F620   ; -> $06F620
0698CA    20 6C EA     JSR $EA6C     ; -> $06EA6C
0698CD    7A           PLY
(略)
0698D0    6B           RTL

先ほどの6929Aのサブルーチン呼出は698B8です。これより1つ前の処理の

0698B4     AF E0 D2 7E LDA $7ED2E0   ; "A" = $7ED2E0

で引数を取得しているようです。ではこの$7ED2E0は何なのかを調べてみると、 物理直接攻撃が0、物理投射攻撃が1のように攻撃の種類のようです。
ということで、4.2.1の692E1~692E7までのアドレスリストは「各攻撃方法に応じたステータス取得」(と693EAにある何らかの処理)ということになります。
698BC以降の処理は述べませんが、698CD$00を見てみるとちゃんと攻撃ダメージが入っています。
さらにここからStep Outすると69928に飛ぶので大元のダメージ計算呼出アドレスは69924ということになります。

4.2.3 まとめとCPUログ取り

以上の結果より、69844から始まるサブルーチンによって攻撃ダメージが決定されることがわかりました。
細かい説明を省きまくってかなり強引に話を進めてわかりずらい部分があると思うので、処理の流れをまとめておきます。

69924        JSL $069844      ; ダメージ計算呼出 戻り値 $00
┣698B4       LDA $7ED2E0     ; 攻撃種類読取
┣698B8       JSL $06929A     ; ステータスダメージ計算呼出 引数 AL:攻撃種類 戻り値 $00
┃ ┣692B3     JSR ($92D1,X)  ; 攻撃側ステータス取得 X:攻撃種類 戻り値 $00
┃ ┃ ┣692E9                 ; 物理直接攻撃ステータス計算開始
┃ ┃ ┣69302   JSR $F7F6     ; 武器STR取得
┃ ┃ ┃ ┗44DB8 LDA $04572D  ; 武器STRデータ読込
┃ ┃ ┣6930C   JSL $06F0AF   ; 攻撃側STR取得
┃ ┃ ┣69311   ADC $00       ; 武器STR+攻撃側STR
┃ ┃ ┣69315   JSL $06F183   ; 攻撃側DEX取得
┃ ┃ ┣69319   LSR           ; 攻撃側DEX/2
┃ ┃ ┣6931A   ADC $00       ; 武器STR+攻撃側STR+攻撃側DEX/2
┃ ┃ ┗69320   JSR $93EA     ; 攻撃側ステータスに何らかの処理
┃ ┣692B9     JSR ($92DD,X)  ; 防御側ステータス計算 X:攻撃種類
┃ ┗692C0     SBC $00        ; 攻撃側ステータス-防御側ステータス
┣698BC       JSL $06EC79     ; ステータスダメージに何らかの処理
┣698C0       JSR $9731       ; 同上
┣698C3       JSR $93CC       ; 同上
┣698C6       JSL $06F620     ; 同上
┣698CA       JSR $EA6C       ; 同上 最終ダメージ
┗698D0       RTL             ; ダメージ計算終了

さらに詳しく調べてみたければ、「五人タクティクスオウガ」内の「ダメージの算出」を参考にがんばってみてください。

最後にダメージ計算の処理をCPUログで取って見ましょう。最初からCPUログを見てやった方が早いですが、まぁ今回は練習と言うことで敢えてやりませんでした。
CPUログの取り方はデバッグコンソールの「Logging」の「CPU」にチェックを入れればすぐできますが、ログファイルが無駄に容量がでかくなるので、
まず「8D9924」(69924)でBPを仕掛け、引っかかったらCPUログ取りを開始、「Step Over」で関数が終了するまで実行してCPUログ取り終了。

4.3 逆汗解析

4.3.1 サブルーチンの内容を読む

今までサブルーチンの内容をちゃんと理解せずにやってきました。アセンブリ言語のプログラムはサブルーチンの集合体で、
サブルーチン同士が何をやり取りしているのか さえわかってしまえば、内容自体はわからなくても何とかなってしまうもんです。
しかしそれだけでは解析・改造の幅は狭くなってしまいます。
ということでサブルーチンの内容を読み取れるように練習してみましょう。

まずは練習として4.1.1で出てきた武器STR取得サブルーチンを読んでみます。

044DAF    DA          PHX             ; Xの値をスタックにプッシュ
044DB0    22 44 D1 88 JSL $045144     ; -> $045144           ; 45144のサブルーチン呼出
044DB4    B0 07       BCS #$07        ; ? -> $044DBD         ; Cフラグが1のとき44DBDへ飛ぶ
044DB6    FA          PLX             ; Xへスタックからプル
044DB7    7B          TDC             ; Aレジスタを0にする
044DB8    BF 2D D7 88 LDA $04572D "X" ; "A" = $04572D+"X"    ; 武器STRデータ読出し
044DBC    6B          RTL             ; サブルーチン終了

044DBD    C2 20       REP #$20        ; Aレジスタを2バイトにする
044DBF    BF 96 BD 7E LDA $7EBD96 "X" ; "A" = $7EBD96+"X"    ; スナップドラゴン剣のSTRデータ読出し
044DC3    E2 20       SEP #$20        ; Aレジスタを1バイトにする
044DC5    FA          PLX             ; Xへスタックからプル
044DC6    6B          RTL             ; サブルーチン終了

おや?と思うかもしれません。4.1.1の時よりも長くなっています。しかしこれで良いのです。
44DB4で分岐しているためです。
また予め書いておきますが、このサブルーチンの引数はXで武器IDが入っています。

まず、サブルーチンの内容を説明する前に、サブルーチン呼出が実際に何をやっているか説明すると、
PC:プログラムカウンタ(命令の現在位置)をスタックにプッシュし、指定されている呼出サブルーチンのアドレスをPCに入れて呼出先へ移動します。
RTLRTS等でサブルーチンが終了するとスタックにプッシュしてあった呼出元のアドレスをPCにプルして戻ります。
4.2.2で書いたとおり、サブルーチン開始時と終了時でスタックポインタを変化させてはならないのはこのためです。

これはつまり、44DAFでプッシュした値と44DB644DC5でプルする値は絶対に同じになることを意味します
(実際にGSDでSquelchオプションをつけないでログを見るとS:スタックポインタの値は同じはずです)。
そもそも何故44DAFでXの値をプッシュしなければならないかといえば、
45144のサブルーチン内でXの値が変化するからです(45144の内容は説明しないので、自分で見てみてください)。

サブルーチンの開始時にスタック操作命令がよくありますが、
これはサブルーチンの基本原則 「サブルーチンは戻り値以外はなるべく変化させないで終了させる」 のためです。
スタックポインタは例外として、それ以外で変化させて良いか良くないのかは状況によって違います。
変化させないに越したことはありませんが、改造する場合には無駄な命令を増やさないためにもその辺の見極めは重要になってきます。

さて、45144のサブルーチンは引数である武器IDがスナップドラゴン剣かどうかチェックして、
スナップドラゴン剣だったらCフラグを1にしてスナップドラゴンの順番をXに入れる、
スナップドラゴン剣と違ったらCフラグを0で返します。
そのため、44DB4でCフラグが1だった場合に分岐するようになっています。

次は44DB7のTDCに注目してみましょう。TDCの説明は1.3.2の通りですが、何故ここでTDCをしなければならないかといえば
このサブルーチンの戻り値であるAレジスタの上位1バイト(AH)を0にするためです。
武器STR自体は1バイトなので下位1バイト(AL)で収まりますが、もしAレジスタを2バイトで読み込んだ場合にAHに値が入っていたらエラー確定です。
これと同様に、TAXでXにAの値を入れるときもMフラグが0だろうが1だろうがXにはAL、AHの内容が入るので注意が必要です。
(ちなみに、PHXの後でPLAする場合にはMフラグによってプルする値が1バイトか2バイトに変化します)

以上でこのサブルーチンの説明は終わりです。
まとめると、引数:X(武器ID) 戻り値:A(武器STR)ということになります。

4.3.2 サブルーチンの効能を調べる

"4.2.2"ではサブルーチンの中身を少しだけ見て戻り値が何であるか判断しました。
ここではサブルーチンの中身を全く見ないでサブルーチンの効能を調べる方法を述べます。

4.3.2.1 サブルーチン呼出をNOPで潰す

"4.1.2"で述べたように、JSL $XXXXXX(22XXXXXX)JSR $XXXX(20XXXX)NOP(EA) で埋めてサブルーチンを呼び出させなくしてからEMU上でその変化を調べます。
サブルーチン呼出にBPを仕掛けておいてSKIPで命令を飛ばしても同じことができますが面倒なのでこっちを推奨します。
具体的なやり方としては、例えば武器STRデータ読出しサブルーチンを呼び出す

069302  20 F6 F7 JSR $F7F6 -> $06F7F6  ; 武器STR取得呼出

これを

069302  EA EA EA

のように書き換えてからゲーム内で誰かに攻撃してみれば、与えるダメージが減っているはずです。
もし6F7F6の内容を知らなければ、このサブルーチンはダメージ処理に関わっている、ということが推測できるはずです。
このやり方の欠点としてはEMU上に変化が現れなければやる意味が無い、ということです。

4.3.2.2 チートサーチ

そこで次はEMU上に変化が現れなくても調べられるやり方を述べます。
やり方としてはGSDでサブルーチン呼出にBPを仕掛け、HITしたらチートサーチで「Reset」→「OK」を押してから
STEP OVERでそのサブルーチンが終了するまで実行させた後、Not equal toで「Search」します。
そうすれば、そのサブルーチンを実行した時のRAM上の変化を見ることができます。
このやり方ならEMU上に現れない変化も捉えることができる反面、どれが戻り値なのかわかりづらいのが欠点です。
どちらかと言えば圧縮データを解析する際に使います。

§5 逆汗改造

5.1 プログラム改造

5.1.1 アドレス移動

改造コードで改造する場合、基本的にはあるアドレスの値を別の値に変えれば改造できます。
しかし、プログラム改造の場合はそういうわけにもいきません。
OPコードが連続して並んでいるので、命令をEAで埋めて無効にすることはできても、コードを追加することができません。
そこで必要になるのが現在のアドレスから別の領域へ移動することです。
以降、アドレス移動に使うための命令を説明します。

5.1.1.1 分岐命令

分岐命令はステータスフラグの状態によって分岐する命令です。
ここでは無条件分岐であるBRAを例に挙げます。

(OP)    (ニーモニック)
80 XX   BRA XX

条件分岐のアドレス移動で気をつけるべき点は2つあります。

  1. 移動指定範囲は-128~+127
    オペランド(XXのことです)が符号付1バイトなので-128~+127の範囲までしかアドレス移動できません。
    文字通りプログラムの条件分岐する際には重宝しますが、
    別領域へのアドレス移動にはあまり使いません。

  2. 移動アドレスの基点となるアドレスは次の命令のあるアドレス
    どういうことかというと

    0D0063 80 03 BRA #$03 ; ? -> $0D0068 0D0065 DE A9 76 DEC $76A9 "X" ; $76A9+"X" --

このBRA命令の移動先はD0068ですが、これは

D0068 = D0065(次の命令のあるアドレス) + 03(オペランド)

ということで、BRA命令のあるアドレスにオペランドを足すわけではないということです。

5.1.1.2 ジャンプ命令

ジャンプ命令は単純に指定したアドレスへ移動します。
移動したアドレス先でまた元のアドレスへ戻るためのジャンプ命令が必要なのでOPコードのバイト数が多めになります。
そのため、次のサブルーチン呼出の方が使いやすいですが、場合によって使い分けると良いでしょう。

(OP)    (ニーモニック)
4C XXXX   JMP $XXXX
5C XXXXXX   JMP $XXXXXX

前者は同じバンク内で移動する際に、後者はバンク外へ移動する際に用います。

5.1.1.3 サブルーチン呼出命令

サブルーチンについては§4で大体述べましたのでここでは説明を省きます。

(OP)      (ニーモニック)
20 XXXX   JSR $XXXX
60        RTS

22 XXXXXX JSL $XXXXXX
6B        RTL

ジャンプ命令と同様に前者は同バンク用、後者はバンク外用です。

5.1.2 プログラム改造例

では実際に改造してみましょう。具体例として4.2.1の直接攻撃攻撃側能力取得ルーチンを改造します。

 069302  20 F6 F7    JSR $F7F6   ; -> $06F7F6  ;武器STR取得呼出 
 069305  AA          TAX 
 069306  86 00       STX $00     ; $00 = "X" 
 069308  A6 0C       LDX $0C     ; "X" = $0C 
 06930A  C2 21       REP #$21 
 06930C  22 AF F0 8D JSL $06F0AF ; -> $06F0AF  ;STR取得呼出
 069310  18          CLC 
 069311  65 00       ADC $00     ; "A" += $00 
 069313  85 00       STA $00     ; $00 = "A" 
*069315* 22 83 F1 8D JSL $06F183 ; -> $06F183  ;DEX取得呼出
 069319  4A          LSR 
 06931A  65 00       ADC $00     ; "A" += $00 
 06931C  85 00       STA $00     ; $00 = "A" 
 06931E  E2 20       SEP #$20 
 069320  20 EA 93    JSR $93EA   ; -> $0693EA 
 069323  60          RTS

攻撃側能力は「武器STR + STR + DEX/2」だったので、これを「武器STR + STR + (DEX + MEN)/2」に変えてみます。
まずステータス取得ルーチンを探さなければなりませんが、ここでは手順は割愛。
さらにステータス取得ルーチンは攻撃用、防御用等ありますがその説明も省略。

(ROMアドレス) (メモリアドレス) (名称)
6F183        8DF183        攻撃用DEX取得
6F1E6        8DF1E6        攻撃用INT取得
6F249        8DF249        攻撃用MEN取得
6F31D        8DF31D        防御用VIT取得
6F3EA        8DF3EA        AGI取得
etc...

次にどの領域にプログラムを追加するかを決めます。
どこでも構わないのですが65463-67FFFに大量に空きがあるので65463以降に埋めることにします。

そして65463にアドレス移動するようOPコードを書き換えます。
これもどこを書き換えても構いませんが、サブルーチン呼出を書き換えると余計な手間がかからないので
69315DEX取得呼出を書き換えます。

069315  22 83 F1 8D JSL $06F183 -> $06F183  ; DEX取得呼出
                             ↓
069315  22 63 D4 8C JSL $065463 -> $065463  ; 65463のサブルーチンへジャンプ

最後に65463に新たなプログラムを追加します。
ステータス取得を呼出、値を半分にして$00に加える、というコードは69315-6931Dに既にあるのでそれを真似します。
最初のうちは既存のコードやサブルーチンを並べるようにすればやりやすいと思います。

065463  22 49 F2 8D JSL $06F249     ; -> $06F249  ;MEN取得呼出
065467  4A          LSR
065468  65 00       ADC $00         ; "A" += $00
06546A  85 00       STA $00         ; $00 = "A"
06546C  22 83 F1 8D JSL $06F183     ; -> $06F183  ;DEX取得呼出
065470  6B          RTL

これで書き換え終了です。
あとはEMUで上手くいくかどうか確認してみてください。
(続きはそのうち更新)

Original by 546◆rbsENsUukE / TOb546 at gmail