#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 s_items; public static ZNetPeer FindPlayer(string name, out string problem) { problem = null; List list = (from p in ZNet.instance.GetPeers() where p.IsReady() && !string.IsNullOrEmpty(p.m_playerName) select p).ToList(); ZNetPeer val = ((IEnumerable)list).FirstOrDefault((Func)((ZNetPeer p) => string.Equals(p.m_playerName, name, StringComparison.OrdinalIgnoreCase))); if (val != null) { return val; } List list2 = list.Where((ZNetPeer p) => p.m_playerName.StartsWith(name, StringComparison.OrdinalIgnoreCase)).ToList(); List 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 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().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(val, val4, Quaternion.identity).GetComponent().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)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 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(StringComparer.OrdinalIgnoreCase); foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if (Object.op_Implicit((Object)(object)prefab) && Object.op_Implicit((Object)(object)prefab.GetComponent()) && !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 list = new List(); 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 codePage, LineHandler handler) { bool noticed = false; using Stream stream = Console.OpenStandardInput(); byte[] array = new byte[4096]; List list = new List(); 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 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("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 list = new List(); 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 | broadcast | event | events | help -- anything else goes to the game console: kick, ban, unban, banned, stopevent, devcommands, skiptime ..."; private static readonly ConcurrentQueue s_commands = new ConcurrentQueue(); 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 | broadcast | event | 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 , 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 "); } 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 , 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 | broadcast | event | 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 | broadcast | event | 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 Replacements = new Dictionary { { "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 NullSafeLocalPlayer(IEnumerable 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 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 Expected = new Dictionary { { 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 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 hashSet = GameCalls(item.Key); List list = (from s in hashSet.Except(item.Value) orderby s select s).ToList(); List 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 GameCalls(MethodBase method) { HashSet hashSet = new HashSet(); 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 IPatchRequirement.Checker => Utilities.IsDebugBuild; } } } namespace Serverbound.Patching { public interface IPatchRequirement { string Name { get; } Func Checker { get; } } public class PatchRequirements { private readonly Dictionary> _requirements; public PatchRequirements() { _requirements = new Dictionary>(); } public PatchRequirements(Dictionary> 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().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 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 Transpiler(IEnumerable instructions, MethodBase __originalMethod) { return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 1); } } [HarmonyPatch(typeof(Ship), "UpdateSailSize")] public static class Ship_UpdateSailSize_Patch { private static IEnumerable Transpiler(IEnumerable instructions, MethodBase __originalMethod) { return ServerSafe.NullSafeLocalPlayer(instructions, __originalMethod, 2); } } [HarmonyPatch(typeof(CookingStation), "SpawnItem")] public static class CookingStation_SpawnItem_Patch { private static IEnumerable Transpiler(IEnumerable 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 s_seen = new HashSet(); 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 tempCurrentObjects = __instance.m_tempCurrentObjects; List 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 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 s_peerPositions = new List(); private static int s_peerPositionsFrame = -1; private static IEnumerable Transpiler(IEnumerable 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 list = new List(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 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 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 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)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 Transpiler(IEnumerable 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 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 Transpiler(IEnumerable 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 list = instructions.ToList(); CodeMatcher val = new CodeMatcher((IEnumerable)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 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 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 s_logged = new HashSet(); 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[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(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 Transpiler(IEnumerable 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 list = new List(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 s_peers = new Dictionary(); 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 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 TargetMethods() { List list = new List(); 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 list = new List(); 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 _features; public AvailableFeatures() { _features = new List(); } public AvailableFeatures(List features) { _features = features; } public AvailableFeatures AddFeature(IFeature feature) { _features.Add(feature); return this; } public List 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 modEnabled; public static ConfigEntry maxObjectsPerFrameEnabled; public static ConfigEntry maxObjectsPerFrame; public static ConfigEntry networkingEnabled; public static ConfigEntry networkQueueSizeKB; public static ConfigEntry networkSendRateMinKB; public static ConfigEntry networkSendRateMaxKB; public static ConfigEntry networkStatsMinutes; public static ConfigEntry consoleCommandsEnabled; public static ConfigEntry unityJobWorkers; public static ConfigEntry sendIntervalMs; public static ConfigEntry maxCatchUpMs; public static ConfigEntry maxZonesPerTick; public static ConfigEntry performanceStatsMinutes; public static ConfigEntry serverTargetFps; public static ConfigEntry maxGiveAmount; public static ConfigEntry consoleInputCodePage; public static ConfigEntry fixSaveClientChanges; public static ConfigEntry fixDungeonLoadGuard; public static ConfigEntry compatVcpSpawnQueue; public static ConfigEntry compatVcpUnload; public static ConfigEntry 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("General", "Enabled", true, "Enable or disable the mod"); maxObjectsPerFrameEnabled = config.Bind("MaxObjectsPerFrame", "Enabled", true, "Enable or disable the feature"); maxObjectsPerFrame = config.Bind("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(1, 10000), Array.Empty())); networkingEnabled = config.Bind("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("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(10, 80), Array.Empty())); networkSendRateMinKB = config.Bind("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(64, 4096), Array.Empty())); networkSendRateMaxKB = config.Bind("Networking", "SteamSendRateMaxKB", 1024, new ConfigDescription("Maximum rate Steam sends to each player, KB/s. Valheim: 150.", (AcceptableValueBase)(object)new AcceptableValueRange(64, 4096), Array.Empty())); networkStatsMinutes = config.Bind("Networking", "StatsIntervalMinutes", 5, "Every this many minutes, log per player how often their send queue was full. 0 disables."); consoleCommandsEnabled = config.Bind("Server", "ConsoleCommands", true, "Read commands from standard input: save, stop, players, give , broadcast , event , 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("Server", "MaxGiveAmount", 1000, "Most items one give command may drop."); consoleInputCodePage = config.Bind("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(new int[4] { 1250, 852, 1252, 65001 }), Array.Empty())); unityJobWorkers = config.Bind("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("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.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); maxCatchUpMs = config.Bind("Performance", "MaxCatchUpMs", 100, new ConfigDescription("Longest frame the server counts in full (Unity's maximum allowed timestep). After a slow frame Unity runs physics and every creature's fixed update again for each 20 ms it fell behind; Valheim allows 200 ms (10 steps), 100 caps it at 5, so one slow frame does not make the next one slow too. Game time runs slightly slower during such frames. Needs a restart. 0 keeps the game's setting.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000), Array.Empty())); maxZonesPerTick = config.Bind("Performance", "MaxZonesPerTick", 1, new ConfigDescription("Most new zones the server generates per zone tick (10 ticks a second), shared by all players in turn. A new zone is generated in full in one frame, so several players exploring at once used to cost one zone each in the same frame. 0 = one per player per tick, as before.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); serverTargetFps = config.Bind("Performance", "ServerTargetFps", 60, new ConfigDescription("Frame rate the server aims for. The game sets a dedicated server to 30, so even with time to spare a frame waits 33 ms; every reaction to a player (a hit, a felled tree) waits for a server frame. 60 halves that wait whenever the server has the headroom, and costs nothing when it has not. Needs a restart. 0 keeps the game's 30.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 240), Array.Empty())); performanceStatsMinutes = config.Bind("Performance", "StatsIntervalMinutes", 5, "Every this many minutes, log frame times, physics steps per frame, the cost of sending world updates and of generating zones. 0 disables."); fixSaveClientChanges = config.Bind("Fixes", "SaveClientChanges", true, "Mark a world chunk as changed when a player's own change to an object arrives, so the next save writes it. Valheim 1.0 only rewrites changed chunks and does not count changes received from players, so what a player just built or moved could be missing after a restart."); fixDungeonLoadGuard = config.Bind("Fixes", "DungeonLoadGuard", true, "Keep a dungeon whose room bundle fails to load from wedging its zone for good. When Unity refuses a bundle, the game still reports the load as done and then throws, the dungeon never hears back and its zone stays flagged as loading. A vanilla dedicated server never loads dungeons, this mod does. The guard takes over a bundle Unity says is already loaded, reports a load that really failed as failed, and lets such a dungeon go so its zone keeps working and it is tried again next time."); compatVcpSpawnQueue = config.Bind("Compat", "ValheimCommunityPatchSpawnQueue", false, "Only matters with ValheimCommunityPatch installed. Its spawn queue replaces ZNetScene.CreateObjectsSorted and orders new objects by the server's reference position, which on a dedicated server is the world origin, so this mod's ordering by the nearest player never runs. Off: that patch of ValheimCommunityPatch is removed when the world starts. On: it is kept."); compatVcpUnload = config.Bind("Compat", "ValheimCommunityPatchUnload", false, "Only matters with ValheimCommunityPatch installed. Its zone-diff unload replaces ZNetScene.RemoveObjects and, whenever the object lists look untouched, drops everything outside the simulation distance of the server's reference position -- the world origin on a dedicated server. With players whose areas do not overlap, objects around them are then destroyed and created again on every pass (dungeons reloading dozens of times a second, doors, beds and items that cannot be used). Off: that patch of ValheimCommunityPatch is removed when the world starts. On: it is kept, and this mod marks its object lists as edited on every pass so that patch takes the game's own unload check."); fixTeleportGhosts = config.Bind("Fixes", "TeleportGhosts", true, "Tell the players near the old spot to drop a player who teleported away. Valheim 1.0 checks whether an object left their area before it stores the new position, so the teleported player stayed there for them, frozen, until they next crossed a zone line."); } private static void RemoveRetiredSettings(ConfigFile config) { bool saveOnConfigSet = config.SaveOnConfigSet; if (saveOnConfigSet) { config.SaveOnConfigSet = false; } try { RemoveSection("CharacterGuard", new string[8] { "Enabled", "NewCharacters", "NewCharacterAction", "ChangedAway", "ExemptAdmins", "FreshEvent", "NewCharacterMessage", "ChangedAwayMessage" }); RemoveSection("ItemLedger", new string[9] { "Mode", "Items", "ExtraItems", "ExcludeItems", "LogAllMovements", "ExemptAdmins", "GraceHours", "Message", "AltarMessage" }); } finally { if (saveOnConfigSet) { config.SaveOnConfigSet = true; } } void RemoveSection(string section, string[] keys) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown foreach (string text in keys) { ConfigDefinition val = new ConfigDefinition(section, text); config.Remove(val); config.Bind(val, string.Empty, (ConfigDescription)null); config.Remove(val); } } } } } namespace Serverbound.Compatibility { public static class CompatibilityInstaller { internal static Action Info = delegate { }; internal static Action Warning = delegate { }; internal static Action Error = delegate { }; internal const string LegacyGuid = "MVP.Valheim_Serverside_Simulations"; internal static void CheckInstallation(Func resolve, Func version, bool dedicated = true) { if (dedicated && resolve("org.bepinex.plugins.valheim_plus") != null) { throw new NotSupportedException("ValheimPlus is outside the audited compatibility matrix. Serverbound simulation cannot start with it until its implementation is validated."); } if (resolve("MVP.Valheim_Serverside_Simulations") != null || Harmony.GetAllPatchedMethods().SelectMany(SimulationPatch.All).Any((Patch p) => p.owner == "MVP.Valheim_Serverside_Simulations" || p.owner.StartsWith("MVP.Valheim_Serverside_Simulations.", StringComparison.Ordinal))) { throw new NotSupportedException("Remove predecessor simulation DLLs before loading Serverbound. Competing ownership patches are unsafe."); } Version version2 = version("DeepNorthCompat"); if (version2 != null && version2 < new Version(1, 2, 0)) { throw new NotSupportedException("Upgrade DeepNorthCompat to 1.2.0 or later on every peer. Older builds install competing simulation and skill hooks."); } if (Harmony.GetAllPatchedMethods().SelectMany(SimulationPatch.All).Any((Patch p) => p.owner == "DeepNorthCompat.Simulation" || p.owner == "DeepNorthCompat.OwnerSkills")) { throw new NotSupportedException("Obsolete DeepNorthCompat simulation/skill hooks remain installed. Stop and replace the old package."); } } public static void Install(Func resolve, Action info, Action warning, Action error) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) Info = info; Warning = warning; Error = error; SimulationPatch.Prepare(resolve); try { OwnerSkillPatch.Prepare(resolve("MidnightsFX.ImpactfulSkills")); } catch (Exception ex) { new Harmony("org.serverbound.valheim.OwnerSkills").UnpatchSelf(); Error("OwnerSkills: NOT APPLIED; optional integration rejected. " + ex); } } public static void Verify() { SimulationPatch.Verify(); OwnerSkillPatch.Verify(); } } internal static class ExpectedBuilds { internal static readonly string[] ImpactfulSkills = new string[2] { "ADE4FD2943A886B9B90C0241CB77EC65A97C1840EF39A906B58BAF3925086706", "D6E0844F3DADE42B23C33A678EB6E3D6DAA288DCC4187F497B8BB2A1F3CF47BA" }; internal static readonly string[] Valheim = new string[2] { "96CFC004F7F4A6F30D070BEF39EAFD79C466A137121C4665A2F19FB9C15C6127", "25A0A107DCE4D834C44C2B72D0EAFD5CB7793933BDA81816ACCFA1EA9543DACE" }; internal static readonly string[] Server = new string[4] { "7CAB9B49D31EC064591CA80402DD35C566E03B7297CFB7BF4696C38DA4E24D8B", "50035055F9B158A025CACD25E038B603943F7C2A465DA3021707B5F1E44E39FD", "0DFC7E81436F822121148EFED859BA1E661D58B9484BD45E3B6F33B6DD86BFAD", "E7220DC5D9CF9D38270E751352D94918308E59CCA86E56C4BDA0210016C847FA" }; } public static class Guard { internal static bool KnownServerBuild() { using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(typeof(ZNet).Assembly.Location); return ExpectedBuilds.Server.Contains(BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "")); } public static void Build(Assembly assembly, params string[] expectedHashes) { using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(assembly.Location); string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", ""); if (!expectedHashes.Contains(text)) { throw new NotSupportedException(assembly.GetName().Name + " build changed; expected " + string.Join(" or ", expectedHashes) + ", found " + text + ". Re-audit required."); } } internal static Type Type(Assembly assembly, string name) { return assembly.GetType(name, throwOnError: true); } internal static MethodInfo Method(Type type, string name, Type returnType, params Type[] parameters) { MethodInfo method = type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameters, null); if (method == null || method.ReturnType != returnType) { throw new MissingMethodException(type.FullName, name); } return method; } internal static HarmonyMethod Hook(Type type, string name, int priority = 400) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return new HarmonyMethod(AccessTools.DeclaredMethod(type, name, (Type[])null, (Type[])null)) { priority = priority }; } internal static void Registered(MethodBase target) { CompatibilityInstaller.Info("Registered: " + Name(target)); } internal static string Name(MethodBase target) { return target.DeclaringType.Assembly.GetName().Name + ":" + target.DeclaringType.FullName + "." + target.Name; } } internal static class OwnerSkillPatch { internal sealed class Scope : IDisposable { private readonly Player? previous; internal Scope(Player? player) { previous = current; current = player; } public void Dispose() { current = previous; } } internal const string Owner = "org.serverbound.valheim.OwnerSkills"; private const string GrantRpc = "Serverbound.OwnerSkillXP"; private const string HelloRpc = "Serverbound.OwnerSkillHello"; private const string StatusRpc = "Serverbound.OwnerSkillStatus"; private static readonly int ProtocolKey = StringExtensionMethods.GetStableHashCode("Serverbound.skillProtocol"); private static readonly int MiningKey = StringExtensionMethods.GetStableHashCode("Serverbound.mining"); private static readonly int WoodKey = StringExtensionMethods.GetStableHashCode("Serverbound.woodcutting"); private static readonly int AnimalKey = StringExtensionMethods.GetStableHashCode("Serverbound.animalHandling"); private static readonly int FarmingLevelKey = StringExtensionMethods.GetStableHashCode("Serverbound.level.farming"); private static readonly int AnimalLevelKey = StringExtensionMethods.GetStableHashCode("Serverbound.level.animalHandling"); private static readonly int VoyagingLevelKey = StringExtensionMethods.GetStableHashCode("Serverbound.level.voyaging"); private const float FlagRange = 30f; private static readonly HashSet<(ZDOID, int)> minedAreas = new HashSet<(ZDOID, int)>(); private static readonly HashSet missingPlayers = new HashSet(); private static readonly ConditionalWeakTable registrations = new ConditionalWeakTable(); private static Assembly? impact; private static bool prepared; private static bool server; private static bool installed; private static bool coreOnServer; private static float nextPublish; private static float nextHello; private static FieldInfo view = null; private static FieldInfo animalSkill = null; private static FieldInfo animalRange = null; private static FieldInfo animalEnabled = null; private static FieldInfo miningEnabled = null; private static FieldInfo woodEnabled = null; private static FieldInfo voyagingSkill = null; private static MethodInfo treeBonus = null; [ThreadStatic] private static Player? current; private static readonly FieldInfo shipPlayers = AccessTools.Field(typeof(Ship), "m_players"); internal static bool Enabled { get { if (server && installed) { return SimulationPatch.Active; } return false; } } private static SkillType Animal => (SkillType)animalSkill.GetValue(null); private static SkillType Voyaging => (SkillType)voyagingSkill.GetValue(null); private static float LootRange => ((ConfigEntry)animalRange.GetValue(null)).Value; internal static void Prepare(Assembly? skills) { //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown prepared = false; installed = false; impact = null; if (skills == null) { CompatibilityInstaller.Info("OwnerSkills: optional mod absent; inactive."); return; } Guard.Build(skills, ExpectedBuilds.ImpactfulSkills); try { Guard.Build(typeof(ZNet).Assembly, ExpectedBuilds.Valheim); server = false; } catch (NotSupportedException) { Guard.Build(typeof(ZNet).Assembly, ExpectedBuilds.Server); server = true; } if (server && !SimulationPatch.CoreActive()) { CompatibilityInstaller.Info("OwnerSkills: dedicated Core absent; inactive on server."); return; } impact = skills; view = AccessTools.Field(typeof(Character), "m_nview"); animalSkill = AccessTools.Field(Guard.Type(skills, "ImpactfulSkills.patches.AnimalWhisper"), "AnimalHandling"); animalRange = AccessTools.Field(Guard.Type(skills, "ImpactfulSkills.ValConfig"), "AnimalHandlingLootRange"); animalEnabled = AccessTools.Field(Guard.Type(skills, "ImpactfulSkills.ValConfig"), "EnableAnimalWhisper"); miningEnabled = AccessTools.Field(Guard.Type(skills, "ImpactfulSkills.ValConfig"), "EnableMining"); woodEnabled = AccessTools.Field(Guard.Type(skills, "ImpactfulSkills.ValConfig"), "EnableWoodcutting"); voyagingSkill = AccessTools.Field(Guard.Type(skills, "ImpactfulSkills.patches.Voyaging"), "VoyagingSkill"); treeBonus = Guard.Method(Guard.Type(skills, "ImpactfulSkills.patches.Woodcutting"), "IncreaseTreeDrops", typeof(void), typeof(TreeBase)); Harmony val = new Harmony("org.serverbound.valheim.OwnerSkills"); val.Patch((MethodBase)Guard.Method(typeof(Player), "SetLocalPlayer", typeof(void)), (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "Publish"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)Guard.Method(typeof(Player), "FixedUpdate", typeof(void)), (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "PublishPeriodic"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); string[] array = new string[4] { "RaiseSkill", "LowerAllSkills", "Load", "CheatRaiseSkill" }; foreach (string text in array) { val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Skills), text, (Type[])null, (Type[])null), (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "PublishAfterSkillChange"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } val.Patch((MethodBase)AccessTools.Constructor(typeof(ZRoutedRpc), new Type[1] { typeof(bool) }, false), (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "RegisterRpc"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Shutdown", (Type[])null, (Type[])null), (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "Reset"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo = AccessTools.DeclaredMethod(Guard.Type(skills, "ImpactfulSkills.patches.Mining+Minerock5DestroyPatch"), "Postfix", (Type[])null, (Type[])null); val.Patch((MethodBase)methodInfo, Guard.Hook(typeof(OwnerSkillPatch), "OncePerMiningArea"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); prepared = true; CompatibilityInstaller.Info("OwnerSkills: state publisher and XP RPC registered; server integration awaits Core verification."); } internal static void Verify() { //IL_0786: 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_003a: Expected O, but got Unknown if (!prepared || !server || !SimulationPatch.Active || impact == null) { return; } prepared = false; List<(MethodBase, Patch, HarmonyPatchType)> list = new List<(MethodBase, Patch, HarmonyPatchType)>(); try { Harmony val = new Harmony("org.serverbound.valheim.OwnerSkills"); (Type, string, Type[])[] array = new(Type, string, Type[])[5] { (typeof(MineRock), "RPC_Hit", new Type[3] { typeof(long), typeof(HitData), typeof(int) }), (typeof(MineRock5), "RPC_Damage", new Type[3] { typeof(long), typeof(HitData), typeof(int) }), (typeof(TreeBase), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }), (typeof(TreeLog), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }), (typeof(Destructible), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }) }; for (int i = 0; i < array.Length; i++) { (Type, string, Type[]) tuple = array[i]; Type item = tuple.Item1; string item2 = tuple.Item2; Type[] item3 = tuple.Item3; MethodInfo methodInfo = Guard.Method(item, item2, typeof(void), item3); val.Patch((MethodBase)methodInfo, Guard.Hook(typeof(OwnerSkillPatch), "BeginDamage", 800), (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "EndScope"), (HarmonyMethod)null); } (Type, string, string)[] array2 = new(Type, string, string)[3] { (typeof(Tameable), "DecreaseRemainingTime", "BeginTaming"), (typeof(CharacterDrop), "GenerateDropList", "BeginLoot"), (typeof(Tameable), "OnDeath", "BeginSlaughter") }; for (int i = 0; i < array2.Length; i++) { var (type, text, name) = array2[i]; val.Patch((MethodBase)AccessTools.DeclaredMethod(type, text, (Type[])null, (Type[])null), Guard.Hook(typeof(OwnerSkillPatch), name, 800), (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "EndScope"), (HarmonyMethod)null); } (MethodInfo, string)[] array3 = new(MethodInfo, string)[4] { (Guard.Method(typeof(Beehive), "CheckBiome", typeof(bool)), "BeginHive"), (Guard.Method(typeof(Plant), "UpdateHealth", typeof(void), typeof(double)), "BeginPlant"), (Guard.Method(typeof(WearNTear), "RPC_Damage", typeof(void), typeof(long), typeof(HitData)), "BeginShipDamage"), (AccessTools.DeclaredMethod(typeof(ImpactEffect), "OnCollisionEnter", (Type[])null, (Type[])null), "BeginShipImpact") }; for (int i = 0; i < array3.Length; i++) { var (methodInfo2, name2) = array3[i]; val.Patch((MethodBase)methodInfo2, Guard.Hook(typeof(OwnerSkillPatch), name2, 800), (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "EndScope"), (HarmonyMethod)null); } string[] array4 = new string[8] { "ImpactfulSkills.patches.Mining+MinerockDropsPatch", "ImpactfulSkills.patches.Mining+Minerock5DestroyPatch", "ImpactfulSkills.patches.Mining+IncreaseDropsFromDestructibleRock", "ImpactfulSkills.patches.Woodcutting+IncreaseDropsFromTree", "ImpactfulSkills.patches.Woodcutting+IncreaseDropsFromDestructibleTree", "ImpactfulSkills.patches.AnimalWhisper+IncreaseTamingSpeed", "ImpactfulSkills.patches.AnimalWhisper+ScaleTamedAnimalLoot", "ImpactfulSkills.patches.AnimalWhisper+TamedAnimalSlaughterXP" }; foreach (string text2 in array4) { MethodInfo methodInfo3 = AccessTools.DeclaredMethod(Guard.Type(impact, text2), text2.Contains("MinerockDropsPatch") ? "ApplyIncreasedMiningDrops" : ((text2.Contains("IncreaseDropsFromTree") || text2.Contains("Minerock5DestroyPatch") || text2.Contains("ScaleTamedAnimalLoot") || text2.Contains("TamedAnimalSlaughterXP")) ? "Postfix" : "Prefix"), (Type[])null, (Type[])null); val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "RewriteContext"), (HarmonyMethod)null, (HarmonyMethod)null); } array4 = new string[2] { "Mining", "Woodcutting" }; foreach (string text3 in array4) { val.Patch((MethodBase)AccessTools.DeclaredMethod(Guard.Type(impact, "ImpactfulSkills.patches." + text3), (text3 == "Mining") ? "IncreaseMiningDrops" : "IncreaseWoodDrops", (Type[])null, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "RewriteContext"), (HarmonyMethod)null, (HarmonyMethod)null); } (string, string)[] array5 = new(string, string)[4] { ("AnimalWhisper+BehivesInAnyBiome", "Prefix"), ("PlantBiome", "IsBiomeUnrestricted"), ("Voyaging+ShipDamageReduction", "Prefix"), ("Voyaging+ShipDamageImpactReduction", "ImpactDamagesSelf") }; for (int i = 0; i < array5.Length; i++) { var (text4, text5) = array5[i]; val.Patch((MethodBase)AccessTools.DeclaredMethod(Guard.Type(impact, "ImpactfulSkills.patches." + text4), text5, (Type[])null, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "RewriteContext"), (HarmonyMethod)null, (HarmonyMethod)null); } val.Patch((MethodBase)treeBonus, Guard.Hook(typeof(OwnerSkillPatch), "HasTreeActor"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); (MethodBase, Patch, HarmonyPatchType) tuple5 = SimulationPatch.ExactHook(typeof(TreeBase), "RPC_Damage", AccessTools.DeclaredMethod(Guard.Type(impact, "ImpactfulSkills.patches.Woodcutting+DamageHandler_Apply_Patch"), "Transpiler", (Type[])null, (Type[])null), "MidnightsFX.ImpactfulSkills", (HarmonyPatchType)3); FixTreeAnchor(PatchProcessor.GetOriginalInstructions(tuple5.Item1, (ILGenerator)null)).ToArray(); list.Add(tuple5); val.Unpatch(tuple5.Item1, tuple5.Item2.PatchMethod); val.Patch(tuple5.Item1, (HarmonyMethod)null, (HarmonyMethod)null, Guard.Hook(typeof(OwnerSkillPatch), "FixTreeAnchor"), (HarmonyMethod)null, (HarmonyMethod)null); SimulationPatch.RememberRemoved(tuple5); installed = true; CompatibilityInstaller.Info("OwnerSkills: APPLIED; owner-side calculations use replicated actor skills; original formulas and server ownership retained."); } catch (Exception ex) { installed = false; try { new Harmony("org.serverbound.valheim.OwnerSkills").UnpatchSelf(); foreach (var item4 in list) { SimulationPatch.Restore(item4); } } catch (Exception ex2) { CompatibilityInstaller.Error("OwnerSkills: rollback failed; inspect Harmony state before launch. " + ex2); } CompatibilityInstaller.Error("OwnerSkills: NOT APPLIED; required launch gate failed. " + ex); } } private static bool Configured(FieldInfo field) { return ((ConfigEntry)field.GetValue(null)).Value; } private static ZDO? PlayerZdo(Player player) { object? value = view.GetValue(player); object? obj = ((value is ZNetView) ? value : null); if (obj == null) { return null; } return ((ZNetView)obj).GetZDO(); } private unsafe static int Key(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) if ((int)skill != 12) { if ((int)skill != 13) { if (skill != Animal) { throw new NotSupportedException("Unexpected owner-side skill " + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString()); } return AnimalKey; } return WoodKey; } return MiningKey; } private unsafe static int LevelKey(SkillType skill) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: 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_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if ((int)skill != 106) { if (skill != Animal) { if (skill != Voyaging) { throw new NotSupportedException("Unexpected owner-side skill level " + ((object)(*(SkillType*)(&skill))/*cast due to .constrained prefix*/).ToString()); } return VoyagingLevelKey; } return AnimalLevelKey; } return FarmingLevelKey; } private static void Publish(Player __instance) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected I4, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected I4, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected I4, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if (server || !coreOnServer || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } ZDO val = PlayerZdo(__instance); if (val == null || val.GetOwner() != ZNet.GetUID()) { return; } SkillType[] array = (SkillType[])(object)new SkillType[3] { (SkillType)12, (SkillType)13, (SkillType)(int)Animal }; foreach (SkillType val2 in array) { float skillFactor = ((Character)__instance).GetSkillFactor(val2); if (!SkillState.ValidFactor(skillFactor)) { throw new InvalidOperationException("Invalid local skill factor."); } val.Set(Key(val2), skillFactor); } array = (SkillType[])(object)new SkillType[3] { (SkillType)106, (SkillType)(int)Animal, (SkillType)(int)Voyaging }; foreach (SkillType val3 in array) { float skillLevel = ((Character)__instance).GetSkillLevel(val3); if (!SkillState.ValidLevel(skillLevel)) { throw new InvalidOperationException("Invalid local skill level."); } val.Set(LevelKey(val3), skillLevel); } val.Set(ProtocolKey, 1, false); } private static void PublishPeriodic(Player __instance) { if (!server && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !(Time.unscaledTime < nextPublish)) { if (ZRoutedRpc.instance != null && Time.unscaledTime >= nextHello) { nextHello = Time.unscaledTime + 5f; ZRoutedRpc.instance.InvokeRoutedRPC(ServerId(), "Serverbound.OwnerSkillHello", Array.Empty()); } nextPublish = Time.unscaledTime + 0.25f; Publish(__instance); } } private static void PublishAfterSkillChange(Skills __instance) { Player localPlayer = Player.m_localPlayer; if (!server && Object.op_Implicit((Object)(object)localPlayer) && (Object)(object)((Character)localPlayer).GetSkills() == (Object)(object)__instance) { Publish(localPlayer); } } internal static Player? Actor() { if (!Enabled) { return Player.m_localPlayer; } return current; } internal static float Factor(Character player, SkillType skill) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_0035: Expected O, but got Unknown //IL_0018: 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) if (!Enabled) { return player.GetSkillFactor(skill); } if (skill == Voyaging) { return Mathf.Clamp01(Level(player, skill) / 100f); } ZDO val = PlayerZdo((Player)player); if (val == null) { throw new InvalidOperationException("Player has no replicated skill state."); } return SkillState.RequireFactor(val.GetInt(ProtocolKey, 0), val.GetFloat(Key(skill), float.NaN)); } internal static float Level(Character player, SkillType skill) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0008: 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) if (!Enabled) { return player.GetSkillLevel(skill); } ZDO val = PlayerZdo((Player)player); if (val == null) { throw new InvalidOperationException("Player has no replicated skill state."); } return SkillState.RequireLevel(val.GetInt(ProtocolKey, 0), val.GetFloat(LevelKey(skill), float.NaN)); } private static void Raise(Character player, SkillType skill, float amount) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected I4, but got Unknown //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (!Enabled) { player.RaiseSkill(skill, amount); return; } ZPackage val = new ZPackage(); val.Write(player.GetZDOID()); val.Write((int)skill); val.Write(amount); ZRoutedRpc.instance.InvokeRoutedRPC(player.GetOwner(), "Serverbound.OwnerSkillXP", new object[1] { val }); } private static long ServerId() { return ZRoutedRpc.instance.GetServerPeerID(); } private static void RegisterRpc(ZRoutedRpc __instance) { if (!registrations.TryGetValue(__instance, out object _)) { __instance.Register("Serverbound.OwnerSkillXP", (Action)GrantXP); __instance.Register("Serverbound.OwnerSkillHello", (Action)Hello); __instance.Register("Serverbound.OwnerSkillStatus", (Action)Status); registrations.Add(__instance, new object()); } } private static void Hello(long sender) { if (server && sender != 0L && ZRoutedRpc.instance.m_peers.Any((ZNetPeer p) => p.m_uid == sender && p.IsReady())) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "Serverbound.OwnerSkillStatus", new object[1] { Enabled }); } } private static void Status(long sender, bool active) { if (!server && sender != 0L && sender == ServerId()) { coreOnServer = active; if (active && Object.op_Implicit((Object)(object)Player.m_localPlayer)) { Publish(Player.m_localPlayer); } } } private static void GrantXP(long sender, ZPackage package) { //IL_0022: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_0058: Invalid comparison between I4 and Unknown if (!server && coreOnServer && ZRoutedRpc.instance != null && sender != 0L && sender == ServerId()) { ZDOID val = package.ReadZDOID(); int num = package.ReadInt(); float num2 = package.ReadSingle(); Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer) && val == ((Character)localPlayer).GetZDOID() && num == (int)Animal && num2 >= 0f && !float.IsNaN(num2) && !float.IsInfinity(num2)) { ((Character)localPlayer).RaiseSkill((SkillType)num, num2); } } } private static Coroutine RunCoroutine(MonoBehaviour original, IEnumerator routine) { if (!Enabled) { return original.StartCoroutine(routine); } return ((MonoBehaviour)Plugin.instance).StartCoroutine(routine); } internal static IEnumerable RewriteContext(IEnumerable instructions) { FieldInfo local = AccessTools.Field(typeof(Player), "m_localPlayer"); MethodInfo factor = AccessTools.Method(typeof(Character), "GetSkillFactor", (Type[])null, (Type[])null); MethodInfo level = AccessTools.Method(typeof(Character), "GetSkillLevel", (Type[])null, (Type[])null); MethodInfo raise = AccessTools.Method(typeof(Character), "RaiseSkill", (Type[])null, (Type[])null); MethodInfo coroutine = AccessTools.Method(typeof(MonoBehaviour), "StartCoroutine", new Type[1] { typeof(IEnumerator) }, (Type[])null); foreach (CodeInstruction instruction in instructions) { CodeInstruction val = new CodeInstruction(instruction); if (val.opcode == OpCodes.Ldsfld && object.Equals(val.operand, local)) { val.opcode = OpCodes.Call; val.operand = AccessTools.Method(typeof(OwnerSkillPatch), "Actor", (Type[])null, (Type[])null); } else if (CodeInstructionExtensions.Calls(val, factor)) { val.opcode = OpCodes.Call; val.operand = AccessTools.Method(typeof(OwnerSkillPatch), "Factor", (Type[])null, (Type[])null); } else if (CodeInstructionExtensions.Calls(val, level)) { val.opcode = OpCodes.Call; val.operand = AccessTools.Method(typeof(OwnerSkillPatch), "Level", (Type[])null, (Type[])null); } else if (CodeInstructionExtensions.Calls(val, raise)) { val.opcode = OpCodes.Call; val.operand = AccessTools.Method(typeof(OwnerSkillPatch), "Raise", (Type[])null, (Type[])null); } else if (CodeInstructionExtensions.Calls(val, coroutine)) { val.opcode = OpCodes.Call; val.operand = AccessTools.Method(typeof(OwnerSkillPatch), "RunCoroutine", (Type[])null, (Type[])null); } yield return val; } } private static void BeginDamage(Component __instance, HitData __1, out Scope? __state) { __state = null; if (Enabled) { Character obj = ((__1 != null) ? __1.GetAttacker() : null); Player val = (Player)(object)((obj is Player) ? obj : null); bool flag = __instance is MineRock || __instance is MineRock5; bool flag2 = __instance is TreeBase || __instance is TreeLog; if (__instance is Destructible) { int num = (int)AccessTools.Field(typeof(Destructible), "m_destructibleType").GetValue(__instance); flag = num == 1; flag2 = num == 2; } if ((Object)(object)val != (Object)null && ((flag && Configured(miningEnabled) && !Ready(val, (SkillType)12)) || (flag2 && Configured(woodEnabled) && !Ready(val, (SkillType)13)))) { val = null; } __state = new Scope(val); } } private static bool Ready(Player player, SkillType skill) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000e: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) try { Factor((Character)(object)player, skill); missingPlayers.Remove(((Character)player).GetZDOID()); return true; } catch (InvalidOperationException) { if (missingPlayers.Add(((Character)player).GetZDOID())) { CompatibilityInstaller.Warning("OwnerSkills: no valid skill state from player " + ((object)((Character)player).GetZDOID()/*cast due to .constrained prefix*/).ToString() + "; their owner-side ImpactfulSkills bonuses are skipped. Check the client's matching compatibility package."); } return false; } } private static Player? Nearby(Component target, float range) { return SkillState.Closest(Player.GetAllPlayers(), delegate(Player p) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) Vector3 val = ((Component)p).transform.position - target.transform.position; return ((Vector3)(ref val)).sqrMagnitude; }, (Player p) => ((Character)p).GetOwner(), range); } private static void BeginTaming(Tameable __instance, out Scope? __state) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) __state = null; if (Enabled && Configured(animalEnabled)) { Player val = Nearby((Component)(object)__instance, 30f); __state = new Scope(((Object)(object)val != (Object)null && Ready(val, Animal)) ? val : null); } } private static void BeginLoot(CharacterDrop __instance, out Scope? __state) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) __state = null; if (Enabled && Configured(animalEnabled)) { Character component = ((Component)__instance).GetComponent(); Player val = ((Object.op_Implicit((Object)(object)component) && component.IsTamed()) ? Nearby((Component)(object)__instance, LootRange) : null); __state = new Scope(((Object)(object)val != (Object)null && Ready(val, Animal)) ? val : null); } } private static void BeginSlaughter(Tameable __instance, out Scope? __state) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) __state = null; if (Enabled && Configured(animalEnabled)) { Character component = ((Component)__instance).GetComponent(); Player val = ((Object.op_Implicit((Object)(object)component) && component.IsTamed()) ? Nearby((Component)(object)__instance, LootRange) : null); __state = new Scope(((Object)(object)val != (Object)null && Ready(val, Animal)) ? val : null); } } private static void EndScope(Scope? __state) { __state?.Dispose(); } private static bool HasTreeActor() { if (Enabled) { return (Object)(object)current != (Object)null; } return true; } private static bool HasLevel(Player player, SkillType skill) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { Level((Character)(object)player, skill); return true; } catch (InvalidOperationException) { return false; } } private static Player? Best(IEnumerable players, SkillType skill) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return (from p in players where Object.op_Implicit((Object)(object)p) && HasLevel(p, skill) orderby Level((Character)(object)p, skill) descending, ((Character)p).GetOwner() select p).FirstOrDefault(); } private static IEnumerable Near(Component target, float range) { return Player.GetAllPlayers().Where(delegate(Player p) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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) Vector3 val = ((Component)p).transform.position - target.transform.position; return ((Vector3)(ref val)).sqrMagnitude <= range * range; }); } private static void BeginHive(Beehive __instance, out Scope? __state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) __state = (Enabled ? new Scope(Best(Near((Component)(object)__instance, 30f), Animal)) : null); } private static void BeginPlant(Plant __instance, out Scope? __state) { __state = (Enabled ? new Scope(Best(Near((Component)(object)__instance, 30f), (SkillType)106)) : null); } private static void BeginShipDamage(WearNTear __instance, out Scope? __state) { __state = (Enabled ? new Scope(Aboard(((Component)__instance).GetComponent())) : null); } private static void BeginShipImpact(ImpactEffect __instance, out Scope? __state) { __state = (Enabled ? new Scope(Aboard(((Component)__instance).GetComponent())) : null); } private static Player? Aboard(Ship? ship) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)ship)) { return null; } return Best((List)shipPlayers.GetValue(ship), Voyaging); } private static bool OncePerMiningArea(MineRock5 __0, int __2, float __3) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) if ((!server && !coreOnServer) || (server && !Enabled)) { return true; } object? value = AccessTools.Field(typeof(MineRock5), "m_nview").GetValue(__0); object? obj = ((value is ZNetView) ? value : null); ZDO val = ((obj != null) ? ((ZNetView)obj).GetZDO() : null); if (val == null || val.GetOwner() != ServerId()) { return true; } if (!server) { return false; } if (__3 <= 0f && (Object)(object)current != (Object)null) { return minedAreas.Add((val.m_uid, __2)); } return false; } private static void TreeDrops(TreeBase tree) { treeBonus.Invoke(null, new object[1] { tree }); } internal static IEnumerable FixTreeAnchor(IEnumerable instructions) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown List codes = instructions.Select((Func)((CodeInstruction c) => new CodeInstruction(c))).ToList(); if (codes.Count < 3) { throw new NotSupportedException("TreeBase damage body changed; refusing to move drop logic."); } int[] array = (from i in Enumerable.Range(0, codes.Count - 2) where codes[i].opcode == OpCodes.Ldloc_0 && codes[i + 1].opcode == OpCodes.Ldc_R4 && object.Equals(codes[i + 1].operand, 0f) && (codes[i + 2].opcode == OpCodes.Bgt_Un || codes[i + 2].opcode == OpCodes.Bgt_Un_S) select i).ToArray(); if (array.Length != 2) { throw new NotSupportedException("TreeBase damage health branches changed; refusing to move drop logic."); } codes.InsertRange(array[1] + 3, (IEnumerable)(object)new CodeInstruction[2] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), CodeInstruction.Call(typeof(OwnerSkillPatch), "TreeDrops", (Type[])null, (Type[])null) }); return codes; } private static void Reset() { minedAreas.Clear(); missingPlayers.Clear(); nextPublish = 0f; nextHello = 0f; current = null; coreOnServer = false; } } internal static class SimulationPatch { internal const string ServerboundGuid = "org.serverbound.valheim"; internal const string CoreOwner = "org.serverbound.valheim.Core"; private const string CompatOwner = "org.serverbound.valheim.Compat_ValheimCommunityPatch"; private const string VpoGuid = "dev.ontrigger.vpo"; private const string VcpGuid = "MidnightsFX.ValheimCommunityPatch"; private const string VpoHash = "614CD643343E2E2D182BA4B50AA8C96D8FEDB8E09A16C6AA0018BAD222D6EE70"; private const string VcpHash = "E48804F2280878B1BB30393C57C354EFDAA68F106A0B2C77F150C113A25EAA68"; private static Assembly? vpo; private static Assembly? vcp; private static readonly List<(MethodBase Target, Patch Hook, HarmonyPatchType Kind)> takenOver = new List<(MethodBase, Patch, HarmonyPatchType)>(); private static bool pending; internal static Assembly? VcpAssembly => vcp; internal static bool Active { get; private set; } internal static void Prepare(Func resolve) { Active = false; vpo = resolve("dev.ontrigger.vpo"); vcp = resolve("MidnightsFX.ValheimCommunityPatch"); takenOver.Clear(); pending = true; } internal static void Deactivate() { Active = false; pending = false; } internal static bool CoreActive() { (Type, string, string)[] array = new(Type, string, string)[3] { (typeof(ZNetScene), "CreateDestroyObjects", "CreateDestroyObjects_Patch"), (typeof(ZDOMan), "ReleaseNearbyZDOS", "ZDOMan_ReleaseNearbyZDOS_Patch"), (typeof(ZoneSystem), "Update", "ZoneSystem_Update_Patch") }; for (int i = 0; i < array.Length; i++) { (Type, string, string) tuple = array[i]; Type item = tuple.Item1; string item2 = tuple.Item2; string item3 = tuple.Item3; Type nestedType = typeof(Core).GetNestedType(item3); MethodInfo hook = AccessTools.DeclaredMethod(nestedType, "Prefix", (Type[])null, (Type[])null); Patches patchInfo = Harmony.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(item, item2, (Type[])null, (Type[])null)); if (patchInfo == null || !patchInfo.Prefixes.Any((Patch p) => p.owner == "org.serverbound.valheim.Core" && p.PatchMethod == hook)) { return false; } } return true; } internal static void Verify() { //IL_0527: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_046e: Unknown result type (might be due to invalid IL or missing references) //IL_048c: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Expected O, but got Unknown if (!pending) { return; } pending = false; List<(MethodBase, Patch, HarmonyPatchType)> list = new List<(MethodBase, Patch, HarmonyPatchType)>(); (MethodInfo, MethodInfo)? tuple = null; bool flag = false; try { if (!CoreActive()) { CompatibilityInstaller.Info("Simulation: Core not active on this process; object-management patches retained."); return; } flag = true; Guard.Build(typeof(ZNet).Assembly, ExpectedBuilds.Server); List<(MethodBase, Patch, HarmonyPatchType)> list2 = new List<(MethodBase, Patch, HarmonyPatchType)>(); (MethodInfo, MethodInfo)? tuple2 = null; if (vpo != null) { Guard.Build(vpo, "614CD643343E2E2D182BA4B50AA8C96D8FEDB8E09A16C6AA0018BAD222D6EE70"); Type type = Guard.Type(vpo, "ValheimPerformanceOptimizations.Patches.ObjectManagement.ZNetSceneObjectManagementPatch"); Type type2 = Guard.Type(vpo, "ValheimPerformanceOptimizations.Patches.ObjectManagement.ZDOManReleaseNearbyPatch"); (Type, string, string, HarmonyPatchType)[] array = new(Type, string, string, HarmonyPatchType)[8] { (typeof(ZNetScene), "CreateDestroyObjects", "ZNetScene_CreateDestroyObjects_Prefix", (HarmonyPatchType)1), (typeof(ZDOMan), "AddToSector", "ZDOMan_AddToSector_Postfix", (HarmonyPatchType)2), (typeof(ZDO), "InvalidateSector", "ZDO_InvalidateSector_Postfix", (HarmonyPatchType)2), (typeof(ZDOMan), "HandleDestroyedZDO", "ZDOMan_HandleDestroyedZDO_Postfix", (HarmonyPatchType)2), (typeof(ZDO), "Deserialize", "ZDO_Deserialize_Postfix", (HarmonyPatchType)2), (typeof(ZNetScene), "AddInstance", "ZNetScene_AddInstance_Postfix", (HarmonyPatchType)2), (typeof(ZNetScene), "Destroy", "ZNetScene_Destroy_Prefix", (HarmonyPatchType)1), (typeof(ZNetScene), "Shutdown", "ZNetScene_Shutdown_Prefix", (HarmonyPatchType)1) }; for (int i = 0; i < array.Length; i++) { var (type3, name, text, kind) = array[i]; list2.Add(ExactHook(type3, name, AccessTools.DeclaredMethod(type, text, (Type[])null, (Type[])null), "dev.ontrigger.vpo", kind)); } list2.Add(ExactHook(typeof(ZDOMan), "ReleaseNearbyZDOS", AccessTools.DeclaredMethod(type2, "Prefix", (Type[])null, (Type[])null), "dev.ontrigger.vpo", (HarmonyPatchType)1)); } if (vcp != null) { Guard.Build(vcp, "E48804F2280878B1BB30393C57C354EFDAA68F106A0B2C77F150C113A25EAA68"); Type type4 = Guard.Type(vcp, "ValheimCommunityPatch.Patches.Performance.SceneIdleSkipPatch"); list2.Add(ExactHook(typeof(ZNetScene), "CreateDestroyObjects", AccessTools.DeclaredMethod(type4, "CreateDestroyObjectsPrefix", (Type[])null, (Type[])null), "MidnightsFX.ValheimCommunityPatch", (HarmonyPatchType)1)); list2.Add(ExactHook(typeof(ZNetScene), "CreateDestroyObjects", AccessTools.DeclaredMethod(type4, "CreateDestroyObjectsPostfix", (Type[])null, (Type[])null), "MidnightsFX.ValheimCommunityPatch", (HarmonyPatchType)2)); Type typeFromHandle = typeof(Compat_ValheimCommunityPatch.ZNetScene_Awake_Patch); MethodInfo methodInfo = Guard.Method(typeof(ZNetScene), "Awake", typeof(void)); MethodInfo hook = Guard.Method(typeFromHandle, "Postfix", typeof(void)); if (!All(methodInfo).Any((Patch p) => p.PatchMethod == hook)) { if (Compat_ValheimCommunityPatch.ZNetScene_Awake_Patch.s_done) { throw new NotSupportedException("Serverbound VCP takeover already ran but its registration is missing."); } tuple2 = (methodInfo, hook); } else { ExactHook(typeof(ZNetScene), "Awake", hook, "org.serverbound.valheim.Compat_ValheimCommunityPatch", (HarmonyPatchType)2); } } foreach (var hook2 in list2) { list.Add(hook2); new Harmony("org.serverbound.valheim.Simulation").Unpatch(hook2.Item1, hook2.Item2.PatchMethod); if (All(hook2.Item1).Any((Patch p) => p.PatchMethod == hook2.Item2.PatchMethod)) { throw new InvalidOperationException("Conflicting vendor hook remains after removal: " + Guard.Name(hook2.Item2.PatchMethod)); } } if (tuple2.HasValue) { tuple = tuple2; new Harmony("org.serverbound.valheim.Compat_ValheimCommunityPatch").Patch((MethodBase)tuple.Value.Item1, (HarmonyMethod)null, new HarmonyMethod(tuple.Value.Item2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ExactHook(typeof(ZNetScene), "Awake", tuple.Value.Item2, "org.serverbound.valheim.Compat_ValheimCommunityPatch", (HarmonyPatchType)2); CompatibilityInstaller.Info("Simulation: registered Serverbound's VCP takeover after chainloading."); } if (!CoreActive()) { throw new InvalidOperationException("Core changed during integration."); } takenOver.AddRange(list); Active = true; CompatibilityInstaller.Info($"Simulation: APPLIED; removed {list.Count} exact server-side VPO/VCP hooks; Core retained."); } catch (Exception ex) { if (tuple.HasValue) { try { new Harmony("org.serverbound.valheim.Compat_ValheimCommunityPatch").Unpatch((MethodBase)tuple.Value.Item1, tuple.Value.Item2); } catch (Exception ex2) { CompatibilityInstaller.Error("Simulation: Serverbound VCP registration rollback failed. " + ex2); } } foreach (var item in list) { try { Restore(item); } catch (Exception ex3) { CompatibilityInstaller.Error("Simulation: rollback failed; inspect Harmony state before launch. " + ex3); } } CompatibilityInstaller.Error("Simulation: REJECTED BUILD/REGISTRATIONS; required launch gate failed. " + ex); if (flag) { Plugin.DisableSimulation(); } } } internal static void RememberRemoved((MethodBase Target, Patch Hook, HarmonyPatchType Kind) hook) { takenOver.Add(hook); } internal static void RestoreVendors() { List list = new List(); foreach (var item2 in takenOver) { try { Restore(item2); } catch (Exception item) { list.Add(item); } } takenOver.Clear(); if (list.Count != 0) { throw new AggregateException("Vendor hook restoration failed; stop the server.", list); } } internal static IEnumerable All(MethodBase target) { Patches patchInfo = Harmony.GetPatchInfo(target); if (patchInfo != null) { return patchInfo.Prefixes.Concat(patchInfo.Postfixes).Concat(patchInfo.Transpilers).Concat(patchInfo.Finalizers) .Concat(patchInfo.ILManipulators); } return Enumerable.Empty(); } internal static (MethodBase Target, Patch Hook, HarmonyPatchType Kind) ExactHook(Type type, string name, MethodInfo method, string owner, HarmonyPatchType kind) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) return ExactHook(AccessTools.DeclaredMethod(type, name, (Type[])null, (Type[])null), method, owner, kind); } private static (MethodBase Target, Patch Hook, HarmonyPatchType Kind) ExactHook(MethodBase target, MethodInfo method, string owner, HarmonyPatchType kind) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) Patches patchInfo = Harmony.GetPatchInfo(target); Patch[] array = (from p in All(target) where p.PatchMethod == method select p).ToArray(); IEnumerable enumerable2; if (patchInfo != null) { IEnumerable enumerable = (((int)kind == 1) ? patchInfo.Prefixes : (((int)kind == 2) ? patchInfo.Postfixes : patchInfo.Transpilers)); enumerable2 = enumerable; } else { enumerable2 = Enumerable.Empty(); } IEnumerable source = enumerable2; if (array.Length != 1 || array[0].owner != owner || !source.Contains(array[0])) { throw new NotSupportedException($"Expected one {owner} {kind}: {Guard.Name(method)} on {Guard.Name(target)}."); } return (Target: target, Hook: array[0], Kind: kind); } internal static void Restore((MethodBase Target, Patch Hook, HarmonyPatchType Kind) hook) { //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Invalid comparison between Unknown and I4 //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Invalid comparison between Unknown and I4 //IL_020b: Unknown result type (might be due to invalid IL or missing references) if (All(hook.Target).Any((Patch p) => p.PatchMethod == hook.Hook.PatchMethod)) { Patch item = ExactHook(hook.Target, hook.Hook.PatchMethod, hook.Hook.owner, hook.Kind).Hook; if (item.priority != hook.Hook.priority || !item.before.SequenceEqual(hook.Hook.before) || !item.after.SequenceEqual(hook.Hook.after) || item.debug != hook.Hook.debug) { throw new InvalidOperationException("Existing vendor hook has changed ordering: " + Guard.Name(hook.Hook.PatchMethod)); } return; } HarmonyMethod val = new HarmonyMethod(hook.Hook.PatchMethod, hook.Hook.priority, hook.Hook.before, hook.Hook.after, (bool?)hook.Hook.debug); Harmony val2 = new Harmony(hook.Hook.owner); if ((int)hook.Kind == 1) { val2.Patch(hook.Target, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else if ((int)hook.Kind == 2) { val2.Patch(hook.Target, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { val2.Patch(hook.Target, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); } ExactHook(hook.Target, hook.Hook.PatchMethod, hook.Hook.owner, hook.Kind); } } public static class SkillState { public const int Protocol = 1; public static bool ValidFactor(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value) && value >= 0f) { return value <= 1f; } return false; } public static bool ValidLevel(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return value >= 0f; } return false; } public static T? Closest(IEnumerable players, Func squaredDistance, Func id, float range) where T : class { return players.Where((T p) => squaredDistance(p) <= range * range).OrderBy(squaredDistance).ThenBy(id) .FirstOrDefault(); } public static float RequireFactor(int protocol, float value) { if (protocol != 1 || !ValidFactor(value)) { throw new InvalidOperationException("Missing or invalid Serverbound skill state. All players must use the matching compatibility package."); } return value; } public static float RequireLevel(int protocol, float value) { if (protocol != 1 || !ValidLevel(value)) { throw new InvalidOperationException("Missing or invalid Serverbound skill level. All players must use the matching compatibility package."); } return value; } } }