Modding Dead In Vinland
Dead In Vinland is the older sibling of Dead In Antares from the same studio (Ishtar Games / CCCP). It is also a Unity Mono game, which means its gameplay logic ships as plain .NET assemblies that any modder can decompile, reference, and patch at runtime. The modding model on Vinland is simpler than on Antares because there is no established plugin community yet: the convention is one mod, one DLL, loaded directly by Doorstop. This page explains how that works, how to install a Vinland mod, how to write one, and what to watch out for when more than one mod wants to coexist.
Reference mod: FireWarning on Nexus Mods is a published mod that follows the exact pattern described on this page (single C# file, bare Doorstop, three-file install). Read the rest of this page for the model and the gotchas, and use FireWarning as a worked example if you want to see them all in action.
Ground rules for mods
Same three rules as the Antares modding scene. They are the bare minimum for the ecosystem to coexist with the developers and with itself.
- Never unlock paid content. A mod must not add, re-enable, or circumvent functionality that the developers or publisher gatekeep behind a DLC, a Supporter Pack, or any other paid add-on. Balance tweaks, UI fixes, quality-of-life improvements and translations are fine. Stripping the paid tier out kills the incentive to keep the game alive, and a thriving mod scene that does this ends up with nothing to mod.
- Mods are free. Never charge money for a mod, never gate a mod behind a Patreon tier, never put a mod behind a paywall, a donation wall, or a "free for supporters only" release window. Tip jars are fine. Paid mods are not.
- The modding community solves its own problems. Bugs that only appear with mods installed are the modding community's problem, not the developers'. When reporting a suspected game bug, reproduce it on an unmodded install first. When two mods conflict, the fix is between the mod authors, not a ticket to the studio.
What "modding" means here
A mod for Dead In Vinland is a compiled .NET DLL that runs inside the game's process and patches or extends its behavior at runtime. It is not a save-edit, a file replacement, or a texture pack. The DLL is loaded automatically at game startup by a small loader called Doorstop, which gives your code access to the game's C# classes (DayManager, CharactersManager, UpgradeManager, WeatherManager, InventoryManager, etc.) as if you were writing them yourself. From inside that process you can read or overwrite any runtime state the game holds in memory.
Because mods run inside the game, anything the game does in C# is modifiable:
- Change displayed text (localizations, labels, tooltips).
- Change numeric balance (drop rates, resource costs, stat gains, fire drain, weather odds).
- Add new UI elements, warning panels, or HUD overlays.
- Change the behavior of existing systems (day progression, character recruitment, expedition outcomes).
- Log, measure, or instrument the game for research or debugging.
What mods cannot easily do: replace compiled art assets, replace audio in the FMOD bank, or ship new 3D models. Those live outside the C# layer and require a different toolchain.
The runtime model: just Doorstop, no BepInEx
This is the biggest difference from the Antares modding scene. On Antares, every mod runs under BepInEx, a Unity-modding framework that scans a plugins folder and loads multiple DLLs. On Vinland, the convention so far is to skip BepInEx entirely and target Doorstop directly. There is one reason for this: when a game has zero or one mods, BepInEx is infrastructure you do not need yet. As soon as a second mod ships and the two want to coexist, BepInEx (or an equivalent plugin host) becomes the right answer. See "Adding a second mod" below.
1. Doorstop, the foothold
Windows has a feature where a program will load a DLL named winhttp.dll from its own folder before falling back to the system copy in C:\Windows\System32\. Doorstop exploits this: it ships a fake winhttp.dll that you drop next to Dead In Vinland.exe. When you launch the game, Windows auto-loads Doorstop's DLL first. Doorstop reads a small config file called doorstop_config.ini that sits next to it, learns which .NET assembly to hand control to, and passes execution there before the game proper even starts.
That is the entire trick. Doorstop is a minimal proxy DLL whose only job is "be the first code that runs". It does not itself know anything about Dead In Vinland or about modding. It just executes the assembly the .ini tells it to.
2. The mod IS the assembly Doorstop loads
On a Vinland install with one mod, doorstop_config.ini contains exactly one line that matters:
target_assembly=YourMod.dll
That is it. There is no plugin host in between. Doorstop loads YourMod.dll directly, looks for a method called Doorstop.Entrypoint.Start() inside it (this name is Doorstop's calling convention), and invokes it. The mod's entire bootstrap runs from that single static method.
The trade-off is that this layout cleanly handles exactly one mod. Two mods that both want to be the assembly Doorstop loads cannot both win. The "Adding a second mod" section below explains the options.
Install = drop three files next to the exe
A Vinland mod author ships a single ZIP file containing three files. Installation is:
- Right-click the ZIP, "Extract All...", into the folder C:\Program Files (x86)\Steam\steamapps\common\Dead In Vinland\ (or wherever your Steam library lives, right-click the game in Steam → Manage → Browse local files to find it).
- Launch the game from Steam as normal.
That is it. There is no install step beyond the extract, and uninstalling is deleting the three files you added.
The three files are always the same kind:
- winhttp.dll: Doorstop's hijack DLL, identical across every Vinland mod.
- doorstop_config.ini: tells Doorstop which DLL to load. Different per mod (it names the mod's own DLL).
- The mod's own DLL (YourMod.dll): the actual code.
The file layout
A fully installed game folder with one mod looks like this:
Dead In Vinland\ ← Steam install folder
├── Dead In Vinland.exe ← the game itself (untouched)
├── Dead In Vinland_Data\ ← Unity assets and Managed\ (untouched)
├── winhttp.dll ← Doorstop loader
├── doorstop_config.ini ← target_assembly=YourMod.dll
└── YourMod.dll ← the mod
No subfolders, no plugins directory, no BepInEx. Every part of this layout is load-bearing, so it is worth walking through the reasoning.
winhttp.dll must be named exactly that, at the game root
Doorstop is the only piece that runs before the game's own code gets to initialize anything. That first-mover status is provided by Windows' DLL search order rule, which only applies to specific DLL names that real Windows programs routinely load. winhttp.dll is the conventional choice for game-modding loaders because nearly every Steam-wrapped game talks to winhttp.dll at some point during startup, so the hijack is guaranteed to fire. Rename it and nothing loads. Put it in a subfolder and nothing loads.
doorstop_config.ini sits next to winhttp.dll
Doorstop reads its config from the same folder it was loaded from. The file is short and the only line that matters is target_assembly. On Vinland that line points at the mod's own DLL directly (target_assembly=YourMod.dll), not at a plugin host.
The mod DLL sits at the game root, not in a subfolder
Because Doorstop loads the mod assembly by the path written into doorstop_config.ini, the path is whatever you write there. Convention is to keep it at the root next to the exe so the install instructions are "extract three files into the game folder, done". A subfolder would work too but adds a path component the user has to get right.
Writing a mod, the smallest viable template
A bare-Doorstop Vinland mod is a single C# source file that compiles into a single DLL. The only convention you must follow is: declare a public static Start() method inside a public static Doorstop.Entrypoint class. Doorstop will find it by name and call it.
using System;
using System.IO;
using System.Reflection;
namespace Doorstop
{
public static class Entrypoint
{
public static void Start()
{
// This runs BEFORE Unity's managed environment is fully wired up.
// Do not touch UnityEngine types here directly. Defer that to a
// later callback (see below).
File.WriteAllText("MyMod.log", "MyMod entrypoint reached\n");
// Step 1: install an AssemblyResolve handler so .NET can find
// UnityEngine.dll and Assembly-CSharp.dll under the Managed folder
// when your code finally references them.
string gameDir = Directory.GetCurrentDirectory();
string managedDir = Path.Combine(gameDir, "Dead In Vinland_Data", "Managed");
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string shortName = new AssemblyName(args.Name).Name;
string path = Path.Combine(managedDir, shortName + ".dll");
return File.Exists(path) ? Assembly.LoadFrom(path) : null;
};
// Step 2: wait until Assembly-CSharp is loaded before doing anything
// that touches game code, then attach a MonoBehaviour for per-frame work.
AppDomain.CurrentDomain.AssemblyLoad += (sender, args) =>
{
if (args.LoadedAssembly.GetName().Name == "Assembly-CSharp")
{
// ... subscribe to SceneManager.sceneLoaded, then create a
// GameObject and AddComponent your MonoBehaviour to it ...
}
};
}
}
}
Compile it into MyMod.dll, write doorstop_config.ini with target_assembly=MyMod.dll, drop the two files plus winhttp.dll into the game folder, launch the game. The log file appears next to the exe and you have a working mod.
Why the AssemblyResolve dance
This is the one non-obvious gotcha for bare-Doorstop Vinland mods. Doorstop calls your Start() method extremely early, before Unity's native-to-managed bindings are all in place. If Start() directly references a UnityEngine type, the .NET JIT compiler tries to resolve that type when it compiles the method body, which forces a load of UnityEngine.dll from a path .NET does not know about. The result is a FileNotFoundException and your mod silently fails to load.
The fix is two-step:
- In
Start()itself, do not write any code that references UnityEngine types directly. Keep it pure System.* calls. Install anAssemblyResolvehandler that knows where to find UnityEngine.dll (under Dead In Vinland_Data\Managed\). - Put all your Unity-touching code in a separate method that
Start()only references through a delegate or a deferred callback (like anAssemblyLoadevent handler). That way, the JIT does not try to resolve UnityEngine until after the handler is in place, and the handler resolves the path from the Managed folder.
The same trick works for Assembly-CSharp.dll, which is where the game's own classes live. A robust bootstrap waits for Assembly-CSharp to be loaded, then waits for the first scene to load, and only then creates a GameObject and attaches a MonoBehaviour for any per-frame work.
Why csc, not MSBuild
A single-file Vinland mod compiles in one csc.exe invocation with explicit -reference flags pointing at the Managed\ folder. There is no packages.config to manage, no NuGet restore to run, no csproj to keep in sync with the Unity runtime. A typical Vinland mod's build.sh is six lines and the references are mscorlib.dll, System.dll, System.Core.dll, UnityEngine.dll, UnityEngine.CoreModule.dll, UnityEngine.IMGUIModule.dll, and Assembly-CSharp.dll. Add UnityEngine.TextRenderingModule.dll if your mod calls anything in the GUI text family.
Useful game classes for Vinland modders
The Dead In Vinland Assembly-CSharp.dll exposes a lot of public state. A few classes show up in almost every mod:
DayManager:_TimeOfDay(int 0..3),_IsMorning,_IsMidDay,_IsEvening,_IsNight. The day clock the game uses for everything time-of-day related.WeatherManager: current weather, weather drain modifiers, transition probabilities (these are scaled by difficulty, fire drain itself is not).UpgradeManager/BuildingManager: which camp upgrades are built (CookingPot1/2/3, Shelter1/2, FireConservation1/2, IronCookingPot1/2, etc.). Several of these directly modify fire and resource numbers.InventoryManager: current resource counts (wood, food, water, materials).CookingPotManager: queued recipes and the per-recipe fire cost.CharactersManager: the camp roster, each character's stats, current task, and state effects.WorkshopManager/ForgeManager: selected recipes and reserved materials, useful when you need to subtract resources that are spoken for but not yet consumed.
None of these are documented externally. The way you discover them is the same way you do it for Antares: open Assembly-CSharp.dll in dnSpy or ILSpy and read the public API. The class names are stable across the v1.x patch series.
Adding a second mod
Bare Doorstop loads exactly one assembly. The moment a Vinland install needs two mods, the model has to grow. There are three options:
- Switch the install to BepInEx. Drop BepInEx (x86, since Dead In Vinland.exe is a 32-bit Unity build) into the game folder, then point doorstop_config.ini at BepInEx.Preloader.dll instead of at any individual mod. From then on, every mod ships as a DLL inside BepInEx\plugins\<ModName>\ and BepInEx's plugin scanner loads them all. This is the same model the Antares modding scene already uses, and it scales to any number of mods. The downside is the install is no longer "three files", it is "BepInEx tree plus a per-mod subfolder".
- Write a tiny custom multi-mod loader. A mod author can ship their own assembly that targets Doorstop and then scans a known subfolder for further DLLs and invokes a known entry method on each. This is reinventing BepInEx in 50 lines. It is fine for a small, controlled set of mods, but you lose Harmony, the BepInEx config system, the existing mod ecosystem's conventions, and the BepInEx log.
- Pre-merge the mods at build time. If two mods are maintained by the same author and never need to be installed independently, the simplest answer is to compile both into a single DLL and ship that as one mod. No loader changes, no plugin host. This obviously does not work for unrelated mods by different authors.
Option 1 is the right answer once a real Vinland modding community exists. While the published mod count is small, the bare-Doorstop layout is simpler for users to install and easier to support.
Bitness: Vinland is 32-bit
Dead In Vinland.exe is a 32-bit (x86) Windows executable. This is unusual for modern Unity games and it has two consequences for modders:
- You must use the x86 build of Doorstop, not x64. Dropping the x64 winhttp.dll into the game folder produces the symptom "no log file appears, the game just launches as if no mod were there", because Windows refuses to load a 64-bit DLL into a 32-bit process and silently moves on to the system winhttp.dll.
- If you ever switch this install to BepInEx, you need the BepInEx x86 build for the same reason. The BepInEx x64 build will silently do nothing.
Dead In Antares, by contrast, is 64-bit. Do not copy a winhttp.dll between the two games' folders.
Where the logs go
With bare Doorstop there is no shared log file like BepInEx's LogOutput.log. Each mod writes its own log file wherever it likes. The convention is "next to the game exe, in the same folder where the user dropped the three install files", named after the mod (YourMod.log). The benefit is that anyone troubleshooting an install can find the mod's log without hunting through subfolders.
A well-behaved Vinland mod writes enough to its log on startup to verify it loaded without launching the game's own UI: a sentinel line written before anything else (so a crash in the next line does not eat the evidence), the bootstrap chain (Doorstop entry, AssemblyResolve handler installed, Assembly-CSharp seen, scene loaded, MonoBehaviour attached), and one line per state transition the mod cares about. If none of that appears in the log, the mod's DLL never reached Doorstop, usually because winhttp.dll is the wrong bitness or doorstop_config.ini points at the wrong filename.
Unity itself writes a player log at %APPDATA%\..\LocalLow\CCCP\Dead In Vinland\Player.log (the publisher folder is "CCCP", the developer's earlier name). Engine-level errors and missing-asset warnings land there. If your mod's own log looks fine but the game still misbehaves, check the player log next.
Compatibility and game updates
When the game updates, three things can break a mod:
- Renamed classes or methods. A mod that calls
DayManager._TimeOfDaywill throw aMissingFieldExceptionif that field is renamed in an update. The mod has to be rebuilt against the new assembly. - Changed enum values. Vinland uses enums like
E_WeatherandE_TimeOfDay. If the game adds, removes, or reorders enum members, mods that hardcode integer comparisons can silently misbehave. - Unity version change. A major engine upgrade can change the .NET runtime surface or the bitness, which would invalidate the Doorstop install entirely.
None of these are modding-specific. They are the standard cost of binary compatibility. The pragmatic workflow after a game update is: launch the game, check the mod's log file for exceptions, rebuild against the new Assembly-CSharp.dll if anything broke, ship a new release.
Save files: a well-written Vinland mod never touches save files. The mod runs in the live game process and can read whatever the game has in memory without modifying anything on disk in the save folder. That makes mods safe to add or remove mid-playthrough as long as they are not actively persisting state of their own.
Related
- Modding Dead In Antares: the BepInEx-based modding model used by the sibling game. Most of the fundamentals are the same, but the file layout and the plugin host differ.
- Unity Doorstop: github.com/NeighTools/UnityDoorstop
- BepInEx (for the day Vinland mods need a real plugin host): github.com/BepInEx/BepInEx
- Harmony (runtime method patching library, usable from a bare-Doorstop mod too if you reference its DLL): github.com/pardeike/Harmony