2006.02.05
No.003 パラメータMAXの基本

パラメータMAX
いわゆる、体力9999や所持金999999・アイテム全種255個取得など、任意のステータスを最大まで引き上げる改造ですね。
最も需要のあるコードではありますが、難易度は限りなく低いです(ゲームによっては例外もありますが…)。

さて、RPGでも何でもありませんが、DS同時発売の名作「直感ヒトフデ」で、
スコアを最大(99999999)に固定する改造コードを作ってみます。
ここで学ぶ概念は全てのゲームに応用できますので、是非身に付けましょう。

逆アセンブルリストを出力
まず、直感ヒトフデのROMイメージ(ASNJ.nds)を逆アセンブラ NDSDIS2 に渡し、逆アセンブルリストを出力しましょう。
コマンドプロンプトから以下のコマンドを実行してください。
NDSDIS2 AJSJ.nds > AJSJ.TXT
当然NDSDIS2はパスの通ったフォルダに置いた状態で実行してください。
(自力でROMを吸い出したのなら、言うまでもなくわかっているはずですが)。
単純に引数にROM名を与え、リダイレクトをテキストに吐いてるだけですが、
詳しい使い方はNDSDIS2同封のマニュアルをご覧ください。

これで同一フォルダ内に逆アセンブルリスト AJSJ.TXT が出力されますので、任意のテキストエディタで開いてください。

ゲーム画面を確認する
解析の前に、ゲーム画面を眺めることでサーチしたい数列の特徴を掴むことが大切です。
例えば直感ヒトフデの場合…

スコア表示

とまあ、こんな感じにスコアは左側に8桁、消去ライン数は右側に4桁並んでいます。
今回サーチするスコアは8桁…つまり最大値は 99999999 であると推測できます。
つまり、プログラム内部に9桁目にオーバーしないための上限値を制限する処理があるとも考えられます。
具体的には「99999999以下か?」や「100000000未満か?」などですね。

これをヒントに逆アセンブルリストから処理を探し出します。

文字列検索
まずはテキストエディタの検索機能を用いて、文字列 99999999 を検索します。
すると、直感ヒトフデの場合は以下の2つの処理がヒットします。
(1つめの該当)
:02012564 E59F1020 ldr r1,[r15, #+0x20]		;r15+0x20=*(0201258c)=#99999999(0x05f5e0ff)
:02012568 E59F0010 ldr r0,[r15, #+0x10]		;r15+0x10=*(02012580)=#34317172(0x020ba374)
:0201256C E5801000 str r1,[r0, #+0x0]		;r0+0x0=*(020ba374)=#-382909456(0xe92d43f0)

(2つめの該当)
:02029EA8 E59F00B8 ldr r0,[r15, #+0xb8]		;r15+0xb8=*(02029f68)=#100000000(0x05f5e100)
:02029EAC E59120C0 ldr r2,[r1, #+0xc0]		;r1+0xc0=*(020b627c)=#-373489652(0xe9bd000c)
:02029EB0 E59F70B4 ldr r7,[r15, #+0xb4]		;r15+0xb4=*(02029f6c)=#10000000(0x00989680)
:02029EB4 E1520000 cmp r2,r0
:02029EB8 A59F00B0 ldrge r0,[r15, #+0xb0]		;r15+0xb0=*(02029f70)=#99999999(0x05f5e0ff)
:02029EBC E59F409C ldr r4,[r15, #+0x9c]		;r15+0x9c=*(02029f60)=#34311348(0x020b8cb4)
:02029EC0 A58100C0 strge r0,[r1, #+0xc0]		;r1+0xc0=*(020b627c)=#-373489652(0xe9bd000c)
「ARMのコード並べられてもサッパリわからんわ!!」
とか思っちゃったりしそうですが、じゃあ今度は少し解説もつけてみましょう。

(1つめの該当)
:02012564 E59F1020 ldr r1,[r15, #+0x20] ;r15+0x20=*(0201258c)=#99999999(0x05f5e0ff)
:02012568 E59F0010 ldr r0,[r15, #+0x10] ;r15+0x10=*(02012580)=#34317172(0x020ba374)
:0201256C E5801000 str r1,[r0, #+0x0] ;r0+0x0=*(020ba374)=#-382909456(0xe92d43f0)
r1レジスタに0x05f5e0ff(10進数99999999)を入れる。
r0レジスタに0x020ba374を入れる。
アドレス020ba374に99999999を書き込む。

(2つめの該当)
:02029EA8 E59F00B8 ldr r0,[r15, #+0xb8] ;r15+0xb8=*(02029f68)=#100000000(0x05f5e100)
:02029EAC E59120C0 ldr r2,[r1, #+0xc0] ;r1+0xc0=*(020b627c)=#-373489652(0xe9bd000c)
:02029EB0 E59F70B4 ldr r7,[r15, #+0xb4] ;r15+0xb4=*(02029f6c)=#10000000(0x00989680)
:02029EB4 E1520000 cmp r2,r0
:02029EB8 A59F00B0 ldrge r0,[r15, #+0xb0] ;r15+0xb0=*(02029f70)=#99999999(0x05f5e0ff)
:02029EBC E59F409C ldr r4,[r15, #+0x9c] ;r15+0x9c=*(02029f60)=#34311348(0x020b8cb4)
:02029EC0 A58100C0 strge r0,[r1, #+0xc0] ;r1+0xc0=*(020b627c)=#-373489652(0xe9bd000c)
r0レジスタに0x05f5e100(10進数100000000)を入れる。
r2レジスタにアドレス020b627cに格納された値を入れる。
r7レジスタに0x00989680を入れる。
cmp(比較)命令でr2(スコア)とr0(100000000)を比較。
もしr2(アドレス020b627cの値)が大きければr0に99999999を入れる。
r4レジスタに0x020b8cb4を入れる。
r0に99999999を入れたならアドレス020b627cにr0(99999999)を書き込む。

まだ具体的に命令の意味は説明しません(2つめは不要な命令をあえて消してます)。
さて……どちらがスコアの最大値を扱う処理だと思いますか?
まあ、正直な話、前者が当たりだったりする場合もあるんですが、こういうときは素直に後者にアタリを付けてやります。

どうすれば99999999に固定できるかを考える
さて、さきほどの処理を改造して「スコアを99999999」に固定する場合、
もしr2(アドレス020b627cの値)が大きければr0に99999999を入れる。
r0に99999999を入れたならアドレス020b627cにr0(99999999)を書き込む。
これらの処理を、
r0に99999999を入れる。
アドレス020b627cにr0(99999999)を書き込む。
こんな感じに捻じ曲げてやれば良い、と言えます。

ニンテンドーDSに採用されているARMには「全ての命令に条件文を付けられる」という特徴があるのですが、
コイツはたった4ビット書き換えるだけで自在に操ることができる仕組みになっています。

ldr r0,[r15, #+0xb8] … この命令の場合、素直に無条件で実行されます。
ldrge r0,[r15, #+0xb0] … この命令の場合、命令末尾のge(大きいか等しい)に影響される。
そして、
前者のコードは E59F00B8
後者のコードは A59F00B0
……どこを書き換えればよいか思い浮かんだでしょうか?

答えは、
:02029EB8 A59F00B0 ldrge r0,[r15, #+0xb0] ;r15+0xb0=*(02029f70)=#99999999(0x05f5e0ff)
:02029EC0 A58100C0 strge r0,[r1, #+0xc0] ;r1+0xc0=*(020b627c)=#-373489652(0xe9bd000c)

:02029EB8 E59F00B0 ldr r0,[r15, #+0xb0] ;r15+0xb0=*(02029f70)=#99999999(0x05f5e0ff)
:02029EC0 E58100C0 str r0,[r1, #+0xc0] ;r1+0xc0=*(020b627c)=#-373489652(0xe9bd000c)
こうですね。

実際に改造コードとして入力してみる
アドレス02029EB8をE59F00B0に、02029EC0をE58100C0に書き換えれば良いので、
改造コードの書式は以下のようになります。
22029EB8 E59F00B0
22029EC0 E58100C0

このコードを実行することで、
スコア99999999
スコアが99999999になれば成功です。

さいごに
ARMの命令を何一つ説明することなく、激しく端折りながら突き進めましたが、
大まかな概念は理解できたでしょうか?

「とりあえず、999やら9999やらを検索して、ヒットした場所の直後の命令がldr/strでなく、
ldrge/strgeやらldrlt/strltみたいに語尾に何か文字がついてたら、頭1桁をEに書き換えてみる」
それが本稿の教訓でありますです。
(CDチェック外すためにメッセージボックスの手前の条件分岐を潰しまくるみたいな感じですね)。

慣れてくるとそんな無駄なステップは全く不要ですが、初めは「多くをこなす」ために、
あえて無駄なこともやっておくが吉なのですよ。

次回は「エミュレータを利用してサーチ」です。

>>次の講座へ進む