[NES] Prince of Persiaのレベルエディタを作成しています。 第3章 コードの最初の行

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



免責事項


研究プロセスは非常に面白いプロセスです。 ファイル内にあるバイナリの混乱をバイト単位で捨てると、非常に調和のとれたアーキテクチャが得られます。これは、16進エディタ(またはさらに悪いことにテキストエディタ)に表示される面白い波線のベールの後ろに隠れています。 研究プロセスへのアプローチは、微妙な技術を伴う必要はありません。時には、ハンマーですべてを破壊し、断片を研究する方が簡単です。 まあ、子供時代のように。





HEXエディターを開きます


データのビット間をジャンプすると、ROMファイルのオフセット0x18010から開始し、データが変更されると、最初の部屋がブロックごとに変換を開始することがわかりました。 しかし、最初に、データを見てください:

E0 E0 E0 01 21 01 21 01 14 14 13 13 21 03 00 14 14 14 14 14 14 14 14 14 0E 03 0B 21 01 14 ...





これらのデータは、以前想定したものに完全に該当すると言えます。 繰り返しバイトの非常に調和のとれた行(つまり、明らかにコードではない)を見ることができ、目で見ると、暗号化されたデータのようには見えません。 最初にE0をE0に慎重に変更してみましょう。



これまでにないプラットフォームが登場しました。

各バイトに1つずつ追加することにより、1バイトが部屋の1ブロックを担当するようにします。 したがって、データブロックのサイズは30バイトです(部屋は10x3ブロックで構成されます)。

これはデバッガーを使用して理解できますが、このコードまたはそのコードをすぐに言うのが難しくなるのは何であり、このデータを使用して後で行うのはリバスです。 古き良き「突く」方法を使用する方が簡単です(ただし、信頼性は高くありません!)。



見回す


部屋の1つのブロックには1バイトが関与するため、特定のバイト値がどのタイプのブロックに関与するかを理解しておくと便利です。 検索中、32の異なるブロックがあり、そのうちのいくつかはグラフィックガベージを描画し、いくつかは完全にゲームを台無しにします。 32個のブロックがあるため、これは値の最初の5ビットがブロックのタイプを担当することを意味します。 バイト内の残りの3ビットを変更すると、それらがブロックの特性に関与していることがわかります。 たとえば、値#04は閉じたグリッドで、#24は開いたグリッドです。

そして最後に、この30バイトブロック外のバイトをソートする場合は、他の部屋も変更するようにしてください。 ただし、ここでは、部屋の順序が混howとしています。 同じ混oticとした順序は、以前に別のデータブロックでユニットを変更し、王子が1つの部屋に現れ、次に別の部屋に現れたときでした。 確かに、その後失敗しました。



そのブロックを詳しく見てみましょう。 そのブロックのオフセットが0x18340であることを思い出して、それに進みます。 シングルではなくデュースを置き、王子は別の部屋にジャンプします。 前のブロックのデータを編集して、この部屋を見つけてみましょう。 検索により、これらは最初の部屋を担当する最初の30バイトブロックの次の30バイトであることがわかります。 したがって、その単位は部屋番号です。 また、部屋番号は30バイトブロックの番号です。 まあ、それは私たちの部屋に番号が付けられ、1から番号が付けられることを意味します。



さて、これで部屋にシリアル番号があることがわかりました。つまり、このシリアル番号はメモリのどこかに保存されています。 彼を探す必要はありませんが、将来のために覚えておいてください。



現時点では、部屋の構築元となるデータの不特定の配列があることがわかっています。 各部屋は、サイズが30バイトのブロックで記述されます。 部屋の後には、不定サイズの特定のデータ構造があります。 レベルの「見出し」と呼びます。 そして...実際、次は何を知っていますか? すでに知っていることを見てみましょう。



部屋に装備します


1つの部屋がこの配列の30バイトであると仮定して、先頭と「ヘッダー」の間に収まる部屋の数を計算してみましょう。

0x182E9 - 0x18010 + 1 = 0x2DA = 730





30では分割されません。 行き止まり? どんなに。 部屋番号を含むレベル図を作成してみましょう。 今のところ、「ヘッダー」の最初のバイトを変更してプリンスを移動することによってのみこれを行うことができます。 部屋から部屋へと順番に移動すると、別の部屋で終わるか、ゲームがまったく開始されないことがわかります。 失敗した数字を除外し、ゲーム内での位置に応じて残りの数字を配置します。



開発者が何によって導かれたのかを言うのは難しいですが、数字に頼らないのであれば、そのスキームはもっともらしいです。



これらの部屋を私たちのアレイにある部屋と比較してみましょう。 1から12までの図には部屋がありますが、13(そして18と24)が欠落しています。 オフセットを考慮します: 0x18010 + 12( )*30( ) = 0x18010 + 0x168 = 0x18178





私たちは合格して見ます:

FF 14 14 14 E1 03...





#FF? そして、次に何が来るのでしょうか? 部屋14に移動すると、左上隅に3つの「コンクリート」ブロック、プラットフォーム、そして列があります。 14番目の部屋の最初の3つの「コンクリート」ブロックは番号#14で記述され、プラットフォームは#E1、列は#03です。 したがって、#FFには部屋14の説明がすでにあり、部屋13自体は1バイト-#FFに削減されました。 #FFはある種のマーカーであるように見えます。これは、部屋が存在しないことを意味します。 状況は18室と24室で同じです。

しかし、25番以上の番号は、もはや存在しません。これは、部屋番号24がないことを示す#FFマーカーの後に、前のものと決して同じではないデータのセットがあるためです。

05 00 00 02 06 03 01 00 02 09 00 00...





もちろん、これらの数値を試してみることもできますが、スキームを見てみましょう。





見つかった配列に同じ番号が表示されます。 推測してみましょう。



数字5、6、2は4の倍数であることがわかります。 したがって、1つの部屋の環境は4バイトの構造で記述され、最初の部屋は左側の部屋番号です。 レベルマップに基づいて、残りの3バイトは、右、上、下の部屋を表します。 さらに、スペースがない場合、適切な場所に0が配置され、これらの構造の数は正確に24になり、その後、レベルの「見出し」が始まります。 もしそうなら、最大24の部屋を構築することができます(そして、コードを調べることで、これを見ることができます)。



警備員に電話してください!


前回、特定のデータ構造へのポインターの配列を見つけました。これを「ヘッダー」と呼びます。 さらに、配列を読み取るときに、二重レベルのシーケンス番号がインデックスとして使用されました。 したがって、この配列の次の2バイトは、同じ構造を指すが、第2レベルのポインターです。 私たちもそれを勉強します:

ポインターの配列: D9 82 61 86 91 89 ...







そのようなデータに出会えます:

05 0D FF 1E 9E ...



(おもしろい、ここでも、最初の3バイトの後、0x0Eで終わる数字が再び行く)



ここに最初の番号#05があるので、王子はシリアル番号5の部屋で始まります。しかし、次の2バイトはゼロではなくなりました。

あなたが彼らと遊んでいると、#0Dが部屋の王子の位置であることがわかります。 可能な位置の合計数は10x3 = 30です。10は部屋の幅です。つまり、#0D = 13は3x1の位置です。つまり、左の4番目のブロックと上の2番目のブロックの代わりになります。 値が#FFの次のバイトはその方​​向です。#FFの場合、それは向きを変えて左に目を向け、#FFでない場合は、右に残ります。 唯一の奇妙なことは、最初のレベルでは彼が自分の位置を変えないということです。 ただし、後で確認します。



最初の3バイトに続くものを見てみましょう。

覚えているように、最初のレベルでは、最初のバイトを変更すると、部屋に警備員が現れました。 したがって、これらの数値は、部屋に警備員がいることに何らかの形で関与しています。



最初のレベルに戻り、4番目のフィールドに#02(前回と同様)を書きます。 警備員は王子の非常に近くに現れました。 #03を入れます。 登場しましたが、すでに遠く離れています。 #1Dまでの値を調べて、このバイトがガード位置を担当していることを確認します。 しかし、0x1Eを設定すると消えます。 しかし、10進表現で0x1E = 30であるため、位置の制限値を設定した場合、部屋にガードがないことを意味します。 どうやら、残りのバイトは他の部屋の警備員を担当しています。 つまり、次のバイトは、2番目の部屋、次に3番目の部屋などでガードの存在を担当します。 ただし、これは簡単な確認によって確認されます。 また、値の最初の5ビットのみがガード位置を担当し、残りの3ビットはどのようにねじれても使用されないことを確認します。



すべてをまとめる:ビルダー、アーキテクト、ガーディアン


合計で、次のデータ構造があります。



しかし、それは不便です:(ポインターの配列から)持っているポインターは「見出し」につながります。つまり、レベルの先頭のオフセットを数える必要があります。 デバッガーを見て、それがどのようにカウントされるかを見てみましょう。

開始点はオフセット0x18010にあることがわかっています。つまり、アドレス$ 8000のメモリ内にあることを意味します。 ただし、銀行は複数あるため、必要なもののみを追跡します。 彼の数を数えることはせず、8000ドルでの読み取りに関心があることをデバッガーに伝えます。最初のバイトは#E0(最初のレベルの最初の部屋の最初のブロック)でなければなりません





最初の手順では、手順を説明します。

 $C0F6:20 C7 C0 JSR $C0C7 $C0F9:A5 52 LDA $0052 = #$00 $C0FB:18 CLC $C0FC:65 6D ADC $006D = #$00 $C0FE:85 0E STA $000E = #$00 $C100:A5 53 LDA $0053 = #$00 $C102:65 6E ADC $006E = #$80 $C104:85 0F STA $000F = #$80 $C106:8C 0B 04 STY $040B = #$03 $C109:A0 00 LDY #$00 $C10B:B1 0E LDA ($0E),Y @ $8000 = #$E0 ;; <<<<<  $C10D:AC 0B 04 LDY $040B = #$03 $C110:60 RTS
      
      







セル$ 0E:$ 0Fにアドレス8000があります。 そこで、彼はおなじみの$ 6D:$ 6Eセルから取得し、それに$ 52および$ 53セルの値が追加されました。 セル$ 52、$ 53はゼロになったため、数値#00と#80がどのように$ 6D:$ 6Eになったかに興味があります。

プロシージャの一番最初に、$ C0C7で別のプロシージャへの呼び出しが表示されます。 私たちはそこに入ります:

 $C0C7:20 D5 C0 JSR $C0D5 $C0CA:BD 3A EB LDA $EB3A,X @ $EB3A = #$00 $C0CD:85 6D STA $006D = #$00 $C0CF:BD 3B EB LDA $EB3B,X @ $EB3B = #$80 $C0D2:85 6E STA $006E = #$80 $C0D4:60 RTS
      
      







最初に、使い慣れたプロシージャsub_C0D5(レベルXのダブルシーケンス番号をレジスタXに返す)を呼び出し、最後のバンクから読み取ります。 ルールに従って検討します: $EB3A+0x10010=0x1EB4A







私たちは見ます:

00 80 31 83 9B 86 C1 89 ...





そうだね 「ブリック」へのポインター。 このコードの近くを登ると、3つの同様の手順が表示されます。それらはほぼ次々に実行されます。 2つを見ましたが、3つ目(0x1EB82)は配列を参照しており、部屋のレイアウトにつながっています。



みんな集まったようですか? どんなに。



ドアを開けて


原則として、基礎から屋根に沿ってレベルを構築できますが、グリルはどうですか? 管理する必要があります。 彼らの行動を研究してみましょう。



一度に2つの格子と3つのボタンがあるため、王子を部屋番号5に配置します。 それらの1つは火格子を閉じ、他の2つは開いています。 その後、私はst迷しました:開発者としてどのようにボタンをグリッドに接続できますか?





まず、ボタンまたはバーを動かして、エフェクトを確認します。

移動後の最初の火格子が開きを停止しました。 また、移動時にボタンが機能しなくなりました。 しかし、2つのボタン(「高」と「低」)を交換すると、「低」ボタン(画像では、王子が立つ「高」の左側の2つの位置-見えない)が開き始め、「高」 »閉じる。 したがって、ボタンとバーの位置(以前と同様に、ゼロから数え、部屋を1つから数えます)は一意に接続されていますが、どのようにですか? これらの位置を列に書き、このプレートを入手しました。

Button Room - Button Position : Door Room - Door Position





05 - 02 : 05 - 09



「低」ボタンは2番目のグリルを閉じます。

05 - 04 : 05 - 05 + 05 - 09



最初の「高」ボタンは両方のグリルを開きます。

05 - 08 : 05 - 09



番目の「高」ボタンは2番目のグリルのみを開きます。



この表を熟考すると、どういうわけか自動的にHEXエディターに入り、レベルの「見出し」をもう一度見ることにしました。 私の予想に反して、警備員の部屋を収容していたアレイが終わった後、それは第2レベルではなく、他のデータのアレイでした。 第二レベルはそれらの直後に始まりました。

05 02 01 05 09 05 04 00 05 09 05 04 00 05 05 05 08 00 05 09 ...





バイトはテーブルのエントリをほぼ正確に繰り返し、コロンの代わりに00または01がありました。最初のレコードの反対側に「閉じる」という単語があり、他のレコードの前に「開く」と考えると、00が言うのは簡単ですこの束で格子が開き、01が閉じます。 検証により、そうであることが示されました。 また、ドアを開くメカニズムの原因となるメカニズムを見つけました。



エディター


さて、エディタはほとんど準備ができていますか? 3つのオフセットを取得し、それらから3つの構造(部屋、レベルジオメトリ、および「見出し」)へのポインタを決定し、エディターに表示するだけで十分です。 どんなに。



部屋を配置するアルゴリズムは非常に単純です。最初の部屋から始めて、隣接する部屋を再帰的に「ポーリング」し、グリッドに配置します。 最初の部屋はポイント(0,0)、左側の部屋-ポイント(-1、0)、下の部屋-ポイント(0、1)などに配置されます。 次に、「最も負の」X座標とY座標を取得し、それらの絶対値をすべての部屋の座標に追加します。 したがって、レベル全体のマップを取得します。



すべては問題ありませんが、レベル10でアルゴリズムは例外をスローします。つまり、部屋1は部屋3を指し、部屋3は順番に00を指し、これはその上に部屋がないことを意味します。 ゲームでは、「アンダー」ルームNo. 1も取得できません(レベル10はそこから始まります)。

部屋3を大まかにスケッチして、これがゲームが始まる非常に出発する部屋であることが明らかになりました。 その中で、パスワードを入力して適切なレベルに到達するために、右に移動してゲームを再び開始するか、左に移動するように求められます。

ルーム番号3にはルームメート-ルーム番号12があります。 それでも、はい、ボトルと出口ドアだけがあります。



したがって、エディターでこれらの部屋を考慮し、レベルマップを描画するだけで十分です。



ネスプリンス


エディターの作成に近づきました。 建設にはほとんどすべてのことが必要です。 ダンジョンを宮殿に、またはその逆に適切に変換する方法を見つけることができるだけです。 外観を変更する方法は知っていますが、色を変更する方法はまだです。



私は長い間エディタの名前について考えていませんでした。DOSバージョン用のPrincEdがあり、NESバージョン用のNESPrincEdがあります。

この時点で、シェルを作成するだけでエディタの準備ができたと思いました。 当時はなんと間違っていたのでしょうか...そして第4章では、「彼は走ります! またはクローゼットの中のスケルトン、「私たちはこれを確信するでしょう。



All Articles