Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
ValheimEnforcer
Make your friends use the agreed mods and stop bringing stuff into the server.
| Date uploaded | 3 days ago |
| Version | 0.30.0 |
| Download link | MidnightMods-ValheimEnforcer-0.30.0.zip |
| Downloads | 893 |
| Dependency string | MidnightMods-ValheimEnforcer-0.30.0 |
This mod requires the following mods to function
denikson-BepInExPack_Valheim
BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.
Preferred version: 5.4.2350ValheimModding-Jotunn
Jötunn (/ˈjɔːtʊn/, 'giant'), the Valheim Library was created with the goal of making the lives of mod developers easier. It enables you to create mods for Valheim using an abstracted API so you can focus on the actual content creation.
Preferred version: 2.30.1ValheimModding-YamlDotNet
Shared version 16.3.0 of YamlDotNet from Antoine Aubry and contributors, net47 package for use in Valheim mods. Maintained by the ValheimModding team.
Preferred version: 16.3.1README
Valheim Enforcer
Lightweight mod synchronization and server-side character progression for Valheim.
A drop-in, no-maintenance solution for people who would rather play than configure. Install it on the server and on your clients and it works: every mod the server loads becomes a required mod, and characters are saved on the server rather than trusted from the player's own machine. All of it is configurable, and the server always decides.
Full documentation — every feature, setting and command explained.
Install
- Install on the server and on every client. Both sides need it; only the server's copy decides anything.
- Start the server.
That is the whole setup. There is no mod list to write — the server publishes the plugins it loaded and requires them of everyone. Characters are already server-side.
Requires BepInEx, Jotunn and YamlDotNet (handled for you by any mod manager).
Already have players? Anyone joining for the first time since you installed this has no save yet and counts as a new character. Check the new-character rules first.
Coming from ServerCharacters? Import your saves so nobody loses anything. The two mods cannot run at the same time.
Features
On by default
Mod enforcement — every client is checked at connect against the mods this server allows, and is told exactly what to fix when it does not match. Optional per-mod lists for required, optional, admin-only and server-only mods; strict version pinning; SHA256 file verification that catches a mod recompiled with the version string untouched; an allowlist for BepInEx patchers; and per-connection attestation so a mod report cannot be a replayed canned answer. Full docs →
Character progression — items, skills and custom data live on the server. Untracked items are confiscated and kept so you can hand them back, skills raised elsewhere are clamped and every reduction is recorded so it can be undone, and a character whose local save was lost gets its progress back. Optionally holds Forsaken Power, eaten food, the map, known recipes, known texts, trophies, statistics and spawn point too; optionally hands new characters a starter kit or limits an account to one character. NewCharacterClearKnownTexts (on) has a first-time joiner forget the compendium entries it read somewhere this server never saw, which vanilla's own recipe reset leaves untouched; KnownTextPassthroughPrefixes (EpicMMOSystem) leaves known-text keys a mod owns to that mod, so EpicMMO's level, experience and attribute points are neither tracked nor overwritten.
Full docs →
Cheat detection — clients are checked against a catalog of known cheat tools across running processes, DLLs injected into the game, open window titles, the managed assemblies loaded into the game, and proxy DLLs dropped beside valheim.exe. The last two are what see an injected cheat menu, which is not a mod and so has no plugin file to hash and nothing in the declared mod list. Log, kick or ban; dedicated game-cheating loaders and menus are banned on sight, and the server makes that call from its own catalog. Only matched entries ever leave the player's machine.
Full docs →
Player activity audit — the evidence layer under everything else. What a player is carrying, what they gained and lost, what they took from or put into chests, graves, ships and carts, and a rolling summary of the damage they are dealing. Seven days on disk, searchable with grep, downloadable to your own machine. It records and reports only — it never kicks, bans or confiscates.
Full docs →
Off until you switch them on
Structure validation — catches a client spawning world-generation geometry (dungeon rooms, dvergr towns, ruins) instead of building, and pieces whose health has been set above what the prefab allows, which is how an indestructible structure is made. Blueprint and bulk-build mods cannot trip it, by design. enforcer-structures-scan finds what is already in your world.
Full docs →
Network integrity — guards on the vanilla RPCs the server otherwise relays without looking at them. Binds chat messages to the name the server holds, refuses the player-teleport message from non-admins, filters mass object deletion down to what the sender owns, drops impossible damage values, and optionally re-decides PvP and restricts global keys. These read packets the server already holds, so there is nothing for a client to lie about. Full docs →
Item origins — reports equipment that appears with no crafter and that nothing in this world drops, sells or spawns, and equipment crafted by a player id nobody here has ever been. Only looks at items that have just appeared, so existing characters are never re-examined. Warns, never confiscates. Full docs →
Save archives — rolling compressed archives holding the world save and the character saves from the same moment. The game's own backups keep two uncompressed copies of the world only, and none of your players' characters. Configurable history, size ceiling and destination. Full docs →
Emergency crash recovery — each player holds a sealed, signed snapshot of their own character that only the server can open. After an unclean shutdown the server asks for them back and adopts the ones that verify and are newer. Narrow window, every restore logged, and an honest note about the item duplication it can cause. Full docs →
Discord notifications — joins, leaves, startup, shutdown, saves, cheat bans and refused connections. Each category can post to a channel of its own, and every message is a template you can rewrite, role pings included. Paste in a webhook URL and it starts posting; nothing else needs configuring. Full docs →
Console commands
Run them from the server console, or from a connected admin's client — a dedicated server is administered entirely from in-game. Type enforcer-help for the list, or enforcer-help items for one area. Tab completion works past the first argument, through the account ids the server actually has and then that account's characters.
| Command | What it does |
|---|---|
enforcer-help |
Lists the commands, grouped by area |
enforcer-whoami |
Whether the server treats you as an admin, and what to fix if it does not |
enforcer-player-list |
Every account with a save, and the characters under it |
enforcer-items-list / -return / -clear |
See, hand back, or discard confiscated items |
enforcer-skills-list / -restore / -clear |
See, undo, or forget the skill reductions this mod made |
enforcer-progress-show / -clear |
What synced progression the server holds for a character |
enforcer-loadout-list / -apply |
Starter kits, and giving one to an existing character |
enforcer-audit-inventory |
What a character is carrying, and who crafted it |
enforcer-audit-history |
Timeline of what a player gained, lost, took and stored |
enforcer-audit-damage |
Live damage summary for everyone currently fighting |
enforcer-audit-available / -download |
What history the server holds, and keeping a copy of it |
enforcer-structures-scan |
Finds cheat-placed structures already in the world |
enforcer-item-origins |
The equipment this world has no uncrafted route to |
enforcer-trust |
Which players have tripped which network guards |
enforcer-archive-list / -now / -prune |
Save archives on disk, taking one, applying rotation |
enforcer-recovery-status / -open / -close / -key-rotate |
Crash recovery window and key |
enforcer-characters-import |
Imports saves from ServerCharacters |
enforcer-notify-test |
Previews a Discord message |
enforcer-memory |
What the mod is holding, and the world's object counts |
Everything except enforcer-help and enforcer-whoami needs admin rights on the server, which the server checks itself — a client that claims to be an admin is refused and told so.
Troubleshooting: your admins stopped being admins
A Valheim update changed the spelling adminlist.txt requires, and it is now the only spelling accepted for an account whose id is a number:
| Platform | Line to write |
|---|---|
| Steam | V_<steamid> |
| Nintendo Switch 2 | N_<in-game id> |
| Xbox | X_<in-game id> |
| PlayStation | S_<in-game id> |
| GameCenter | A_<in-game id> |
A bare 76561198…, or the older Steam_76561198…, is no longer honoured. The game says nothing when this happens — the file still looks exactly as correct as it always did, and every admin on the server simply stops being one. If admin commands stopped working after an update and you changed nothing, this is why.
Run enforcer-whoami and it prints the exact line to write for your account. It works whether or not you are an admin, from the console or from chat as /enforcer-whoami, and only the server answers — so it reports the id that actually decides things rather than what your client believes. It also catches the other two cases: an id not in the file at all, and a line with stray whitespace or a byte order mark, which Valheim does not trim and which costs you admin over one trailing space.
adminlist.txt is re-read within about ten seconds of being saved, so there is no need to restart the server to test a fix.
What this mod can and cannot do
Valheim is client authoritative, and the checks here fall into two kinds that fail very differently. Checks that ask the client about itself — what mods it runs, what is in its inventory, what is running on that machine — can be lied to by a client that can already cheat; what they change is the cost, from "edit one file and rebuild" to "reverse engineer and patch the enforcer". Checks the server makes for itself — the sender of every message verified against the connection it arrived on, the join rules re-run server-side, and the network guards reading packets the server already holds — have nothing for a client to lie about.
The full statement of both is in the documentation.
Roadmap
Not yet implemented, but planned:
- Automatic mod suggestions and download links for clients that are missing mods or have the wrong versions
- Platform-ID based "moderator" mod list, so server owners can give mod permissions to specific players without making them admins
Got a bug to report or just want to chat about the mod? Drop by the Discord or GitHub.
CHANGELOG
0.31.0
- Improves ProxyLoader detection
- Adds AllowedProxyLoaderHashes (Anti-Cheat, empty): SHA256s to treat as legitimate. The hash of the
flagged file is printed in the server log line for the detection and the log prints the hash when detecting one.
- IgnoredCheatProcesses entries containing a path separator now also match the folder a proxy DLL
was loaded from. Entries without one keep matching names only, so an existing entry like "steam"
cannot become an exemption for the whole game folder.
- Fixes the server log flooding with "Delta removal ... found no match"
- Fixes skill reductions being recorded for a rounding error (it wasn't causing a change, but was logging more than it should)
0.30.0
- Adds NewCharacterClearKnownTexts (Player Sync, default on): a character joining a server for the first time
forgets its known texts.
- Vanilla's own reset did not cover this
- EpicMMO keeps its data in these KnownTexts, which
- Adds KnownTextPassthroughPrefixes (Player Sync, advanced, default EpicMMOSystem): comma separated key
prefixes in a character's known texts that are not touched.
- Fixes EpicMMO progression being rolled back on every join.
- Adds EpicMMOKnownTextCompat (Player Sync, advanced, default on): does nothing unless EpicMMO is installed.
0.29.0
- Adds detection for injected cheat menus
- Adds the inventory grid check (World Integrity, DetectInventoryGrid, default on).
- Warns and never confiscates, like the item origin checks beside it. It does record a contradiction
against the connection, so it counts toward ContradictionThreshold and shows up in enforcer-trust.
- Adds enforcer-harden: reports which of this server's defences against an injected cheat menu are switched
off and what each one leaves open.
- Fixes a chance that customdata will become stale
0.28.3
- Fixes clean logouts being reported as stale data in the Discord player-left message. The check read the
departing player's save off disk, but their final save was still queued for the character store's background
writer at that point - ZRpc hands the final-save RPC and vanilla's disconnect to us from the same batch of
incoming packets - so it read back the DirtyDisconnect a mid-session delta had written and announced the data
as outdated by a delta window. Almost every clean logout was reported wrongly. The final save's arrival is now
recorded as it is received and the message reads that instead: the same fact the file's own field carried, a
round trip earlier.
- Removes a main-thread stall from every disconnect. Resolving that same clean/stale state read and fully
deserialized the departing player's save - inventory, confiscated list, custom data and the whole progression
record - to recover one enum, on the server's main thread, whether or not a Discord webhook was configured.
With no webhook, which is the default, the message it was for was discarded, so the work was pure waste on
every server running the mod. In internal storage mode it inflated every character on the account rather than
the one leaving.
- This is also the allocation most worth removing from a world autosave. ZDOExtraData.PrepareSave clones the
world's entire extra-data store and holds it live for the duration of the background save thread, and a
large character graph inflating on the main thread inside that window can push Unity's Boehm collector
into a mark stack overflow it cannot recover from - an immediate SIGABRT, exit code 134. The clone itself
is vanilla and is not patched here.
- Times the character load path, which was the untimed half of character persistence. A slow read or
deserialize now reports itself through the stall watch the way the save path already did, rather than showing
up as an unexplained hitch.
- Fixes the container audit reading the item list out of the wrong place, which had left it unable to record
anything at all. Container.Save writes the list with ZDO.Set(int, byte[]) and ZDOExtraData keeps byte arrays
in a different store from strings, so asking for "items" as a string returned the default for every container
on the server and no take or store could ever be diffed. It reads the byte array now. Both are dictionary
lookups that hand back the stored reference, so the unchanged path - the one every replicated container walks
- still allocates nothing.
- Fixes cart, sled and ship storage never being audited. The index of which prefabs are containers was built
with GetComponent, which only looks at a prefab's root object, and vanilla mounts the Container of a Cart,
Sled, Karve, VikingShip, VikingShip_Ashlands, Trailership and the incinerator on a child instead - pointing
Container.m_rootObjectOverride back at the root so the contents still live on the root's ZDO. That is the ZDO
the server receives, under the root prefab's hash, so none of the seven ever matched and the one container a
player can empty out of sight of everybody and then drive away was the single thing this audit did not watch.
A child's Container now counts when its override resolves to the prefab's own root, which deliberately
excludes the dungeon and camp pieces that are built by parenting whole chest prefabs underneath them - those
carry their own ZDO, and filing their contents under the building they stand in would be a false record.
- A performance pass aimed at servers running 50 to 100 players, where the concern is how much short-lived memory
the mod produces: Unity's collector stops every thread to clean up, so garbage made on a worker thread costs the
main thread exactly what garbage made on the main thread does. Measured on a real 66 KB character save:
- Character saves are written by a purpose-built writer instead of the general YAML serializer
(FastCharacterWriter, Advanced, default on). Same file, same keys and layout, read back by the same reader and
interchangeable with saves written the old way. Writing one cost 3.5 MB of garbage and now costs about 90 KB,
and a character is rewritten for every incremental update from every player. The writer checks itself
against the serializer once at startup - every property of every nested type, found by reflection so a field
added later is covered, plus a set of strings chosen to break a YAML writer - and hands every save back to
the serializer for the session if the two disagree about anything, saying so in the log.
- Incremental updates travel in a compact binary form instead of YAML (BinaryDeltaUpdates, Advanced, default
on). Reading one on the server cost 152 KB on the main thread and now costs 7 KB. Negotiated rather than
assumed: the server says it understands the form in the character payload every client is sent at connect,
so a client from before this existed keeps sending YAML and is handled as it always was, and a newer client
on an older server does the same.
- Audit lines are written by hand into a reused buffer. One 275 character line cost 50 KB and now costs
nothing to speak of. The line is unchanged - same keys, same order, still read back as YAML - and the
serializer remains as the fallback.
- The periodic full saves are asked for one player at a time, spread evenly across FullSyncPullIntervalMinutes,
rather than from everybody in waves at the end of it (FullSyncSpreadAcrossInterval, Advanced, default on).
Each player is asked exactly as often. A hundred full saves inside a minute was half a gigabyte of garbage
every interval; one every fifteen seconds is nothing.
- Adds CharacterWriteIntervalSeconds (Advanced, default 0, which changes nothing). When set, a character
changed by an incremental update is written at most that often instead of after every update. Full saves,
deaths, a player joining or leaving, admin commands and a shutdown still write at once. The cost is that a
server which dies without shutting down loses up to that many seconds of routine updates. 30 to 60 is a
sensible value for a busy server.
- A login no longer waits on other players' saves. It asked the character store whether anything at all was
queued, and on a busy server something nearly always is, so a join could hold the main thread for up to
three quarters of a second - twice, with map sync on - over a save that had nothing to do with it. It now asks
about its own character only.
- Looking up a character's save no longer lists every account that has ever joined. That listing is remembered
and only re-read when the character folder changes, which is when an account joins for the first time. "This
player has no save here" is still always checked against the disk before it is believed, because that answer
is what gets a new character's inventory stripped.
- The admin exemptions for structure validation, the RPC guards and the audit remember whether a connection is
an admin for ten seconds - the same staleness vanilla's own admin list already has. ZNet.IsAdmin builds
several strings per call and these asked it once per ZDO packet and twice per relayed hit. The command relay's
admin gate still asks vanilla directly, every time.
- Three log lines per incremental update move from Info to Debug. On a full server that was around twenty
lines a second, all day.
- Main-thread stall warnings are written as warnings again, without debug logging on. They had ended up behind the
debug switch along with the background-thread ones, so a production server never reported a stall at all.
Background-thread timings stay debug-only, and StallWarningThresholdMs = 0 still silences both.
- A connecting client's mod list is accepted once per connection. The handler stayed registered, so a peer could
send the list again as often as it liked - before the password and the ban list are checked - and have the
server parse up to 2 MB of YAML on the main thread each time, for as long as it held the socket open. The
ceiling on that list is now 512 KB and 4096 entries, and working out which required mods are missing no longer
costs declared-mods times required-mods.
0.28.2
- Fixes empty loadouts causing errors
0.28.1
- Documentation overhaul and readme condense
0.28.0
- Fixes Mods.yaml losing optionalMods, adminOnlyMods and serverOnlyMods on a restart.
- A file that fails to parse is now copied to Mods.yaml.unreadable-<date>-<time>.bak before anything is
written, and the error names the line and column and what did load. Previously it logged "fix the file and
restart" and then published an empty file over the top of it, which made that impossible.
- A duplicate top-level key is now reported with its line number instead of being silently resolved
last-wins, which quietly discarded whichever copy came first and then wrote the loss to disk.
- Fixes a NullReferenceException on every connecting client when any mod entry was written with no settings under
it, and the same entry then being named twice in the rejection text. An entry with nothing under it now reads as
listing the mod with no options set, which is what it looks like it means.
- Adds keepWhenUnloaded on a mod entry (default false): keeps a requiredMods entry out of the reach of
RemoveUnloadedModsFromRequired, for a mod required of clients that the server does not run itself. Corrects that
setting's description and the README, both of which said it was off by default when it has been on.
- Removes the versionStrictness field, which was read by nothing. A versionStrictness line in an existing file is
ignored and disappears on the next rewrite.
- Adds Starter Loadouts (Player Sync, EnableStarterLoadouts, default off): a kit of items, skill levels, known
materials and a spawn point handed to a character joining this server for the first time. The kits are written
in Loadouts.yaml beside the other config files, created with a commented example in it and re-read while the
server is running; DefaultStarterLoadout picks the one new characters get.
- Granted AFTER the new-character rules have stripped what the character arrived with, which is what keeps the
two from fighting: nothing in a loadout needs listing in NewCharacterStartingItems as well, and a loadout may
hand over an upgraded item even though the allowlist refuses one that turns up on its own.
- Skills are only ever raised, never lowered. Items arrive at full durability and are not marked cheated, so
they do not put a character out of the running for the game's own achievements.
- knownMaterials, knownRecipes and the spawn point need SyncKnownItems / SyncSpawnPoint, which are what give
the server somewhere to record them; enforcer-loadout-list says so against each line when they are off.
- A prefab name that does not exist is named in the log and skipped rather than failing the join, and a
Loadouts.yaml that will not parse keeps the kits already loaded rather than emptying them.
- Adds enforcer-loadout-list [name] and enforcer-loadout-apply <accountId> <characterName> <name> confirm, the
second for giving a kit to a character the server already has a save for.
- Adds Emergency Crash Recovery (Crash Recovery, EnableCrashRecovery, default off): every few minutes each
connected player is handed a sealed snapshot of their own character. Server still verifies and wins if the
clients character is tampered with.
- CrashRecoveryAutoRestore (default on) is a separate switch from the master one, so snapshots can be kept and
distributed without the server ever acting on one unasked. CrashRecoveryPushIntervalMinutes (default 10)
bounds how much a crash can cost and is also the bandwidth knob. CrashRecoveryKeepBlobs (default 3) bounds
what a client keeps per world.
- Adds enforcer-recovery-status, enforcer-recovery-open, enforcer-recovery-close and
enforcer-recovery-key-rotate.
- It cannot help with a lost or corrupted LOCAL character file - the client cannot read its own snapshot
which is what progression sync is for. This is exlusively to handle server-crash recovery.
- Adds Save Archives (Backups, EnableSaveArchives, default off): rolling, compressed archives holding the world save
and the character saves from the same moment.
- An archive is only ever taken just after a world save FINISHES.
- SaveArchiveIntervalMinutes (default 120) is a minimum gap, not a schedule
- SaveArchiveKeepCount (default 5) and SaveArchiveMaxTotalMB (default 0, no ceiling) decide what is kept,
oldest deleted first.
- SaveArchiveIncludeCharacters (default on), SaveArchiveIncludeConfig (default on), SaveArchiveCompression
(Fastest / Optimal / NoCompression) and SaveArchivePath (empty = BepInEx/config/ValheimEnforcer/Archives).
- Adds enforcer-archive-list, enforcer-archive-now [save] and enforcer-archive-prune
- This does drastically increase storage size of the world on disk.
- Adds Server-Synced Progression (Player Sync, EnableProgressionSync, default off)
- SyncMapExploration (default off): Syncs the players map to the server
- SyncKnownItems (default off): recipes, build pieces, materials, crafting stations, biomes, runestone texts and
permanent unlocks.
- SyncTrophies (default off): Syncs trophy list
- SyncPlayerStats (default off): Syncs player statistics, these are also used for achievements
- SyncSpawnPoint (default off): the bed a character has claimed here, and the fallback point.
- NewCharacterClearPlayerStats (default off): a character joining for the first time gets their stats cleared
- MapSyncIntervalMinutes (Advanced, default 15): how often a client may upload its map. This is mildly expensive.
- Adds enforcer-progress-show <accountId> <characterName>, which reports what the server holds section by section
- Adds enforcer-progress-clear <accountId> <characterName> <map|items|stats|spawn|all> allows deleting saved character progress
- Slight memory optimization when Gzipping contents such as large save data
0.27.1
- Adds an Admin override config (NOT SERVER SYNCED, must be set on the server itself)
0.27.0
- Adds ModValidationExemptAdmins (Mods, default off): anyone on the server's adminlist may connect with any mods at
all - missing required mods, mods the server does not allow, mismatched versions, modified files, unlisted BepInEx
patchers, and a missing or wrong attestation even under Require. It also covers an admin running no ValheimEnforcer
at all, who is otherwise refused for never sending a mod list. Meant for testing a mod against the live server
without editing Mods.yaml first.
- The checks still run and their result still goes to the server log, so you can read what the admin was
carrying; only the rejection is skipped.
- The Discord mod-mismatch notification is not sent for an exempt admin - nobody was turned away.
- An exempt admin whose list failed gets no client-contradiction declaration on file, since that feature reasons
from a premise this setting sets aside.
- The oversize mod-list guard is not part of the exemption.
- enforcer-whoami reports when it is on.
- Reduces player memory footprint
- Adds CharacterCacheIdleMinutes (Advanced, default 30): a character untouched for this long is dropped from
memory and read back from its file on the next update. 0 keeps everything until restart, as before.
- Adds enforcer-memory (Diagnostics): process and managed heap size, what the mod is holding, and the world's
object counts including the game's own per-peer object tables. MemoryReportIntervalMinutes (Advanced, default
0 = off) writes the same summary to the server log on a schedule.
0.26.0
- Fixes incorrect default coloring of player left message
- Adds PreventExternalFoodChanges (Player Sync, default off). The character save now records the foods a character has
eaten and how long each has left, and puts exactly those back when they join - so food eaten in a solo world or on
another server, or a free top-up, cannot be walked in.
- A character joining for the first time has all of their food cleared.
- A save written before the setting was on has no foods recorded. That character keeps what they arrive with on
their next join and is tracked from then on, so switching this on strips nobody.
- Re-applied server side on the first save of each session when ServerSideJoinEnforcement is on.
- Adds NewCharacterClearKnownRecipes (Player Sync, default on): a character joining a server for the first time forgets
the recipes and build pieces they discovered elsewhere, along with the materials, crafting stations and trophies
that discover them. Only once the server confirms it holds no save for the character, and never in singleplayer.
- Moves the list of loaded mods out of Mods.yaml into ServerActiveMods.yaml, beside it. Entries are sorted and written
exactly like a Mods.yaml entry, ready to copy into a list. The file is deleted and rewritten every start, never read,
and not synced.
- An existing Mods.yaml drops its activeMods section on the next rewrite, and its header banner has the activeMods
line swapped for one pointing at the new file. Comments are unaffected.
- Adds RemoveUnloadedModsFromRequired (Mods, default true): at startup, removes requiredMods entries for mods the server
does not have loaded, so an uninstalled mod stops being demanded of clients.
- Fixes adminOnlyMods being required of admins. A mod on both adminOnlyMods and requiredMods - which is what adding a mod
the server loads to adminOnlyMods usually leaves behind - was treated as required: every client, admins included, had
to install it, and non-admins were let in with it. adminOnlyMods now wins, so the mod is optional for admins and
refused to everyone else. A startup log line names any mod on both lists.
- Adds enforcer-skills-list, enforcer-skills-restore and enforcer-skills-clear, and RecordSkillReductions (Player Sync,
default on). Every skill this mod lowers - a returning character clamped back to the stored level, a new character's
skills set to zero - is now recorded in the character's save with the level it was lowered from and to, when and why,
and an admin can put it back.
- Restore puts each skill back to the highest level it was recorded being lowered from, and never lowers one. A
player who is online gets it straight away; one who is offline gets it on their next join, and a restore that
does not reach a player is applied on a later join rather than lost.
- The game's own skill loss on death is not recorded, and neither is the correction of a reported level the game
could never produce.
- Adds RestoreSkillsFromPlayerServerSave (Player Sync, default on): a returning character whose skills arrive below the
levels the server holds has them raised on join, the way missing items are handed back - so a character recreated
after its local save was deleted keeps the progress the server holds instead of pushing blank skills up as the new
record. Skipped on a dirty reconnect unless ItemReturnForDirtyReconnection is on.
0.25.0
- Adds full support for crossplay exclusive servers
0.24.0
- Adds PreventExternalForsakenPowerChanges (Player Sync, default off). The character save now records the Forsaken Power a
character has selected and puts it back when they join, so a power picked up in a solo world or on another
server - one this server may never have unlocked at its boss stones - cannot be walked in.
- A power selected at a boss stone here is saved as normal, mid-session included.
- A save written before the setting was on has no power recorded. That character keeps the power they arrive
with on their next join and is tracked from then on, so switching this on strips nobody's existing power.
- Re-applied server side on the first save of each session when ServerSideJoinEnforcement is on.
- Adds NewCharacterClearForsakenPower (Player Sync, default off): a character joining for the first time has their Forsaken
Power cleared, on the client at join and again server side on their first save.
- Adds NewCharacterResetMapExploration (Player Sync, default off): a character joining for the first time has their map of
this world wiped - explored areas, cartography table data and saved pins.
- Improves WeMod detection
0.23.2
- Adds enforcer-whoami: says whether the server treats you as an admin and, when it does not, what to change.
- Runs for anybody, admin or not. The person who needs to ask why they are not an admin is by definition
the person every other command is refusing, so locking the answer behind the same check left an operator
whose id is misspelled in adminlist.txt with nothing to go on from in-game.
- Distinguishes "your id was never added" from "your id is in the file but has a trailing space or a byte
order mark on it", which look identical from in-game and are the usual cause. Prints the exact line to
add or replace.
- enforcer-help also runs without admin now, which is what the documentation already claimed.
- Adds AllowPublicDiagnosticCommands (Advanced, on): set it false to make those two admin-only again.
- Handles the adminlist.txt format change. A Valheim update made the one-letter platform prefix - V_ Steam,
N_ Nintendo, X_ Xbox, S_ PlayStation, A_ GameCenter - the ONLY spelling accepted for a numeric account id:
ZNet.ListContainsId computes the old answer and then overwrites it with a lookup of the filtered form
alone. Every adminlist.txt written before that update silently stopped granting anybody admin, and nothing
in the game says so.
- enforcer-whoami prints the exact line to write, including for console accounts, where the in-game id is
derived from the platform id and cannot be worked out by hand.
0.23.1
- Console commands no longer flagged as cheats, but do require admin
- Prepatchers detected on the server stay in activePatchers and allowedPatchers when Mods.yaml is re-read.
Previously only startup did that, so an edit to the file replaced the detected list with whatever the file
said and dropped the server's own patchers out of the allowlist until the next restart.
0.23.0
- Deep North update
0.22.0
- Adds the Player Activity Audit (EnableAuditLog, on by default): a record of what players do, stored and monitored server side.
- Item gains and losses
- Container takes and stores
- A rolling window of damage dealt per player
- One file per UTC day under BepInEx/config/ValheimEnforcer/Audit, kept for 7 days (AuditRetentionDays) by default
- EnableAuditLog is the only switch: AuditItemChanges, AuditContainerAccess and AuditDamage are gone. Three of the four ways to configure it produced a record with a hole in it that nothing in the output announced.
- Container auditing has to watch every object a client replicates, so its hook is only installed when EnableAuditLog is on at startup. Turning the audit on in a running server takes a restart; turning it off takes effect immediately.
- Trimming the container snapshot table no longer copies and sorts the whole table with a comparison delegate, which it did on the main thread inside the packet loop.
- Adds five commands: enforcer-audit-inventory, -history, -damage, -available and -download.
- Resolves the peer behind a connection from a cached map instead of walking the peer list, so routed RPC sender verification (EnforceRoutedRpcSender) and ZDOData inspection no longer cost more per packet as the server fills up.
- Server-side death recording no longer inspects every object a client replicates. It now looks only at the objects a packet newly created, once the packet is done, so a server running it with structure validation off - the default - carries no hook on ZDO deserialization at all.
- The excessive-health check still needs that hook, since it compares against the value an object held before the client's write. It is now only installed when EnableStructureValidation and DetectExcessiveStructureHealth are both on at startup, so turning either on in a running server takes a restart before that one check begins working. The log says so if it happens.
0.21.0
- Improves scanning performance and prevents main thread hitches related to these scans
- Adds Network Integrity: server-side validation of the vanilla routed RPCs the server relays without ever
looking at them, configurable EnableRpcGuards (off by default)
- Adds BepInEx patcher validation (ValidatePatchers, off by default)
- Adds per-connection session handshakes
- Adds client contradiction reporting (ReportClientContradictions, off by default), which ties what a client
declared at join to what the guards later catch it doing
- Adds item origin detection (DetectItemOrigins, off by default), which monitors users aquiring items that are not fully valid
- Admins are exempt by default (ItemOriginExemptAdmins); IgnoredItemOriginPrefabs disables the check for certain items
0.20.1
- Improve item compatibility storage of Extraslots, ExtraCustomSlots, Equipment & Quickslots, and InventorySlots
- Configurable through PassthroughCompatModCustomData (default on). If disabled, you must disable your inventory mod from restoring backup items
0.20.0
- Routed RPC sender verification (EnforceRoutedRpcSender, Advanced, on by default). Valheim's routed RPC
carries a sender id the sending client writes, which the server now validates, this is applied to every registered RPC.
- Client-side messages from the server are now verified before being accepted.
- Character names and account ids are validated before being used as save-file paths, and a connection
whose character name is not a safe file name is refused at the handshake with a clear reason.
- A player's death is now recorded server-side (from the grave the client creates), so a client that skips
its own death handling can no longer keep its pre-death inventory.
- Skill levels reported by a client are clamped to the game's valid 0-100 range before being stored.
- Adds structure validation: server-side detection of clients placing structures no build tool can
place, and of pieces whose health is above what their prefab allows
- Configurable EnableStructureValidation (off by default) to enable this functionality
- StructureValidationAction determines the automated response to a player triggering this
- Admins are exempt by default
- Blocks ZNetScene's SpawnObject RPC (BlockSpawnObjectRPC, on by default), an unused routed call that
otherwise lets any client have the server instantiate any prefab by hash (creatures and items included,
not just structures). A block posts to the moderation Discord channel by default and follows
StructureValidationAction (default Log, so it reports without kicking or banning)
- Adds Enforcer-Scan-Structures, which allows finding existing structures like this
- Adds a structureFlagged Discord notification, routed to the moderation webhook
- Console commands improvements
- Commands now provide a summary back of what their action taken or result was
- Output goes to the console you typed in, including when the server ran the command for you.
- Renamed to enforcer-<area>-<verb>: enforcer-player-list, enforcer-items-list/-return/-clear,
enforcer-characters-import, enforcer-notify-test, enforcer-structures-scan. Every old name still
works and is shown beside its replacement in the new enforcer-help
- Adds enforcer-help, and enforcer-items-list
- Tab completion now works past the first argument, and offers the account ids and character names
the server actually has
- Adds EnableTerminalColors (on, local) to colour command output by severity
- Fixes clearing confiscated items doing nothing at all when the player was offline
- Naming a character the server has no save for now says so
- Fixes first-join allowing items on in a specific scenario
0.19.0
- Reduces false positive kicks for having applications which could be cheat-engines running
- Generic window classes are now a low-confidence signal: the sighting is still reported and
logged on the server
- Kicks and bans from cheat reports now target the reporting connection's platform ID instead of
the player name it self-reported, so a crafted report cannot hit another player and duplicate
character names cannot misfire
- A wrong mod version is now reported as a version mismatch instead of a modified file
- This covers the case where enforceVersion is off and the recorded hash is what caught it
- Version mismatches now name both versions - "com.example.Mod (needs 1.4.2, has 1.3.0)" - on the
disconnect screen, in the server log and in the Discord {versionMismatches} field
0.18.0
- Discord notifications can now be split across channels
- WebhookUrlPlayerActivity, WebhookUrlServerStatus, WebhookUrlModeration and WebhookUrlModMismatch
each take a webhook of their own; any left empty falls back to WebhookUrl as before
- Every notification is now a template you can edit, in config/ValheimEnforcer/Notifications.yaml
- Each entry is the literal message body posted to Discord, anything Discord accepts works, including
author/footer/thumbnail/image
- Deleting a key deletes that part of the message: drop "timestamp" and no date stamp is sent,
drop "embeds" and it becomes a plain text post. Nothing is added back for you
- A 'content' line is the only place a mention pings - use it for role alerts
- Placeholders like {player}, {playerId}, {reason} and {missingMods}; a mod mismatch also exposes
its missing/extra/version/hash lists separately instead of one block of prose
- Adds a world save notification (NotifyWorldSaved, off by default - the autosave is every ~20 minutes)
- Adds ServerLabel, exposed to templates as {server}, for several servers sharing one channel
- Adds Enforcer-Test-Notification, which posts any event with sample data so a template can be
previewed without waiting for the real thing
0.17.0
- Adds character import from the ServerCharacters mod (ImportServerCharacters, off by default)
- Reads the character files ServerCharacters leaves behind and turns them into enforcer saves, so
migrating players keep their inventory and skills instead of being confiscated on first join
- Item quality, variants, crafter names and mod item data (EpicLoot and friends) come across intact,
as do modded skills
- Runs once at server start, or on demand with Enforcer-Import-ServerCharacters, which has a dryrun
- Existing characters are never overwritten unless 'force' is given
0.16.0
- Adds an optional one-character-per-account rule (EnforceCharacterLimit, off by default)
- An account may only join with a character the server already has a save for, up to
MaxCharactersPerAccount; anything else is refused at the connect handshake
- Characters that already exist are never affected, so enabling it locks out no current player
- Refused players are told which character to rejoin with, instead of a generic connection error
- CharacterLimitExemptAccounts allows specific accounts any number of characters, whether or not
they are admins; CharacterLimitExemptAdmins extends that to the whole adminlist
0.15.0
- Adds file verification of client plugin DLLs at connect time
- New HashEnforcement setting: Off / WhenKnown (default) / Strict, overridable per mod in Mods.yaml
- Mods the server loads pin themselves; client-only mods pin by hand or from a thunderstorePackage
- Fixes a mod with the wrong version being reported as both a version mismatch and a non-allowed mod
- Comments in Mods.yaml now survive the startup rewrite, which used to delete them - a note stays
attached to the entry it was written above
- Documents the mod list in the README: the five lists, how an entry is structured, what is kept up
to date for you and what you have to write yourself
0.14.1
- Update Jotunn version
0.14.0
- Greatly expands cheat tool detection
- Detects the loaders used to deliver Valheim cheats, these are banned on sight
- generic trainers are also detected, with a configurable moderation action (default ban)
- Ban reasons and Discord notifications now name the specific tool and how it was found
0.13.0
- Fixes item duplication on death edgecases
- Improves compatibility with death mods that change what happens to items on death
- Inventory changes are now tracked as they happen instead of being polled on a timer
- An idle player sends nothing at all; CharacterDeltaTracker is now a rate limit (default 15s, was a 60s poll)
- Singleplayer and listen-host sessions now keep their character save current mid-session
- Fixes restored items being dropped on the ground when the player had room for them
- Singleplayer fixed skill progress earned during a session being rolled back on death
- Status effects can no longer carry across a death
0.12.0
- Improves multiplayer disconnect saving for extremely large character saves
- Allows server admin editing of save files to be hot-reloaded (please ensure the player you are editing is logged off first)
0.11.1
- Improves accuracy of saves in singleplayer games
0.11.0
- Server-side character saves and delta updates are now written off the main thread
- Full/delta saves are deserialized, serialized and written on a background worker with an in-memory cache
- Repeated writes to the same character are coalesced, so a burst of saves (e.g. every client on a "save player profiles" broadcast) can no longer stall the server or time players out
- Internal storage mode keeps its existing behavior (registry writes must stay on the main thread)
- Full character saves are now pulled by the server instead of riding the world/profile autosave
- The server asks connected players for a full save every FullSyncPullIntervalMinutes (default 25)
- No more than FullSyncMaxConcurrentPlayers upload at once (default 5); larger player counts are staggered into waves so incoming saves never spike bandwidth
- Removes the client-side full-save timer and the Player.Save trigger; routine changes still stream up incrementally via CharacterDeltaTracker, and join/logout still push a full save
0.10.1
- Forward leads character saves to ensure first round of delta saves are not discarded
0.10.0
- Anti-Cheat now enabled by default
- ValheimTooler detection reworked to be more flexible
- A confirmed ValheimTooler detection is always auto-banned (when cheat detection is enabled)
- Discord notification when a player is banned for cheat usage (NotifyCheaterBanned, default on, requires seperate webhook)
- Cheat Engine process scan throttled
- ScanIntervalSeconds default raised to 30 (now only affects the Cheat Engine check)
- Added another user to the global ban list
0.9.1
- Admin only mods now strongly restricted to admins
0.9.0
- Added Automatic ban list, built in known-banned
- Added discord notifications (server side) [Configurable!]
- Notify on player join
- Notify on player leave
- Notify on server start
- Notify on server shutdown
- Notify on mod mismatch
0.8.2
- Configurable save sync intervals for full saves and delta saves
- Last disconnect status tracked
- Allows reduction in strictness of item confiscation
- Added a confiscated timestamp
- Improved item return logic to drop items on the ground if the player does not have room for it
0.8.1
- Null check for status effects which no longer exist when adding to character
- Improves Item return RPC logic to deal with partially valid clients
- Improves compatibility with some custom status effects and saved custom data
0.8.0
- Improved Item, skill, status effect, and custom data consistency
- Added a catchall to persist character data when exiting without saving
0.7.3
- Polling filewatcher for better server side support with unix/hybrid storage (default check interval is 30s, configurable)
0.7.2
- Adds support for status effect tracking between sessions (configurable)
- Status effects (such as poison) will now be applied when you log back in, with their previous durations etc
- No more save scumming for a 60s poison tick
- On the plus side, your rested buff now stays between play sessions!
0.7.1
- Adds a very small amount of variance allowed for float rounding when validating item durability
- Adds extra details to the confiscation reason
0.7.0
- Added a confiscation reason field on items confiscated, field is optional but will be set for all confiscated items
- Removed redundant NewCharacterSkillsCleared setting (replaced by NewCharacterSetSkillsToZero)
- Set NewCharacterSetSkillsToZero default to false
- Added CheatDetector module (in testing, disabled by default)
- Client-side scanning for ValheimTooler (loaded assemblies) and Cheat Engine (process name, window class, injected speedhack/DBK modules, debugger, time-drift speedhack)
- New Anti-Cheat config section; default ActionOnDetection=Log
- Detections reported to server via new VENFORCE_CHEAT RPC
0.6.4
- Cache busting between player sessions
- Fixes character switching allowances for local only usage
- Add Extraslots compatability (restores items to the correct slots for characters with extraslots)
- Restores equipped status of items when they are returned to the player
0.6.3
- Explicitly requires yaml.net
0.6.2
- Improves item durability save bounding
0.6.1
- Adds item durability validation (configurable through ValidateItemDurability setting, default on)
0.6.0
- Improves custom data validation
- Enables Enforcer- commands for admins to retrieve confiscated items
- List player saves
- List confiscated items for a player
- Retrieve confiscated items (give to admin) from a player save
- Retrieve confiscated items (give to player) from a player save
- Optional (disabled by default) portable mode which stores all data inside the world
0.5.5
- Enforce quality and custom data consistency for all characters, including new characters on first load
- Added extra safety checks for player data settings
0.5.4
- Defaults to enforcing mod versions for active mods
- Automatically updates mod versions in all lists when the mod is updated on the server
- Fixes inconsistent server save IDs when recieving data from the client
0.5.3
- Fixes character fallback logic to more consistently select a non-mutating ID, prefers steamID and playfabID
0.5.2
- Fixes skill removal for new chracters on first load
0.5.1
- Fixes player custom data loading for new characters on first init
0.5.0
- Initial public beta

