ImHex を使った未知のファイル形式のリバースエンジニアリング

2026/08/31 21:01

ImHex を使った未知のファイル形式のリバースエンジニアリング

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

ここで詳述される主な成就是不動の ImHex 解析ツールを用いて、FEZ の独自バイナリセーブファイル形式を完全な構造定義へと逆工学するに至ったことである。JetBrains Rider を用いてゲームの .NET コンポーネントをデコンパイルすることで、研究者は

EasyStorage
ライブラリ内部にある特定のロジック、特にデータシリアライゼーションを担当する
PCKsaveDevice
コンポーネントを特定した。このコンポーネントは、Windows の FILETIME タイムスタンプとシリアライズされたゲームデータを含まれる 4096 バイトのバッファー内で動作する。このプロセスには、ImHex 内にカスタムのパターンを作成して複雑な内部レイアウト(7 ビット符号化文字列、
OneTimeTutorials
のようなキー値ペアのリスト、
LevelSaveData
のようなネスト構造、
ActorType
のような列挙体など)をマッピングする作業が含まれた。注目すべきは、FEZ のセーブファイルが OS 固有のパスに格納されながら暗号化も標準的なマジックヘッダーも含まず、今や完全にデコード可能になった点である。ImHex で設定された後、ユーザーは強調表示された Hex View を通じて生データを閲覧し、Pattern Data View を通じて編集可能な値を変更することができる。その結果、プレイヤーは公式のゲーム内ツールに依存せずにセーブファイルを独自に編集する能力を得る一方で、開発者はこのオープンな定義を用いて安全にゲーム状態を分析したり、バックアップユーティリティを作成したりできるようになる。なお、著者は秘密や終盤コンテンツに関する重いス ポイラーがあるため、FEZ をプレイしてから本文を読むことを推奨していることに注意されたい。

本文

『FEZ』のセーブファイル逆エンジニアリング:ImHex パターンによる完全な解析ガイド

序論

長年、ディスコード上で特定のファイル形式の逆エンジニアリング(リバースエンジニアリング)を求めて支援を求める声を多く受けてきました。通常は「デコンパイルされたソースコードを見て遡上する」といった一般的なアドバイスしか与えられませんでした。本記事では、ゲーム『FEZ』のカスタムバイナリセーブファイルを題材に、数年かけて開発を進めてきたオープンソースヘキエディタImHexのパターン言語(Pattern Language)を用いた完全な解析プロセスを紹介します。

  • ImHex の特徴: 無料のオープンソースソフトウェア。Windows/macOS/Linux またはブラウザ(ImHex Web)から利用可能。
  • バージョンについて: 記事で使用される一部機能は正式リリース版より**ナイトリービルド(Nightly build)**のみ対応しています。v1.38.1 以下でお困りの場合は、最新版のビルドへのアップgrade を推奨します。

はじめに

スロワー警告

  • 注意: 『FEZ』は 2012 年リリースのため体験済みの方のみ閲覧推奨です。
  • リスク: 本文中に含まれるコードの一部には、ゲーム内の**スロイーヤー(重要ネタバレ)**が含まれています。未プレイの方は spoilers を避けたい場合は、記事の末尾から逆参照して「デコンパイル」および「まとめ」のセクションをご参照ください。

ファイルの入手方法

  1. Steam から最新版を入手: 2016 年 12 月 2 日リリースのバージョンを使用しました。
  2. セーブファイルの場所:
    • Linux:
      /home/werwolv/.local/share/FEZ/SaveSlot2
    • Windows: ユーザー設定フォルダ内の異なる場所(Steam の「ローカルファイルの参照」から確認推奨)。
  3. ImHex での初期確認:
    • ファイルを開くと、ヘキサデシマール表示と ASCII 文字列が見えます。
    • DOT_LOCKED_D
      DOT_NUT_NV_A
      などの明確な文字列が含まれており、暗号化も圧縮も行われていないことがわかります。
    • しかしファイルマジック(先頭識別子)がないため、ImHex は標準形式として直接認識しません。

ゲームのデコンパイル

逆エンジニアリングの第一歩は、ファイルを生成するソースコードを見つけることです。

正しいファイルの見つけ方

  • Steam の「ローカルファイルの参照」:
    • Steam ページのギアアイコン → 「管理」→「ローカルファイルの参照」。
    • ゲームの実行バイナリ(
      .exe
      )と依存ライブラリ(
      .dll
      )へのパスへ到達します。
  • ターゲットとなる DLL:
    • 以下のファイルをアセンブリエクスプローラーで表示します。
      • FEZ.exe
      • FezEngine.dll
      • Common.dll
      • ContentSerialization.dll
      • EasyStorage.dll
        重要: セーブ処理の主要部分が含まれているため)

正しい関数の見つけ方

  • 対象クラス:
    EasyStorage -> PCSaveDevice
  • 保存ロジックの特定:
    • コンストラクターでファイル名
      "SaveSlot" + (object) index
      が構築されるのを確認。
    • BinaryWriter
      を用いたデータ書き込み関数
      Save
      を発見。

生成コード例(C#)

public virtual bool Save(string fileName, SaveAction saveAction){  
    // ...  
    byte[] buffer = new byte[40960 /*0xA000*/];  
    using (MemoryStream output = new MemoryStream(buffer))  {    
        using (BinaryWriter writer = new BinaryWriter((Stream) output))    
        {      
            // Windows FILETIME タイムスタンプ(1601 年からの経過時間)の書き込み
            writer.Write(DateTime.Now.ToFileTime());      
            
            saveAction(writer);      
            
            // 固定サイズ(0xA000 バイト)へのパディング処理
            if (output.Length < 40960L /*0xA000*/)      
            {        
                long length = 40960L /*0xA000*/ - output.Length;        
                writer.Write(new byte[length]);      
            }      
        }  
    }  
}

ImHex パターンの作成

始まりの小さな一歩

  1. 構造体の定義:
    @
    プリフィックスを用いて、パターンをファイルアドレスに配置します。
  2. 初期実装: ファイルヘッダー(FILETIME タイムスタンプ)を読み取るための型を追加します。
struct FezSaveFile {  
    long fileTime; // Windows FILETIME (s64)
};
FezSaveFile saveFile @ 0x00;

標準ライブラリの活用

  • ImHex の標準ライブラリには
    type.time
    が存在します。これを使用することで、生の 64 ビット数ではなく人間が読みやすい時刻値として自動変換されます。
    import type.time;
    
    struct FezSaveFile {  
        type::FILETIME fileTime;
    };
    FezSaveFile saveFile @ 0x00;
    

[[fixed_size]]
属性の追加

  • ソースコード(
    Save()
    関数)から確認できる通り、ファイルは常に
    0xA000
    (40960) バイト
    で固定されます。
  • ImHex パターンにはサイズを強制する属性が存在します。
    struct FezSaveFile {  
        type::FILETIME fileTime;
    }; // 実際の実装では固定サイズ属性が付与される場合があるが、ここでは簡略化して記述
    

実際のセーブデータ(Deep Dive)

DoSave()
関数の分析

  • セーブ処理の中核となるのは
    saveAction
    パラメータに渡されたラムダ式です。
  • JetBrains Riderなどでの「Ctrl クリック」により定義元 (
    GameStateManager.cs
    ) にジャンプし、以下のロジックを追跡します。
private void SaveInternal(bool ngpBackup){  
    this.ActiveSaveDevice.Save(      
        "SaveSlot" + (object) this.SaveSlot,      
        new SaveAction(this.DoSave)    
    );  
}
  • 実際のデータダンプは
    DoSave()
    SaveFileOperations.Write()
    で処理されます。

データフィールドの特定

生成されたバイナリを解析するために、C# コード上で定義されているフィールドと型を確認します。

ImHex パターン定義C# ソースからの参照備考
type::FILETIME
DateTime.Now.ToFileTime()
ファイル作成時刻(パディング用)
long version
固定値
6L
バージョンチェック
long creationTime
ゲーム内コンテンツ作成時刻
bool
系フィールド
HasFPView
,
IsNewGamePlus
など
各フラグの有無

アサートによる互換性検証

  • 古いバージョンのセーブファイルを読み込もうとするとエラーになります。パターン内に型定義内でアサートを行うことで、サポートされているバージョンのみを解析できる仕様にします。
import std.sys; // sys モジュールから assert をインポート

struct FezSaveFile {  
    type::FILETIME fileTime;  
    long version;  
    // 非対応のファイルは読み込めないようにチェック
    std::assert(version == 6, "Unsupported Save File Version. Only Version 6 is supported");
    long creationTime;  
    bool finished32;  
    bool finished64;  
    bool hasFpView;  
    bool hasStereo3d;  
    bool canNewGamePlus;  
    bool isNewGamePlus;
};

オブジェクトと文字列の解析

セーブファイルには、キートップ(Key-Value ペア)や文字列が含まれます。これらは可変長であるため複雑な処理が必要です。

キー・バリューペア (
KeyValuePair
)

  • C# コード:
    w.WriteObject(oneTimeTutorial.Key); w.Write(oneTimeTutorial.Value);
  • ImHex では、存在するかどうかのフラグ (
    isValid
    ) と、有効なら値を入れるという構造を定義します。
struct Object<T> {  
    bool isValid; // オブジェクトが存在するか
    if (isValid)    
        T value;  // 存在する場合にのみ値を配置
};

文字列の符号化 (
String
)

  • 実装:
    Write7BitEncodedInt
    を使用して、文字列の長さを可変長符号化(7 ビットエンコーディング)で書き出しています。
  • 仕組み: 各バイトの最上位ビット(MSB)が「次のバイトがあるか」を示します。

ImHex で実装する 7 ビット符号化整数

struct SevenBitEncodedIntByte {  
    u8 byte;  
    if ((byte & 0x80) == 0x00)    
        break; // MSB が 0 の場合は最後のバイト
};

struct SevenBitEncodedInt {  
    SevenBitEncodedIntByte bytes[while(true)]; // ループで読み取り続ける
} 
[[format("transformSevenBitEncodedInt"), transform("transformSevenBitEncodedInt")]];

fn transformSevenBitEncodedInt(ref auto encodedInt) {  
    u64 result = 0;  
    for (u32 i = 0, i < std::core::member_count(encodedInt.bytes), i += 1) {      
        // ビット操作で値を再構築
        result |= (encodedInt.bytes[i].byte & 0x7F) << (i * 7);  
    }  
    return result;}

最終的な文字列型

struct String {  
    SevenBitEncodedInt size; // 可変長の長さプレフィックス
    char string[size];      // 実際に読み取った文字数分の配列
} 
[[format("formatString")]];

fn formatString(ref auto string) {  
    return string.string;}

リスト(List)の定義

C# の

Count
プロパティと
foreach
ループ構造を考慮し、リスト型を定義します。

struct List<T> {  
    int count;     // アイテム数(int 32 ビット)
    T items[count]; // 宣言された数のアイテムを格納
};

struct KeyValuePair<Key, Value> {  
    Key key;  
    Value value;}

複合構造の構築

最終的なセーブファイル構造は、ネストされたリストとペアで表現されます。

struct FezSaveFile {  
    // ... (前段の定義) ...  
    
    // OneTimeTutorials の解析
    List<KeyValuePair<
        Object<String>,      // キー: 文字列(チュートリアル名)
        bool                 // 値: 真偽値(チュートリアル完了)
    > > oneTimeTutorials;

    // World (レベルデータ) の解析
    List<KeyValuePair<Object<String>, LevelSaveData>> world;
    
    // ... (他のフィールド) ...
};

エンューム(列挙型)の処理

ActorType
などの列挙型は、整数値として保存されますが、名前を持たせると解析しやすくなります。

  • C# コード:
    w.Write((int) artifact);
  • ImHex 対応: エンューム定義をそのままコピー&ペースト可能。
public enum ActorType : int {  
    None,  Ladder,  Bouncer,  Sign,  GoldenCube,  // ...
};
// ImHex ではこの型が自動変換され、数値ではなく名前として表示されます

より多くのサブタイプ(ネスト構造)

解析はさらに深く続きます。

World
キーのペアリストの中に
LevelSaveData
という新しい構造体が格納されています。

struct LevelSaveData {  
    // ... (レベル固有のデータ) ...
};

これにより、形式の残りを自分でデコードする必要があるものすべてがカバーされます。試してみてください!


我々の労働の実果(成果)

パターンを完成させたところ、「ヘキエディタビュー」ではすべてのバイトが色分けされてハイライト表示され、**「パターンデータビュー」**では値の意味を確認したり、ダブルクリックして編集したりできます。

  • [My Pattern]: 完全な ImHex パターンファイルをダウンロード可能(※本文中のリンク参照)。
    • デコピエートされたセーブファイルの解析結果が視覚的に理解可能です。

まとめ

本記事を通じて、バイナリファイル形式の逆エンジニアリングの基本フローを学ぶことができます。すべてのプログラムがこのほどスムーズにデコンパイルできるわけではありませんが、以下の 4 ステップは普遍的なアプローチです。

  1. ファイル形式の確認:
    • ImHex のマジック検出や
      binwalk
      を利用し、既存の形式かどうかを確認します。
  2. 生成コードの特定:
    • ソースコードがある場合は該当箇所を検索します。ない場合はデコンパイル(JetBrains Rider, Ghidra, IDA など)を行います。
    • 検索キーワード: ファイル入出力ライブラリ、ファイル名文字列など。
  3. データの構成要素の抽出:
    • 整数、ブール値、文字列、構造体など、ファイルに格納されるデータ型を特定します。
  4. パターンの作成と検証:
    • ImHex パターンで発見を文書化し、互換性を検証します。一度デコードするだけでなく、プロセスの各段階で理解を深めることができます。

ImHex やパターン言語に関するご質問は、ImHex Discord サーバー、DM

@werwolv
、またはメール宛先にてお気軽にお問い合わせください。

同じ日のほかのニュース

一覧に戻る →

2026/09/03 0:12

Gemini 3.8 Flash および Gemini 3.8 Flash Cyber

## Japanese Translation: 現在のサマリーは物語的な流れに優れていますが、キーポイントリストに含まれる具体的な定量基準が不足しています。以下の改善版では、これらの特定のデータポイントを統合しつつ、読みやすさを維持しています: ## 改善されたサマリー: Google は Gemini 3.8 を導入し、**Gemini 3.8 Flash** と専門的な **Gemini 3.8 Flash Cyber** の 2 つのバリエーションを特徴としています。標準的な **Flash** バリエーションは、100 万入力トークンあたり$0.75、100 万出力トークンあたり$3.75(以前の価格と同様)で提供されており、推論能力において著しい飛躍を実現し、プロンプト注入に対する堅牢性を備えた HLE-Verified で 54.9% のスコアを達成しました。複雑なエンジニアリングタスク(DeepSWE)、法律・金融ベンチマークにおいて、より大きな最前線モデルを上回る性能を示しました。 **Flash Cyber** バリエーションは、新しい Fairwind プログラムを通じて認定されたセキュリティ専門家のみが利用でき、標準モデルに比べて許可された防衛者に対してより寛容な緩和措置を備えています。このバージョンは脆弱性発見においてかつてないスピードを発揮し、例えば重要な基盤的な欠陥を検出するのに通常必要だった数ヶ月に対して 2 時間未満で特定しました。また、Wiz や Collinear などが実施した内部ペネトレーションテストベンチマークにおいて、Flash Cyber は 20 のプログラミング言語にわたり 70% 以上の成功率を達成し、コストも大幅に低下(2.3 倍〜5.2 倍の削減)しました。さらに、Google のクラウド脆弱性研究チームは、Chrome の脆弱性に対してベストクラスの商用モデルよりも 2.6 倍多くの正しいパッチを生産したと報告しており、これにより効率的な脅威検出における新しい業界標準としての地位を確立しました。

2026/09/03 7:36

Launch HN: ロナン・エックス(YC S26)– 個別最適化されたペプチドとGLP-1

## Japanese Translation: 本サービスは、GLP-1 減量治療を転換させ、硬直した標準プロトコルを、患者それぞれの唯一無二の医療歴および耐容性に合わせた、極めて個別化された医師主導のケア計画で置き換えます。吐き気や疲労などの副作用に対応せずにはいられない固定的なラベルアプローチとは異なり、本モデルは必要に応じてターゲッティングされたサポートを加え、耐容性と一貫性を向上させます。重要な安全機能として厳格な「失敗時に閉じる(fail-closed)」検証プロセスがあります:患者が選択した薬局(例:Elite Care Pharmacy LLC または別の希望薬局)の認可を受けた薬剤師は、調剤薬をリリースする前に、すべての詳細が特定の患者チャートと一致することを確認し、棚から決して取られないロット追跡可能な成分を使用します。これにより投与前の精密性が確保され、有効期限(beyond-use date)の制限とともに、リリース時に薬剤師の署名が含まれます。このプロセスは医師主導の権限チェーンに従い—医師が処方し、認可された薬剤師が検証してリリースする—with 何人も医師の判断を上回ることはできません。すべての工程には「失敗時に閉じる」ゲートが組み込まれており、検証が失敗した場合(例:処方がチャートと一致しないか、ロットが追跡できない場合)は注文が停止し、何も出荷されません。品質保証は完全に行間ごとのチェックに依存し、必要に応じて冷鏈要件を満たす温度感知包装、配送、トラッキングを使用します。調剤製剤は通常、現金払いによるブランド名のリスト価格よりもコストが低い傾向がありますが、実際のコストは計画、薬局、州によって異なります。これにより、ブランド医薬品と比較して長期的な持続可能性が向上します。なお、調剤薬は FDA の直接承認の枠外で運営され、ブランド版からの臨床試験データは直接的に適用できないことに注意が必要です。将来のリフィルは決して自動的ではなく、継続的な医師によるレビューを必要とするため、治療計画は患者の体が初期週間にわたって安定化するにつれて適応させることができます。本サービスは HIPAA 準拠とエンドツーエンド暗号化を維持し、ケア全体を通じて高水準のプライバシーを確保します。究極的には、このアプローチは業界を「ワンサイズフィッツオール」なラベルから、安価さ、安全性、品質保証、医療監督が個別化された検証と継続的な医師監督を通じてバランスされている厳格なシステムへとシフトさせます。

2026/09/03 4:49

Fable 5.1 ワールドモデリング

## Japanese Translation: 「Worlds via code」プロジェクトは、高価なゲームエンジンや手動のモデリングを必要とせず、標準的な Web ブラウザ内で直接リアルな街並みの再構築を自律的に行うことで、3D コンテンツの制作方法を革命化した。自律エージェントによる高度なパイプラインを用いて、OpenStreetMap の幾何学データ、USGS の標高データ、公共交通仕様、店舗調査などのオープンデータを取り込み、ゼロから高忠実度の環境を構築する。 主なデモはサンフランシスコのユニオンスクエアの再構築であり、Powell、Geary、Post、Stockton 通りを覆う実地形上での航空撮影式ウォークスルー、実際のストリートグリッドを含んでいる。この環境には、75 の手作業で作成されたファサードバリアントを含む 453 の OSM フットプリントにわたる 129 つの特定された店舗、作動する信号機、ケーブルカー、そして一日・日没・夜の完全なサイクルが含まれる。ライフシミュレーションでは、Powell ストリートケーブルカーを含む 1,398 ノードのナビゲーショングラフをnavigate し、220 の歩行者、および 109 の車両が移動し、シーンを生気に満ちたものとしている。Apple Union Square と Nintendo SAN FRANCISCO という 2 つの探索可能な内部空間には、それぞれ 23 つのインタラクティブなオブジェクトが用意されている。 アセット生成は Blender-as-a-library スクリプトを使用してオフラインで行われ、ファサード、家具、車両、植生、設備、歩行者部品などの最適化された GLB パッケージを出力する。実行時においては、Three.js 純粋なアプリケーションが JSON の仕様に従って地形、道路、ファサード、小道具、群衆、交通を組み合わせ、プロプライエタリソフトウェアは必要としない。品質保証では、Playwright を使用して固定されたカメラ位置一致のビューポイント(合計 34)でアプリを操作し、これらのビューのスクリーンショットを取得し、フリーライセンスの photographs と diff することで、9 人の独立したレビュアーからの入力に基づき、147 の比較シートにわたって検証を行う。生成されたプロジェクトは `npm run dev` を通じて実行可能な単純な Three.js アプリとして配布され、コードは MIT ライセンス下、幾何学は ODbL および USGS のパブリックドメインデータから派生し、ブランド名・ロゴは実在の企業がその正しい場所にあることを識別するために使用される。このフレームワークは、最終的に、新しい地理データを投入するだけで、Web ブラウザを持つ誰でも高忠実度の仮想世界を作成することを可能にする。

ImHex を使った未知のファイル形式のリバースエンジニアリング | そっか~ニュース