[NES] Prince of Persiaのレベルエディタを作成しています。 エピローグ。 ダンジョン

第1章第2章第3 第4 第5章エピローグ



免責事項


「何度も何度も、私がこれまで好きだったゲームに合格し、すべての可能な秘密を見つけて、何度も何度もプレイしたかったのですが、新しいレベル、新しい秘密、新しい機会がありました。」と書いています。 当然のことながら、「通常」モードで同じゲームを渡すと、舞台裏に隠された何かの検索が始まりました。 ゲームに隠されたレベル、部屋、レセプション、またはパスワードのシステムがある場合、一日中、真夜中にブルースクリーンを見つけてパスワードを解読しようとする必要がありました。 PoPも例外ではありませんでした。 また、パスワードをコンパイルするためのアルゴリズムを選択しませんでしたが、既存のパスワードから正しいパスワードを作成できるいくつかの方法を見つけることができました。 新しいパスワードがどこにつながるかを言うのは本当ですが、その使用の瞬間まで、私はできませんでした。





ダンジョン


NESのPoPパスワードシステムは今や詳細を超えています。既存のパスワードを変更する方法と、アルゴリズム自体の説明の両方があります。



それは短いので、私は腕によって明らかにされた最初の言語でそれをもたらします:
Bashパスワードジェネレーター
#!/bin/bash PLEVEL=$1 PTIME=$2 if ! [[ ${PLEVEL} =~ ^[0-9]{1,2}$ ]] ; then echo "Invalid level" >&2 ; exit 1 ; fi if ! [[ ${PTIME} =~ ^[0-9]{1,2}$ ]] ; then echo "Invalid time" >&2 ; exit 1 ; fi if [ "0" == ${PLEVEL} ] ; then echo "Level must be great than 0" >&2 ; exit 1 ; fi PLEVEL=$[PLEVEL-1] R1=$[RANDOM % 10] R2=$[RANDOM % 10] PASS0=$[((PTIME / 10)+R1) % 10] PASS3=$[((PTIME % 10)+R2) % 10] PASS1=$[((PLEVEL & 3)+R1) % 10] PASS7=$[((PLEVEL / 4)+R2) % 10] PASS2=$R1 PASS5=$R2 SUM=$[PASS0+PASS1+PASS2+PASS3] SUM=$[SUM+(SUM % 10)+PASS5] SUM=$[SUM+(SUM / 10)+PASS7] PASS4=$[SUM % 10] PASS6=$[SUM / 10] echo "${PASS0}${PASS1}${PASS2}${PASS3}${PASS4}${PASS5}${PASS6}${PASS7}${PASS8}${PASS9}"
      
      









そして今、パスワードを並べ替えました(当時は、現在の形式とプロジェクトにエミュレーターがなかったとき)、私は明らかに開発者によって提供されていない奇妙な場所に行きました:



または





これらの「レベル」での管理は部分的にしか機能せず、外観は奇妙であり、メインゲームからそこに到達することはできません。

ここで、エンジンがレベルデータをどのように保存するかを見て、ゲーム内の14以上のレベルが単に提供されないことを確認しました。 これらのパスワードはどこにつながるのですか?



オーバーフロー


判明したように、ゲームでは、オーバーフローするものに対するチェックはありません。 たとえば、エディタ内の部屋によりアクティブなアクティブブロックを配置すると、フリーズしたゲームや、出現したダブルまたはその他の興味深い形の興味深いアーティファクトを取得できます。



検証の欠如は、パスワードをコンパイルする手順にも適用されます。このアルゴリズムでは、レベル番号0〜15が提供されます。 0)から、誰もそれらを入力しません。 うん。



最終レベルが構築される3つのポインターテーブルでは、14個の要素と16個の15個の要素のみが提供されていないことがわかります。 したがって、レベルは、既存のテーブルに続くデータを解釈するときにポインターに該当するガーベッジから構築されます。 しかし、なぜあなたはキャラクターを完全に制御できないのですか?



理論を思い出してください


レベルは3つのデータ型に基づいて構築され、各データ型には独自のポインターテーブルがあります。





補助データもあります:





ポインターを持つテーブルは、正確に28バイト離れています。 言い換えれば、彼らはお互いに続きます。 その場合、ポインタは目的のテーブルからではなく、次のテーブルから取得されます。 これらのポインターによって参照されるデータも一緒に混合され、15または16の「レベル」に移動するため、そのデータ配列の中央のどこかに到達します。 さらに、ある感覚のデータは別の感覚のデータとして解釈されます。



たとえば、15の「レベル」のビューを精神的に想像してみましょう。 オフセット0x1EB66(ヘッダー)を取得し、28を追加して、ポインターを確認します。

D9 82 61 86 91 89 F1 8C 06 90 85 92 0D 96 61 99 F3 9C CD 9F 2C A3 9B A6 5B A8 AC A9 >> 79 82 << ...





8,279ドルは、第1レベルの見出し[82D9]よりも早いです。 最初のレベルのジオメトリを記述するデータに到達することは明らかです。 しかし、これらは異なる解釈になります:

05 00 00 02 06 03 01 00 02 09 00 00 13 0E 14 00 15 01 00 06 08 02 05 00 ...





* 05-5番目の部屋から開始します。

* 00 00-00の位置から開始し、右を向く。

*残りのデータは、いくつかの例外を除き、警備員が左上隅の各部屋にいることを示しています。



0x1EB4A + 28 = 0x1EB66:$ 82D9。 レベルは、かつて最初のレベルの見出しであったデータに基づいて構築されます。 しかし、5番目の部屋から開始するため、$ 82D9から開始して、ルールに従って4つの部屋をスキップする必要があります。最初のバイトが#FFでない場合は+30バイト、それ以外の場合は+1バイト:

$ 82D9 =#01。 +30を追加します。

$ 82F7 =#05。 +30を追加します。

$ 8315 =#08。 +30を追加します。

$ 8333 =#20。 +30を追加します。

$ 8351: 20 00 00 14 01 03 21 03 14 14 20 00 00 14 14 14 14...





データの性質から判断すると、最終的には第2レベルの部屋の真ん中のどこかに行き着きました。 2番目のレベルは8331ドルで始まるため、2番目の部屋のどこかにあり、次のようになります。



ガードは、提供されるヘッダーに基づいて、左上隅に配置する必要があります。



次に、レベルジオメトリを見てみましょう。

0x1EB82 + 28 = 0x1EBA0: 0C 03 C0 30 0C 03...



つまり、RAM(ROMではなく!)のアドレス$ 030Cの周りのどこかになります。 これらのアドレスのデータは24を超えることがわかっています。これは、すべての要望があり次の部屋に行くことができないことを示唆しています。



比較:



この「部屋」の境界線は、第2レベルの2番目の部屋の境界線と一致しないため、「アーキテクチャ」が左にシフトしていることがわかります。 また、左側には隣の部屋に対応する崖がありますが、レベルのジオメトリが間違っているため、そうではありません。



動き出す


ここで、レベル内のガードに関する情報がどのように使用されるかを確認することを提案します。

 $F284:20 DA C0 JSR $C0DA $F287:B1 6D LDA ($6D),Y @ $9FC7 = #$1E $F289:29 1F AND #$1F $F28B:C9 1E CMP #$1E $F28D:B0 0E BCS $F29D $F28F:A6 17 LDX $0017 = #$00 $F291:9D 11 07 STA $0711,X @ $0723 = #$00 $F294:A5 18 LDA $0018 = #$00 $F296:9D 10 07 STA $0710,X @ $0722 = #$00 $F299:E6 17 INC $0017 = #$00 $F29B:E6 17 INC $0017 = #$00 $F29D:E6 18 INC $0018 = #$00 $F29F:A5 18 LDA $0018 = #$00 $F2A1:C9 19 CMP #$19 $F2A3:D0 D9 BNE $F27E $F2A5:A6 17 LDX $0017 = #$00 $F2A7:A9 FF LDA #$FF $F2A9:9D 10 07 STA $0710,X @ $0722 = #$00 $F2AC:60 RTS
      
      







プロシージャ$ C0DAは、思い出すように、レベル見出しへのポインタを抽出し、アドレス$ 6D:$ 6Eに配置します。レジスタYのオフセット#03があります。ガードに関する情報は、レベルの始まり。 さらに、最初の5ビットが番号#1Eに追加されると、反復がスキップされることがわかります。それ以外の場合、次の構造が$ 0710から始まるアドレスに書き込まれます:<room number>:<guardordinate>-構造ごとに2バイト。 すべての部屋が警備員でいっぱいの場合、データを配置できる最後の住所は0740ドルになり、その後、#FFマーカーは0742ドルに配置されます。 しかし、0735ドルから始まるアドレスは別の目的に使用されます-これはゲームの通常の操作で明らかです。つまり、この状況では古典的なバッファーオーバーフローが発生します。

0735ドルで、ゲームパッドの制御を担当するフラグがあります。 0がある場合、コントロールは標準であり、0でない場合、コントロールが制限されているスタートルームにいると見なされます。 さて、この「レベル」では、警備員の座標が重要なデータを上書きしているため、セル$ 0735にゼロはありません。0を入れると、王子を完全に制御できます。 確かに、レベルジオメトリが壊れているため、この部屋を超えて脱出することはできません。



同じことが16番目の「レベル」にも当てはまります。 17以上の「レベル」で、$ 70のパスワードを入力したときに目的の数字を入力した場合、それらは明らかにゴミになり、表示されません。



エピローグのエピローグ


この記事では、「秘密」レベルの広まっている神話が真実ではないことを伝えようとしました。 原則として、ゲームコードを分析します。これは以前の記事で見られましたが、ゲームには秘密がまったくありません。隠された部屋、レベル、またはそのようなものです。 「秘密レベル」-これは、パスワードを読み取るための手順に必要な検証条件がまったくないことです。



全体の調査の結果、次のように言えます。ゲームは2番目のマッパーのフレームワーク内で非常に単純であり、さらに、急いで開発される可能性が高いです。 エンジンのかなり良い基礎を見ることができますが、最終的には松葉杖で明確に終了しました:レベル間の二重または非標準の遷移の実装は、偶数コードの途中に挿入されるハードコードです。 ここで、NESバージョンと他のポートの違いが表示されます。最終的には、いくつかの簡単な手順で十分に思い浮かびますが、ささいなことは簡単には行われませんでした。 これをエンジンに簡単なパッチの形で追加すると、元のバージョンとほぼ完全に準拠することができ、DOSバージョンと比較してNESのバージョンが勝つように思えます。 少なくとも音楽の存在、パ​​スワードシステム、およびより暗い雰囲気。



これで、NESリバースに関する一連の記事の終わりです。 今、計画には、実際のデンディで新しいゲームをプレイしたいので、より低いレベル-鉄のレベルへの降下が含まれています。 これに関する記事がさらに2つあります。



All Articles