Mod Features

This page provides an insight into the modding system of Daggerfall Unity and documentation for its patterns and features.

Mod Loading

Daggerfall Unity Mod Builder as it appears inside the Unity Editor


Inside the Unity Editor, go to Assets/Game/Mods and create a subfolder for your mod. A ReadMe.txt file in this folder suggest how to organize files inside it. Then open Daggerfall Workshop > Mod Builder, fill mod informations (title, author, version etc.) and save the manifest file inside the folder from previous point (i.e. Assets/Game/Mods/Example/Example.dfmod.json).

If the mod contains scripts, a static intro point is needed to instantiate one of them and effectively start the mod.

namespace ExampleMod
    public class Example : MonoBehaviour
        static Mod mod;

        [Invoke(StateManager.StateTypes.Start, 0)]
        public static void Init(InitParams initParams)
            mod = initParams.Mod;
            var go = new GameObject(mod.Title);

        void Awake()
            var settings = mod.GetSettings();
            // ---

            mod.IsReady = true;


Open Daggerfall Workshop > Mod Builder, ensure that all the required assets are listed and build the mod. Three files with .dfmod extension are created, one for each OS. Additional meta files are also generated but are not required for mod operation.

The .dfmod file can be placed inside StreamingAssets/Mods, or any subdirectory, both in game build and Unity Editor.


Mod manifest files automatically generate a virtual mod inside the Unity Editor, meaning that all assets listed are assumed to be part of the mod even without a physical .dfmod file. A mod inside StreamingAssets/Mods overrides its virtual version.

These are the steps for running a mod inside the editor in virtual mode:

  1. When you create a new mod with the mod builder it asks you where to save the manifest file. Pick a subfolder of Assets/Game/Mods, for example Assets/Game/Mods/EXAMPLE/EXAMPLE.dfmod.json.
  2. Select all the assets that should be shipped with the mod inside the mod builder and save. You don’t need to build.
  3. Launch the game with the play button and EXAMPLE will be available as a virtual mod.

Operations such as loading all assets from AssetBundle are ignored when running a virtual mod because assets are immediately available. In most cases everything should work just fine, but conditional execution is possible if you have particular requirements.

    if (mod.IsVirtual)
        Debug.LogFormat("{0} is running in virtual mode.", mod.Title);
        Debug.LogFormat("{0} is running in editor mode.", mod.Title);
    Debug.LogFormat("{0} is running in release mode.", mod.Title);

Please note that some discrepancies are possible when running in virtual mode. Always test a release mod before publishing it.


C# scripts can be provided by a mod using one of the following methods:

  • Add C# files (.cs) as text assets to the list of assets bundled with the mod; they will be compiled at runtime with a portable mcs compiler. Please note that enums, string interpolation and tuple syntax are not supported.
  • Add C# files (.cs) as text assets to the list of assets bundled with the mod and enable the flag Precompiled (experimental); they will be compiled at build time in a single assembly using the same Mono compiler used for the core game.
  • Add one or more .NET assemblies (.dll.bytes) as binary assets to the list of assets bundled with the mod. This is possible using Visual Studio and following these steps:
    • File > New > Project. Pick Class Library (.NET Framework). Choose project name and location. Select NET Framework 4.6.2. (note: any 4.x should be fine).
    • Open Project > {project_name} Properties > Reference Paths and add \Daggerfall Unity\DaggerfallUnity_Data\Managed from the game installation you want to target. This is the folder that contains all the .dll files. Then Project > Add Reference… > Assemblies > Extensions and pick the following libraries: UnityEngine.CoreModule (Unity API), Assembly-CSharp (Daggerfall Unity API) and FullSerializer – Unity (optional, json serializer API).
    • Use Project > Add Class… to add new files to the solution.
    • Change the configuration from Debug to Release in the top bar and run Build > Build Solution. A .dll file is created inside bin/Release. Rename it to .dll.bytes, import it with the Unity Editor and include it with your mod. Make sure to use a globally unique name.


Mod settings can be created or changed with the UI settings editor. Open it from the the tools menu and select a folder; for example Addons/ModName/.

UI ControlC# TypeDescription
checkboxbooleanA flag that can be set or unset
multiplechoiceintIndex of selected choice
int sliderintAn integer number in a range
float sliderfloatA floating point number in a range
int tupleTuple<int, int>Two integer numbers
float tupleTuple<float, float>Two floating point numbers
textboxstringA raw string
color pickerColor32A color
Types available for mod settings.

Settings consist of a list of options grouped inside sections. Each section has a name, an optional description and a advanced flag. Each option has a name, an optional description, a type, a default value and a number of other values depending on type. The type determines how the option is proposed to the player with a in-game UI. For example a boolean is a simple checkbox while a number is drawn as a slider in the requested range.

The end result is saved in a file named settings.json inside the current folder. This file needs to be bundled inside the mod.

Settings can be retrived in game from the mod instance:

var settings = mod.GetSettings();
int number = settings.GetValue<int>("section", "key");

It is possible to benefit of live changes to mod settings by setting the callback Mod.LoadSettingsCallback.

private void Awake()
    mod.LoadSettingsCallback = LoadSettings;
    mod.IsReady = true;

private void LoadSettings(ModSettings settings, ModSettingsChange change)
    if (change.HasChanged("Foo"))
        var number = settings.GetValue<int>("Foo", "Number");
        var color = settings.GetValue<Color32>("Foo", "Color");

    if (change.HasChanged("Bar", "Text"))
        var text = settings.GetValue<string>("Bar", "Text");


A preset is a group of values for a given number of settings, which can be provided by the mod developer or created by players and shared with the community. Presets are identified by a title and a description.

  • Presets created from the mod settings editor are saved in a file named presets.json inside the current folder. This file needs to be bundled inside the mod.
  • Presets created from the in-game UI are saved to PersistentDataPath/Mods/GameData/GUID/modpresets.json (EditorData when testing with the Unity Editor). Previously they were stored  in a file named modFileName_presets.json, where modFileName is the name excluding the extension of modFileName.dfmod, inside the folder StreamingAssets/Mods. Existing presets are moved automatically to new position.
  • Presets can also be created by players and shared; the same functionality allows mod developers to provide presets for other mods by placing json files inside StreamingAssets/Presets/TargetModFileName or %ModRoot%/Assets/Presets/TargetModFileName.  Read Share Presets section on the main page for deails.
  • Legacy support is currently also provided for files named modFileName_presets_name.json, where name is an unique identifier, and placed in the same folder of second point. Such file contains a list of one or more presets, each one with a title and description. This functionality is deprecated and will be eventually removed.

Local settings

Local mod settings are stored inside PersistentDataPath/Mods/GameData/GUID/modsettings.json (EditorData when testing with the Unity Editor). Previously, they were saved alongside mod files in the folder StreamingAssets/Mods with the name modFileName.json, where modFileName is the name excluding the extension of modFileName.dfmod. Existing settings are moved automatically to new position.


Mod settings can provide a version number which is used to validate presets and local settings. Outdated presets can still be used but a warning is shown in the preset selection list, while local settings are deleted and replaced with a fresh, updated, copy. Mod settings version is different than mod version because a mod can benefits of updates that don’t require wiping local settings.

Save Data

IHasModSaveData interface allows to store and retrieve custom save data.

A mod must define a class to hold the data that needs to be saved. The attribute fsObject can be used for versioning; see FullSerializer docs for more details.

public class MyModSaveData
    public string Text;
    public List<int> Items;

When the mod is started we have to link the interface to the Mod instance given by InitParams. We can do this in Init, Start or another method used to retrieve the singleton.

mod.SaveDataInterface = instance;


This is the type of the data class, used for deserialization.

public Type SaveDataType
    get { return typeof(MyModSaveData); }


This is called when the mod is installed for the first time or the serialized file is missing for any reason. In most cases this is just a new instance of the save data class, but it’s also a chance to set initial values different than type defaults.

public object NewSaveData()
    return new MyModSaveData
        Text = "Default text",
        Items = new List<int>();


This method asks the data to serialize when a new save is created. If the return value is null the file is not created.

public object GetSaveData()
    return new MyModSaveData
        Text = text,
        Items = items;


Gives the mod the data deserialized (or result of NewSaveData()) to be applied in the running instance. Be sure to set all values to avoid leftovers from the previously running save.

public void RestoreSaveData(object saveData)
    var myModSaveData = (MyModSaveData)saveData;
    text = myModSaveData.Text;
    items = myModSaveData.Items;

Custom Data

Mod.PersistentDataDirectory can be used to store custom data shader by all saves. Each mod is assigned a directory but it’s up to the mod itself to create the folder, load and store data as well as deleting it when not needed anymore. The whole folder is also deleted if mod is uninstalled.

string filePath = Path.Combine(mod.PersistentDataDirectory, "PersistentData.txt");
File.WriteAllText(filePath, "content");

Mod.TemporaryCacheDirectory can be used to store temporary data shared by all saves. Each mod is assigned a directory but it’s up to the mod itself to create the folder, load and store data. The content of this folder can be deleted at any time by the OS.

string filePath = Path.Combine(mod.TemporaryCacheDirectory, "Cache.txt");
File.WriteAllText(filePath, "content");


Localized text content can be provided by including csv-like tables with the following naming:

  • textdatabase.txt
  • [<identifier>]textdatabase.txt (i.e. [en]textdatabase.txt)
  • [<identifier>(<GUID>)]textdatabase.txt (i.e. [fr(7e0510a9-be7d-4fd4-9c89-ed2f2b2e5fb3)]textdatabase.txt))
  • mod_modname.txt inside StreamingAssets/Text

Localize Settings

The file settings.json bundled with a mod provides informations used by the mod manager to draw controls in the mod settings window and store values to a local file. This includes setting names and descriptions/tooltips. These text string are seked automatically in the language table using the following patterns:


If a key is missing, the default value from settings.json is used. This means there is no need to provide an english table. No support is required from individual mods, this is all managed by the mod manager.

The mod settings editor can automatically export an english table that can be given to translators for localization.

Localize Additional Content

The Mod instance includes a method called Localize() which can be used to seek additional localized strings from the text table. The key can be a string or a succession of strings wich are concatenated.

string Localize(string key) // key, text
string Localize(params string[] keyParts) // a.b.c, text
schema: *key, $text

Fountain.Drink, "Press key to drink at the fountain."
string text = mod.Localize("Fountain.Drink").

Asset Loading

The Modding System provides a ready-to-use framework to store assets and load them at run time, which is internally based on AssetBundles. A mod bundle can contains any kind of asset that derives from UnityEngine.Object, including textures, meshes, sounds, shaders etc. 

Asset-Injection retrieves assets from all mods automatically to replace classic assets without any interaction with the mod. See Import Assets for details.

Assets bundled with a mod can be retrieved from the AssetBundle with the Mod instance.

// Load and get a reference to an asset
Material mat = mod.GetAsset("assetName");

// Load and clone an asset
GameObject go = mod.GetAsset("assetName", true);

You can also load all assets in the background and then dispose the AssetBundle.

// Load assets from AssetBundle at startup
IEnumerator loadAssets = mod.LoadAllAssetsFromBundleAsync(true);

// Get a reference to individual assets when needed
Texture tex = mod.GetAsset("assetName");


A serialized GameObject must not have any script wich is not defined in the Unity Engine or Daggerfall Unity APIs. Custom scripts that derives from UnityEngine.Component can be instantiated at run time.

GameObject go = new GameObject();
Foo foo = go.AddComponent<Foo>();

A MonoBehaviour class imported from a mod can be marked with the ImportedComponentAttribute to automatize this process. During mod building, imported component instances are serialized to a separate file and stripped from the prefab. When this prefab is requested at run time, imported components are recreated and deserialized, with the types retrieved from mod assemblies. Asset references are also serialized with their name, then seeked in the mod assetbundle.

public class Foo : MonoBehaviour

Loose Files

The main purpose of loose files is to easily provide contributions to  Asset-Injection, but they can also be useful if an asset file is to be provided, edited or replaced by user, which is otherwise impossible if the asset is stored in the mod bundle.

Loose files are organized in folders for different asset types; mods that want to benefit of this feature should place assets within subfolders to avoid name collision with other mods. Valid examples are StreamingAssets/Textures/ModName or StreamingAssets/Textures/AuthorName/ModName.

// Import a png file from StreamingAssets/Textures to override the bundled texture asset 
using DaggerfallWorkshop.Utility.AssetInjection;

Texture2D tex;
if (!TextureReplacement.TryImportTextureFromLooseFiles(Path.Combine("ModName", "Example"), mipMaps, encodeAsNormalMap, readOnly, out tex))
    tex = mod.GetAsset<Texture2D>("Example");

Game Resources


New quest actions, implementing IQuestAction, can be registered with QuestMachine.RegisterAction, when requested by OnRegisterCustomActions event.


New effects, inheriting from BaseEntityEffect, can be registered with EntityEffectBroker.RegisterEffectTemplate; It is also possible to override classic effects.


Formulas can be overridden from FormulaHelper, registering a delegate with FormulaHelper.RegisterOverride().

Item Templates

Items data can be overridden providing a file named ItemTemplates.json. The property index is mandatory because is the identifier for the item; everything else can be omitted not to alter classic value.

        "index": 151,
        "name": "Casual Pants",
        "baseWeight": 0.5,
        "hitPoints": 150,
        "capacityOrTarget": 0,
        "basePrice": 5,
        "enchantmentPoints": 40,
        "rarity": 1,
        "variants": 4,
        "drawOrderOrEffect": 10,
        "isBluntWeapon": false,
        "isLiquid": false,
        "isOneHanded": false,
        "isIngredient": false,
        "worldTextureArchive": 204,
        "worldTextureRecord": 0,
        "playerTextureArchive": 239,
        "playerTextureRecord": 16

Mods Interaction

The ModManager singleton allows to retreve a Mod instance from its title.

Mod mod = ModManager.Instance.GetMod("modTitle");
bool modFound = mod != null;
bool modStarted = mod != null && mod.IsReady;

Consider using GetModFromGUID(string modGUID) to avoid breaking if the title is changed.

Message Receiver

The message receiver allows to exchange messages and data with other mods. The receiving mod must have a delegate of type DFModMessageReceiver assigned to Mod.MessageReceiver. A reply can be sent with the callback parameter.

void ModManager.Instance.SendModMessage(string modTitle, string message, object data = null, DFModMessageCallback callback = null);
void DFModMessageReceiver(string message, object data, DFModMessageCallback callBack);
void DFModMessageCallback(string message, object data);

A simple example:

mod.MessageReceiver = (string message, object data, DFModMessageCallback callBack) =>
    if (message == "numberRequest" && callBack != null)
        callBack("numberReply", 0);
ModManager.Instance.SendModMessage("modTitle", "numberRequest", null, (string message, object data) =>
    int number = (int)data;

The mod message system can also be used to handle events. Note that the receiver is setup from Awake (or the static entry point if you prefer) while the handler registration is performed from Start. This is consistent with the pattern of using Awake to initialize self and Start to access other objects, ensuring that you aren’t accessing something that doesn’t exist yet.

public event Action OnGameObjectSpawned;

void Awake()
    mod.MessageReceiver = (string message, object data, DFModMessageCallback callback) =>
        switch (message)
            case "RegisterOnGameObjectSpawned":
                OnGameObjectSpawned += data as Action<GameObject>;
void Start()
    ModManager.Instance.SendModMessage("Mod Title", "RegisterOnGameObjectSpawned", (Action<GameObject>)((go) =>


It is possible for a mod to declare other mods as dependencies that must also be installed, or define criteria to validate local load order. For example you can enforce that your mod is positioned below another one if is available or state compatibility only with a certain version.

To do so, open the mod manifest file (.dfmod.json) and add a new property named Dependencies:

"Files": [
"Dependencies": [
        "Name": "example-mod",
        "IsOptional": false,
        "IsPeer": false,
        "Version": "1.0.0"

These are the supported properties:

  • Name: Name of target mod.
  • IsOptional: If true, target mod doesn’t need to be available, but must validate these criteria if it is.
  • IsPeer: If true, target mod can be positioned anywhere in the load order, otherwise must be positioned above.
  • Version: If not null this string is the minimum accepted version with format X.Y.Z. Pre-release identifiers following an hyphen are ignored in target version so they must be omitted here. For example “1.0.0” is equal to “1.0.0-rc.1”.


Scripts from other mods can be used with reflection. In the example below a class type is retrieved from a mod assembly and a new object is instantiated. Then a method is invoked, taking care of using a correct function signature.

// Type safe code
Example example = new Example();
int result = example.MethodName("arg0", "arg1");
// Find the type
Mod mod = ModManager.Instance.GetMod("ModTitle");
Type type = mod.GetCompiledType("type");

// Make a new class instance
object instance = Activator.CreateInstance(type);

// Call a method on the class
MethodInfo method = type.GetMethod("MethodName", BindingFlags.Public | BindingFlags.Instance);
int result = (int)method.Invoke(instance, new object[] { "arg0", "arg1" });

If we want to access a specific instance, the procedure is the same but we need to retrieve a reference to the object. This can be requested via Message Receiver or, if the aim is to edit an instantiated gameobject, it can be found inside the scene.

// Get a MonoBehaviour instance from a gameobject
Component component = go.GetComponent("type");

// Read the value of a field
FieldInfo field = component.GetType().GetField("FieldName", BindingFlags.Public | BindingFlags.Instance);
float value = (float)field.GetValue(component);

This pattern can be used for all members of a type, but it’s important for performance to store all results of reflection that need to be accessed again. For more informations on reflection see C# docs Reflection and Dynamically Loading and Using Types.


If a mod relies on a framework provided by another mod, it is not unlikely that events are needed. The following example shows how to subscribe to an instance event in a class that follows the singleton pattern. Subscription to a static event is the same except that a null value is passed in place of the instance.

// Type safe code
Example example = Example.Instance;
example.EventName += HandlerName;
// Get instance from static property
PropertyInfo propertyInfo = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static);
object instance = propertyInfo.GetValue(null, null);

// Subscribe to instance event
EventInfo eventInfo = type.GetEvent("EventName", BindingFlags.Public | BindingFlags.Instance);
Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this.GetType(), "HandlerName");
eventInfo.GetAddMethod().Invoke(instance, new object[] { handler });