Modlet

Last-modified: 2024-04-12 (金) 11:09:12

Modletとは

Modletとは、ゲームファイル本体を上書きしない小さなModのことです。
Alpha17から導入されました。
(Modletの仕組みが導入される前は、Mod製作者たちはゲームファイル自体を編集して配布していたため、競合やゲームバージョンのアップデートで問題が起きやすくなっていました。Modletはゲームファイル本体を直接書き換えたりしないため、そういった問題が起きにくくなっています)

Modletの導入方法

基本的に、Modsフォルダに任意のModletを放り込むだけで導入できます。
すでにセーブデータがある環境に導入する場合は、導入前に必ずバックアップをとってください。
Mod Launcher を利用する場合は、ManageModletsからModletを管理することができます(プロジェクト別管理のようなことも可能です)。

Modletの詳細

形式は公式・非公式の物が存在し、簡便さや自由度の違いからすみ分けされています。
ここでは広く使われている公式の方法を紹介します。

 

Modletの形式

公式基準のModletは、7 days to dieのホームディレクトリ直下のModsディレクトリに、それぞれ固有のフォルダ名で保存され、その中には必ずModInfo.xmlが入っています。

Mods/MyMod/ModInfo.xml
Mods/YourMod/ModInfo.xml

ModInfo.xml

以下の内容で作るか、fileここからダウンロードして入れましょう。
NameはそれぞれのModlet固有である必要があり、同じものは無視されます。

~~展開~~

折り畳み

<?xml version="1.0" encoding="UTF-8" ?>
<xml>
	<ModInfo>
		<Name value="test" />
		<Description value="test dayo" />
		<Author value="Yamada Taro" />
		<Version value="1.0.0" />
	</ModInfo>
</xml>
 

ModInfo.xmlはa21からフォーマットがV2に変わりました。
旧フォーマットのV1でも動作はしますが、ワーニングが出ます。
今後V1は使用できなくなる可能性があるのでV2に更新するようにしましょう。

~~展開~~

折り畳み

<?xml version="1.0" encoding="UTF-8" ?>
<xml>
	<Name value="test" />
    <DisplayName value="V2 test" />
	<Version value="1.0.0.0" />
	<Description value="test dayo" />
	<Author value="Yamada Taro" />
    <Website value="http://test.com" />
</xml>

Name:【必須】世界で固有の内部名となるような名前を付けます。英数字、アンダーバー、ハイフンのみ使用可能となりました。
DispkayName:【必須】表示名で、ゲーム中に表示される場合に使われます。
Version:【必須】数字とピリオドのみで、メジャーバージョン・マイナーバージョン・ビルド番号・変更番号のように付けることが推奨されています。
ビルド番号と変更番号、または変更番号のみの省略は可能です。
Description:【省略可】Mod/Modletの説明です。
Author:【省略可】作成者の名前です。複数人で作成している場合は列挙しましょう。
Website:【省略可】Mod/Modletの説明や公開サイトのURL

 

公式の方法で可能なことは、Config内のXMLの操作と一部Assetの追加です。
Assetは7 days to dieのホームディレクトリ以下なら場所を問いませんが、XMLの操作はModletフォルダ内のConfigsに、変更するXMLと同じ名前で入っています。

Mods/MyMod/Configs/items.xml
Mods/MyMod/Configs/XUi/windows.xml
 

XMLの操作

以下の8個の操作でXMLを変更することができます。

a18からLocalizationが変更できるようになりました。

  • append 子要素の後尾に追加
  • prepend 子要素の先頭に追加
  • insertAfter その要素の一つ後として追加
  • insertBefore その要素の一つ前として追加
  • remove その要素を消す
  • set 今ある要素/属性の代わりに設定(存在しない属性を足すことはできません)
  • setattribute 属性を追加
  • removeattribute 属性を削除(a18から使用可)
~~append~~

append折り畳み
元XML

<items>
  <item name="1"/>
</items>

XML操作XML

<configs>
  <append xpath="/items">
    <item name="2"/>
  </append>
</configs>

出力XML

<items>
  <item name="1"/>
    <item name="2"/>
</items>
~~prepend~~

prepend折り畳み
元XML

<items>
  <item name="1"/>
</items>

XML操作XML

<configs>
  <prepend xpath="/items">
    <item name="2"/>
  </prepend>
</configs>

出力XML

<items>
    <item name="2"/>
  <item name="1"/>
</items>
~~insertAfter~~

insertAfter折り畳み
元XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5"/>
  </item>
</items>

XML操作XML

<configs>
  <insertAfter xpath="/items/item[@name='1']/property[@name='A']">
    <property name="C" value="7"/>
  </insertAfter>
</configs>

 ※itemが1つしか無い場合、又は全てのitemに適用する場合は[@name='1']を省略できます。
  詳細はXPathの項を参照。

出力XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="C" value="7"/>
    <property name="B" value="5"/>
  </item>
</items>
~~insertBefore~~

insertBefore折り畳み
元XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5"/>
  </item>
</items>

XML操作XML

<configs>
  <insertBefore xpath="/items/item[@name='1']/property[@name='B']">
    <property name="C" value="7"/>
  </insertBefore>
</configs>

 ※itemが1つしか無い場合、又は全てのitemに適用する場合は[@name='1']を省略できます。
  詳細はXPathの項を参照。

出力XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="C" value="7"/>
    <property name="B" value="5"/>
  </item>
</items>
~~remove~~

remove折り畳み
元XML

<items>
  <item name="1"/>
  <item name="2"/>
</items>

XML操作XML

<configs>
  <remove xpath="/items/item[@name='1']"/>
</configs>

出力XML

<items>
  <item name="2"/>
</items>
~~set~要素の置き換え

set(要素の置き換え)折り畳み
元XML

<items>
  <item name="1"/>
</items>

XML操作XML

<configs>
  <set xpath="/items">
    <item name="2"/>
  </set>
</configs>

出力XML

<items>
     <item name="2"/>
</items>
~~set~属性の置き換え

set(属性の置き換え)折り畳み
元XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5"/>
  </item>
</items>

XML操作XML

<configs>
  <set xpath="/items/item[@name='1']/property[@name='B']/@value"> 10 </set>
</configs>

 ※itemが1つしか無い場合、又は全てのitemに適用する場合は[@name='1']を省略できます。
  同様に、全てのpropertyに適用する場合は[@name='B']を省略できます。
  詳細はXPathの項を参照。

出力XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="10"/>
  </item>
</items>
~~setattribute~~

setattribute折り畳み
元XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5"/>
  </item>
</items>

XML操作XML

<configs>
  <setattribute xpath="/items/item[@name='1']/property[@name='B']" name="condition"> walk </setattribute>
</configs>

 ※itemが1つしか無い場合、又は全てのitemに適用する場合は[@name='1']を省略できます。
  同様に、全てのpropertyに適用する場合は[@name='B']を省略できます。
  詳細はXPathの項を参照。

出力XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5" condition="walk"/>
  </item>
</items>
~~removeattribute~~

removeattribute折り畳み
元XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5" condition="walk"/>
  </item>
</items>

XML操作XML

<configs>
  <removeattribute xpath="/items/item[@name='1']/property[@name='B']/@condition"/>
</configs>

 ※itemが1つしか無い場合、又は全てのitemに適用する場合は[@name='1']を省略できます。
  同様に、全てのpropertyに適用する場合は[@name='B']を省略できます。
  詳細はXPathの項を参照。

出力XML

<items>
  <item name="1">
    <property name="A" value="3"/>
    <property name="B" value="5"/>
  </item>
</items>

操作を行う場所は、XPathで指定します。

 

XPath

XPathはXML内のとある位置を指し示す世界標準の方法です。
専門的な言葉を使えば、XMLをツリーとみなしてルートからたどるパスです。

基本

基本的に<>で囲まれたテキストの初めに来る、要素名を/(スラッシュ)で区切ってたどっていきます。

<items>
  <item name="meleeToolStoneAxe"/>
</items>

上の例で<item name="meleeToolStoneAxe"/>を指定する場合は、/items/itemでたどることができます。

 

ただし、次のような場合、/items/itemをたどると2個のitem両方が指定されます。

<items>
  <item name="meleeToolStoneAxe"/>
  <item name="meleeToolFireaxeIron"/>
</items>

要素名だけでたどれない時は、条件で絞り込むことができます。
条件は[]のカッコで囲みます。単純な例は、[1]のような数字で、何番目か指定する方法です。
例えば、/items/item[1]<item name="meleeToolStoneAxe"/>を指定できます。

ただし、この方法では新しいアイテムが追加されたり、順番が変わるだけで動かなくなります。

<items>
  <item name="meleeClubWood"/>
  <item name="meleeToolStoneAxe"/>
  <item name="meleeToolFireaxeIron"/>
</items>

この場合、<item name="meleeClubWood"/>の方が指定されてしまいます。

 

代わりに、属性を使った条件の方がうまくいくことが多いでしょう。
XPathでは属性の名前には@を付けて、@nameというように書きます。
/items/item[@name='meleeToolStoneAxe']と書けば、一通りに指定することができます。

 

条件で使える関数

last()
今の要素の中で最後の要素の番号を返します。
[last()]とすれば、一番最後の要素を指定できます。
starts-with(s1,s2)
文字列s1が文字列s2で始まる場合、真になります。
[starts-with(@name,'meleeTool')]とすれば、nameがmeleeToolで始まる要素を指定できます。
contains(s1,s2)
文字列s1が文字列s2を含む場合、真になります。
[contains(@name,'Iron')]とすれば、nameにIronを含む要素を指定できます。
position()
要素の番号を返します。
[position() &lt; 3]とすれば、3番目より後の要素を指定でき、
[position() &gt; 3]とすれば、3番目より前の要素を指定できます。
(&lt;は<の、&gt;は>のxml用の書き換えです。)
string-length(str)
文字列strの文字数を返します。
[string-length(@id)=1]とすれば、idが一桁の要素を指定できます。
substring(str,num)
文字列strの中で、num文字目から先の文字列を返します。
[substring(@name,string-length(@name) - string-length('Radiated') +1) = 'Radiated']とすれば、nameがRadiatedで終わる要素を指定できます。
(ends-withの代わり)
and
前の条件と後ろの条件が両方正しい場合真になります。
[starts-with(@name,'melee') and contains(@name,'Iron')]とすれば、nameがmeleeで始まって、Ironを含む要素を指定できます。
ただし、[starts-with(@name,'melee')][contains(@name,'Iron')]のように、2重に条件を使うことでも同じように指定できます。
or
前の条件と後ろの条件のどちらかが正しい場合真になります。
[starts-with(@name,'melee') or starts-with(@name,'gun')]とすれば、nameがmeleeかgunで始まる要素を指定できます。
not(条件)
条件を反転します。
[starts-with(@name,'gun') and not(ends-with(@name,'Admin'))]とすれば、nameがgunで始まり、終わりがAdminではない要素を指定できます。
 

省略

*
名前に関係なく要素を指定します。
名前を指定しても意味がない時や、複数の経路を同時にたどる時に使えます。
//
/の代わりに使うと、その要素直下だけでなく、それ以下の全ての要素の中から探します。
 

ローカライゼーション

Alpha18のローカライゼーションの操作はModletフォルダ内のConfigsに、Localization.txtという名前で入っています。

Mods/MyMod/Configs/Localization.txt
 

このファイルはテキストファイルで、カンマで区切るcsv形式で書かれています。
テキストとは言え、UTF-8を使っていますので、UTF-8の使えるテキストエディタ等で編集する必要があります。

 

作成する場合は、まずオリジナルのLocalization.txtの1行目をコピーし、2行目以降に追加したい内容を記述します。

 

1行目はアイテムの分類や各種言語の項目名が入っています。
それにのっとり説明を追加していきます、
1行が大変長いですが、Keyには内部名を英語で記述、englishより前までは、オリジナルファイルの記述を参考にします。
※内部名とは、Items.xmlやBlocks.xml等で記述した名前です

 

englishから後は各言語による記述になります。
途中で改行を入れることはできません。
※説明文の表示時に改行を入れたい場合は、改行したい位置に\nを入れます。
※説明文の表示にカンマを含む場合は、その言語の部分の文全てをダブルクォーテーションで囲む必要があります。

 

内容はアイテム名やアイテムの説明文、クエストの説明文になります。
アイテムの説明文のKeyは、アイテム名のKeyの後ろにDescを付けます。しかし、items.xml内で当該アイテムに DescriptionKey プロパティを特別に設定した場合はこの限りではありません。

 

各言語の部分はenglish以外は省略でき、その場合は英語の記述が引用されます。
カンマは省略できません。

 

他言語の記述をしない場合は1行目を含め省略して書くことができます。

Key,File,Type,english,japanese
内部名,items,Item,英名,日本名
内部名Desc,items,Item,英文説明,日本文説明

※アイテムを追加する場合の例

 

詳しくはオリジナルのLocalization.txtを参考にしてください。

既存のアイテムに日本語を追加する場合

日本語表示されない物に日本語を追加するだけであれば、Localization.txtの中身を大幅に省略できます。
具体的には、内部名と日本語だけあればOKです。

Key,japanese
内部名,日本語説明文

間違った日本語を修正する場合も同様です。
但し、追加するアイテムがある場合などは混在ができませんので、そちらに合わせる必要があります。

アイテムアイコン

詳しくは、アイコン追加

Harmony

Alpha20 から導入された機能です。ゲーム本体のバイナリ (Assembly-CSharp.dll) にパッチを与えたり、独自クラスを定義したりすることが可能となります。
かつて大型 Mod/オーバーホール Mod と呼ばれたインストール方法を過去のものにします。ディレクトリ 7DaysToDie_Data を上書きする必要がなくなると同時に、アンインストール時に整合性チェックを実行する必要もなくなりました。手軽にオーバーホールを楽しむことが可能です。
XML を書き換えるだけでは実現し得なかった実装を、このチュートリアルで試してみてください。
Harmony を利用した場合、EAC を通して起動することが不可能となります。 EAC をオフにして動作確認を行ってください。

より詳しい情報は、Harmony 公式サイト https://harmony.pardeike.net/ (English) にアクセスしてください。

 

Step by step coding tutorial

  • .NET Framework コンパイル/ビルド環境を整える
    • Visual Studio Community を利用する
    • Visual Studio Code を利用する
  • 必要最低限のテンプレートを準備する
  • 実際にコードを書き換える
  • Modlet に同梱する

Reference

  • Prefix
  • Postfix

Step by step coding tutorial

この例で紹介するソースコードは Alpha20.6(b9) 時点の情報をもとにしており、CC0-1.0 ライセンスが適用されます。

Visual Studio Community

Windows をお使いの皆様は、Visual Studio Community を利用する選択肢が最も有力です。

インストーラを起動して、.NET デスクトップ開発にチェックを入れます。インストールされる個別のコンポーネントの中に、.NET Framework 4.x SDK と Targeting Pack が含まれていることを確認してください。執筆時点の最新版は 4.8.1 です。
インストールにはしばらく時間がかかります。再起動を促された場合、無理して続行せず再起動をして仕切り直しましょう。

Visual Studio を起動して、新しくプロジェクトを作成します。以下の設定を確認してください:

  • プロジェクト テンプレートClass Library(.NET Framework) を選択すること
  • フレームワーク バージョンはインストールしたバージョンを選択すること

.NET Core や WPF .NET Framework 等をターゲットにコンパイルされた DLL は、7DTD に認識されません。 必ずプレーンな .NET Framework を選択してください。

ソリューションの名前は TestProject とします。

ソリューションが作成されたら、必要に応じて参照を追加してください。必須の参照は 0Harmony.dll と Assembly-CSharp.dll です。
Steam デフォルト設定では以下のパスに存在します:
%PROGRAMFILES(X86)%\Steam\steamapps\common\7 Days To Die\7DaysToDie_Data\Managed\

Visual Studio Code

macOS や Linux ディストリビューションをお使いの皆様は、Visual Studio Code を利用する選択肢が最も有力です。

加えて、以下の SDK を入手してください:

SDK がインストールされると、mcs コマンドが使用可能になります。使用できない場合、お使いの OS に対応していない可能性があります。OS を最新版に更新する、もしくは他の無料の Linux ディストリビューションを利用する、Windows を利用する等、回避策を検討してください。

Visual Studio Code を起動して、ワークスペースを準備します。ディレクトリ名は TestProject とします。

TestProject ディレクトリに .vscode という名前のディレクトリを配置し、tasks.json という名前のファイルを準備します。
もしくは、ショートカットキー Ctrl + Shift + B (macの場合は ⌘Cmd + ⇧Shift + B) から、テンプレート Others のタスクを構成してください。

□ .vscode/tasks.json

{
    "tasks": [
        {
            "label": "compile",
            "type": "shell",
            "command": "mcs",
            "args": [
                "-target:library",
                "-warn:0",
                "-o+",
                "-unsafe",
                "-r:${userHome}/.steam/steam/steamapps/common/7 Days To Die/7DaysToDie_Data/Managed/0Harmony.dll",
                "-r:${userHome}/.steam/steam/steamapps/common/7 Days To Die/7DaysToDie_Data/Managed/Assembly-CSharp.dll",
                "-r:${userHome}/.steam/steam/steamapps/common/7 Days To Die/7DaysToDie_Data/Managed/UnityEngine.CoreModule.dll",
                "-langversion:latest",
                "-out:${workspaceFolderBasename}.dll",
                "**/*.cs",
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

0Harmony.dll や Assembly-CSharp.dll までのパスは、ご自身の環境に置き換えてください。必要に応じて、参照を追加してください。

必要最低限のテンプレートを準備する

Harmony に対してフックを仕掛けるには、インターフェース IModApi を実装したクラスが必要です。

□ Harmony/TestProject_HarmonyInit.cs

using HarmonyLib;
using System.Reflection;
using UnityEngine;

public class TestProject_HarmonyInit : IModApi
{
    public void InitMod(Mod _instance)
    {
        Debug.Log($" Loading Patch: {GetType()} Mod: {_instance.ModInfo.Name}");
        var harmony = new Harmony(GetType().ToString());
        harmony.PatchAll(Assembly.GetExecutingAssembly());
    }
}

このコードにより、7DTD 側に Harmony を実装している Modlet であると宣言することになります。
ビルドが正常終了することを確かめてください。

よくあるエラー

出てきたエラーコード error CSXXXX を検索すれば、だいたい docs.microsoft.com のトラブルシューティング ページに案内されます。ここでは、陥りやすいエラーとその回避策を記述します。

 
error CS1525: Unexpected symbol `***'(, expecting `***' or ...)

文法エラーです。C# の文法に則っているか確認してください。

 
error CS0117: `***' does not contain a definition for `***'

定義が不足しています。対象のクラスや名前空間に定義が含まれているかどうか、キーワードが正しいかどうか確認してください。

 
error CS0246: The type or namespace name `***' could not be found. Are you missing an assembly reference?

参照が不足しているため、クラスや名前空間が利用できない状態です。Managed ディレクトリの中から必要な DLL を参照に追加してください。

 
error CS0006: Metadata file `/.../7 Days To Die/7DaysToDie_Data/Managed/***.dll' could not be found

参照までのパスが間違っています。パスが正しいかどうか確認してください。

 
error CS1070: The type `***' has been forwarded to an assembly that is not referenced. Consider adding a reference to assembly `***, Version=X.X.X.X, Culture=***, PublicKeyToken=***'

参照の実体は別のアセンブリに含まれています。サジェストされた DLL を参照に追加してください。

実際にコードを書き換える

この例では、ツールベルトを15個に増やします。

□ Harmony/FifteenSlotToolbelt_Inventory.cs

using HarmonyLib;

public class FifteenSlotToolbelt_Inventory
{
    /**
     * <summary>New slot number for Toolbelt</summary>
     */
    public const int INVENTORY_SLOT_NEW = 15;

    [HarmonyPatch(typeof(Inventory))]
    [HarmonyPatch("get_PUBLIC_SLOTS")]
    public class FifteenSlotToolbelt_harmony_Inventory_get_PUBLIC_SLOTS
    {
        public static void Postfix(ref int __result)
        {
            __result = INVENTORY_SLOT_NEW;
        }
    }
}

解説

HarmonyPatch 属性を駆使して、既存のコードを書き換えます。

10行目 [HarmonyPatch(typeof(Inventory))]
クラス Inventory に対してコード書き換えを行う宣言です。
11行目 [HarmonyPatch("get_PUBLIC_SLOTS")]
メソッド get_PUBLIC_SLOTS に対してコード書き換えを行う宣言です。

Assembly-CSharp.dll の中に、int 型の Inventory.PUBLIC_SLOTS というプロパティが存在します。この値をもとにして、ツールベルトの数が決まります。
C# では中間言語 (以下: IL) にコンパイルされる時点で、プロパティの getter は get_${PROPERTY_NAME} という名前のメソッドに変換されます。

14行目 public static void Postfix(ref int __result)
対象のメソッド内の処理が完了した後、このメソッドに記述されているコードが実行されます。
16行目 __result = INVENTORY_SLOT_NEW;
引数を書き換えます。

引数 __result はオリジナルの戻り値を受け取ります。今回は戻り値を書き換えるため、ref キーワードを設定して戻り値に影響を及ぼさせます。
引数 __result にはツールベルトの数が設定されています。処理して返されたオリジナルの値がどんなものであろうと、INVENTORY_SLOT_NEW (=今回は15) に書き換えます。

 

ビルドが正常終了することを確かめてください。

また、IL の中身を覗いてみたい衝動に駆られた方は、ILSpy や dnSpy を利用してください。

続いて、このままではツールベルトの HUD が2行になるにも関わらず、残りの5つを選択することができなくなります。見た目を改善するため、Config/XUi/windows.xml を書き換えます。

□ Config/XUi/windows.xml

<modconfig>
    <set xpath="/windows/window[@name='windowToolbelt']/@width">1128</set><!-- ツールベルト ウィンドウサイズ -->
    <set xpath="/windows/window[@name='windowToolbelt']/@pos">-575,92</set><!-- ツールベルト ウィンドウをどこに表示させるか、親コンポーネントからの相対位置 -->
    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@controller='Toolbelt']/grid[@name='inventory']/@cols">15</set><!-- スロットは15個 -->

    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@controller='Toolbelt']/grid[@name='inventory2']/@visible">false</set><!-- 2行目は非表示 -->

    <set xpath="/windows/window[@name='windowToolbelt']/rect/sprite[@fill='{xp}']/@width">1125</set><!-- 大きくなったウィンドウサイズに経験値インジケータを合わせる -->

    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@stat_type='Food']/@width">565</set><!-- 食料インジケータも合わせる -->
    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@stat_type='Food']/filledsprite[@name='BarContent']/@width">563</set>

    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@stat_type='Water']/@width">565</set><!-- 水分インジケータも合わせる -->
    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@stat_type='Water']/@pos">563,-77</set>
    <set xpath="/windows/window[@name='windowToolbelt']/rect/rect[@stat_type='Water']/filledsprite[@name='BarContent']/@width">562</set>
</modconfig>

以上で、Modlet に使うすべてのファイルが揃いました。

Modlet に同梱する

できあがった DLL を、ModInfo.xml と同じ階層に配置してください。ModInfo.xml の記述方法は、上記 Modlet の詳細を参考にしてください。
Config を書き換える XML ファイルも同じ階層に配置して問題ありませんが、7DTD の慣例に従うと、見やすくなるのと同時にわかりやすくなります。

ファイル配置の例:

TestProject/
├ Config/
│ └ XUi/
│   └ windows.xml
├ Harmony/
│ ├ TestProject_HarmonyInit.cs
│ └ FifteenSlotToolbelt_Inventory.cs
├ ModInfo.xml
└ TestProject.dll

既存のセーブデータの破損を避けるため、新しくセーブを作成することをおすすめします。
Mods ディレクトリに配置し、実際にゲームを起動して、ツールベルトが15個に増えているか確かめてみてください。
もしエラーが起こった場合、コンパイル時にエラーとして出てこないものは、[HarmonyPatch] 属性の指定部分です。スペルミスがないか確かめてください。
また、Alpha20.6(b9) の情報がすでに古くなっている可能性や、筆者がダウンロードした 7DTD は、もしかしたら一般的に知られている 7DTD ではない可能性が考えられます。IL をじっくり読んでエラーの改善を試してみてください。

Reference

ここではおもに https://harmony.pardeike.net/articles/patching.html の一部を邦訳したものを記載します。
気がついたら内容が増えているかもしれません。

Prefix

オリジナルメソッドが実行される前に割り込みます。以下のような場合に用いられます:

  • オリジナルメソッドの引数にアクセスしたり、変更したりする場合
  • オリジナルメソッドの戻り値を変更する場合
  • オリジナルメソッド内の処理をスキップさせ、以下で説明する Postfix にすべての処理を任せる場合
  • あらかじめ値をセットしておき、Postfix にて処理を行う場合

usage:

  • static void Prefix(ORIGINAL_ARGS...)
  • static bool Prefix(ORIGINAL_ARGS...)
  • static void Prefix(ref RETURN_TYPE __result, ORIGINAL_ARGS...)
  • static bool Prefix(ref RETURN_TYPE __result, ORIGINAL_ARGS...)
using UnityEngine;

public class OriginalCode
{
    public void Test(int counter, string name)
    {
        // ...
    }
}

[HarmonyPatch(typeof(OriginalCode))]
[HarmonyPatch("Test")]
class Patch
{
    static void Prefix(int counter, ref string name)
    {
        Debug.Log("counter = " + counter); // 値を読む
        name = "test"; // ref キーワードを設定しているため、name が変更された状態でオリジナルメソッドの処理が走る
    }
}
public class OriginalCode
{
    public string Test(int counter, string name)
    {
        // ...
    }
}

[HarmonyPatch(typeof(OriginalCode))]
[HarmonyPatch("Test")]
class Patch
{
    static bool Prefix(ref string __result, int counter, string name)
    {
        __result = "test"; // 戻り値を書き換える
        return false; // false を返すと、オリジナルメソッドの中身は実行されない
    }
}

Postfix

オリジナルメソッドが実行された後に割り込みます。以下のような場合に用いられます:

  • オリジナルメソッドの戻り値にアクセスしたり、変更したりする場合
  • オリジナルメソッドの引数にアクセスする場合
  • オリジナルメソッドが実行された後、必ず実行させたい処理がある場合
  • Prefix にて、あらかじめセットされた値を処理する場合

usage:

  • static void Postfix(ORIGINAL_ARGS...)
  • static void Postfix(ref RETURN_TYPE __result, ORIGINAL_ARGS...)
  • static RETURN_TYPE Postfix(RETURN_TYPE __result, ORIGINAL_ARGS...)

RETURN_TYPE に ref キーワードが使えない場合で、戻り値を変更したい場合、3つ目のメソッド シグネチャを使用します。

public class OriginalCode
{
    public string Test()
    {
        return this.name;
    }
}

[HarmonyPatch(typeof(OriginalCode))]
[HarmonyPatch("Test")]
class Patch
{
    static void Postfix(ref string __result)
    {
        if (__result == "test") {
            __result = "modified";
        }
    }
}
public class OriginalCode
{
    public IEnumerable<int> GetNumbers()
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
}

[HarmonyPatch(typeof(OriginalCode))]
[HarmonyPatch("GetNumbers")]
class Patch
{
    static IEnumerable<int> Postfix(IEnumerable<int> __result)
    {
        yield return 0;
        foreach (var value in __result)
            if (value > 1)
                yield return value * 5;
        yield return 60;
    }
    // 戻り値は [ 1, 2, 3 ] から [ 0, 10, 15, 60 ] に変更される
}
using System.Diagnostics;
using UnityEngine;

public class OriginalCode
{
    public string Test(int counter, string name)
    {
        // ...
    }
}

[HarmonyPatch(typeof(OriginalCode))]
[HarmonyPatch("Test")]
class Patch
{
    static void Prefix(out Stopwatch __state)
    {
        __state = new Stopwatch(); // 独自の値を設定
        __state.Start();
    }

    static void Postfix(Stopwatch __state)
    {
        __state.Stop();
        Debug.Log(__state.Elapsed.ToString()); // 実行時間をログに表示
    }
}

Asset

余談

Modletとは、ゲームに変更を加えるMODと、小さい○○という意味をもつ接尾辞-letを合わせた造語です。
必要最小限の変更で済み、同じXMLを異なるMOD同士が同時に書き換えることができることから、Forumメンバーによってこのような呼称が広まりました。

リンク

https://7daystodie.com/forums/showthread.php?93816-XPath-Modding-Explanation-Thread
https://7daystodie.com/forums/showthread.php?99279-XPath-Error-Checking
https://7daystodie.com/forums/showthread.php?97419-Vanilla-Asset-Bundle-Loading-Hooks
http://www.edankert.com/xpathfunctions.html