Wonderland Seeker

スマホの子はTOPを見てね

fifth法による任意コード実行

ポケモンをやっているはずなのにいっこうにポケモンバトルがうまくなりません
バグですか?ありすです

 

みなさん、fifth法はご存知ですよね。第一世代ポケモントレーナーのなかではふぶきとはかいこうせんが強いくらい常識ですからね

しかし第二世代以降から始めたトレーナーへ向けて一応簡単に解説しておきます

 

A.fifth法とは

まずトレーナーと会った場合のシステムについて

トレーナーに見つかった時、ゲームは以下の4段階に分かれて処理を行います

 

1.トレーナーに発見される
2.トレーナーが歩いてくる
3.メッセージボックスを出す
4.戦闘に入る

 

fifth法では、この1と2の間に
そらをとぶ・テレポート・あなをほる(あなぬけのヒモ)等の
座標自体書き換える方法で飛ぶ行動を用います

感覚としては見つかる地点に立った直後にメニューを開いてテレポートを選択し、

!マークと同時にマップから出るという感じですかね

 

例えば、マップAでテレポートを使ってfifth法を仕込むとします
するとゲームは
「トレーナーに見つかっている最中だ」
「トレーナーが歩いてきている最中だ」
「マップAでこの後メッセージを表示して戦闘に入るね」
というメモリを保持します
更に
「この後表示するメッセージがそのマップの何番目のメッセージか」と
「この後戦うトレーナーがどの種類の何番目か」
をそれぞれメモリに保持します

同時にテレポートで移動したので、操作権はプレイヤーに返されます
なので、マップ上で移動することが可能になります
しかしゲームが「トレーナーに見つかっている最中だ」というメモリを保持したままである限り、メニューを開くことはできなくなります

この時マップAに戻ると、全てのトレーナーが反応しない状態になっています
これは、マップAの状態が「普通にマップを歩いている時」ではないからです

その後、適当なトレーナーに見つかって歩かせることで
「トレーナーに見つかった」「トレーナーが歩いてきた」
という情報をゲームに与えてやることで、
「トレーナーに見つかっている最中」ではなくなり
「トレーナーが歩いてきている最中」でもなくなります
ゲームがメモリに保持しているのは
「マップAでこの後メッセージを表示して戦闘に入るね」
ということだけです
この2つの情報は、レポート&リセットしてから連行されたりしても失わせる事ができます
そのため歩いてくるトレーナーに見つかる必要はありません

では、マップAに戻ってみましょう
ゲームは今から、マップAでメッセージを表示することにします
しかし残念ながら
「この後表示するメッセージがそのマップの何番目のメッセージか」という情報は、1つしかメモリに保持することができません
何番目のメッセージを表示するかは、トレーナーに見つかったりして既に書き換わってしまいました

でもゲームはそのメッセージが今から表示するメッセージであると思って実行します
なのでそのメッセージを表示します

大抵の場合、マップAに戻ってくるまでに最後に表示するメッセージは、スタートメニューになります
(テレポートをする際にスタートメニューを表示しますよね)

スタートメニューはどのマップでも「0番目のメッセージ」です
そして0番はマップでのデフォルト値なので、レポート&リセット直後も0番目のメッセージを表示した直後になっています
なので大抵の場合、ここでスタートメニューが出ます

次に戦闘を始める処理に入ります
ゲームは「この後戦うトレーナーがどの種類の何番目か」を見に行きます。
この部分はちょうど「戦闘中の相手のポケモンの特殊下桁/攻撃のランク」と同じ場所です

戦闘を挟んでいるので、当然この部分は上書きされています
そして戦闘が終わってもリセットされません
なのでゲームは「最後に戦ったポケモンの特殊下桁/攻撃のランク」に応じたポケモンやトレーナーとの戦闘を始めます

ここまでが、fifth法の仕組みです

簡単に言ってしまえば
トレーナーに見つかったと同時にテレポートする
その直後にとくしゅの値が21のポケモンと戦闘する
見つかったトレーナーのいたマップへ移動する
エンカウント値が21のミュウとの戦闘に入る
ということが出来るものです

 

では、fifth法についてのおさらいも済んだところで話を進めましょう

 

B.MIT


次に、fifth法の副作用とも言えるバグのついてです

大抵の場合、マップAに戻る前に最後に開くのはスタートメニューでしたね
ですが看板を読んでから戻ってみたりすると、トレーナーのセリフなどが表示されることがあります

例えばマップAに近いマップBにおいて「7番目のメッセージ」を表示する看板があるとします
そして、fifth法の最中にマップBでその看板を読んでから、マップAに戻ったとしましょう

マップAに戻ったので、ゲームはメッセージを表示しようとします
表示するメッセージの番号は一つしか記憶できませんから
最後に読んだメッセージの番号を、今から表示するメッセージの番号として実行しますよね
なのでここで表示されるのは「マップAの7番目のメッセージ」になります


これをMatching ID Textbox 略してMITと呼びます

今ありすが命名しました

 

ではMIT「未戦闘のトレーナーのセリフを呼び出した」場合どうなるでしょうか?

未戦闘のトレーナーに話しかけると戦闘になりますよね
これは、トレーナーに話しかけた時に出現するメッセージに
TX_ASMという「プログラムを実行する」制御コマンドが仕込まれていて
「今から戦闘を始めるね」という状態に遷移しトレーナーの情報を記憶してからセリフを表示するようになっているためです
未戦闘のトレーナーのセリフを呼び出すとこの処理までが行われます

普段、マップを歩きまわっている時の状態を「状態#0」としましょう
トレーナーがこっちを見つけてくれるのはこの「状態#0」のみです

トレーナーに見つかったり、トレーナーに話しかけたりすると「状態#1」になります

トレーナー戦が始まると「状態#2」になり
戦闘が終わると「状態#2」であるなら「状態#0」に戻るようになっています

「今から戦闘を始めるね」というのは「状態#1」と言えますね
この状態へ移る前は普通であれば「状態#0」のときだけです
なので「今から戦闘を始めるね」という状態に遷移したいときは
状態の番号を+1するように命令が書かれています
トレーナー戦が始まるときも、状態の番号が+1するようになっています
ですがMITによって未戦闘のトレーナーのセリフを呼び出すと
呼び出した時点で「今から戦闘を始めるね」という状態、つまり「状態#1」になります
TX_ASMによって実行された処理でさらに状態の番号が+1され「状態#2」になってしまいます
さらに出たセリフのトレーナーのセリフが記録されることにより
トレーナー戦が始まり、状態は+1されます。「状態#3」に行ってしまいます
戦闘が終わると、マップに戻ってきます
しかし「状態#2」ではないので、「状態#0」に戻ることはありません

しかも本来はセリフの後に戦闘が発生するはずでしたのでゲームはもう一度状態を+1するのです。状態は「状態#4」になってしまいました

そもそも、状態の番号は0~2しか用意されていません
では、「状態#4」はどうなるのでしょうか

ROM内では、各マップのデータは
「状態#0の処理の場所」「状態#1の処理の場所」「状態#2の処理の場所」
というようにどこに処理が書かれているかが配列のように並んでいます
その後ろには、「1番目のテキストの処理の場所」「2番目のテキストの処理の場所」「3番目のテキストの処理の場所」……と続いています

「状態#4」は、マップの状態からはみ出しています
結果、テキストの処理をマップの状態と勘違いしたままマップの状態として
「2番目のテキストの処理」を行う、という意味になってしまいます
「2番目のテキスト」は、トレーナーのセリフです
そのトレーナーとの戦闘済みのフラグが立っていない場合、再び戦闘が始まります

テキストの処理はテキストボックスの制御コマンドによって書かれており、メッセージの表示、効果音、プログラムなどを切り替えることができま

しかし、今実行しているのは単なるプログラムなので、この制御コマンドをもプログラムとして実行します

結果、本来とは全く違う領域からそのトレーナーのデータを読み込み始めます
戦闘済みフラグも全く無関係のデータから読んできてしまいますし
どのトレーナーと戦うかが更新されないので同じトレーナーと連戦するハメになります
戦闘済みのフラグが立っている場合、戦闘は始まりません
ですが、その場合も何らかのテキストを表示する必要があります
そしてテキストを表示する処理でもないのにテキストの処理を行うようになると
ゲームは「中身が正しくない」「透明な」テキストボックスを出し続けます

これはそのマップに居る限り、定期的に、かつ永久に出し続けます
このせいで、そのマップは非常に処理が重くなります。
この現象はレポートすると(「状態#」ごとレポートするため)リセットしても変わりません
どうにかして「状態#0」に戻してやるまで永久にこのままです
また、「状態#0」でもないので、トレーナーはこちらを見つけてくれません
しかしこの状態であってもトレーナーに話しかけることで戦闘を発生させられます
トレーナー戦が発生する度に状態は進みますね、でも「状態#0」でないため状態は+2されていき
処理はテキスト番号が+2された位置へと進んでいきます


では、この処理を行うテキスト番号も終わってしまったら?

ついに、処理は何が書かれているかわからない場所へと飛びます
「処理があるはずだから」とそのまま飛んでいきます
大抵の場合、ここでゲームはフリーズします
テキストではない処理をテキストとして実行するのですから当然です

ここまでがMITの解説となります

ここからが本番ですよ

 

C.MITによる任意コード実行

 

「処理があるはずだから」と飛んでいった先がもしも
RAM領域内、それもメインメモリでプレイ中に操作可能であればどうでしょうか
話がなかよしバッヂと同じことになりましたね?
そうです、ここにプログラムを書いておけば「状態#」がそこまで辿り着いた瞬間にそのプログラムを読み込みます


つまり任意コード実行ができますね


同様にMITで表示されたn番目のテキストが本来テキストではない範囲だった場合もそこに書かれてあるアドレスへ飛んで
「テキストボックスの中身として」実行し始めます
これも同じく任意コード実行となります
この二つの違いは、テキストボックス内の処理でのif文ですね

テキストボックス内でプログラム処理を行うのに必要な制御コマンドはTX_ASMです
そのためニックネームの先頭にTX_ASMを置くと、以下の処理はプログラムとして実行されます

これによりfifth法+MITによる任意コード実行が設立されましたね

ありすはこれをFMA(Fifth法Matching ID TextboxによるArbitrary code execution)
そう名付けました(グレンのポケモンやしきの日誌風)

では、いよいよ真髄に入りましょう

 

・MITによるエンディング呼び出し

 

では、このゲームを終わらせましょう

要約すると以下の3つを行えば可能です

1.2000h-3FFFhの何処かに16hを書き込む
2.FFB8hに16hを書き込む
3.7DC8hへジャンプし、命令を実行する

EDイベントはROMバンク#16hの番地7DC8hからの一連の命令です
ですが7DC8hへとプログラムがジャンプすればいいのかというと答えはNOです
ROMバンクが違えば7DC8hの処理も違いますからね
例えばトキワのもりはROMバンク#18にイベントデータがあるのですがこのまま7DC8hからプログラムとして実行するとフリーズします
そのためまずはROMバンクを#16hへ切り替える必要があります
ROMバンクの切り替えの方法は番地2000h-3FFFhへのバンク番号の書き換えが必要です
加えてゲーム側が現在どこのバンクを読んでいるのか、というのを記憶する部分にも、バンク番号を書き込んでおく必要があります
これをしないと次のバンク切り替えのときに間違ったバンクへ切り替えてしまいフリーズを引き起こします
これが書かれているのは番地FFB8hですので、こちらも書き換えます
そして上3つの手順になるわけです

あとは最後に表示されるテキストボックスをマップ(DED0h~)に更新することでテキストボックスの処理として実行させます

この行動が「森地図」と言われる所以でもあります

では、肝心のソースコードになります

$DEA0:
LD b,D7h ;ギら
SWAP b ;ひだ
LD de,2650h ;ヅ[EOM]が
LD c,C8h ;ゾね
SUB c;ツ
LD (de),a ;デ
LD d,b ;[EOM]
PUSH bc ;な
LD (FFB8h),a ;ゃく
SUB a,A0h ;よメ
LD d,b ;[EOM]
LD de,D2DCh ;ヅわめ
INC de ;ド
LD (de),a ;デ
LD d,b ;[EOM]
RET ;の

$DED0:
TX_ASM ;ゲ
XOR a ;ョ
ADD h ;オ
JR nz,-35h ;だひ

 

ニックネームに直します
11匹目から
ギらひだヅ
がゾねツデ
なゃくよメ
ヅわめドデ

19匹目
ゲョオだひ

ではコードの説明を簡単にしましょう


最初に来るのは制御コマンドのTX_ASMです
テキストの制御コマンドでTX_ASMが入ると、ここから先の処理はそのままプログラムとして読み取ります

XOR XはaレジスタとXに指定されたレジスタ/値の排他的論理和を取り、結果をaレジスタに代入すると言うものです
同じレジスタを比較しますから、全ての桁は必ず同じ数値が入っています
その為、aレジスタは0にクリアされます

JRは相対ジャンプの命令です。プログラムカウンタの指している場所から±7Fhくらいの範囲でジャンプできます
JR z,Xですと、直前の計算結果が0のときのみXに応じてジャンプします。条件付きジャンプですね、直前の計算結果は0だったので、必ずジャンプします
なんでわざわざ条件付き相対ジャンプなんかするのか、って言うと無条件相対ジャンプの命令は18hでして、文字に直すと「ノ゛」です。入力できませんね

さて、プログラムは無事にDE64hにジャンプしたのでここからさらにプログラムとして読み取らせます

LDはロードの命令です、レジスタに値を代入します
LD d,bについてはこの後の処理で全く意味のない命令ですが
メッセージの終端を意味する50hがニックネームの最後に必ず入るというのは以前話しましたね?
こういう形で無理やり消費させるというわけです

次に入っているSWAP bは、bレジスタの上位4ビットと下位4ビットを交換しろという命令です
16進数の1桁はそれぞれ2進数4桁、つまり4ビットで表現できます
上位4ビットと下位4ビットを交換するというのは、16進数の各桁を交換しろという意味に等しくなります
よって、D7hが入っていたbレジスタには、この命令によって7Dhが入ることになります
7Dhは直接ニックネームから入力できる文字ではないので、仕方ないです

PUSH bcはbcレジスタの値をスタックに積むという命令です
スタックはラストインファーストアウトで値を保持する領域で
サブルーチンの「どこから呼び出されるかわからない命令」は
帰る先をスタックに積んでおいて、それを取り出すことで帰る場所がわかるようになっています
ここでは、7DC8hという値がスタックに積まれました

LD de,2650hはこれはdeレジスタを16bitレジスタと考えて値を入力する命令です
dレジスタに26h、eレジスタには50hという値が入りました
直後のLD (de),aは、deレジスタの値で指定された番地に、aレジスタの値を書き込めという命令です
deは2650hでしたので、2650hという番地に、aレジスタの16hが書き込まれました

LD (FFB8h),aはFFB8hにaレジスタの値を書き込めという命令ですねFFXXh番地に書き込むための専用命令がGBZ80には用意されています
FFB8hは現在ロード中のバンク番号が格納されている場所です。ここにもaレジスタの値、ですから16hが入ります

同様にLD de,D2DChもそうなります

後は7DC8hへジャンプするだけです

ここで、RETが登場します。サブルーチンから帰る命令ですね
プログラムは元いたところへ帰ってしまいます
ここで、先程7DC8hをスタックにPUSHしておいたことが意味を持ちます
今、スタックの一番上には7DC8hがあります。そして、RET命令が登場しました
するとどうなるか
帰る先を確認するため、スタックの一番上が崩されます。そこには、7DC8hと書いてありました
RETはそこへ帰れという命令なので、プログラムはもと来たところでもなんでもない7DC8hへと帰って、そこから続けて処理を進めていきます
バンクは既に16hに変更されています

ROMバンク#16hの7DC8hには
「でんどういり」の処理があります
これにてエンディングです、長くつらい戦いでした

 

どうです、ついにこのポケットモンスターというゲームもいつでも戦犯リストスタッフロールが見れるようになりました

 

ポケットモンスターピカチュウ版であればニビシティがラストダンジョンになります

チャートとしては

 

マサラタウン

ピカ様にニックネーム「の」と命名します
イーブイに勝利します

トキワシティ

モンスターボールを15個購入します

2ばんどうろ~トキワのもり

14匹捕獲、この14匹はニックネームデフォルトでOK
5匹捕獲、この5匹は以下のように名付けます

 

ギらひだヅ
がゾねツデ
なゃくよメ
ヅわめドデ
ゲョオだひ(19匹目)

 

ニビシティ

どうぐを売りそのお金であなぬけのヒモとモンスターボール3個を買います
※足りなければトキワのもりでミニスカートに勝利します
忘れずにポケモンセンターで回復します
その際ボックス11匹目からのコードも確認しましょう


トキワのもり

追加トレーナーであなぬけのヒモを用いてfifth法を行います

ニビシティ
ボックスを1から1に変えたらリセットします
博物館に誘導する人に誘導してもらいfifth法の1~2を解除します
民家でタウンマップを見ます


トキワのもりに入ります
エンディング

 

およそ30~40分あたりで実現できます

ね、簡単でしょ?

 

これにてポケモン講座の授業を終わります、おつかれさまでした

 

f:id:Alice_Wreath:20191011123543p:plain

この記事を読んだあなたもチャレンジしましょうね