絶対可憐チルドレンDSの「13番目のパスワード」を求めて


 というわけで「妖怪道中記隠しパスワード解析選手権(参考:おたくま経済新聞)」以来、ひさしぶりの隠しパスワードネタです。
 ニンテンドーDS用ゲームソフト「絶対可憐チルドレンDS 第4のチルドレン」にはオマケ要素として隠しパスワードが存在しており、当時は週刊少年サンデーや公式サイトのイベントページなどで開示されたものなどを含め、合計12種類が公開されていました(※2008年当時のサイトはウェイバックマシンなどで確認可能)

 ところがROM内部には「13番目のパスワード(桐壺局長の解禁)」のデータが存在しており、これが公開されないまま2024年に至っています。
 ただし、既に私がアルゴリズムの解析は済ませており、ここから資料やパスワード解析ツールのダウンロードzipもできます。
 一応Discord鯖に専用チャンネルも作りましたので、参戦したい方はご来場くださいませませ。
 ……と、ここまでお伝えしたところで前回の妖怪道中記パスワード解析選手権とは異なる点をお伝えします。

1.組み合わせが「50音80種×最大20文字の可変長なのにチェック値が32bit(CRC)」
 妖怪道中記だとパスワード重複が1.6億個でしたが、絶チルDSは理論上で驚異の26,843,545,600,000,000,000,000,000,000個のパスワードが存在していると考えられています。

2.プログラム開発者も誰なのか分からず後ろ盾も判定基準もない
 宇田川氏のようなレジェンドが見守ってくれるわけでもなく歴史的な背景もないため、よほどの奇跡が起こらないと4STネタになりません。
 また、公式パスワードですら「ひしとなこあゆたふやおしなさはちみしゆこ」という完全ランダムとしか思えないものが存在しているため、奇跡的に長文パスワードが見つかったとしても、それが真のパスワードという確証は得られません。

3.もうゲームで使えるパスワードは見つかっている
 今回の選手権を開始した直後に、N64解析の大御所ねこかぶさんが「ぷうぴぜぅ」「ぞづゆらし」「うえうえしたびえぇええるびえみぎえる」という3種の衝突パスワード(※ゲームではしっかり通ります)を発見済み。
 単に桐壺局長が解禁する場面を見たいだけなら、上記3つのパスワードを打てば目的を果たすことができます。

・・

 というわけで、それでもチャレンジしたい方向けの選手権となっております。
 「俺の見つけたパスワードは絶チル原作本xx巻に登場するシーンのセリフだから、これが絶対当たり!!」のような奇跡の発見を果たす、ガチ勢のご参加をお待ちしております。
 ツールは常時差し替わっているので、適当に下記リンクから落としてください。
 Download : https://i486.mods.jp/files/zkpass.zip

~2024.3.30現在の解析ツールの中身~

解析メモ.txt
 パスワード生成アルゴリズム解説
zkpass1
 手打ちでパスワード照合値94AE54E1を探すツール
zkpass3
 大量のパスワードを書いたテキストファイルを一括チェックするツール
zkpass4
 さらにzkpass3を各行を分解して全パターンチェックするツール
zkpass5(これらの中では理論上これが最も新種発見率が高い)
 zkpass4をさらに全行ランダムミックスしてアタックするツール

スーパーバグファミコンをエミュで再現する

※まだ完全ではないので世界中のチャレンジャー求む。
 おそらく完全再現できました。

 先に動画を見ておくが吉。

Youtube : 4ST 次々とゲームをバグらせる恐怖のスーパーファミコン【実態調査編】 

Youtube : 4ST 次々とゲームをバグらせる恐怖のスーパーファミコン【原因究明編】 

 4STシイナさんが調査した結果、スーパーバグファミコンはCPUに実装されたDIV 16/8(除算エンジン)が故障していたのが原因と判明。
 でも、そんな都合よくイイ感じに壊れたスーパーファミコンなんて奇跡でも起きないかぎり手に入りませんし、CPUだけピンポイントで壊すなんてもっと無理。

 ですが、もっと都合良くCPUレベルで破壊できるスーパーファミコン実行環境が身近にあります…そう、エミュレータ(SNES9X)です。
 SFCの除算命令はコード4206…というわけで、まずはSNES9Xのソースコードを眺めます。

case 0x4206: // WRDIVB
{
	uint16 a = Memory.FillRAM[0x4204] + (Memory.FillRAM[0x4205] << 8);
	uint16 div = Byte ? a / Byte : 0xffff;
	uint16 rem = Byte ? a % Byte : a;
	// FIXME: The update occurs 16 machine cycles after $4206 is set.
	Memory.FillRAM[0x4214] = (uint8) div;
	Memory.FillRAM[0x4215] = div >> 8;
	Memory.FillRAM[0x4216] = (uint8) rem;
	Memory.FillRAM[0x4217] = rem >> 8;
	break;
}

 上記ルーチンを書き換えてソースコードをビルドすればスーパーバグファミコン完成~…と出来れば良いのですが、ぶっちゃけSNES9XをWin32ビルドする環境を組むのはアホほど大変です。
 今回は狙いのルーチンだけぶっ壊せれば良いので、SNESエミュレータをハックして解決してみることにします。

 私が解析に使っている、デバッガ搭載のSNES9X亜種「Geiger’s Snes9x Debugger」を逆アセンブルして、00004214、00004215、00004216、00004217に連続アクセスするような怪しい処理を探すと…

---------
:005BC8FB 8B353C056C00            mov esi, dword[006C053C]
:005BC901 660FB69605420000        movzx dx, byte[esi+00004205]
:005BC909 660FB68604420000        movzx ax, byte[esi+00004204]
:005BC911 8A5D08                  mov bl, byte[ebp+08]
:005BC914 66C1E208                shl dx, 08
:005BC918 6603D0                  add dx, ax
:005BC91B 84DB                    test bl, bl
:005BC91D 0FB7CA                  movzx ecx, dx
:005BC920 7411                    je 005BC933
:005BC922 0FB7C1                  movzx eax, cx
:005BC925 0FB6CB                  movzx ecx, bl
:005BC928 99                      cdq
:005BC929 F7F9                    idiv ecx
:005BC92B 0FB7C0                  movzx eax, ax
:005BC92E 0FB7CA                  movzx ecx, dx
:005BC931 EB08                    jmp 005BC93B
---------
:005BC933 B8FFFF0000              mov eax, 0000FFFF
:005BC938 0FB7C9                  movzx ecx, cx
---------
:005BC93B 888614420000            mov byte[esi+00004214], al
:005BC941 8B153C056C00            mov edx, dword[006C053C]
:005BC947 88A215420000            mov byte[edx+00004215], ah
:005BC94D A13C056C00              mov eax, dword[006C053C]
:005BC952 888816420000            mov byte[eax+00004216], cl
:005BC958 8B153C056C00            mov edx, dword[006C053C]
:005BC95E 88AA17420000            mov byte[edx+00004217], ch
:005BC964 A13C056C00              mov eax, dword[006C053C]
:005BC969 881C07                  mov byte[edi+eax], bl
:005BC96C 5F                      pop edi
:005BC96D 5E                      pop esi
:005BC96E 5B                      pop ebx
:005BC96F 8BE5                    mov esp, ebp
:005BC971 5D                      pop ebp
:005BC972 C3                      ret

 というわけで無事に発見。
 ここをエミュレータが停止しない程度に都合よくゲームだけバグるように壊すという、なんとも力加減の難しい作業をすることになります。

・パッチ1「EAXレジスタにFFFFが入ったまま突き抜けるよう分岐破壊

:005BC931 EB08                    jmp 005BC93B << ここを90(nop)で壊す
---------
:005BC933 B8FFFF0000              mov eax, 0000FFFF << 必ず実行される
:005BC938 0FB7C9                  movzx ecx, cx

 本来であればEB08(jmp 005BC93B)でeaxレジスタにFFFFをセットする処理をEB08→9090とすることで直後の4214と4215への書込がFFになり、除算ルーチンが破壊されます。
 ただし効果が強すぎるためか、モンスター闘技場のダメージが異常値になる傾向が強く、FF6の戦闘獲得EXPが16,777,216(0xFFFFFF)固定で仲間の脳力がバグったりと、4STの映像とはかなり異なる挙動でした。

バイナリエディタ用パッチ1
001BC931: EB 90
001BC932: 08 90


・パッチ2「空きメモリに転送してedx+4215だけにFFFF書き込み

 説明すると長くなりすぎるので超ざっくりと説明。

:005BC941 8B153C056C00            mov edx, dword[006C053C]
:005BC947 88A215420000            mov byte[edx+00004215], ah
:005BC94D A13C056C00              mov eax, dword[006C053C]

 これを相対ジャンプで00400500に強制分岐。

:005BC941 8B153C056C00            mov edx, dword[006C053C]
:005BC947 E9B43BE4FF              jmp 00400500
:005BC94C 90                      nop
:005BC94D A13C056C00              mov eax, dword[006C053C]

 飛んだ先の空きメモリで4215に2バイトFFFFを書き込んでから元の処理へ再び相対ジャンプで戻る。

// 本来
:00400500 66C78215420000FFFF      mov word[edx+00004215],FFFF
:00400509 E93FC41B00              jmp 005BC94D // 元のルーチンに戻る

 これで4215への書き込みがFFFFに強制化され、こちらもモンスター闘技場で勝つ都度に11111111ゴールドが入手できるようになります。

 ただし「防御に向かって会心の一撃でダメージ5桁になる」が観測できておらず、FF6も獲得EXP1700万オーバーのため、こちらも不完全な可能性が高いです。

バイナリエディタ用パッチ
00000500: 00 66
00000501: 00 C7
00000502: 00 82
00000503: 00 15
00000504: 00 42
00000507: 00 FF
00000508: 00 FF
00000509: 00 E9
0000050A: 00 3F
0000050B: 00 C4
0000050C: 00 1B
001BC947: 88 E9
001BC948: A2 B4
001BC949: 15 3B
001BC94A: 42 E4
001BC94B: 00 FF
001BC94C: 00 90

~~

 そんなわけで、エミュレータでスーパーバグファミコンを[理論上、再現できる」ことは確定しました。
 皆様は是非とも「DQ5でコイン11111111枚が手に入り、防御に向かって攻撃すると会心の一撃で5桁ダメージが出て、FF6で獲得EXPが1500万程度で、マリオカートでCPUが蛇行運転しまくるdiv16/8破壊パターン」を見つけてみてください。

・2024.3.20追記
 ねこかぶさんトコでSNESエミュレータAresを改造&ビルドするハック方法が公開されました。
スーパーバグファミコン再現したエミュを作成する

 しかも都合の良いことにAresはSNES9Xとは異なる実装になっているおかげで、4215を実行した時に返る値まで自由に調整が可能です。

case 0x4215: return io.rddiv.byte(1); //RDDIVH
を
case 0x4215: return 0xff; //io.rddiv.byte(1); //RDDIVH

 ねこかぶさんは上記のように書き換えを提案していましたが、4STシイナさんのところに届いたスーパーバグファミコンはFF6で15728672ポイントを取得していたので、試しに0xffではなく0xf0に書き換えてAresをビルド。
 その結果…

 無事にスーパーバグファミコンの完全再現に成功しました。
 ドラクエ5の1111111…は現時点では未確認ですが、理論上ダメな理由はないので、おそらくこれでハック完遂ではないかと思われます。