白銀のライねる

解析とかのメモとかをなんとか

【空の探検隊】パックファイルの展開

大量のデータが1つにまとめられた「パックファイル」の展開方法。
空の探検隊に含まれているパックファイルは主に以下のファイルを指す。

  • DUNGEON/dungeon.bin
  • EFFECT/effect.bin
  • MONSTER/m_attack.bin
  • MONSTER/m_ground.bin
  • MONSTER/monster.bin
  • BALANCE/m_level.bin


パックファイルの構造は以下の通り。
以降、パックファイルに含まれているファイルを「サブファイル」と呼ぶ。

アドレス データ型 備考
0x00~0x03 空。0で埋められている
0x04~0x08 UInt32 このパックファイルに含まれているデータ数
0x09~ Array[8] * データ数 + 1 サブファイル情報配列。
0~3…サブファイルの開始位置(UInt32)
4~7…サブファイルのデータの長さ(UInt32)
+1の分は0で埋められた空の配列。
サブファイル情報配列の後 パディング。0xFFで埋められている
パディングの後 以降はサブファイルの塊。サブファイル情報配列の内容を元にここからデータを切り出す。

成長率データ(m_level.bin)を例にしてみる。

0x4~0x7の赤枠はサブファイルの個数を表す。
値は0x23B=571なので、サブファイルは571個存在することがわかる。
0x8~0xFの青枠はサブファイル情報配列を表す。
この配列の1つ目の0~3には0x1200が格納されており、1つ目のサブファイルはパックファイル内の0x1200から始まる。
4~7には0x270とあり、データの長さは0x270=624バイトであることがわかる。

よって1つ目のサブファイルを切り出したい場合は、パックファイルの0x1200から数えて624バイト取ればいいということになる。

以降はここから何バイト、ここから何バイト・・・という流れで分けていくと展開できる。

プログラムで取得する(C#)

パックファイルをbyte型配列で渡すと展開してファイルに出力してくれる関数。

/// <summary>
/// データクラス
/// </summary>
private class UnpackData
{
    /// <summary>ファイル名</summary>
    public string fileName { get; set; }

    /// <summary>バイナリデータ</summary>
    public byte[] data { get; set; }
}

/// <summary>
/// パックファイルの展開
/// </summary>
/// <param name="bin">binファイル</param>
/// <param name="path">パス</param>
private void UnpackBin(byte[] bin, string path)
{
    int count = BitConverter.ToInt32(bin, 4);
    List<UnpackData> dataList = new List<UnpackData>();

    int pos = 8;
    try
    {
        // データ分解
        for (int i = 0; i < count; i++)
        {
            int addr = BitConverter.ToInt32(bin, pos + 0);
            int len = BitConverter.ToInt32(bin, pos + 4);

            // binファイルから取得
            byte[] data = new byte[len];
            Array.Copy(bin, addr, data, 0, len);

            // 追加
            dataList.Add(new UnpackData { fileName = $"{addr:X8}", data = data });

            pos += 8;
        }

        // データ出力
        string output = Path.GetDirectoryName(path) + @"\output\";
        if (!Directory.Exists(output)) Directory.CreateDirectory(output);

        foreach (UnpackData unpack in dataList)
            using (FileStream fs = new FileStream(output + unpack.fileName, FileMode.Create))
            using (BinaryWriter bw = new BinaryWriter(fs))
                bw.Write(unpack.data.ToArray());

        MessageBox.Show($"完了!\n\n出力ファイル数: {dataList.Count}\n出力先フォルダ: {output}");
    }
    catch (Exception e)
    {
        MessageBox.Show($"ファイルの展開に失敗しました。\n{e.Message}");
    }
}

試しに/DUNGEON/dungeon.binをぶち込む。

成功。

参考

  • PMD2: Pack File Format - Mystery Dungeon NDS - Project Pokemon Forums

projectpokemon.org