SIR0形式とは、NDSと3DSのポケダンシリーズで使用されている一般的なファイル形式。
中身は普通のバイナリデータだが、ASCII文字で「SIR0」と書かれた特徴的なヘッダーが含まれている。複数のデータがある場合はバイナリデータの後にオフセットとそのポインターが含まれている。
読み方は「しろ」でいいのかな?
SIR0ファイル
SIR0ファイルは以下のように構成されている。
- {SIR0ヘッダー}
- {データ・ソ・ノモノ}
- {0xAAで穴埋め}
- {オフセットポインター}
- {0xAAで穴埋め}
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; }