Mod Project Setup¶
Framework stack, assembly references, plugin template, Config.Bind usage, and Harmony patch types used by every Stationeers mod in this monorepo. Reach for this page when scaffolding a new mod or when a contributor asks "what does the baseline look like."
When to use¶
- Seeding a new mod from
Mods/Template/and wondering what the framework baseline is. - Adding a BepInEx config option and wondering where the file lands and how to wire it up.
- Writing a first Harmony patch and needing the patch-type vocabulary.
Framework stack¶
| Component | Version | Role |
|---|---|---|
| BepInEx | 5.4.21+ (x64, Mono) | Plugin loader |
| Harmony (HarmonyX) | 2.x (bundled with BepInEx) | Runtime method patching |
| StationeersLaunchPad | Latest | Mod loader UI + config |
| .NET Framework | 4.5.2 | Target framework |
| Unity | 2021.2.x (game version) | Engine (relevant for asset work only) |
Assembly references needed¶
From the Stationeers install at $(StationeersPath)\rocketstation_Data\Managed\:
Assembly-CSharp.dll(game code)UnityEngine.dll+UnityEngine.CoreModule.dllcom.unity.multiplayer-hlapi.Runtime.dll(networking)
From $(StationeersPath)\BepInEx\core\:
0Harmony.dllBepInEx.dll
Community package for game assemblies: https://github.com/ilodev/stationeers.modding.assemblies
Plugin template¶
[BepInPlugin("com.author.modname", "Mod Name", "1.0")]
public class MyPlugin : BaseUnityPlugin
{
public static MyPlugin Instance;
void Awake()
{
Instance = this;
var harmony = new Harmony("com.author.modname");
harmony.PatchAll();
}
}
Harmony patch types¶
- Prefix: Runs before original. Return
falseto skip original. Gets__instance, can modify params. - Postfix: Runs after original. Can modify
__result. - Transpiler: Modifies IL code at load time.
- Reverse Patch: Calls private/internal methods from mod code.
For the traps around inherited methods, __instance typing, and attribute placement, see HarmonyPatchTypes.md.
Config.Bind pattern¶
var config = Config.Bind("Section", "Key", defaultValue, "Description");
// Auto-saved to BepInEx/Config/org.author.modname.cfg
Every config value lives in a single .cfg file under the Stationeers install's BepInEx/Config/ folder named after the plugin's GUID. Subscribers can edit the file directly; the plugin re-reads on load.
Private field access¶
// Harmony convention: 4 underscores + field name
static FieldInfo myField = AccessTools.Field(typeof(WeatherManager), "_stormWindStrength");
myField.SetValue(__instance, newValue);
// Or via parameter naming in patch method:
public static void Patch(float ____privateFieldName) { ... }
Key singletons¶
GameManager.IsServer/NetworkManager.IsServerWorldManager.IsPaused,WorldManager.Instance.GameModeWeatherManager.InstanceWorldManager.Instance.SourcePrefabs- master prefab list
Known gotchas¶
Device.AllDevicescan contain duplicates (use HashSet to dedup)AtmosphericsManager.AllAtmospherescan contain nulls (must filter)- Power calculations can produce
NaN(guard against this) - Power tick runs on background thread -- can't use
UnityEngine.Random - Atmosphere may be null until a player logs in on dedicated servers
Verification¶
- After the initial scaffold compiles, drop the DLL into the Stationeers install's
BepInEx/plugins/<ModName>/folder, launch the game, and confirm the BepInEx log records the plugin's Awake. - After the first
Config.Bindcall, confirm the expected.cfgfile appears under the Stationeers install'sBepInEx/Config/folder with the configured section, key, default value, and description comment. - After the first
harmony.PatchAll()call, confirm Harmony's own startup log lists the patched methods.
Pitfalls¶
- Target
.NET Framework 4.5.2; newer target frameworks produce assemblies the game cannot load. - Reference game assemblies via
$(StationeersPath)(seeDirectory.Build.props.templateat the repo root). Hardcoded absolute paths break clones on any other machine. harmony.PatchAll()reflects over the plugin assembly looking for[HarmonyPatch]classes. Classes that target types the game no longer exposes throw at startup; guard with[HarmonyPatch]onTargetMethod()plus aPrepare()check when reflecting against a moving target.
Verification history¶
- 2026-04-20: page created from the Research migration; verbatim content lifted from F0229d (framework stack), F0229k (Config.Bind), and F0229l (plugin template + patch types) in
Plans/RepairPrototype/plan.md. Per Phase 2 misfit resolutions M2 / M3 / M4.
Open questions¶
None at creation.