SkyrimのModをプログラムで生成する - 8 - 実際にModファイルを読み取ってみる

  • 0
    いいね
  • 0
    コメント

    はじめに

    この記事は、SkyrimというゲームソフトのModファイルのバリナリ解析とそれを利用してModを生成するC#のコードについて記載していくシリーズ物です。
    ファイルフォーマットについての詳しい情報は以下のサイトがあります。(以降UESP)
    Tes5Mod:Mod File Format - The Unofficial Elder Scrolls Pages (UESP)

    これまでの内容で大体の必要なクラスの説明が出来たと思いますので、今回は実際にそれらを使ってModの読み取りを行ってみます。

    コレまでの記事はコードは一部抜粋で、説明もわかりづらいものでしたので、はずかしいものの、一通りのソースコードをGitHubにアップしました。
    これまで紹介していた内容とは一部異なっていますのでご注意ください。
    なお、ほとんどコメントをいれていませんので意図がわかりづらい点があるかもしれませんが、まだ試行錯誤している部分やテストコード的な部分も含まれています。また、例外対処もほとんどいれていませんのでご了承ください。

    https://github.com/rrryutaro/RTesLib

    ワールド情報から特定のオブジェクトの情報を抜き出してみる

    この例では、ワールド情報内になるマップマーカー(0x10)を抜き出し、そのFormIDと英語名称と日本語名称を出力してみます。
    前提として、RTesLibを参照しているものとします。

            public void OutputAllMapMarker()
            {
                TesTableStrings tblEN = new TesTableStrings(@"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\strings\skyrim_english.strings");
                TesTableStrings tblJP = new TesTableStrings(@"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\strings\skyrim_japanese.strings");
    
                string path = @"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\Skyrim.esm";
    
                TesFile esp = new TesFile(path, new List<string>(new string[] { "WRLD" }));
                TesWorldspace wrld = (TesWorldspace)esp["WRLD"];
                foreach (var x in wrld.Records)
                {
                    if (x.Main.Cell != null)
                    {
                        foreach (var x2 in x.Main.Cell.CellMain.Subs)
                        {
                            foreach (var x3 in x2.Records)
                            {
                                if (x3.Contains("NAME"))
                                {
                                    uint name = x3["NAME"][0].ToUInt32();
                                    if (name == 0x10)
                                    {
                                        uint id = x3["FULL"][0].ToUInt32();
                                        System.Diagnostics.Debug.Print(string.Format("{0}\t{1}\t{2}", x3.Header.FormID.Value.ToString("x8"), tblEN[id], tblJP[id]));
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
    1. Skyrim.esmより、WRLDのみ読み取る
    2. WRLDの全てのセルデータをチェックする
    3. セルデータのフィールドにNAMEがあり、それがマップマーカー(0x10)なら出力対象
    4. FULLにあるストリングテーブルのIDを取得する
    5. セルデータのレコードヘッダーのFormIDと英語文字列と日本語文字列をタブ区切で出力ウィンドウに出力する

    といった感じです。

    武器データを改造したModを作ってみる

    次の例は、バニラの全ての武器データのダメージを+100するチートModです。

            public void CreateDamagePlus100Weapons()
            {
                TesTableStrings tblEN = new TesTableStrings(@"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\strings\skyrim_english.strings");
                TesTableStrings tblJP = new TesTableStrings(@"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\strings\skyrim_japanese.strings");
    
                string path1 = @"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\Skyrim.esm";
                string path2 = @"C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\Data\DamagePlus100Weapons.esp";
    
                TesFile esp1 = new TesFile(path1, new List<string>(new string[] { "WEAP" }));
                TesFile esp2 = new TesFile(path2);
    
                TesGroup WEAP = esp1.Groups["WEAP"];
                esp2.OutputItems.Add(WEAP);
    
                foreach (var x in WEAP.Records)
                {
                    if (x.Contains("FULL"))
                    {
                        TesBytes full = x["FULL"][0];
                        uint id = full.ToUInt32();
                        full.Clear();
                        full.AddRange(new TesBytes(tblJP[id], true));
                    }
                    TesBytes b = x["DATA"][0];
                    ushort dmg = (new TesBytes(b.GetRange(8, 2).ToArray())).ToUInt16();
                    dmg += 100;
                    b.RemoveRange(8, 2);
                    b.AddRange((new TesUInt16(dmg)).ToBytes());
                }
                esp2.Save(path2);
            }
    
    1. Skyrim.esmからWEAPのみ読み取る
    2. ファイルヘッダーのみのModを読み取り、WEAPグループを追加する
    3. 武器データを順次読込む
    4. フィールドにFULLがある場合、日本語文字列にした内容に書きかえる(idのままなので、FULLTesBytesの中身をクリアしてストリングテーブルから取得した文字列をNULL終端としてバイト配列を追加する)
    5. DATAフィールドより、ダメージ情報のある部分を読み取る
    6. ダメージの値に100を加算する
    7. DATAフィールドのダメージの値を書きかえる(ダメージの値のある部分を削除して、バイト配列を追加(バイト配列内の最後なので追加でよい))
    8. 同名でファイル出力する

    試しにSSEEditで確認すると確かに100追加されています。
    image

    といった感じで、読み取りやデータの変更などが行えるようになりました。

    以上

    前回 次回
    7 - ストリングテーブルを扱う