バックエンジニアリングシーザーIII

私はゲーム、特に経済戦略をするのが好きです。子供の頃の都市開発シミュレーター、Caesar III、彼らが言うように、暖かくてランプについて話したいです。 このゲームは、1998年にその分野の専門家であるImpressions Gamesによってリリースされました。 これは、古代ローマの都市をリアルタイムで管理する経済シミュレータです。 何年も経って、私はそれをもう一度調べて、ゲームの楽しみを広げ、リソースを見て、プログラマーの観点からゲームのロジックを理解しようとすることにしました。



カットの下で、テクスチャを抽出し、ゲームアルゴリズムを検索し、趣味がどのように独立したプロジェクトに変わったかを伝えるプロセスについて説明します。 また、RGB555パレット、IDA、HexRays、およびいくつかのコードもあります。





ミュージック

音楽については何も書きません。なぜなら、音楽は.wav形式のゲームのディスクに誰もが開梱しているからです。



グラフィックス



グラフィックス(テクスチャ)では、すべてがはるかに複雑になり、テクスチャは拡張子.sg2および.555のいくつかの擬似アーカイブに分割されます。



拡張子が.sg2のファイル(「目次」と呼びます)には、寸法、アトラスのオフセット、名前と番号、識別子、さまざまなフラグなどのテクスチャパラメータが含まれています。



拡張子が.555のファイル(「アトラス」と呼びます)には、画像自体が独自の記述形式で含まれており、3つのタイプに分けられます。

-シンプル(bmp)

-等尺性

-アルファチャンネル付き

各テクスチャタイプには、独自の「圧縮」形式があります。 「コンテンツのテーブル」は複数のアトラスを参照できます。「アトラス」の名前は、含まれているテクスチャのグループの名前に対応している必要があります。 単純なテクスチャは色の配列として読み取られ、画面上で何も処理せずに実際に描画できます。「処理」は、チャンネルごとに深さ5ビットのBGR555色を作業に便利なARGB32に変換することです。 Caesar IIIでは、透明度のあるテクスチャは使用されません。これらは、このシリーズのゲーム(ファラオ、クレオパトラなど)で後で使用されます



C3.SG2ファイルには、イメージグループの説明が含まれています。

このファイルを16進エディターで開くと、次のデータブロックが表示されます。



これは、 plateauという名前の44(n_images:0x0000002C)イメージのグループを記述し、その情報はインデックス201(start_index:0x000000C9)で始まります。 合計すると、「目次」にはそのようなグループが100個あります。 グループの説明の後に、特定の画像の説明が続き、並べ替えて写真自体を復元できます。 残っているのは、目次を読んで、絞ったテクスチャを解凍し、本格的な画像に組み立てることだけです。 高原グループを開梱すると、次のようになります





ここでは、フィルターを使用せずに、ネイティブ形式で復元されたテクスチャをいくつか示します。





そして、これがアルファチャンネル付きの処理されたテクスチャです。





テクスチャのアトラスとそれに使用されるデータ構造を、創意工夫、16進エディタ、および少しの運に頼ってなお理解できる場合、これはテクスチャ復元アルゴリズムでは機能しません。 そして、ここでIlfakは不可欠なIDAデバッガーと同様に有用なHex-Raysデコンパイラーで救助に来ます。 デバッガでc3.exeを開きます。ほとんどの場合、Java(java)またはプラス(c ++)でプログラミングしているため、画像は決して虹ではありません。私にとっては、これは暗い森のようではなく、確かに密林です。





ここでは、asmをプレーンCの擬似コードに復元するIDAの機能が役立ちます。 F5キーを押すと、人間が読めるコードが用意されています。このコードは既に使用できます。





関数と変数、構造化された構造、そしておそらく目の肥えた読者は、上記のコードに規則性があることに気づいたので、読みやすくしましょう。 Nボタンを押して、関数の通常の名前を入力すると、コードがはるかに単純になります。





そして、しばらくすると(日、週、月など)、このようになります。 同意し、アルゴリズムを検索する方がはるかに便利になりました





Caesar IIIゲーム実行可能ファイルは、Visual C ++ 5.0コンパイラによってデバッグ情報を使用してコンパイルされました。これにより、アプリケーションロジックをより効率的に復元することもできます。 デバッガー、逆コンパイラー、および独自のグレーセルを使用して、アーカイブから画像を読み取る機能にアクセスできます。

たくさんのコード
int __cdecl fun_drawGraphic(signed int graphicId, int xOffset, int yOffset) { int result; // eax@2 LONG v4; // [sp+50h] [bp-8h]@43 drawGraphic_graphicId = graphicId; drawGraphic_xOffset = xOffset; drawGraphic_yOffset = yOffset; if ( graphicId <= 0 ) return 0; if ( graphicId >= 10000 ) return 0; drawGraphic_fileOffset = c3_sg2[graphicId].offset; if ( drawGraphic_fileOffset <= 0 ) return 0; LOWORD(drawGraphic_width) = c3_sg2[graphicId].width; LOWORD(drawGraphic_height) = c3_sg2[graphicId].height; drawGraphic_type = c3_sg2[graphicId].type; graphic_xOffset = xOffset; graphic_yOffset = yOffset; drawGraphic_visiblePixelsClipX = (signed __int16)drawGraphic_width; if ( c3_sg2[graphicId].extern_flag && (signed __int16)drawGraphic_width <= ddraw_width ) { strcpy(drawGraphic_555file, &c3sg2_bitmaps[200 * c3_sg2[graphicId].bitmap_id]); j_fun_changeFileExtensionTo(drawGraphic_555file, &extension_555[4 * graphics_format_id]); if ( !j_fun_readDataFromFilename( drawGraphic_555file, screen_buffer, c3_sg2[graphicId].data_length, c3_sg2[graphicId].offset - 1) ) { j_fun_changeFileExtensionTo(drawGraphic_555file, "555"); if ( !j_fun_readDataFromFilename( drawGraphic_555file, screen_buffer, c3_sg2[graphicId].data_length, c3_sg2[graphicId].offset - 1) ) return 0; if ( c3_sg2[graphicId].compr_flag ) j_fun_convertCompressedGraphicToSurfaceFormat(screen_buffer, c3_sg2[graphicId].data_length); else j_fun_convertGraphicToSurfaceFormat(screen_buffer, c3_sg2[graphicId].data_length); } j_fun_setGraphicXClipCode(); j_fun_setGraphicYClipCode(); if ( drawGraphic_clipYCode == 5 ) return 0; if ( drawGraphic_type ) { if ( drawGraphic_clipYCode == 5 ) return 0; drawGraphic_fileOffset = 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop; drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft; if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicUncompressedClipLeft((char *)screen_buffer + drawGraphic_fileOffset); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicUncompressedClipRight((char *)screen_buffer + drawGraphic_fileOffset); else j_fun_drawGraphicUncompressedClipY((char *)screen_buffer + drawGraphic_fileOffset); } } else { if ( c3_sg2[graphicId].compr_flag ) { if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicCompressedClipLeft((char *)screen_buffer); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicCompressedClipRight((char *)screen_buffer); else j_fun_drawGraphicCompressedFull((char *)screen_buffer); } } else { drawGraphic_fileOffset = 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop; drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft; if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicUncompressedClipLeft((char *)screen_buffer + drawGraphic_fileOffset); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicUncompressedClipRight((char *)screen_buffer + drawGraphic_fileOffset); else j_fun_drawGraphicUncompressedClipY((char *)screen_buffer + drawGraphic_fileOffset); } } } result = (signed __int16)drawGraphic_width; } else { if ( c3_sg2[graphicId].extern_flag ) { if ( window_id == 21 || window_id == 20 ) { drawGraphic_visiblePixelsClipX = fullscreenImage_width; drawGraphic_visiblePixelsClipY = fullscreenImage_height; drawGraphic_copyBytesInBufferForClipX = 2 * ((signed __int16)drawGraphic_width - drawGraphic_visiblePixelsClipX); drawGraphic_skipBytesInBufferForClipX = 2 * (ddraw_width - drawGraphic_visiblePixelsClipX); j_fun_drawGraphicUncompressedFull(&c3_555[2 * fullscreenImage_xOffset + 13000000] + 2 * (signed __int16)drawGraphic_width * fullscreenImage_yOffset); return drawGraphic_visiblePixelsClipX; } v4 = 2 * (signed __int16)drawGraphic_width * fullscreenImage_yOffset + 2 * fullscreenImage_xOffset; drawGraphic_visiblePixelsClipX = fullscreenImage_width; drawGraphic_visiblePixelsClipY = fullscreenImage_height; strcpy(drawGraphic_555file, &c3sg2_bitmaps[200 * c3_sg2[graphicId].bitmap_id]); j_fun_changeFileExtensionTo(drawGraphic_555file, &extension_555[4 * graphics_format_id]); if ( !j_fun_readUncompressedImageData( drawGraphic_555file, screen_buffer, 2 * drawGraphic_visiblePixelsClipX, drawGraphic_visiblePixelsClipY, v4) ) { j_fun_changeFileExtensionTo(drawGraphic_555file, "555"); if ( !j_fun_readUncompressedImageData( drawGraphic_555file, screen_buffer, 2 * drawGraphic_visiblePixelsClipX, drawGraphic_visiblePixelsClipY, v4) ) return 0; j_fun_convertGraphicToSurfaceFormat( screen_buffer, drawGraphic_visiblePixelsClipY * 2 * drawGraphic_visiblePixelsClipX); } drawGraphic_copyBytesInBufferForClipX = 0; drawGraphic_skipBytesInBufferForClipX = 0; j_fun_drawGraphicUncompressedFull((char *)screen_buffer); result = drawGraphic_visiblePixelsClipX; } else // internal { if ( (unsigned __int8)drawGraphic_type == 30 )// isometric { switch ( (signed __int16)drawGraphic_width ) { case 58: LOWORD(drawGraphic_height) = 30; break; case 26: LOWORD(drawGraphic_height) = 14; break; case 10: LOWORD(drawGraphic_height) = 6; break; default: if ( (signed __int16)drawGraphic_width == 118 ) return j_fun_drawBuildingFootprintSize2(); if ( (signed __int16)drawGraphic_width == 178 ) return j_fun_drawBuildingFootprintSize3(); if ( (signed __int16)drawGraphic_width == 238 ) return j_fun_drawBuildingFootprintSize4(); if ( (signed __int16)drawGraphic_width == 298 ) return j_fun_drawBuildingFootprintSize5(); break; } } j_fun_setGraphicXClipCode(); j_fun_setGraphicYClipCode(); if ( drawGraphic_clipYCode == 5 ) { result = 0; } else { if ( drawGraphic_type ) { if ( (unsigned __int8)drawGraphic_type == 30 ) { if ( drawGraphic_clipXCode == 1 ) { switch ( (signed __int16)drawGraphic_width ) { case 58: j_fun_drawBuildingFootprint_xClipRight(&c3_555[drawGraphic_fileOffset], drawGraphic_clipYCode); break; case 26: j_fun_drawBuildingFootprint_26px_xClipRight(); break; case 10: j_fun_drawBuildingFootprint_10px_xClipRight(); break; default: j_fun_drawGraphicUncompressedClipLeft(&c3_555[drawGraphic_fileOffset]); break; } } else { if ( drawGraphic_clipXCode == 2 ) { switch ( (signed __int16)drawGraphic_width ) { case 58: j_fun_drawBuildingFootprint_xClipLeft(&c3_555[drawGraphic_fileOffset], drawGraphic_clipYCode); break; case 26: j_fun_drawBuildingFootprint_26px_xClipLeft(); break; case 10: j_fun_drawBuildingFootprint_10px_xClipLeft(); break; default: j_fun_drawGraphicUncompressedClipRight(&c3_555[drawGraphic_fileOffset]); break; } } else { switch ( (signed __int16)drawGraphic_width ) { case 58: j_fun_drawBuildingFootprint_xFull(&c3_555[drawGraphic_fileOffset], drawGraphic_clipYCode); break; case 26: j_fun_drawBuildingFootprint_26px_xFull(); break; case 10: j_fun_drawBuildingFootprint_10px_xFull(); break; default: j_fun_drawGraphicUncompressedClipY(&c3_555[drawGraphic_fileOffset]); break; } } } } else { if ( (unsigned __int8)drawGraphic_type == 13 && drawGraphic_clipXCode ) { j_fun_drawImage_32x32((int *)&c3_555[drawGraphic_fileOffset]); } else { if ( (unsigned __int8)drawGraphic_type == 12 && drawGraphic_clipXCode ) { j_fun_drawImage_24x24((int *)&c3_555[drawGraphic_fileOffset]); } else { if ( (unsigned __int8)drawGraphic_type == 10 && drawGraphic_clipXCode ) { j_fun_drawImage_16x16((int *)&c3_555[drawGraphic_fileOffset]); } else { if ( (unsigned __int8)drawGraphic_type == 2 && drawGraphic_clipXCode ) { j_fun_drawGraphicType2(&c3_555[drawGraphic_fileOffset]); } else { if ( (unsigned __int8)drawGraphic_type == 20 ) { if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicLetterColoredClipLeft(&c3_555[drawGraphic_fileOffset]); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicLetterColoredClipRight(&c3_555[drawGraphic_fileOffset]); else j_fun_drawGraphicLetterColoredFull(&c3_555[drawGraphic_fileOffset]); } } else { drawGraphic_fileOffset += 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop; drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft; if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicUncompressedClipLeft(&c3_555[drawGraphic_fileOffset]); } else { if ( drawGraphic_clipXCode == 2 ) { j_fun_drawGraphicUncompressedClipRight(&c3_555[drawGraphic_fileOffset]); } else { if ( drawGraphic_clipYCode ) j_fun_drawGraphicUncompressedClipY(&c3_555[drawGraphic_fileOffset]); else j_fun_drawGraphicUncompressedFull(&c3_555[drawGraphic_fileOffset]); } } } } } } } } } else // type == 0 { if ( c3_sg2[graphicId].compr_flag ) { if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicCompressedClipLeft(&c3_555[drawGraphic_fileOffset]); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicCompressedClipRight(&c3_555[drawGraphic_fileOffset]); else j_fun_drawGraphicCompressedFull(&c3_555[drawGraphic_fileOffset]); } if ( drawGraphic_colorMask ) { if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicCompressedColorMaskClipLeft(&c3_555[drawGraphic_fileOffset], drawGraphic_colorMask); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicCompressedColorMaskClipRight(&c3_555[drawGraphic_fileOffset], drawGraphic_colorMask); else j_fun_drawGraphicCompressedColorMaskFull(&c3_555[drawGraphic_fileOffset], drawGraphic_colorMask); } } } else // not compressed { drawGraphic_fileOffset += 2 * (signed __int16)drawGraphic_width * drawGraphic_invisibleHeightClipTop; drawGraphic_fileOffset += 2 * drawGraphic_invisibleWidthClipLeft; if ( drawGraphic_clipXCode == 1 ) { j_fun_drawGraphicUncompressedClipLeft(&c3_555[drawGraphic_fileOffset]); } else { if ( drawGraphic_clipXCode == 2 ) j_fun_drawGraphicUncompressedClipRight(&c3_555[drawGraphic_fileOffset]); else j_fun_drawGraphicUncompressedClipY(&c3_555[drawGraphic_fileOffset]); } } } result = drawGraphic_visiblePixelsClipX; } } } return result; }
      
      







このコードに基づいて、ゲームで使用されるテクスチャを表示できるアプリケーションを構築できます。



趣味

ゲームのバックエンジニアリングに関する投稿が他の誰かのプログラムへのリンクで終わった場合は奇妙です)))ゲームのリソースを改造することに対する私の情熱は、いくつかのエラーを修正する多くの修正を書くことになり、今ではゲームの本格的なリメイクになりました

プロジェクトの目標や著作権についての考えを読むことにあまり興味がない場合は、ダウンロードセクションにアクセスして、元の記事にどう近づいたかを確認してください。



リメイクの目標は何ですか

+他の人に、Windowsだけでなく忘れられたゲームをプレイする機会を与えます。

+エミュレータなしでCaesar IIIをプレイし、タンバリンで踊り、Wineの下でのゲームの立ち上げに大騒ぎします。現在は800x600の高解像度です。

+テクスチャ、フォント、およびゲームの速度の品質を向上させます。

+開発を楽しんでください-私はゲーム、特に経済的なゲームをプレイするのが好きです。ゲームがバグである、クラッシュする、正しく動作しないときは本当に嫌いです。 私は自分のゲームを書くよりもリメイクをする方が簡単です。私はプログラムに非常に批判的で、不具合を取り除きバランスを最大限に調整しようとしているからです。 しかし、結果は常に予想より少し悪いため、おそらく独自のプロジェクトを作成するのに数倍の時間がかかります。

+最後に、私が幼少期に欠いていたネットワークゲームを追加します。

+タブレットで、渋滞に立っている野bar人を倒します-農場に寄付するよりもはるかに面白いことに同意します。

+例えば、ロシア語話者だけでなく、フランス語についても、翻訳は英語で行われました。



著作権の取り扱い

いくつかのオプションがあります:

1.あなたが望むものを採点し、行うことは私たちのやり方ではありません。私たちは文明的な人々です。原作者がリメイクに多大な時間を費やして、原作者が最後にそれを禁止するのは嫌です。

2.郵送で著作権者に手紙を書き、許可を求めます(口頭、リソースまたはブランドの使用許可、「紙上」など)。 さらに悪いことに、文明の作者、または権利所有者(現時点ではActivision)は、ゲームが利益を生まなくても、原則として最後まで保持します。 権利があります-その後、リメイクはありません。 ポイント。

3. トレントからダウンロードされ、 GOG.comで正直に購入されたオリジナルゲームを必要とするmodとしてゲームを配置します。たとえば、Corsix THは、テーマホスピタルのリメイクをリリースしてこれを行いました。 しかし、最も正当化された最も安全な方法は...



古いゲームは悪いことを意味しません。 古いゲームの多くは、ほこりを吹き飛ばしたり、きれいにしたり、グリースや接着剤を塗ったりすると...これらの現代のおもちゃは、多くの現代の工芸品のベルトに詰め込まれています。
ヴァディム・バラショフ



最後まで読んでくれてありがとう!



PS


リメイクの開発を手伝ってくれた人々に感謝します。

Bianca van Schaik(http://pecunia.nerdcamp.net/)、元のゲームのバックエンジニアリング

Gregoire Athanase(http://sourceforge.net/projects/opencaesar3/)、レンダリングおよび多くのアルゴリズムの著者

George Gaal(https://github.com/gecube/opencaesar3)セーブのバックエンジニアリング

他の多くのコミッター




UPD1。 このゲームのバックエンジニアリングの結果(exe + idb)に興味がある場合は、メールまたはPMで連絡することをお勧めします。このトピックは「灰色の法的領域」と呼ばれます。 ゲームに慣れるために、IDA 5.5 + Hex-Rays 1.01が使用されました。 Bianca van Schaik(http://caesar.biancavanschaik.nl/)の許可を得て投稿されたファイルと資料。



UPD2。 この投稿がなぜLinuxハブに入ったのか。 OllyDbg u IDAはWin7仮想マシン上で実行され、QtCreator 3.0.1 + cmake + gcc 4.8は開発に使用され、ゲームはネイティブにLinux用に作成されています。 Windowsでのアセンブリには、mingw-w64クロスコンパイラが使用され、MacOSXおよびHaikuでは、仮想マシンが使用されます。 Android用にビルドするには、libsdl-androidの環境を使用します。



All Articles