Deep dive into Nitrox’s Harmony-based runtime patching system
Nitrox uses Harmony (specifically HarmonyX) to modify Subnautica’s behavior at runtime without modifying the game’s original files. This approach allows the mod to intercept, modify, and extend game functionality for multiplayer support.
Harmony patches work by modifying the Common Intermediate Language (CIL) of methods at runtime:
Target Method: Identify a method in Subnautica’s code to patch
Patch Type: Choose how to modify it (Prefix, Postfix, Transpiler, or Finalizer)
Patch Application: Harmony rewrites the method’s IL code to include your modifications
Runtime Execution: When the game calls the method, your patch executes
Harmony patches are non-destructive and can be removed at runtime, making them ideal for dynamic multiplayer features that only need to be active during a session.
Applied when the game starts and remain active for the entire process lifetime. Used for core functionality that must always be present.
public sealed partial class Application_runInBackground_Patch : NitroxPatch, IPersistentPatch{ public static readonly MethodInfo TARGET_METHOD = Reflect.Property(() => Application.runInBackground).GetSetMethod(); public static bool Prefix(bool value) { if (!value) { Log.WarnOnce($"An attempt to set runInBackground to false was ignored."); Application.runInBackground = true; return false; // Skip original method } return true; // Execute original method }}
Location: NitroxPatcher/Patches/Persistent/Purpose: Ensures critical Nitrox functionality (like keeping the game running in background for packet processing) is always active.
Run before the original method. Can prevent the original method from executing.
public static bool Prefix(OriginalClass __instance, ParameterType param){ // Return false to skip original method // Return true to execute original method return true;}
public static void Prefix(YourClassName __instance, ParameterType parameter){ Log.Debug($"MethodName called with {parameter}");}public static void Postfix(YourClassName __instance){ // Send packet to synchronize this action Resolve<IPacketSender>().Send(new YourCustomPacket(__instance.Data));}
Use the format ClassName_MethodName_Patch so developers can quickly identify what’s being patched.
Document why, not what
/// <summary>/// Prevents the game from unloading multiplayer-critical entities when/// they move far from the player. Required for cross-player interactions./// </summary>
Use dependency injection
Access services via Resolve<T>() instead of static references:
Resolve<IPacketSender>().Send(packet);
Handle null cases
Subnautica’s code may have unexpected null values:
if (!@base.TryGetNitroxId(out NitroxId baseId)){ Log.Error("Couldn't find NitroxEntity on deconstructed base"); return;}
Choose dynamic vs persistent carefully
Use persistent for core functionality (logging, assembly loading)
Use dynamic for multiplayer synchronization (entity updates, player actions)