バトルタワーの乱数列をどうやって調べたかのメモ

バトルタワーの相手の手持ちは専用の乱数が使われていてseedがセーブデータに保存されていること

一度セーブしてロードしなおしても相手の手持ちが変わらなかったから

1周目に挑戦するたびにseedが変化するということ

試したらすぐ分かる

初期seedが格納されているアドレスの特定

複数のメモリダンプを比較して変化している部分を探すことで特定

初期seedの式の特定

1消費させたseedを確認できるので簡単。
値を0にして1消費させたら値が1になったのでs*A+BのBは1だと判明。
値を1にして1消費させたら値が0x5d588b66になったのでAは0x5d588b65だと判明。

初期seedが日替わり乱数から決定されていること

「セーブデータにseedが格納されている」って時点でktxadさんの記事を思い出して日替わり乱数との関連を睨んでいたけれど、初期seedがs * 0x5d588b65 + 1で更新されるってわかった時点で確定した。

seedが格納されているアドレスの特定

複数のメモリダンプを比較して変化している部分を探すことで特定

seedの式の特定 (s * 0x02e90edd + 1)

これは1固定消費させることができなかったので厳しかった。ダメもとでseed 0とseed 1で決定される結果を比較してみてこれら2つは1消費ずれであることがわかって、なんとかs * A + BのBが1であることを特定。あとは、0x10000000と0xd0000001を比較してAの一番下の桁が0xdであることを特定して、0x01000000と0xdd000001を比較してAの2つ目の桁が0xdであることを特定して(以下ry
この方法ではs * A + BのBが1でなきゃ詰んでたと思う。
その後、「うさみみハリケーンでメモリにハードウェアブレークを設置して、そのときのARM9のPCレジスタの値を見れば書き換えているアドレスが分かる(あらかじめDeSmuMEがどこにレジスタを格納しているか調べておいて)。その周辺の逆アセンブルリストを読めばよい」と教えてもらったので今の自分だったらBが1でなくても詰まない。

seedから乱数を取り出す式 (seed / 0xffff & 0xffff)

基本的には(s >> 16)なんだけどときどき((s >> 16) + 1)になるのでその条件はなんだろうなあーと思っていたら、さっきのうさみみハリケーンを使って書き換えているアドレスを特定する方法を教えてもらった。
+1になる条件はseedの値のみに依存しているっぽいってことは分かっていたので逆アセンブルを読まなくても特定できていただろうけどせっかくの機会なので逆アセンブルを読んで特定した

トレーナー決定ルーチン

乱数列からどう計算して相手の手持ちを決定しているかは基本的に乱数列と結果を照らし合せて調べた。トレーナーの決定ルーチンも照らし合わせて調べることはできたしファクトリーのときもそうやったけど、面倒くさいので逆アセンブルを読んだ。
乱数のルーチンを足踏み (とりあえずNDSROM解析のためのメモ - はむのブログ Ver.4.8.6 ~= Irregular child)に書き換えることで擬似的にブレークさせ、r14の値を見て呼び出し元を特定。
あとは逆アセンブルを読むだけ...なんだけど、なぜか ldr Rd,[PC,#0xXX]のような間接参照でndsdis2が吐く結果と実際に格納される値が違うことがあるのでそのときはldr命令の直後の命令を足踏みに書き換えて実行させレジスタの値を見ることでなんとかした。 (4/22追記: ndsdis2 が2.23にバージョンアップされてこのバグは直りました)

筆者: oupo (連絡先: oupo.nejiki@gmail.com)