Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of kou fixdesync v1.0.0
kou_fixdesync.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("TestHarness")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("kou_fixdesync")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("kou_fixdesync")] [assembly: AssemblyTitle("kou_fixdesync")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 KouFixDesync { [BepInPlugin("kou.fixdesync", "kou_fixdesync", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string GUID = "kou.fixdesync"; public const string NAME = "kou_fixdesync"; public const string VERSION = "1.0.0"; internal static Plugin Instance; internal static ManualLogSource Log; internal ConfigEntry<bool> CfgWindowEnabled; internal ConfigEntry<int> CfgWindowBytes; internal ConfigEntry<int> CfgMinWindowBytes; internal ConfigEntry<bool> CfgAdaptiveWindow; internal ConfigEntry<int> CfgTargetRateKBps; internal ConfigEntry<bool> CfgCadenceEnabled; internal ConfigEntry<int> CfgSendIntervalMs; internal ConfigEntry<bool> CfgSteamRatesEnabled; internal ConfigEntry<int> CfgRateMinKBps; internal ConfigEntry<int> CfgRateMaxKBps; internal ConfigEntry<bool> CfgDistantSyncEnabled; internal ConfigEntry<int> CfgDistantThreshold; internal ConfigEntry<bool> CfgOwnershipEnabled; internal ConfigEntry<int> CfgOwnershipPingCeiling; internal ConfigEntry<int> CfgOwnershipPingFloor; internal ConfigEntry<int> CfgOwnershipCooldownSec; private void Awake() { //IL_0247: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; CfgWindowEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1.SendWindow", "Enabled", true, "Enlarge the per-peer ZDO send window (vanilla: 10240 bytes incl. unacked data - the main desync cause)."); CfgWindowBytes = ((BaseUnityPlugin)this).Config.Bind<int>("1.SendWindow", "WindowBytes", 61440, "Send window size in bytes. Hard-clamped to 10240-262144 (Steamworks drops single messages >512KB)."); CfgMinWindowBytes = ((BaseUnityPlugin)this).Config.Bind<int>("1.SendWindow", "MinWindowBytes", 2048, "Minimum free window required before sending a batch (vanilla: 2048). Clamped 1024-8192."); CfgAdaptiveWindow = ((BaseUnityPlugin)this).Config.Bind<bool>("1.SendWindow", "PingAdaptive", true, "Scale each peer's window with its measured round-trip time (window = TargetRate x RTT). High-ping players get bigger windows automatically."); CfgTargetRateKBps = ((BaseUnityPlugin)this).Config.Bind<int>("1.SendWindow", "TargetRateKBps", 300, "Target per-peer send rate in KB/s used for the ping-adaptive window. Clamped 60-2048."); CfgCadenceEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("2.SendCadence", "Enabled", true, "Service ALL peers every send tick instead of vanilla's one-peer-per-frame round robin."); CfgSendIntervalMs = ((BaseUnityPlugin)this).Config.Bind<int>("2.SendCadence", "SendIntervalMs", 50, "Send tick interval in milliseconds (vanilla: 50). Clamped 20-200."); CfgSteamRatesEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("3.SteamRates", "Enabled", true, "Raise Steam networking send rate limits (vanilla pins min=max=150 KB/s)."); CfgRateMinKBps = ((BaseUnityPlugin)this).Config.Bind<int>("3.SteamRates", "SendRateMinKBps", 256, "Steam SendRateMin in KB/s. Clamped 150-4096."); CfgRateMaxKBps = ((BaseUnityPlugin)this).Config.Bind<int>("3.SteamRates", "SendRateMaxKBps", 1024, "Steam SendRateMax in KB/s. Keeping max > min lets Steam's congestion control work. Clamped to >= min, <= 8192."); CfgDistantSyncEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("4.DistantSync", "Enabled", true, "Sync distant-zone objects when fewer than DistantThreshold near objects are queued (vanilla threshold: 10)."); CfgDistantThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("4.DistantSync", "DistantThreshold", 30, "Queue-size threshold below which distant objects are added to the sync list. Clamped 10-200."); CfgOwnershipEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("5.SmartOwnership", "Enabled", true, "SERVER ONLY: assign mob/object simulation ownership to the lowest-ping player covering the area, and migrate it off high-ping players."); CfgOwnershipPingCeiling = ((BaseUnityPlugin)this).Config.Bind<int>("5.SmartOwnership", "PingCeilingMs", 150, "Migrate objects away from an owner whose ping exceeds this (ms)."); CfgOwnershipPingFloor = ((BaseUnityPlugin)this).Config.Bind<int>("5.SmartOwnership", "PingFloorMs", 80, "Only migrate when a candidate with ping below this (ms) also covers the area."); CfgOwnershipCooldownSec = ((BaseUnityPlugin)this).Config.Bind<int>("5.SmartOwnership", "CooldownSec", 10, "Per-object cooldown between ownership migrations, to prevent flapping."); Settings.ApplyLocal(); new Harmony("kou.fixdesync").PatchAll(); Log.LogInfo((object)"kou_fixdesync 1.0.0 loaded, patches applied"); } } internal static class Settings { public const int PkgVersion = 1; public const int AbsoluteMaxWindow = 262144; public static bool WindowEnabled = true; public static int WindowBytes = 61440; public static int MinWindowBytes = 2048; public static bool AdaptiveWindow = true; public static int TargetRateKBps = 300; public static bool CadenceEnabled = true; public static int SendIntervalMs = 50; public static bool SteamRatesEnabled = true; public static int RateMinKBps = 256; public static int RateMaxKBps = 1024; public static bool DistantSyncEnabled = true; public static int DistantThreshold = 30; public static bool OwnershipEnabled = true; public static int OwnershipPingCeiling = 150; public static int OwnershipPingFloor = 80; public static int OwnershipCooldownSec = 10; public static void ApplyLocal() { Plugin instance = Plugin.Instance; WindowEnabled = instance.CfgWindowEnabled.Value; WindowBytes = Clamp(instance.CfgWindowBytes.Value, 10240, 262144); MinWindowBytes = Clamp(instance.CfgMinWindowBytes.Value, 1024, 8192); AdaptiveWindow = instance.CfgAdaptiveWindow.Value; TargetRateKBps = Clamp(instance.CfgTargetRateKBps.Value, 60, 2048); CadenceEnabled = instance.CfgCadenceEnabled.Value; SendIntervalMs = Clamp(instance.CfgSendIntervalMs.Value, 20, 200); SteamRatesEnabled = instance.CfgSteamRatesEnabled.Value; RateMinKBps = Clamp(instance.CfgRateMinKBps.Value, 150, 4096); RateMaxKBps = Clamp(instance.CfgRateMaxKBps.Value, RateMinKBps, 8192); DistantSyncEnabled = instance.CfgDistantSyncEnabled.Value; DistantThreshold = Clamp(instance.CfgDistantThreshold.Value, 10, 200); OwnershipEnabled = instance.CfgOwnershipEnabled.Value; OwnershipPingCeiling = Clamp(instance.CfgOwnershipPingCeiling.Value, 50, 1000); OwnershipPingFloor = Clamp(instance.CfgOwnershipPingFloor.Value, 10, OwnershipPingCeiling); OwnershipCooldownSec = Clamp(instance.CfgOwnershipCooldownSec.Value, 1, 300); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"kou_fixdesync: local config applied"); } } public static void WritePackage(ZPackage pkg) { pkg.Write(1); pkg.Write(WindowEnabled); pkg.Write(WindowBytes); pkg.Write(MinWindowBytes); pkg.Write(AdaptiveWindow); pkg.Write(TargetRateKBps); pkg.Write(CadenceEnabled); pkg.Write(SendIntervalMs); pkg.Write(SteamRatesEnabled); pkg.Write(RateMinKBps); pkg.Write(RateMaxKBps); pkg.Write(DistantSyncEnabled); pkg.Write(DistantThreshold); } public static void ReadPackage(ZPackage pkg) { int num = pkg.ReadInt(); if (num != 1) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"kou_fixdesync: server config version {num} != {1}, keeping local values"); } return; } WindowEnabled = pkg.ReadBool(); WindowBytes = Clamp(pkg.ReadInt(), 10240, 262144); MinWindowBytes = Clamp(pkg.ReadInt(), 1024, 8192); AdaptiveWindow = pkg.ReadBool(); TargetRateKBps = Clamp(pkg.ReadInt(), 60, 2048); CadenceEnabled = pkg.ReadBool(); SendIntervalMs = Clamp(pkg.ReadInt(), 20, 200); SteamRatesEnabled = pkg.ReadBool(); RateMinKBps = Clamp(pkg.ReadInt(), 150, 4096); RateMaxKBps = Clamp(pkg.ReadInt(), RateMinKBps, 8192); DistantSyncEnabled = pkg.ReadBool(); DistantThreshold = Clamp(pkg.ReadInt(), 10, 200); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)($"kou_fixdesync: server-dictated config applied (window={WindowBytes} adaptive={AdaptiveWindow} " + $"cadence={CadenceEnabled}/{SendIntervalMs}ms rates={RateMinKBps}-{RateMaxKBps}KB/s distant={DistantThreshold})")); } } private static int Clamp(int v, int min, int max) { return Mathf.Clamp(v, min, max); } } } namespace KouFixDesync.Patches { internal static class ConfigSyncPatch { [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class OnNewConnectionPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown peer.m_rpc.Register<ZPackage>("KouFixDesync_Cfg", (Action<ZRpc, ZPackage>)OnConfigReceived); if (__instance.IsServer()) { ZPackage val = new ZPackage(); Settings.WritePackage(val); peer.m_rpc.Invoke("KouFixDesync_Cfg", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Awake")] private static class AwakePatch { [HarmonyPostfix] private static void Postfix() { Settings.ApplyLocal(); } } private const string RpcName = "KouFixDesync_Cfg"; private static void OnConfigReceived(ZRpc rpc, ZPackage pkg) { if (!((Object)(object)ZNet.instance != (Object)null) || !ZNet.instance.IsServer()) { Settings.ReadPackage(pkg); SteamRatePatch.Apply(); } } } [HarmonyPatch(typeof(ZDOMan), "CreateSyncList")] internal static class DistantSyncPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { int replaced = 0; foreach (CodeInstruction instruction in instructions) { if (replaced == 0 && instruction.opcode == OpCodes.Ldc_I4_S && Convert.ToInt32(instruction.operand) == 10) { replaced++; yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(DistantSyncPatch), "GetThreshold", (Type[])null, (Type[])null)) { labels = instruction.labels, blocks = instruction.blocks }; } else { yield return instruction; } } if (replaced != 1) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)$"kou_fixdesync: CreateSyncList transpiler matched {replaced} (expected 1) - game update may have changed the method"); } } } public static int GetThreshold() { if (!Settings.DistantSyncEnabled) { return 10; } return Settings.DistantThreshold; } } [HarmonyPatch(typeof(ZDOMan), "ReleaseNearbyZDOS")] internal static class OwnershipPatch { private static readonly List<ZDO> _tempObjects = new List<ZDO>(); private static readonly Dictionary<ZDOID, float> _lastMigration = new Dictionary<ZDOID, float>(); private static float _lastCacheClean; [HarmonyPrefix] private static bool Prefix(ZDOMan __instance, Vector3 refPosition, long uid) { //IL_0022: 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_0028: 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_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_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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (!Settings.OwnershipEnabled || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return true; } Vector2i zone = ZoneSystem.GetZone(refPosition); _tempObjects.Clear(); __instance.FindSectorObjects(zone, ZoneSystem.instance.m_activeArea, 0, _tempObjects, (List<ZDO>)null); int num = ZoneSystem.instance.m_activeArea - 1; List<ZNetPeer> peers = ZNet.instance.GetPeers(); float time = Time.time; CleanMigrationCache(time); foreach (ZDO tempObject in _tempObjects) { if (!tempObject.Persistent) { continue; } Vector2i sector = tempObject.GetSector(); long owner = tempObject.GetOwner(); if (owner == uid) { if (!ZNetScene.InActiveArea(sector, zone, num)) { tempObject.SetOwner(0L); } else if (uid != ZDOMan.GetSessionID()) { TryMigrate(tempObject, sector, owner, peers, time); } } else if ((!tempObject.HasOwner() || !IsInPeerActiveArea(sector, owner)) && ZNetScene.InActiveArea(sector, zone, num)) { tempObject.SetOwner(BestOwnerFor(sector, uid, peers)); } } return false; } private static long BestOwnerFor(Vector2i sector, long fallbackUid, List<ZNetPeer> peers) { //IL_0019: 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) long result = fallbackUid; int num = int.MaxValue; foreach (ZNetPeer peer in peers) { if (ZNetScene.InActiveArea(sector, peer.GetRefPos())) { int pingMs = GetPingMs(peer); if (pingMs < num) { num = pingMs; result = peer.m_uid; } } } return result; } private static void TryMigrate(ZDO zdo, Vector2i sector, long owner, List<ZNetPeer> peers, float now) { //IL_0023: 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_0066: 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) ZNetPeer peer = ZNet.instance.GetPeer(owner); if (peer == null || GetPingMs(peer) <= Settings.OwnershipPingCeiling || (_lastMigration.TryGetValue(zdo.m_uid, out var value) && now - value < (float)Settings.OwnershipCooldownSec)) { return; } ZNetPeer val = null; int num = int.MaxValue; foreach (ZNetPeer peer2 in peers) { if (peer2.m_uid != owner && ZNetScene.InActiveArea(sector, peer2.GetRefPos())) { int pingMs = GetPingMs(peer2); if (pingMs < num) { num = pingMs; val = peer2; } } } if (val != null && num < Settings.OwnershipPingFloor) { zdo.SetOwner(val.m_uid); _lastMigration[zdo.m_uid] = now; } } private static bool IsInPeerActiveArea(Vector2i sector, long uid) { //IL_0008: 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_002a: 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) if (uid == ZDOMan.GetSessionID()) { return ZNetScene.InActiveArea(sector, ZNet.instance.GetReferencePosition()); } ZNetPeer peer = ZNet.instance.GetPeer(uid); if (peer == null) { return false; } return ZNetScene.InActiveArea(sector, peer.GetRefPos()); } private static int GetPingMs(ZNetPeer peer) { if (peer.m_socket == null) { return int.MaxValue; } float num = default(float); float num2 = default(float); int result = default(int); float num3 = default(float); float num4 = default(float); peer.m_socket.GetConnectionQuality(ref num, ref num2, ref result, ref num3, ref num4); return result; } private static void CleanMigrationCache(float now) { if (!(now - _lastCacheClean < 60f) && _lastMigration.Count >= 512) { _lastCacheClean = now; _lastMigration.Clear(); } } } [HarmonyPatch(typeof(ZDOMan), "SendZDOToPeers2")] internal static class SendCadencePatch { private static readonly FieldInfo PeersField = AccessTools.Field(typeof(ZDOMan), "m_peers"); private static readonly FieldInfo NextSendPeerField = AccessTools.Field(typeof(ZDOMan), "m_nextSendPeer"); private static readonly MethodInfo SendZDOsMethod = AccessTools.Method(typeof(ZDOMan), "SendZDOs", (Type[])null, (Type[])null); private static float _timer; private static readonly object[] _args = new object[2]; [HarmonyPrefix] private static bool Prefix(ZDOMan __instance, float dt) { if (!Settings.CadenceEnabled) { return true; } IList list = (IList)PeersField.GetValue(__instance); if (list == null || list.Count == 0) { return false; } _timer += dt; if (_timer < (float)Settings.SendIntervalMs / 1000f) { return false; } _timer = 0f; _args[1] = false; for (int i = 0; i < list.Count; i++) { _args[0] = list[i]; SendZDOsMethod.Invoke(__instance, _args); } NextSendPeerField.SetValue(__instance, -1); return false; } } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] internal static class SendWindowPatch { private struct PingCache { public float Time; public int Ping; } private static FieldInfo _zdoPeerPeerField; private static readonly Dictionary<object, PingCache> _pingCache = new Dictionary<object, PingCache>(); [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { int windows = 0; int mins = 0; foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_I4 && instruction.operand is int num && num == 10240) { windows++; yield return new CodeInstruction(OpCodes.Ldarg_1, (object)null) { labels = instruction.labels, blocks = instruction.blocks }; yield return CodeInstruction.Call(typeof(SendWindowPatch), "GetWindow", (Type[])null, (Type[])null); } else if (instruction.opcode == OpCodes.Ldc_I4 && instruction.operand is int num2 && num2 == 2048) { mins++; yield return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SendWindowPatch), "GetMinWindow", (Type[])null, (Type[])null)) { labels = instruction.labels, blocks = instruction.blocks }; } else { yield return instruction; } } if (windows != 2 || mins != 1) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)$"kou_fixdesync: SendZDOs transpiler matched {windows}x10240 (expected 2), {mins}x2048 (expected 1) - game update may have changed the method"); } } } public static int GetWindow(object zdoPeer) { if (!Settings.WindowEnabled) { return 10240; } int num = Settings.WindowBytes; if (Settings.AdaptiveWindow) { int peerPingMs = GetPeerPingMs(zdoPeer); if (peerPingMs > 0) { int num2 = (int)((long)Settings.TargetRateKBps * 1024L * peerPingMs / 1000); if (num2 > num) { num = num2; } } } return Mathf.Min(num, 262144); } public static int GetMinWindow() { if (!Settings.WindowEnabled) { return 2048; } return Settings.MinWindowBytes; } private static int GetPeerPingMs(object zdoPeer) { if (zdoPeer == null) { return 0; } float time = Time.time; if (_pingCache.TryGetValue(zdoPeer, out var value) && time - value.Time < 1f) { return value.Ping; } int num = 0; if (_zdoPeerPeerField == null) { _zdoPeerPeerField = AccessTools.Field(zdoPeer.GetType(), "m_peer"); } object? obj = _zdoPeerPeerField?.GetValue(zdoPeer); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && val.m_socket != null) { float num2 = default(float); float num3 = default(float); float num4 = default(float); float num5 = default(float); val.m_socket.GetConnectionQuality(ref num2, ref num3, ref num, ref num4, ref num5); } if (_pingCache.Count > 64) { _pingCache.Clear(); } _pingCache[zdoPeer] = new PingCache { Time = time, Ping = num }; return num; } } [HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")] internal static class SteamRatePatch { [HarmonyPostfix] private static void Postfix() { Apply(); } public static void Apply() { if (!Settings.SteamRatesEnabled) { return; } try { SetGlobalInt((ESteamNetworkingConfigValue)10, Settings.RateMinKBps * 1024); SetGlobalInt((ESteamNetworkingConfigValue)11, Settings.RateMaxKBps * 1024); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"kou_fixdesync: Steam send rate set to {Settings.RateMinKBps}-{Settings.RateMaxKBps} KB/s"); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("kou_fixdesync: could not set Steam send rates: " + ex.Message)); } } } private static void SetGlobalInt(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 { SteamNetworkingUtils.SetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject()); } finally { gCHandle.Free(); } } } }