雑用担当の備忘録

技術的な話のみになります。翻訳データそのものはありません。 カテゴリ>その他>注意事項を必ず参照してください

2015年11月

続リパック

注意:
name_offset が word なのでやはり簡単にオバーフローします。適当な実装では、やはり駄目ですなー
なので names が 0x10000 をオバーした場合の処理 name_block をまたぐ処理をきちんと実装する必要があります。

また Entry.number についてスクリプトとテキストは 0 でそれ以外は entry_size と同じと書きましたが、テクスチャー関連 d3dtx, font, particle はテクスチャーのオフセットを格納する。

テクスチャーのオフセットの算出はファイルのヘッダーが
magic: u32,
headder_size: u64,
data_size: u32,
となっているので ファイルサイズ - data_size でオフセットを算出できる。

リパック

このゲームはリパックが必要です。
すべての情報を生成できるので元ファイルのヘッダーを利用したりする必要が無いので割と簡単です。

アンパックの時の構造と同じですが、Entry.hash はファイル名の crc64 です。
struct Arch {
    magic: u32,
    name_size: u32,
    entry_count: u32,
    entries: Vec<Entry>,
    names: Vec<u8>,        // name_size
}
struct Entry {
    hash: u64,                    // ファイル名の crc64
    offset: u64,
    entry_size: u32,
    number: u32,                // テキストとスクリプトは 0、それ以外は entry_size と同じ
    name_block: u16,
    name_offset: u16,
}
余談:
crc64 とは言っても crc は派生形が多いし実際これも派生形です。
テーブルのシード値を検索しようとしましたが GPU を使って計算しても約5年…
しょうがないので実装はテーブル値埋め込み、実際の計算部分も若干違います。

本題に戻って
entries は hash でソートした順番(昇順)で格納
ファイルデータは name_offset でソートした順番(昇順)で格納

name_offset でソートはファイル名でソートした順番と同じ事になります。
ファイル名でソートした順に names を格納するため。

ファイルデータをすべて格納した後、ファイルサイズは 0x10000 で割り切れるように 0 でパディングします。

分かりにくいので実装の概略
パックするフォルダを検索してファイル名を取得する。この時ファイル名でソート。
ファイル名から names を生成しファイルサイズを取得する。
この情報から Entry が決定するのでファイル数分=エントリー分生成する。
Entry を hash でソートしヘッダー及び Entry と names を出力。
Entry を name_offset でソートして対象のファイルデータを読み込み出力ファイルに書き出す。
書きだした後パディング処理を行う。

重要:
某ツールではスクリプトを blowfish 復号した状態でリパックするようになっているのですが、MCSMのバージョンからはこれでは起動しなくなるので、暗号化されたままアンパックしたスクリプトをパックするようにしてください。

パックした状態ではまだ動きません。ヘッダー付加が必要
magic: u32,
pack_size: u64,
を付加します。

ここでファイルの説明
Nxxx: 無圧縮 上記のパックした状態のもの
Zxxx: Nxxx を deflate 圧縮したもの
Exxx: Zxxx を blowfish(カスタム) で暗号化したもの

Exxx がゲームのファイルなんですがここまで復元する必要はありません。
無圧縮状態でも動きます。

Zxxx は deflate 圧縮で元のバイナリと同じにはならないのですが miniz の level = 9 で圧縮した状態のもので起動は確認済みです。


前回のローカライズと今回のリパックでようやく豆腐が表示されるようになります。

ローカライズデータ

文字コードは UTF-8N

struct Locale {
    magic_no: u32,
    text_segment_size: u32,
    index_segment_size: u32,
    num0: u32,
    version: u32,
    headder: [u8; 0x78],
    num1: u32,
    num2: u32,
    num3: u32,
    num4: u32,
    entry_size: u32,
    entry_count: u32,
    entries: Vec<Entry>,
    text_segment_footer: [u8; text_segment_size - entry_size - 0x10],
    indexes: Vec<Index>,
}
struct Entry {
    headder: [u8; 0x38],
    text_length2: u32,        // text_length + 28
    num1: u32,
    num2: u32,
    text_length3: u32,        // text_length + 8
    text_length: u32,
    text_string: String,        // ローカライズデータ
    num3: u32,
    language_code: u32,   // english = 1
    num4: u32,
    num5: u32,
    num6: u32,
}
struct Index {
    index_length: u32,
    index_string: String,
    anm_length: u32,
    anm_string: String,      // anm_length == 0 の場合は格納しない
    wav_length: u32,
    wav_string: String,      // wav_length == 0 の場合は格納しない
}



前回の記事 のコメントより補足が必要みたいなので

UE4でフォントを作成した後、パッケージを作成する必要があります。

フォントをインポートした時点で作業フォルダに uasset ファイルが作成されます。
このファイルはあくまでも中間ファイルです。これをリリース状態にするにはパッケージ化が必要です。ビルドとかデプロイとかコンパイルに相当するものと考えてください。
UE4_pkg01
パッケージ化は上の画像を参考に
ただこのままだと生成したファイルが pak ファイルにまとめられてしまいますので
パッケージング設定
UE4_pkg02
Use Pak File のチェックを外してください。この状態だと pak にまとめないのでアンパックの必要性が無くなります。

ここからは余談
中間ファイルの中身
UE4_pkg03
ポイントは FString の None の後の dword (0x25 からの4byte) と緑で囲っているバージョン文字列。dword は PackageFlags になるんですが中間ファイルは PackageFlags = 0x00000000
パッケージ後は
UE4_pkg04
PackageFlags = 0x80000000 となっています。またバージョン文字列もありません。
中間ファイルの PackageFlags を 0x80000000 に置き換えると実行ファイルから uasset が読み込まれるようになります。これがコメントで書いていた 0x28:00 -> 0x28:80 の意味なんですが
バージョン文字列の関係もあって普通にパッケージ化した方が良いです。

フォントについて

このゲームにはフォントが4種類あります。sharedassets0.assets に存在します。
  1. BerkshireSwash-Regular
  2. CreteRound-Regular
  3. Francois-One
  4. Arial.1
BerkshireSwash-Regular はタイトル部分等なので前の記事を参考に

CreteRound-Regular が大体の部分を占めます。
以下置換例
TFDG02b
TFDG03
TFDG05

Francois-One の場所
TFDG06

Arial.1 の場所
TFDG07

以上チュートリアル部分までの解析です。

↑このページのトップヘ