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
を足す
yyyy
が8000
以上だったら xx \* 2 + 80 + 1
、7FFF
以下だったら 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"
1A7E
、1A7F
、1AA6
、1AA7
というアドレスは7E1A7E
、7E1A7F
、7E1AA6
、7E1AA7
と同じです。
というのも 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)を足してるようです。
では6F0AF
と6F183
で何を取得しているのでしょうか。またもや細かい説明を省きますが
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
先ほど692B3
で692E9
のサブルーチンを呼び出したわけですが、
「サブルーチンの呼出と終了前後でスタックポインタを変化させてはならない」 という鉄則(呼出元に戻れなくなる為)があるので、
692B2
でXレジスタの値をスタックにプッシュし、
692B6
でプッシュした値をXレジスタにプルしているので692B2
と692B7
の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
もこのサブルーチンの戻り値のようです。
さて、692B3
、692B9
でどのサブルーチンを呼び出すかは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に入れて呼出先へ移動します。
RTL
やRTS
等でサブルーチンが終了するとスタックにプッシュしてあった呼出元のアドレスをPCにプルして戻ります。
4.2.2で書いたとおり、サブルーチン開始時と終了時でスタックポインタを変化させてはならないのはこのためです。
これはつまり、44DAF
でプッシュした値と44DB6
や44DC5
でプルする値は絶対に同じになることを意味します
(実際に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つあります。
-
移動指定範囲は-128~+127
オペランド(XXのことです)が符号付1バイトなので-128~+127の範囲までしかアドレス移動できません。
文字通りプログラムの条件分岐する際には重宝しますが、
別領域へのアドレス移動にはあまり使いません。 -
移動アドレスの基点となるアドレスは次の命令のあるアドレス
どういうことかというと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コードを書き換えます。
これもどこを書き換えても構いませんが、サブルーチン呼出を書き換えると余計な手間がかからないので
69315
のDEX
取得呼出を書き換えます。
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