hkk333
级别: 火花会员
编号: 17693
精华: 1
发帖: 258
威望: 5 点
配偶: 单身
火 花 币: 24571 HHB
注册时间:2004-05-11
最后登陆:2017-10-14
|
SNES逆汗解析・改造入門(只翻译了部分>_<)+皇家骑士团2 ROM/RAM资料
SNES逆汗解析・改造入門
アセンブラに関する資料は見かけるものの, 具体的な解析手法を公開しているサイトはほとんど見ないので作ってみました. まー私自身大した知識や技術があるわけではないので,逆汗初心者向けです.
この文章について この文章はSNESのROMを対象にして逆汗解析をしたり改造をしたりするためには 作業をどのように進めていけば良いのか,を具体的に説明することを目的としています. 前提的知識としてはゲイムのお部屋の「2から始める改造講座」をなんとなく理解できるくらいを想定して書いています.
§1 基礎知識(の中の基礎) 1.1 逆汗解析とは何ぞや コンピュータはCPUによって動作しているわけですが,そのCPUには固有の機械語があります. また,機械語の命令に一対一対応している言語をアセンブリ言語と呼びます. プログラムというものは人間のわかりやすい言語(高級言語)でソースコードを書いて それを機械語へ変換(コンパイル)するとプログラムとしてコンピュータ上で動作するようになります.
で,ここでの本題である逆汗解析とは 「機械語をアセンブリ言語に変換(逆アセンブル)し,アセンブリ言語でプログラムを理解してしまおう」というものです.
1.2 65C816アセンブリについて 1.1では「CPUには固有の機械語がある」と書きました. SNESのCPUは65C816と呼ばれるものですが,もちろん65C816にも固有の機械語がありアセンブリ言語(65C816アセンブリ)があります. SNESのROMを逆汗解析をするには,当然,65C816アセンブリについて理解していなければなりません.
65C816アセンブリに関する資料は,このサイトのトップページのリンクから辿れば見つけられるはずです. 入門用としてはAssemlyTutorialが非常に良く纏まっているのでお勧めです. 資料を精読しなくてもこれ以降の文章は理解できるように書くつもりですが,なるべく資料には目を通しておくと良いでしょう. 以下,65C816アセンブリに関して知っておくべき情報をまとめておきます.
1.2.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.2.2 ニーモニック ニーモニックとはアセンブリ言語の各命令のことです.65C816のニーモニックはここで網羅されています. とりあえず必要なニーモニックは,
LDA,LDX,LDY :各レジスタに値を読み込む STA,STX,STY :各レジスタの値を書き込む JSL,RTL :24bit(3バイト)アドレス指定の関数呼び出しと呼び出し終了 JSR,RTS :アドレスの下位16bit(2バイト)指定関数呼び出しと呼び出し終了.上位8bitのバンクはDBレジスタを使用 REP,SEP :M,Xフラグの切り替え TDC :Aレジスタを初期化※ EA :何もしない
あたりでしょうか.詳しくは資料で探してください. ※ほんとは「Dレジスタの値をAレジスタに書き込む」のだけど,Dレジスタの値が0以外になるのを今まで見たことがない.
1.2.3 アドレッシングモード アセンブリ言語では,メモリアドレスを指定する方法が様々あります.これらの表記方法を総称してアドレッシングモードと呼びます. これはかなり重要なんですが,まぁ,dis65816のコメントを見ればなんとなくわかるでしょうからここでは説明しません.
1.3 メモリマッピングとメモリ/ROMアドレス 1.2.2で「バンク」という単語が出てきましたが,これはXX:YYYYというアドレスがあったとしたらXXを指す言葉です. YYYYの方はオフセットだったかな.
それはさておき,SNESのメモリ領域は00:0000-FF:FFFFまででこの中にROMがロードされます. メモリ中のROMの位置はROMの種類(LoROMとかHiROM)によって異なります. Tactics OgreやStar Ocean(※実はHiRomでした。勘違い)は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
よくわかんなかったらThe Rom Hacking Repositryで手に入れられるLunar Addressを使えば手っ取り早いでしょう.
§2 必要なTOOL 2.1 SNESエミュ本体 これがないと始まらない.ここでは「SNESGT Ver.0.163a」を使います. ここ見るような人なら使い方はわかるだろうから説明省略.
2.2 SNES用逆アセンブラ ここでは「dis65816 Ver1.04」と「TRaCER」を使います. 1.2.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 SNES用CPUトレーサー 「Peer's Snes9x Tracer v1.43-dev ep8」と「Snes9x 1.39Mk3b DOS Version with GRIP support v2」を両方紹介しておきます.
2.3.1 Peer's Snes9x Tracer SNES9xにCPUトレース機能がついた代物です.使い方は簡単なので説明省略. (設定の詳細がよくわからん・・・)
2.3.2 Snes9x 1.39Mk3b DOS Version 厳密にはデバッガと言ったほうが良いでしょうね. はっきり言って使いづらいです.でも個人的には↑よりもこっちの方が好き. 使っていると「逆汗解析してるんだ」って実感が湧くから(笑 Peer's Snes9x Tracerと比べて違うところはCPUログを出力する際,DBレジスタとスタックポインタも出力してくれるところ. 良い点でもありますが,出力ファイルがでかくなってしまうという諸刃の剣. あと,Peer'sの方で「Trace」(「Trace All」じゃない方)でログ取りすると, ループ部分は記録されないのでかなりログが小さくなります. readmeは付属してないので「DOS版Snes9xの本家最終版であるv1.26b(s9xd126b.zip)をどこかからか拾ってきて, その中のREADME.TXTを参照する。」(と「-out of date-」の中の人から教わりました.謝謝.) 詳しくはs9xd126b.zipの中のreadmeを見てもらうとして,ここでは必要最低限のことだけ説明しておきます.
2.3.2.1 起動 基本的にはSnes9x.exeにROMを放り込めば勝手にフルスクリーンで起動します. コマンドラインを指定して起動する場合, DOSプログラムのショートカットであるPIFファイルでコマンドラインを決めようにも, 何故かプログラムのタイトルが変なことになって設定できないため, コマンドラインが指定できるランチャーに登録して起動しましょう. 高速で動作させたければ,"-f ?"(?はフレームスキップ数.)を指定してください. 又,デバッグモードで起動させたければ"-d"で. あと,セーブデータ(SRMファイル)は同フォルダ下の"snesnaps"フォルダにいれましょう.
2.3.2.2 操作 キーボードのみで操作します. 設定を変えられないのが使いづらいところ.
・エミュレート画面での使用キー ↑ :↑ボタン ← :←ボタン ↓ :↓ボタン → :→ボタン D :Aボタン C :Bボタン S :Xボタン X :Yボタン Z :Rボタン A :Lボタン Enter :Startボタン Space :Selectボタン Esc :プログラム終了 F1~F10 :ステートロード Shift+F1~F10 :ステートセーブ Alt+F1 :デバッグ画面へ切り替え Alt+F2 :ファイルを選択してステートロード Alt+F3 :ファイルを選択してステートセーブ
・デバッグ画面でのコマンドライン ? :コマンドラインヘルプ t :現在位置のコードを実行 s :現在位置のコードをスキップ T :CPUのトレースOn/Off(ログをtrace.logに保存) bs #n $XX:XXXX :nは0~4,$XX:XXXXはブレークポイントを仕掛けるメモリアドレス bs #n :#nのブレークポイントアドレスを消去 g :エミュレート画面へ切り替え q :プログラム終了
2.4 バイナリエディタ ここでは「Stirling」を使います.説明略. 「環境設定」->「キーアサイン」で「上書き保存」,「元に戻す」,「やり直し」,「マーク登録/解除」, 「次のマーク位置」,「前のマーク位置」のキー設定をしておくと幸せになれます.
2.5 メモリエディタ エミュでのRAM(改造コード)検索用です.改造コードサーチ機能のついているエミュを使うなら必要ないでしょう.
§3 解析対象に関する情報収集 解析をする上で情報は多いに越したことはありません. ゲームについてよく知ってるほど解析しやすいですから. そんなわけで,そのゲームの改造コード,データベースやダメージ算出式などを載せているサイトは非常に役に立ちます. まぁ,ほんとは逆汗解析結果を公開しているサイトを見つけるのが一番良いのですが・・・
有名なゲームの場合,2chで立ってるスレのテンプレ,またはテンプレサイトを探すと楽に見つけられますが, 大抵の場合,Googleで検索してれば見つかります.Googleの「検索オプション」を利用したり, キーワードを""で一纏めにしたり,正規表現を駆使して活用しましょう.
§4 逆汗解析 4.1 置換解析 準備も整ったということで,いざ解析をやってみましょう. 解析対象は「Tactics Ogre」です.今んとこそれ以外は解析するつもりはないので. (※最近気分変わってきました(笑))
題名の「置換解析」ていうのは私が勝手に名付けましたが, ROMの中のある部分をStirlingで置換してエミュで変化するかどうかを確認して調べたいものを見つけます. 今回,置換するのはRAMアドレス(改造コードのこと)です. 大抵,一つのRAMを扱っている関数は複数あるので,複数ある関数の中から実際に使用されている関数を見つけなければなりません.
では,「殺害人数をカウントする関数」でも探してみましょうか. そのためにはまず殺害数を扱うRAMをみつけます. んでサーチしみると7E1D4Eがどうやら一人目のユニットの殺害人数であることがわかります. RAMの0000-1FFFまでの領域は,バンクが$00-$3F,$7Eまでの間でミラーリングされているので, プログラムが扱うのは「1D4E」というアドレスだけになります.
さて,これから1D4Eを扱っている場所をROMの中から探すわけですが, SNESのROMはリトルエンディアン方式なので,実際には「4E1D」という値を探すことになります. では,Stirlingを使って4E1Dを別の値で全置換してみましょう.変える値はエミュが落ちないように「0018」あたりにしときますか. 置換してみると39個も置換できました.ここで一度保存してエミュで確認してみると,見事に殺害人数はカウントされなくなっています. この39個の中から1箇所だけ探すために「元に戻す→エミュで実行」を39回繰り返さなければならないかというと, もっと少ない回数で見つけることができます.
全置換した直後のカーソルの位置は「2DEFF9」であると思いますが, ここから「12E698」まで元に戻して保存しましょう.Stirlingでこの位置をマークしておいてください. なぜこの位置まで元に戻すのかというと,ROMの中でプログラムは大体$10:0000までで収められており, それ以降はグラフィックや音楽などのデータだからです. (これはTactics Ogreの場合ですが,前半プログラム後半データの傾向はどのROMも同じだと思われます.)
再びエミュで確認してみると,まだ殺害人数は変化しません.ここまでで置換してあるのは残り12箇所です. では次に,6回元に戻して保存してマークを付けてエミュで確認します. そうすると変化する値がおかしいことになりましたが,まだ元には戻ってません. 次は,前回の半分の3回元に戻す→保存→マーク→エミュ実行をしてみてください.今度は正常に変化するようになりました. さて,前々回のマーク位置と前回のマーク位置の間で置換できるのは3箇所だけです.あとは一つずつやり直して確認してみてください. 3回目でまた再び値の変化がおかしくなります.どうやらここらへんで何かがありそうなのが感じ取れますよね.
元に戻す/やり直しで半分ずつ絞り込んでいくこの手法は是非とも覚えておいてください. どんなに置換回数が多くても「総置換回数≒2^n」となるnの回数だけエミュで確認すれば見つけられるわけですから. まぁ,置換回数が多くなるとその半分の回数なんて数えてられないので, 現在のバンクの位置からその半分のバンクの位置まで,というように大まかで良いでしょう. ちなみに,マークを付ける意味は絞り込むんでいる位置がわからなくなるのを防ぐためです.
目をつけたROMアドレスは6DBF4なので,dis65816の逆汗結果であるrom06.sssをテキストエディタで開いてみましょう. 6DBE2から6DBFDまでが一つの関数で以下のようになっています.
06DBE2 DA PHX 06DBE3 A5 A3 LDA $A3 ; "A" = $A3 06DBE5 89 02 D0 BIT #$D002 06DBE8 13 A6 ORA $A6 "Y" 06DBEA 91 BD STA $BD "Y" ; $BD+"Y" = "A" 06DBEC 2E 1A 4A ROL $4A1A 06DBEF D0 0B BNE #$0B ? -> $06DBFC 06DBF1 BD 4E 1D LDA $1D4E "X" ; "A" = $1D4E+"X" 06DBF4 1A INC ; "A" ++ 06DBF5 D0 02 BNE #$02 ? -> $06DBF9 06DBF7 A9 FF 9D LDA #$9DFF ; "A" = #$9DFF 06DBFA 4E 1D FA LSR $FA1D 06DBFD 6B RTL
さて,この中で逆アセンブラの読み間違えている可能性がある箇所があります.
06DBE5 89 02 D0 BIT #$D002 06DBF7 A9 FF 9D LDA #$9DFF ; "A" = #$9DFF
それはこの二箇所です.両方ともAレジスタに対する処理なので,もしAレジスタが8bitだったら指定する数値は1バイトだけなはずです. ここでTRaCerの出力結果で同じ部分を見てみましょう.
C6/DBE2: DA PHX C6/DBE3: A5 A3 LDA $A3 C6/DBE5: 89 02 BIT #$02 C6/DBE7: D0 13 BNE $DBFC C6/DBE9: A6 91 LDX $91 C6/DBEB: BD 2E 1A LDA $1A2E,X C6/DBEE: 4A LSR C6/DBEF: D0 0B BNE $DBFC C6/DBF1: BD 4E 1D LDA $1D4E,X C6/DBF4: 1A INC C6/DBF5: D0 02 BNE $DBF9 C6/DBF7: A9 FF LDA #$FF C6/DBF9: 9D 4E 1D STA $1D4E,X C6/DBFC: FA PLX C6/DBFD: 6B RTL
こちらの方が意味がわかりやすいので,この場合,TRaCerのほうが正しいと思われます. このあたりの判断は経験を積むしかないですね.CPUログでいちいち確認するのも面倒ですし. では,dis65816の出力結果をTRaCERで手直ししておきましょう.
06DBE2 DA PHX 06DBE3 A5 A3 LDA $A3 ; "A" = $A3 C6/DBE5: 89 02 BIT #$02 C6/DBE7: D0 13 BNE $DBFC C6/DBE9: A6 91 LDX $91 C6/DBEB: BD 2E 1A LDA $1A2E,X C6/DBEE: 4A LSR 06DBEF D0 0B BNE #$0B ? -> $06DBFC 06DBF1 BD 4E 1D LDA $1D4E "X" ; "A" = $1D4E+"X" 06DBF4 1A INC ; "A" ++ 06DBF5 D0 02 BNE #$02 ? -> $06DBF9 C6/DBF7: A9 FF LDA #$FF C6/DBF9: 9D 4E 1D STA $1D4E,X C6/DBFC: FA PLX 06DBFD 6B RTL
ちょっと変な感じもしますが,今後このような間違いを訂正する機会は大量にあるので気にしない方が良いです. んで,この関数の中で「1D4E」を扱っているのは
06DBF1 BD 4E 1D LDA $1D4E "X" ; "A" = $1D4E+"X" C6/DBF9: 9D 4E 1D STA $1D4E,X
この二箇所でAレジスタに殺害人数を読み込んでその後書き込んでいるようです. この関数が明らかに殺害人数を扱っているのがわかりますよね. さて,どこかに1D4Eの値を読み込んだ後その値を増やしている命令があるはずです.
06DBF4 1A INC ; "A" ++
この処理は「Aレジスタの値を+1する」という命令で, 6DBF1から6DBF9の間でAレジスタに対して処理しているのはこれ以外ありません. Stirlingで6DBF4をEA(NOP:何もしない命令)で書き換えると,確かに殺害人数がカウントされなくなりました. ということで,「6DBE2から始まる関数は殺害人数をカウントする」という事実が得られました.
4.2 CPUログ解析 次はCPUトレーサーを使ってCPUログを取りそれを解析をします. ここではPeer's Snes9x Tracerを使います. まずは4.1の解析をこっちの方法でやってみましょうか.
殺害人数がカウントされるであろうタイミングの直前で「Trace」 -> 「Trace」を押せばログ取りを開始してくれます. 殺害人数がカウントされたらもう一度「Trace」 -> 「Trace」でログ取りを終了します. そうすると,SRAMファイルのあるフォルダに「TO0000.log」というファイルができているので それをテキストエディタで開いてください.
んで,「1D4E」を検索すると見事に4.1で見つけた殺害人数カウント関数が見つかるはずです. 正直言って最初からCPUログを使ったほうが楽なことが多いですが, まぁ置換解析も使わないわけではないので覚えておいてください.
もーちょっと解析してみますかね. ちょうどTOスレで「Lサイズユニット制限人数」の場所を見つけたいと言ってる人がいたのでやってみましょう.
とりあえずLサイズユニットを一匹用意してください. そして,戦闘マップへ行くなりトレーニングなりで編成画面に持って行きます. 用意したLサイズユニットを出撃させる直前(Aボタンを押す前)にトレースを開始させ, そのユニットが現れたらトレースを終了します.
さて,今取ったCPUログからLサイズユニットの出撃人数を制限している場所を探すわけですが, プログラムで値を制限するには大抵比較命令を行っています. 但し,比較命令と言ってもCMP,CPX,CPY,さらにアドレッシングモードとかを考慮すると 命令自体は結構な数なので一々調べるのはめんどうです. が,とりあえず命令がどれも「C」から始まるのは間違いありません
比較命令で比較する値は2もしくは3であると思われるので CPUログをテキストエディタ(但し,正規表現検索をサポートしているエディタ.秀丸とか)で 「(半角スペース)02\tC」で検索していけば「02と比較する命令」は見つけることができます. これでダメなら比較値を「03」に変えてみれば良いわけです.
候補は何箇所かありますが,制限値を比較するのは恐らく1回きりであるため, 比較命令前後が他と似ていない候補を重点的に調べてみれば良いでしょう. 正解は一番最初に出てくるであろう
$89/9230 C9 02 CMP #$02
です.これはメモリアドレスなので,ROMアドレスのLサイズユニット出撃制限人数は49231となります.
4.1,4.2で共通しているのは,コードをあまり細かく読んでいません. つまり何が言いたいかというと「少しの知識である程度逆汗解析はできる」ってことです. あくまで「ある程度」ですが.
(続きはそのうち更新)
|
|
[楼 主]
|
Posted:2006-02-21 08:22| |
顶端
| |