プログラム改造講座(GBA)


説明の前に

この講座では、GBAのワークコードではなく、プログラム改造をできるようにすることを目的とした講座です.

プログラム改造とは、キャラクター毎に作らなければいけないワークコード等とは違い、そのコード1つだけで、全員に効果が及ぶようにプログラム自体

を改変させてしまうコードのことです.

この講座では勘を頼りに見つけるのではなく、プログラムの流れを理解する事を一番に考えて解説していきます.そのため、少々回りくどい説明の仕

方をしています.

※プログラムコードは雑誌等には投稿しないようお願いします.実機(PAR等)は動作が不安定ですし、 雑誌買うユーザーは実機が主ですから、

  実機で動作が不安定な改造コードを投稿するのは問題ありますからね.それに昔ゲームラボにある事をやられて、嫌いですから(笑).

※見にくいのは、ご了承ください.尚、誤筆等ありましたらどんどん申し出てください.


用意する物

ROMイメージ
これがないと始まりませんね.
VisualBoyAdvance-SDL(デバッガ)
プログラムを調べるのに必要.このデバッガが一番ソフトが動くし便利です.
goldroad 1.7(GBA用アセンブラ) 
arm/thumbプログラムのバイナリコードを調べるのに有ると楽.

 

 

 

goldroadの基本ソースファイル(goldroadの使用方法等は付属のファイルを参照)

ダウンロード


ショートカット

Visual Boy Advance SDL 起動方法

Visual Boy Advance SDL コマンド表

増減系サーチ方法

○○MAX系サーチ方法

戦闘後全回復系サーチ方法

一撃死系サーチ方法

○○n倍系サーチ方法

レジスタの値からワークコードを探す方法

アイテム全部所持系サーチ方法


Visual Boy Advance SDL 起動方法

最近は、本当の意味でのDOSが無くなったからまともに使えない人が増えたのですかね?ということで起動方法を教えます.

----------------------------------------------------------------------------------------------------------------------------------

1つ目の方法

起動方法は至って簡単.[ VisualBoyAdvance-SDL.exe ]にドラッグ&ドロップするだけです.ドラッグ&ドロップすれば、後はプログラム側が勝手に

ファイル名を引数として起動してくれます.ROMイメージのファイル名&フォルダ名に関しては日本語でも英語でも大丈夫です.さらに、ROMイメージは

[ VisualBoyAdvance-SDL.exe ]と違うフォルダに置いても起動可能.ゲーム毎にステートセーブを多用するので分けた方が無難です.

その他、[ VisualBoyAdvance-SDL ]の設定は、同フォルダにある[ VisualBoyAdvance.cfg ]を弄くって調整してください.基本的には、[ VisualBoyAdvance ]

と同じ設定ができるはずです.私は、これをメインで遊ぶわけではないので、キー配置とフレームの設定ぐらいしか調整してないです.

----------------------------------------------------------------------------------------------------------------------------------

2つ目の方法

ドラッグ&ドロップで動かない場合は、DOS窓から実行するしかないです.ディレクトリ移動のDOSコマンド等については、他のところで勉強してください.

[ VisualBoyAdvance-SDL.exe ]が置いてあるフォルダにDOS窓で移動します.移動したら次のコマンドを実行してください.その際、あらかじめROMイメージも

コピーしておくと楽です.[ VisualBoyAdvance-SDL] とROMイメージを別々のフォルダに置いてもファイル名を入力するときにフルパス入れてやれば起動できます.

コマンド オプション ROMイメージの名前
VisualBoyAdvance-SDL [options] file-name

 

 

VisualBoyAdvance-SDL *.gba (オプションは省略可能)

これで起動できるはずです.オプションについては、[ VisualBoyAdvance-SDL ]と入力すればリストがDOS窓に出力されるのでそれを見てください.

----------------------------------------------------------------------------------------------------------------------------------

2つ目の方法(改良)

上記の2つ目の方法がオーソドックスですが、ファイル名にフルパスを入れないといけないから面倒ですね.こういう場合は、どのフォルダからでも

[ VisualBoyAdvance-SDL.exe ]が実行できるようにシステム環境変数のPathに追加してしまいましょう.

コントロールパネル→システム→詳細→環境変数 (Windows2000の場合)

ここまで移動したら、システム環境変数のPathを選んで編集をクリックします.

ここの最後の部分に [ VisualBoyAdvance-SDLのあるフォルダのフルパス ]を追加して最後に;(半角セミコロン)を付けて終了です.

これでどの場所からも[ VisualBoyAdvance-SDL ]が実行できるようになっているはずです.


Visual Boy Advance SDL コマンド表

命令 意味 書式例
ba ARM命令にブレイクポイントを仕掛ける ba 08000000
bd ブレイクポイントを削除する bd 0
bl ブレイクポイントのリストを表示 bl
bpw 指定したアドレスに対して書き込みが行われたらブレイクするように設定する bpw 02000000 1(調べたい値のバイト長に合わせる)
bpwc bpwブレイクポイントを全て削除する bpwc
break Adds a breakpoint on the given function break <function>|<line>|<file:line>
bt サム命令にブレイクポイントを仕掛ける bt 08000000
c 停止中のプログラムを再開させる c
d 現在のステートで逆アセンブルする d 08000000
da ARM命令で逆アセンブルする da 08000000
dt サム命令で逆アセンブルする dt 08000000
eb 1バイト長で指定アドレスに書き込みをする eb 02000000 00
eh 2バイト長で指定アドレスに書き込みをする eh 02000000 0000(アドレス指定は2h毎に指定)
ew 4バイト長で指定アドレスに書き込みをする eh 02000000 00000000(アドレス指定は4h毎に指定)
h コマンドのヘルプを表示する h
io IOレジスタの状態を表示する io
locals Shows local variables locals
mb 1バイト長で指定アドレスを表示する mb 02000000
mh 2バイト長で指定アドレスを表示する mh 02000000
mw 4バイト長で指定アドレスを表示する mw 02000000
n トレース実行 n
print Print the value of a expression (if known) print
q 終了させる q
r ARMレジスタを表示する r
radix Sets the print radix radix
symbols List symbols symbols
verbose Change verbose setting verbose
where Shows call chain where
  ※一部英語あるのは、うまく訳せない+実装されてるのかが不明(汗) ※必ず半角小文字でコマンドを入れてください

その他のコマンド

F1~F10 ステートロード
SHIFT+F1~F10 ステートセーブ
F11 実行中にデバッグ画面の呼び出し
F12 スクリーンキャプチャー
ESC 終了(デバッグ画面が出てない時)

 

 

 

 


増減系サーチ方法

 

シャイニングソウルの[ HP減らない ]を題材にして説明していきます.

まず、主人公の現在HP(現在:36)のアドレスは[ 030045ea ]です.F11を押し、デバッグ画面に入ります.

そこで、[ bpw 030045ea 2 ]と入力します.

そして試しに敵あたってダメージを食らってみます.(後:34)するとここでブレークしました.

Breakpoint (on write) address 030045ea old:0024 new:0022
R00=00000022 R04=03004570 R08=03004638 R12=030045ea
R01=00000000 R05=00000000 R09=03004640 R13=030042f8
R02=030045ea R06=00000001 R10=03004612 R14=0800b1ff
R03=030045c4 R07=03004570 R11=00000000 R15=0800b218
CPSR=2000003f (..C...T Mode: 1f)
0800b216 1c22 add r2, r4, #0x0
↑このアドレスの2バイト手前の命令で書き込みされた.

適当に、[ dt 0800b200 ]と入力します.

0800b200 2d00 cmp r5, #0x0
0800b202 d123 bne $0800b24c
0800b204 227a mov r2, #0x7a
0800b206 1912 add r2, r2, r4
0800b208 4694 mov r12, r2
0800b20a 1c23 mov r3, r4
0800b20c 3354 add r3, #0x54
0800b20e 8810 ldrh r0, [r2, #0x0]  ;r0の値(現在HP)をハーフワードロード
0800b210 8819 ldrh r1, [r3, #0x0]  ;r1の値をハーフワードロード、アドレス[ 030045c4 ]がダメージの格納アドレス
0800b212 1a40 sub r0, r0, r1         ;r0をr1で減算
0800b214 8010 strh r0, [r2, #0x0]  ;r0の値をアドレス[ 030045ea ]に書き込み
0800b216 1c22 mov r2, r4           ;ブレークポイント
0800b218 3256 add r2, #0x56
0800b21a 8811 ldrh r1, [r2, #0x0]
0800b21c 1a40 sub r0, r0, r1
0800b21e 4661 mov r1, r12
0800b220 8008 strh r0, [r1, #0x0]
0800b222 801d strh r5, [r3, #0x0]
0800b224 8015 strh r5, [r2, #0x0]
0800b226 2200 mov r2, #0x0

0800b214(strh r0, [r2, #0x0])が書き込みを行っていることがわかりました.

この命令の意味は、アドレス[ r2+$0x0 ]にr0の値を2バイト長で書き込むという意味です.

そこで、ブレークした時のレジスタの状態を見てみましょう.[ R00=00000022 ]、[ R02=030045ea ]ということがわかります.

つまりこの命令が実行される時、R00の値が書き込まれない.もしくは変化してなければ良いということになります.

方法としては、2つあります.一つはアドレス[ 0800b214 ]をNOP命令に書き換えてしまうこと.もう一つは、アドレス[ 0800b212 ]

を良く見てください.(sub r0, r0, r1)とありますね.意味は、( r0 = r0 - r1) です.r0の値を[ 0800b214 ]で書き込むわけですか

ら、ここの命令をNOPに変えるか、(sub r0,r0,$0x0)に書き換えてもできそうです.他にも書き換えられる箇所はありますが、

とりあえずこの2箇所で事足りそうなので、今回はアドレス[ 0800b212 ]をNOP命令に書き換えて見ましょう.

[ eh 0800b212 46c0 ]と入力します.(46c0は(NOP = mov r8,r8)のバイナリです.)

それでは、ゲームを再開して試してみましょう.敵からダメージを食らってみます.すると成功しました.これで改造は成功です.

今回は、HP減らないを作ってみましたが、他のゲームでも店で買い物した時に、お金が減らない、売った時に増えない等でも

今回のようなアプローチ方法で大抵のゲームは改造が可能です.


○○MAX系サーチ方法

 

ブレスオブファイア2の[ 一度の戦闘でお金MAX ]を題材にして説明していきます.

まず、お金(現在:255)のアドレスは[02006860]です.F11を押しデバッグ画面に入ります.

そこで、[ bpw 02006860 4 ]と入力します.

戦闘をして、お金を手に入れます(後:267).するとここでブレークしました.

Breakpoint (on write) address 02006860 old:000000ff new:0000010b
R00=0098967f R04=03002700 R08=00000010 R12=0000000b
R01=0000010b R05=00000002 R09=083d73ac R13=03007db4
R02=02006810 R06=030026c0 R10=083d73de R14=080b6f43
R03=00000001 R07=083d74e1 R11=083d718d R15=08023336
CPSR=8000003f (N.....T Mode: 1f)
08023334 4770 bx lr
↑このアドレスの2バイト手前の命令で書き込みされた.

適当に、[ dt 08023310 ]と入力します.

08023310 e005 b $0802331e
08023312 0000 lsl r0, r0, #0x00
08023314 6810 ldr r0, [r2, #0x0]
08023316 0200 lsl r0, r0, #0x08
08023318 1a40 sub r0, r0, r1
0802331a 6510 str r0, [r2, #0x50]
0802331c 2001 mov r0, #0x1
0802331e 4770 bx lr
08023320 0400 lsl r0, r0, #0x10
08023322 0c00 lsr r0, r0, #0x10
08023324 4a04 ldr r2, [$08023338] (=$02006810)     ;r2にアドレス[ 08023338 ]の値(02006810)をワードロード
08023326 6d11 ldr r1, [r2, #0x50]                  ;r1にアドレス[ 02006860 ](お金のアドレス)の値をワードロード
08023328 1809 add r1, r1, r0                       ;r1をr0で加算
0802332a 4804 ldr r0, [$0802333c] (=$0098967f)     ;r0にアドレス[ 0802333c ]の値(0098967f)をワードロード
0802332c 4281 cmp r1, r0                           ;r1-r0について比較演算
0802332e d900 bls $08023332                        ;r1の値がr0(0098967f)より小さいか等しければアドレス[ 08023332 ]にジャンプ
08023330 1c01 mov r1, r0                           ;r1にr0の値(0098967f)を代入
08023332 6511 str r1, [r2, #0x50]                  ;r1の値をアドレス[ 02006860 ](お金のアドレス)にワードストア
08023334 4770 bx lr
08023336 0000 lsl r0, r0, #0x00

08023332(str r1, [r2, #0x50])の命令が書き込みを行っていることがわかりました.

この命令の意味は、アドレス[ r2+$0x50 ]にr1の値を4バイト長で書き込むという意味です.

そこで、ブレークした時のレジスタの状態を見てみましょう.[ R01=0000010b ]、[ R02=02006810 ]ということがわかります.

ここでR00の値にも注目してみましょう.[ R00=0098967f ] ですね.これを10進数に直すと…9999999です.これは、ゲーム

中のお金の値の最大値です.そこで上のプログラムのリストを注目してみましょう.プログラムを上から眺めていくと…アドレス[ 0802332a ]~[ 0802332e ]

の命令が怪しいです.アドレス[ 0802332a ]でr1にお金の最大値を代入し、次のアドレス[ 0802332a ]で比較演算し、その結果によってはアドレス[ 08023330 ]

で最大値を代入し、アドレス[ 08023332 ]で書き込みを行っています.つまり、ここがお金の最大値チェックの場所であることがわかります.

ここまで分かれば、書き換える場所はアドレス[ 0802332e ]かアドレス[ 08023332 ]ですね.書き換える方法としては、アドレス[ 0802332e ]をBLSと反対の条件

分岐命令のBHIにするかNOP命令にする方法.アドレス[ 08023332 ]を[ R00=0098967f ]なのを利用して、[ str r0, [r2, #0x50]]に書き換える方法があります.

今回はアドレス[ 0802332e ]を書き換えることにします.しかし、最初のBHI命令では、最大値(0098967f)を超えた状態だと、本来その前の加算で最大値を超え

ている場合にアドレス[ 08023330 ]で最大値(0098967f)に書き戻されなくなってしまいます.ゲームを進行するのには支障はないのですが、個人的には少々美

しくないです.ということで、ここはNOP命令に書き換えることにします.

[ eh 0802332e 46c0 ]と入力します.

それでは、ゲームを再開してみましょう.戦闘してお金を手に入れてみます.すると成功しました.これで改造は成功です.

今回紹介したブレスオブファイア2は、同様の方法で戦闘後に経験値MAXができますので、それを自分でやってみて理解を深めてみてください.


戦闘後全回復系サーチ方法

ドラゴンクエスト キャラバンハートの[ 戦闘後HP&MP全快 ]を題材にして説明していきます.

説明の前にポイント.この手のサーチにはHPやMPのアドレスが戦闘中とフィールド上で別々じゃないと改造方法がかなり面倒になってしまいます.

今回の題材は、別々に管理されていたので楽にできました.では、その手順を説明します.

まずガードモンスター1匹目の現在HPのアドレスは[ 020143e0 ]です.

[ bpw 020143e0 2 ]と入力します.

戦闘をして、フィールドに戻る直前でブレークしました.

Breakpoint (on write) address 020143e0 old:001a new:0019
R00=020143c0 R04=02010538 R08=03005060 R12=030061dc
R01=00000019 R05=00000074 R09=02013850 R13=03007e38
R02=0000001a R06=020143c0 R10=00000000 R14=08026031
R03=00000000 R07=02013830 R11=00000000 R15=080326d8
CPSR=0000003f (......T Mode: 1f)
080326d6 7820 ldrb r0, [r4, #0x0]
↑このアドレスの2バイト手前の命令で書き込みされた.

適当に、[ dt 080326c6 ]と入力します.

080326c6 7820 ldrb r0, [r4, #0x0]
080326c8 1c01 mov r1, r0
080326ca 4369 mul r1, r5
080326cc 1c08 mov r0, r1
080326ce 1980 add r0, r0, r6
080326d0 8db9 ldrh r1, [r7, #0x2c]          ;r1にアドレス[ 201385C ]の値をハーフワードロード
080326d2 8c02 ldrh r2, [r0, #0x20]          ;r2にアドレス[ 20143E0 ]の値をハーフワードロード
080326d4 8401 strh r1, [r0, #0x20]          ;r1の値をアドレス[ 20143E0 ](フィールド上の現在HPのアドレス)にハーフワードストア
080326d6 7820 ldrb r0, [r4, #0x0]           ;ブレークポイント
080326d8 1c01 mov r1, r0
080326da 4369 mul r1, r5
080326dc 1989 add r1, r1, r6
080326de 7820 ldrb r0, [r4, #0x0]
080326e0 1c02 mov r2, r0
080326e2 436a mul r2, r5
080326e4 1c10 mov r0, r2
080326e6 1980 add r0, r0, r6
080326e8 8c09 ldrh r1, [r1, #0x20]
080326ea 8c40 ldrh r0, [r0, #0x22]
080326ec 4281 cmp r1, r0

080326d4(strh r1, [r0, #0x20])の命令が書き込みを行っていることがわかりました.

この命令の意味は、アドレス[ r0+$0x20 ]にr1の値を2バイト長書き込むという意味です.

そこで、ブレークした時のレジスタの状態を見てみましょう.[ R00=020143c0 ]、[ R01=00000019 ]ということがわかります.

ここでプログラムを眺めていくと、アドレス[ 080326d0 ]が怪しいですね.アドレス[ 080326d4 ]の書き込みしている命令の直前でr1に値をロードしています.

普通に考えれば、ここが現在HPの値をロードしている場所ですね.

[ mb 201385C ]と入力します.

0201385c 19 00 1e 00 06 00 06 00 16 00 10 00 10 00 40 00
0201386c 00 00 00 00 9c 00 00 00 ff ff ff ff ff ff ff ff

メモリを確認してみると、ここが戦闘中のHP等の格納アドレスであることがわかりました.2バイト毎に現在HP、最大HP、現在MP、最大MPと並んでいること

がわかります.つまり、現在HPのアドレスの2バイト先が最大HPのアドレスになるのです.ここまでわかれば、後は命令を書き換えるだけですね.書き換え

る方法としては、アドレス[ 080326d0 ]を[ ldrh r1, [r7, #0x2e ] ]に書き換えるか、[ R00=020143c0 ]なのを利用して[ ldrh r1, [r0, #0x22 ] ]に書き換えるか

ですね.後者の書き換えはフィールド上の現在HPのアドレスに2バイト足したアドレス(最大HP)です.どちらでも良いのですが、今回は後者の命令に書き換

えることにします.

[ eh 080326d0 8c41 ]と入力します.

それでは確かめて見ることにしましょう.戦闘終了して、フィールドに戻るとHPが最大値になっていました.これで改造は成功です.

次にMPを戦闘後に最大値になるようにしましょう.ガードモンスター1匹目の現在MPのアドレスは[ 020143e4 ]です.

[ bpw 020143e4 2]と入力します.

Breakpoint (on write) address 020143e4 old:0006 new:0006
R00=020143c0 R04=02010538 R08=03005060 R12=030061dc
R01=00000006 R05=00000074 R09=02013850 R13=03007e38
R02=00000006 R06=020143c0 R10=00000000 R14=08026031
R03=00000000 R07=02013830 R11=00000000 R15=0803271a
CPSR=0000003f (......T Mode: 1f)
08032718 7820 ldrb r0, [r4, #0x0]
↑このアドレスの2バイト手前の命令で書き込みされた.

適当に、[ dt 08032700 ]と入力します.

08032700 1980 add r0, r0, r6
08032702 8c40 ldrh r0, [r0, #0x22]
08032704 8c0a ldrh r2, [r1, #0x20]
08032706 8408 strh r0, [r1, #0x20]
08032708 7820 ldrb r0, [r4, #0x0]
0803270a 1c01 mov r1, r0
0803270c 4369 mul r1, r5
0803270e 1c08 mov r0, r1
08032710 1980 add r0, r0, r6
08032712 8e39 ldrh r1, [r7, #0x30]          ;r1にアドレス[ 2013860 ]の値をハーフワードロード
08032714 8c82 ldrh r2, [r0, #0x24]          ;r2にアドレス[ 20143E4 ]の値をハーフワードロード
08032716 8481 strh r1, [r0, #0x24]          ;r1の値をアドレス[ 20143E4 ](フィールド上の現在MPのアドレス)にバイトストア
08032718 7820 ldrb r0, [r4, #0x0]           ;ブレークポイント
0803271a 1c01 mov r1, r0
0803271c 4369 mul r1, r5
0803271e 1989 add r1, r1, r6
08032720 7820 ldrb r0, [r4, #0x0]
08032722 1c02 mov r2, r0
08032724 436a mul r2, r5
08032726 1c10 mov r0, r2

現在HPの時と考え方はまったく一緒です.ただ単にアドレスが違うだけですね.アドレス[ 08032712 ]を書き換えればいけそうですね.

書き換える候補としては、[ ldrh r1, [r7, #0x32] ]にするか、[ ldrh r1, [r0, #0x26 ]ですね.今回は後者で書き換えます.

[ eh 08032712 8cc1 ]と入力します.

それでは確かめて見ることにしましょう.戦闘終了して、フィールドに戻るとMPが最大値になっていました.これで改造は成功です.

この方法は、戦闘中とフィールド上でのアドレスが別々のゲームなら殆どこのアプローチの仕方で良いと思いますので、他のゲーム

を自分で解析してみて理解を深めてください.


一撃死系サーチ方法

ブレスオブファイア2の[ 敵一撃死 ]を題材にして説明していきます.

説明の前にポイント.この手のサーチには、方法がいくつかあります.2つほど紹介します.一つ目は、戦闘中にHPを減算する命令の部分を書き換える.

二つ目は、戦闘開始時にHPがロードされる部分を書き換える等です.しかし、一つ目はポインタの値を変えて、減算ルーチン自体は一緒という場合が多

々あります.効率を考えれば当たり前の話ですよね.その場合、ルーチンが一緒のために死亡判定等を書き換えたりすると、味方にまで影響が及んで

しまいます.ということで今回は、2つ目の戦闘開始時にHPがロードされている命令を書き換えることにします.この命令のルーチンは味方と敵は別々と

いう場合が多いです.なぜなら、味方には装備している武器、防具、アイテム等の値も一緒にメモリに書き込まないといけないですが、敵の場合はステ

ータスのみという場合が多いからです.その場合には、ルーチン自体を別々にした方が管理しやすいですからね.ちなみに2つ目は、死亡判定等を書き

換えるのではなく、ロードされる値を常に0にすることによって一撃死を可能にさせます.それでは、説明に入ります.

敵一体目(今回の場合はスライム.現在HP:13)の戦闘中の現在HPは[ 030047d4 ]です.

[ bpw 030047d4 2 ]と入力します.

戦闘画面に入る直前にブレークしました.

Breakpoint (on write) address 030047d4 old:0000 new:000d
R00=030047d4 R04=030047cc R08=03004784 R12=00000000
R01=0000000d R05=03004785 R09=03002750 R13=03007d78
R02=ffffffff R06=00000060 R10=03002750 R14=08025a63
R03=081764bc R07=03004740 R11=083d718d R15=08025a80
CPSR=0000003f (......T Mode: 1f)
08025a7e 3002 add r0, #0x2
↑このアドレスの2バイト手前の命令で書き込みされた.

適当に、[ dt 08025a60 ]と入力します.

08025a60 f95d blh $02ba
08025a62 1c30 mov r0, r6
08025a64 3008 add r0, #0x8
08025a66 9b01 ldr r3, [sp, #0x4]
08025a68 18c0 add r0, r0, r3
08025a6a 7801 ldrb r1, [r0, #0x0]
08025a6c 1c30 mov r0, r6
08025a6e 3009 add r0, #0x9
08025a70 18c0 add r0, r0, r3
08025a72 7800 ldrb r0, [r0, #0x0]
08025a74 0200 lsl r0, r0, #0x08
08025a76 4301 orr r1, r0                   ;r1をr0で論理和
08025a78 1c38 mov r0, r7
08025a7a 3094 add r0, #0x94
08025a7c 8001 strh r1, [r0, #0x0]          ;r1の値をアドレス[ 030047d4 ](戦闘中の現在HPのアドレス)にハーフワードストア
08025a7e 3002 add r0, #0x2                 ;ブレークポイント
08025a80 8001 strh r1, [r0, #0x0]
08025a82 1c30 mov r0, r6
08025a84 300a add r0, #0xa
08025a86 18c0 add r0, r0, r3

08025a7e(strh r1, [r0, #0x0])の命令が書き込みを行っていることがわかりました.

この命令の意味は、アドレス[ r0+$0x0 ]にr1の値を2バイト長で書き込むという意味です.

そこで、ブレークした時のレジスタの状態を見てみましょう.[ R00=030047d4 ]、[ R01=0000000d ]ということがわかります.

プログラムを眺めていくと、2箇所程書き換えられそうな場所があります.アドレス[ 08025a76 ]、[ 08025a7c ]です.アドレス[ 08025a76 ]の方は、

アドレス[ 08025a7c ]で書き込まれるr1の値を最終的に変化させてる命令ですね.ここを[ mov r1,$0x0]と書き換えてやれば、r1の値が常に0に

なりますね.アドレス[ 08025a7c ]の方は、書き込む命令ですから、ここをNOP命令に書き換えてやれば、書き込み命令が実行されないのでメモ

リには書き込まれず、結果として常に0の状態になります.今回は、後者のNOP命令で書き換えることにします.

[ eh 08025a7c 46c0 ]と入力します.

それでは確かめることにしましょう.戦闘に入り敵のHPを調べてみると、HPのバーが全部なくなっている状態で戦闘が開始されました.

改造は成功です.このやり方はNOP命令で潰すだけなので、まだ慣れてない人にはいい練習台になると思います. ちなみにサモンナイトは、メ

モリに0を書き込む方法で可能なので、持っている方は練習がてら是非試してみてください.


○○n倍系サーチ方法

サモンナイトの[ 戦闘後獲得する経験値n倍 ]を題材にして説明していきます.

最初に一言:この手の改造はプログラムの構造等で、できないゲームはできません.無理矢理やっても効率が悪くなるだけです.

説明の前に補足、PS2でもよくあるn倍系改造コードは対象となる値に対して、左シフトをn回実行することによって可能.

2^n乗なので一回実行すれば…2倍.二回実行すれば…4倍.3回実行すれば…8倍.4回実行すれば…16倍という風になります.

逆に1/nは右シフトをn回実行すれば可能.右シフトは2^-n乗となります.では説明に入っていきます.

戦闘終了後の獲得経験値は[ 03001420 ]です.(今回はスライム2匹)

[ bpw 03001420 4 ]と入力します.

戦闘終了後にブレークしました.

Breakpoint (on write) address 03001420 old:00000000 new:00000008
R00=00000008 R04=00000006 R08=00000000 R12=02001100
R01=03001420 R05=00000008 R09=0000000a R13=03007d80
R02=00000008 R06=03001424 R10=03001420 R14=0802fb41
R03=00000010 R07=03000000 R11=00000000 R15=0802fb4c
CPSR=0000003f (......T Mode: 1f)
0802fb4a 2201 mov r2, #0x1
↑このアドレスの2バイト手前の命令で書き込みされた.

普段ならここで、dtコマンドを使ってプログラムを見るのですが、少々おかしいことに気がつきました.このゲームはレベルが上がるごとに、

獲得経験値が減っていきますが、どうもそれだけでなく1匹分しか獲得してないように見えます.そこで、cコマンドを使って再開させてみる

ことにします.するとcコマンドを入力した直後にまたブレークしました.

Breakpoint (on write) address 03001420 old:00000008 new:00000010
R00=00000010 R04=00000006 R08=00000001 R12=02001100
R01=03001420 R05=00000008 R09=0000000a R13=03007d80
R02=00000008 R06=03001424 R10=03001420 R14=0802fb41
R03=00000010 R07=03000000 R11=00000000 R15=0802fb4c
CPSR=0000003f (......T Mode: 1f)
0802fb4a 2201 mov r2, #0x1
↑このアドレスの2バイト手前の命令で書き込みされた.

同じモンスターなので分かり難いですが、どうやらこのゲームはメモリに対して獲得経験値を一気に書き込むのではなく、1体ずつ加算して

いくようです.もっと詳しく調べるためにdtコマンドを入れます.

適当に、[ dt 0802fb2a ]と入力する.

0802fb2a 1824 add r4, r4, r0
0802fb2c 4264 neg r4, r4
0802fb2e 2c09 cmp r4, #0x9
0802fb30 dd00 ble $0802fb34
0802fb32 2409 mov r4, #0x9
0802fb34 464a mov r2, r9
0802fb36 1b10 sub r0, r2, r4
0802fb38 4368 mul r0, r5
0802fb3a 210a mov r1, #0xa
0802fb3c f03b bl $0806b7fc                              ;アドレス[ 0806b7fc ]のサブルーチンへジャンプ
0802fb40 1c05 mov r5, r0                                ;r5にr0の値を格納
0802fb42 4651 mov r1, r10                               ;r1にr10の値を格納
0802fb44 6808 ldr r0, [r1, #0x0]                        ;r0にアドレス[ 03001420 ]の値をワードロード
0802fb46 1940 add r0, r0, r5                            ;r0をr5で加算
0802fb48 6008 str r0, [r1, #0x0]                        ;r0の値をアドレス[ 03001420 ](獲得経験値のアドレス)にワードストア
0802fb4a 2201 mov r2, #0x1                              ;ブレークポイント
0802fb4c 4490 add r8, r2
0802fb4e 4910 ldr r1, [$0802fb90] (=$0000051e)
0802fb50 1878 add r0, r7, r1
0802fb52 8800 ldrh r0, [r0, #0x0]

0802fb48(str r0, [r1, #0x0])の命令が書き込みを行っていることがわかりました.

この命令の意味は、アドレス[ r1+$0x0 ]にr0の値を4バイト長で書き込むという意味です.

そこで、ブレークした時のレジスタの状態を見てみましょう.[ R00=00000010 ]、[ R01=03001420 ]ということがわかります.

プログラムを眺めていくと、アドレス[ 0802fb44 ]~[ 0802fb46 ]が怪しいですね.アドレス[ 03001420 ]の値をr0にワードロードして、r0とr5の値

を加算した後に、アドレス[ 0802fb48 ]にr0の値を書き込んでいます.この事から、r0かr5の値を変化させればn倍が実現できそうですね.

そこで、1回目のブレーク時と2回目のブレーク時レジスタの値を比較してみましょう.

ブレーク1回目:[ R00=00000008 ]、[ R05=00000008 ]

ブレーク2回目:[ R00=00000010 ]、[ R05=00000008 ]

ここで唯一変化しているのは、r0の値だけです.1回目と2回目のr0の値の差は[ $0x10 - $0x8 = $0x8 ]です.今回の例では、スライム2匹です.

スライム1体の経験値は10進数で[ 8 ]です.2匹分だと10進数で[ 16 ]、16進数で[ 10 ]になります.これより、r5の値が敵1体分の経験値の値で

あるということがわかりました.つまり、r5の値をn倍してやれば経験値n倍にすることが可能ですね.この事を踏まえてプログラムを眺めていきま

しょう.するとアドレス[ 0802fb40 ]が変えられそうな感じがします.サブルーチンでr0の値を敵1体分の経験値を格納した後に、復帰し、r5にr0の

値を格納しています.ここのr0の値をn倍してr5に格納してやればOKですね.今回は、変化がわかりやすい経験値16倍にしてチェックをしてみま

す.書き換える命令は[ lsl r5,r0,$0x4 ]となります.意味は、[ r5 = r0 * 2^4 ]です.

[ eh 0802fb40 0105 ]と入力する.

それでは確かめてみましょう.今回は、全く同じ条件のスライム2匹です.戦闘終了後に獲得経験値を見てみると…獲得経験値が16の16倍の

256になっていました.これで改造は成功です.今回のサモンナイトは上記のようなアプローチ方法でサーチできました.基本的な考え方は一

緒のはずなので他のゲームもチャレンジしてみてください.


レジスタの値からワークコードを探す方法

ファイアーエムブレム 烈火の剣の[ 戦闘終了後攻撃側のステータス増加値 ]を題材にして説明していきます.

今回の例は本来は戦闘終了後にステータスMAXになるようなプログラムコードを探している時に見つかった副産物で、最初から見つけよ

うとしたわけではありません.最初から見つけるなら普通にサーチした方が早いです.ということでプログラムコードを作成するのではなく、

デバッガを使ってワークアドレスを探す方法を説明します.不安定なプログラムコードではなくワークコードで対応できるなら、極力それにし

た方が良いですからね.では説明していきます.

まずレベルアップして力が上昇するようにステートセーブとロードを使って確認しながらやります.1人目の力は[ 0202bd60 ]です.

力が上がる様にうまく戦闘順番をずらしたりして確認してください.力が上がるのが確認できたらステートロードでその戦闘の直前に戻ります.

現在の:5、レベルアップ後:6

[ bpw 0202bd60 1 ]と入力します.

戦闘が終了してMAP画面に復帰するときにブレークしました.

Breakpoint (on write) address 0202bd60 old:05 new:06
R00=00000006 R04=0202bd4c R08=00000000 R12=00000001
R01=00000005 R05=0203a3ec R09=00000000 R13=03007d70
R02=03002778 R06=0202cf04 R10=00000000 R14=08029f87
R03=ffffffff R07=0202bd4c R11=03007dfc R15=0802a122
CPSR=0000003f (......T Mode: 1f)
0802a120 1c28 mov r0, r5
↑このアドレスの2バイト手前の命令で書き込みされた.

適当に、[ dt 0802a100 ]と入力します.

0802a100 1c01 mov r1, r0
0802a102 1c20 mov r0, r4
0802a104 f7ed bl $080179d8
0802a108 1c28 mov r0, r5
0802a10a 3073 add r0, #0x73
0802a10c 7ca1 ldrb r1, [r4, #0x12]
0802a10e 7800 ldrb r0, [r0, #0x0]
0802a110 1808 add r0, r1, r0
0802a112 74a0 strb r0, [r4, #0x12]
0802a114 1c28 mov r0, r5                    ;r0にr5の値を格納
0802a116 3074 add r0, #0x74                 ;r0を$0x74で加算
0802a118 7d21 ldrb r1, [r4, #0x14]          ;r1にアドレス[ 0202bd60 ](力)の値をバイトロード
0802a11a 7800 ldrb r0, [r0, #0x0]           ;r0にアドレス[ r0 ]の値をバイトロード
0802a11c 1808 add r0, r1, r0                ;r0をr1で加算
0802a11e 7520 strb r0, [r4, #0x14]          ;r0の値をアドレス[ 0202bd60 ](力)にバイトストア
0802a120 1c28 mov r0, r5                    ;ブレークポイント
0802a122 3075 add r0, #0x75
0802a124 7d61 ldrb r1, [r4, #0x15]
0802a126 7800 ldrb r0, [r0, #0x0]
0802a128 1808 add r0, r1, r0

0802a11e(strb r0, [r4, #0x14])の命令が書き込みを行っていることがわかりました.

この命令の意味は、アドレス[ r4+$0x14 ]にr0の値を1バイト長で書き込むという意味です.

そこで、ブレークした時のレジスタの状態を見てみましょう.[ R00=00000006 ]、[ R04=0202bd4c ]ということがわかります.プログラムを眺めて

いくと怪しい箇所がありますね.アドレス[ 0802a11c ]ですね.r0にr1の値を加算しています.この直後にメモリに対して書き込みを行っています

から、これが力の値であることがわかります.普段ならここでアドレス[ 0802a11c ]の命令を[ add r0,$0x1 ]とでも書き換えてやれば、必ず力が1

上がるようになるのですが、ここでアドレス[ 0802a118 ]、[ 0802a11a ]に注目してください.アドレス[ 0802a118 ]で現在の力の値をr1にロードし

て、アドレス[ 0802a11a ]でr0に[ r0 ]の値をロードしています.アドレス[ 0802a11c ]から見てもわかるように、このr0の値が加算に使う値となり

ます.そこでr0に[ r0 ]の値をロードする時のr0の値がわかれば、メモリアドレスが特定できますね.通常ならばアドレス[ 0802a11a ]にbtコマンド

を仕掛けてロードする直前でブレークさせてr0の値を確かめるのも良いのですが、今回は少し上の方を見てみるとアドレス[ 0802a114 ]、[ 0802a116]

でr0にr5の値を格納して、r0に$0x74を足してますね.つまりr5の値に$0x74を足した値が、アドレス[ 0802a11a ]でr0にロードされる時の[ r0 ]の

値になります.ここでr5を確認してみましょう.[ R05=0203a3ec ]となっています.[ R5 + $0x74 = 203A460 ]となります.つまりここが力に加算さ

れる値が格納されているメモリアドレスとなります.試しにVBAのチート機能を使って$0x1で固定してみます.すると戦闘終了後に力が1上がって

いました.これで改造は成功です.後は芋づる式に周りのアドレスを探れば他のステータスのアドレスもわかります(実はブレークポイントのアド

レス周辺を逆アセンブルすれば他のステータス値に書き込む命令が書かれているので、そんなことしなくてもわかったりします).


アイテム全部所持系サーチ方法

真・女神転生の[ SORTをするとアイテム全部所持 ]を題材にして説明していきます.

最初に一言:PSやPC等では通常[ Memory on Read ]で解析するのですが、デバッガにその機能が実装されていないので[ Memory on Write ]

        を使っての解析の方法を説明していきます.ちなみに、このゲームはX-TAの連続書き込み型のコードで対応できたりします(汗).

        まぁこんなアプローチの仕方ってことを覚えてください.尚、今回は今までより少々難しいので今までの講座が理解できてる事が前

        提です.

[ Memory on Write ]はSDLでは[ bpw ]に当たります.解析するゲームの基準としては、アイテム等の自動整頓機能がついてるということが重要

です.[ Memory on Read ]ならアイテムリスト等を開いた時にメモリを参照するはずなのでそこにブレークポイントを仕掛ければOKです.これなら

ば、どのゲームだって解析できそうですけど、デバッガに機能が実装されてないので現時点ではあきらめるしかありません.では解説に入ります.

1つ目のアイテム番号は[ 03004e48 ]、アイテム個数は[ 03004e4a ]です.

[ bpw 03004e48 1]、[ bpw 03004e4a 1]と入力します.

SORTをしてみるとすぐにブレークしました.

Breakpoint (on write) address 03004e48 old:00b0 new:0002
R00=00000002 R04=00000140 R08=080dcbf0 R12=00000140
R01=0300009c R05=03004e48 R09=00000000 R13=03007e18
R02=00000002 R06=00000002 R10=00000000 R14=0807c093
R03=ffffffff R07=00000003 R11=00000000 R15=0807c0a6
CPSR=0000003f (......T Mode: 1f)
0807c0a4 8808 ldrh r0, [r1, #0x0]
↑このアドレスの2バイト手前の命令で書き込みされた

もう一つ仕掛けているのでcコマンドで再実行します.

Breakpoint (on write) address 03004e4a old:0003 new:0001
R00=00000001 R04=00000140 R08=080dcbf0 R12=00000140
R01=0300009c R05=03004e48 R09=00000000 R13=03007e18
R02=00000002 R06=00000002 R10=00000000 R14=0807c093
R03=ffffffff R07=00000003 R11=00000000 R15=0807c0aa
PSR=0000003f (......T Mode: 1f)
0807c0a8 3504 add r5, #0x4
↑このアドレスの2バイト手前の命令で書き込みされた

二つを見比べると、一発でわかるのがアドレスが近いですね.dtコマンドで逆アセンブルしてみましょう.

[ dt 0807c092 ]と入力する.

0807c092 2301 mov r3, #0x1                 ;r3に$0x1を格納
0807c094 425b neg r3, r3                   ;r3を符号反転(1→-1)
0807c096 1c31 mov r1, r6                   ;r1にr6の値を格納
0807c098 884a ldrh r2, [r1, #0x2]          ;r2にアドレス[ r1 + $0x2 ]の値(アイテム番号)をハーフワードロード(ループ開始)
0807c09a 2602 mov r6, #0x2                 ;r6に$0x2を格納
0807c09c 5f88 ldsh r0, [r1, r6]            ;r0にアドレス[ r1 + r6 ]の値をハーフワードロード
0807c09e 4298 cmp r0, r3                   ;r0-r3について比較演算
0807c0a0 d003 beq $0807c0aa                ;比較演算の結果が0ならばアドレス[ 0807c0aa ]へジャンプ
0807c0a2 802a strh r2, [r5, #0x0]          ;r2の値をアドレス[ 03004e48 ](アイテム番号)にハーフワードストア
0807c0a4 8808 ldrh r0, [r1, #0x0]          ;r0にアドレス[ 0300009c ]の値(アイテムの個数)をハーフワードロード
0807c0a6 8068 strh r0, [r5, #0x2]          ;r0の値をアドレス[ 03004e4a ](アイテム個数)にハーフワードストア
0807c0a8 3504 add r5, #0x4                 ;r5+$0x4
0807c0aa 3104 add r1, #0x4                 ;r1+$0x4
0807c0ac 3c01 sub r4, #0x1                 ;r4-$0x1
0807c0ae 2c00 cmp r4, #0x0                 ;r4-$0x0について比較演算
0807c0b0 d1f2 bne $0807c098                ;比較演算の結果が0でなければアドレス[ 0807c098 ]へジャンプ(ループ終了)
0807c0b2 b001 add sp, #0x4                 
0807c0b4 bc70 pop {r4-r6}                  
0807c0b6 bc01 pop {r0}
0807c0b8 4700 bx r0

アイテム番号は0807c0a2(strh r2, [r5, #0x0])、アイテム個数は0807c0a6(strh r0, [r5, #0x2])の命令が書き込みを行っていることがわかりました.

そこで、ブレークした時のレジスタの状態を見てみましょう.

実行アドレス[ 0807c0a2 ]の時[ R00=00000002 ]、[ R02=00000002 ]、[ R05=03004e48 ]ということがわかります.

実行アドレス[ 0807c0a6 ]の時[ R00=00000001 ]、[ R02=00000002 ]、[ R05=03004e48 ]ということがわかります.

以上のことから、アドレス[ 0807c0a2 ]の時、r2の値がアイテム番号ということがわかります.

同様に、アドレス[ 0807c0a6 ]の時は、r0がアイテム個数.これを踏まえて、プログラムを眺めていきましょう.

まず、アドレス[ 0807c0a8 ]~[ 0807c0b0 ]が怪しいことがわかります.メモリに対して書き込みを行った後にその部分でいろいろ処理をしています.

アドレス[ 0807c0a8 ]、[ 0807c0aa ]はロード、ストアする時に必要なメモリアドレスの値を加算しています.

アドレス[ 0807c0ac ]~[ 0807c0b0 ]は、r4の値を$0x1で減算した後に、r4の値と$0x0について比較し、その結果が0でない場合に

アドレス[ 0807c098 ]にジャンプします.ここでr4の値を見てみましょう.[ R04=00000140 ]ですね.これがループカウンタの可能性が高いです.

確認のためにbtコマンドを使ってプログラムにブレークポイントを仕掛けて見ましょう.

[ bt 0807c0a2 ]、[ bt 0807c0a6 ]と入力します.

最初にかかるブレークは上記のレジスタの状態と一緒なのでスルーします.2回目のを見てみることにします.

R00=00000003 R04=0000013f R08=080dcbf0 R12=00000140
R01=030000a0 R05=03004e4c R09=00000000 R13=03007e18
R02=00000003 R06=00000002 R10=00000000 R14=0807c093
R03=ffffffff R07=00000003 R11=00000000 R15=0807c0a6
CPSR=0000003f (......T Mode: 1f)
0807c0a4 8808 ldrh r0, [r1, #0x0]
R00=00000001 R04=0000013f R08=080dcbf0 R12=00000140
R01=030000a0 R05=03004e4c R09=00000000 R13=03007e18
R02=00000003 R06=00000002 R10=00000000 R14=0807c093
R03=ffffffff R07=00000003 R11=00000000 R15=0807c0a8
0807c0a6 8068 strh r0, [r5, #0x2]

[ R04=0000013f ]になっていますね.つまりR04の値の分だけループする様になっていることがわかります.

ここでもう一回プログラムを眺めていきましょう.アイテムがR04の値の分だけあるとは限らないので、ちゃんと前の方でループを抜ける

命令があります.それがアドレス[ 0807c09c ]~[ 0807c0a0 ]です.アイテムが存在してなかったらアドレス[ 0807c0aa ]へジャンプして

ループを抜ける仕組みになっています.今までわかったことを踏まえて、今回の主旨のアイテム全部所持について考えていきましょう.

1.R00の値がアイテム個数である.

2.R01の値はアイテム個数及び番号のメモリアドレスが格納されている一時的な場所である.

3.R02の値がアイテム番号である.

4.R04の値はループを行うためのカウンタである.

5.R05の値はアイテム個数及び番号のメモリアドレスが格納されている.

以上のことからプログラムを改変していきます.これは考え方が人によって違うので省略します.自分でも考えてみてください.

[ eh 0807c098 1c1a ]、[ eh 0807c09c 3201 ]、[ eh 0807c0a0 46c0 ]、 [ eh 0807c0a4 2063 ]、 [ eh 0807c0b0 d1f3 ]

と入力します.では試してみましょう.

SORTを実行してみると、全てのアイテムを所持している状態になりました.改造は成功です.

 

下に私が改変したプログラムを載せます.ただ覚えておいて欲しいことは、この下記のプログラムが絶対に正しいというわけではありません.

より短い行数で行えるかもしれませんし、人によっては別の命令で改変する人もいますからね.

 

改変例

0807c092 2301 mov r3, #0x1
0807c094 425b neg r3, r3
0807c096 1c31 mov r1, r6
0807c098 1c1a mov r2, r3                   ;r2(アイテム番号)の値にr3の値(-1)を格納
0807c09a 2602 mov r6, #0x2
0807c09c 3201 add r2, #0x1                 :r2(アイテム番号)の値を$0x1で加算
0807c09e 4298 cmp r0, r3
0807c0a0 46c0 mov r8, r8                   ;beqをnopに変えアイテムが存在してなくてもループするように設定
0807c0a2 802a strh r2, [r5, #0x0]          
0807c0a4 2063 mov r0, #0x63                ;r0(アイテム個数)の値に$0x63を格納し常に99個になるように設定
0807c0a6 8068 strh r0, [r5, #0x2]
0807c0a8 3504 add r5, #0x4
0807c0aa 3104 add r1, #0x4
0807c0ac 3c01 sub r4, #0x1
0807c0ae 2c00 cmp r4, #0x0
0807c0b0 d1f3 bne $0807c09a                ;r4の値が0でない場合はアドレス[ 0807c09a ]へジャンプするように設定
0807c0b2 b001 add sp, #0x4
0807c0b4 bc70 pop {r4-r6}
0807c0b6 bc01 pop {r0}
0807c0b8 4700 bx r0
※改変した場所だけコメントを付けてあります.

 

改変したプログラムの部分の説明

アドレス[ 0807c098 ]

r2に-1を格納しています.これは、ループをする上でアイテム番号の加算をする必要があったのですが、加算命令をループの中に入れなけれ

ばならないため、r2を0で設定してしまうと最初のループでr2の値が1になってしまうのを防ぐためです.r2を-1で初期化することによって最初の

ループで0になるように設定しました.

 

アドレス[ 0807c09c ]

r2(アイテム番号)を1で加算します.これで、アイテム番号の変更を行っています.

 

アドレス[ 0807c0a0 ]

アドレス[ 0807c09e ]の比較演算の結果によってはループを抜けてしまうのでそれを抜けないようしました.

 

アドレス[ 0807c0a4 ]

ロード命令を格納命令に変え、さらにr0(アイテム個数)に$0x63を格納するようにしました.これで、アイテム

の個数がMAX値の99個に固定されるようにしてます.

 

アドレス[ 0807c0b0 ]

本来はループカウンタが0になるまでアドレス[ 0807c098 ]に戻るようになっていたが、アドレス[ 0807c098 ]

をr2の値の初期化に割り当てたために、そこに戻るとずっとアイテム番号が0になってしまうので、次のアドレス

[ 0807c09a ]に戻るようにした.