Dr.Riptideを例にしたゲームリソースの形式の研究

私はこのゲームをより新しいプラットフォームに移植することを始めました。 しかし、もちろん、このゲームはオープンソースとはほど遠いものであり、1994年に開発者が25ドルを費やしたため、すべてのゲームリソースを再描画するか、単一のゲームアーカイブを破壊する必要がありました。 私がしたこと。



RIPTIDE.DATという名前のゲームアーカイブは、独自の形式のバイナリファイルです。 ところで、それはアーカイブではなく、いわゆる疑似アーカイブです。 つまり ファイルは圧縮せずに単一のコンテナに格納され、コンテナ内のファイルにアクセスする方法を指示する基本的なファイルシステムがいくつかあります。

このファイルを16進エディタで開くと、コンテナ内のファイルに関するレコードが最初に表示され、次にバイナリデータ自体が表示されます。 実際には、コンテナに含まれるファイルの数と記録形式自体を調べる必要があります。 最初に注意することは、固定レコード長、つまり 1つのレコードのファイル名の先頭から次のレコードの先頭まで、すべてのレコードについて、0x19(25)バイトと同じで等しい。







ただし、最初のレコードのファイル名はファイルの先頭からわずかにオフセットされているため、その前にあるものが表示されます。 なぜなら 通常、コンパイラはサイズが1(バイト)、2(ワード)、4(dword)、8(qword)バイトの標準データを使用します。その後、心のデータをそのようなサイズのブロックに分割します。 2つのdword 0x00001A60



0x00013EEC



、およびワード0x010E



は、データの開始またはファイル自体のサイズよりも小さいためデータのサイズを示すことができるため、注目を集めています。 0x00013EEC



0x000001E



は関係ありません。 最初はいくつかのバイナリデータの中間を指し、2番目はファイルレコードの中間になります。 ただし、 0x00001A60



は、最後のファイルエントリの直後にあるバイナリデータを均等に示します。 このフィールドはファイルレコードを参照するため、次のレコードについて同じフィールドを調べます。 これを行うには、オフセットに数値0x19を追加します。この数値は、上記で受信され、ファイルレコードの長さです: 0x0000000A+0x19=0x00000023



。 このオフセットの0x0001594C



には、ファイルのサイズ内にある数値0x0001594C



があります。 最初のファイルレコードの数値0x00013EEC



は、この数値よりも小さいことが0x00013EEC



ます。 ご覧ください。 0x00001A60+0x00013EEC=0x0001594C



。 他のレコードでこれを確認し、このフィールドにはコンテナにあるファイルのサイズが含まれていると結論付けます。









原則として、これはすでにコンテナからすべてのファイルを取得するのに十分ですが、残りのフィールドの目的を見てみましょう。 オフセットとサイズの間の数値は、擬似アーカイブ自体のサイズよりもはるかに大きいため、アドレスまたはサイズにすることはできません。 最初に頭に浮かぶのはチェックサムです。これは、ファイルが破損していることが判明した場合にデータをチェックすることが論理的であるためです。 ただし、この場合、開発者の行動は異なります。 これらのフィールドには、ファイルのタイムスタンプが含まれます。 なぜこれが必要なのかは謎のままです。 最初のファイルレコードの例を使用して、 0x1CEF2292



DOS形式で07/15/1994 04:20:36に変換されます。

最後の未解決の値は、ファイルの先頭にあるワード0x010Eのみです。 コンテナ内のファイルの数が含まれていると仮定するのが最も論理的です。 これは簡単に確認できます。 最初のファイルレコード0x00001A60



から最初のファイルへのオフセットを取得し、単語自体で2バイトを減算し、0x19バイトのファイルレコードの長さで除算して、正確に(00001A60-2)/19 = 010E



します。

その結果、これは次のようにCの構造体として記述できます。

 typedef _FILE_ITEM { uint32_t Size; uint32_t TimeStamp; uint32_t Offset; char Name[13]; } FILE_ITEM, *PFILE_ITEM; typedef _HEADER { uint16_t Count; FILE_ITEM Files[0]; } HEADER, *PHEADER;
      
      





解凍後、CMF、L、M、PCS、PCX、TXT、VOCの拡張子を持つ270個のファイルを取得します。

これらの拡張機能のうち、非常に一般的な形式であるTXT、PCXを分析する必要はありません。 短い検索の後、サウンドファイルであるCMFとVOCを分析する必要はありませんでした。 L、M、およびPCSのままにします。 正直なところ、PCSが何に使用されているのかはまだわかりません。その必要はありませんでした。



ファイル名を分析すると、Lフォーマットファイルにはグラフィックスが含まれ、Mフォーマットにはゲームレベルカードに関する情報が含まれていると想定できます。



Lフォーマット


グラフィックを分析するとき、画像のサイズとグラフィックとして表示される情報自体はどこかの形式でどこかに表示される必要があるという事実に依存しています。 アニメーションの場合、少なくとも別のフレーム数と、場合によってはフレーム間の時間間隔が追加されます。

繰り返しますが、16進エディターでファイル(またはいくつか)を開きます。 すぐに目を引くのは、ゲームの最初のバイトで静的に表示されるすべてのグラフィックスの値が0x01であり、アニメーション化されたグラフィックスには複数あることです。 したがって、この数はファイル内のフレーム数を示すと仮定します。 次は2バイトで、その後にほとんどの場合ゼロがあります。 幅と高さだとします。 チェック-最初の値に2番目の値を掛けると、ファイルの長さのほとんどから、先頭の同じ3バイトだけを引いた値が得られます。

色を記述するために色ごとに1バイトのみが使用されるため、色はパレットのインデックスによって示され、同時に使用される色の最大数は256であり、これは当時のグラフィックモードに対応します。 256色モードでは、次のパレットが使用されます。







フレーム数が複数のファイルの場合、各フレームのサイズは前のグラフィックデータの直後になります。







この図では、2番目のフレームへのオフセットを簡単に見つけることができます。 最初のフレーム0x00000001へのオフセットを取得し、最初にサイズに割り当てられた2バイトを追加してから、スケジュールに0x0C * 0x10を追加します。 ちょうど0x000000C3



を取得します。



注目に値するのは、スペースを節約するために、説明されているフレームのサイズが異なる場合があることです。 透明色の場合、値0が使用されます。



Mフォーマット


これまでは、フォーマットがシンプルで難しくなく、おおよそ何を探すべきかがわかっていた場合、非常に直感的に行動する必要があります。



繰り返しますが、16進エディタで複数のMファイルを一度に開き、それらの類似した領域を識別しようとします。

短い分析の後、ファイルにいくつかの主要なブロックがあります。





最初に気づいたのは、すべてのファイルの3番目のブロックのサイズが0x8000バイトであることです。 2番目に気づいたのは、2番目のブロックがDWORDの配列に似ており、その長さが最初の4バイトの単語の倍数であるということです。 これら2つの単語がdwordの2次元配列の次元を設定し、次に配列自体を設定すると仮定することは論理的でした。

この配列の値を調べ始めます。 ほとんどの場合、dwordの下位バイトはゼロではありませんが、上位ビットに近いほど、ゼロ以外の値になる頻度は低くなります。

この2次元配列を、2バイトのdwordがポイントをペイントするかどうかを示す画像として表示することが決定されました。



次の図が判明しました。





これはおおよそ地図のシルエットに似ており、最初はこのバイトが壁との衝突をチェックするための地図を記述していると思いました。

次に、dwordの上位バイトがゼロ以外の色をピクセル単位で表示することにしました。 3番目は赤、4番目は黄色です。







写真がきれいになり始めます。 これらの値は、Lファイルに個別のグラフィックが存在する静的および動的なゲームオブジェクトを示します。



dwordの最初のバイトがタイルマップ内の画像のインデックス番号を格納していることが明らかになりました。これはこの場所に表示されることになっています。 ただし、タイルマップはグラフィックファイルのどこにも保存されていないため、そのデータブロックをサイズ0x8000バイトとして表示しようとしました。 私は画像の幅を知らなかったので、1ピクセルの厚さの長いストリップが最初に取得されました。 画像の幅を徐々に縮小すると、一部のマップ画像のシルエットが表示され始めました。 画像の幅が8ピクセルの場合、タイルに切り分けられたはっきりした正方形の鮮明な画像が得られました。 結果は、幅8、高さ4096ピクセルの画像になりました。

以下の図にいくつかのフラグメントを示します。 バイト値はRGBカラーコンポーネントとして使用されたため、画像はグレーの陰影で表示されました。

ちなみに、ほとんどの場合、写真に表示されるすべてのものは、セルが独自の色でペイントされた巨大なテーブルを含むレンダリングされたHTMLページのスクリーンショットです。 バイナリファイルの解析は、PHPを使用して実行されました。 私が変態であったということではなく、グラフィカルなライブラリを見るのが面倒でした。







4096ピクセルのタイルマップの高さを8で割ると、256枚ではなく512枚の画像が得られます。 したがって、壁との衝突をチェックするためのマスクだと思ったのは、マップ内の同じ画像インデックスであることが判明しました。 これが、開発者が1発で1石で2羽の鳥を殺した方法です。 つまり 泳ぐことが不可能なオブジェクトの若い256の画像、それを通過できる古い画像。 また、1バイトではなく、2バイトがインデックスに割り当てられます。



レンダリングされたオーバーレイマップは次のようになります。







これにより、マップ全体が1つの画面に簡単に収まり、320x200ピクセルの解像度のグラフィックスでは、ゲームの背景がスムーズにスクロールします。



この形式の最後の2つのブロックが、直感的に失敗したことが判明した理由。



All Articles