SkyrimのModをプログラムで生成する - 3 - レコードを扱う

  • 0
    いいね
  • 0
    コメント

    はじめに

    この記事は、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の情報があるかわかっていますので、ファイルリーダーで、グループやレコードを一つの単位で扱えるようにします。

    TesFileReader
            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;
            }
    

    続いて、グループクラスです。

    TesGroup
        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 - フィールドを扱う