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(b)
    =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)
    = 01001101(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のニーモニックはここで網羅されています。
     とりあえず必要なニーモニックは、

      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であります。これとか参考にどうぞ。
    ここでは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 必要なもの
     以下の4つはZophar's DomainSnes Emuから取ってきてください
     ・Geiger's Snes9x Debugger Mark 9 Release 8
     ・mfc71.dll
     ・msvcp71.dll
     ・msvcr71.dll

     その他
     ・DirectX 9 (いらないかも)

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

    2.1.2 デバッグコンソールの説明
     ・Next Op次に実行する命令を表示する。
     ・Step Into次の命令を1つだけ実行する。サブルーチンが呼び出された場合、サブルーチンのアドレスへ飛ぶ
     ・Step Over次の命令を実行する。サブルーチンが呼び出された場合、そのサブルーチンが終了するまで実行し続ける。
     ・Step Out次の命令を現在のサブルーチンが終了するまで実行し続ける。
     ・Skip次の命令を1つだけ無視する。
     ・Clear Textデバッグコンソールに表示されているログを消去する。
     ・Disassemble開始/終了アドレスを指定してディスアセンブルし表示する。
     ・Runブレークポイントにヒットするまで実行し続ける。
     ・ResetROMをリセットする。
     ・Frame Adv指定したフレーム数だけ実行する。(*1)
     ・Vector InfoCPUとAPUのベクターを表示する。(*2)
     ・Sprite StatusEMUが表示しているスプライトに関する情報を表示する。
     ・APU StateAPUに関する情報を表示する。
     ・Sample AddrAPUの保持するサンプルのアドレスを表示する。
     ・Show HexHex Editorを呼び出す。
     ・Dump RAM開始アドレスとバイト数を指定してRAMを。binで出力する。
     ・Dump Paletteパレットを表示する。
     ・What's UsedROMが現在使用している表示機能(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一度実行した命令は出力しない。
SquelchDBレジスタやステータスポインタは出力しない。
Splitログを分割出力する。
Tabbed Outputタブ区切りで出力する。
 
     ・Misc Optionsその他のオプション
Tilde FFチルダキーでターボモードにできるか否か(日本語キーボードだと`キー?)。
このチェックがoffだとTabキーで切り替え。
Alt Menu BehaviorEmu本体のメニューバーを表示しない。
Auto Usage MapUsageマップを自動出力する。
 
     ・Usage MapsUsageと言うのは、読み込んだアドレスはデータとして、
実行したアドレスはサブルーチンとして記録しておくログみたいなもんです(多分)
Open Usage.Usageを開き、現在のUsageのログと置き換える。
Merge Usage.Usageを開き、現在のUsageのログに追加する。
Save Usage現在のUsageのログを保存する。
Gen OffsetsUsageのログをtxtファイルで出力する。
 
     ・Hex EditorShow Hexで呼び出される少し便利なメモリエディタ。
スクロールしてるとたまに強制終了するので気をつけましょう。
Viewing: ROMLoROMなら80:8000~BF:FFFF、HiROMならC0:0000~FF:FFFFのROM領域を表示
      RAM7E:0000~7F:FFFFのRAM領域を表示
      VRAM0000~FFFFのVRAM領域を表示
      ARAM0000~FFFFのARAM領域を表示
FreezeRAM領域で選択した範囲の値を変更できなくする。
Save ROMROM領域で書き換えた値を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 SNES用逆アセンブラ
   ここでは「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)の逆汗結果で見てみましょう。

     044DAFDAPHX
     044DB022 44 D1 88JSL $045144-> $045144
     044DB4B0 07BCS #$07? -> $044DBD
     044DB6FAPLX
     044DB77BTDC
     044DB8BF 2D D7 88LDA $04572D "X"; "A" = $04572D+"X"; 武器STRデータ
     044DBC6BRTL

    ここではあまり深く解説しません(詳細は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のところををまた逆汗結果で見てみましょう。

     06F7F6A5 9FLDA $9F; "A" = $9F
     (略)
     06F832FAPLX
     06F833C9 02 F0CMP #$F002
     06F8364ALSR
     06F837C9 01 F0CMP #$F001
     06F83A46 22LSR $22
     06F83CAF CD 88 A8LDA $1408CD; "A" = $1408CD
     06F84084 02STY $02; $02 = "Y"
     06F842A4 0CLDY $0C; "Y" = $0C
     06F8448ATXA
     06F845D9 7E 1ACMP $1A7E "Y"
     06F848D0 04BNE #$04? -> $06F84E
     06F84AC6 04DEC $04; $04 --
     06F84CF0 21BEQ #$21? -> $06F86F
     06F84ED9 7F 1ACMP $1A7F "Y"
     06F851D0 04BNE #$04? -> $06F857
     06F853C6 04DEC $04; $04 --
     06F855F0 1DBEQ #$1D? -> $06F874
     06F857D9 A6 1ACMP $1AA6 "Y"
     06F85AD0 04BNE #$04? -> $06F860
     06F85CC6 04DEC $04; $04 --
     06F85EF0 19BEQ #$19? -> $06F879
     06F860D9 A7 1ACMP $1AA7 "Y"
     06F863D0 04BNE #$04? -> $06F869
     06F865C6 04DEC $04; $04 --
     06F867F0 15BEQ #$15? -> $06F87E
     06F869A4 02LDY $02; "Y" = $02
     06F86B84 00STY $00; $00 = "Y"
     (略)
     06F883A5 00LDA $00; "A" = $00
     (略)
     06F89260RTS

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

     06F7F6A5 9FLDA $9F; "A" = $9F
     (略)
     06F832FAPLX
     C6/F833:C9 02CMP #$02
     C6/F835:F0 4ABEQ $F881
     C6/F837:C9 01CMP #$01
     C6/F839:F0 46BEQ $F881
     C6/F83B:22 AF CD 88JSR $88CDAF;武器STR読み込み呼出
     C6/F83F:A8TAY
     06F84084 02STY $02; $02 = "Y"
     06F842A4 0CLDY $0C; "Y" = $0C
     06F8448ATXA
     06F845D9 7E 1ACMP $1A7E "Y"
     06F848D0 04BNE #$04? -> $06F84E
     06F84AC6 04DEC $04; $04 --
     06F84CF0 21BEQ #$21? -> $06F86F
     06F84ED9 7F 1ACMP $1A7F "Y"
     06F851D0 04BNE #$04? -> $06F857
     06F853C6 04DEC $04; $04 --
     06F855F0 1DBEQ #$1D? -> $06F874
     06F857D9 A6 1ACMP $1AA6 "Y"
     06F85AD0 04BNE #$04? -> $06F860
     06F85CC6 04DEC $04; $04 --
     06F85EF0 19BEQ #$19? -> $06F879
     06F860D9 A7 1ACMP $1AA7 "Y"
     06F863D0 04BNE #$04? -> $06F869
     06F865C6 04DEC $04; $04 --
     06F867F0 15BEQ #$15? -> $06F87E
     06F869A4 02LDY $02; "Y" = $02
     06F86B84 00STY $00; $00 = "Y"
     (略)
     06F883A5 00LDA $00; "A" = $00
     (略)
     06F89260RTS

     (※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の周辺を逆汗結果で見てみましょう。

     0692D1E9 92 F1SBC #$F192; "A" -= #$F192
     0692D492 68STA $68; $68 = "A"
     0692D693 4CSTA $4C "Y"; $4C+"Y" = "A"
     0692D893 68STA $68 "Y"; $68+"Y" = "A"
     0692DA93 24STA $24 "Y"; $24+"Y" = "A"
     0692DC93 9ASTA $9A "Y"; $9A+"Y" = "A"
     0692DE93 9ASTA $9A "Y"; $9A+"Y" = "A"
     0692E093 9ASTA $9A "Y"; $9A+"Y" = "A"
     0692E293 B3STA $B3 "Y"; $B3+"Y" = "A"
     0692E493 9ASTA $9A "Y"; $9A+"Y" = "A"
     0692E693 9ASTA $9A "Y"; $9A+"Y" = "A"
     0692E893 7BSTA $7B "Y"; $7B+"Y" = "A"
     0692EAAF DF D2 7ELDA $7ED2DF; "A" = $7ED2DF
     0692EEAATAX
     0692EF80 11BRA #$11? -> $069302
     0692F17BTDC
     0692F2AF DF D2 7ELDA $7ED2DF; "A" = $7ED2DF
     0692F6AATAX
     0692F7A4 0CLDY $0C; "Y" = $0C
     0692F9B9 AF 17LDA $17AF "Y"; "A" = $17AF+"Y"
     0692FC22 28 CF 88JSL $044F28-> $044F28
     06930090 06BCC #$06? -> $069308
     06930220 F6 F7JSR $F7F6-> $06F7F6 ;武器STR取得呼出
     069305AATAX
     06930686 00STX $00; $00 = "X"
     069308A6 0CLDX $0C; "X" = $0C
     06930AC2 21REP #$21
     06930C22 AF F0 8DJSL $06F0AF-> $06F0AF
     06931018CLC
     06931165 00ADC $00; "A" += $00
     06931385 00STA $00; $00 = "A"
     06931522 83 F1 8DJSL $06F183-> $06F183
     0693194ALSR
     06931A65 00ADC $00; "A" += $00
     06931C85 00STA $00; $00 = "A"
     06931EE2 20SEP #$20
     06932020 EA 93JSR $93EA-> $0693EA
     06932360RTS

    このようになってます。これを詳しく見る前に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バイトアドレスリストとして手直しすると

     0692D1E9 92-> $0692E9
     0692D3F1 92-> $0692F1
     0692D568 93-> $069368
     0692D74C 93-> $06934C
     0692D968 93-> $069368
     0692DB24 93-> $069324
     0692DD9A 93-> $06939A
     0692DF9A 93-> $06939A
     0692E19A 93-> $06939A
     0692E3B3 93-> $0693B3
     0692E59A 93-> $06939A
     0692E79A 93-> $06939A
     0692E97BTDC
     0692EAAF DF D2 7ELDA $7ED2DF; "A" = $7ED2DF
     0692EEAATAX
     0692EF80 11BRA #$11? -> $069302
     0692F17BTDC
     0692F2AF DF D2 7ELDA $7ED2DF; "A" = $7ED2DF
     0692F6AATAX
     0692F7A4 0CLDY $0C; "Y" = $0C
     0692F9B9 AF 17LDA $17AF "Y"; "A" = $17AF+"Y"
     0692FC22 28 CF 88JSL $044F28-> $044F28
     06930090 06BCC #$06? -> $069308
     06930220 F6 F7JSR $F7F6-> $06F7F6;武器STR取得呼出
     069305AATAX
     06930686 00STX $00; $00 = "X"
     069308A6 0CLDX $0C; "X" = $0C
     06930AC2 21REP #$21
     06930C22 AF F0 8DJSL $06F0AF-> $06F0AF
     06931018CLC
     06931165 00ADC $00; "A" += $00
     06931385 00STA $00; $00 = "A"
     06931522 83 F1 8DJSL $06F183-> $06F183
     0693194ALSR
     06931A65 00ADC $00; "A" += $00
     06931C85 00STA $00; $00 = "A"
     06931EE2 20SEP #$20
     06932020 EA 93JSR $93EA-> $0693EA
     06932360RTS

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

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

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

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

     06930220 F6 F7JSR $F7F6-> $06F7F6;武器STR取得呼出
     069305AATAX
     06930686 00STX $00; $00 = "X"

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

     06930C22 AF F0 8DJSL $06F0AF-> $06F0AF
     06931018CLC
     06931165 00ADC $00; "A" += $00
     06931385 00STA $00; $00 = "A"
     
     06931522 83 F1 8DJSL $06F183-> $06F183
     0693194ALSR
     06931A65 00ADC $00; "A" += $00
     06931C85 00STA $00; $00 = "A"

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

     06F0B5BD C6 18LDA $18C6 "X"; "A" = $18C6+"X"
     06F189BD 3E 19LDA $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)になりました。
    さらに

     06932020 EA 93JSR $93EA-> $0693EA

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

     06929A8BPHB
     (略)
     0692ACEBXBA
     C6/92AD:A9 00LDA #$00
     C6/92AF:EBXBA
     0692B00AASL
     0692B1AATAX
     0692B2DAPHX
     0692B3FC D1 92JSR $92D1 "X"-> $0692D1; 武器STR+攻撃側STR+攻撃側DEX/2とさらに何かの処理
     0692B6FAPLX
     0692B7D4 00PEI $00
     0692B9FC DD 92JSR $92DD "X"-> $0692DD
     0692BCC2 20REP #$20
     0692BE68PLA
     0692BF38SEC
     0692C0E5 00SBC $00; "A" -= $00
     0692C285 00STA $00; $00 = "A"
     (略)
     0692D06BRTL

    まず

     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レジスタの値はどこから来たのでしょうか。

     0692ACEBXBA;AH⇔AL
     C6/92AD:A9 00LDA #$00;#$00→AL
     C6/92AF:EBXBA ;AH⇔AL
     0692B00AASL;AL*2
     0692B1AATAX;AL→X
     0692B2DAPHX
     0692B3FC D1 92JSR $92D1 "X"-> $0692D1

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

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

     069844 8BPHB
     (略)
     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
     (略)
     0698D06BRTL

    先ほどの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から始まるサブルーチンによって攻撃ダメージが決定されることがわかりました。
    細かい説明を省きまくってかなり強引に話を進めてわかりずらい部分があると思うので、処理の流れをまとめておきます。

     69924JSL $069844; ダメージ計算呼出 戻り値 $00
      ┣698B4LDA $7ED2E0; 攻撃種類読取
      ┣698B8JSL $06929A; ステータスダメージ計算呼出 引数 AL:攻撃種類 戻り値 $00
      ┃ ┣692B3JSR ($92D1,X); 攻撃側ステータス取得 X:攻撃種類 戻り値 $00
      ┃ ┃ ┣692E9; 物理直接攻撃ステータス計算開始
      ┃ ┃ ┣69302JSR $F7F6; 武器STR取得
      ┃ ┃ ┃ ┗44DB8LDA $04572D; 武器STRデータ読込
      ┃ ┃ ┣6930CJSL $06F0AF; 攻撃側STR取得
      ┃ ┃ ┣69311ADC $00; 武器STR+攻撃側STR
      ┃ ┃ ┣69315JSL $06F183; 攻撃側DEX取得
      ┃ ┃ ┣69319LSR; 攻撃側DEX/2
      ┃ ┃ ┣6931AADC $00; 武器STR+攻撃側STR+攻撃側DEX/2
      ┃ ┃ ┗69320JSR $93EA; 攻撃側ステータスに何らかの処理
      ┃ ┣692B9JSR ($92DD,X); 防御側ステータス計算 X:攻撃種類
      ┃ ┗692C0SBC $00; 攻撃側ステータス-防御側ステータス
      ┣698BCJSL $06EC79; ステータスダメージに何らかの処理
      ┣698C0JSR $9731; 同上
      ┣698C3JSR $93CC; 同上
      ┣698C6JSL $06F620; 同上
      ┣698CAJSR $EA6C; 同上 最終ダメージ
      ┗698D0RTL; ダメージ計算終了

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

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

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

    まずは練習として4.1.1で出てきた武器STR取得サブルーチンを読んでみます。
    
     044DAFDAPHX;Xの値をスタックにプッシュ
     044DB022 44 D1 88JSL $045144-> $045144;45144のサブルーチン呼出
     044DB4B0 07BCS #$07? -> $044DBD;Cフラグが1のとき44DBDへ飛ぶ
     044DB6FAPLX;Xへスタックからプル
     044DB77BTDC;Aレジスタを0にする
     044DB8BF 2D D7 88LDA $04572D "X"; "A" = $04572D+"X";武器STRデータ読出し
     044DBC6BRTL;サブルーチン終了
 
     044DBDC2 20REP #$20;Aレジスタを2バイトにする
     044DBFBF 96 BD 7ELDA $7EBD96 "X"; "A" = $7EBD96+"X";スナップドラゴン剣のSTRデータ読出し
     044DC3E2 20SEP #$20;Aレジスタを1バイトにする
     044DC5FAPLX;Xへスタックからプル
     044DC66BRTL;サブルーチン終了

    おや?と思うかもしれません。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つあります。
      
      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コードを書き換えます。
    これもどこを書き換えても構いませんが、サブルーチン呼出を書き換えると余計な手間がかからないので
    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で上手くいくかどうか確認してみてください。
    
(続きはそのうち更新)



Copyright©2004-2006 546◆rbsENsUukE