A dedicated server host for CastleMiner Z built in C# / .NET Framework 4.8.1.
This project now uses a split host layout with two dedicated server implementations:
- CMZDedicatedSteamServer for Steam-based dedicated hosting
- CMZDedicatedLidgrenServer for direct-IP / Lidgren-based dedicated hosting
Both hosts load the original game/runtime assemblies through reflection, start a compatible dedicated server runtime, and host persistent world state outside the normal game client.
- Starts a dedicated CastleMiner Z compatible server by IP and port
- Loads the original game assemblies at runtime via reflection
- Supports a configurable
game-pathinstead of requiring the game under a hardcoded localGamefolder - Loads Harmony from a local
Libsfolder instead of next to the executable - Handles connection approval, discovery/session metadata, and gameplay packet relay
- Hosts world state server-side through
ServerWorldHandler - Loads
world.info, chunk delta data, and player inventories from disk - Relays player visibility, movement, text, block edits, chunk requests, pickups, and inventory flow
- Keeps authoritative day/time progression server-side and periodically broadcasts it to clients
- Automatically respawns all players in Endurance mode when every real connected player is dead, preventing dedicated servers from getting stuck on "Waiting for host to restart"
- Can persist and restore world time between restarts through the built-in RememberTime plugin
- Supports a configurable bind IP, port, player count, world GUID, view distance, and tick rate
- Separates the dedicated server implementations into Steam and Lidgren projects while keeping the shared project flow familiar
- Includes server-side Player Enforcement commands for listing players, hard-kicking, banning, unbanning, and viewing saved bans
- Hardens host authority by validating packet sender identity and blocking client-authored host-only messages such as forced host migration and spoofed kick packets.
- Logs command usage to dated audit files under
Logs\commands-yyyy-MM-dd.logwhenlog-command-audit=true. - Includes a built-in VanillaSpawners plugin that can prevent new vanilla spawner blocks and random vanilla loot blocks from generating, while optionally blocking existing spawner activation.
- Supports configurable command prefix aliases such as
!and/for in-game server commands. - Supports escaped multiline Announcements plugin messages using
\n. - Supports external server plugin DLLs under each host's
Pluginsfolder, with optional per-pluginDependenciesfolders. - Includes a shared
CMZDedicatedServer.PluginAPIproject for transport-neutral plugin contracts. - RegionProtect supports name-based allow-lists and SteamID64 allow-lists on the Steam dedicated server.
CMZDedicatedServers/
├─ LICENSE
├─ README.md
├─ build.bat
├─ clean.bat
├─ Directory.Build.props
├─ Build/
│ ├─ CMZDedicatedSteamServer/
│ └─ CMZDedicatedLidgrenServer/
├─ CMZDedicatedServers.sln
├─ CMZDedicatedServer.PluginAPI/
│ ├─ CMZDedicatedServer.PluginAPI.csproj
│ └─ Plugins/
│ └─ ServerPluginContracts.cs
├─ Shared/
│ └─ Plugins/
│ └─ Example/
│ ├─ Example.csproj
│ └─ ServerExamplePlugin.cs
├─ CMZDedicatedLidgrenServer/
│ ├─ CMZDedicatedLidgrenServer.csproj
│ ├─ Program.cs
│ ├─ Config/
│ │ └─ ServerConfig.cs
│ ├─ Hosting/
│ │ ├─ LidgrenServer.cs
│ │ ├─ ServerAssemblyLoader.cs
│ │ └─ ServerRuntime.cs
│ ├─ Networking/
│ │ └─ CmzMessageCodec.cs
│ ├─ Patching/
│ │ └─ ServerPatches.cs
│ ├─ Templates/
│ │ └─ server.properties
│ ├─ World/
│ │ └─ ServerWorldHandler.cs
│ ├─ Libs/
│ │ └─ 0Harmony.dll
│ └─ ServerHost/
│ ├─ RunServer.bat
│ ├─ Game/
│ ├─ Inventory/
│ ├─ Libs/
│ └─ Worlds/
└─ CMZDedicatedSteamServer/
├─ CMZDedicatedSteamServer.csproj
├─ Program.cs
├─ Common/
│ └─ ReflectEx.cs
├─ Config/
│ └─ SteamServerConfig.cs
├─ Hosting/
│ ├─ ServerAssemblyLoader.cs
│ ├─ SteamConnectionApproval.cs
│ ├─ SteamDedicatedServer.cs
│ ├─ SteamLobbyHost.cs
│ └─ SteamPeerRegistry.cs
├─ Networking/
│ └─ CmzMessageCodec.cs
├─ Steam/
│ └─ SteamServerBootstrap.cs
├─ Templates/
│ ├─ server.properties
│ └─ steam_appid.txt
├─ World/
│ └─ ServerWorldHandler.cs
├─ Libs/
│ └─ 0Harmony.dll
└─ ServerHost/
├─ RunServer.bat
├─ Game/
├─ Inventory/
├─ Libs/
└─ Worlds/
Entry point for each dedicated host.
It:
- loads
server.properties - resolves the game binaries folder from
game-pathor falls back to the local server layout - resolves support libraries from the local
Libsfolder - loads
CastleMinerZ.exeand related assemblies - applies Harmony patches
- prints a startup summary
- starts the server and enters the update loop
The direct-IP / Lidgren dedicated host.
It is responsible for:
- binding the socket
- connection approval
- direct-IP traffic handling
- gameplay packet relay
- player-exists cache/replay for new joiners
- live connection enumeration for outgoing sends
- pickup consume relay support
- authoritative day/time progression and periodic world time broadcasts
Key files include:
Hosting/LidgrenServer.csHosting/ServerRuntime.csWorld/ServerWorldHandler.csNetworking/CmzMessageCodec.csPatching/ServerPatches.cs
The Steam-based dedicated host.
It is responsible for:
- Steam dedicated server bootstrap and registration
- Steam connection approval and peer tracking
- lobby and Steam networking host flow
- gameplay packet relay through the reflected game runtime
- authoritative world hosting and persistence
Key files include:
Hosting/SteamDedicatedServer.csHosting/SteamLobbyHost.csHosting/SteamConnectionApproval.csHosting/SteamPeerRegistry.csSteam/SteamServerBootstrap.csWorld/ServerWorldHandler.csNetworking/CmzMessageCodec.cs
The shared plugin API project contains transport-neutral server plugin contracts and context types used by both dedicated hosts.
It is intended for external and shared server plugins to reference without depending on Steam-only or Lidgren-only internals.
The shared plugin source tree can hold reusable plugin projects that target both hosts.
A plugin under Shared/Plugins/<PluginName>/ can build once and copy its output into both:
Build\CMZDedicatedSteamServer\Plugins\<PluginName>\
Build\CMZDedicatedLidgrenServer\Plugins\<PluginName>\
Across both hosts, the project still centers around the same core flow:
- config loading
- reflected game/runtime loading
- packet/message translation
- server-side world persistence
- inventory/world save handling
- authoritative time/day progression
- Windows
- .NET Framework 4.8.1
- Visual Studio / MSBuild capable of building .NET Framework projects
- The original CastleMiner Z game files available somewhere on disk
The game files do not have to live under a folder literally named Game as long as game-path points to the correct location.
Each dedicated host reads configuration from server.properties in its server root.
Example:
server-name=CMZ Server
game-name=CastleMinerZSteam
network-version=4
game-path=C:\Program Files (x86)\Steam\steamapps\common\CastleMiner Z
server-ip=0.0.0.0
server-port=61903
max-players=8
steam-user-id=76561198296842857
world-guid=b8c81243-b6ac-48fe-a782-1e2dc5a44d17
view-distance-chunks=8
tick-rate-hz=60
game-mode=1
pvp-state=0
difficulty=1The server-name value supports simple runtime tokens. These tokens are replaced by the dedicated server before the name is shown to players.
Example:
server-name=Test Server | Day {day}This may appear in the server browser or join/session info as:
Test Server | Day 12
Supported tokens:
| Token | Description | Example |
|---|---|---|
{day} |
Current player-facing world day. | 12 |
{day00} |
Current world day padded to two digits. | 07 |
{players} |
Current connected player count. | 3 |
{maxplayers} |
Configured maximum player count. | 32 |
Example with player count:
server-name=Test Server | Day {day00} | {players}/{maxplayers}Example output:
Test Server | Day 07 | 3/32
Notes:
- Tokens are optional. A normal static name such as
server-name=CMZ Serverstill works. - The day value is controlled by the dedicated server's authoritative time progression.
- Very long names may be shortened before being published to the server/session browser.
For CMZDedicatedLidgrenServer, the resolved name is sent through discovery responses and join-time server/session info packets. The raw template remains in server.properties, but compatible clients see the resolved display name.
Example:
server-name=Test Server | Day {day} | dsc.gg/cforgeDirectConnect/session display:
Test Server | Day 12 | dsc.gg/cforge
The server-message value controls the player-facing in-game/session message.
Example:
server-message=Welcome to the CastleForge 24/7 server! Discord: dsc.gg/cforgeThe message can also use the same runtime tokens as server-name:
server-message=Welcome! Current day: {day}. Players online: {players}/{maxplayers}. Discord: dsc.gg/cforgeSupported tokens:
| Token | Description | Example |
|---|---|---|
{day} |
Current player-facing world day. | 12 |
{day00} |
Current world day padded to two digits. | 07 |
{players} |
Current connected player count. | 3 |
{maxplayers} |
Configured maximum player count. | 32 |
For CMZDedicatedLidgrenServer, the resolved message is sent through discovery/session info so compatible clients can show it separately from the server name. For CMZDedicatedSteamServer, the resolved message is published through the Steam-hosted session/lobby metadata used by CastleMiner Z's browser/details flow.
Steam note: CastleMiner Z's vanilla Steam browser may use the same Steam session metadata field for the displayed server message/details text, so the visible browser/details behavior can depend on how the Steam lobby metadata is consumed by the client.
| Key | Purpose |
|---|---|
server-name |
Display name shown to clients |
server-message |
Player-facing in-game/session message. Supports dynamic tokens such as {day}, {day00}, {players}, and {maxplayers}. |
game-name |
Expected network game name |
network-version |
Expected protocol version |
game-path |
Optional path to the CastleMiner Z binaries folder; falls back to the local server layout when omitted |
server-ip |
Bind address (0.0.0.0 or any binds all interfaces) |
server-port |
Port clients connect to |
max-players |
Maximum connected players |
steam-user-id |
Save-device / world key identity used for storage access |
world-guid |
GUID used to locate the world folder |
view-distance-chunks |
Chunk radius used by the host |
tick-rate-hz |
Server update loop rate |
game-mode |
Session game mode value. Within Endurance; when all players are dead, the server automatically sends a restart message. |
pvp-state |
Session PVP state value |
difficulty |
Session difficulty value |
build.batThis script:
- locates MSBuild using
vswhere - restores and builds the dedicated-server solution
- writes output into the shared root
Buildfolder - keeps the Steam and Lidgren outputs separated into their own build folders
Expected build output:
Build/
├─ CMZDedicatedSteamServer/
│ ├─ CastleForge.ServerPluginAPI.dll
│ └─ Plugins/
│ └─ Example/
│ └─ Example.dll
└─ CMZDedicatedLidgrenServer/
├─ CastleForge.ServerPluginAPI.dll
└─ Plugins/
└─ Example/
└─ Example.dll
Solution:
src/CMZDedicatedServers.sln
Project files:
src/CMZDedicatedSteamServer/CMZDedicatedSteamServer.csproj
src/CMZDedicatedLidgrenServer/CMZDedicatedLidgrenServer.csproj
src/CMZDedicatedServer.PluginAPI/CMZDedicatedServer.PluginAPI.csproj
src/Shared/Plugins/Example/Example.csproj
Important project settings:
- Target framework:
net481 - Platform target:
x86 - Shared build root:
Build\ - Steam host output path:
Build\CMZDedicatedSteamServer\ - Lidgren host output path:
Build\CMZDedicatedLidgrenServer\ - Shared plugin API project:
CMZDedicatedServer.PluginAPI - Shared plugin source projects:
Shared\Plugins\<PluginName>\
After building, run the host you want to use.
Build\CMZDedicatedSteamServer\CMZDedicatedSteamServer.exeBuild\CMZDedicatedLidgrenServer\CMZDedicatedLidgrenServer.exeYou can also use the included per-host helper scripts under each project's ServerHost folder if you prefer to maintain a host-specific runtime layout.
On startup the server prints a summary similar to:
CMZ Dedicated Server
--------------------
GameName : CastleMinerZSteam
NetworkVersion : 4
Bind : 0.0.0.0:61903
ServerName : CMZ Server
MaxPlayers : 8
SaveOwnerSteamId : 76561198296842857
WorldGuid : ...
WorldFolder : Worlds\...
WorldPath : ...
WorldInfo file : ...\world.info
World loaded : True
Then connect using the server IP and the configured server-port.
The server needs access to the real CastleMiner Z binaries and content.
Each host can be pointed at the real game install using game-path, for example:
game-path=C:\Program Files (x86)\Steam\steamapps\common\CastleMiner ZIf you prefer a local runtime layout instead, the host-specific ServerHost\Game\ folders can be used as the local game-content location.
0Harmony.dll is expected under the host's local Libs folder/runtime layout.
Examples:
src\CMZDedicatedSteamServer\Libs\0Harmony.dll
src\CMZDedicatedLidgrenServer\Libs\0Harmony.dll
And for local host layouts:
src\CMZDedicatedSteamServer\ServerHost\Libs\0Harmony.dll
src\CMZDedicatedLidgrenServer\ServerHost\Libs\0Harmony.dll
It does not need to live next to the executable.
External server plugins reference the shared plugin API assembly.
For public/release builds, the intended friendly DLL name is:
CastleForge.ServerPluginAPI.dll
This file should live beside each dedicated server executable:
Build\CMZDedicatedSteamServer\CastleForge.ServerPluginAPI.dll
Build\CMZDedicatedLidgrenServer\CastleForge.ServerPluginAPI.dll
Do not copy a separate copy of the API DLL into each plugin's Dependencies folder. Keeping one shared API assembly loaded by the server avoids plugin type identity problems.
The host derives the world folder from world-guid and expects it under the selected host's world storage layout:
Worlds\{world-guid}
Typical files used by the host include:
world.info- chunk delta data
- player inventory saves
Both dedicated hosts keep the same general world/save flow even though the repository is now split into two server implementations.
The current implementation includes:
- compatible gameplay packet handling through the reflected game runtime
- direct send and broadcast wrapper handling
- player visibility bootstrap via cached/replayed
PlayerExistsMessage - relay of text/chat-style packets and gameplay updates
- server-side pickup resolution for create/request/consume flow
- live connection enumeration to avoid stale connection lists on outbound sends
The exact transport/bootstrap behavior differs by host:
- CMZDedicatedSteamServer focuses on Steam hosting/bootstrap flow
- CMZDedicatedLidgrenServer focuses on direct-IP / Lidgren hosting flow
Both dedicated hosts validate packet sender identity before relaying gameplay packets. Client-declared sender ids must match the actual connected peer assigned by the server.
The hosts also block client-authored host-only packets, including forced host migration and spoofed kick-style packets. This keeps host authority on the dedicated server instead of allowing a modified peer to appoint itself as host or remove other players.
This protection is built in and has no required configuration.
Both dedicated hosts include basic console-based player enforcement.
Supported commands:
| Command | Description |
|---|---|
players |
Lists connected players. |
bans |
Lists saved bans. |
kick <target> [reason] |
Hard-kicks a connected player. |
ban <target> [reason] |
Bans and hard-drops a player. |
unban <target> |
Removes a saved ban. |
Examples:
players
bans
kick 2 Being annoying
ban 2 Griefing protected areas
ban "Jacob Smith" Griefing protected areas
unban jacob
Steam bans are SteamID-backed and also save the last known player name for readability.
Lidgren bans are IP/name-backed and are useful for direct-IP hosting, but they are weaker than SteamID bans because names and IP addresses can change.
Player Enforcement uses a hard drop / transport-level removal path instead of relying only on normal client-side kick messages. This helps prevent modified clients from bypassing enforcement by removing the kick-message pipeline.
The dedicated host advances world time using real elapsed time rather than fixed loop iterations.
This is important because the server loop may run faster or slower than a normal 60 FPS client host. The current implementation keeps authoritative day progression on the server and periodically broadcasts the current world day/time to clients.
When game-mode=0 is used, CastleMiner Z's normal Endurance flow expects the original host player to restart the level after everyone dies.
Dedicated servers do not have a real playable host sitting on the death screen, so players could otherwise remain stuck on:
Waiting for host to restart
To prevent this, the dedicated host tracks the dead/alive state of real connected players. When every real player is dead, the server automatically sends the vanilla restart/respawn message to connected clients.
This does not restart the dedicated server process and does not intentionally reset the world back to Day 1. After the respawn message is sent, the server continues using its authoritative day/time value and broadcasts that time back to clients.
Notes:
- This behavior only applies to
game-mode=0/ Endurance. - The synthetic dedicated-server host/player is ignored and does not count as an alive player.
- The world save, chunks, inventories, and server process remain active.
- If the client briefly appears to reset its local day display, the server's authoritative time broadcast corrects it.
- The server is built around reflection rather than direct game project references.
- The hosts use the original game message types and message registry where possible.
- The server update loop is driven by
tick-rate-hz. - Pickup, inventory, chunk, and terrain flow are now handled server-side well enough for basic dedicated play.
- The repository now keeps Steam and Lidgren as separate projects under one solution.
- The root build process outputs both hosts into the shared
Buildfolder. - External plugin DLLs are loaded after the built-in plugins are registered, then all registered plugins are initialized together.
CastleForge Dedicated Servers include server-side plugin support for host-authoritative world protections, server automation, and future server extensions.
Plugins run inside the dedicated server process and can inspect selected host/world packets before the server applies or relays them. This allows the server to enforce rules even when connecting players do not have the matching client-side mod installed.
Both hosts support two plugin styles:
- Built-in plugins compiled directly into the dedicated server build and registered by the host at startup.
- External plugin DLLs loaded from each server's runtime
Pluginsfolder.
Current built-in plugin support includes:
- Announcements private join messages and timed global messages
- FloodGuard malicious packet spam protection
- RememberTime per-world time persistence between restarts
- RegionProtect server enforcement
- Player Enforcement commands for listing players, hard-kicking, banning, unbanning, viewing saved bans, managing command ranks, and protecting operators from kick/ban commands
- VanillaSpawners vanilla spawner and random loot block generation controls
- block mining / placing protection
- explosion protection
- crate item protection
- crate break protection
- per-world plugin configuration
External plugins should reference the shared server plugin API project:
CMZDedicatedServer.PluginAPI
The public runtime assembly is intended to be copied beside each dedicated server executable:
Build\CMZDedicatedSteamServer\CastleForge.ServerPluginAPI.dll
Build\CMZDedicatedLidgrenServer\CastleForge.ServerPluginAPI.dll
If your local project file still outputs CMZDedicatedServer.PluginAPI.dll, either rename the project AssemblyName to CastleForge.ServerPluginAPI or adjust the runtime/docs to use the technical DLL name consistently.
The plugin API contains the transport-neutral contracts and context types used by both hosts, such as:
IServerPluginIServerWorldPluginIServerInboundPacketPluginIServerPlayerEventPluginIServerTickPluginIServerShutdownPluginServerPluginContextHostMessageContextServerInboundPacketContextServerPlayerEventContext
Shared plugins should depend on these server callbacks, not on Steam-only or Lidgren-only transport internals.
External plugins are loaded from the host's runtime Plugins folder.
Example:
Build/
├─ CMZDedicatedSteamServer/
│ ├─ CMZDedicatedSteamServer.exe
│ ├─ CastleForge.ServerPluginAPI.dll
│ └─ Plugins/
│ └─ Example/
│ ├─ Example.dll
│ └─ Dependencies/
│ └─ SomeDependency.dll
└─ CMZDedicatedLidgrenServer/
├─ CMZDedicatedLidgrenServer.exe
├─ CastleForge.ServerPluginAPI.dll
└─ Plugins/
└─ Example/
├─ Example.dll
└─ Dependencies/
└─ SomeDependency.dll
Dependency DLLs are optional. If a plugin has no extra DLL dependencies, the Dependencies folder is not required.
Reusable plugins that target both hosts can live under:
Shared/
└─ Plugins/
└─ Example/
├─ Example.csproj
└─ ServerExamplePlugin.cs
Each shared plugin project can build once and copy its output into both dedicated server runtime folders:
Build\CMZDedicatedSteamServer\Plugins\Example\Example.dll
Build\CMZDedicatedLidgrenServer\Plugins\Example\Example.dll
The server reload command can reload runtime-safe config and plugin state, but it should not be treated as a DLL hot-reload system.
On .NET Framework, plugin assemblies loaded into the default AppDomain cannot be unloaded cleanly. Adding, removing, or replacing plugin DLLs requires a dedicated server restart.
External server plugins run in the same process as the dedicated server. Only install plugins from trusted sources, because a plugin can read files, write files, send network messages, or crash the server process.
The dedicated servers include a built-in Announcements plugin for simple server messages.
Announcements can:
- send a private welcome message to each joining player
- send a timed global message to all connected players
- split configured messages into multiple chat lines using escaped
\n - wait a configurable amount of time before the first global message
- require a minimum number of online players before global messages are sent
- reload its config from disk using the server console
reloadcommand, if enabled by the host
The Announcements config is stored beside each dedicated server executable:
CMZDedicatedSteamServer/
└─ Plugins/
└─ Announcements/
└─ Announcements.Config.ini
For the Lidgren dedicated server:
CMZDedicatedLidgrenServer/
└─ Plugins/
└─ Announcements/
└─ Announcements.Config.ini
[General]
Enabled = true
[Join]
PrivateJoinMessageEnabled = true
PrivateJoinMessage = Welcome {player}! This is a CastleForge dedicated server. Join us: dsc.gg/cforge\nUse '/help' or '!help' for available commands.
[Global]
TimedGlobalMessageEnabled = true
GlobalMessage = Need help, updates, or mods? Join the CastleForge Discord: dsc.gg/cforge\nCurrent players: {players}/{maxplayers}
InitialGlobalDelaySeconds = 120
GlobalMessageIntervalMinutes = 15
MinimumPlayersForGlobalMessage = 1Announcement messages support simple runtime tokens:
| Token | Description | Example |
|---|---|---|
{player} |
Joining player's display name. | RussDev7 |
{players} |
Current connected player count. | 3 |
{maxplayers} |
Configured maximum player count. | 32 |
{time} |
Current local server time. | 8:30 PM |
{date} |
Current local server date. | 2026-04-26 |
Announcement messages can use escaped \n to split one config value into multiple chat lines.
Example:
PrivateJoinMessage = Welcome {player}!\nUse '/help' or '!help' for available commands.
GlobalMessage = Join the CastleForge Discord: dsc.gg/cforge\nPlayers online: {players}/{maxplayers}The server sends each line as a separate chat message.
Example private join output:
Welcome RussDev7!
Use '/help' or '!help' for available commands.
Example global output:
Join the CastleForge Discord: dsc.gg/cforge
Players online: 3/32
Physical multiline INI values are not required. Keep the message on one config line and use \n where a line break should appear.
The private join message is sent only to the joining player.
The timed global message is broadcast to all connected players after InitialGlobalDelaySeconds, then repeats every GlobalMessageIntervalMinutes.
Set MinimumPlayersForGlobalMessage = 0 to allow global messages even when the server is empty, or set it to 1 or higher to only announce when players are online.
Notes:
\nis decoded by the Announcements plugin and sent as separate chat lines.- Empty multiline entries are skipped.
- Multiline support applies to both
PrivateJoinMessageandGlobalMessage.
The dedicated servers include a built-in RegionProtect plugin that protects configured world areas directly from the server.
Unlike the normal client/host RegionProtect mod, the dedicated server version does not require players to install anything client-side. The server checks protected actions before saving or relaying world changes.
RegionProtect currently protects:
| Action | Packet handled | Description |
|---|---|---|
| Mining / block breaking | AlterBlockMessage |
Blocks protected terrain removal |
| Block placing | AlterBlockMessage |
Blocks protected block placement |
| Explosions | DetonateExplosiveMessage / RemoveBlocksMessage |
Blocks protected explosion damage |
| Crate item edits | ItemCrateMessage |
Blocks adding/removing crate contents in protected areas |
| Crate breaking | DestroyCrateMessage |
Blocks crate destruction in protected areas |
RegionProtect stores its configuration beside each dedicated server executable:
CMZDedicatedLidgrenServer/
└─ Plugins/
└─ RegionProtect/
├─ RegionProtect.Config.ini
└─ Worlds/
└─ <world-guid>/
└─ RegionProtect.Regions.ini
For the Steam dedicated server:
CMZDedicatedSteamServer/
└─ Plugins/
└─ RegionProtect/
├─ RegionProtect.Config.ini
└─ Worlds/
└─ <world-guid>/
└─ RegionProtect.Regions.ini
RegionProtect.Config.ini controls which protection systems are enabled:
[General]
Enabled = true
ProtectMining = true
ProtectPlacing = true
ProtectExplosions = true
ProtectCrateItems = true
ProtectCrateMining = true
WarnPlayers = true
WarningCooldownSeconds = 2
LogDenied = trueEach world has its own RegionProtect.Regions.ini file:
AllowedPlayers accepts player display names. On the Steam dedicated server, AllowedSteamIds also accepts SteamID64 values for stable identity-based allow-lists.
[SpawnProtection]
Enabled = true
Range = 64
AllowedPlayers = RussDev7
AllowedSteamIds = 76561198XXXXXXXXX
[Region:SpawnTown]
Min = -80,0,-80
Max = 80,120,80
AllowedPlayers = RussDev7,SomeAdmin
AllowedSteamIds = 76561198XXXXXXXXX,76561198XXXXXXXXXWhen a player tries to edit a protected area, the server blocks the action and sends a warning such as:
[RegionProtect] Protected by region 'SpawnTown'. Breaking blocks here was blocked. Client-only desync; not saved to server.
In some cases, the client may briefly show a block as broken or changed. The server does not save that blocked change, and the area will correct itself after resyncing or rejoining.
- RegionProtect is server-authoritative.
- Players do not need the RegionProtect mod installed to be blocked by protected regions.
- Commands such as
/regionposand/regioncreateare not currently part of the dedicated server plugin. - Regions are currently edited manually through the
.inifiles. - Explosion restoration can visually desync on the attacking client, but protected explosion damage is not saved to the server.
- On the Steam dedicated server, prefer
AllowedSteamIdsfor trusted players/admins because SteamID64 is stable across display-name changes. - On the Lidgren dedicated server, RegionProtect allow-lists are effectively name-based.
- Existing
RegionProtect.Regions.inifiles are not automatically rewritten with new keys. AddAllowedSteamIds =manually or regenerate the file if you want the new example format. AllowedSteamIdsexpects numeric SteamID64 values, not Steam profile names or vanity URLs.
FloodGuard is a lightweight packet-rate guard for the dedicated server. It watches inbound gameplay packets before normal host/world handling or peer relay. When a sender exceeds the configured rate, the server temporarily blackholes that sender's packets instead of relaying or applying them.
Config is created on first run at:
Plugins\FloodGuard\FloodGuard.Config.ini
Default config:
[General]
Enabled = true
PerSenderMaxPacketsPerSec = 120
BlackholeMs = 3000
[AllowedPlayers]
# Comma-separated allow list. Entries may be player names, Player1-style fallback names,
# numeric GIDs, or SteamIDs on the Steam server.
AllowedPlayers =Notes:
PerSenderMaxPacketsPerSecis counted per sender/GID over a one-second window.BlackholeMsis how long to silently drop packets after the sender exceeds the limit.AllowedPlayersbypasses FloodGuard for trusted players or test accounts.
The dedicated servers include a built-in VanillaSpawners plugin for controlling vanilla-generated spawner blocks and random vanilla loot blocks.
This is useful for long-running dedicated servers where players stay near one area for a long time and newly generated terrain can accumulate a large number of monster, alien, hell, boss spawner blocks, or random loot blocks.
VanillaSpawners can:
- prevent new vanilla cave / alien / hell / boss spawner blocks from generating
- prevent new random vanilla
LootBlock/LuckyLootBlockblocks from generating - optionally block activation of already-existing vanilla spawner blocks
- keep old saves intact without deleting existing spawner or loot blocks
- enforce the behavior server-side even when players do not have a matching client-side mod installed
For the Steam dedicated server:
CMZDedicatedSteamServer/
└─ Plugins/
└─ VanillaSpawners/
└─ VanillaSpawners.Config.ini
For the Lidgren dedicated server:
CMZDedicatedLidgrenServer/
└─ Plugins/
└─ VanillaSpawners/
└─ VanillaSpawners.Config.ini
[General]
Enabled = true
# Allows new vanilla cave / alien / hell / boss spawner blocks to generate.
# false prevents NEW spawner blocks from being placed in newly generated terrain.
# Existing chunks and saves are not deleted or modified.
GenerateSpawnerBlocks = true
# Allows existing vanilla spawner blocks to be activated.
# false consumes spawner-origin enemy spawns and spawner block-state changes server-side.
AllowSpawnerActivation = true
# Allows new vanilla LootBlock / LuckyLootBlock blocks to generate.
# false prevents NEW random loot blocks from being placed in newly generated terrain.
# Existing chunks and saves are not deleted or modified.
GenerateLootBlocks = true
# Logs each blocked spawner activation packet.
# Useful for debugging, but noisy if players keep trying old spawners.
LogBlockedActivation = false[General]
Enabled = true
GenerateSpawnerBlocks = false
AllowSpawnerActivation = true
GenerateLootBlocks = false
LogBlockedActivation = falseThis prevents newly generated terrain from placing new vanilla spawner blocks or random vanilla loot blocks, while still allowing existing spawner blocks to work.
[General]
Enabled = true
GenerateSpawnerBlocks = false
AllowSpawnerActivation = false
GenerateLootBlocks = false
LogBlockedActivation = falseThis prevents new vanilla spawner blocks and random vanilla loot blocks from generating, and also blocks existing vanilla spawner activation server-side.
GenerateSpawnerBlocks=falseonly affects newly generated terrain.GenerateLootBlocks=falseonly affects newly generated terrain.- Existing chunks and saved worlds are not modified.
- Existing spawner blocks and loot blocks may still be visible in old worlds.
GenerateLootBlocks=falsetargets vanillaLootBlockandLuckyLootBlockworld-generation blocks.- It does not block normal crate/container blocks unless a separate crate-control feature is added later.
AllowSpawnerActivation=falseblocks spawner-origin enemy spawns and spawner block-state changes server-side.- Vanilla clients may briefly appear to interact with an old spawner locally, but the server rejects the resulting spawner behavior.
- For the cleanest client-side experience, pair the server plugin with the ModLoaderExtensions client-side vanilla world-generation controls.
The dedicated servers include a built-in RememberTime plugin that saves the server's authoritative day/time value and restores it when the server starts again.
RememberTime can:
- save the current authoritative server time on a configurable interval
- restore the saved time when the server process starts
- save one final time during clean server shutdown
- store saved time per world so different worlds keep separate day/time progress
- reduce disk writes by using
SaveIntervalSecondsinstead of writing every tick
RememberTime creates a shared plugin config and per-world state file beside each dedicated server executable.
For the Steam dedicated server:
CMZDedicatedSteamServer/
└─ Plugins/
└─ RememberTime/
├─ RememberTime.Config.ini
└─ Worlds/
└─ <world-guid>/
└─ Time.State.ini
For the Lidgren dedicated server:
CMZDedicatedLidgrenServer/
└─ Plugins/
└─ RememberTime/
├─ RememberTime.Config.ini
└─ Worlds/
└─ <world-guid>/
└─ Time.State.ini
[General]
Enabled = true
# Saves the server's current day/time every X seconds.
# Higher values reduce disk writes.
# Lower values reduce lost time if the server crashes.
SaveIntervalSeconds = 60
# Restores the saved time when the server process starts.
RestoreOnStartup = true
# Logs every successful interval save.
# Very spammy. Useful only when debugging RememberTime.
LogIntervalSaves = false
# Writes one final time when the server stops cleanly.
SaveOnShutdown = true[State]
TimeOfDay = 12.4135227
DisplayDay = 13
SavedUtc = 2026-04-27T18:20:31.0000000Z
Reason = intervalTimeOfDayis the full server day/time float, not only the0.0to1.0visual time fraction.- Example:
12.41means the server is on display Day 13 at roughly the same visual time as0.41. SaveIntervalSeconds = 60is a good default for normal hosting.- If the process crashes, the server may lose up to the configured interval. Clean shutdowns still write one final save when
SaveOnShutdown = true. - The saved time affects dynamic
{day}and{day00}tokens after restore because those tokens use the authoritative server time. LogIntervalSaves = falsekeeps normal interval saves quiet in the console. Startup, restore, shutdown, and error logs can still appear.- Set
LogIntervalSaves = trueonly when debugging RememberTime save timing.
Make sure CastleMinerZ.exe exists either in the location specified by game-path or in the local host runtime layout you are using.
Make sure Harmony exists in the expected host Libs folder.
Check:
server-port- firewall rules
- that both clients are using the same IP and port
game-nameandnetwork-version- that you are launching the correct host for the connection flow you want to test
Check the world-guid value in server.properties and verify the folder exists under the selected host's Worlds\ storage location.
Check that steam-user-id is populated correctly, because the current save-device setup still uses it as the storage identity/key seed.
This project is licensed under GPL-3.0-or-later. See LICENSE for details.
Developed and maintained by:
- RussDev7
- unknowghost0