HN 日本語サマリー

← 一覧へ戻る
プログラミング

Fast Food Tycoon 2 のデコンパイル:世界の描画

Decompiling Fast Food Tycoon 2: Drawing the World (healeycodes.com)

15 pointsby healeycodes2 コメント

要約

この記事は、2001年のビジネスシミュレーションゲーム「Fast Food Tycoon 2」をデコンパイルし、その描画エンジンとライティングシステムを分析した記録です。GPT-6.1 Sol MediumとGhidraを使用し、ゲームの内部構造を解明することで、どのように都市やキャラクターが描画され、光の表現が実装されているかが詳細に解説されています。

全文翻訳

Fast Food Tycoon 2 のデコンパイル:世界の描画

ここ数日、私のお気に入りのコンピュータゲーム「Fast Food Tycoon 2」をデコンパイルしていました。この風変わりな2001年のビジネスシミュレーターの名前を聞いたことがある人は、ほとんどいないでしょう。しかし、私が子供だった2004年、モールにあった割引ゲームの中からこれを選び、それ以来私の心から離れることはありませんでした。

[画像: メモリ注釈付きのベルリン]

ゲームプレイを簡単に説明すると、プレイヤーは生きたアイソメトリックな都市でピザ帝国を築きます。レストランや倉庫を借り、スタッフを雇い、ピザをデザインし、サプライチェーンを管理します。顧客はあなたの食べ物、サービス、評判を評価します。ライバル企業は、広告、政治的影響力、組織犯罪を通じて競争相手となります。

私にとって、このゲームは常に時代を先取りしているように感じられ、驚くほどの世界の忠実度を持っていました。

デコンパイルするために、GPT-6.1 Sol Mediumを使用してコンピュータ上にエージェントループを設定しました。Ghidraを使用して fastfood2.exe の4,046個の関数を約9.5MBのC言語風テキスト(関数アドレス、フィールドオフセット、定数、呼び出し)にデコンパイルしました。

この生のコードはSQLiteにロードされ、大量のテキストを読み込むことなく関連関数を見つけられるようにするスクリプトでラップされました。

decomp_index.py grep    "0xe8\) = "  # フィールド書き込みを検索
decomp_index.py callers 0x4ed360     # 呼び出し元を検索
decomp_index.py string  "fnt"        # フォント参照を検索

Windowsには、プロセスメモリを読み取るAPI ReadProcessMemory があります。これにより、エージェントは実行中のゲームを検査し、画面に表示される値とメモリ値を比較することができました。例えば、現金、カメラの位置、または選択された建物のメモリアドレスを見つけることができます。そして、デコンパイルされたコードに対して、それらの値を変更した関数をクエリしました。

ゲームを制御するために、Windowsメッセージハンドラーがパッチされ、ディスク上の実行可能ファイルを変更することなく、ゲーム自身のUIスレッドで実行されるコマンドを送信できるようになりました。初期のブレークスルーは、クローラーを使用してゲームのインターフェースをクリックし、残りの未知のコントロールを発見したことでした。

この時点で、私は好奇心でエージェントを誘導し、私たちは掘り下げていきました。

オーバーワールド

都市は、絵画と数百のスプライトで構成されています。以下は、ベルリンの12時30分の様子です。

[画像: 12時30分のベルリン]

この完全にレンダリングされたシーンの下には、マップファイル ber.tga があり、通りや区画(そしてブランデンブルク門の影)はありますが、建物や人々、車はありません。

[画像: ベルリンの背景絵画]

オーバーワールドを描画するために、FFT2のレンダラーはシーンをメモリ内のバッファに描画します。まず、人が移動した場所やカメラ位置が変更された場所など、再描画が必要な画面領域を特定します。これらの領域の背景を復元し、その上にシーンを描画します。各ピクセルは16ビットのカラー値と個別のライティングバイトを保持します。

dirty_areas = find_areas_that_changed()
draw_background(dirty_areas)
for draw_pass in scene_draw_passes:
    for cell in diagonal_map_order:
        draw_parts_for_this_pass(draw_pass, cell, dirty_areas)

レンダラーは、どのオブジェクトが他のオブジェクトを覆うかを制御するために、いくつかのパスを実行します。マップセルを対角線(またはアイソメトリックに)訪問します。隠れた人々が見えるように(壁越しに輪郭が見えるように)、レンダラーはオブジェクトを描画する前に一時的なピクセル値でその形状をマークします。後続のパスでは、隠れた体の一部に黄色い輪郭、隠れた思考アイコンに白い輪郭を描画し、最後に表示される部分を塗りつぶします。

ここに完全な再描画があります。

[動画]

最後に、レンダラーは各ピクセルの色とそのライティング値を組み合わせて、最終的な16ビット表示バッファに書き込みます。この変換は、繰り返しディザパターンを適用して中間色を描画します。

mark_shape_with_temporary_value()
draw_covering_objects()
outline_covered_parts(outline_colour)
fill_parts_where_temporary_value_remains()

レンダラーの効率を示す別のクールなグラフィックを生成しました。過去0.6秒間に変更されたピクセル(車、歩行者、揺れる木のてっぺん、点滅する看板)がハイライトされています。ダーティリージョンシステムが再描画が必要なものを制限するため、画像のほとんどは変更されていません。

[画像: 0.6秒間で変更されたピクセル]

ライティング

昼光は、25個の毎時輝度値のテーブルに従います。夜間は70、夜明け頃に急上昇して日中は251になり、夕暮れ時に低下します。

ランプはこの光レベルに反応します。それらの輝度しきい値はオブジェクトIDによってわずかに異なり、小さなグループでオンになります。それらの光のプールは少なくとも160に保たれるため、夜間でも舗装のパッチが明るく保たれます。

light = interpolate_hourly_table(clock, brightness_setting)
for lamp in lamps:
    show_glow = light <= 160 + (lamp.id & 7)
    draw_lamp(lamp, glow=show_glow)
    draw_light_pool(lamp, light=max(light, 160))

id & 7 は8つの可能なしきい値オフセット(0-7)を与えます。FFT2のデフォルト輝度では、標準ランプは約18:25–18:30にオンになり、約05:35–05:40にオフになります。他のオブジェクトファミリーはより低いしきい値を使用するため、都市が暗くなるにつれてそれらの輝度が遅れて表示されます。

これらすべてが、非常に満足のいく昼夜サイクルの感覚をサポートしています。

各ピザ屋のステージには独自のライトマスクがあり、例えば h_pizz_a1.m.spr に格納されています。このマスク内の表示される各ピクセルは輝度値を保持します。レンダラーはその値を、同じ位置にある既存の光と比較し、高い方を保持します。したがって、建物の残りの部分が暗くなっても、窓と看板は明るいままです!

[動画]

別のファイル h_pizz_a1.l.spr は、夜間に使用される窓の色を提供します。レンダラーはそれらの色を描画し、次にライトマスクを適用します(前述のように、ピクセルが画面に描画される最終ステップまで、色と光は別々に保たれます)。

[動画]

これらのライトカーブ値は、輝度設定によって変更される可能性があります。これは次のように機能します。

light = base + (251 − base) * brightness

キャスト

動くものはすべて、スプライトシートと名前付きアニメーションシーケンスである .ani ファイルに格納されています。遭遇した内部テキストのほとんどはドイツ語(開発者の言語)で書かれていました。

ここに、オフィスワーカーがレストランで食事をするフレームごとの内訳があります。

[動画]

FFT2は2001年のピクセルベースのアイソメトリックゲームであるため、すべてのアニメーションは事前に描画されています。

[動画]

「ウォーカーアップデート」ロジックは、次のノードの位置と現在の位置を比較し、2つの記号から方向アニメーションを選択します:(−,+) は1、(0,+) は2、(+,+) は3などです。

名前付きアニメーションシーケンスは、スプライトがフレームごとに移動する距離も格納します。例えば、ウェイトレスの gehen4 はフレームごとに (+1.25, 0) 移動し、gehen3 は (+0.884, +0.442) 移動します。ティックは、足と地面が同期するように、フレームの進行ごとにこのベクトルを追加します。

マップには、歩行者用の舗装ノードと車両用の道路ノードがあらかじめ定義されています。

[動画]

使い走りの少年は、観光客(16.2px/秒)や有名人(13.8px/秒)と比較して、毎秒27ピクセルで最も速い歩行者です。

ブランチの内部

レストランビューはオーバーワールドと同じレンダリングプロセスを使用し、フラットな画像をバッファに描画し、色と光を別々に格納しますが、屋内光は時間に関係なく248に保たれます(外の昼光は251に達します)。

[動画]

このビューは、キャラクター用に別個の大きなアートワークを使用し、特別な描画順序を使用して人々が家具に座っているように配置します。椅子の背もたれがカメラに面している場合、レンダラーはメインの椅子画像、次にキャラクター、そして椅子の背もたれ(またはカメラの視点からの椅子の「前面」)を描画します。

[画像: 椅子、座っている顧客、次に椅子の前面]

テーブルの上のピザには特別なスプライトがあります。誰もが同じ食事パターンを持っていることをアニメーターが作ったことは許します。

[画像: テーブルのピザの9フレーム]

調査中に多くの奇妙な点に出くわしましたが、お気に入りをいくつか挙げます。

人々は日々の習慣を維持します。市民が作成されると、スケジュールが割り当てられます。これらのスケジュールは毎日リセットされないため、午後に特定の映画館に割り当てられたオフィスワーカーはそこに繰り返し戻ります。おそらく、都市の「リアルな感覚」に貢献しています。

これらのスケジュールにはステレオタイプが含まれています。

  • ゴスは午前9時まで家にいます。
  • 学生は深夜から午前3時まで40%のパブ割り当てがあります。
  • 大物は午後6時から午後9時まで70%のオペラ割り当てがあります。

ゲームは自身を「Pizza3」と呼んでいます。都市設定ファイル Init-File für Pizza3-Stadt で見られるように。

そして最後に、子供の頃の謎を解くことができました。このゲームはどうやって私のピザ作りの腕前を測定し、スコアリングしているのでしょうか?この知識をアンロックしたので、今レビューしましょう。

[動画]

ゲームはピザを24の領域に分割して、トッピングの広がり具合をスコアリングし、反対側にある一致するピースを探し、どの材料が互いに合うか(そして異なる顧客グループが異なる評価を得る)をチェックします。

材料の重みに基づいた盗作チェック(!)さえあります。したがって、競合他社のピザを並べ替えても(私が以前試した戦略です)、それをだますことはできません。コピーされたレシピは「オリジナルではない」とマークされ、ヤッピー、ゴス、観光客には+30、大物には+5の味ポイントを失います。

これだけの詳細がありながら、ゲーム内でのガイダンスはほとんどありません。覚えている限り、引っ越し中に紛失しましたが、FFT2は100ページほどのマニュアルと共にリリースされました。これは、ゲームが本質的に挑戦的であることが許されていた時代でした!

クールな注:この投稿のために生成した画像とビデオは、FFT2のデコンパイルされたフォントの一部を使用しました。また、ヨーロッパ名であるため、このゲームをPizza Connection 2と呼んでいるため、一部のコンテンツにはまだ「PC2」が表示されています!