Modding
Dead In Antares is a Unity game built with IL2CPP-free Mono scriptable backend, which means it exposes its gameplay logic as plain .NET assemblies. That makes it straightforwardly moddable using the same tooling the rest of the Unity modding community relies on. This page explains the model, the file layout, and, importantly, why it looks the way it does, so a new modder can make informed changes instead of blindly copying a template.
Reference mod: German Translation on Nexus Mods is a published mod that follows the exact pattern described on this page (single C# file, BepInEx plugin, plugin folder containing the DLL plus its data files). Read the rest of this page for the model and the gotchas, and use German Translation as a worked example if you want to see them all in action.
Ground rules for mods
Before touching any code, the modding community should agree on three rules. They're the bare minimum for this 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. "Here is the Digital Labor Companion for free" is not. Building the game costs money; stripping the paid tier out kills the incentive to keep developing it, 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. Selling mods is also its own legal mess. 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 a mod conflicts with another mod, the fix is between the mod authors, not a ticket to the studio. Ishtar Games is a three-person team and has to spend its support budget on the base game.
What "modding" means here
A mod for Dead In Antares is a compiled .NET DLL that runs inside the game's process and patches or extends its behavior at runtime. It is not a file replacement, a save-edit, or a texture pack. The DLL is loaded automatically at game startup by a small loader called BepInEx, which gives your code access to the game's C# classes (Localization, CharactersManager, DayManager, 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).
- Add new UI elements or buttons.
- Change the behavior of existing systems (day progression, random events, character recruitment).
- 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: BepInEx + Doorstop
Every mod in this ecosystem piggybacks on two pieces of shared infrastructure. It's important to understand what they are, because the file layout on disk is a direct consequence of how they work.
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 Antares.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's the entire trick. Doorstop is a minimal proxy DLL whose only job is "be the first code that runs." It doesn't itself know anything about Dead In Antares or about modding — it just executes the assembly the .ini tells it to.
2. BepInEx — the plugin host
The assembly Doorstop loads is BepInEx's BepInEx\core\BepInEx.Preloader.dll. BepInEx is a Unity-modding framework that does three things:
- It sets up the .NET environment so plugin code can reference
Assembly-CSharp.dllandUnityEngine.dll. - It provides the
BaseUnityPluginbase class and the[BepInPlugin]attribute that every mod extends from. - It scans the folder
BepInEx\plugins\and loads every DLL it finds there as a plugin, calling each plugin'sAwake()method after the game has initialized enough for C# code to run safely.
Point 3 is the one that makes BepInEx special: it's a directory-scan loader, not a single-entrypoint loader. You don't need to register mods, configure a load order, or edit a manifest file. Dropping a DLL into BepInEx\plugins\ is the entire installation.
Install = unzip
A mod author ships a single ZIP file. Its contents are arranged so that extracting the ZIP into the Dead In Antares install folder puts every file exactly where it belongs. The end user doesn't run an installer, doesn't edit config files, doesn't launch anything other than the game. The workflow is:
- Right-click the ZIP, "Extract All…", choose the folder
C:\Program Files (x86)\Steam\steamapps\common\Dead in Antares\. - Launch the game from Steam as normal.
That's it. There is no "install step" beyond the extract, and there is no uninstall beyond deleting the plugin subfolder you added.
The file layout — and why it is what it is
A fully installed game folder with mods looks like this:
Dead in Antares\ ← Steam install folder
├── Dead In Antares.exe ← the game itself (untouched)
├── winhttp.dll ← Doorstop loader (shared)
├── doorstop_config.ini ← points at BepInEx.Preloader.dll (shared)
└── BepInEx\
├── core\ ← BepInEx runtime (shared)
└── plugins\
├── AntaresGerman\ ← one subfolder per mod
│ ├── AntaresGerman.dll
│ └── german.tsv
├── HideInactive\
│ └── HideInactive.dll
└── ... more mods ...
Every part of this layout is load-bearing. It's worth walking through the reasoning item by item, because understanding why each constraint exists saves hours of debugging later.
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 entirely 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 needs to know which .NET assembly to hand off to. It reads that path from doorstop_config.ini in the same folder it was loaded from. The file is tiny — five or six lines — and the only line that matters for modding is target_assembly=BepInEx\core\BepInEx.Preloader.dll. Every BepInEx-based mod ships an identical copy of this .ini. When a second mod is unzipped on top of the first, its copy of the .ini overwrites the existing one byte-for-byte, with no effect.
BepInEx\core\ is shared infrastructure, not part of any individual mod
The core\ folder contains BepInEx itself: BepInEx.dll, 0Harmony.dll (the runtime patching library), Mono.Cecil.dll, a handful of MonoMod DLLs, and the preloader. These files are identical across every BepInEx-based mod of the same BepInEx version. Every mod's ZIP is expected to include a copy of them, so that a user who has never installed any mod before can get started with any single mod's ZIP file. When a second mod is unzipped on top of the first, the core\ files are overwritten with identical bytes. This is deliberate: it means there is no "install BepInEx first, then install mods" step. The first mod you install is BepInEx.
One subfolder per mod under BepInEx\plugins\
This is the rule that makes the system scale to arbitrary numbers of mods without filesystem conflicts. Every mod owns its own subfolder inside BepInEx\plugins\ and puts everything it needs there: the main DLL, any data files (TSVs, CSVs, JSONs), any Harmony patches, any bundled fonts or images. Nothing from mod A can ever collide with mod B on the filesystem, because their files live in different subdirectories.
BepInEx's plugin scanner is recursive — it walks the subdirectories and loads every DLL it finds. So a user who has ten different mods installed has ten subfolders under BepInEx\plugins\, each holding a DLL and maybe some data. No manifest file lists them. No config says "load these in this order." The scanner finds them all.
The practical consequence: installing mod number 21 is the same as installing mod number 1. Unzip, launch, done. There is no registry of mods to edit, no load-order config, no conflict resolution step.
Writing a mod — the smallest viable template
A BepInEx plugin is one C# source file. It inherits from BaseUnityPlugin and declares its identity with the [BepInPlugin(guid, name, version)] attribute. The framework handles the rest:
using BepInEx;
using BepInEx.Logging;
namespace MyMod
{
[BepInPlugin("com.example.mymod", "My Mod", "0.1.0")]
public class Plugin : BaseUnityPlugin
{
private void Awake()
{
Logger.LogInfo("My Mod loaded.");
// Read or overwrite anything the game exposes as public C# state.
// Example: force-override a localization key.
Localization.ReplaceKey("Loading", "[MOD AKTIV] Wird geladen...");
}
}
}
That's a complete, installable, runnable mod. Compile it into a DLL, drop the DLL into BepInEx\plugins\MyMod\MyMod.dll, launch the game, and the loading screen will show the overridden text. BepInEx calls Awake() after the game's C# types are loaded but before any gameplay has started, which is the window where most mods do their setup.
Why one source file, not a project with folders
This is a style choice rather than a hard rule, but it matters for collaboration and longevity. A mod is usually a single concern: "change the loading screen text," "hide inactive characters from the roster," "translate the UI." Splitting that concern into ten small classes across a project file, a package manifest, a resources folder, and a handful of Harmony patch files adds surface area without adding clarity. For the vast majority of mods, a single .cs file with the plugin class, any helpers, and any Harmony patches inline is easier to read, easier to audit, and easier to modify a year later.
This does not mean a mod must stay in one file if it genuinely has reasons to split: a translation mod ships a data file separately from the DLL, a mod that bundles a custom font ships the font as an asset, and a mod that adds a big UI panel may legitimately want separate UI classes. The principle is "split only when splitting adds clarity."
Why the reference pattern is csc, not MSBuild
Mods for Dead In Antares target .NET Framework-compatible assemblies bundled with the Unity runtime. A direct invocation of csc.exe with explicit -reference: flags against the game's Managed\ folder and BepInEx's core\ folder produces a working DLL in one command. There is no packages.config to manage, no NuGet restore to run, no csproj to keep in sync with the actual references. The mod's build.sh is five or six lines and any modder can read it top to bottom. For a single-file mod this is strictly simpler than a MSBuild-based project, and it avoids the common failure mode where a csproj's target-framework setting drifts out of sync with the Unity runtime.
Scaling to many mods
The layout above works unchanged for 1 mod, 10 mods, or 100 mods. When the user installs mod 21:
- They unzip the mod's ZIP into the game folder.
winhttp.dll,doorstop_config.ini, and theBepInEx\core\files are overwritten with identical bytes (no effect).- A new subfolder appears under
BepInEx\plugins\with the new mod's files. - The user launches the game. BepInEx's plugin scanner finds all 21 subfolders and loads their DLLs in an arbitrary order.
Nothing about the filesystem layout, the install workflow, or the runtime model changes as the mod count grows. The few plausible failure modes at scale are:
- Version skew in BepInEx itself. If one mod ships BepInEx 5.4.22 and another ships 5.4.23, whichever is unzipped last wins. In practice BepInEx 5.4.x has been stable enough that this rarely causes visible problems; every mod should document the BepInEx version it was built against.
- Two mods patching the same method in incompatible ways. If mod A and mod B both use Harmony to prefix the same method, Harmony handles ordering but the mods may step on each other's intent. This is a runtime logic collision, not a filesystem one, and there's no automatic fix — the mod authors have to talk to each other or a user has to pick one.
- Load-order-sensitive behavior. BepInEx loads plugins in an unspecified order. A mod that depends on another mod's state being already initialized when
Awake()runs is fragile. The fix is to not assume load order and to late-bind to other mods' state.
Compatibility and game updates
When the game updates, three things can break a mod:
- Renamed classes or methods. A mod that calls
CharactersManager.AddCharacter(...)will throw aMissingMethodExceptionif that method is renamed in an update. The mod has to be rebuilt against the new assembly. - Changed key IDs in data files. Localization mods reference keys like
EnergyTooltip_Desc. If the game's CSV adds, removes, or renames keys, the mod's data file has to be regenerated. - Unity version change. A major engine upgrade can change the .NET runtime surface. BepInEx itself may need to be updated to match.
None of these are modding-specific — they're the standard cost of binary compatibility. The pragmatic workflow is: after a game update, launch the game, check the BepInEx log file at BepInEx\LogOutput.log for exceptions, rebuild or regenerate anything that broke, and move on.
Where to find the logs when something goes wrong
Two places. Both are plain text. Both are the first thing to check before filing a bug.
BepInEx\LogOutput.log— written by BepInEx itself. Shows which plugins loaded, any exceptions duringAwake(), and anyLogger.LogInfo(...)calls your plugin made.%LOCALAPPDATA%Low\Ishtar Games\Dead In Antares\Player.log— Unity's own player log. Shows engine-level errors, missing assets, and anything Unity itself decided to complain about.
A well-behaved mod writes enough to the BepInEx log on startup that you can verify it loaded without launching the game's own UI: the plugin name, the version, the outcome of whatever initialization it did, and a "completed without exception" marker at the end. If none of that appears in LogOutput.log, the mod's DLL never reached BepInEx's loader — usually because it's in the wrong subfolder or the architecture is wrong (x86 vs x64).
A note on x86 vs x64
Dead In Antares is a 64-bit Windows game. This means BepInEx must be the x64 build, not the x86 build. The two builds ship different copies of winhttp.dll — the x86 one is a 32-bit DLL that Windows will silently refuse to load into a 64-bit process, producing the exact symptom "no BepInEx log file appears after launch." If the log is missing and everything else looks correct, this is the first thing to check.
Related
- Modding Dead In Vinland: the bare-Doorstop, single-DLL modding model used by the sibling game from the same studio.
- BepInEx documentation and downloads: github.com/BepInEx/BepInEx
- Harmony (runtime method patching library bundled with BepInEx): github.com/pardeike/Harmony
- Unity Doorstop: github.com/NeighTools/UnityDoorstop