はじめに
この記事は、SkyrimというゲームソフトのModファイルのバリナリ解析とそれを利用してModを生成するC#のコードについて記載していくシリーズ物です。
ファイルフォーマットについての詳しい情報は以下のサイトがあります。(以降UESP)
Tes5Mod:Mod File Format - The Unofficial Elder Scrolls Pages (UESP)
前回はグループについて扱いましたので、今回はレコードについてです。
- グループ
-
レコード
- フィールド
-
レコード
レコードクラスとヘッダークラス
前回、グループ、レコード、フィールドの最初にはヘッダー情報があることを記載しました。
ヘッダーには何の情報を扱うかの4文字の記号、全体のデータサイズなどが記載されています。
ヘッダーは24byteあり次のようになっています。
項目名 | 型 | byte数 | byte数合計 | 説明 |
---|---|---|---|---|
Signature | string | 4 | 4 | どのような情報を扱うかを識別する記号。 |
DataSize | uint | 4 | 8 | ヘッダーサイズを含まないレコードの全体サイズ。 |
RecordFlags | uint | 4 | 12 | あまり気にしない。詳細はUESPのRecordsのflags参照。 |
FormID | uint | 4 | 16 | mod内でデータを一意に識別するためのID。 |
VersionControlInfo1 | uint | 4 | 20 | あまり気にしない。詳細はUESPのRecordsのrevision参照。 |
FormVersion | ushot | 2 | 22 | あまり気にしない。詳細はUESPのRecordsのversion参照。 |
VersionControlInfo2 | ushot | 2 | 24 | あまり気にしない。詳細はUESPのRecordsのdata参照。 |
※項目名はSSEEditでの表示名称を使用 |
では、ヘッダー情報を扱うクラスを作成します。
public class TesHeader : TesBase
{
public TesString Signature { get; }
public TesUInt32 DataSize { get; set; }
public TesUInt32 RecordFlags { get; }
public TesUInt32 FormID { get; set; }
public TesUInt32 VersionControlInfo1 { get; }
public TesUInt16 FormVersion { get; }
public TesUInt16 VersionControlInfo2 { get; }
public TesHeader(TesFileReader fr)
{
Signature = new TesString(fr);
DataSize = new TesUInt32(fr);
RecordFlags = new TesUInt32(fr);
FormID = new TesUInt32(fr);
VersionControlInfo1 = new TesUInt32(fr);
FormVersion = new TesUInt16(fr);
VersionControlInfo2 = new TesUInt16(fr);
OutputItems.Add(Signature);
OutputItems.Add(DataSize);
OutputItems.Add(RecordFlags);
OutputItems.Add(FormID);
OutputItems.Add(VersionControlInfo1);
OutputItems.Add(FormVersion);
OutputItems.Add(VersionControlInfo2);
}
}
}
コンストラクターでファイルリーダーを受け取り、順次読み込んで行きます。
各項目は必ず順番通りにファイル出力用のOutputItems
に格納するようにします。
続いて、レコードクラスを作成します。
public class TesRecord : TesBase
{
public TesHeader Header { get; }
public TesBytes FieldData { get; }
public TesRecord(TesFileReader fr)
{
Header = new TesHeader(fr);
FieldData = fr.GetBytes(Header.DataSize);
}
}
とりあえずまだ、フィールドデータは丸ごと読込む形とします。
グループクラスでレコードクラスを扱えるようにする
前回、グループクラスは丸ごとデータを読込んでいましたが、レコードを扱うように修正を加えます。
基本的な処理方法は、ファイルをDataSize
に合わせて順次読込んでいく形です。(このため、どこかで読込み間違えると大変なことになります)
これまでの内容で、どこにDataSize
の情報があるかわかっていますので、ファイルリーダーで、グループやレコードを一つの単位で扱えるようにします。
public TesFileReader GetGroup(bool next = true)
{
long count = GetUInt32(4, false);
TesFileReader result = new TesFileReader(br, pos, pos + count);
if (next)
pos += count;
return result;
}
public TesFileReader GetRecord(bool next = true)
{
long count = GetUInt32(4, false) + 24;
TesFileReader result = new TesFileReader(br, pos, pos + count);
if (next)
pos += count;
return result;
}
続いて、グループクラスです。
public class TesGroup : TesBase
{
public TesString GRUP { get; }
public TesUInt32 DataSize { get; }
public public TesBytes Other { get; }
public TesList<TesRecord> Records = new TesList<TesRecord>();
public TesGroup(TesFileReader fr)
{
GRUP = new TesString(fr);
DataSize = new TesUInt32(fr);
Other = new TesBytes(fr.GetBytes(16));
while (!fr.EOF)
{
Records.Add(new TesRecord(fr.GetRecord()));
}
OutputItems.Add(GRUP);
OutputItems.Add(DataSize);
OutputItems.Add(Other);
OutputItems.Add(Records);
}
}
※前回グループクラスの内容にはOutputItems
への追加を記載し忘れていました
ファイルリーダーのGetGroup
で1グループ分の読み取り範囲のファイルリーダーをTesGroup
のコンストラクターに渡し、ヘッダー情報を読込み後、読み取り範囲が終了するまで、繰り返しレコードクラスを作成します。
以上、今回はここまでです。
前回 | 次回 |
---|---|
2 - グループを扱う | 4 - フィールドを扱う |