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.
Decompiled source of Serverbound v0.1.2
BepInEx/plugins/Serverbound/Serverbound.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
#define TRACE using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mono.Cecil.Cil; using MonoMod.Cil; using Serverbound.Compatibility; using Serverbound.FeatureModel; using Serverbound.Features; using Serverbound.Patching; using Serverbound.Requirements; using Serverbound.Settings; using SoftReferenceableAssets; using Steamworks; using Unity.Jobs.LowLevel.Unsafe; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(false)] [assembly: InternalsVisibleTo("Serverbound.Tests")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: IgnoresAccessChecksTo("SoftReferenceableAssets")] [assembly: AssemblyCompany("Serverbound")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Dedicated world simulation and optional client compatibility for Valheim.")] [assembly: AssemblyFileVersion("0.1.2.0")] [assembly: AssemblyInformationalVersion("0.1.2")] [assembly: AssemblyProduct("Serverbound")] [assembly: AssemblyTitle("Serverbound")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [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 Serverbound { public static class AdminCommands { private static Dictionary<string, GameObject> s_items; public static ZNetPeer FindPlayer(string name, out string problem) { problem = null; List<ZNetPeer> list = (from p in ZNet.instance.GetPeers() where p.IsReady() && !string.IsNullOrEmpty(p.m_playerName) select p).ToList(); ZNetPeer val = ((IEnumerable<ZNetPeer>)list).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => string.Equals(p.m_playerName, name, StringComparison.OrdinalIgnoreCase))); if (val != null) { return val; } List<ZNetPeer> list2 = list.Where((ZNetPeer p) => p.m_playerName.StartsWith(name, StringComparison.OrdinalIgnoreCase)).ToList(); List<ZNetPeer> list3 = ((list2.Count > 0) ? list2 : list.Where((ZNetPeer p) => p.m_playerName.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0).ToList()); if (list3.Count == 1) { return list3[0]; } problem = ((list3.Count == 0) ? ("No player '" + name + "' online. Online: " + Names(list)) : ("'" + name + "' matches several players: " + Names(list3))); return null; } public static string Players() { List<ZNetPeer> list = (from p in ZNet.instance.GetPeers() where p.IsReady() select p).ToList(); if (list.Count == 0) { return "No players online"; } return string.Join("; ", list.Select(delegate(ZNetPeer p) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Vector3 refPos = p.GetRefPos(); return $"{p.m_playerName} ({p.m_socket.GetHostName()}) at {refPos.x:0},{refPos.z:0}"; })); } public static string Give(ZNetPeer target, string itemName, int amount, string byWhom) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) GameObject val = FindItem(itemName); if (!Object.op_Implicit((Object)(object)val)) { return "No item '" + itemName + "'." + Suggest(itemName); } amount = Mathf.Clamp(amount, 1, Mathf.Max(1, Configuration.maxGiveAmount.Value)); ZDO zDO = ZDOMan.instance.GetZDO(target.m_characterID); Vector3 val2 = ((zDO != null) ? zDO.GetPosition() : target.GetRefPos()) + Vector3.up * 1.5f; int num = Mathf.Max(1, val.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxStackSize); int num2 = 0; for (int num3 = amount; num3 > 0; num3 -= num) { Vector2 val3 = Random.insideUnitCircle * 0.75f; Vector3 val4 = val2 + new Vector3(val3.x, 0.25f * (float)num2, val3.y); Object.Instantiate<GameObject>(val, val4, Quaternion.identity).GetComponent<ItemDrop>().SetStack(Mathf.Min(num3, num)); num2++; } string text = $"{byWhom} gave {amount} x {((Object)val).name} to {target.m_playerName} in {num2} stack(s) at {val2.x:0},{val2.z:0}"; Plugin.logger.LogInfo((object)("Admin: " + text)); return text; } public static string Save(string byWhom) { if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer()) { return "No world loaded, nothing to save"; } bool flag = default(bool); if (!ZNet.instance.EnoughDiskSpaceAvailable(ref flag, false, (Action<bool>)null)) { return "Not enough disk space, world not saved"; } Plugin.logger.LogInfo((object)("Admin: " + byWhom + " requested a world save")); ZNet.instance.Save(false, true, true); return "Saving world"; } public static string Broadcast(string text, string byWhom) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text }); Plugin.logger.LogInfo((object)("Admin: " + byWhom + " broadcast: " + text)); return $"shown to {ZNet.instance.GetNrOfPlayers()} player(s): {text}"; } public static string StartEvent(string name, ZNetPeer target, string byWhom) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)RandEventSystem.instance) || !RandEventSystem.instance.HaveEvent(name)) { return "No event '" + name + "'. Events: " + Events(); } ZDO zDO = ZDOMan.instance.GetZDO(target.m_characterID); Vector3 val = ((zDO != null) ? zDO.GetPosition() : target.GetRefPos()); RandEventSystem.instance.SetRandomEventByName(name, val); string text = $"{byWhom} started event {name} at {target.m_playerName} ({val.x:0},{val.z:0})"; Plugin.logger.LogInfo((object)("Admin: " + text)); return text; } public static string Events() { if (!Object.op_Implicit((Object)(object)RandEventSystem.instance)) { return "none (no world loaded)"; } return string.Join(", ", RandEventSystem.instance.m_events.Select((RandomEvent e) => e.m_name)); } public static bool TryParseAmount(string text, out int amount) { return int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out amount); } private static string Names(List<ZNetPeer> peers) { if (peers.Count != 0) { return string.Join(", ", peers.Select((ZNetPeer p) => p.m_playerName)); } return "nobody"; } private static GameObject FindItem(string name) { if (s_items == null) { s_items = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase); foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if (Object.op_Implicit((Object)(object)prefab) && Object.op_Implicit((Object)(object)prefab.GetComponent<ItemDrop>()) && !s_items.ContainsKey(((Object)prefab).name)) { s_items[((Object)prefab).name] = prefab; } } } if (!s_items.TryGetValue(name, out var value)) { return null; } return value; } private static string Suggest(string name) { List<string> list = new List<string>(); foreach (string key in s_items.Keys) { if (key.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0) { list.Add(key); if (list.Count == 8) { break; } } } if (list.Count <= 0) { return ""; } return " Similar: " + string.Join(", ", list); } } public static class ConsoleInput { public delegate void LineHandler(string line, string notice); private static readonly UTF8Encoding s_utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private const string Cp1250 = "€\ufffd‚\ufffd„…†‡\ufffd‰Š‹ŚŤŽŹ\ufffd‘’“”•–—\ufffd™š›śťžź\u00a0ˇ\u02d8٤Ą¦§\u00a8©Ş«¬\u00ad®Ż°±\u02dbł\u00b4µ¶·\u00b8ąş»Ľ\u02ddľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ\u02d9"; private const string Cp852 = "ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ\u00b4\u00ad\u02dd\u02dbˇ\u02d8§÷\u00b8°\u00a8\u02d9űŘř■\u00a0"; public static void ReadLines(Func<int> codePage, LineHandler handler) { bool noticed = false; using Stream stream = Console.OpenStandardInput(); byte[] array = new byte[4096]; List<byte> list = new List<byte>(); int num; while ((num = stream.Read(array, 0, array.Length)) > 0) { for (int i = 0; i < num; i++) { if (array[i] == 10) { Emit(list, codePage(), handler, ref noticed); list.Clear(); } else { list.Add(array[i]); } } } Emit(list, codePage(), handler, ref noticed); } private static void Emit(List<byte> line, int codePage, LineHandler handler, ref bool noticed) { if (line.Count > 0 && line[line.Count - 1] == 13) { line.RemoveAt(line.Count - 1); } if (line.Count != 0) { byte[] array = line.ToArray(); string notice = null; bool fellBack; string line2 = Decode(array, codePage, out fellBack); if (fellBack && !noticed) { noticed = true; notice = string.Format("Console input is not UTF-8 (bytes: {0}); decoded with code page {1}.", BitConverter.ToString(array).Replace("-", " "), codePage) + " If accents come out wrong, set [Server] ConsoleInputCodePage to what the panel sends (1250, 852, 1252 or 65001)."; } handler(line2, notice); } } public static string Decode(byte[] data, int codePage, out bool fellBack) { fellBack = false; bool flag = true; for (int i = 0; i < data.Length; i++) { if (data[i] >= 128) { flag = false; break; } } if (flag) { return Encoding.ASCII.GetString(data); } if (codePage != 65001) { try { return s_utf8.GetString(data); } catch (DecoderFallbackException) { fellBack = true; } } return SingleByte(data, codePage); } private static string SingleByte(byte[] data, int codePage) { string text = codePage switch { 1250 => "€\ufffd‚\ufffd„…†‡\ufffd‰Š‹ŚŤŽŹ\ufffd‘’“”•–—\ufffd™š›śťžź\u00a0ˇ\u02d8٤Ą¦§\u00a8©Ş«¬\u00ad®Ż°±\u02dbł\u00b4µ¶·\u00b8ąş»Ľ\u02ddľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ\u02d9", 852 => "ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ\u00b4\u00ad\u02dd\u02dbˇ\u02d8§÷\u00b8°\u00a8\u02d9űŘř■\u00a0", _ => null, }; char[] array = new char[data.Length]; for (int i = 0; i < data.Length; i++) { byte b = data[i]; array[i] = (char)((b < 128) ? b : (((int?)text?[b - 128]) ?? ((int)b))); } return new string(array); } } [Harmony] [BepInPlugin("org.serverbound.valheim", "Serverbound", "0.1.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("MVP.Valheim_Serverside_Simulations")] public class Plugin : BaseUnityPlugin { public const string PluginGUID = "org.serverbound.valheim"; public const string PluginName = "Serverbound"; public const string PluginVersion = "0.1.2"; private static Plugin context; public static Configuration configuration; public static Harmony harmony; public const string ValheimPlusPluginId = "org.bepinex.plugins.valheim_plus"; public static ManualLogSource logger; private static bool consoleStarted; private static bool installed; public static Plugin instance => context; private void Awake() { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Expected O, but got Unknown context = this; logger = ((BaseUnityPlugin)this).Logger; Configuration.Load(((BaseUnityPlugin)this).Config); CompatibilityInstaller.Info = delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }; CompatibilityInstaller.Warning = delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }; CompatibilityInstaller.Error = delegate(string message) { ((BaseUnityPlugin)this).Logger.LogError((object)message); }; try { CompatibilityInstaller.CheckInstallation(Resolve, VersionOf, IsDedicated()); Guard.Build(typeof(ZNet).Assembly, IsDedicated() ? ExpectedBuilds.Server : ExpectedBuilds.Valheim); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Serverbound: REJECTED BUILD/INSTALLATION; no patches installed. " + ex)); return; } if (!ModIsEnabled()) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Serverbound is disabled. (configuration)"); return; } if (!IsDedicated()) { if (((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "ClientSkills", true, "Publish ImpactfulSkills state only after a Serverbound server handshake. Dedicated simulation never runs on clients.").Value) { CompatibilityInstaller.Install(Resolve, CompatibilityInstaller.Info, CompatibilityInstaller.Warning, CompatibilityInstaller.Error); } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Serverbound client compatibility loaded; dedicated simulation is inactive on this client."); return; } ((BaseUnityPlugin)this).Logger.LogInfo((object)"Installing Serverbound"); LimitJobWorkers(Configuration.unityJobWorkers.Value); LimitPhysicsCatchUp(Configuration.maxCatchUpMs.Value); if (Configuration.consoleCommandsEnabled.Value) { ServerConsole.Start(); consoleStarted = true; } harmony = new Harmony("org.serverbound.valheim"); AvailableFeatures availableFeatures = new AvailableFeatures(); availableFeatures.AddFeature(new Core()); availableFeatures.AddFeature(new MaxObjectsPerFrame()); availableFeatures.AddFeature(new Networking()); availableFeatures.AddFeature(new Performance()); availableFeatures.AddFeature(new Fixes()); availableFeatures.AddFeature(new Debugging()); availableFeatures.AddFeature(new Compat_ValheimCommunityPatch()); availableFeatures.AddFeature(new DungeonLoadGuard()); PatchRequirements patchRequirements = new PatchRequirements(); patchRequirements.AddRequirement(new PatchRequirement.DebugBuild()); if (PatchFeatures(availableFeatures, new HarmonyFeaturesPatcher(patchRequirements))) { VanillaDrift.Check(((BaseUnityPlugin)this).Logger); installed = true; CompatibilityInstaller.Install(Resolve, CompatibilityInstaller.Info, CompatibilityInstaller.Warning, CompatibilityInstaller.Error); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Serverbound installed; deferred compatibility verification pending."); } } private static Assembly Resolve(string guid) { if (!Chainloader.PluginInfos.TryGetValue(guid, out var value)) { return null; } return ((object)value.Instance)?.GetType().Assembly; } private static Version VersionOf(string guid) { if (!Chainloader.PluginInfos.TryGetValue(guid, out var value)) { return null; } return value.Metadata.Version; } private void Start() { try { CompatibilityInstaller.CheckInstallation(Resolve, VersionOf, IsDedicated()); CompatibilityInstaller.Verify(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Serverbound: deferred compatibility gate failed. " + ex)); DisableSimulation(); } } internal static void DisableSimulation() { //IL_0086: Unknown result type (might be due to invalid IL or missing references) installed = false; SimulationPatch.Deactivate(); try { string[] array = (from p in Harmony.GetAllPatchedMethods().SelectMany(SimulationPatch.All) select p.owner).Where(IsSimulationOwner).Distinct().ToArray(); for (int num = 0; num < array.Length; num++) { new Harmony(array[num]).UnpatchSelf(); } if (Harmony.GetAllPatchedMethods().SelectMany(SimulationPatch.All).Any((Patch p) => IsSimulationOwner(p.owner))) { throw new InvalidOperationException("Serverbound hooks remain installed."); } SimulationPatch.RestoreVendors(); CompatibilityInstaller.Error("Simulation: SERVERBOUND DISABLED; resolve the rejected integration before admitting players."); } catch (Exception ex) { CompatibilityInstaller.Error("Simulation: rollback could not disable Serverbound; stop the server before players join. " + ex); } } private static bool IsSimulationOwner(string owner) { if (owner == "org.serverbound.valheim" || owner.StartsWith("org.serverbound.valheim.", StringComparison.Ordinal)) { return owner != "org.serverbound.valheim.ServerConsole"; } return false; } private void Update() { if (consoleStarted) { ServerConsole.ProcessPending(); } if (installed) { PerformanceStats.Frame(); } } private void FixedUpdate() { if (installed) { PerformanceStats.FixedStep(); } } private void LimitJobWorkers(int limit) { int jobWorkerCount = JobsUtility.JobWorkerCount; if (limit > 0 && limit < jobWorkerCount) { JobsUtility.JobWorkerCount = limit; ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Unity job worker threads: {jobWorkerCount} -> {JobsUtility.JobWorkerCount}"); } } private void LimitPhysicsCatchUp(int milliseconds) { if (milliseconds > 0) { float maximumDeltaTime = Time.maximumDeltaTime; Time.maximumDeltaTime = Mathf.Max((float)milliseconds / 1000f, Time.fixedDeltaTime); ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Physics catch-up: at most {Mathf.RoundToInt(Time.maximumDeltaTime / Time.fixedDeltaTime)} fixed steps per frame " + $"(fixed step {1000f * Time.fixedDeltaTime:0} ms, longest frame counted {1000f * maximumDeltaTime:0} -> {1000f * Time.maximumDeltaTime:0} ms)")); } } private bool PatchFeatures(AvailableFeatures availableFeatures, HarmonyFeaturesPatcher patcher) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown List<Harmony> list = new List<Harmony>(); foreach (IFeature item in availableFeatures.EnabledFeatures()) { string name = item.GetType().Name; Harmony val = new Harmony("org.serverbound.valheim." + name); try { patcher.PatchAll(item.GetType().GetNestedTypes(), val); list.Add(val); } catch (Exception arg) { val.UnpatchSelf(); if (item is Core) { ((BaseUnityPlugin)this).Logger.LogError((object)string.Format("Core patches failed to apply; {0} is disabled and the server runs vanilla. {1}", "Serverbound", arg)); foreach (Harmony item2 in list) { item2.UnpatchSelf(); } harmony.UnpatchSelf(); return false; } ((BaseUnityPlugin)this).Logger.LogError((object)$"Feature {name} failed to apply and is disabled. {arg}"); } } return true; } public bool ModIsEnabled() { return Configuration.modEnabled.Value; } public static bool IsDedicated() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return new ZNet().IsDedicated(); } } public static class ServerConsole { private const string Commands = "save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ..."; private static readonly ConcurrentQueue<string> s_commands = new ConcurrentQueue<string>(); private static bool s_capturing; private static int s_captured; private static Harmony s_capture; public static void Start() { Thread thread = new Thread(ReadLoop); thread.IsBackground = true; thread.Name = "Serverbound console"; thread.Start(); Plugin.logger.LogInfo((object)"Console commands enabled on standard input: save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ..."); } private static void ReadLoop() { try { ConsoleInput.ReadLines(() => Configuration.consoleInputCodePage.Value, delegate(string line, string notice) { if (notice != null) { s_commands.Enqueue("\u0001" + notice); } line = line.Trim(); if (line.Length > 0) { s_commands.Enqueue(line); } }); } catch (Exception ex) { s_commands.Enqueue("\0" + ex.Message); } } private static void Reply(string text) { Plugin.logger.LogInfo((object)("Console: " + text)); WriteOut(text); } private static void WriteOut(string text) { try { Console.Out.WriteLine("Console: " + text); Console.Out.Flush(); } catch (Exception) { } } public static void ProcessPending() { string result; while (s_commands.TryDequeue(out result)) { Execute(result); } } private static void Execute(string command) { if (command[0] == '\0') { Plugin.logger.LogWarning((object)("Console commands unavailable, cannot read standard input: " + command.Substring(1))); return; } if (command[0] == '\u0001') { Plugin.logger.LogWarning((object)command.Substring(1)); WriteOut(command.Substring(1)); return; } string[] array = command.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); bool flag = Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer(); try { switch (array[0].ToLowerInvariant()) { case "save": Reply(AdminCommands.Save("console")); break; case "stop": case "quit": case "shutdown": Reply("saving world and shutting down"); Application.Quit(); break; case "players": Reply(flag ? AdminCommands.Players() : "no world loaded"); break; case "give": { if (!flag) { Reply("no world loaded"); break; } if (array.Length < 4 || !AdminCommands.TryParseAmount(array[2], out var amount)) { Reply("usage: give <item> <amount> <player>, e.g. give Copper 120 Ulf"); break; } string problem; ZNetPeer val = AdminCommands.FindPlayer(string.Join(" ", array, 3, array.Length - 3), out problem); Reply((val == null) ? problem : AdminCommands.Give(val, array[1], amount, "console")); break; } case "broadcast": case "say": if (!flag) { Reply("no world loaded"); } else if (array.Length < 2) { Reply("usage: broadcast <text>"); } else { Reply(AdminCommands.Broadcast(command.Substring(array[0].Length).Trim(), "console")); } break; case "event": { if (!flag) { Reply("no world loaded"); break; } if (array.Length < 3) { Reply("usage: event <name> <player>, e.g. event army_eikthyr Ulf; `events` lists the names"); break; } string problem2; ZNetPeer val2 = AdminCommands.FindPlayer(string.Join(" ", array, 2, array.Length - 2), out problem2); Reply((val2 == null) ? problem2 : AdminCommands.StartEvent(array[1], val2, "console")); break; } case "events": Reply(flag ? ("events: " + AdminCommands.Events()) : "no world loaded"); break; case "help": Reply("commands: save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ..."); break; default: RunGameCommand(command, array[0]); break; } } catch (Exception ex) { Plugin.logger.LogWarning((object)$"Console: '{command}' failed: {ex}"); Reply("'" + command + "' failed: " + ex.Message); } } private static void RunGameCommand(string command, string word) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)Console.instance)) { Reply("'" + word + "' is not one of ours and the game console is not up yet. Commands: save | stop | players | give <item> <amount> <player> | broadcast <text> | event <name> <player> | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ..."); return; } if (s_capture == null) { s_capture = new Harmony("org.serverbound.valheim.ServerConsole"); s_capture.Patch((MethodBase)AccessTools.Method(typeof(Terminal), "AddString", new Type[1] { typeof(string) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ServerConsole), "AddStringPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } s_capturing = true; s_captured = 0; try { ((Terminal)Console.instance).TryRunCommand(command, false, true); } finally { s_capturing = false; } if (s_captured == 0) { Reply("game console ran '" + word + "' without output"); } } public static void AddStringPostfix(Terminal __instance, string text) { if (s_capturing && (Object)(object)__instance == (Object)(object)Console.instance) { s_captured++; WriteOut(text); } } } public static class ServerSafe { private static readonly Dictionary<string, MethodInfo> Replacements = new Dictionary<string, MethodInfo> { { "GetZDOID", AccessTools.Method(typeof(ServerSafe), "LocalPlayerZDOID", (Type[])null, (Type[])null) }, { "GetPlayerID", AccessTools.Method(typeof(ServerSafe), "LocalPlayerID", (Type[])null, (Type[])null) }, { "GetPlayerName", AccessTools.Method(typeof(ServerSafe), "LocalPlayerName", (Type[])null, (Type[])null) } }; public static ZDOID LocalPlayerZDOID() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return ZDOID.None; } return ((Character)Player.m_localPlayer).GetZDOID(); } public static long LocalPlayerID() { if (!Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return 0L; } return Player.m_localPlayer.GetPlayerID(); } public static string LocalPlayerName() { if (!Object.op_Implicit((Object)(object)Player.m_localPlayer)) { return ""; } return Player.m_localPlayer.GetPlayerName(); } public static IEnumerable<CodeInstruction> NullSafeLocalPlayer(IEnumerable<CodeInstruction> instructions, MethodBase original, int expected) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "m_localPlayer"); List<CodeInstruction> list = instructions.ToList(); int num = 0; for (int i = 0; i < list.Count - 1; i++) { if (CodeInstructionExtensions.LoadsField(list[i], fieldInfo, false) && !(list[i + 1].opcode != OpCodes.Callvirt) && list[i + 1].operand is MethodInfo methodInfo && methodInfo.DeclaringType.IsAssignableFrom(typeof(Player)) && Replacements.TryGetValue(methodInfo.Name, out var value)) { CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)value); val.labels.AddRange(list[i].labels); val.labels.AddRange(list[i + 1].labels); val.blocks.AddRange(list[i].blocks); val.blocks.AddRange(list[i + 1].blocks); list[i] = val; list.RemoveAt(i + 1); num++; } } if (num != expected) { Plugin.logger.LogWarning((object)$"{original.DeclaringType.Name}.{original.Name}: replaced {num} Player.m_localPlayer call(s), expected {expected}. The game changed this method; the patch needs reviewing."); } return list; } } public class Utilities { public static bool IsDebugBuild() { return false; } } public static class VanillaDrift { public const string ReviewedForGameVersion = "1.0.15"; private static readonly Dictionary<MethodBase, string[]> Expected = new Dictionary<MethodBase, string[]> { { AccessTools.Method(typeof(ZoneSystem), "Update", (Type[])null, (Type[])null), new string[15] { "CinematicsManager::IsPlaying", "TextViewer::IsShowingIntro", "Utils::GetTimeBudgetForTargetFrameRate", "ZNet::GetConnectionStatus", "ZNet::GetPeers", "ZNet::GetReferencePosition", "ZNet::IsServer", "ZNet::get_instance", "ZNetPeer::GetRefPos", "ZoneSystem::CreateGhostZones", "ZoneSystem::CreateLocalZones", "ZoneSystem::TimeSinceStart", "ZoneSystem::UpdatePrefabLifetimes", "ZoneSystem::UpdateTTL", "ZoneSystem::get_LocationsGenerated" } }, { AccessTools.Method(typeof(ZoneSystem), "IsActiveAreaLoaded", (Type[])null, (Type[])null), new string[7] { "SimulationDistance::get_IsClassic", "SimulationDistance::get_NearSimulationDistance", "Vector2s::.ctor", "ZNet::GetReferencePosition", "ZNet::get_instance", "ZoneSystem::GetZone", "ZoneSystem::ZonesWithinRadius" } }, { AccessTools.Method(typeof(ZNetScene), "CreateDestroyObjects", (Type[])null, (Type[])null), new string[8] { "ZDOMan::FindSectorObjects", "ZDOMan::get_instance", "ZNet::GetReferencePosition", "ZNet::GetSyncedSimulationDistance", "ZNet::get_instance", "ZNetScene::CreateObjects", "ZNetScene::RemoveObjects", "ZoneSystem::GetZone" } }, { AccessTools.Method(typeof(ZDOMan), "ReleaseNearbyZDOS", (Type[])null, (Type[])null), new string[14] { "SimulationDistance::.ctor", "SimulationDistance::get_IsClassic", "SimulationDistance::get_NearSimulationDistance", "ZDO::GetOwner", "ZDO::GetPosition", "ZDO::HasOwner", "ZDO::SetOwner", "ZDO::get_Persistent", "ZDOMan::FindSectorObjects", "ZDOMan::IsInPeerActiveArea", "ZNet::GetSyncedSimulationDistance", "ZNet::get_instance", "ZNetScene::InActiveArea", "ZoneSystem::GetZone" } }, { AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", (Type[])null, (Type[])null), new string[1] { "ZDOMan::SendZDOs" } }, { AccessTools.Method(typeof(ZNetScene), "OutsideActiveArea", new Type[1] { typeof(Vector3) }, (Type[])null), new string[4] { "ZNet::GetReferencePosition", "ZNet::get_instance", "ZNetScene::OutsideActiveArea", "ZoneSystem::GetZone" } }, { AccessTools.Method(typeof(Ship), "UpdateOwner", (Type[])null, (Type[])null), new string[8] { "Ship::GetNewOwnerID", "Ship::IsPlayerInBoat", "Ship::RefreshPlayerList", "ZDO::SetOwner", "ZLog::Log", "ZNetView::GetZDO", "ZNetView::IsOwner", "ZNetView::IsValid" } } }; public static void Check(ManualLogSource logger) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) string text = ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString(); int num = 0; foreach (KeyValuePair<MethodBase, string[]> item in Expected) { if (item.Key == null) { logger.LogError((object)"Vanilla drift check: a replaced method no longer exists in this game version."); num++; continue; } HashSet<string> hashSet = GameCalls(item.Key); List<string> list = (from s in hashSet.Except(item.Value) orderby s select s).ToList(); List<string> list2 = (from s in item.Value.Except(hashSet) orderby s select s).ToList(); if (list.Count != 0 || list2.Count != 0) { num++; logger.LogWarning((object)("Vanilla " + item.Key.DeclaringType.Name + "." + item.Key.Name + " changed since this mod's replacement was reviewed (game 1.0.15, running " + text + "). Now calls: [" + string.Join(", ", list) + "]. No longer calls: [" + string.Join(", ", list2) + "]. The replacement may be dropping new vanilla behaviour.")); } } if (num == 0) { logger.LogInfo((object)string.Format("Vanilla drift check passed ({0} replaced methods, reviewed for {1}, running {2}).", Expected.Count, "1.0.15", text)); } } private static HashSet<string> GameCalls(MethodBase method) { HashSet<string> hashSet = new HashSet<string>(); foreach (CodeInstruction originalInstruction in PatchProcessor.GetOriginalInstructions(method, (ILGenerator)null)) { if ((originalInstruction.opcode == OpCodes.Call || originalInstruction.opcode == OpCodes.Callvirt || originalInstruction.opcode == OpCodes.Newobj) && originalInstruction.operand is MethodBase methodBase && methodBase.DeclaringType != null) { string name = methodBase.DeclaringType.Assembly.GetName().Name; if (name == "assembly_valheim" || name == "assembly_utils") { hashSet.Add(methodBase.DeclaringType.Name + "::" + methodBase.Name); } } } return hashSet; } } } namespace Serverbound.Requirements { public class PatchRequirement { public class DebugBuild : IPatchRequirement { public const string name = "DebugBuild"; string IPatchRequirement.Name => "DebugBuild"; Func<bool> IPatchRequirement.Checker => Utilities.IsDebugBuild; } } } namespace Serverbound.Patching { public interface IPatchRequirement { string Name { get; } Func<bool> Checker { get; } } public class PatchRequirements { private readonly Dictionary<string, Func<bool>> _requirements; public PatchRequirements() { _requirements = new Dictionary<string, Func<bool>>(); } public PatchRequirements(Dictionary<string, Func<bool>> requirements) { _requirements = requirements; } public PatchRequirements AddRequirement(IPatchRequirement patchRequirement) { _requirements.Add(patchRequirement.Name, patchRequirement.Checker); return this; } public bool IsAllowed(string requirement_name) { _requirements.TryGetValue(requirement_name, out var value); return value?.Invoke() ?? false; } } [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] public class PatchRequiresAttribute : Attribute { public readonly string requirement_name; public PatchRequiresAttribute(string requirement_name) { this.requirement_name = requirement_name; } } public class HarmonyFeaturesPatcher { private readonly PatchRequirements _patchRequirements; public HarmonyFeaturesPatcher(PatchRequirements availableFeatures) { _patchRequirements = availableFeatures; } public void PatchAll(Type[] types, Harmony harmony_instance) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) foreach (Type type in types) { if (!type.GetCustomAttributes<PatchRequiresAttribute>().ToList().Any((PatchRequiresAttribute attribute) => !_patchRequirements.IsAllowed(attribute.requirement_name))) { ZLog.Log((object)("Patching: " + type.ToString())); new PatchClassProcessor(harmony_instance, type).Patch(); } else { ZLog.Log((object)("Patch disabled: " + type.ToString())); } } } } } namespace Serverbound.Features { public class Compat_ValheimCommunityPatch : IFeature { [HarmonyPatch(typeof(ZNetScene), "Awake")] public static class ZNetScene_Awake_Patch { internal static bool s_done; private static void Postfix() { if (s_done || !SimulationPatch.Active || SimulationPatch.VcpAssembly == null) { return; } s_done = true; removed.Clear(); try { TakeOver("CreateObjectsSorted", Configuration.compatVcpSpawnQueue, "spawn queue", "new objects are created in order of distance from the world origin, not from the nearest player."); TakeOver("RemoveObjects", Configuration.compatVcpUnload, "zone-diff unload", "objects around players far from the world origin can be destroyed and created again on every pass."); foreach (var item in removed) { SimulationPatch.RememberRemoved(item); } } catch (Exception ex) { foreach (var item2 in removed) { try { SimulationPatch.Restore(item2); } catch (Exception ex2) { Plugin.logger.LogError((object)("VCP takeover rollback failed; stop the server. " + ex2)); } } Plugin.logger.LogError((object)("VCP takeover rejected. " + ex)); Plugin.DisableSimulation(); } finally { removed.Clear(); } } } public const string PluginId = "MidnightsFX.ValheimCommunityPatch"; public static bool UnloadKept; private static readonly List<(MethodBase Target, Patch Hook, HarmonyPatchType Kind)> removed = new List<(MethodBase, Patch, HarmonyPatchType)>(); public bool FeatureEnabled() { return true; } private static void TakeOver(string method, ConfigEntry<bool> keep, string what, string cost) { MethodBase methodBase = AccessTools.DeclaredMethod(typeof(ZNetScene), method, (Type[])null, (Type[])null); if (keep.Value) { if (method == "RemoveObjects") { UnloadKept = true; Plugin.logger.LogInfo((object)("Keeping ValheimCommunityPatch's " + what + " on ZNetScene." + method + " ([Compat] " + ((ConfigEntryBase)keep).Definition.Key + " = true); the object lists are marked as edited on every pass so it uses the game's own unload check.")); } else { Plugin.logger.LogWarning((object)("Keeping ValheimCommunityPatch's " + what + " on ZNetScene." + method + " ([Compat] " + ((ConfigEntryBase)keep).Definition.Key + " = true): " + cost)); } return; } string text = ((method == "RemoveObjects") ? "ZoneDiffRemovalPatch" : "SpawnQueueCachePatch"); string text2 = ((method == "RemoveObjects") ? "RemoveObjectsPrefix" : "CreateObjectsSortedPrefix"); Assembly vcpAssembly = SimulationPatch.VcpAssembly; Guard.Build(vcpAssembly, "E48804F2280878B1BB30393C57C354EFDAA68F106A0B2C77F150C113A25EAA68"); MethodInfo hook = AccessTools.DeclaredMethod(Guard.Type(vcpAssembly, "ValheimCommunityPatch.Patches.Performance." + text), text2, (Type[])null, (Type[])null); (MethodBase, Patch, HarmonyPatchType) item = SimulationPatch.ExactHook(typeof(ZNetScene), method, hook, "MidnightsFX.ValheimCommunityPatch", (HarmonyPatchType)1); removed.Add(item); Plugin.harmony.Unpatch(methodBase, hook); if (SimulationPatch.All(methodBase).Any((Patch patch) => patch.PatchMethod == hook)) { throw new InvalidOperationException("Could not remove ValheimCommunityPatch's " + what + " from ZNetScene." + method + ": " + cost); } Plugin.logger.LogInfo((object)("Removed ValheimCommunityPatch's " + what + " from ZNetScene." + method + ": it works around the server's reference position, the world origin on a dedicated server, not around the players. Set [Compat] " + ((ConfigEntryBase)keep).Definition.Key + " = true to keep it.")); } } public class Core : IFeature { [HarmonyPatch(typeof(Pickable), "RPC_Pick")] public static class Pickable_RPC_Pick_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 1); } } [HarmonyPatch(typeof(Ship), "UpdateSailSize")] public static class Ship_UpdateSailSize_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 2); } } [HarmonyPatch(typeof(CookingStation), "SpawnItem")] public static class CookingStation_SpawnItem_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 2); } } [HarmonyPatch(typeof(Leviathan), "RPC_Left")] public static class Leviathan_RPC_Left_Patch { private static bool Prefix() { return (Object)(object)Player.m_localPlayer != (Object)null; } } [HarmonyPatch(typeof(ZNetScene), "CreateDestroyObjects")] public class CreateDestroyObjects_Patch { private static readonly HashSet<ZDO> s_seen = new HashSet<ZDO>(); private static bool Prefix(ZNetScene __instance) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) List<ZDO> tempCurrentObjects = __instance.m_tempCurrentObjects; List<ZDO> tempCurrentDistantObjects = __instance.m_tempCurrentDistantObjects; tempCurrentObjects.Clear(); tempCurrentDistantObjects.Clear(); SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady()) { Vector2s zone = ZoneSystem.GetZone(peer.GetRefPos()); ZDOMan.instance.FindSectorObjects(zone, syncedSimulationDistance, tempCurrentObjects, tempCurrentDistantObjects); } } RemoveDuplicates(tempCurrentObjects); RemoveDuplicates(tempCurrentDistantObjects); if (Compat_ValheimCommunityPatch.UnloadKept) { tempCurrentObjects.Reverse(0, 0); tempCurrentDistantObjects.Reverse(0, 0); } __instance.CreateObjects(tempCurrentObjects, tempCurrentDistantObjects); __instance.RemoveObjects(tempCurrentObjects, tempCurrentDistantObjects); return false; } private static void RemoveDuplicates(List<ZDO> zdos) { s_seen.Clear(); zdos.RemoveAll((ZDO zdo) => !s_seen.Add(zdo)); } } [HarmonyPatch(typeof(ZNetScene), "CreateObjectsSorted")] public static class ZNetScene_CreateObjectsSorted_Patch { private static readonly List<Vector3> s_peerPositions = new List<Vector3>(); private static int s_peerPositionsFrame = -1; private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { MethodInfo methodInfo = AccessTools.Method(typeof(Utils), "DistanceSqr", new Type[2] { typeof(Vector3), typeof(Vector3) }, (Type[])null); MethodInfo operand = AccessTools.Method(typeof(ZNetScene_CreateObjectsSorted_Patch), "NearestPeerDistanceSqr", (Type[])null, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = 0; foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, methodInfo)) { item.operand = operand; num++; } } if (num != 1) { Plugin.logger.LogWarning((object)$"{__originalMethod.DeclaringType.Name}.{__originalMethod.Name}: replaced {num} sort distance call(s), expected 1. The game changed this method; the patch needs reviewing."); } return list; } public static float NearestPeerDistanceSqr(Vector3 referencePosition, Vector3 position) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) if (s_peerPositionsFrame != Time.frameCount) { s_peerPositionsFrame = Time.frameCount; s_peerPositions.Clear(); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady()) { s_peerPositions.Add(peer.GetRefPos()); } } } if (s_peerPositions.Count == 0) { return Utils.DistanceSqr(referencePosition, position); } float num = float.MaxValue; foreach (Vector3 s_peerPosition in s_peerPositions) { num = Mathf.Min(num, Utils.DistanceSqr(s_peerPosition, position)); } return num; } } [HarmonyPatch(typeof(ZoneSystem), "IsActiveAreaLoaded")] public static class ZoneSystem_IsActiveAreaLoaded_Patch { private static bool Prefix(ZoneSystem __instance, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) SimulationDistance simulationDistance = __instance.m_simulationDistance; int nearSimulationDistance = ((SimulationDistance)(ref simulationDistance)).NearSimulationDistance; Vector2s val = default(Vector2s); foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (!peer.IsReady()) { continue; } Vector2s zone = ZoneSystem.GetZone(peer.GetRefPos()); for (int i = zone.y - nearSimulationDistance; i <= zone.y + nearSimulationDistance; i++) { for (int j = zone.x - nearSimulationDistance; j <= zone.x + nearSimulationDistance; j++) { ((Vector2s)(ref val))..ctor(j, i); if ((((SimulationDistance)(ref simulationDistance)).IsClassic || __instance.ZonesWithinRadius(zone, val, nearSimulationDistance, false)) && !__instance.m_zones.ContainsKey(val)) { __result = false; return false; } } } } __result = true; return false; } } [HarmonyPatch(typeof(ZoneSystem), "Update")] public static class ZoneSystem_Update_Patch { private static int s_nextPeer; private static bool Prefix(ZoneSystem __instance) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)ZNet.GetConnectionStatus() != 2 || (ZNet.instance.IsServer() && !__instance.LocationsGenerated)) { return true; } __instance.m_lastFixedTime = Time.fixedTime; __instance.m_updateTimer += Time.deltaTime; if (__instance.m_updateTimer > 0.1f) { __instance.m_updateTimer = 0f; __instance.UpdateTTL(0.1f); if (ZNet.instance.IsServer()) { long timestamp = Stopwatch.GetTimestamp(); List<ZNetPeer> peers = ZNet.instance.GetPeers(); int num = SpawnZones(__instance, peers, ghost: false); int ghost = ((num == 0) ? SpawnZones(__instance, peers, ghost: true) : 0); PerformanceStats.Zones(num, ghost, timestamp); } __instance.UpdatePrefabLifetimes(); } return false; } private static int SpawnZones(ZoneSystem zoneSystem, List<ZNetPeer> peers, bool ghost) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) int num = ((Configuration.maxZonesPerTick.Value > 0) ? Configuration.maxZonesPerTick.Value : int.MaxValue); int count = peers.Count; int num2 = 0; for (int i = 0; i < count; i++) { if (num2 >= num) { break; } int num3 = (s_nextPeer + i) % count; if (peers[num3].IsReady()) { Vector3 refPos = peers[num3].GetRefPos(); if (ghost ? zoneSystem.CreateGhostZones(refPos) : zoneSystem.CreateLocalZones(refPos)) { num2++; s_nextPeer = (num3 + 1) % count; } } } return num2; } } [HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")] public static class ZDOMan_ReleaseNearbyZDOS_Patch { private static bool Prefix(ZDOMan __instance, Vector3 refPosition, long uid) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) Vector2s zone = ZoneSystem.GetZone(refPosition); List<ZDO> tempNearObjects = __instance.m_tempNearObjects; tempNearObjects.Clear(); SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance(); __instance.FindSectorObjects(zone, new SimulationDistance(((SimulationDistance)(ref syncedSimulationDistance)).NearSimulationDistance, 0, ((SimulationDistance)(ref syncedSimulationDistance)).IsClassic), tempNearObjects, (List<ZDO>)null); long uID = ZNet.GetUID(); foreach (ZDO item in tempNearObjects) { if (!item.Persistent) { continue; } Vector3 position = item.GetPosition(); bool flag = false; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && ZNetScene.InActiveArea(position, ZoneSystem.GetZone(peer.GetRefPos()))) { flag = true; break; } } long owner = item.GetOwner(); if (owner == uid || owner == uID) { if (!flag) { item.SetOwner(0L); } } else if ((owner == 0L || !__instance.IsInPeerActiveArea(position, owner)) && flag) { item.SetOwner(uID); } } return false; } } [HarmonyPatch(typeof(RandEventSystem), "FixedUpdate")] public static class RandEventSystem_FixedUpdate_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { MethodInfo isAnyPlayerInEventArea = AccessTools.Method(typeof(RandEventSystem), "IsAnyPlayerInEventArea", (Type[])null, (Type[])null); MethodInfo isInsideRandomEventArea = AccessTools.Method(typeof(RandEventSystem), "IsInsideRandomEventArea", (Type[])null, (Type[])null); MethodInfo methodInfo = AccessTools.Method(typeof(Object), "op_Implicit", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.PropertyGetter(typeof(Component), "transform"); MethodInfo methodInfo3 = AccessTools.PropertyGetter(typeof(Transform), "position"); FieldInfo fieldInfo = AccessTools.Field(typeof(Player), "m_localPlayer"); FieldInfo fieldInfo2 = AccessTools.Field(typeof(RandEventSystem), "m_randomEvent"); List<CodeInstruction> list = instructions.ToList(); int num = list.FindIndex((CodeInstruction code) => CodeInstructionExtensions.Calls(code, isAnyPlayerInEventArea)); CodeInstruction val = ((num >= 0 && num + 1 < list.Count) ? LoadOf(list[num + 1]) : null); int num2 = -1; for (int num3 = Math.Max(num, 0); num3 + 2 < list.Count; num3++) { if (val == null) { break; } if (CodeInstructionExtensions.LoadsField(list[num3], fieldInfo, false) && CodeInstructionExtensions.Calls(list[num3 + 1], methodInfo) && (list[num3 + 2].opcode == OpCodes.Brfalse || list[num3 + 2].opcode == OpCodes.Brfalse_S)) { num2 = num3 + 2; break; } } int num4 = ((num2 >= 0) ? list.FindIndex(num2, (CodeInstruction code) => CodeInstructionExtensions.Calls(code, isInsideRandomEventArea)) : (-1)); int num5 = num4 - 6; bool flag = num5 > num2 && CodeInstructionExtensions.IsLdarg(list[num5], (int?)0) && CodeInstructionExtensions.IsLdarg(list[num5 + 1], (int?)0) && CodeInstructionExtensions.LoadsField(list[num5 + 2], fieldInfo2, false) && CodeInstructionExtensions.LoadsField(list[num5 + 3], fieldInfo, false) && CodeInstructionExtensions.Calls(list[num5 + 4], methodInfo2) && CodeInstructionExtensions.Calls(list[num5 + 5], methodInfo3); if (val == null || num2 < 0 || !flag) { string text = ((val == null) ? "the IsAnyPlayerInEventArea result" : ((num2 < 0) ? "the local player check" : "the IsInsideRandomEventArea call for the local player")); Plugin.logger.LogWarning((object)(__originalMethod.DeclaringType.Name + "." + __originalMethod.Name + ": " + text + " not found; left unchanged, so random events will not become active on the server. The game changed this method; the patch needs reviewing.")); return list; } list[num2].opcode = ((list[num2].opcode == OpCodes.Brfalse_S) ? OpCodes.Brtrue_S : OpCodes.Brtrue); for (int num6 = num5; num6 <= num4; num6++) { val.labels.AddRange(list[num6].labels); val.blocks.AddRange(list[num6].blocks); } list.RemoveRange(num5, num4 - num5 + 1); list.Insert(num5, val); return list; } private static CodeInstruction LoadOf(CodeInstruction store) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown if (store.opcode == OpCodes.Stloc_0) { return new CodeInstruction(OpCodes.Ldloc_0, (object)null); } if (store.opcode == OpCodes.Stloc_1) { return new CodeInstruction(OpCodes.Ldloc_1, (object)null); } if (store.opcode == OpCodes.Stloc_2) { return new CodeInstruction(OpCodes.Ldloc_2, (object)null); } if (store.opcode == OpCodes.Stloc_3) { return new CodeInstruction(OpCodes.Ldloc_3, (object)null); } if (store.opcode == OpCodes.Stloc_S) { return new CodeInstruction(OpCodes.Ldloc_S, store.operand); } if (store.opcode == OpCodes.Stloc) { return new CodeInstruction(OpCodes.Ldloc, store.operand); } return null; } } [HarmonyPatch(typeof(SpawnSystem), "UpdateSpawning")] public static class SpawnSystem_UpdateSpawning_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); CodeMatcher val = new CodeMatcher((IEnumerable<CodeInstruction>)list, (ILGenerator)null).MatchForward(true, (CodeMatch[])(object)new CodeMatch[4] { new CodeMatch((OpCode?)OpCodes.Ldsfld, (object)AccessTools.Field(typeof(Player), "m_localPlayer"), (string)null), new CodeMatch((OpCode?)OpCodes.Ldnull, (object)null, (string)null), new CodeMatch((OpCode?)OpCodes.Call, (object)AccessTools.Method(typeof(Object), "op_Equality", (Type[])null, (Type[])null), (string)null), new CodeMatch((Func<CodeInstruction, bool>)((CodeInstruction instruction) => instruction.opcode == OpCodes.Brfalse || instruction.opcode == OpCodes.Brfalse_S), (string)null) }); bool isValid = val.IsValid; if (isValid) { val.Opcode = ((val.Opcode == OpCodes.Brfalse_S) ? OpCodes.Brtrue_S : OpCodes.Brtrue); val.MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((OpCode?)OpCodes.Callvirt, (object)AccessTools.Method(typeof(RandEventSystem), "GetCurrentSpawners", (Type[])null, (Type[])null), (string)null) }); } if (!isValid || !val.IsValid) { string text = ((!isValid) ? "the local player check" : "the RandEventSystem.GetCurrentSpawners call"); Plugin.logger.LogWarning((object)(__originalMethod.DeclaringType.Name + "." + __originalMethod.Name + ": " + text + " not found; left unchanged, so the server will not spawn creatures. The game changed this method; the patch needs reviewing.")); return list; } val.Opcode = OpCodes.Call; val.Operand = AccessTools.Method(typeof(Core), "GetCurrentSpawners", (Type[])null, (Type[])null); val.Insert((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Ldarg_0, (object)null) }); return val.InstructionEnumeration(); } } [HarmonyPatch(typeof(ZNetScene), "OutsideActiveArea", new Type[] { typeof(Vector3) })] public static class ZNetScene_OutsideActiveArea_Patch { private static bool Prefix(ref bool __result, ZNetScene __instance, Vector3 point) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) __result = true; foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer.IsReady() && !ZNetScene.OutsideActiveArea(point, ZoneSystem.GetZone(peer.GetRefPos()))) { __result = false; } } return false; } } [HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")] public static class ZRoutedRpc_RouteRPC_Patch { private static void Prefix(RoutedRPCData rpcData) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (rpcData.m_methodHash == StringExtensionMethods.GetStableHashCode("RequestRespons")) { bool flag = rpcData.m_parameters.ReadBool(); ZDO zDO = ZDOMan.instance.GetZDO(rpcData.m_targetZDO); if (zDO != null && flag) { Plugin.logger.LogDebug((object)$"RequestRespons: Setting ship's owner to {rpcData.m_targetPeerID}"); zDO.SetOwner(rpcData.m_targetPeerID); } } } } [HarmonyPatch(typeof(Humanoid), "UpdateAttack")] public static class Humanoid_UpdateAttack_Patch { private static void Prefix(ref Humanoid __instance) { if (__instance.m_currentAttack != null && (Object)(object)__instance.m_currentAttack.m_character == (Object)null) { __instance.m_currentAttack = null; } } } [HarmonyPatch(typeof(Ship), "UpdateOwner")] public static class Ship_UpdateOwner_Patch { private static bool Prefix(ref Ship __instance) { if (!__instance.m_nview.IsValid()) { return false; } ZDO zDO = __instance.m_nview.GetZDO(); if (zDO.GetInt("InUse", 0) == 0) { if (!__instance.m_shipControlls.HaveValidUser()) { __instance.m_lastWaterImpactTime = Time.time; zDO.SetOwner(ZNet.GetUID()); return false; } long user = __instance.m_shipControlls.GetUser(); Player val = ((user != 0L) ? Player.GetPlayer(user) : null); if ((Object)(object)val != (Object)null) { long owner = ((Character)val).GetOwner(); Plugin.logger.LogDebug((object)$"UpdateOwner: Setting ship's owner to {owner}"); zDO.SetOwner(owner); } } return false; } } [HarmonyPatch(typeof(AudioMan), "Update")] public static class AudioMan_Update_Patch { private static bool Prefix() { return false; } } [HarmonyPatch(typeof(ShieldDomeImageEffect), "GetDomeColor")] public static class ShieldDomeImageEffect_GetDomeColor_Patch { private static bool Prefix(ref Color __result) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) __result = new Color(1f, 1f, 1f); return false; } } public bool FeatureEnabled() { return true; } public static bool IsServer() { if (Object.op_Implicit((Object)(object)ZNet.instance)) { return ZNet.instance.IsServer(); } return false; } public static void PrintLog(string text) { Trace.WriteLine(text); } public static void PrintLog(object[] obj) { Trace.WriteLine(string.Concat(obj)); } public static List<SpawnData> GetCurrentSpawners(RandEventSystem instance, SpawnSystem spawnSystem) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) RandomEvent activeEvent = instance.m_activeEvent; if (activeEvent == null) { return null; } Vector3 position = spawnSystem.m_nview.GetZDO().GetPosition(); foreach (Player allPlayer in Player.GetAllPlayers()) { if (ZNetScene.InActiveArea(position, ZoneSystem.GetZone(((Component)allPlayer).transform.position)) && instance.IsInsideRandomEventArea(activeEvent, ((Component)allPlayer).transform.position)) { return instance.GetCurrentSpawners(); } } return null; } } [Harmony] internal class Debugging : IFeature { [PatchRequires("DebugBuild")] [HarmonyPatch(typeof(Chat), "RPC_ChatMessage")] public static class Chat_RPC_ChatMessage_Patch { private static void Prefix(ref long sender, ref string text) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) ZNetPeer peer = ZNet.instance.GetPeer(sender); if (peer == null) { return; } if (text == "startevent") { RandEventSystem.instance.SetRandomEventByName("army_theelder", peer.GetRefPos()); } else if (text == "stopevent") { RandEventSystem.instance.ResetRandomEvent(); } else if (text.StartsWith("maxobjects")) { string[] array = text.Split(new char[1] { ' ' }); if (array.Length > 1 && int.TryParse(array[1], out var result)) { Configuration.maxObjectsPerFrame.Value = result; } } } } public bool FeatureEnabled() { return Utilities.IsDebugBuild(); } } public class DungeonLoadGuard : IFeature { [HarmonyPatch(typeof(BundleLoader), "OnLoadAsyncCompleted", new Type[] { })] public static class BundleLoader_OnLoadAsyncCompleted_Patch { private static bool Prefix(ref BundleLoader __instance) { AssetBundleCreateRequestWrapper asyncLoadRequest = __instance.m_asyncLoadRequest; if (asyncLoadRequest == null || (Object)(object)asyncLoadRequest.AssetBundle != (Object)null) { return true; } AssetBundle val = (__instance.m_bundle = FindLoaded(__instance.m_bundleName, __instance.m_bundlePath)); __instance.m_asyncLoadRequest = null; LogOnce("bundle " + __instance.m_bundleName, ((Object)(object)val != (Object)null) ? ("Unity refused to load bundle " + __instance.m_bundleName + " again because it is already loaded; using the one it holds, so what needs it still loads.") : ("Unity returned no bundle for " + __instance.m_bundleName + "; reporting its assets as failed rather than letting the game throw on it.")); if (__instance.m_shouldBeLoaded) { ((BundleLoader)(ref __instance)).InvokeCallbacks((LoadResult)(!((Object)(object)val != (Object)null))); } else if (BundleLoader.s_tickOnAsyncOperationComplete) { ((BundleLoader)(ref __instance)).TickAsync(); } return false; } } [HarmonyPatch(typeof(AssetLoader), "LoadAssetFromBundleAsync")] public static class AssetLoader_LoadAssetFromBundleAsync_Patch { private unsafe static bool Prefix(ref AssetLoader __instance) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) AssetBundleLoader instance = AssetBundleLoader.Instance; if (instance == null || (Object)(object)((BundleLoader)(ref instance.m_bundleLoaders[__instance.m_bundleLoaderIndex])).Bundle != (Object)null) { return true; } AssetID assetID = __instance.m_assetID; LogOnce("asset " + ((object)(*(AssetID*)(&assetID))/*cast due to .constrained prefix*/).ToString(), $"Asset {__instance.m_assetID} ({__instance.m_assetPathInBundle}) has no bundle to load from; reporting it as failed."); ((AssetLoader)(ref __instance)).InvokeCallbacks((LoadResult)1); return false; } } [HarmonyPatch(typeof(DungeonGenerator), "OnRoomLoaded")] [HarmonyAfter(new string[] { "MidnightsFX.ValheimCommunityPatch" })] public static class DungeonGenerator_OnRoomLoaded_Patch { private static bool Prefix(DungeonGenerator __instance, LoadResult result, bool __runOriginal) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!__runOriginal || (int)result == 0 || (Object)(object)__instance == (Object)null || !Object.op_Implicit((Object)(object)__instance)) { return true; } Vector3 position = ((Component)__instance).transform.position; LogOnce("dungeon " + ((object)ZoneSystem.GetZone(position)/*cast due to .constrained prefix*/).ToString(), $"Dungeon {((Object)__instance).name} at {position:F0}: a room failed to load ({result}). Letting it go so its zone keeps working; it is tried again the next time it is created."); __instance.m_roomsToLoad = int.MaxValue; __instance.ReleaseHeldReferences(); return false; } } private static readonly HashSet<string> s_logged = new HashSet<string>(); public bool FeatureEnabled() { return Configuration.fixDungeonLoadGuard.Value; } private static void LogOnce(string key, string text) { if (s_logged.Add(key)) { Plugin.logger.LogWarning((object)text); } } private static AssetBundle FindLoaded(string bundleName, string bundlePath) { string text = (string.IsNullOrEmpty(bundlePath) ? null : Path.GetFileName(bundlePath)); foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { if ((Object)(object)allLoadedAssetBundle != (Object)null && (((Object)allLoadedAssetBundle).name == bundleName || (text != null && ((Object)allLoadedAssetBundle).name == text))) { return allLoadedAssetBundle; } } return null; } } public class Fixes : IFeature { [HarmonyPatch(typeof(ZDO), "Deserialize")] public static class ZDO_Deserialize_Patch { private static void Postfix(ZDO __instance) { if (Configuration.fixSaveClientChanges.Value && __instance.Persistent && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && ZDOMan.instance != null) { ZDOMan.instance.SetDirtySector(__instance); } } } [HarmonyPatch(typeof(ZDO), "InternalSetPosition")] public static class ZDO_InternalSetPosition_Patch { private static void Prefix(ZDO __instance, out SectorIndex __state) { //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) __state = FiledSector(__instance); } private static void Postfix(ZDO __instance, SectorIndex __state) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (Configuration.fixTeleportGhosts.Value && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer() && ZDOMan.instance != null && !(FiledSector(__instance) == __state) && (!Object.op_Implicit((Object)(object)Game.instance) || !Game.instance.PortalPrefabHash.Contains(__instance.GetPrefab()))) { ZDOMan.instance.ZDOSectorInvalidated(__instance); } } private static SectorIndex FiledSector(ZDO zdo) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (!zdo.OutsideZones) { return zdo.GetSectorIndex(); } return ZoneSystem.SectorZero; } } public bool FeatureEnabled() { if (!Configuration.fixSaveClientChanges.Value) { return Configuration.fixTeleportGhosts.Value; } return true; } } public class MaxObjectsPerFrame : IFeature { [HarmonyPatch(typeof(ZNetScene), "CreateObjects")] public class CreateObjects_Patch { public static void ILManipulator(ILContext il) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) new ILCursor(il).GotoNext((MoveType)0, new Func<Instruction, bool>[5] { (Instruction i) => ILPatternMatchingExt.MatchLdarg(i, 0), (Instruction i) => ILPatternMatchingExt.MatchLdarg(i, 1), (Instruction i) => ILPatternMatchingExt.MatchLdloc(i, 0), (Instruction i) => ILPatternMatchingExt.MatchLdloca(i, 1), (Instruction i) => ILPatternMatchingExt.MatchCall<ZNetScene>(i, "CreateObjectsSorted") }).Emit(OpCodes.Call, (MethodBase)AccessTools.Method(typeof(MaxObjectsPerFrame), "GetMaxCreatedPerFrame", (Type[])null, (Type[])null)).Emit(OpCodes.Stloc_0); } } public bool FeatureEnabled() { return Configuration.maxObjectsPerFrameEnabled.Value; } public static int GetMaxCreatedPerFrame() { return Configuration.maxObjectsPerFrame.Value; } } public class Networking : IFeature { [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] public static class ZDOMan_SendZDOs_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Networking), "QueueSize", (Type[])null, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = 0; for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.LoadsConstant(list[i], 10240L)) { CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)methodInfo); val.labels.AddRange(list[i].labels); val.blocks.AddRange(list[i].blocks); list[i] = val; num++; } } if (num != 2) { Plugin.logger.LogWarning((object)$"{__originalMethod.DeclaringType.Name}.{__originalMethod.Name}: replaced {num} send queue limit(s), expected 2. The game changed this method; the patch needs reviewing."); } return list; } private static void Prefix(ZDOPeer peer, bool flush) { Stats.Record(peer.m_peer, flush); } } [HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")] public static class ZSteamSocket_RegisterGlobalCallbacks_Patch { private static void Postfix() { int num = Configuration.networkSendRateMaxKB.Value * 1024; int num2 = Math.Min(Configuration.networkSendRateMinKB.Value * 1024, num); SetSteamConfig((ESteamNetworkingConfigValue)10, num2); SetSteamConfig((ESteamNetworkingConfigValue)11, num); Plugin.logger.LogInfo((object)$"Networking: Steam send rate {num2 / 1024}-{num / 1024} KB/s, send queue {QueueSize() / 1024} KB per player"); } private static void SetSteamConfig(ESteamNetworkingConfigValue key, int value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned); try { SteamGameServerNetworkingUtils.SetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject()); } finally { gCHandle.Free(); } } } private static class Stats { private class PeerStats { public string name; public int ticks; public int full; public int maxQueued; } private static readonly Dictionary<long, PeerStats> s_peers = new Dictionary<long, PeerStats>(); private static float s_nextReport = -1f; public static void Record(ZNetPeer peer, bool flush) { int value = Configuration.networkStatsMinutes.Value; if (!(value <= 0 || flush) && peer != null && peer.m_socket != null) { int sendQueueSize = peer.m_socket.GetSendQueueSize(); if (!s_peers.TryGetValue(peer.m_uid, out var value2)) { value2 = new PeerStats(); s_peers[peer.m_uid] = value2; } value2.name = peer.m_playerName; value2.ticks++; if (QueueSize() - sendQueueSize < 2048) { value2.full++; } value2.maxQueued = Math.Max(value2.maxQueued, sendQueueSize); float realtimeSinceStartup = Time.realtimeSinceStartup; if (s_nextReport < 0f) { s_nextReport = realtimeSinceStartup + (float)value * 60f; } else if (realtimeSinceStartup >= s_nextReport) { Report(); s_nextReport = realtimeSinceStartup + (float)value * 60f; } } } private static void Report() { foreach (PeerStats value in s_peers.Values) { Plugin.logger.LogInfo((object)$"Networking {value.name}: send queue full in {100f * (float)value.full / (float)value.ticks:0.0}% of {value.ticks} send ticks, at most {value.maxQueued / 1024} of {QueueSize() / 1024} KB queued"); } s_peers.Clear(); } } private const int VanillaQueueSize = 10240; public bool FeatureEnabled() { return Configuration.networkingEnabled.Value; } public static int QueueSize() { return Mathf.Clamp(Configuration.networkQueueSizeKB.Value, 10, 80) * 1024; } } public class Performance : IFeature { [HarmonyPatch(typeof(ZDOMan), "SendZDOToPeers2")] public static class ZDOMan_SendZDOToPeers2_Patch { private static double s_owed; private static int s_next; private static double s_last = -1.0; private static bool Prefix(ZDOMan __instance) { float num = (float)Configuration.sendIntervalMs.Value / 1000f; if (num <= 0f) { return true; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; double num2 = ((s_last < 0.0) ? 0.0 : (realtimeSinceStartupAsDouble - s_last)); s_last = realtimeSinceStartupAsDouble; List<ZDOPeer> peers = __instance.m_peers; int count = peers.Count; if (count == 0) { s_owed = 0.0; return false; } s_owed = Math.Min(s_owed + (double)count * num2 / (double)num, count); int num3 = (int)s_owed; s_owed -= num3; for (int i = 0; i < num3; i++) { if (s_next >= count) { s_next = 0; } __instance.SendZDOs(peers[s_next++], false); } return false; } } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] public static class ZDOMan_SendZDOs_Timing { private static void Prefix() { PerformanceStats.SendStarted(); } private static void Postfix() { PerformanceStats.SendFinished(); } } [HarmonyPatch] public static class PresentManager_RequestTargetFrameRate_Patch { private static int s_logged; private static int s_forced; private static bool s_looked; private static FieldInfo s_refreshRate; private static MethodInfo s_apply; private static IEnumerable<MethodBase> TargetMethods() { List<MethodBase> list = new List<MethodBase>(); foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(PresentManager))) { ParameterInfo[] parameters = declaredMethod.GetParameters(); if (declaredMethod.Name == "RequestTargetFrameRate" && parameters.Length >= 1 && parameters[0].ParameterType == typeof(int)) { list.Add(declaredMethod); } } if (list.Count == 0) { Plugin.logger.LogWarning((object)"PresentManager.RequestTargetFrameRate(int, ...) is gone from this game version: the server keeps the frame rate the game asks for."); } return list; } private static void Prefix(ref int __0) { int num = __0; int value = Configuration.serverTargetFps.Value; if (value <= 0 || !Plugin.IsDedicated()) { s_forced = 0; return; } int num2 = Mathf.Clamp(value, 30, 240); if (s_logged++ == 0 || num2 != num) { Plugin.logger.LogInfo((object)$"Server target frame rate: {num} -> {num2}"); } __0 = num2; s_forced = num2; } private static void Postfix(PresentManager __instance) { if (s_forced > 0 && __instance != null) { if (!s_looked) { s_looked = true; s_refreshRate = typeof(PresentManager).GetField("m_requestedRefreshRate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); s_apply = typeof(PresentManager).GetMethod("UpdatePresentSettings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (!(s_refreshRate == null) && s_refreshRate.GetValue(__instance) is int num && num != s_forced) { s_refreshRate.SetValue(__instance, s_forced); s_apply?.Invoke(__instance, null); } } } } [HarmonyPatch(typeof(MonoUpdaters), "FixedUpdate")] public static class MonoUpdaters_FixedUpdate_Timing { private static void Prefix() { PerformanceStats.FixedStarted(); } private static void Postfix() { PerformanceStats.FixedFinished(); } } [HarmonyPatch(typeof(ZNet), "UpdatePeers")] public static class ZNet_UpdatePeers_Timing { private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(long __state) { PerformanceStats.Section(0, __state); } } [HarmonyPatch(typeof(ZDOMan), "Update")] public static class ZDOMan_Update_Timing { private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(long __state) { PerformanceStats.Section(1, __state); } } [HarmonyPatch(typeof(ZNetScene), "Update")] public static class ZNetScene_Update_Timing { private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(long __state) { PerformanceStats.Section(2, __state); } } [HarmonyPatch(typeof(MonoUpdaters), "Update")] public static class MonoUpdaters_Update_Timing { private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(long __state) { PerformanceStats.Section(5, __state); } } [HarmonyPatch(typeof(MonoUpdaters), "LateUpdate")] public static class MonoUpdaters_LateUpdate_Timing { private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(long __state) { PerformanceStats.Section(5, __state); } } [HarmonyPatch(typeof(ZNet), "SaveWorld")] public static class ZNet_SaveWorld_Timing { private static void Prefix(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Postfix(long __state) { PerformanceStats.Section(6, __state); } } public bool FeatureEnabled() { if (Configuration.sendIntervalMs.Value <= 0 && Configuration.performanceStatsMinutes.Value <= 0) { return Configuration.serverTargetFps.Value > 0; } return true; } } public static class PerformanceStats { private const double SlowFrameSeconds = 0.1; private const int BucketMs = 5; public const int Peers = 0; public const int World = 1; public const int Objects = 2; public const int ZoneGen = 3; public const int Creatures = 4; public const int Updaters = 5; public const int Save = 6; private static readonly string[] SectionNames = new string[7] { "players' messages", "world updates", "objects", "zones", "creatures", "other game updates", "saving" }; private static readonly double[] s_sections = new double[SectionNames.Length]; private static readonly double[] s_worstSections = new double[SectionNames.Length]; private static int s_worstGc0; private static int s_worstGc2; private static int s_worstSteps; private static int s_lastGc0; private static int s_lastGc2; private static readonly int[] s_histogram = new int[200]; private static double s_lastFrame = -1.0; private static double s_nextReport = -1.0; private static double s_periodStart; private static int s_frames; private static int s_slowFrames; private static double s_frameSum; private static double s_frameMax; private static int s_stepsThisFrame; private static int s_stepSum; private static int s_stepMax; private static long s_fixedStarted; private static double s_fixedThisFrame; private static double s_fixedSum; private static double s_fixedMax; private static int s_sends; private static double s_sendSum; private static double s_sendMax; private static long s_sendStarted; private static int s_localZones; private static int s_ghostZones; private static double s_zoneSum; private static double s_zoneMax; private static bool Enabled => Configuration.performanceStatsMinutes.Value > 0; private static double Seconds(long ticks) { return (double)ticks / (double)Stopwatch.Frequency; } public static void FixedStep() { s_stepsThisFrame++; } public static void FixedStarted() { s_fixedStarted = Stopwatch.GetTimestamp(); } public static void FixedFinished() { double num = Seconds(Stopwatch.GetTimestamp() - s_fixedStarted); s_fixedThisFrame += num; s_sections[4] += num; } public static void Section(int index, long started) { s_sections[index] += Seconds(Stopwatch.GetTimestamp() - started); } public static void SendStarted() { s_sendStarted = Stopwatch.GetTimestamp(); } public static void SendFinished() { double num = Seconds(Stopwatch.GetTimestamp() - s_sendStarted); s_sends++; s_sendSum += num; s_sendMax = Math.Max(s_sendMax, num); } public static void Zones(int local, int ghost, long started) { if (local + ghost != 0) { double num = Seconds(Stopwatch.GetTimestamp() - started); s_sections[3] += num; s_localZones += local; s_ghostZones += ghost; s_zoneSum += num; s_zoneMax = Math.Max(s_zoneMax, num); } } public static void Frame() { int num = s_stepsThisFrame; double num2 = s_fixedThisFrame; s_stepsThisFrame = 0; s_fixedThisFrame = 0.0; if (!Enabled) { return; } double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; if (s_lastFrame < 0.0) { s_lastFrame = realtimeSinceStartupAsDouble; s_periodStart = realtimeSinceStartupAsDouble; s_nextReport = realtimeSinceStartupAsDouble + (double)Configuration.performanceStatsMinutes.Value * 60.0; return; } double num3 = realtimeSinceStartupAsDouble - s_lastFrame; s_lastFrame = realtimeSinceStartupAsDouble; s_frames++; s_frameSum += num3; int num4 = GC.CollectionCount(0); int num5 = GC.CollectionCount(2); if (num3 > s_frameMax) { s_frameMax = num3; Array.Copy(s_sections, s_worstSections, s_sections.Length); s_worstGc0 = num4 - s_lastGc0; s_worstGc2 = num5 - s_lastGc2; s_worstSteps = num; } s_lastGc0 = num4; s_lastGc2 = num5; Array.Clear(s_sections, 0, s_sections.Length); if (num3 > 0.1) { s_slowFrames++; } s_histogram[Math.Min((int)(num3 * 1000.0 / 5.0), s_histogram.Length - 1)]++; s_stepSum += num; s_stepMax = Math.Max(s_stepMax, num); s_fixedSum += num2; s_fixedMax = Math.Max(s_fixedMax, num2); if (realtimeSinceStartupAsDouble >= s_nextReport) { Report(realtimeSinceStartupAsDouble - s_periodStart); s_periodStart = realtimeSinceStartupAsDouble; s_nextReport = realtimeSinceStartupAsDouble + (double)Configuration.performanceStatsMinutes.Value * 60.0; } } private static string WorstFrame() { double num = 0.0; List<string> list = new List<string>(); for (int i = 0; i < SectionNames.Length; i++) { num += s_worstSections[i]; if (s_worstSections[i] >= 0.001) { list.Add($"{SectionNames[i]} {1000.0 * s_worstSections[i]:0}"); } } list.Add($"unaccounted {1000.0 * Math.Max(0.0, s_frameMax - num):0}"); string text = ((s_worstGc2 > 0) ? $", full GC x{s_worstGc2}" : ((s_worstGc0 > 0) ? $", GC x{s_worstGc0}" : "")); return string.Format("Worst frame ({0:0} ms) spent, ms: {1}; {2} fixed step(s){3}.", 1000.0 * s_frameMax, string.Join(", ", list), s_worstSteps, text); } private static int Percentile(double share) { int num = (int)Math.Ceiling(share * (double)s_frames); int num2 = 0; for (int i = 0; i < s_histogram.Length; i++) { num2 += s_histogram[i]; if (num2 >= num) { return (i + 1) * 5; } } return s_histogram.Length * 5; } private static void Report(double period) { int num = (Object.op_Implicit((Object)(object)ZNet.instance) ? ZNet.instance.GetPeers().Count : 0); if (s_frames > 0 && num > 0) { Plugin.logger.LogInfo((object)($"Performance over {period / 60.0:0.#} min, {num} player(s): " + $"{(double)s_frames / period:0.#} FPS, frame avg {1000.0 * s_frameSum / (double)s_frames:0} ms, median {Percentile(0.5)}, 95% {Percentile(0.95)}, 99% {Percentile(0.99)}, worst {1000.0 * s_frameMax:0} ms, {s_slowFrames} over {100.0:0} ms; " + $"fixed steps per frame avg {(double)s_stepSum / (double)s_frames:0.0}, max {s_stepMax}, their game logic {100.0 * s_fixedSum / period:0.0}% of the time, worst frame {1000.0 * s_fixedMax:0} ms; " + $"world sends {s_sends} ({(double)s_sends / period:0.#}/s), avg {((s_sends > 0) ? (1000.0 * s_sendSum / (double)s_sends) : 0.0):0.0} ms, worst {1000.0 * s_sendMax:0.0} ms, {100.0 * s_sendSum / period:0.0}% of the time; " + $"zones generated {s_localZones} (+{s_ghostZones} ghost), {1000.0 * s_zoneSum:0} ms in all, worst tick {1000.0 * s_zoneMax:0} ms. " + WorstFrame())); } Array.Clear(s_histogram, 0, s_histogram.Length); Array.Clear(s_worstSections, 0, s_worstSections.Length); s_worstGc0 = (s_worstGc2 = (s_worstSteps = 0)); s_frames = (s_slowFrames = (s_stepSum = (s_stepMax = (s_sends = (s_localZones = (s_ghostZones = 0)))))); s_frameSum = (s_frameMax = (s_sendSum = (s_sendMax = (s_zoneSum = (s_zoneMax = (s_fixedSum = (s_fixedMax = 0.0))))))); } } } namespace Serverbound.FeatureModel { public interface IFeature { bool FeatureEnabled(); } public class AvailableFeatures { public readonly List<IFeature> _features; public AvailableFeatures() { _features = new List<IFeature>(); } public AvailableFeatures(List<IFeature> features) { _features = features; } public AvailableFeatures AddFeature(IFeature feature) { _features.Add(feature); return this; } public List<IFeature> EnabledFeatures() { return _features.Where((IFeature feature) => feature.FeatureEnabled()).ToList(); } public Type[] GetAllNestedTypes() { return (from feature in EnabledFeatures() select feature.GetType().GetNestedTypes() into list from item in list select item).ToArray(); } } } namespace Serverbound.Settings { public class Configuration { public static ConfigEntry<bool> modEnabled; public static ConfigEntry<bool> maxObjectsPerFrameEnabled; public static ConfigEntry<int> maxObjectsPerFrame; public static ConfigEntry<bool> networkingEnabled; public static ConfigEntry<int> networkQueueSizeKB; public static ConfigEntry<int> networkSendRateMinKB; public static ConfigEntry<int> networkSendRateMaxKB; public static ConfigEntry<int> networkStatsMinutes; public static ConfigEntry<bool> consoleCommandsEnabled; public static ConfigEntry<int> unityJobWorkers; public static ConfigEntry<int> sendIntervalMs; public static ConfigEntry<int> maxCatchUpMs; public static ConfigEntry<int> maxZonesPerTick; public static ConfigEntry<int> performanceStatsMinutes; public static ConfigEntry<int> serverTargetFps; public static ConfigEntry<int> maxGiveAmount; public static ConfigEntry<int> consoleInputCodePage; public static ConfigEntry<bool> fixSaveClientChanges; public static ConfigEntry<bool> fixDungeonLoadGuard; public static ConfigEntry<bool> compatVcpSpawnQueue; public static ConfigEntry<bool> compatVcpUnload; public static ConfigEntry<bool> fixTeleportGhosts; public static void Load(ConfigFile config) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Expected O, but got Unknown //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Expected O, but got Unknown //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Expected O, but got Unknown RemoveRetiredSettings(config); modEnabled = config.Bind<bool>("General", "Enabled", true, "Enable or disable the mod"); maxObjectsPerFrameEnabled = config.Bind<bool>("MaxObjectsPerFrame", "Enabled", true, "Enable or disable the feature"); maxObjectsPerFrame = config.Bind<int>("MaxObjectsPerFrame", "MaxObjects", 100, new ConfigDescription("Maximum number of objects the server can create per frame. A vanilla dedicated server creates 100 (the game's loading-screen rate: a server has no player of its own); lower values make areas fill in more slowly.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10000), Array.Empty<object>())); networkingEnabled = config.Bind<bool>("Networking", "Enabled", true, "Raise the limits on how fast the server sends world data to each player (server-side part of BetterNetworking). Needs a restart."); networkQueueSizeKB = config.Bind<int>("Networking", "QueueSizeKB", 48, new ConfigDescription("World data queued per player before sending pauses for that tick. A larger queue can delay new updates. Match the Steam send rate to the server's upload capacity and measure queue occupancy under load.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 80), Array.Empty<object>())); networkSendRateMinKB = config.Bind<int>("Networking", "SteamSendRateMinKB", 256, new ConfigDescription("Minimum rate Steam attempts to send to each player, KB/s. Valheim: 150. Keep it below the server's upload speed divided by the number of players.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 4096), Array.Empty<object>())); networkSendRateMaxKB = config.Bind<int>("Networking", "SteamSendRateMaxKB", 1024, new ConfigDescription("Maximum rate Steam sends to each player, KB/s. Valheim: 150.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 4096), Array.Empty<object>())); networkStatsMinutes = config.Bind<int>("Networking", "StatsIntervalMinutes", 5, "Every this many minutes, log per player how often their send queue was full. 0 disables."); consoleCommandsEnabled = config.Bind<bool>("Server", "ConsoleCommands", true, "Read commands from standard input: save, stop, players, give <item> <amount> <player>, broadcast <text>, event <name> <player>, events, help; anything else goes to the game's own console (kick, ban, unban, banned, stopevent, devcommands, skiptime ...). In AMP set App.HasWriteableConsole=True to type them into its console, and App.ExitMethod=String with App.ExitString=stop to shut down cleanly. On Windows also set [Logging.Console] Enabled = false in BepInEx.cfg, or BepInEx's own console takes over standard input."); maxGiveAmount = config.Bind<int>("Server", "MaxGiveAmount", 1000, "Most items one give command may drop."); consoleInputCodePage = config.Bind<int>("Server", "ConsoleInputCodePage", 1250, new ConfigDescription("How to read a console line that is not valid UTF-8 (UTF-8 is always tried first). Panels on Windows may write their code page instead: 1250 Central European (Czech), 852 Central European DOS, 1252 Western, 65001 UTF-8 only. The log says which bytes arrived the first time this is needed.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 1250, 852, 1252, 65001 }), Array.Empty<object>())); unityJobWorkers = config.Bind<int>("Server", "UnityJobWorkers", 8, "Upper limit on Unity job worker threads. Unity starts one per CPU core, and on many-core hosts the idle ones still use CPU. Only ever lowers the count. 0 leaves Unity's default."); sendIntervalMs = config.Bind<int>("Performance", "SendIntervalMs", 100, new ConfigDescription("How often each player is sent world updates, in real milliseconds. Valheim sends to one player per frame, so each player waits players+1 frames: at 15 FPS with 4 players ~330 ms. Every send builds that player's list of nearby objects, so shorter intervals cost server CPU. 0 keeps Valheim's behaviour.", (AcceptableV