-
-
Notifications
You must be signed in to change notification settings - Fork 2
Mod reload
English | 日本語
Experimental. This came in with core 1.2.0 (released 2026-09-17). It's for people who build mods, and it only works while Developer tools are on.
Build the mod, close the game, start it again, click through the title screen, open the shop. That's a minute per attempt, and you make a lot of attempts in an hour. Mod reload cuts that out. After a build, the new DLL takes the place of the running one while the game keeps running. It's a developer-tools feature (Playing well with others, rule 8), so players never see it.
- Mods (plugins): yes, when they say so (below). Everything a mod registers with the framework carries its GUID, so the old build can be taken out cleanly.
- Libraries (the framework's Dialogue, Assets and so on, or a library of your own): no. Other mods reference a library's types directly, so after a new library build they'd still be bound to the old types. A library needs a restart.
- Mono never unloads an assembly. A reload loads the new build next to the old one and stops the old one from doing anything. The old code stays in memory, a few hundred kilobytes per reload, which doesn't matter while you're developing.
- Installing or updating a mod without restarting, for players: no. A mod written for "once, at startup" would break, and the Direct3D 12 problem below would land on players. Players still restart as before.
- Only the DLL is reloaded. Textures have their own reload (Assets), and so do Drag'n Wash Localization's translation files.
- Turn on Developer tools: Options → Mods → Drag'n Wash ModFramework.
- Say that your mod is reloadable:
ModInfo.Reloadable = truewhen you register, or a[ReloadableMod]attribute on the plugin class. A mod that says nothing is never reloaded. - Build, and put the DLL next to the installed one as
<Mod>.dll.new(the.csprojexample below does this for you). On Windows, Mono locks the running DLL so it can't be overwritten. The framework reloads from the.newfile instead, and the preloader patcher makes it the real DLL at the next launch. Where overwriting works (Linux), a changed DLL is picked up too.
Half a second after the file stops changing, the mod is reloaded, and the log (and the Console) says Reloaded <Mod> (1st time) from <file>. If there's a .pdb next to the DLL, it's loaded along with it.
Changes are picked up while the game's window is in front. Unity doesn't run the game's updates in the background, so a build that arrives while you're in another window is reloaded when you come back to the game.
| Where | What |
|---|---|
[Developer] WatchMods (Mods screen: Drag'n Wash ModFramework, Reload mods when their DLL changes, under Show advanced settings; on by default) |
Reload a reloadable mod by itself when its DLL or .dll.new changes |
mods reload <guid> in the Console |
Reload one mod now, whether or not WatchMods is on |
mods watch on, mods watch off
|
Switch WatchMods |
mods |
Lists which mods are reloadable and how many times each was reloaded |
| The Mods screen | A reloaded mod shows Reloaded with the count this session, and that the running build is not the file BepInEx loaded |
<!-- In the .csproj: after a build, the DLL goes to the game as .dll.new, and the running game reloads it. -->
<PropertyGroup>
<GameDir>C:\Program Files (x86)\Steam\steamapps\common\Drag'n Wash</GameDir>
</PropertyGroup>
<Target Name="CopyToGame" AfterTargets="Build" Condition="Exists('$(GameDir)')">
<Copy SourceFiles="$(TargetPath)" DestinationFiles="$(GameDir)\BepInEx\plugins\$(AssemblyName)\$(AssemblyName).dll.new" />
</Target>- The new build is checked first. It's loaded from bytes (so the file stays unlocked for the next build), and the framework looks for its plugin with the same GUID. If that fails, the log says why and the old build keeps running. Nothing gets taken down unless it can be replaced.
-
The old build is taken out. Everything it registered with the framework and the libraries goes, by its GUID and by its assembly: Tool window tabs and commands, text rewriters,
GameEventshandlers, handlers on the libraries' events, services,GameHooksrecords, itsModInfo. Its Harmony patches are removed, and its plugin component is destroyed, so itsOnDestroyruns. -
The new build starts where BepInEx started the old one, and its
Awakeruns just like at startup. ItsGameEvents.OnGameStartedhandlers run right away, as they do for any late registration. If it adds an Options row again with the same id, that row takes over the old row's callbacks.
If a reload fails before step 2, the old build keeps running. If it fails after that, the log says so, the Mods screen marks the mod's running build unavailable, and you need a restart.
The old assembly stays behind, and so does any object of the old build the game still holds.
The new build's Awake runs mid-game, so a mod that loads textures or prepares fonts there uploads them at the very moment Direct3D 12 may crash (Unity UUM-140564). A native crash can't be caught, so the framework writes a marker file (BepInEx/config/<core GUID>.reload-in-progress) before a reload and removes it afterwards. If the marker is still there at the next start, the reload took the game down. The log says so, WatchMods is switched off until you turn it back on, and the Mods screen shows "Automatic mod reload" as unavailable. You can avoid the problem by working with -force-d3d11 in the game's launch options.
If your mod says it's reloadable, it has to keep these promises:
- Its Harmony ID is its GUID:
new Harmony(MyMod.Guid). That's how its patches are found and removed. - It registers through the framework (
ModFramework.Register,AddTab,AddCommand,AddRewriter,GameEvents,Services,GameOptions) instead of holding things in static fields of its own. The framework can't take out what it doesn't know about. - Only the new assembly's own statics start fresh. Anything the old build handed to the game (a coroutine, a
DontDestroyOnLoadobject, a file watcher) is cleaned up inOnDestroy. - Its
Awakedoes nothing that's unsafe mid-game on Direct3D 12 (a texture upload), or that part is gated withGameFonts.RuntimeUploadsAreSafe.
A library holds registrations from other mods, and when a mod is reloaded, its old ones have to go. ModReload.Unloading is raised just before a mod's old build is taken down, with the mod's GUID and assembly. Remove what that mod registered with you, by GUID where you kept one and by assembly otherwise.
ModReload.Unloading += (guid, assembly) =>
{
MyLibrary.RemoveOwned(guid); // what you keep by GUID
ModReload.PruneEvent(typeof(MyLibrary), nameof(MyLibrary.Changed), assembly); // handlers on a static event
};| Member | What it does |
|---|---|
event Action<string, Assembly> Unloading |
Raised before a mod's old build is taken down. A handler that throws is logged; the others still run |
Delegate Prune(Delegate handlers, Assembly assembly) |
The handlers without those whose method or target lives in that assembly |
void PruneEvent(Type type, string eventName, Assembly assembly) |
Prune on a static event of type, by name, in place |
string Reload(string guid) |
Queues a reload for the next frame; returns what was queued, or why not (developer tools off, not loaded, not reloadable, a library) |
bool IsReloadable(string guid), IReadOnlyList<string> ReloadableMods()
|
Whether a mod said it is reloadable; the loaded mods that did |
int ReloadCount(string guid) |
How many times the mod was reloaded this session |
bool Watching |
True while WatchMods is on and developer tools are on |
They're all in the core, in the DragNWash.ModFramework namespace, along with ReloadableModAttribute.
Players
Mod authors
- Getting started
- Playing well with others (the guide)
- Going online
- Installer
- Mod reload
- Overrides (no code)
- Graphs (no code, makes things happen)
Tools (F1, developer tools)
- Inspector
- Console
- Code graph
- Bridge (AI clients, MCP)
API
日本語
- ホーム
- プレイヤー向け · ランチャー · FAQ · クラッシュレポート
- はじめての Mod · ほかの Mod と一緒に動かす · 外と通信する Mod · インストーラー · Mod の再読み込み · Overrides · Graphs
- Inspector · Console · コードのグラフ · Bridge
- 中核 API · 操作の登録簿 · Text · Dialogue · Tool window · Assets · Flags and saves · GameEvents · SettingMeta
Links
- Repository
- Releases
- Changelog
- Design records: DESIGN · ROADMAP · CONTENT_POLICY