白銀のライねる

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

【空の探検隊】SIR0形式の解説

SIR0形式とは、NDS3DSポケダンシリーズで使用されている一般的なファイル形式。
中身は普通のバイナリデータだが、ASCII文字で「SIR0」と書かれた特徴的なヘッダーが含まれている。複数のデータがある場合はバイナリデータの後にオフセットとそのポインターが含まれている。
読み方は「しろ」でいいのかな?

SIR0ファイル

SIR0ファイルは以下のように構成されている。

SIR0ヘッダー

アドレス データ型 備考
0x00~0x03 マジックナンバー"SIR0" {0x53, 0x49, 0x52, 0x30}
0x04~0x07 UInt32 「オフセットリスト」のアドレス。オフセットリストが無い場合、データの開始部分(0x10)を指す。
0x08~0x0B UInt32 「オフセットポインター」のアドレス。後述参照。
0x0C~0x0F 空。0で埋められている

ファイルの先頭から16バイトで構成。
データを取り出す目的なら開始位置~オフセットポインターのアドレスで切り出せばOK。
更に深掘りするなら後述のオフセットポインターを参照。

オフセットポインター

オフセットポインターとはSIR0ファイルに含まれるすべてのオフセットに対するポインター(アドレス位置)を指す。
住所録みたいなものと思ってもらえればわかりやすいかも。
ヘッダーの0x08~0x0Bの値がオフセットポインターの位置となっている。


「FONT」ディレクトリ内にあるframe0.wteを例として挙げると、ヘッダー0x08~0x0Bの値は0x950とあるのでそのアドレスが開始位置となる。

0x950を見てみると、以下のようになっている。

04 04 92 0C 14 00 AA AA AA AA AA AA AA AA AA AA

0x00以降の0xAAは穴埋めなので無視し、残ったのは「04 04 92 0C 14 00」の6バイトとなる。

これらの値がそのままポインターになるわけではなく、圧縮された値を正しい値に変換する必要がある。

値の変換

左から順番に0x80未満のバイトで区切ってグループ化する。0x00はリストの終端を示すので0x00の値が見つかった時点で終了とする。
今回の6バイトの場合、[0x04] [0x04] [0x92, 0x0C] [0x14]と4グループが作られることになる。なお、4バイトを超えるグループは作られない。

このグループの長さによって計算方法が変わってくる。

  • 1バイトの場合

計算は行われず、1つ目のバイトがそのまま使われる。

  • 2バイトの場合

(({1つ目のバイト} & 0x7F) << 7) | {2つ目のバイト}

  • 3バイトの場合

(({1つ目のバイト} & 0x7F) << 14) | (({2つ目のバイト} & 0x7F) << 7) | {3つ目のバイト}

  • 4バイトの場合

(({1つ目のバイト} & 0x7F) << 21) | (({2つ目のバイト} & 0x7F) << 14) | (({3つ目のバイト} & 0x7F) << 7) | {4つ目のバイト}

バイト数の分左にシフトされるビット数が7の倍数で変化する。1つの式にまとめたい所…頭が悪いので思いつきません!


この計算結果を以前の計算結果に加算した値ポインターとなる。
最初のみ、以前の計算結果が無いのでそのまま使う。

よって今回の「04 04 92 0C 14 00」の場合は以下のような式でポインターが求められる。
0x04 = 0x04
0x04 + 0x04 = 0x08
0x04 + 0x04 + ((0x92 & 0x7F) << 7) | 0x0C = 0x914
0x04 + 0x04 + ((0x92 & 0x7F) << 7) | 0x0C + 0x14 = 0x928

この結果は「0x04、0x08、0x914、0x928の値はポインターですよ~」と教えてくれていることになる。
0x04と0x08はSIR0ヘッダーにある2つのアドレスのことを指している。


他の例として、3バイト以上のグループがあるmappa_s.binでは「04 04 82 98 18 04 …(省略)」とあり、これを変換すると以下のような結果になる。
0x04 = 0x04
0x04 + 0x04 = 0x08
0x04 + 0x04 + ((0x82 & 0x7F) << 14) | ((0x98 & 0x7F) << 7) | 0x18 = 0x8C20
0x04 + 0x04 + ((0x82 & 0x7F) << 14) | ((0x98 & 0x7F) << 7) | 0x18 + 0x04 = 0x8C24


ちなみに成長率データ(m_level.bin)に含まれるSIR0ファイルの場合、オフセットポインターは「04 04 00」なのでSIR0ヘッダーの2つの値以外にはオフセットが存在しない。

SIR0ファイルの中身だけ取り出す(C#)

ヘッダーとポインターを除いたデータ部分のみを取得する。0x10からオフセット関連の所までをまとめて取得するだけなので至って単純。
※2023-01-27追記 普通に欠陥コード書いてたので修正

/// <summary>
/// SIR0フォーマットの展開
/// </summary>
/// <param name="sir0">SIR0フォーマットバイナリファイル</param>
public static byte[] DecodeSIR0Byte(byte[] sir0)
{
    byte[] result = { };

    // SIR0フォーマットの確認
    if (sir0[0x00] == 0x53 && sir0[0x01] == 0x49 &&
        sir0[0x02] == 0x52 && sir0[0x03] == 0x30)
    {
        int headerLen = 0x10;
        int offsetListPos = (int)BitConverter.ToUInt32(sir0, 0x4);
        int offsetPointPos = (int)BitConverter.ToUInt32(sir0, 0x8);

        // オフセットリストのポインターが0x10未満の値の場合、
        // 0x10~オフセットリストまでをデータとして取得
        if (offsetListPos < headerLen)
        {
            result = new byte[offsetListPos - headerLen];
            Array.Copy(sir0, headerLen, result, 0, offsetListPos - headerLen);
        }
        // 0x10~オフセットポインターまでをデータとして取得
        else
        {
            result = new byte[offsetPointPos - headerLen];
            Array.Copy(sir0, headerLen, result, 0, offsetPointPos - headerLen);
        }
    }
    else result = sir0; // SIR0ファイルでなければそのまま返す

    return result;
}

SIR0ファイルからポインターオフセットを取得(C#)

ポインターオフセットのみをListで取得する。

/// <summary>
/// ポインターオフセットリストの取得
/// </summary>
/// <param name="binary"></param>
/// <returns></returns>
private List<int> GetPointerOffsetList(byte[] binary)
{
    List<int> result = new List<int>();

    // ヘッダーからオフセット取得
    int hContent = (int)BitConverter.ToUInt32(binary, 0x04);
    int hPointer = (int)BitConverter.ToUInt32(binary, 0x08);

    // ポインターオフセットリスト展開
    int prev = 0;
    for (int i = hPointer; i < binary.Length; i++)
    {
        // 0x00 = リストの終端
        if (binary[i] == 0x00) break;

        int value = 0;
        int calc = 0;

        // 0x80未満になるまでバイトを連続して取得 (最大4バイト)
        List<byte> chain = new List<byte>();
        while (chain.Count < 4)
        {
            // バイト取得
            chain.Add(binary[i]);

            // 取得したバイトが0x80未満ならループ抜ける
            if (binary[i] < 0x80) break;

            // 1バイト進める
            i++;
        }

        // 取得した分計算
        if (chain.Count == 4)
            calc = ((chain[0] & 0x7F) << 21) | ((chain[1] & 0x7F) << 14) | ((chain[2] & 0x7F) << 7) | chain[3];
        else if (chain.Count == 3)
            calc = ((chain[0] & 0x7F) << 14) | ((chain[1] & 0x7F) << 7) | chain[2];
        else if (chain.Count == 2)
            calc = ((chain[0] & 0x7F) << 7) | chain[1];
        else if (chain.Count == 1)
            calc = chain[0];
        else
        {
            MessageBox.Show("オフセットリスト展開エラー");
            break;
        }
        value = prev + calc;

        // リストに追加
        result.Add(value);

        // 値を保存
        prev = value;
    }

    return result;
}

参考

  • SIR0/SIRO Format - Mystery Dungeon NDS - Project Pokemon Forums

projectpokemon.org