ポケモン赤緑を通してアセンブリ言語を学ぼう:入門編
ゲームのバグって面白いですよね。
昔は飽きてしまったゲームを意図的にバグらせてよく遊んだりしていました。
初代ポケモンをやったことがある人ならほとんどの人は Lv100バグ利用を試したことはあるのではないでしょうか。
私の時代はまだインターネットが発達していおらず、 知るのはもっぱらゲーム雑誌でした。
当時マイナーなゲーム雑誌からミュウの出し方を知った私は 上級生からも教えを乞われたりしてとても楽しかった記憶があります。
今思うと市販のゲーム雑誌にバグを悪用する方法を書くのはどうかなとは思いますが(笑)
バグを意図的に利用することを「グリッチ(glitch)」と呼ぶそうです。
日本人としては「バグらせる」といった方が馴染み深いですね。
当時はなぜ、道具欄の特定の位置でセレクトを押して一定の手順を踏むと ポケモンがバグるのか謎でしたが、仕組みを知ればなんということはありませんでした。
しかし当時のゲームはアセンブリ言語という機械語に近いプログラミング言語を用いて 作られていたりもしたので、ポケモンバグの仕組みを知っても流石にアセンブリ言語を覚えるのは キツイなーと思って触れすらしなかったのですが最近ではちょっと覚えてみようかななんて気になってきました。
25年前のゲームなので流石に解析され尽くしているため、 ネット上に解説サイトがいくつかありました。
先に申しておきますが、著作権のあるゲームでなおかつバグを利用するという行為なので 限りなくアウトに近いものでもありますから基本的に参考ページのリンクや、 ゲームのスクリーンショットなどは載せていく気はありません。
あくまでポケモン赤緑を通してアセンブリ言語を学ぶための記事となっておりますことをご了承ください。
ただ、アセンブリ言語はプログラミング言語を現役でやってる人でも 結構難解な書き方になっているため、牛歩作戦で1つの命令を1記事で覚える速度でやろうと思います。
ポケモン赤緑はなぜバグるのか
ポケモン自体、普通にプレイしてたらそうそうはバグらないのですが プレイヤー側が「あ、そうだ!」ってなったときにバグを発動してしまう仕組みになっています。
いわゆる特定の位置でセレクトを押して他のところで選択してしまったりですね。
バグが起こる理由の1つとしては、道具を入れ替える仕組みそのものが プログラムのメモリの値を入れ替えるという仕組みを使っているからです。
最近のプログラミング言語で説明すると、 変数Aには0~9までの値が入るように設計されてたとします。
そして変数Bには「あいうおえ」という文字列が入ってたとして その変数Aと変数Bの中身は最近のプログラミング言語で入れ替えようとしても 数値型と文字列型なので入れ替えることはできませんよね。
なぜなら実行する前にエラーを表示してくれるからです。
しかしアセンブリ言語の場合は文字列だろうが数字だろうがすべて16進数の数値になっています。
なので変数Aと変数Bは文字列だろうが数値だろうが入れ替えられるってわけですね。
話をポケモンに戻しますと、道具欄は道具欄用のメモリスペースを利用しているので メモリの値をそっくりそのまま入れ替えればアイテム同士の入れ替えが行えるわけです。
プログラマ最大のミス!状態解除のし忘れ
道具欄でセレクトを押すと、コンピューター側はいわゆる
「メモリの値を入れ替える」
という状態のモードになります。
道具欄を閉じたり、メモリ数値入れ替えモードのときにキャンセルボタンを押すと 「メモリ数値入れ替えモード」から「道具選択モード」に切り替えなければいけません。
しかしプログラマはその切り替え機能の呼び出しが出来ておらず フィールド移動画面に戻ってもメモリ数値入れ替えモードが解除されていないままになっていました。
メモリ数値入れ替えモード自体はフィールド上では使われないためなんにも影響はないのですが、 何かと何かを入れ替え機能があるところ(ポケモン一覧画面など)では有効になってしまいます。
ポケモン一覧画面はポケモンの入れ替えの仕組みに道具入れ替えと同じ仕組みを使っているため、 道具欄でメモリ数値入れ替えモードに入っているとポケモン一覧画面を開いたときに、 ポケモン選択モードではなくいきなりメモリ数値入れ替えモードになってしまっています。
メモリ数値入れ替えモードになったときは
「入れ替え元のメモリの番地を記憶している状態」
となっています。
このためポケモン選択画面に入った時、道具のメモリの番地を保持したまま それぞれのポケモンが保有するメモリの番地を入れ替える
といった状態になってしまいます。
これを入れ替えてしまうと、最初にも言ったとおり本来入るはずのない値が メモリに入ってしまうためプログラムがおかしくなってしまうということになります。
Lv100になってしまうバグの理由
ここで説明するのは正しいメモリの入れ替えの話ではなくてイメージの話です。
先ほどの道具とポケモンの入れ替えの話をそのまま使いましょう。
道具欄を開いた時にプレイヤー側が見えたり認知できる道具が持つ情報としては
- 道具の種類
- 道具の個数
- 道具の位置
の3つになるとおもいます。
道具の位置はプログラム側がこの「メモリの番地00」が道具1個目の場所 という風に固定してるためプレイヤー側は気にしなくても良い箇所です。
そうすると道具の種類を判別できる数値を「メモリ番地00」に入れ、 続いて道具の個数を「メモリ番地01」に入れることになります。
基本的に関連しているものはメモリの番地は連続しています。
理由は容量を効率良く使えるようにですね。
関連する者同士のメモリの番地をわざわざ遠い番地に設定する必要もありません。
昔のゲームはメモリの容量が限りなく少ないため節約しないといけない時代でした。
現代はいくらでもメモリを使えますが、 それでもわざわざ別の番地のメモリに分けて管理する必要はないですね(笑)
次にポケモンに関して見てみましょう。
ポケモンはプレイヤーが認識できる状態で以下のものがあります。
- 種類
- ニックネーム
- 現在のレベル
- 現在の経験値
- 次のレベルまでの経験値
- 各種能力値
- 種族値
- 努力値
- 現在の状態
- 保有タイプ1と2
- プレイヤーのID
- プレイヤーの名前
- 技スペース4つ
- 技のPPの現在値
- 技のPPの最大値
努力値や種族値は視覚的には見えませんが、 最近のポケモンプレイヤーにはもう周知の隠れパラメーターですね。
道具と違ってたくさんメモリを使っていることが一目瞭然です。
そしてポケモンの一覧画面と、ポケモンそのものの情報は別々のメモリで管理しています。
一覧画面にはたくさんの値を持つポケモンの情報を1つのIDとして紐付かせています。
そのIDを照会すると、ポケモンの詳細な情報を保持しているメモリを引っ張ってくるというイメージですね。
そして道具を入れ替えるときは「メモリ00」「メモリ01」を入れ替える準備をすることになります。
次にメモリ入れ替えモード担っている最中にポケモン選択画面にいき、 特定のポケモンを選択すると道具の情報とポケモンの情報の一部を入れ替えるといった現象が起こります。
動きとしては正しくないかも知れませんが、あくまでイメージの問題なのでご了承ください。
Lv100に話を戻しますと道具とポケモンの情報を強制的に入れ替えるわけですから 特定の位置の道具の値が「現在の経験値」のところに入ってしまうことで起こります。
これはあくまでイメージですが、現在の経験値が16進数で「10 FF」なんかの状態のとき 「20 FF」という状態になれば次レベルになり、「FF FF」になったら最大のLv100になるといったイメージの時、 入れ替えバグを使うとこの「10 FF」を強制的に「FF FF」に無理矢理入れ替えることになります。
そしてLvアップの判定は経験値を得た時なので現在の経験値を見ると「FF FF」なので こいつはLv100だなってプログラム側が認識します。
もちろん本来の処理はもうちょっと複雑な感じで、 経験値が本来ではありえない数値になってしまい経験値の数値を保持しているメモリが書き換えられLv100になるという仕組みです。
てきとうにやると大体経験値がオーバーして最大レベルに達してしまうのですが、 道具の種類や個数なんかを調整すればうまくやれば任意で好きなレベルにすることができます。
あげるだけではなく下げることも可能というわけですね。
メモリを2byte使うと最大65535までの値を表現できる
これは余談になりますが仕組みを知るためには必須の知識になります。
ゲームボーイは8bitゲーム機です。
機械語は2進法、つまり0と1しか表現出来ません。
そして1bitはその0か1を入れるための場所です。
つまり8bitは0か1を入れる箱が8つあるというわけですね。
8bitは2進数で数えていくと0,1,10,11...と最大「1111 1111」となり 0と1だけで最大255、つまり0を含んで合計256カウントできることになります。
そして「8bit = 1byte」とも言いかえられます。
道具でいうと種類で1byte、個数で1byteの合計2byteつかうということになりますね。
プラスのみを使用する場合1byteで0から255までカウントできますが、 マイナスを利用する場合も絶対値としては256個になるので「-127~128」の範囲だけ使えます。
-128~128じゃないの?と思った方はおそらく0の存在を忘れています(笑)
プログラミングは基本的に0から開始と覚えておくといいでしょう。
道具の個数で説明するとゲーム上では99個が最大値、 つまり16進数で言えば63になります。
プログラム側で63を越えて64になってしまいそうなら63のままにしろ。
という命令されるので99を越えることがないというわけですね。
バグを利用してその命令を無視して無理矢理そのメモリを63以上の FFという数値に書き換えてやると道具の個数が255個になるわけです。
マスターボール255個なんかはこういった仕組みを利用しているわけです。
そして道具はマイナスを許可しない型を利用しており0以下になると アンダーフローして最大値の255になってしまいます。 逆に255から0になることはオーバーフローといいます。
なのでその状態を防ぐために道具の個数は1から0になったときに、 メモリを空にするという命令にして空になった分後ろのメモリをそのまま詰めて移動させるといった処理をしています。
この詰め処理をしないと道具欄に無意味な空白が表示されてしまうからですね。
アセンブリ言語の命令を知ろう
ちょっと1つ目のお題が長すぎましたね。
それではアセンブリ言語について見てみましょう。
アセンブリ言語とは、プログラミング言語の一種です。
たぶん人間が扱える認知度が高いプログラミング言語の中で、 一番機械語に近いプログラミング言語になります。
プログラミングでよくあるforループ文なんかはアセンブリ言語では ジャンプ命令を駆使して表現しています。
というかジャンプ命令は複雑なコードになってくると見通しが悪くなるので 新しい言語では繰り返し処理はforとして使えるようにしているといったほうが正しいでしょう。
BASICをやったことがある人はジャンプ命令≒GOTO命令みたいなものと考えてもらえればOKです。
ちなみに描き込むチップの仕様によって挙動は変わるものだと思うので、 当ブログではポケモン赤緑に使える知識だけに集中していこうと思います。
そしてポケモン赤緑のバグ利用では実際にアセンブリ言語を描いていくわけじゃないので 内容が理解できれば自ずとどうやったらどういう処理が発生するのかを理解できればOKです。
もちろんアセンブリ言語でゲームを作ることができるレベルになれば最強です(笑)
ジャンプ命令
プログラミング言語はどの言語であっても基本的に上から下に処理します。
ジャンプ命令とはその名の通り指定の行に処理をジャンプするという命令になります。
コードで言えばD000が一番最初に実行される行だとして、 D001~D005を飛ばしてD006にコードをジャンプさせたい場合
jp D006
のように書きます。
よく言われる(最近ではあまり効かないかも)スパゲッティコードっていうのは このジャンプ命令を使いまくってどういう処理か分かりづらい入り組んだコードのことを指します。
しかしアセンブラに関してはこのジャンプを駆使しないとゲームを作るのはおそらく難しいんじゃないかなと思います。
たぶん当時のプログラマはC言語で書いてアセンブリに変換してGB用に調整したりがメインだと思います。
企業によっては直接アセンブリ言語を使って開発していたところもまだまだ多かった時代なので Cを使わずにアセンブリメインで開発してたかも知れません。
続いて変数の概念的なものも紹介したかったのですが、 レジスタに入れるとかなんやらでちょっと文字に書き起こせるほど頭で理解していないので次回に回します。
アセンブラ開発環境は難易度が高い
アセンブリ・アセンブル・アセンブラと色々言い方がありますが、
リが言語(1行1行のコード)で、ルがアセンブリをバイナリファイルに変換することで、 ラがアセンブリで書いたプログラムのことを指します。
アセンブリ言語は最近の言語のようにインストーラーでインストールして ぱぱっと開発環境を構築するのがほとんどありません。
一応エミュレーターなどで環境を作ることは可能です。
頑張ればファミコンのゲームソフトやゲームボーイのソフトを 規格にそってコードを組んでコンパイルすれば NESエミュレーターやGBエミュレーターで動く自分だけのゲームを作ることも可能です。
しかし今回はポケモン赤緑の中での操作がメインになるので 別に環境構築をする必要は特にないかなと思います。
アセンブリと機械語は密接している
最後に、どんなゲームソフトも最終的には機械語に翻訳されます。
しかしアセンブリの1つの命令と機械語は1:1の関係になっているので アセンブリを理解する=機械語を理解することにもつながってきます。
ポケモン赤緑はすでに機械語になったものをイジるわけですから 本来なら機械語を理解するべきではありますが流石にそれは難易度が高すぎるので それなら機械語に密接していてなおかつ人間でもわかりやすく組まれたアセンブリ言語を覚えることができれば ポケモンの内部の数値をどういじれば何が起こるかを理解できるようになります。
実はプログラミングはできないけどポケモンのバグを利用して アセンブリ言語を覚えた人もちらほらいるみたいです。
なので必ずしもプログラミング言語を覚えてからじゃないとダメというわけでもありませんが、 覚えておいたほうが理解の速さとかもかなり変わってくるんじゃないかなと思います。
正直今からアセンブリ言語を覚えても正直お金にはなりませんし 現代のプログラミングにおいて効率も悪すぎます。
もし組み込みとかロボットを扱うような職に着きたいのであれば、 アセンブリ言語を覚えても損ではありません。
それでもC言語だけで十分だとは思いますが……
私は趣味でアセンブリ言語を覚えてみようかなと思っています。
しかし今から覚えるには難易度も高いですしモチベもあがりにくいので 大好きなポケモンバグを利用して覚えていこうかなと思いました。
もちろん趣味なので気が向いたときにやる感じなのであまり更新には期待しないでください(笑)
ちゃんと理解できたら続きを書いていこうと思います。