見出し画像

古今東西オーバーフローでゲームがバグる例【iパス講座】

※この記事は「現実の身近な事例でITパスポート試験の内容をふんわり理解してみよう」という試みの一環として書かれています。が、別に試験とか関係なく読み物として面白いはずなので読んでください。読んでね?

古今東西ゲームには色んなバグがありますが、中でも攻撃力やアイテムの個数といった数値がおかしくなるバグは本当によく見かけます。
で、これは調べるとオーバーフローという現象が原因であることが多いです。

例えば…

・ファミコンのドラクエ4で、カジノのコインを838861枚買おうとするとなぜか4Gで買える(本来なら1600億Gぐらいかかる)
・ファイナルファンタジー10で、味方へのヘイスガをリフレクで敵に反射するとなぜか敵のターンが全く回ってこなくなる
・初代Civilizationで、インドが民主主義化するとなぜかガンジーが核攻撃をしてくる

これらは全てオーバーフローが原因で引き起こされています。

オーバーフローという言葉はみなさん聞いたことがありますか?これは無い人も多いかもしれないね。

オーバーフローによって引き起こされるゲームのバグを説明するには、前提となる基礎知識がいくらか要るので先にそこから解説していきます。

まず情報を扱う単位であるビットとバイトの話

ゲームに限らず、我々が普段使っているコンピューターは電気で動いています。で、コンピューターに使われている部品の中には、電気が流れているか/いないかを判別できるものがあります。(※厳密にはちょっと違うけど大体の理解で)

これを使って、電気が流れているかいないか、つまりYESかNOか、0か1か、みたいな2択が表現できるようになっています。

このとき、0と1の2択だけで表現できる情報量の単位ビットと言います。1ビットという情報量だと、0と1、つまり2通りの情報を表すことができます。

これをたくさん組み合わせると、表現できる情報量がどんどん増えます。

2ビットだと、00、01、10、11の4通りの情報を表すことができます。ジャンケンの手3つはこれで表現できそうです(00がグー、01がチョキ、10がパー、みたいな感じで表現できますね。11は余るので無敵とかにする)
3ビットだと000、001、010、011、100、101、110、111の8通りの情報を表すことができます。
4ビットだと、0000、0001、…、1110、1111の16通りです。

この辺で察しがつく人もいると思いますが、ビット数が1増えるごとに情報量は倍々計算で増えていますね。
正確には、nビットの情報量=2のn乗 になっているのが分かるかと思います。

このままビット数を増やせばいくらでも大きな数を表現できるのですが…そうなると0と1の羅列がどんどん膨大な長さになっていくので、コンピューターはそれでもいいけど人間にとってはちょっと分かりにくくて扱いにくいです。

そこで、現実のコンピューターではもうちょっと大きな情報量の単位がよく使われます。バイトです。
KBキロバイト、MBメガバイト、GBギガバイト、みたいな単位はスマホとか触っててもよく見かけませんか?ギガが無くなるとか言いませんか?あれは全部バイトという情報量の単位の話をしています。

ちなみに1バイトとは8ビットのことです。8ビットは00000000〜11111111の256通りの情報を表すことができます。
1バイト=8ビットなので、2バイト=16ビット、3バイト=24ビット、4バイト=32ビット…という感じで増えていきます。

ここからゲームで扱っている数値の話をします

ゲームもコンピューターですから、体力やアイテムの個数などといった数値もビットやバイトの単位で表現・管理されています。

例えばアイテム所持数の上限が99個なゲームはよくありますが、99という数値をビットで表現するには7ビット以上必要です。(7ビット=2の7乗=128通りの情報を表せる)

でもバイトで管理する方が楽なので、(メモリ容量がカツカツだった昔のゲームを除けば)アイテム所持数を1バイトで管理しているゲームは多いです。

こういうゲームの場合、データの構造上は最高255個までアイテムを持てるはずですが、実際には個数が100個以上になりそうなら99個で止めるようなプログラムが別途存在していると思われます。

念のため補足:1バイト(=8ビット)で表現できる数値は0〜255の256通りです。0を含むのでMAXは255という点に留意してください。

255という数字はゲームにはよく出てくるもので、例えばドラクエのステータスは255が上限になっているものが多いです。1バイトで管理しているのでしょうね。

ちなみに、2バイト=16ビットだと、2の16乗=65536通りの情報を表すことができます。こちらは昔のFFのHPなんかでよく出てくる数値です(隠しボスのHPが65535だったりする)

いよいよ本題、オーバーフローの話

オーバーフローは、日本語で言えば「桁溢れ」です。

足し算や引き算の際に、くり上がりやくり下がりの桁が溢れてしまう現象を言います。

どういう意味?
例えば4ビットで1111というデータがあるとします。これに1を足すと、くり上がりが発生するため桁が1つ増えて10000になる…のが正しい計算ですが、このデータは4ビットですから5桁目は存在しません。なので下4桁だけを見ると0000ですね。

つまり、1111 + 1 = 0000 という、意味不明な計算結果になっているわけです。最大値だったはずの数値が最低値にまで減ってる。

あるいは、4ビットの0000から1を引くとどうなるでしょう?結論だけ言うと1111になってしまいます(詳細は省きますが、10000-1=1111 みたいな感じの挙動をしてしまいます。5桁目なんて存在しないのにくり下がりをしてしまう)
足し算のときとは逆で、最低値だったはずの数値がいきなり最大値になる。

これがオーバーフローです。

この問題を対策するには、計算過程でオーバーフローが起こりそうなときにエラー対応するようなプログラムを書いておけばいいんですが…こういう挙動を想定していない場合は…↓みたいなことが起こる。

FF3の敵のHPは16ビット(上限65535)で管理されている。わざと回復させて敵のHPをピッタリ65536にできればオーバーフローしてピッタリ0になり、死ぬ。これを利用すると勝てないボスのバハムートも倒せる。

ドラクエ4で、カジノのコインを買う際の代金は24ビット(上限16777215)で管理されている。1枚当たり20Gのコインを838861枚買おうとするとその代金は16777220G。上限値を僅かにオーバーしているためオーバーフローを起こし、4Gだけ残る。結果、代金4Gで大量のコインが買えてしまう。

筆者が昔やっていたRPG『ワイルドアームズ』では、アイテムが0個になっても内部システム上は「0個のアイテムがまだそこにある」という不具合があった。これを悪用すると0個のアイテムを「使う」ことができてしまい、そうするとアイテム個数がオーバーフローして255個(8ビットの上限)に増えた。

筆者が昔やっていたRPG『幻想水滸伝2』では、主人公への好意が高いほど攻撃力が上がる装備品「好意の封印球」があるが、好意を0未満まで減らすとオーバーフローして逆に最大値になるバグがあった。これにより、一番強くしたいお気に入りの味方に好意の封印球をつけ、そいつを徹底的に死なせまくる(死ぬと好意が下がる)という倒錯した愛情表現が横行した。
※逆に好意が最大値を超えてもオーバーフローしない(そっちはバグ対策がされていた)ので、みんな安心して味方を死なせまくって好意を最大値にした。

初代Civilizationで、非暴力主義を貫くガンジーは攻撃性のパラメータが全キャラ中最小の1。一方、文明が民主主義を採用すると攻撃性のパラメータは2下がる。よって、ガンジーのいるインドが民主主義化するとガンジーの攻撃性がオーバーフローして255(8ビットの上限)になり、突然核戦争を仕掛けてくるようになってしまう。

亜種、バッファオーバーフロー

ここまでの話で、「減りすぎて逆にカンスト、増えすぎて逆に0、みたいな現象が起こるんだなあ」ということはなんとなく理解できたかと思います。

でもオーバーフローによる悲劇はこれだけではありません。ビットの管理方法によっては、全く予想もつかないバグが起こってしまうこともあります。

例えばドラクエ4にて(ドラクエよく出てくるね)、ボス戦で「にげる」を8回選ぶとなぜか全ての攻撃が会心の一撃になるバグがあるのですが、このバグはこのゲームのビットを管理している仕組みを紐解けば分かってきます。

説明しましょう。まず、ドラクエ4は戦闘で「にげる」を4回選ぶと必ず逃走に成功するという仕様があります。この仕組みを実現するため、一時的にデータを保存しておける場所(バッファと言います)に以下のようなビットのデータを用意しています↓

画像1

各項目に0か1が入るイメージです(データXとデータYは「にげる」とは無関係なバッファ領域です)
戦闘中に「にげる」を選んで逃走に失敗すると、『にげるを選んだ回数』を1つ増やします。
1回失敗すると01、2回失敗すると10、3回失敗すると11、とカウントします。3回失敗した時点で下記画像のような感じになります↓

画像2

この、11の状態で「にげる」を選ぶと必ず逃走に成功する仕組みです。

問題はここからです。ボス戦は絶対に逃げることができません。ですが、『にげるを選んだ回数』はカウントされ続けてしまいます。もし4回「にげる」を選ぶと、普通の戦闘なら逃走成功ですが、絶対に逃げられないボス戦の場合…

画像3

こうなってしまいます。
隣にある無関係なバッファ領域『データY』にくり上がりの計算をしてしまい、データYが1になってしまいます。
このままさらに逃走を重ねると…

画像4

逃走8回目の時点でさらにくり上がりが発生し、さらに隣の無関係なバッファ領域『データX』が1になってしまいます。

このデータXが実は、『パルプンテの効果で全ての攻撃が会心の一撃になったときのフラグ』です。
(※パルプンテ:ランダムで何が起こるか分からない呪文)
このフラグが1になっていると、全ての攻撃が会心の一撃になるという仕様です。

こんな仕組みで、8回にげると全ての攻撃が会心の一撃になるバグが発生しているのです。

今まで説明していた、数値が増えたり減ったりするようなオーバーフローの例と違って、この例では隣り合った無関係なバッファ領域にまでオーバーフローを発生させてしまっています。これをバッファオーバーフローと言います。

バッファオーバーフロー攻撃

バッファオーバーフローは、意図的に何かのバッファを溢れさせることで、無関係な別のバッファの値を改竄することが可能です。
なのでゲームにおいては結構デタラメでめちゃくちゃなバグに使われる傾向がありますが、現実社会ではサイバー攻撃として使われることがあります。

※この記事で説明したバッファオーバーフローは最も古典的な例で、現実のバッファオーバーフロー攻撃にはもっと多彩な手法があります。

ちなみにバッファオーバーフロー攻撃は、iパス試験にたまに出ます(iパス講座ノルマ達成)

ところで、記事タイトルは「古今東西」だけど実例が古今東西じゃなくね?

君のような勘のいいガキは嫌いだよ


…オーバーフローは、基本的にはプログラムを書く際にきちんと対応すれば防げるバグですし、近年ではプログラム言語やライブラリ側がすでに対策してくれている場合が多いです。
今どきな開発環境では早々お目にかかるものではないということです。

また、今と違って昔のゲームはとにかくメモリ容量がカツカツで、これでもかと工夫して工夫してデータを詰め詰めにしています。それも、オーバーフローが容易に起こる土壌になっているわけです。

さらに言うと、今どきのアプリやゲームはオンライン環境前提で頻繁にアップデートが入るため、仮にオーバーフローするバグが見つかったとしても即時修正されていることでしょう。

ということで、残念ながら(?)近年のゲームでオーバーフローに遭遇することはそんなにないんですね…。もし見かけたら相当レアなイベントなので運がいいですよ(?)


以上!iパス講座ふんわり理解シリーズはおおよそ火曜日に更新される予定です。次回もよろしくお願いします!

iパスに関する記事をまとめたマガジンはこちら↓

この記事が気に入ったらサポートをしてみませんか?