using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("FiresSteamworksPatcher")] [assembly: AssemblyDescription("BepInEx preloader patcher. (1) Adds the missing Steam SDK 1.51+ recv-buffer enum members to Valheim's bundled com.rlabrecque.steamworks.net.dll so existing SetConfigValue paths can target them. (2) Bumps the ZDOMan.SendZDOs queue cap from vanilla 10240 to 102400 (10x) so per-peer ZDO flushes have more headroom on busy dedicated servers.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("FiresSteamworksPatcher")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("ee0f80de-6bd2-4a52-8997-bd60dce71b00")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FiresSteamworksPatcher { public static class FiresSteamworksPatcher { private const int ZdoSendQueueCapBytes = 102400; private const int VanillaZdoSendQueueCapBytes = 10240; private const int QueueSizeCallSearchRadius = 15; private const string SteamworksConfigEnumFullName = "Steamworks.ESteamNetworkingConfigValue"; private const string ZdoManTypeName = "ZDOMan"; private const string SendZdosMethodName = "SendZDOs"; private const string GetSendQueueSizeMethodName = "GetSendQueueSize"; private const string GhettoNetworkingDllGlob = "*GhettoNetwork*.dll"; private static readonly ManualLogSource Log = Logger.CreateLogSource("FiresSteamworksPatcher"); private static readonly (string Name, int Value)[] RecvBufferEnumMembers = new(string, int)[4] { ("k_ESteamNetworkingConfig_RecvBufferSize", 47), ("k_ESteamNetworkingConfig_RecvBufferMessages", 48), ("k_ESteamNetworkingConfig_RecvMaxMessageSize", 49), ("k_ESteamNetworkingConfig_RecvMaxSegmentsPerPacket", 50) }; private static bool? _ghettoNetworkingPresent; private static bool? _isDedicatedServer; public static IEnumerable TargetDLLs { get { if (!IsDedicatedServer()) { Log.LogInfo((object)"Not running as a dedicated server — FiresSteamworksPatcher will not patch any assemblies. Both patches (Steam recv-buffer enum members and ZDOMan.SendZDOs queue cap) only affect server-side network behavior, so the patcher is no-op on client installs even when FGN is present."); return Array.Empty(); } if (!IsGhettoNetworkingInstalled()) { Log.LogInfo((object)"FiresGhettoNetworking not detected in BepInEx/plugins/ — FiresSteamworksPatcher will not patch any assemblies. This patcher exists to support FiresGhettoNetworking's send-buffer + queue-cap features and has no other effect; without FGN there's nothing to do."); return Array.Empty(); } return new string[2] { "com.rlabrecque.steamworks.net.dll", "assembly_valheim.dll" }; } } public static void Patch(AssemblyDefinition assembly) { AssemblyNameDefinition name = assembly.Name; string text = ((name != null) ? ((AssemblyNameReference)name).Name : null) ?? ""; if (!IsDedicatedServer()) { Log.LogInfo((object)(text + ": not a dedicated server, leaving assembly untouched.")); return; } if (!IsGhettoNetworkingInstalled()) { Log.LogInfo((object)(text + ": FGN not present, leaving assembly untouched.")); return; } try { ModuleDefinition mainModule = assembly.MainModule; TypeDefinition type = mainModule.GetType("Steamworks.ESteamNetworkingConfigValue"); if (type != null) { AddMissingRecvBufferEnumMembers(type, text); } TypeDefinition type2 = mainModule.GetType("ZDOMan"); if (type2 != null) { RaiseSendZdosQueueCap(type2, text); } } catch (Exception arg) { Log.LogError((object)$"{text}: patch failed, server will run with vanilla behavior: {arg}"); } } private static bool IsDedicatedServer() { if (_isDedicatedServer.HasValue) { return _isDedicatedServer.Value; } try { string processName = Process.GetCurrentProcess().ProcessName; _isDedicatedServer = !string.IsNullOrEmpty(processName) && processName.IndexOf("server", StringComparison.OrdinalIgnoreCase) >= 0; } catch (Exception ex) { Log.LogInfo((object)("Could not read process name (" + ex.GetType().Name + ": " + ex.Message + "); treating as 'not a server'.")); _isDedicatedServer = false; } return _isDedicatedServer.Value; } private static bool IsGhettoNetworkingInstalled() { if (_ghettoNetworkingPresent.HasValue) { return _ghettoNetworkingPresent.Value; } try { string pluginPath = Paths.PluginPath; if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath)) { _ghettoNetworkingPresent = false; return false; } string[] files = Directory.GetFiles(pluginPath, "*GhettoNetwork*.dll", SearchOption.AllDirectories); _ghettoNetworkingPresent = files.Length != 0; } catch (Exception ex) { Log.LogInfo((object)("Could not scan plugins directory (" + ex.GetType().Name + ": " + ex.Message + "); treating as 'FGN not present'.")); _ghettoNetworkingPresent = false; } return _ghettoNetworkingPresent.Value; } private static void AddMissingRecvBufferEnumMembers(TypeDefinition enumType, string assemblyName) { int num = 0; int num2 = 0; (string, int)[] recvBufferEnumMembers = RecvBufferEnumMembers; for (int i = 0; i < recvBufferEnumMembers.Length; i++) { (string, int) tuple = recvBufferEnumMembers[i]; if (EnumHasMember(enumType, tuple.Item1)) { num2++; continue; } enumType.Fields.Add(BuildEnumLiteral(enumType, tuple.Item1, tuple.Item2)); num++; } Log.LogInfo((object)$"{assemblyName}: Steamworks enum — added {num}, {num2} already present."); } private static FieldDefinition BuildEnumLiteral(TypeDefinition enumType, string name, int value) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return new FieldDefinition(name, (FieldAttributes)32854, (TypeReference)(object)enumType) { Constant = value }; } private static bool EnumHasMember(TypeDefinition enumType, string name) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) Enumerator enumerator = enumType.Fields.GetEnumerator(); try { while (enumerator.MoveNext()) { FieldDefinition current = enumerator.Current; if (((MemberReference)current).Name == name) { return true; } } } finally { ((IDisposable)enumerator).Dispose(); } return false; } private static void RaiseSendZdosQueueCap(TypeDefinition zdoManType, string assemblyName) { MethodDefinition val = FindMethodByName(zdoManType, "SendZDOs"); if (val == null) { Log.LogWarning((object)(assemblyName + ": ZDOMan.SendZDOs not found; queue cap patch skipped.")); return; } Collection instructions = val.Body.Instructions; int num = 0; int num2 = 0; for (int i = 0; i < instructions.Count; i++) { if (IsQueueCapConstantAt(instructions, i, out var value)) { if (value >= 102400) { num2++; continue; } instructions[i].Operand = 102400; num++; } } if (num == 0 && num2 == 0) { Log.LogWarning((object)(assemblyName + ": no queue cap constant matched in ZDOMan.SendZDOs; vanilla behavior in effect.")); return; } Log.LogInfo((object)$"{assemblyName}: ZDOMan.SendZDOs queue cap — raised {num} to {102400}, {num2} already at target."); } private static MethodDefinition FindMethodByName(TypeDefinition type, string name) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) Enumerator enumerator = type.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { MethodDefinition current = enumerator.Current; if (((MemberReference)current).Name == name) { return current; } } } finally { ((IDisposable)enumerator).Dispose(); } return null; } private static bool IsQueueCapConstantAt(Collection instructions, int index, out int value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) value = 0; Instruction val = instructions[index]; if (val.OpCode != OpCodes.Ldc_I4) { return false; } value = (int)val.Operand; if (value < 10240) { return false; } return IsNearGetSendQueueSizeCall(instructions, index); } private static bool IsNearGetSendQueueSizeCall(Collection instructions, int center) { int num = Math.Max(0, center - 15); int num2 = Math.Min(instructions.Count, center + 15 + 1); for (int i = num; i < num2; i++) { if (IsCallTo(instructions[i], "GetSendQueueSize")) { return true; } } return false; } private static bool IsCallTo(Instruction instruction, string methodName) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (instruction.OpCode != OpCodes.Call && instruction.OpCode != OpCodes.Callvirt) { return false; } object operand = instruction.Operand; MethodReference val = (MethodReference)((operand is MethodReference) ? operand : null); return val != null && ((MemberReference)val).Name == methodName; } } }