Mod Features

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

Mod Loading

Create a mod

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;

Build a mod

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.

Debug a mod

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.

Operations such as loading all assets from AssetBundle are ignored when running a virtual mod beacuse 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 virtual mods are experimental and some discrepancies are possible. Always test a release mod before publishing it.


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/.

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");

This is a list of accepted types:

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

The IHasModSaveData interface allows mods to store and retrieve save data.

First of all a mod must define a class to hold the data that needs to be saved. SaveLoadManager sends and requests the mod an instance of this class, which is automatically serialized in a file specific to the mod. This is modFileName.txt inside a save folder, where modFileName is the name excluding the extension of modFileName.dfmod. In the worst case this file can be deleted but it may be not a bad idea to make use of the versioning system for a smoother upgrade. See FullSerializer docs for details on versioning.

Deleting this file don’t affect changes that the mod could have caused in the core game! Always refer to mod developer for instructions on how to uninstall a mod.

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.

Loading a save game must result in recreating the same situation as when the save was made and must not be affected by the game that was previosuly running. Make sure to reinitialize all mod functionalities based on the game instance.

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");


A mod called modname is automatically linked to a file named mod_modname.txt in StreamingAssets/Text and/or a file name textdatabase.txt inside the mod bundle. The table in StreamingAssets takes precedence.


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.

Mod Strings

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").

If a key is not found in the table from the Text folder, is seeked in the fallback table inside the mod. This means that no additional files other than the dfmod are required for a standard english version. Mod developers only need to export strings to a text file and build it as an asset.

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);

For performance reasons one may want to load all assets in the background and 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>();

Starting from version 0.6, 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

World and Locations

Refer to the following tutorials:

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;

If a prerequisite is not found do not let a NullReferenceException be thrown, but rather take appropriate actions such as printing to log “modX was terminated because it requires modY.” Also 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 });