using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; 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.Permissions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("WarheimNetwork")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("WarheimNetwork")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e3243d22-4307-4008-ba36-9f326008cde5")] [assembly: AssemblyFileVersion("2.2.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.2.1.0")] namespace WarheimNetwork; internal static class ControlPlaneGuard { private static bool _installed; private static int _protectedAssemblies; private static int _protectedStateMachines; internal static void Install(Harmony harmony) { if (!_installed) { _installed = true; PatchZRpcTimeout(harmony); PatchEmbeddedServerSync(harmony); ApplyRuntimeSettings("installation"); WarheimNetwork.Log.LogInfo((object)($"[Control] Protection installée : ServerSync={_protectedStateMachines} coroutine(s) dans " + $"{_protectedAssemblies} assembly(s), timeout={NetworkConfig.EffectiveControlPlaneTimeoutSeconds():F0}s.")); } } internal static void ApplyRuntimeSettings(string reason) { try { Type type = AccessTools.TypeByName("Jotunn.Entities.CustomRPC, Jotunn"); FieldInfo fieldInfo = ((type == null) ? null : AccessTools.Field(type, "Timeout")); if (fieldInfo == null) { WarheimNetwork.Log.LogWarning((object)"[Control] Jötunn CustomRPC.Timeout introuvable."); return; } float num = (NetworkConfig.IsModuleEnabled(NetworkConfig.ControlPlaneGuardEnabled) ? NetworkConfig.EffectiveControlPlaneTimeoutSeconds() : 30f); fieldInfo.SetValue(null, num); if (!(reason == "installation")) { ConfigEntry verboseLogging = NetworkConfig.VerboseLogging; if (verboseLogging == null || !verboseLogging.Value) { return; } } WarheimNetwork.Log.LogInfo((object)$"[Control] Jötunn timeout={num:F0}s ({reason})."); } catch (Exception ex) { WarheimNetwork.Log.LogWarning((object)("[Control] Application du timeout Jötunn impossible : " + ex.GetType().Name + ": " + ex.Message)); } } private static void PatchZRpcTimeout(Harmony harmony) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZRpc), "SetLongTimeout", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ControlPlaneGuard), "ZRpcTimeoutTranspiler", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { WarheimNetwork.Log.LogError((object)"[Control] Patch ZRpc.SetLongTimeout impossible : méthode introuvable."); return; } try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null); WarheimNetwork.Log.LogInfo((object)"[Control] Timeout ZRpc dynamique installé avec validation stricte."); } catch (Exception ex) { WarheimNetwork.Log.LogError((object)("[Control] Patch ZRpc.SetLongTimeout refusé : " + ex.GetType().Name + ": " + ex.Message)); } } private static void PatchEmbeddedServerSync(Harmony harmony) { //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ControlPlaneGuard), "ConfigSyncTimeoutTranspiler", (Type[])null, (Type[])null); if (methodInfo == null) { WarheimNetwork.Log.LogError((object)"[Control] Transpiler ServerSync introuvable."); return; } HashSet hashSet = new HashSet(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if ((Object)(object)((value != null) ? value.Instance : null) == (Object)null) { continue; } BepInPlugin metadata = value.Metadata; if (((metadata != null) ? metadata.GUID : null) == "dzk.warheimnetwork") { continue; } BepInPlugin metadata2 = value.Metadata; if (((metadata2 != null) ? metadata2.GUID : null) == "com.jotunn.jotunn") { continue; } Assembly assembly = ((object)value.Instance).GetType().Assembly; if (assembly == null || !hashSet.Add(assembly)) { continue; } List types = GetLoadableTypes(assembly).ToList(); List list = FindServerSyncStateMachines(types); if (list.Count == 0 && value.Metadata.GUID == "Azumatt.AzuAntiCheat") { list = FindAzuWaitForQueueStateMachines(types); } int num = 0; foreach (Type item in list) { MethodInfo methodInfo2 = AccessTools.Method(item, "MoveNext", (Type[])null, (Type[])null); if (methodInfo2 == null) { continue; } if (!HasConfigSyncTimeoutPattern(methodInfo2, out var reason)) { if (value.Metadata.GUID == "Azumatt.AzuAntiCheat") { WarheimNetwork.Log.LogInfo((object)("[Control] Candidat AzuAntiCheat ignoré sans patch : " + item.FullName + " : " + reason)); continue; } ConfigEntry verboseLogging = NetworkConfig.VerboseLogging; if (verboseLogging != null && verboseLogging.Value) { WarheimNetwork.Log.LogWarning((object)("[Control] Candidat ServerSync ignoré pour " + value.Metadata.Name + "/" + item.FullName + " : " + reason)); } continue; } try { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null); num++; _protectedStateMachines++; } catch (Exception ex) { WarheimNetwork.Log.LogWarning((object)("[Control] Protection ServerSync refusée pour " + value.Metadata.Name + "/" + item.FullName + " : " + ex.GetType().Name + ": " + ex.Message)); } } if (num > 0) { _protectedAssemblies++; ConfigEntry verboseLogging2 = NetworkConfig.VerboseLogging; if (verboseLogging2 != null && verboseLogging2.Value) { WarheimNetwork.Log.LogInfo((object)$"[Control] {value.Metadata.Name} [{value.Metadata.GUID}] : {num} timeout(s) protégé(s)."); } } } } private static bool HasConfigSyncTimeoutPattern(MethodBase method, out string reason) { try { List currentInstructions = PatchProcessor.GetCurrentInstructions(method, int.MaxValue, (ILGenerator)null); MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(Time), "time"); if (methodInfo == null) { reason = "getter Time.time introuvable"; return false; } int num = CountConfigSyncTimeoutPatterns(currentInstructions, methodInfo); if (num != 1) { reason = $"calcul Time.time+30 attendu une fois, trouvé {num} fois"; return false; } reason = string.Empty; return true; } catch (Exception ex) { reason = "lecture IL impossible : " + ex.GetType().Name + ": " + ex.Message; return false; } } private static List FindServerSyncStateMachines(IEnumerable types) { List list = new List(); foreach (Type item in types.Where((Type type) => type != null && type.IsClass && (type.Name == "ConfigSync" || type.Name == "ServerSync"))) { foreach (Type item2 in GetNestedTypesRecursive(item)) { if (item2.Name.IndexOf("waitForQueue", StringComparison.OrdinalIgnoreCase) >= 0 && AccessTools.Method(item2, "MoveNext", (Type[])null, (Type[])null) != null) { list.Add(item2); } } } return list.Distinct().ToList(); } private static IEnumerable GetNestedTypesRecursive(Type root) { Stack pending; try { pending = new Stack(root.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic)); } catch { yield break; } while (pending.Count > 0) { Type current = pending.Pop(); yield return current; Type[] nested; try { nested = current.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } Type[] array = nested; foreach (Type type in array) { pending.Push(type); } } } private static List FindAzuWaitForQueueStateMachines(IEnumerable types) { try { return (from type in (from method in types.SelectMany((Type type) => type.GetNestedTypes(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)).SelectMany((Type type) => type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) where method.Name.IndexOf("waitForQueue", StringComparison.OrdinalIgnoreCase) >= 0 select method).SelectMany((MethodInfo method) => (method.DeclaringType == null) ? ((IEnumerable)Array.Empty()) : ((IEnumerable)method.DeclaringType.GetNestedTypes(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))) where AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null) != null select type).Distinct().ToList(); } catch { return new List(); } } private static IEnumerable GetLoadableTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type type) => type != null); } catch { return Array.Empty(); } } private static IEnumerable ZRpcTimeoutTranspiler(IEnumerable instructions, MethodBase original) { List list = new List(instructions); List list2 = FindFloatConstants(list, 30f); List list3 = FindFloatConstants(list, 90f); if (list2.Count != 1 || list3.Count != 1) { throw new InvalidOperationException(original?.DeclaringType?.FullName + "." + original?.Name + ": constantes 30/90 attendues 1/1, " + $"trouvées {list2.Count}/{list3.Count}"); } MethodInfo methodInfo = AccessTools.Method(typeof(NetworkConfig), "EffectiveRpcShortTimeoutSeconds", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(NetworkConfig), "EffectiveRpcLongTimeoutSeconds", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { throw new MissingMethodException("Getters de timeout ZRpc introuvables"); } ReplaceWithCall(list[list2[0]], methodInfo); ReplaceWithCall(list[list3[0]], methodInfo2); return list; } private static IEnumerable ConfigSyncTimeoutTranspiler(IEnumerable instructions, MethodBase original) { List list = new List(instructions); MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(Time), "time"); MethodInfo methodInfo2 = AccessTools.Method(typeof(NetworkConfig), "EffectiveControlPlaneTimeoutSeconds", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { throw new MissingMethodException("Getters de temps ou de timeout introuvables"); } List list2 = new List(); for (int i = 0; i + 2 < list.Count; i++) { if (CodeInstructionExtensions.Calls(list[i], methodInfo) && IsFloatConstant(list[i + 1], 30f) && !(list[i + 2].opcode != OpCodes.Add)) { list2.Add(i + 1); } } if (list2.Count != 1) { WarheimNetwork.Log.LogWarning((object)("[Control] Transpiler ServerSync annulé pour " + original?.DeclaringType?.FullName + "." + original?.Name + " : " + $"calcul Time.time+30 attendu une fois, trouvé {list2.Count} fois.")); return list; } ReplaceWithCall(list[list2[0]], methodInfo2); RewriteStandardDisconnectMessage(list, methodInfo2, original); return list; } private static int CountConfigSyncTimeoutPatterns(List instructions, MethodInfo getTime) { int num = 0; for (int i = 0; i + 2 < instructions.Count; i++) { if (CodeInstructionExtensions.Calls(instructions[i], getTime) && IsFloatConstant(instructions[i + 1], 30f) && instructions[i + 2].opcode == OpCodes.Add) { num++; } } return num; } private static void RewriteStandardDisconnectMessage(List instructions, MethodInfo timeoutGetter, MethodBase original) { //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Expected O, but got Unknown //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown List list = new List(); for (int i = 0; i < instructions.Count; i++) { if (instructions[i].opcode == OpCodes.Ldstr && object.Equals(instructions[i].operand, "Disconnecting {0} after 30 seconds config sending timeout")) { list.Add(i); } } if (list.Count == 0) { return; } if (list.Count != 1) { WarheimNetwork.Log.LogWarning((object)("[Control] Message ServerSync inchangé pour " + original?.DeclaringType?.FullName + " : " + $"1 occurrence attendue, {list.Count} trouvées.")); return; } int num = -1; for (int j = list[0] + 1; j < Math.Min(instructions.Count, list[0] + 24); j++) { if (!(instructions[j].opcode != OpCodes.Call) && instructions[j].operand is MethodInfo methodInfo && !(methodInfo.DeclaringType != typeof(string)) && !(methodInfo.Name != "Format") && methodInfo.GetParameters().Length == 2) { num = j; break; } } MethodInfo methodInfo2 = AccessTools.Method(typeof(string), "Format", new Type[3] { typeof(string), typeof(object), typeof(object) }, (Type[])null); if (num < 0 || methodInfo2 == null) { WarheimNetwork.Log.LogWarning((object)("[Control] Message ServerSync inchangé pour " + original?.DeclaringType?.FullName + " : string.Format introuvable.")); return; } instructions[list[0]].operand = "Disconnecting {0} after {1:F0} seconds config sending timeout"; instructions.Insert(num, new CodeInstruction(OpCodes.Call, (object)timeoutGetter)); instructions.Insert(num + 1, new CodeInstruction(OpCodes.Box, (object)typeof(float))); instructions[num + 2].operand = methodInfo2; } private static List FindFloatConstants(List instructions, float value) { List list = new List(); for (int i = 0; i < instructions.Count; i++) { if (IsFloatConstant(instructions[i], value)) { list.Add(i); } } return list; } private static bool IsFloatConstant(CodeInstruction instruction, float value) { return instruction.opcode == OpCodes.Ldc_R4 && instruction.operand is float num && Math.Abs(num - value) < 0.001f; } private static void ReplaceWithCall(CodeInstruction instruction, MethodInfo getter) { instruction.opcode = OpCodes.Call; instruction.operand = getter; } } [HarmonyPatch(typeof(ZoneSystem), "RPC_GlobalKeys")] internal static class MapSyncGuard { [HarmonyPrefix] private static void Prefix(ref MapMode __state) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) __state = (MapMode)(((Object)(object)Minimap.instance != (Object)null) ? ((int)Minimap.instance.m_mode) : 0); } [HarmonyPostfix] private static void Postfix(MapMode __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Invalid comparison between Unknown and I4 if ((int)__state == 2 && !((Object)(object)Minimap.instance == (Object)null) && !MapIsDisabled() && (int)Minimap.instance.m_mode != 2) { Minimap.instance.SetMapMode((MapMode)2); NetworkDiagnostics.RecordMapModeRestore(); WarheimNetwork.Log.LogInfo((object)"[MapSync] Grande carte restaurée après synchronisation des global keys."); } } private static bool MapIsDisabled() { if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)26)) { return true; } Player localPlayer = Player.m_localPlayer; return (Object)(object)localPlayer != (Object)null && PlayerPrefs.GetFloat("mapenabled_" + localPlayer.GetPlayerName(), 1f) == 0f; } } internal static class NetworkConfig { internal const int VanillaZdoQueueLimit = 10240; internal const float VanillaPositionSmooth = 0.2f; internal const float VanillaRotationSmooth = 0.5f; internal const float VanillaMicroThreshold = 0.001f; internal const float VanillaClientDistanceThreshold = 0.01f; internal static ConfigEntry MasterEnabled; internal static ConfigEntry SteamTransportEnabled; internal static ConfigEntry TransportCompressionEnabled; internal static ConfigEntry ZdoSchedulerEnabled; internal static ConfigEntry ZdoQueueLimitEnabled; internal static ConfigEntry AdaptiveBackpressureEnabled; internal static ConfigEntry ControlPlaneGuardEnabled; internal static ConfigEntry TransformSyncEnabled; internal static ConfigEntry LifecycleGuardEnabled; internal static ConfigEntry ShipSyncEnabled; internal static ConfigEntry SaveGuardEnabled; internal static ConfigEntry SteamSendRateMaxKb; internal static ConfigEntry SteamSendRateMinKb; internal static ConfigEntry SteamSendBufferKb; internal static ConfigEntry SteamReceiveBufferKb; internal static ConfigEntry SteamReceiveMaxMessageKb; internal static ConfigEntry CompressionThresholdBytes; internal static ConfigEntry CompressionMinimumSavingsPercent; internal static ConfigEntry CompressionMaxPackageMb; internal static ConfigEntry ZdoSendInterval; internal static ConfigEntry ZdoPeersPerUpdate; internal static ConfigEntry ZdoQueueLimit; internal static ConfigEntry ZdoMinimumPackageBytes; internal static ConfigEntry ZdoMaximumPackageBytes; internal static ConfigEntry ZdoQueueSoftLimit; internal static ConfigEntry ZdoQueueHardLimit; internal static ConfigEntry ZdoQueueEmergencyLimit; internal static ConfigEntry ZdoMinimumPeerInterval; internal static ConfigEntry ZdoMaximumPeerInterval; internal static ConfigEntry ZdoMaximumSchedulerWorkMs; internal static ConfigEntry ZdoMaximumStarvationSeconds; internal static ConfigEntry ZdoCatchUpSeconds; internal static ConfigEntry ZdoFlushThresholdPercent; internal static ConfigEntry ControlPlaneTimeoutSeconds; internal static ConfigEntry PeerDiagnosticsIntervalSeconds; internal static ConfigEntry PositionSmooth; internal static ConfigEntry RotationSmooth; internal static ConfigEntry MicroMovementThreshold; internal static ConfigEntry ClientDistanceThreshold; internal static ConfigEntry VerboseLogging; internal static event Action SteamSettingsChanged; internal static event Action CompressionSettingsChanged; internal static event Action ControlSettingsChanged; internal static void Bind(ConfigFile config) { MasterEnabled = ConfigFileExtensions.BindConfig(config, "00 - Général", "Enabled", true, "Active WarheimNetwork. Les patchs restent installés mais retombent sur le comportement vanilla.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); SteamTransportEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "SteamTransport", true, "Augmente les débits et buffers de SteamNetworkingSockets.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); TransportCompressionEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "TransportCompression", true, "Compresse les paquets Steam suffisamment gros après négociation avec chaque peer.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); ZdoSchedulerEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "ZdoScheduler", true, "Distribue équitablement les envois ZDO avec rattrapage des nouveaux peers.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); ZdoQueueLimitEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "ZdoQueueLimit", true, "Applique un budget ZDO dynamique propre à chaque peer.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); AdaptiveBackpressureEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "AdaptiveCongestion", true, "Adapte chaque peer à partir de sa file, de son débit et de l'inflation de son RTT.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); ControlPlaneGuardEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "ControlPlaneGuard", true, "Réserve de la place aux synchronisations de configuration et prolonge leurs timeouts.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); TransformSyncEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "TransformSync", true, "Applique les réglages de lissage réseau validés pour le PvP.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); LifecycleGuardEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "LifecycleGuard", true, "Récupère les références ZNetView détruites qui feraient boucler ZNetScene.RemoveObjects.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); ShipSyncEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "ShipSync", true, "Accélère la synchronisation du gouvernail et lisse les navires et joueurs attachés.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); SaveGuardEnabled = ConfigFileExtensions.BindConfig(config, "01 - Modules", "SaveGuard", true, "Force les sauvegardes dédiées synchrones, suspend le réseau et sérialise les ZDO sans listes temporaires lorsque le contrat vanilla est compatible.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange(256, 32768); SteamSendRateMaxKb = ConfigFileExtensions.BindConfig(config, "02 - Steam", "SendRateMaxKB", 16384, "Débit d'envoi maximal Steam en Ko/s.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(128, 32768); SteamSendRateMinKb = ConfigFileExtensions.BindConfig(config, "02 - Steam", "SendRateMinKB", 256, "Débit d'envoi minimal demandé à Steam en Ko/s.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(512, 16384); SteamSendBufferKb = ConfigFileExtensions.BindConfig(config, "02 - Steam", "SendBufferKB", 8192, "Taille du buffer d'envoi Steam en Ko.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(512, 16384); SteamReceiveBufferKb = ConfigFileExtensions.BindConfig(config, "02 - Steam", "ReceiveBufferKB", 4096, "Taille du buffer de réception Steam en Ko.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(1024, 32768); SteamReceiveMaxMessageKb = ConfigFileExtensions.BindConfig(config, "02 - Steam", "ReceiveMaxMessageKB", 8192, "Taille maximale d'un message reçu par Steam en Ko.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(256, 65536); CompressionThresholdBytes = ConfigFileExtensions.BindConfig(config, "03 - Compression", "ThresholdBytes", 1024, "Taille minimale d'un paquet avant tentative de compression.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(1, 50); CompressionMinimumSavingsPercent = ConfigFileExtensions.BindConfig(config, "03 - Compression", "MinimumSavingsPercent", 10, "Gain minimal exigé pour envoyer un paquet compressé.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(4, 256); CompressionMaxPackageMb = ConfigFileExtensions.BindConfig(config, "03 - Compression", "MaximumPackageMB", 64, "Taille maximale acceptée après décompression.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.1f); ZdoSendInterval = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "SendInterval", 0.02f, "Intervalle global entre cycles ZDO. Vanilla : 0.05 seconde.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(1, 200); ZdoPeersPerUpdate = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "PeersPerUpdate", 50, "Nombre maximal de peers examinés par cycle ZDO.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(10240, 262144); ZdoQueueLimit = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "InitialPackageBytes", 65536, "Budget ZDO initial par envoi et par peer.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(8192, 65536); ZdoMinimumPackageBytes = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "MinimumPackageBytes", 12288, "Budget ZDO minimal sous forte congestion.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(16384, 524288); ZdoMaximumPackageBytes = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "MaximumPackageBytes", 131072, "Budget ZDO maximal pendant un rattrapage sain.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(32768, 2097152); ZdoQueueSoftLimit = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "QueueSoftLimit", 131072, "File Steam à partir de laquelle la croissance ZDO ralentit.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(65536, 8388608); ZdoQueueHardLimit = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "QueueHardLimit", 524288, "File Steam au-dessus de laquelle aucun nouveau paquet ZDO n'est ajouté.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(262144, 33554432); ZdoQueueEmergencyLimit = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "QueueEmergencyLimit", 2097152, "File Steam signalant une congestion critique dans les diagnostics.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.1f); ZdoMinimumPeerInterval = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "MinimumPeerInterval", 0.015f, "Intervalle minimal d'un peer sain en rattrapage.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.03f, 1f); ZdoMaximumPeerInterval = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "MaximumPeerInterval", 0.15f, "Intervalle maximal d'un peer réellement congestionné.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 20f); ZdoMaximumSchedulerWorkMs = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "MaximumSchedulerWorkMs", 4f, "Temps CPU maximal consacré à un cycle ZDO.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f); ZdoMaximumStarvationSeconds = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "MaximumStarvationSeconds", 0.5f, "Délai maximal avant de prioriser un peer servi trop tard.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(5f, 120f); ZdoCatchUpSeconds = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "CatchUpSeconds", 30f, "Durée du profil de rattrapage après connexion.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(5, 90); ZdoFlushThresholdPercent = ConfigFileExtensions.BindConfig(config, "04 - ZDO", "FlushThresholdPercent", 40, "Seuil de file autorisant un envoi ZDO complet pendant le rattrapage.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(60f, 300f); ControlPlaneTimeoutSeconds = ConfigFileExtensions.BindConfig(config, "05 - Contrôle", "TimeoutSeconds", 120f, "Timeout des RPC longs et des synchronisations ServerSync intégrées aux mods.", false, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(5f, 60f); PeerDiagnosticsIntervalSeconds = ConfigFileExtensions.BindConfig(config, "05 - Contrôle", "PeerDiagnosticsInterval", 10f, "Intervalle des diagnostics détaillés par peer lorsque VerboseLogging est actif.", false, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 1f); PositionSmooth = ConfigFileExtensions.BindConfig(config, "06 - Transform", "PositionSmooth", 0.22f, "Valeur de lissage de position distante. Vanilla : 0.20.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 1f); RotationSmooth = ConfigFileExtensions.BindConfig(config, "06 - Transform", "RotationSmooth", 0.45f, "Valeur de lissage de rotation distante. Vanilla : 0.50.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.0001f, 0.05f); MicroMovementThreshold = ConfigFileExtensions.BindConfig(config, "06 - Transform", "MicroMovementThreshold", 0.004f, "Seuil des micro-mouvements réseau. Vanilla : 0.001.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange(0.0001f, 0.05f); ClientDistanceThreshold = ConfigFileExtensions.BindConfig(config, "06 - Transform", "ClientDistanceThreshold", 0.005f, "Seuil de distance de synchronisation client. Vanilla : 0.01.", true, (int?)null, val, (Action)null, (ConfigurationManagerAttributes)null); VerboseLogging = ConfigFileExtensions.BindConfig(config, "07 - Diagnostic", "VerboseLogging", false, "Ajoute des informations de diagnostic agrégées.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); WatchSteamEntry(MasterEnabled); WatchCompressionEntry(MasterEnabled); WatchControlEntry(MasterEnabled); WatchSteamEntry(SteamTransportEnabled); WatchSteamEntry(SteamSendRateMaxKb); WatchSteamEntry(SteamSendRateMinKb); WatchSteamEntry(SteamSendBufferKb); WatchSteamEntry(SteamReceiveBufferKb); WatchSteamEntry(SteamReceiveMaxMessageKb); WatchCompressionEntry(TransportCompressionEnabled); WatchCompressionEntry(CompressionThresholdBytes); WatchCompressionEntry(CompressionMinimumSavingsPercent); WatchControlEntry(ControlPlaneGuardEnabled); WatchControlEntry(ControlPlaneTimeoutSeconds); } private static void WatchSteamEntry(ConfigEntry entry) { entry.SettingChanged += delegate { NetworkConfig.SteamSettingsChanged?.Invoke(); }; } private static void WatchCompressionEntry(ConfigEntry entry) { entry.SettingChanged += delegate { NetworkConfig.CompressionSettingsChanged?.Invoke(); }; } private static void WatchControlEntry(ConfigEntry entry) { entry.SettingChanged += delegate { NetworkConfig.ControlSettingsChanged?.Invoke(); }; } internal static bool IsModuleEnabled(ConfigEntry module) { ConfigEntry masterEnabled = MasterEnabled; return masterEnabled != null && masterEnabled.Value && (module?.Value ?? false); } internal static int EffectiveSteamSendRateMaxBytes() { if (!IsModuleEnabled(SteamTransportEnabled)) { return 153600; } return Math.Max(SteamSendRateMaxKb.Value, SteamSendRateMinKb.Value) * 1024; } internal static int EffectiveZdoQueueTarget() { if (!IsModuleEnabled(ZdoQueueLimitEnabled)) { return 10240; } return PeerTrafficController.EffectiveQueueTarget(ZdoQueueLimit.Value); } internal static int EffectiveZdoQueueGate() { if (!IsModuleEnabled(ZdoQueueLimitEnabled)) { return 10240; } return PeerTrafficController.EffectiveQueueGate(ZdoQueueSoftLimit.Value); } internal static float EffectiveControlPlaneTimeoutSeconds() { return IsModuleEnabled(ControlPlaneGuardEnabled) ? Math.Max(60f, ControlPlaneTimeoutSeconds.Value) : 30f; } internal static float EffectiveRpcShortTimeoutSeconds() { return IsModuleEnabled(ControlPlaneGuardEnabled) ? Math.Max(60f, ControlPlaneTimeoutSeconds.Value) : 30f; } internal static float EffectiveRpcLongTimeoutSeconds() { return IsModuleEnabled(ControlPlaneGuardEnabled) ? Math.Max(60f, ControlPlaneTimeoutSeconds.Value) : 90f; } internal static float EffectivePositionSmooth() { return IsModuleEnabled(TransformSyncEnabled) ? PositionSmooth.Value : 0.2f; } internal static float EffectiveRotationSmooth() { return IsModuleEnabled(TransformSyncEnabled) ? RotationSmooth.Value : 0.5f; } internal static float EffectiveMicroThreshold() { return IsModuleEnabled(TransformSyncEnabled) ? MicroMovementThreshold.Value : 0.001f; } internal static float EffectiveClientDistanceThreshold() { return IsModuleEnabled(TransformSyncEnabled) ? ClientDistanceThreshold.Value : 0.01f; } } internal static class NetworkDiagnostics { private static long _zdoCycles; private static long _zdoPeersAttempted; private static long _zdoPeersSent; private static long _zdoFlushes; private static long _zdoForced; private static long _zdoWorkLimitedCycles; private static long _lifecycleRecoveries; private static long _lifecycleInstancesPurged; private static long _lifecycleTemporaryPurged; private static long _shipRudderSends; private static long _shipAttachSnaps; private static long _saveBarriers; private static long _forcedSynchronousSaves; private static long _lowAllocationSnapshots; private static long _avoidedInnerClones; private static long _fastSerializedZdos; private static long _serializedExtraValues; private static long _adminSaveRequests; private static long _scheduledSaveRequests; private static long _systemSaveRequests; private static long _mapModeRestores; internal static void RecordZdoCycle(int attempted, int sent, int flushed, int forced, bool workLimited) { _zdoCycles++; _zdoPeersAttempted += attempted; _zdoPeersSent += sent; _zdoFlushes += flushed; _zdoForced += forced; if (workLimited) { _zdoWorkLimitedCycles++; } } internal static void RecordLifecycleRecovery(int instancesPurged, int temporaryPurged) { _lifecycleRecoveries++; _lifecycleInstancesPurged += Math.Max(0, instancesPurged); _lifecycleTemporaryPurged += Math.Max(0, temporaryPurged); } internal static void RecordShipRudderSend() { _shipRudderSends++; } internal static void RecordShipAttachSnap() { _shipAttachSnaps++; } internal static void RecordSaveBarrier() { _saveBarriers++; } internal static void RecordForcedSynchronousSave() { _forcedSynchronousSaves++; } internal static void RecordLowAllocationSaveSnapshot(long avoidedInnerClones) { Interlocked.Increment(ref _lowAllocationSnapshots); Interlocked.Add(ref _avoidedInnerClones, Math.Max(0L, avoidedInnerClones)); } internal static void RecordFastSerializedZdo(int extraValues) { Interlocked.Increment(ref _fastSerializedZdos); Interlocked.Add(ref _serializedExtraValues, Math.Max(0, extraValues)); } internal static void RecordSaveRequest(string origin) { if (string.Equals(origin, "commande admin vanilla", StringComparison.Ordinal)) { Interlocked.Increment(ref _adminSaveRequests); } else if (origin != null && origin.IndexOf("autosave", StringComparison.OrdinalIgnoreCase) >= 0) { Interlocked.Increment(ref _scheduledSaveRequests); } else { Interlocked.Increment(ref _systemSaveRequests); } } internal static void RecordMapModeRestore() { _mapModeRestores++; } internal static void ResetSession() { _zdoCycles = 0L; _zdoPeersAttempted = 0L; _zdoPeersSent = 0L; _zdoFlushes = 0L; _zdoForced = 0L; _zdoWorkLimitedCycles = 0L; _lifecycleRecoveries = 0L; _lifecycleInstancesPurged = 0L; _lifecycleTemporaryPurged = 0L; _shipRudderSends = 0L; _shipAttachSnaps = 0L; _saveBarriers = 0L; _forcedSynchronousSaves = 0L; _lowAllocationSnapshots = 0L; _avoidedInnerClones = 0L; _fastSerializedZdos = 0L; _serializedExtraValues = 0L; _adminSaveRequests = 0L; _scheduledSaveRequests = 0L; _systemSaveRequests = 0L; _mapModeRestores = 0L; } internal static void LogSessionSummary() { if (_zdoCycles > 0) { WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session ZDO : cycles={_zdoCycles}, " + $"peers examinés={_zdoPeersAttempted}, envois={_zdoPeersSent}, flush={_zdoFlushes}, " + $"forcés={_zdoForced}, cycles limités CPU={_zdoWorkLimitedCycles}.")); } if (_lifecycleRecoveries > 0) { WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session Lifecycle : récupérations={_lifecycleRecoveries}, " + $"instances purgées={_lifecycleInstancesPurged}, temporaires purgées={_lifecycleTemporaryPurged}.")); } if (_shipRudderSends > 0 || _shipAttachSnaps > 0) { WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session ShipSync : envois gouvernail supplémentaires={_shipRudderSends}, " + $"recalages joueurs={_shipAttachSnaps}.")); } if (_saveBarriers > 0 || _lowAllocationSnapshots > 0) { WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session SaveGuard : barrières={_saveBarriers}, synchrones forcées={_forcedSynchronousSaves}, " + $"requêtes admin={_adminSaveRequests}, programmées={_scheduledSaveRequests}, système={_systemSaveRequests}, " + $"snapshots faibles allocations={_lowAllocationSnapshots}, " + $"ZDO sérialisés sans listes={_fastSerializedZdos}, valeurs extra={_serializedExtraValues}, " + $"entrées/clones évités={_avoidedInnerClones}.")); } if (_mapModeRestores > 0) { WarheimNetwork.Log.LogInfo((object)$"[Diagnostic] Session MapSync : grandes cartes restaurées={_mapModeRestores}."); } TransportCompression.LogSessionSummary(); } internal static void LogEffectiveSettings(string reason) { double num = (double)NetworkConfig.ZdoMaximumPackageBytes.Value * (double)NetworkConfig.ZdoPeersPerUpdate.Value / (double)Math.Max(NetworkConfig.ZdoSendInterval.Value, 0.01f) / 1048576.0; WarheimNetwork.Log.LogInfo((object)($"[Config] {reason} | master={NetworkConfig.MasterEnabled.Value}, " + $"steam={NetworkConfig.IsModuleEnabled(NetworkConfig.SteamTransportEnabled)}, " + $"compression={NetworkConfig.IsModuleEnabled(NetworkConfig.TransportCompressionEnabled)} " + $"(seuil={NetworkConfig.CompressionThresholdBytes.Value}o, gain={NetworkConfig.CompressionMinimumSavingsPercent.Value}%), " + $"zdo={NetworkConfig.IsModuleEnabled(NetworkConfig.ZdoSchedulerEnabled)} " + $"({NetworkConfig.ZdoSendInterval.Value:F3}s/{NetworkConfig.ZdoPeersPerUpdate.Value} peers), " + $"paquet={NetworkConfig.ZdoMinimumPackageBytes.Value}->{NetworkConfig.ZdoQueueLimit.Value}->{NetworkConfig.ZdoMaximumPackageBytes.Value}, " + $"queue={NetworkConfig.ZdoQueueSoftLimit.Value}->{NetworkConfig.ZdoQueueHardLimit.Value}->{NetworkConfig.ZdoQueueEmergencyLimit.Value}, " + $"adaptive={NetworkConfig.IsModuleEnabled(NetworkConfig.AdaptiveBackpressureEnabled)}, " + $"control={NetworkConfig.IsModuleEnabled(NetworkConfig.ControlPlaneGuardEnabled)} " + $"({NetworkConfig.EffectiveControlPlaneTimeoutSeconds():F0}s), " + $"transform={NetworkConfig.IsModuleEnabled(NetworkConfig.TransformSyncEnabled)}, " + $"lifecycle={NetworkConfig.IsModuleEnabled(NetworkConfig.LifecycleGuardEnabled)}, " + $"ship={NetworkConfig.IsModuleEnabled(NetworkConfig.ShipSyncEnabled)}, " + $"saveGuard={NetworkConfig.IsModuleEnabled(NetworkConfig.SaveGuardEnabled)}, " + $"plafond ZDO théorique={num:F1} Mio/s.")); } internal static void AuditSavePatches(string reason) { AuditSaveMethod(typeof(ZNet), "Save", reason); AuditSaveMethod(typeof(ZNet), "SaveWorld", reason); AuditSaveMethod(typeof(ZNet), "RPC_Save", reason); AuditSaveMethod(typeof(ZDOMan), "PrepareSave", reason); AuditSaveMethod(typeof(ZDOMan), "SaveAsync", reason); AuditSaveMethod(typeof(ZDOExtraData), "PrepareSave", reason); AuditSaveMethod(typeof(ZDO), "Save", reason); } private static void AuditSaveMethod(Type type, string methodName, string reason) { MethodBase methodBase = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodBase == null) { WarheimNetwork.Log.LogWarning((object)("[AuditSave] " + type.Name + "." + methodName + " introuvable.")); return; } Patches patchInfo = Harmony.GetPatchInfo(methodBase); if (patchInfo == null) { WarheimNetwork.Log.LogInfo((object)("[AuditSave] " + reason + " : " + type.Name + "." + methodName + " est vanilla.")); return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); AddOwners(hashSet, patchInfo.Prefixes); AddOwners(hashSet, patchInfo.Postfixes); AddOwners(hashSet, patchInfo.Transpilers); AddOwners(hashSet, patchInfo.Finalizers); bool flag = hashSet.Remove("dzk.warheimnetwork"); string text = ((hashSet.Count == 0) ? "aucun" : string.Join(", ", hashSet.OrderBy((string owner) => owner))); WarheimNetwork.Log.LogInfo((object)$"[AuditSave] {reason} : {type.Name}.{methodName}, WarheimNetwork={flag}, autres={text}."); } internal static void AuditLifecyclePatches(string reason) { AuditMethod(typeof(ZNetScene), "RemoveObjects", reason, allowSelf: true); AuditMethod(typeof(ZNetScene), "CreateDestroyObjects", reason, allowSelf: false); AuditMethod(typeof(ZNetScene), "InLoadingScreen", reason, allowSelf: false); } private static void AuditMethod(Type type, string methodName, string reason, bool allowSelf) { MethodBase methodBase = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodBase == null) { WarheimNetwork.Log.LogWarning((object)("[Audit] " + type.Name + "." + methodName + " introuvable pour l'audit.")); return; } Patches patchInfo = Harmony.GetPatchInfo(methodBase); if (patchInfo == null) { WarheimNetwork.Log.LogInfo((object)("[Audit] " + reason + " : " + type.Name + "." + methodName + " est entièrement vanilla.")); return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); AddOwners(hashSet, patchInfo.Prefixes); AddOwners(hashSet, patchInfo.Postfixes); AddOwners(hashSet, patchInfo.Transpilers); AddOwners(hashSet, patchInfo.Finalizers); bool flag = hashSet.Remove("dzk.warheimnetwork"); if (flag && !allowSelf) { WarheimNetwork.Log.LogError((object)("[Audit] ERREUR CRITIQUE : WarheimNetwork apparaît sur " + type.Name + "." + methodName + ".")); } else if (flag) { WarheimNetwork.Log.LogInfo((object)("[Audit] " + reason + " : " + type.Name + "." + methodName + " conserve son corps vanilla avec finalizer de récupération WarheimNetwork.")); } if (hashSet.Count == 0) { if (!flag) { WarheimNetwork.Log.LogInfo((object)("[Audit] " + reason + " : " + type.Name + "." + methodName + " est entièrement vanilla.")); } return; } string text = string.Join(", ", hashSet.OrderBy((string owner) => owner)); WarheimNetwork.Log.LogWarning((object)("[Audit] " + reason + " : autres patchs détectés sur " + type.Name + "." + methodName + " : " + text)); } private static void AddOwners(HashSet owners, IEnumerable patches) { foreach (Patch patch in patches) { if (!string.IsNullOrEmpty(patch.owner)) { owners.Add(patch.owner); } } } } [HarmonyPatch] internal static class NetworkDiagnosticsPatches { [HarmonyPatch(typeof(Game), "Start")] [HarmonyPostfix] private static void GameStartPostfix() { NetworkDiagnostics.ResetSession(); PeerTrafficController.Reset(); TransportCompression.Reset(); ShipSyncPatches.Reset(); SavePressureGuard.Reset(); NetworkDiagnostics.AuditLifecyclePatches("Game.Start"); NetworkDiagnostics.AuditSavePatches("Game.Start"); SavePressureGuard.FinalizeCompatibilityAudit(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] [HarmonyPrefix] private static void ZNetShutdownPrefix() { NetworkDiagnostics.LogSessionSummary(); ShipSyncPatches.Reset(); SavePressureGuard.Reset(); } } internal enum PeerCongestionState { Open, Loading, Congested, Emergency } internal static class PeerTrafficController { internal sealed class PeerState { internal ZNetPeer Peer; internal PeerCongestionState State; internal float CreatedAt; internal float LastSeen; internal float LastSend; internal float NextEligibleSend; internal float NextQualitySample; internal float NextDiagnostic; internal float LastQueueSampleTime; internal int LastQueueSample; internal int Queue; internal int PeakQueue; internal float QueueEma; internal float QueueSlopeEma; internal int Ping = -1; internal float BaselinePing = -1f; internal float PingEma = -1f; internal float JitterEma; internal float Quality = -1f; internal float OutBytesPerSecond; internal float InBytesPerSecond; internal float Pressure; internal int PackageBudget; internal float SendInterval; internal int QueueBeforeSend; internal long SendAttempts; internal long SuccessfulSends; internal long FlushSends; internal long ForcedSends; internal long SkippedForQueue; internal long SkippedForInterval; internal long BytesEnqueued; internal long EmergencySamples; } private const float QualitySampleInterval = 1f; private const float StalePeerSeconds = 10f; private static readonly Dictionary States = new Dictionary(); [ThreadStatic] private static PeerState _activeSend; private static float _nextMaintenance; internal static int ActivePeerCount => States.Count; internal static bool TryPrepareSend(ZNetPeer peer, out PeerState state, out int queueBefore, out bool flush, out bool forced) { state = null; queueBefore = 0; flush = false; forced = false; if (peer?.m_socket == null || !peer.m_socket.IsConnected()) { return false; } float unscaledTime = Time.unscaledTime; state = GetOrCreate(peer, unscaledTime); state.LastSeen = unscaledTime; try { queueBefore = Math.Max(0, peer.m_socket.GetSendQueueSize()); } catch (Exception ex) { WarheimNetwork.Log.LogWarning((object)("[ZDO] Lecture de file impossible pour " + PeerLabel(peer) + " : " + ex.GetType().Name + ": " + ex.Message)); return false; } ObserveQueue(state, queueBefore); SampleAndAdapt(state, unscaledTime); float num = Math.Max(0.1f, NetworkConfig.ZdoMaximumStarvationSeconds.Value); forced = unscaledTime - state.LastSend >= num; if (unscaledTime < state.NextEligibleSend && !forced) { state.SkippedForInterval++; return false; } int num2 = EffectiveHardLimit(); if (queueBefore >= num2) { state.SkippedForQueue++; if (queueBefore >= EffectiveEmergencyLimit()) { state.EmergencySamples++; } return false; } float val = Math.Max(0.01f, NetworkConfig.ZdoSendInterval.Value); state.NextEligibleSend = unscaledTime + Math.Max(val, state.SendInterval); state.QueueBeforeSend = queueBefore; state.SendAttempts++; if (forced) { state.ForcedSends++; } int num3 = Math.Max(10240, EffectiveSoftLimit() * Mathf.Clamp(NetworkConfig.ZdoFlushThresholdPercent.Value, 5, 90) / 100); flush = queueBefore <= num3; if (flush) { state.FlushSends++; } return true; } internal static void BeginSend(PeerState state) { _activeSend = state; } internal static void EndSend(PeerState state, int queueBefore) { try { if (state?.Peer?.m_socket != null) { int num = Math.Max(0, state.Peer.m_socket.GetSendQueueSize()); int num2 = Math.Max(0, num - queueBefore); state.BytesEnqueued += num2; state.SuccessfulSends++; state.LastSend = Time.unscaledTime; ObserveQueue(state, num); } } catch { } finally { _activeSend = null; } } internal static int EffectiveQueueTarget(int configuredTarget) { if (_activeSend == null) { return Math.Max(10240, configuredTarget); } int val = (NetworkConfig.IsModuleEnabled(NetworkConfig.AdaptiveBackpressureEnabled) ? _activeSend.PackageBudget : configuredTarget); int num = _activeSend.QueueBeforeSend + Math.Max(10240, val); return Mathf.Clamp(num, 10240, EffectiveHardLimit()); } internal static int EffectiveQueueGate(int configuredGate) { if (_activeSend == null) { return Math.Max(10240, configuredGate); } return EffectiveHardLimit(); } internal static void Maintenance(float now) { if (now < _nextMaintenance) { return; } _nextMaintenance = now + 1f; List list = null; foreach (KeyValuePair state in States) { PeerState value = state.Value; if (now - value.LastSeen > 10f || state.Key?.m_socket == null || !state.Key.m_socket.IsConnected()) { if (list == null) { list = new List(); } list.Add(state.Key); LogPeerSummary(value, "retrait"); continue; } ConfigEntry verboseLogging = NetworkConfig.VerboseLogging; if (verboseLogging != null && verboseLogging.Value && now >= value.NextDiagnostic) { value.NextDiagnostic = now + Math.Max(5f, NetworkConfig.PeerDiagnosticsIntervalSeconds.Value); LogPeerSummary(value, "suivi"); value.PeakQueue = value.Queue; } } if (list == null) { return; } foreach (ZNetPeer item in list) { States.Remove(item); } } internal static void Reset() { States.Clear(); _activeSend = null; _nextMaintenance = 0f; } private static PeerState GetOrCreate(ZNetPeer peer, float now) { if (States.TryGetValue(peer, out var value)) { return value; } int packageBudget = Mathf.Clamp(NetworkConfig.ZdoQueueLimit.Value, NetworkConfig.ZdoMinimumPackageBytes.Value, NetworkConfig.ZdoMaximumPackageBytes.Value); value = new PeerState { Peer = peer, State = PeerCongestionState.Loading, CreatedAt = now, LastSeen = now, LastSend = now - Math.Max(0.1f, NetworkConfig.ZdoMaximumStarvationSeconds.Value), NextQualitySample = now, NextDiagnostic = now + Math.Max(5f, NetworkConfig.PeerDiagnosticsIntervalSeconds.Value), LastQueueSampleTime = now, PackageBudget = packageBudget, SendInterval = Math.Max(0.01f, NetworkConfig.ZdoSendInterval.Value) }; States.Add(peer, value); return value; } private static void ObserveQueue(PeerState state, int queue) { state.Queue = Math.Max(0, queue); state.PeakQueue = Math.Max(state.PeakQueue, state.Queue); state.QueueEma = ((state.QueueEma <= 0f) ? ((float)state.Queue) : Mathf.Lerp(state.QueueEma, (float)state.Queue, 0.2f)); } private static void SampleAndAdapt(PeerState state, float now) { if (now < state.NextQualitySample) { return; } state.NextQualitySample = now + 1f; float num = Math.Max(0.1f, now - state.LastQueueSampleTime); float num2 = (float)(state.Queue - state.LastQueueSample) / num; state.QueueSlopeEma = Mathf.Lerp(state.QueueSlopeEma, num2, 0.25f); state.LastQueueSample = state.Queue; state.LastQueueSampleTime = now; try { float localQuality = default(float); float remoteQuality = default(float); int num3 = default(int); float val = default(float); float val2 = default(float); state.Peer.m_socket.GetConnectionQuality(ref localQuality, ref remoteQuality, ref num3, ref val, ref val2); state.Ping = num3; state.Quality = NormalizeQuality(localQuality, remoteQuality); state.OutBytesPerSecond = Math.Max(0f, val); state.InBytesPerSecond = Math.Max(0f, val2); if (num3 > 0) { if (state.BaselinePing < 0f) { state.BaselinePing = num3; state.PingEma = num3; } else { state.BaselinePing = Math.Min(state.BaselinePing * 1.0025f, num3); float pingEma = state.PingEma; state.PingEma = Mathf.Lerp(state.PingEma, (float)num3, 0.15f); state.JitterEma = Mathf.Lerp(state.JitterEma, Math.Abs((float)num3 - pingEma), 0.2f); } } } catch { state.Ping = -1; state.Quality = -1f; state.OutBytesPerSecond = 0f; state.InBytesPerSecond = 0f; } float num4 = EffectiveSoftLimit(); float num5 = EffectiveHardLimit(); float val3 = Mathf.Clamp01((state.QueueEma - num4 * 0.35f) / Math.Max(1f, num5 - num4 * 0.35f)); float num6 = Math.Max(32768f, state.OutBytesPerSecond); float num7 = Mathf.Clamp01(Math.Max(0f, state.QueueSlopeEma) / num6); float num8 = Math.Max(20f, state.BaselinePing * 0.2f); float num9 = ((state.BaselinePing > 0f) ? Mathf.Clamp01((state.PingEma - state.BaselinePing - num8) / Math.Max(50f, state.BaselinePing)) : 0f); float num10 = ((state.Quality < 0f) ? 0f : Mathf.Clamp01((0.85f - state.Quality) / 0.5f)); state.Pressure = Math.Max(val3, Math.Max(num7 * 0.85f, Math.Max(num9 * 0.75f, num10 * 0.65f))); if (state.Queue >= EffectiveEmergencyLimit()) { state.Pressure = 1f; state.State = PeerCongestionState.Emergency; state.EmergencySamples++; } else if (state.Pressure >= 0.7f) { state.State = PeerCongestionState.Congested; } else if (now - state.CreatedAt <= Math.Max(5f, NetworkConfig.ZdoCatchUpSeconds.Value)) { state.State = PeerCongestionState.Loading; } else { state.State = PeerCongestionState.Open; } AdaptWindow(state); } private static void AdaptWindow(PeerState state) { int num = Math.Min(NetworkConfig.ZdoMinimumPackageBytes.Value, NetworkConfig.ZdoMaximumPackageBytes.Value); int num2 = Math.Max(NetworkConfig.ZdoMinimumPackageBytes.Value, NetworkConfig.ZdoMaximumPackageBytes.Value); float num3 = Math.Min(NetworkConfig.ZdoMinimumPeerInterval.Value, NetworkConfig.ZdoMaximumPeerInterval.Value); float num4 = Math.Max(NetworkConfig.ZdoMinimumPeerInterval.Value, NetworkConfig.ZdoMaximumPeerInterval.Value); if (!NetworkConfig.IsModuleEnabled(NetworkConfig.AdaptiveBackpressureEnabled)) { state.PackageBudget = Mathf.Clamp(NetworkConfig.ZdoQueueLimit.Value, num, num2); state.SendInterval = Math.Max(0.01f, NetworkConfig.ZdoSendInterval.Value); return; } if (state.Pressure >= 0.85f) { state.PackageBudget = Mathf.FloorToInt((float)state.PackageBudget * 0.65f); state.SendInterval *= 1.35f; } else if (state.Pressure >= 0.55f) { state.PackageBudget = Mathf.FloorToInt((float)state.PackageBudget * 0.82f); state.SendInterval *= 1.15f; } else if (state.Pressure <= 0.25f) { int num5 = Math.Max(2048, Mathf.FloorToInt((float)state.PackageBudget * 0.08f)); state.PackageBudget += num5; state.SendInterval -= 0.002f; } else { int num6 = Mathf.Clamp(NetworkConfig.ZdoQueueLimit.Value, num, num2); state.PackageBudget = Mathf.RoundToInt(Mathf.Lerp((float)state.PackageBudget, (float)num6, 0.05f)); state.SendInterval = Mathf.Lerp(state.SendInterval, NetworkConfig.ZdoSendInterval.Value, 0.05f); } state.PackageBudget = Mathf.Clamp(state.PackageBudget, num, num2); state.SendInterval = Mathf.Clamp(state.SendInterval, num3, num4); } private static int EffectiveSoftLimit() { return Math.Max(10240, NetworkConfig.ZdoQueueSoftLimit.Value); } private static int EffectiveHardLimit() { return Math.Max(EffectiveSoftLimit() + 10240, NetworkConfig.ZdoQueueHardLimit.Value); } private static int EffectiveEmergencyLimit() { return Math.Max(EffectiveHardLimit() + 10240, NetworkConfig.ZdoQueueEmergencyLimit.Value); } private static float NormalizeQuality(float localQuality, float remoteQuality) { float num = NormalizeQualityValue(localQuality); float num2 = NormalizeQualityValue(remoteQuality); if (num < 0f) { return num2; } if (num2 < 0f) { return num; } return Math.Min(num, num2); } private static float NormalizeQualityValue(float value) { if (value < 0f) { return -1f; } return (value > 1f) ? Mathf.Clamp01(value / 100f) : Mathf.Clamp01(value); } private static void LogPeerSummary(PeerState state, string reason) { if (state?.Peer != null) { WarheimNetwork.Log.LogInfo((object)($"[Peer] {reason} {PeerLabel(state.Peer)} | état={state.State}, pression={state.Pressure:P0}, " + $"queue={state.Queue}/{state.PeakQueue}, pente={state.QueueSlopeEma / 1024f:F0} Ko/s, " + $"rtt={state.Ping} ms, base={state.BaselinePing:F0}, jitter={state.JitterEma:F0}, qualité={FormatQuality(state.Quality)}, " + $"débit={state.OutBytesPerSecond / 1024f:F0}/{state.InBytesPerSecond / 1024f:F0} Ko/s, " + $"budget={state.PackageBudget}, intervalle={state.SendInterval:F3}s, envois={state.SuccessfulSends}, " + $"flush={state.FlushSends}, forcés={state.ForcedSends}, skipQueue={state.SkippedForQueue}, " + $"skipInterval={state.SkippedForInterval}, injecté={(double)state.BytesEnqueued / 1048576.0:F1} Mio.")); } } private static string PeerLabel(ZNetPeer peer) { string arg = (string.IsNullOrWhiteSpace(peer?.m_playerName) ? "peer" : peer.m_playerName); return $"{arg}/{peer?.m_uid ?? 0}"; } private static string FormatQuality(float quality) { return (quality < 0f) ? "n/a" : $"{quality * 100f:F0}%"; } } internal static class SavePressureGuard { private struct SaveRecord { internal int Prefab; internal Vector2s Sector; internal Vector3 Rotation; internal Vector3 Position; internal bool Persistent; internal bool Distant; internal ObjectType Type; internal ZDOConnectionHashData Connection; internal KeyValuePair[] Floats; internal int FloatCount; internal KeyValuePair[] Vec3; internal int Vec3Count; internal KeyValuePair[] Quats; internal int QuatCount; internal KeyValuePair[] Ints; internal int IntCount; internal KeyValuePair[] Longs; internal int LongCount; internal KeyValuePair[] Strings; internal int StringCount; internal KeyValuePair[] ByteArrays; internal int ByteArrayCount; } private static class BlockAccessor { private static KeyValuePair[] _buffer = Array.Empty>(); internal static bool Validate() { return typeof(ICollection>).IsAssignableFrom(typeof(BinarySearchDictionary)); } internal static KeyValuePair[] Rent(int count) { KeyValuePair[] buffer = _buffer; if (buffer.Length >= count) { return buffer; } int num = ((buffer.Length == 0) ? 4 : buffer.Length); while (num < count) { int num2 = ((num < 1024) ? (num * 2) : (num + num / 2)); if (num2 <= num) { num = count; break; } num = num2; } return _buffer = new KeyValuePair[num]; } } private static class IlReader { internal static readonly OpCode[] OneByte; internal static readonly OpCode[] TwoByte; static IlReader() { OneByte = new OpCode[256]; TwoByte = new OpCode[256]; FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public); for (int i = 0; i < fields.Length; i++) { if (fields[i].GetValue(null) is OpCode opCode) { ushort num = (ushort)opCode.Value; if (num < 256) { OneByte[num] = opCode; } else if ((num & 0xFF00) == 65024) { TwoByte[num & 0xFF] = opCode; } } } } } private static readonly MethodInfo RegenerateConnectionsMethod = AccessTools.Method(typeof(ZDOExtraData), "RegenerateConnectionHashData", (Type[])null, (Type[])null); private static readonly FieldInfo FloatsField = AccessTools.Field(typeof(ZDOExtraData), "s_floats"); private static readonly FieldInfo Vec3Field = AccessTools.Field(typeof(ZDOExtraData), "s_vec3"); private static readonly FieldInfo QuatsField = AccessTools.Field(typeof(ZDOExtraData), "s_quats"); private static readonly FieldInfo IntsField = AccessTools.Field(typeof(ZDOExtraData), "s_ints"); private static readonly FieldInfo LongsField = AccessTools.Field(typeof(ZDOExtraData), "s_longs"); private static readonly FieldInfo StringsField = AccessTools.Field(typeof(ZDOExtraData), "s_strings"); private static readonly FieldInfo ByteArraysField = AccessTools.Field(typeof(ZDOExtraData), "s_byteArrays"); private static readonly FieldInfo ConnectionsField = AccessTools.Field(typeof(ZDOExtraData), "s_connectionsHashData"); private static readonly FieldInfo SaveFloatsField = AccessTools.Field(typeof(ZDOExtraData), "s_saveFloats"); private static readonly FieldInfo SaveVec3Field = AccessTools.Field(typeof(ZDOExtraData), "s_saveVec3s"); private static readonly FieldInfo SaveQuatsField = AccessTools.Field(typeof(ZDOExtraData), "s_saveQuats"); private static readonly FieldInfo SaveIntsField = AccessTools.Field(typeof(ZDOExtraData), "s_saveInts"); private static readonly FieldInfo SaveLongsField = AccessTools.Field(typeof(ZDOExtraData), "s_saveLongs"); private static readonly FieldInfo SaveStringsField = AccessTools.Field(typeof(ZDOExtraData), "s_saveStrings"); private static readonly FieldInfo SaveByteArraysField = AccessTools.Field(typeof(ZDOExtraData), "s_saveByteArrays"); private static readonly FieldInfo SaveConnectionsField = AccessTools.Field(typeof(ZDOExtraData), "s_saveConnections"); private static FieldRef _zdoPrefab; private static FieldRef _zdoSector; private static FieldRef _zdoRotation; private static FieldRef _zdoPosition; private static Dictionary> _snapshotFloats; private static Dictionary> _snapshotVec3; private static Dictionary> _snapshotQuats; private static Dictionary> _snapshotInts; private static Dictionary> _snapshotLongs; private static Dictionary> _snapshotStrings; private static Dictionary> _snapshotByteArrays; private static Dictionary _snapshotConnections; private static bool _installed; private static bool _guardReady; private static bool _snapshotFieldsReady; private static bool _serializerReady; private static bool _compatibilityAudited; private static int _barrier; private static int _snapshotBound; private static int _adminSaveDepth; private static float _barrierStartedAt; private static long _lastErrorLogTicks; private static long _barrierStartManagedBytes; private static int _barrierStartZdos; private static string _saveOrigin = "inconnue"; internal static bool IsActive => Volatile.Read(in _barrier) != 0; internal static void Install(Harmony harmony) { //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Expected O, but got Unknown //IL_0215: Expected O, but got Unknown //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_024a: Expected O, but got Unknown //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Expected O, but got Unknown //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Expected O, but got Unknown //IL_02f3: Expected O, but got Unknown if (_installed) { return; } _installed = true; _snapshotFieldsReady = ValidateSnapshotFields(); MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "Save", new Type[3] { typeof(bool), typeof(bool), typeof(bool) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "SaveWorld", new Type[1] { typeof(bool) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ZDOExtraData), "PrepareSave", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(ZNet), "Update", (Type[])null, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(typeof(ZDO), "Save", new Type[1] { typeof(ZPackage) }, (Type[])null); MethodInfo methodInfo6 = AccessTools.Method(typeof(ZNet), "RPC_Save", new Type[1] { typeof(ZRpc) }, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null || methodInfo5 == null) { WarheimNetwork.Log.LogError((object)($"[SaveGuard] Installation refusée : Save={methodInfo != null}, SaveWorld={methodInfo2 != null}, " + $"PrepareSave={methodInfo3 != null}, Update={methodInfo4 != null}, ZDO.Save={methodInfo5 != null}.")); return; } _serializerReady = _snapshotFieldsReady && ValidateSerializerContract(methodInfo5); if (_serializerReady) { WarheimNetwork.Log.LogInfo((object)"[SaveGuard] BinarySearchDictionary validé via API publique Count/CopyTo, sans dépendance aux champs privés."); } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SavePressureGuard), "SavePrefix", (Type[])null), new HarmonyMethod(typeof(SavePressureGuard), "SavePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SavePressureGuard), "SaveWorldPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(SavePressureGuard), "SaveWorldFinalizer", (Type[])null), (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(SavePressureGuard), "PrepareSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(SavePressureGuard), "ZdoSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(SavePressureGuard), "ZNetUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (methodInfo6 != null) { harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(typeof(SavePressureGuard), "RpcSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(SavePressureGuard), "RpcSaveFinalizer", (Type[])null), (HarmonyMethod)null); } _guardReady = true; WarheimNetwork.Log.LogInfo((object)$"[SaveGuard] Protection installée. Snapshot={_snapshotFieldsReady}, sérialiseur ZDO sans listes={_serializerReady}, origine admin={methodInfo6 != null}."); } catch (Exception ex) { WarheimNetwork.Log.LogError((object)("[SaveGuard] Installation impossible : " + ex.GetType().Name + ": " + ex.Message)); } } internal static void Reset() { Interlocked.Exchange(ref _barrier, 0); Interlocked.Exchange(ref _snapshotBound, 0); Interlocked.Exchange(ref _adminSaveDepth, 0); _barrierStartedAt = 0f; _barrierStartManagedBytes = 0L; _barrierStartZdos = 0; _saveOrigin = "inconnue"; ClearSnapshotReferences(clearGameSnapshotFields: false); } private static void RpcSavePrefix() { Interlocked.Increment(ref _adminSaveDepth); if (ShouldProtectSave()) { _saveOrigin = "commande admin vanilla"; EnterBarrier("commande admin vanilla / sauvegarde des profils"); } } private static Exception RpcSaveFinalizer(Exception __exception) { if (Interlocked.Decrement(ref _adminSaveDepth) < 0) { Interlocked.Exchange(ref _adminSaveDepth, 0); } if (IsActive && ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsSaving())) { ExitBarrier("fin RPC_Save"); } return __exception; } private static void SavePrefix(bool __2, out bool __state) { __state = ShouldProtectSave(); if (__state) { _saveOrigin = ((Volatile.Read(in _adminSaveDepth) > 0) ? "commande admin vanilla" : (__2 ? "autosave ou sauvegarde différée" : "sauvegarde système immédiate")); EnterBarrier(_saveOrigin); NetworkDiagnostics.RecordSaveRequest(_saveOrigin); } } private static void SavePostfix(bool __2, bool __state) { if (__state && !__2) { ExitBarrier("retour Save immédiat"); } } private static void SaveWorldPrefix(ref bool __0) { if (ShouldProtectSave()) { EnterBarrier("SaveWorld/" + _saveOrigin); if (!__0) { __0 = true; NetworkDiagnostics.RecordForcedSynchronousSave(); } } } private static Exception SaveWorldFinalizer(Exception __exception) { ClearSnapshotReferences(__exception != null); ExitBarrier("fin SaveWorld"); return __exception; } private static bool PrepareSavePrefix() { Interlocked.Exchange(ref _snapshotBound, 0); ClearSnapshotReferences(clearGameSnapshotFields: false); if (!ShouldProtectSave() || !_snapshotFieldsReady) { return true; } try { RegenerateConnectionsMethod.Invoke(null, null); Dictionary> dictionary = (Dictionary>)FloatsField.GetValue(null); Dictionary> dictionary2 = (Dictionary>)Vec3Field.GetValue(null); Dictionary> dictionary3 = (Dictionary>)QuatsField.GetValue(null); Dictionary> dictionary4 = (Dictionary>)IntsField.GetValue(null); Dictionary> dictionary5 = (Dictionary>)LongsField.GetValue(null); Dictionary> dictionary6 = (Dictionary>)StringsField.GetValue(null); Dictionary> dictionary7 = (Dictionary>)ByteArraysField.GetValue(null); Dictionary dictionary8 = (Dictionary)ConnectionsField.GetValue(null); Dictionary> dictionary9 = new Dictionary>(dictionary); Dictionary> dictionary10 = new Dictionary>(dictionary2); Dictionary> dictionary11 = new Dictionary>(dictionary3); Dictionary> dictionary12 = new Dictionary>(dictionary4); Dictionary> dictionary13 = new Dictionary>(dictionary5); Dictionary> dictionary14 = new Dictionary>(dictionary6); Dictionary> dictionary15 = new Dictionary>(dictionary7); Dictionary dictionary16 = new Dictionary(dictionary8); SaveFloatsField.SetValue(null, dictionary9); SaveVec3Field.SetValue(null, dictionary10); SaveQuatsField.SetValue(null, dictionary11); SaveIntsField.SetValue(null, dictionary12); SaveLongsField.SetValue(null, dictionary13); SaveStringsField.SetValue(null, dictionary14); SaveByteArraysField.SetValue(null, dictionary15); SaveConnectionsField.SetValue(null, dictionary16); long avoidedInnerClones = (long)dictionary9.Count + (long)dictionary10.Count + dictionary11.Count + dictionary12.Count + dictionary13.Count + dictionary14.Count + dictionary15.Count + dictionary16.Count; NetworkDiagnostics.RecordLowAllocationSaveSnapshot(avoidedInnerClones); if (_serializerReady) { _snapshotFloats = dictionary9; _snapshotVec3 = dictionary10; _snapshotQuats = dictionary11; _snapshotInts = dictionary12; _snapshotLongs = dictionary13; _snapshotStrings = dictionary14; _snapshotByteArrays = dictionary15; _snapshotConnections = dictionary16; Volatile.Write(ref _snapshotBound, 1); } return false; } catch (Exception ex) { Interlocked.Exchange(ref _snapshotBound, 0); ClearSnapshotReferences(clearGameSnapshotFields: true); LogFailure("snapshot de sauvegarde", ex); return true; } } private static bool ZdoSavePrefix(ZDO __instance, ZPackage pkg) { if (!ShouldUseFastSerializer() || __instance == null || pkg == null) { return true; } if (!TryCaptureRecord(__instance, out var record)) { return true; } WriteRecord(pkg, ref record); NetworkDiagnostics.RecordFastSerializedZdo(record.FloatCount + record.Vec3Count + record.QuatCount + record.IntCount + record.LongCount + record.StringCount + record.ByteArrayCount); return false; } private static bool TryCaptureRecord(ZDO zdo, out SaveRecord record) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: 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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_013e: 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) record = default(SaveRecord); try { Dictionary> snapshotFloats = _snapshotFloats; Dictionary> snapshotVec = _snapshotVec3; Dictionary> snapshotQuats = _snapshotQuats; Dictionary> snapshotInts = _snapshotInts; Dictionary> snapshotLongs = _snapshotLongs; Dictionary> snapshotStrings = _snapshotStrings; Dictionary> snapshotByteArrays = _snapshotByteArrays; Dictionary snapshotConnections = _snapshotConnections; if (snapshotFloats == null || snapshotVec == null || snapshotQuats == null || snapshotInts == null || snapshotLongs == null || snapshotStrings == null || snapshotByteArrays == null || snapshotConnections == null) { return false; } ZDOID uid = zdo.m_uid; snapshotFloats.TryGetValue(uid, out var value); snapshotVec.TryGetValue(uid, out var value2); snapshotQuats.TryGetValue(uid, out var value3); snapshotInts.TryGetValue(uid, out var value4); snapshotLongs.TryGetValue(uid, out var value5); snapshotStrings.TryGetValue(uid, out var value6); snapshotByteArrays.TryGetValue(uid, out var value7); snapshotConnections.TryGetValue(uid, out var value8); record.Prefab = _zdoPrefab.Invoke(zdo); record.Sector = _zdoSector.Invoke(zdo); record.Rotation = _zdoRotation.Invoke(zdo); record.Position = _zdoPosition.Invoke(zdo); record.Persistent = zdo.Persistent; record.Distant = zdo.Distant; record.Type = zdo.Type; record.Connection = value8; CaptureBlock(value, out record.Floats, out record.FloatCount); CaptureBlock(value2, out record.Vec3, out record.Vec3Count); CaptureBlock(value3, out record.Quats, out record.QuatCount); CaptureBlock(value4, out record.Ints, out record.IntCount); CaptureBlock(value5, out record.Longs, out record.LongCount); CaptureBlock(value6, out record.Strings, out record.StringCount); CaptureBlock(value7, out record.ByteArrays, out record.ByteArrayCount); return true; } catch (Exception ex) { DisableFastSerializer(ex); record = default(SaveRecord); return false; } } private static void WriteRecord(ZPackage pkg, ref SaveRecord record) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Expected I4, but got Unknown ushort num = 0; if (record.Connection != null && (int)record.Connection.m_type > 0) { num |= 1; } if (record.FloatCount > 0) { num |= 2; } if (record.Vec3Count > 0) { num |= 4; } if (record.QuatCount > 0) { num |= 8; } if (record.IntCount > 0) { num |= 0x10; } if (record.LongCount > 0) { num |= 0x20; } if (record.StringCount > 0) { num |= 0x40; } if (record.ByteArrayCount > 0) { num |= 0x80; } bool flag = record.Rotation != Vector3.zero; num |= (ushort)(record.Persistent ? 256 : 0); num |= (ushort)(record.Distant ? 512 : 0); num |= (ushort)(record.Type << 10); num |= (ushort)(flag ? 4096 : 0); pkg.Write(num); pkg.Write(record.Sector); pkg.Write(record.Position); pkg.Write(record.Prefab); if (flag) { pkg.Write(record.Rotation); } if ((num & 0xFF) != 0) { if ((num & 1) != 0) { pkg.Write((byte)(int)record.Connection.m_type); pkg.Write(record.Connection.m_hash); } WriteFloatData(pkg, record.Floats, record.FloatCount); WriteVec3Data(pkg, record.Vec3, record.Vec3Count); WriteQuatData(pkg, record.Quats, record.QuatCount); WriteIntData(pkg, record.Ints, record.IntCount); WriteLongData(pkg, record.Longs, record.LongCount); WriteStringData(pkg, record.Strings, record.StringCount); WriteByteArrayData(pkg, record.ByteArrays, record.ByteArrayCount); } } private static void WriteFloatData(ZPackage pkg, KeyValuePair[] data, int count) { if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); } } } private static void WriteVec3Data(ZPackage pkg, KeyValuePair[] data, int count) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); } } } private static void WriteQuatData(ZPackage pkg, KeyValuePair[] data, int count) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); } } } private static void WriteIntData(ZPackage pkg, KeyValuePair[] data, int count) { if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); } } } private static void WriteLongData(ZPackage pkg, KeyValuePair[] data, int count) { if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); } } } private static void WriteStringData(ZPackage pkg, KeyValuePair[] data, int count) { if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); data[i] = default(KeyValuePair); } } } private static void WriteByteArrayData(ZPackage pkg, KeyValuePair[] data, int count) { if (count > 0) { pkg.WriteNumItems(count); for (int i = 0; i < count; i++) { pkg.Write(data[i].Key); pkg.Write(data[i].Value); data[i] = default(KeyValuePair); } } } private static void CaptureBlock(BinarySearchDictionary dictionary, out KeyValuePair[] block, out int count) { if (dictionary == null) { block = null; count = 0; return; } count = dictionary.Count; if (count <= 0) { block = null; count = 0; } else { block = BlockAccessor.Rent(count); dictionary.CopyTo(block, 0); } } internal static void FinalizeCompatibilityAudit() { if (_compatibilityAudited) { return; } _compatibilityAudited = true; if (_serializerReady) { string foreignPatchOwners = GetForeignPatchOwners(AccessTools.Method(typeof(ZDO), "Save", new Type[1] { typeof(ZPackage) }, (Type[])null)); string foreignPatchOwners2 = GetForeignPatchOwners(AccessTools.Method(typeof(ZDOExtraData), "PrepareSave", (Type[])null, (Type[])null)); if (foreignPatchOwners.Length == 0 && foreignPatchOwners2.Length == 0) { WarheimNetwork.Log.LogInfo((object)"[SaveGuard] Compatibilité finale validée : sérialiseur ZDO sans listes autorisé."); return; } _serializerReady = false; WarheimNetwork.Log.LogWarning((object)("[SaveGuard] Sérialiseur ZDO sans listes désactivé par sécurité. Patchs étrangers ZDO.Save=[" + foreignPatchOwners + "], ZDOExtraData.PrepareSave=[" + foreignPatchOwners2 + "]. La barrière et le snapshot faible allocation restent actifs.")); } } private static string GetForeignPatchOwners(MethodBase method) { if (method == null) { return "méthode introuvable"; } Patches patchInfo = Harmony.GetPatchInfo(method); if (patchInfo == null) { return string.Empty; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); AddForeignOwners(hashSet, patchInfo.Prefixes); AddForeignOwners(hashSet, patchInfo.Postfixes); AddForeignOwners(hashSet, patchInfo.Transpilers); AddForeignOwners(hashSet, patchInfo.Finalizers); return (hashSet.Count == 0) ? string.Empty : string.Join(", ", hashSet); } private static void AddForeignOwners(HashSet owners, IEnumerable patches) { foreach (Patch patch in patches) { if (!string.IsNullOrEmpty(patch.owner) && !string.Equals(patch.owner, "dzk.warheimnetwork", StringComparison.OrdinalIgnoreCase)) { owners.Add(patch.owner); } } } private static void ZNetUpdatePostfix(ZNet __instance) { if (IsActive && !(Time.unscaledTime - _barrierStartedAt < 5f) && ((Object)(object)__instance == (Object)null || !__instance.IsSaving())) { ClearSnapshotReferences(clearGameSnapshotFields: true); ExitBarrier("watchdog"); } } private static bool ShouldProtectSave() { return _guardReady && Application.isBatchMode && NetworkConfig.IsModuleEnabled(NetworkConfig.SaveGuardEnabled); } private static bool ShouldUseFastSerializer() { return _guardReady && _serializerReady && IsActive && Volatile.Read(in _snapshotBound) != 0; } private static void EnterBarrier(string reason) { if (Interlocked.Exchange(ref _barrier, 1) == 0) { _barrierStartedAt = Time.unscaledTime; _barrierStartManagedBytes = GC.GetTotalMemory(forceFullCollection: false); _barrierStartZdos = ((ZDOMan.instance != null) ? ZDOMan.instance.NrOfObjects() : 0); TransportCompression.ReleaseWorkingBuffer(); NetworkDiagnostics.RecordSaveBarrier(); WarheimNetwork.Log.LogInfo((object)$"[SaveGuard] Barrière réseau activée ({reason}) | ZDO={_barrierStartZdos}, heap géré={FormatMiB(_barrierStartManagedBytes)} MiB."); } } private static void ExitBarrier(string reason) { if (Interlocked.Exchange(ref _barrier, 0) != 0) { float num = Math.Max(0f, Time.unscaledTime - _barrierStartedAt); long totalMemory = GC.GetTotalMemory(forceFullCollection: false); long bytes = totalMemory - _barrierStartManagedBytes; _barrierStartedAt = 0f; _barrierStartManagedBytes = 0L; _barrierStartZdos = 0; _saveOrigin = "inconnue"; WarheimNetwork.Log.LogInfo((object)$"[SaveGuard] Barrière réseau levée ({reason}, {num:F2}s) | heap géré={FormatMiB(totalMemory)} MiB, delta={FormatSignedMiB(bytes)} MiB."); } } private static string FormatMiB(long bytes) { return ((double)bytes / 1048576.0).ToString("F1", CultureInfo.InvariantCulture); } private static string FormatSignedMiB(long bytes) { return ((double)bytes / 1048576.0).ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture); } private static void ClearSnapshotReferences(bool clearGameSnapshotFields) { Interlocked.Exchange(ref _snapshotBound, 0); _snapshotFloats = null; _snapshotVec3 = null; _snapshotQuats = null; _snapshotInts = null; _snapshotLongs = null; _snapshotStrings = null; _snapshotByteArrays = null; _snapshotConnections = null; if (!clearGameSnapshotFields || !_snapshotFieldsReady) { return; } try { SaveFloatsField.SetValue(null, null); SaveVec3Field.SetValue(null, null); SaveQuatsField.SetValue(null, null); SaveIntsField.SetValue(null, null); SaveLongsField.SetValue(null, null); SaveStringsField.SetValue(null, null); SaveByteArraysField.SetValue(null, null); SaveConnectionsField.SetValue(null, null); } catch (Exception ex) { LogFailure("nettoyage du snapshot", ex); } } private static bool ValidateSnapshotFields() { FieldInfo[] array = new FieldInfo[16] { FloatsField, Vec3Field, QuatsField, IntsField, LongsField, StringsField, ByteArraysField, ConnectionsField, SaveFloatsField, SaveVec3Field, SaveQuatsField, SaveIntsField, SaveLongsField, SaveStringsField, SaveByteArraysField, SaveConnectionsField }; if (RegenerateConnectionsMethod == null) { WarheimNetwork.Log.LogError((object)"[SaveGuard] RegenerateConnectionHashData introuvable. Snapshot vanilla conservé."); return false; } for (int i = 0; i < array.Length; i++) { if (array[i] == null) { WarheimNetwork.Log.LogError((object)"[SaveGuard] Structure ZDOExtraData incompatible. Snapshot vanilla conservé."); return false; } } return true; } private static bool ValidateSerializerContract(MethodInfo zdoSave) { try { _zdoPrefab = AccessTools.FieldRefAccess("m_prefab"); _zdoSector = AccessTools.FieldRefAccess("m_sector"); _zdoRotation = AccessTools.FieldRefAccess("m_rotation"); _zdoPosition = AccessTools.FieldRefAccess("m_position"); if (!BlockAccessor.Validate() || !BlockAccessor.Validate() || !BlockAccessor.Validate() || !BlockAccessor.Validate() || !BlockAccessor.Validate() || !BlockAccessor.Validate() || !BlockAccessor.Validate()) { WarheimNetwork.Log.LogError((object)"[SaveGuard] Structure BinarySearchDictionary incompatible. Sérialiseur vanilla conservé."); return false; } HashSet hashSet = new HashSet(StringComparer.Ordinal) { "ZDOExtraData.GetSaveFloats", "ZDOExtraData.GetSaveVec3s", "ZDOExtraData.GetSaveQuaternions", "ZDOExtraData.GetSaveInts", "ZDOExtraData.GetSaveLongs", "ZDOExtraData.GetSaveStrings", "ZDOExtraData.GetSaveByteArrays", "ZDOExtraData.GetSaveConnections", "ZDODataHelper.WriteData" }; HashSet hashSet2 = ReadCalledMethods(zdoSave); foreach (string item in hashSet) { if (!hashSet2.Contains(item)) { WarheimNetwork.Log.LogError((object)("[SaveGuard] Contrat ZDO.Save incompatible : appel manquant " + item + ". Sérialiseur vanilla conservé.")); return false; } } return true; } catch (Exception ex) { LogFailure("validation du sérialiseur ZDO", ex); return false; } } private static HashSet ReadCalledMethods(MethodInfo method) { HashSet hashSet = new HashSet(StringComparer.Ordinal); byte[] array = method.GetMethodBody()?.GetILAsByteArray(); if (array == null || array.Length == 0) { return hashSet; } int i = 0; Module module = method.Module; OpCode opCode; for (; i < array.Length; i += OperandSize(opCode.OperandType, array, i)) { byte b = array[i++]; opCode = ((b != 254) ? IlReader.OneByte[b] : IlReader.TwoByte[array[i++]]); if (opCode.OperandType != OperandType.InlineMethod) { continue; } int metadataToken = BitConverter.ToInt32(array, i); try { MethodBase methodBase = module.ResolveMethod(metadataToken); if (methodBase?.DeclaringType != null) { hashSet.Add(methodBase.DeclaringType.Name + "." + methodBase.Name); } } catch { } } return hashSet; } private static int OperandSize(OperandType operandType, byte[] il, int position) { switch (operandType) { case OperandType.InlineNone: return 0; case OperandType.ShortInlineBrTarget: case OperandType.ShortInlineI: case OperandType.ShortInlineVar: return 1; case OperandType.InlineVar: return 2; case OperandType.InlineI8: case OperandType.InlineR: return 8; case OperandType.InlineSwitch: { int num = BitConverter.ToInt32(il, position); return 4 + num * 4; } default: return 4; } } private static void DisableFastSerializer(Exception ex) { _serializerReady = false; Interlocked.Exchange(ref _snapshotBound, 0); LogFailure("sérialiseur ZDO sans listes, désactivé pour la session", ex); } private static void LogFailure(string operation, Exception ex) { long ticks = DateTime.UtcNow.Ticks; long num = Interlocked.Read(in _lastErrorLogTicks); if (num == 0L || ticks - num >= 100000000) { Interlocked.Exchange(ref _lastErrorLogTicks, ticks); Exception ex2 = ((ex is TargetInvocationException && ex.InnerException != null) ? ex.InnerException : ex); WarheimNetwork.Log.LogError((object)("[SaveGuard] Échec " + operation + ". Retour de sécurité : " + ex2.GetType().Name + ": " + ex2.Message)); } } } [HarmonyPatch] internal static class ShipSyncPatches { private const float RudderSendInterval = 0.05f; private const float AttachedPositionSmoothTime = 0.08f; private const float AttachedRotationSmoothTime = 0.1f; private const float AttachedSnapDistance = 5f; private static readonly FieldInfo SendRudderTimeField = AccessTools.Field(typeof(Ship), "m_sendRudderTime"); private static readonly Dictionary PlayerVelocities = new Dictionary(); private static float _lastWarningTime = -999f; internal static void Validate() { if (SendRudderTimeField == null) { WarheimNetwork.Log.LogError((object)"[ShipSync] Ship.m_sendRudderTime introuvable. L'envoi du gouvernail à 20 Hz est désactivé."); } else { WarheimNetwork.Log.LogInfo((object)"[ShipSync] Gouvernail 20 Hz, interpolation Rigidbody et lissage des joueurs attachés installés."); } } internal static void Reset() { PlayerVelocities.Clear(); } [HarmonyPatch(typeof(Ship), "ApplyControlls")] [HarmonyPostfix] private static void FasterRudderPostfix(Ship __instance) { if (!NetworkConfig.IsModuleEnabled(NetworkConfig.ShipSyncEnabled) || SendRudderTimeField == null || (Object)(object)__instance == (Object)null || (Object)(object)__instance.m_nview == (Object)null || !__instance.m_nview.IsValid() || !__instance.m_nview.IsOwner()) { return; } try { float num = (float)SendRudderTimeField.GetValue(__instance); float time = Time.time; if (!(time - num < 0.05f)) { SendRudderTimeField.SetValue(__instance, time); __instance.m_nview.InvokeRPC("Rudder", new object[1] { __instance.m_rudderValue }); NetworkDiagnostics.RecordShipRudderSend(); } } catch (Exception ex) { if (Time.unscaledTime - _lastWarningTime >= 10f) { _lastWarningTime = Time.unscaledTime; WarheimNetwork.Log.LogWarning((object)("[ShipSync] Envoi du gouvernail impossible : " + ex.GetType().Name + ": " + ex.Message)); } } } [HarmonyPatch(typeof(Ship), "Awake")] [HarmonyPostfix] private static void EnableShipInterpolationPostfix(Ship __instance) { if (NetworkConfig.IsModuleEnabled(NetworkConfig.ShipSyncEnabled) && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.m_body == (Object)null)) { __instance.m_body.interpolation = (RigidbodyInterpolation)1; if (!__instance.m_body.isKinematic) { __instance.m_body.collisionDetectionMode = (CollisionDetectionMode)2; } } } [HarmonyPatch(typeof(Player), "UpdateAttach")] [HarmonyPostfix] [HarmonyPriority(0)] private static void SmoothAttachedPlayerPostfix(Player __instance) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!NetworkConfig.IsModuleEnabled(NetworkConfig.ShipSyncEnabled) || !__instance.m_attached || !__instance.m_attachedToShip || (Object)(object)__instance.m_attachPoint == (Object)null) { PlayerVelocities.Remove(instanceID); return; } Transform attachPoint = __instance.m_attachPoint; Transform transform = ((Component)__instance).transform; Vector3 position = attachPoint.position; Quaternion rotation = attachPoint.rotation; if (!PlayerVelocities.TryGetValue(instanceID, out var value)) { PlayerVelocities[instanceID] = Vector3.zero; transform.SetPositionAndRotation(position, rotation); NetworkDiagnostics.RecordShipAttachSnap(); return; } Vector3 val = transform.position - position; if (((Vector3)(ref val)).sqrMagnitude >= 25f) { PlayerVelocities[instanceID] = Vector3.zero; transform.SetPositionAndRotation(position, rotation); NetworkDiagnostics.RecordShipAttachSnap(); } else { transform.position = Vector3.SmoothDamp(transform.position, position, ref value, 0.08f); PlayerVelocities[instanceID] = value; float num = Mathf.Clamp01(Time.deltaTime / 0.1f); transform.rotation = Quaternion.Slerp(transform.rotation, rotation, num); } } [HarmonyPatch(typeof(Player), "AttachStop")] [HarmonyPrefix] private static void ClearCacheOnDetachPrefix(Player __instance) { if ((Object)(object)__instance != (Object)null) { PlayerVelocities.Remove(((Object)__instance).GetInstanceID()); } } [HarmonyPatch(typeof(Player), "OnDestroy")] [HarmonyPrefix] private static void ClearCacheOnDestroyPrefix(Player __instance) { if ((Object)(object)__instance != (Object)null) { PlayerVelocities.Remove(((Object)__instance).GetInstanceID()); } } } [HarmonyPatch] internal static class SteamTransportPatches { private const int VanillaSendRateMaxBytes = 153600; private static readonly HashSet UnsupportedKeysLogged = new HashSet(); private static bool _applyInProgress; private static bool _steamInitializationObserved; private static bool _steamReady; private static bool _lastAttemptSteamNotInitialized; private static float _nextRetryTime; private static string _lastFingerprint = string.Empty; private static string _selectedUtilityTypeName = string.Empty; [HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")] [HarmonyTranspiler] private static IEnumerable RegisterGlobalCallbacksTranspiler(IEnumerable instructions) { List list = new List(instructions); List list2 = new List(); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Ldc_I4 && list[i].operand is int num && num == 153600) { list2.Add(i); } } if (list2.Count != 1) { WarheimNetwork.Log.LogError((object)($"[Steam] Patch SendRateMax refusé : 1 constante {153600} attendue, " + $"{list2.Count} trouvée(s). Vanilla conservé.")); return list; } MethodInfo methodInfo = AccessTools.Method(typeof(NetworkConfig), "EffectiveSteamSendRateMaxBytes", (Type[])null, (Type[])null); if (methodInfo == null) { WarheimNetwork.Log.LogError((object)"[Steam] Getter SendRateMax introuvable. Vanilla conservé."); return list; } list[list2[0]].opcode = OpCodes.Call; list[list2[0]].operand = methodInfo; WarheimNetwork.Log.LogInfo((object)"[Steam] SendRateMax dynamique installé avec validation stricte."); return list; } [HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")] [HarmonyPostfix] private static void RegisterGlobalCallbacksPostfix() { _steamInitializationObserved = true; _nextRetryTime = Time.unscaledTime + 0.5f; } [HarmonyPatch(typeof(ZNet), "Update")] [HarmonyPostfix] private static void ZNetUpdatePostfix() { if (_steamInitializationObserved && !_steamReady && !(Time.unscaledTime < _nextRetryTime)) { _nextRetryTime = Time.unscaledTime + 1f; ApplySettings("initialisation SteamNetworkingSockets", force: true, allowProbe: true); } } internal static bool ApplySettings(string reason, bool force, bool allowProbe = false) { if (_applyInProgress) { return false; } if (!_steamReady && !allowProbe) { return false; } if (!NetworkConfig.IsModuleEnabled(NetworkConfig.SteamTransportEnabled)) { ConfigEntry verboseLogging = NetworkConfig.VerboseLogging; if (verboseLogging != null && verboseLogging.Value) { WarheimNetwork.Log.LogInfo((object)"[Steam] Module désactivé ; valeurs vanilla conservées. Un redémarrage est requis après désactivation à chaud."); } _steamReady = true; return true; } int num = Math.Max(NetworkConfig.SteamSendRateMaxKb.Value, NetworkConfig.SteamSendRateMinKb.Value) * 1024; int num2 = Math.Min(NetworkConfig.SteamSendRateMinKb.Value, NetworkConfig.SteamSendRateMaxKb.Value) * 1024; int num3 = NetworkConfig.SteamSendBufferKb.Value * 1024; int num4 = NetworkConfig.SteamReceiveBufferKb.Value * 1024; int num5 = NetworkConfig.SteamReceiveMaxMessageKb.Value * 1024; string text = $"{num}:{num2}:{num3}:{num4}:{num5}"; if (!force && text == _lastFingerprint) { return true; } _applyInProgress = true; try { if (!TryFindSteamApi(out var setConfigMethod, out var valueEnum, out var scopeEnum, out var dataTypeEnum)) { WarheimNetwork.Log.LogWarning((object)"[Steam] API SetConfigValue introuvable. Application différée jusqu'à l'initialisation de Steam."); return false; } string[] array = new string[5] { "k_ESteamNetworkingConfig_SendRateMax", "k_ESteamNetworkingConfig_SendRateMin", "k_ESteamNetworkingConfig_SendBufferSize", "k_ESteamNetworkingConfig_RecvBufferSize", "k_ESteamNetworkingConfig_RecvMaxMessageSize" }; if (!CanResolveSteamBaseEnums(scopeEnum, dataTypeEnum)) { return false; } _lastAttemptSteamNotInitialized = false; int supportedCount = 0; int successCount = 0; ApplySteamValue(setConfigMethod, valueEnum, scopeEnum, dataTypeEnum, array[0], num, ref supportedCount, ref successCount); ApplySteamValue(setConfigMethod, valueEnum, scopeEnum, dataTypeEnum, array[1], num2, ref supportedCount, ref successCount); ApplySteamValue(setConfigMethod, valueEnum, scopeEnum, dataTypeEnum, array[2], num3, ref supportedCount, ref successCount); ApplySteamValue(setConfigMethod, valueEnum, scopeEnum, dataTypeEnum, array[3], num4, ref supportedCount, ref successCount); ApplySteamValue(setConfigMethod, valueEnum, scopeEnum, dataTypeEnum, array[4], num5, ref supportedCount, ref successCount); if (_lastAttemptSteamNotInitialized) { return false; } if (supportedCount == 0) { WarheimNetwork.Log.LogError((object)"[Steam] Aucune clé SteamNetworkingSockets compatible n'a été trouvée."); return false; } if (successCount != supportedCount) { WarheimNetwork.Log.LogError((object)$"[Steam] Configuration incomplète : {successCount}/{supportedCount} clés supportées appliquées."); return false; } _lastFingerprint = text; _steamReady = true; WarheimNetwork.Log.LogInfo((object)($"[Steam] Paramètres appliqués ({reason}) : rate={num2 / 1024}-{num / 1024} Ko/s, " + $"buffers configurés={num3 / 1024}/{num4 / 1024} Ko, " + $"maxMessage={num5 / 1024} Ko, clés={successCount}/{array.Length}, " + "api=" + _selectedUtilityTypeName + ".")); return true; } catch (Exception arg) { WarheimNetwork.Log.LogError((object)$"[Steam] Échec de l'application des paramètres : {arg}"); return false; } finally { _applyInProgress = false; } } private static bool TryFindSteamApi(out MethodInfo setConfigMethod, out Type valueEnum, out Type scopeEnum, out Type dataTypeEnum) { setConfigMethod = null; valueEnum = null; scopeEnum = null; dataTypeEnum = null; _selectedUtilityTypeName = string.Empty; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); Assembly[] array = assemblies; foreach (Assembly assembly in array) { if ((object)valueEnum == null) { valueEnum = assembly.GetType("Steamworks.ESteamNetworkingConfigValue"); } if ((object)scopeEnum == null) { scopeEnum = assembly.GetType("Steamworks.ESteamNetworkingConfigScope"); } if ((object)dataTypeEnum == null) { dataTypeEnum = assembly.GetType("Steamworks.ESteamNetworkingConfigDataType"); } } string[] array2 = ((!Application.isBatchMode) ? new string[2] { "Steamworks.SteamNetworkingUtils", "Steamworks.SteamGameServerNetworkingUtils" } : new string[2] { "Steamworks.SteamGameServerNetworkingUtils", "Steamworks.SteamNetworkingUtils" }); Assembly[] array3 = assemblies; foreach (Assembly assembly2 in array3) { string[] array4 = array2; foreach (string name in array4) { Type type = assembly2.GetType(name); if (!(type == null)) { setConfigMethod = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(IsCompatibleSetConfigMethod); if (setConfigMethod != null) { _selectedUtilityTypeName = type.Name; break; } } } if (setConfigMethod != null) { break; } } return setConfigMethod != null && valueEnum != null && scopeEnum != null && dataTypeEnum != null; } private static bool IsCompatibleSetConfigMethod(MethodInfo method) { if (method.Name != "SetConfigValue") { return false; } ParameterInfo[] parameters = method.GetParameters(); return parameters.Length == 5 && IsEnumOrIntegral(parameters[0].ParameterType) && IsEnumOrIntegral(parameters[1].ParameterType) && parameters[2].ParameterType == typeof(IntPtr) && IsEnumOrIntegral(parameters[3].ParameterType) && parameters[4].ParameterType == typeof(IntPtr); } private static bool IsEnumOrIntegral(Type type) { if (type.IsEnum) { return true; } TypeCode typeCode = Type.GetTypeCode(type); return typeCode == TypeCode.Int16 || typeCode == TypeCode.UInt16 || typeCode == TypeCode.Int32 || typeCode == TypeCode.UInt32 || typeCode == TypeCode.Int64 || typeCode == TypeCode.UInt64; } private static bool CanResolveSteamBaseEnums(Type scopeEnum, Type dataTypeEnum) { try { Enum.Parse(scopeEnum, "k_ESteamNetworkingConfig_Global"); Enum.Parse(dataTypeEnum, "k_ESteamNetworkingConfig_Int32"); return true; } catch (Exception ex) { WarheimNetwork.Log.LogError((object)("[Steam] Enum Steamworks incompatible : " + ex.Message)); return false; } } private static void ApplySteamValue(MethodInfo method, Type valueEnum, Type scopeEnum, Type dataTypeEnum, string keyName, int value, ref int supportedCount, ref int successCount) { if (!Enum.IsDefined(valueEnum, keyName)) { if (UnsupportedKeysLogged.Add(keyName)) { WarheimNetwork.Log.LogWarning((object)("[Steam] Clé non supportée par cette version : " + keyName + ". Réglage ignoré.")); } return; } supportedCount++; if (TrySet(method, valueEnum, scopeEnum, dataTypeEnum, keyName, value)) { successCount++; } } private static bool TrySet(MethodInfo method, Type valueEnum, Type scopeEnum, Type dataTypeEnum, string keyName, int value) { try { ParameterInfo[] parameters = method.GetParameters(); object obj = ConvertEnumArgument(parameters[0].ParameterType, valueEnum, keyName); object obj2 = ConvertEnumArgument(parameters[1].ParameterType, scopeEnum, "k_ESteamNetworkingConfig_Global"); object obj3 = ConvertEnumArgument(parameters[3].ParameterType, dataTypeEnum, "k_ESteamNetworkingConfig_Int32"); GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned); try { if (method.Invoke(null, new object[5] { obj, obj2, IntPtr.Zero, obj3, gCHandle.AddrOfPinnedObject() }) is bool flag && !flag) { WarheimNetwork.Log.LogWarning((object)$"[Steam] SetConfigValue a refusé {keyName}={value}."); return false; } return true; } finally { gCHandle.Free(); } } catch (Exception ex) { Exception ex2 = ((ex is TargetInvocationException { InnerException: not null } ex3) ? ex3.InnerException : ex); if (ex2 is InvalidOperationException && ex2.Message.IndexOf("not initialized", StringComparison.OrdinalIgnoreCase) >= 0) { _lastAttemptSteamNotInitialized = true; return false; } WarheimNetwork.Log.LogWarning((object)$"[Steam] Impossible d'appliquer {keyName}={value} : {ex2.GetType().Name}: {ex2.Message}"); return false; } } private static object ConvertEnumArgument(Type targetType, Type sourceEnum, string name) { object value = Enum.Parse(sourceEnum, name); int num = Convert.ToInt32(value); if (targetType.IsEnum) { return Enum.ToObject(targetType, num); } return Convert.ChangeType(num, targetType); } } [HarmonyPatch] internal static class TransformSyncPatches { private sealed class FloatReplacement { internal readonly float VanillaValue; internal readonly MethodInfo Getter; internal FloatReplacement(float vanillaValue, string getterName) { VanillaValue = vanillaValue; Getter = AccessTools.Method(typeof(NetworkConfig), getterName, (Type[])null, (Type[])null); } } [HarmonyPatch(typeof(ZSyncTransform), "SyncPosition")] [HarmonyTranspiler] private static IEnumerable SyncPositionTranspiler(IEnumerable instructions) { return ReplaceValidated(instructions, "ZSyncTransform.SyncPosition", new FloatReplacement(0.2f, "EffectivePositionSmooth"), new FloatReplacement(0.5f, "EffectiveRotationSmooth"), new FloatReplacement(0.001f, "EffectiveMicroThreshold")); } [HarmonyPatch(typeof(ZSyncTransform), "ClientSync")] [HarmonyTranspiler] private static IEnumerable ClientSyncTranspiler(IEnumerable instructions) { return ReplaceValidated(instructions, "ZSyncTransform.ClientSync", new FloatReplacement(0.2f, "EffectivePositionSmooth"), new FloatReplacement(0.001f, "EffectiveMicroThreshold"), new FloatReplacement(0.01f, "EffectiveClientDistanceThreshold")); } private static IEnumerable ReplaceValidated(IEnumerable instructions, string targetName, params FloatReplacement[] replacements) { List list = new List(instructions); Dictionary> dictionary = new Dictionary>(); foreach (FloatReplacement key in replacements) { dictionary[key] = new List(); } float num = default(float); for (int j = 0; j < list.Count; j++) { if (list[j].opcode != OpCodes.Ldc_R4) { continue; } object operand = list[j].operand; int num2; if (operand is float) { num = (float)operand; num2 = 1; } else { num2 = 0; } if (num2 == 0) { continue; } foreach (FloatReplacement floatReplacement in replacements) { if (Mathf.Approximately(num, floatReplacement.VanillaValue)) { dictionary[floatReplacement].Add(j); break; } } } foreach (FloatReplacement floatReplacement2 in replacements) { if (dictionary[floatReplacement2].Count == 0) { WarheimNetwork.Log.LogError((object)$"[Transform] Patch {targetName} refusé : constante {floatReplacement2.VanillaValue} introuvable. Vanilla conservé."); return list; } if (floatReplacement2.Getter == null) { WarheimNetwork.Log.LogError((object)$"[Transform] Patch {targetName} refusé : getter pour {floatReplacement2.VanillaValue} introuvable. Vanilla conservé."); return list; } } int num3 = 0; foreach (FloatReplacement floatReplacement3 in replacements) { foreach (int item in dictionary[floatReplacement3]) { list[item].opcode = OpCodes.Call; list[item].operand = floatReplacement3.Getter; num3++; } } WarheimNetwork.Log.LogInfo((object)$"[Transform] {targetName} : {num3} constante(s) remplacée(s) par des valeurs dynamiques."); return list; } } internal static class TransportCompression { private sealed class CompressionPeer { internal ZNetPeer Peer; internal ZSteamSocket Socket; internal bool Compatible; internal bool SendEnabled; internal bool ReceiveEnabled; internal long SentRawBytes; internal long SentWireBytes; internal long ReceivedRawBytes; internal long ReceivedWireBytes; internal long CompressedSent; internal long CompressedReceived; internal long Rejected; internal long Failures; internal float LastFailureLog; } private const string FeaturesRpc = "WarheimNetwork_Features_v2"; private const string CompressionRpc = "WarheimNetwork_Compression_v2"; private const int Protocol = 2; private const int CompressionCapability = 1; private const int MagicA = 1464746802; private const int MagicB = -760794773; private const int HeaderSize = 24; private const int MaximumRetainedBufferBytes = 1048576; private static readonly object Sync = new object(); private static readonly Dictionary Peers = new Dictionary(); private static readonly FieldInfo PackageStreamField = AccessTools.Field(typeof(ZPackage), "m_stream"); [ThreadStatic] private static MemoryStream _compressionOutput; private static long _sentRawBytes; private static long _sentWireBytes; private static long _receivedRawBytes; private static long _receivedWireBytes; private static long _compressedSent; private static long _compressedReceived; private static long _rejected; private static long _failures; private static long _rawCopiesAvoided; private static bool _codecAvailable; internal static void Initialize() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown try { if (PackageStreamField == null) { throw new MissingFieldException(typeof(ZPackage).FullName, "m_stream"); } byte[] array = new byte[8192]; for (int i = 0; i < array.Length; i++) { array[i] = (byte)(i % 31); } ZPackage package = new ZPackage(array); if (!TryGetPackageSegment(package, out var buffer, out var offset, out var count) || !TryCompress(buffer, offset, count, out var package2, out var _)) { throw new InvalidOperationException("échantillon non compressé"); } if (!TryGetPackageSegment(package2, out var buffer2, out var offset2, out var count2) || !TryDecompress(buffer2, offset2, count2, out var package3)) { throw new InvalidDataException("échantillon non décompressé"); } byte[] array2 = package3.GetArray(); if (array2.Length != array.Length) { throw new InvalidDataException("taille du test invalide"); } for (int j = 0; j < array.Length; j++) { if (array2[j] != array[j]) { throw new InvalidDataException("contenu du test invalide"); } } _codecAvailable = true; ReleaseWorkingBuffer(); WarheimNetwork.Log.LogInfo((object)"[Compression] Codec Deflate faible allocation validé."); } catch (Exception ex) { _codecAvailable = false; ReleaseWorkingBuffer(); WarheimNetwork.Log.LogError((object)("[Compression] Codec désactivé après échec du test : " + ex.GetType().Name + ": " + ex.Message)); } } internal static void OnNewConnection(ZNetPeer peer) { ISocket obj = peer?.m_socket; ZSteamSocket val = (ZSteamSocket)(object)((obj is ZSteamSocket) ? obj : null); if (val == null || peer.m_rpc == null) { return; } lock (Sync) { if (Peers.ContainsKey(val)) { return; } Peers.Add(val, new CompressionPeer { Peer = peer, Socket = val }); } peer.m_rpc.Register("WarheimNetwork_Features_v2", (Action)delegate(ZRpc _, int packed) { ReceiveFeatures(peer, packed); }); peer.m_rpc.Register("WarheimNetwork_Compression_v2", (Action)delegate(ZRpc _, bool enabled) { ReceiveCompressionState(peer, enabled); }); peer.m_rpc.Invoke("WarheimNetwork_Features_v2", new object[1] { PackFeatures() }); } internal static void OnDisconnect(ZNetPeer peer) { ISocket obj = peer?.m_socket; ZSteamSocket val = (ZSteamSocket)(object)((obj is ZSteamSocket) ? obj : null); if (val == null) { return; } CompressionPeer value; lock (Sync) { if (!Peers.TryGetValue(val, out value)) { return; } Peers.Remove(val); } ConfigEntry verboseLogging = NetworkConfig.VerboseLogging; if ((verboseLogging != null && verboseLogging.Value) || value.Failures > 0) { WarheimNetwork.Log.LogInfo((object)("[Compression] retrait " + PeerLabel(value.Peer) + " | tx=" + FormatRatio(value.SentRawBytes, value.SentWireBytes) + ", " + $"rx={FormatRatio(value.ReceivedRawBytes, value.ReceivedWireBytes)}, paquets={value.CompressedSent}/{value.CompressedReceived}, " + $"rejetés={value.Rejected}, erreurs={value.Failures}.")); } } internal static void RefreshAll() { List list; lock (Sync) { list = new List(Peers.Values); } foreach (CompressionPeer item in list) { ApplySendState(item, item.Compatible && CompressionConfigured()); } } internal static void Reset() { lock (Sync) { List list = null; foreach (KeyValuePair peer in Peers) { if (peer.Key == null || !peer.Key.IsConnected()) { if (list == null) { list = new List(); } list.Add(peer.Key); continue; } peer.Value.SentRawBytes = 0L; peer.Value.SentWireBytes = 0L; peer.Value.ReceivedRawBytes = 0L; peer.Value.ReceivedWireBytes = 0L; peer.Value.CompressedSent = 0L; peer.Value.CompressedReceived = 0L; peer.Value.Rejected = 0L; peer.Value.Failures = 0L; } if (list != null) { foreach (ZSteamSocket item in list) { Peers.Remove(item); } } _sentRawBytes = 0L; _sentWireBytes = 0L; _receivedRawBytes = 0L; _receivedWireBytes = 0L; _compressedSent = 0L; _compressedReceived = 0L; _rejected = 0L; _failures = 0L; _rawCopiesAvoided = 0L; } ReleaseWorkingBuffer(); } internal static void LogSessionSummary() { lock (Sync) { if (_compressedSent != 0L || _compressedReceived != 0L || _failures != 0) { WarheimNetwork.Log.LogInfo((object)("[Compression] Session | tx=" + FormatRatio(_sentRawBytes, _sentWireBytes) + ", rx=" + FormatRatio(_receivedRawBytes, _receivedWireBytes) + ", " + $"paquets={_compressedSent}/{_compressedReceived}, rejetés={_rejected}, erreurs={_failures}, " + $"copies évitées={_rawCopiesAvoided}.")); } } } internal static void ReleaseWorkingBuffer() { MemoryStream compressionOutput = _compressionOutput; _compressionOutput = null; compressionOutput?.Dispose(); } internal static void CompressForSend(ZSteamSocket socket, ref ZPackage package) { if (SavePressureGuard.IsActive || socket == null || package == null || !TryGetPeer(socket, out var state) || !state.SendEnabled || !TryGetPackageSegment(package, out var buffer, out var offset, out var count) || count < Math.Max(256, NetworkConfig.CompressionThresholdBytes.Value) || HasHeader(buffer, offset, count)) { return; } try { if (!TryCompress(buffer, offset, count, out var package2, out var wireLength)) { state.Rejected++; lock (Sync) { _rejected++; return; } } package = package2; state.SentRawBytes += count; state.SentWireBytes += wireLength; state.CompressedSent++; lock (Sync) { _sentRawBytes += count; _sentWireBytes += wireLength; _compressedSent++; _rawCopiesAvoided += 2L; } } catch (Exception ex) { ReleaseWorkingBuffer(); RecordFailure(state, "compression", ex); } } internal static void DecompressAfterReceive(ZSteamSocket socket, ref ZPackage package) { if (socket == null || package == null || !TryGetPackageSegment(package, out var buffer, out var offset, out var count) || !HasHeader(buffer, offset, count)) { return; } TryGetPeer(socket, out var state); try { if (!TryDecompress(buffer, offset, count, out var package2)) { throw new InvalidDataException("décompression impossible"); } int num = package2.Size(); package = package2; if (state != null) { state.ReceiveEnabled = true; state.ReceivedRawBytes += num; state.ReceivedWireBytes += count; state.CompressedReceived++; } lock (Sync) { _receivedRawBytes += num; _receivedWireBytes += count; _compressedReceived++; _rawCopiesAvoided += 2L; } } catch (Exception ex) { package = null; RecordFailure(state, "décompression", ex); } } private static int PackFeatures() { int num = (_codecAvailable ? 1 : 0); return 0x20000 | num; } private static void ReceiveFeatures(ZNetPeer peer, int packed) { ISocket obj = peer?.m_socket; ZSteamSocket val = (ZSteamSocket)(object)((obj is ZSteamSocket) ? obj : null); if (val != null && TryGetPeer(val, out var state)) { int num = (packed >> 16) & 0xFFFF; int num2 = packed & 0xFFFF; state.Compatible = num == 2 && (num2 & 1) != 0; ApplySendState(state, state.Compatible && CompressionConfigured()); if (state.Compatible) { WarheimNetwork.Log.LogInfo((object)("[Compression] Négociation réussie avec " + PeerLabel(peer) + ".")); } else { WarheimNetwork.Log.LogWarning((object)("[Compression] " + PeerLabel(peer) + " incompatible ou compression désactivée, transport brut conservé.")); } } } private static void ReceiveCompressionState(ZNetPeer peer, bool enabled) { ISocket obj = peer?.m_socket; ZSteamSocket val = (ZSteamSocket)(object)((obj is ZSteamSocket) ? obj : null); if (val != null && TryGetPeer(val, out var state)) { state.ReceiveEnabled = enabled; } } private static void ApplySendState(CompressionPeer state, bool enabled) { if (state?.Peer?.m_rpc == null || state.SendEnabled == enabled) { return; } try { state.Peer.m_rpc.Invoke("WarheimNetwork_Compression_v2", new object[1] { enabled }); state.Socket.Flush(); state.SendEnabled = enabled; } catch (Exception ex) { RecordFailure(state, "activation", ex); } } private static bool CompressionConfigured() { return NetworkConfig.IsModuleEnabled(NetworkConfig.SteamTransportEnabled) && NetworkConfig.IsModuleEnabled(NetworkConfig.TransportCompressionEnabled) && _codecAvailable; } private static bool TryGetPeer(ZSteamSocket socket, out CompressionPeer state) { lock (Sync) { return Peers.TryGetValue(socket, out state); } } private static bool TryCompress(byte[] raw, int rawOffset, int rawLength, out ZPackage package, out int wireLength) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown package = null; wireLength = 0; MemoryStream workingBuffer = GetWorkingBuffer(rawLength); workingBuffer.SetLength(24L); workingBuffer.Position = 24L; using (DeflateStream deflateStream = new DeflateStream(workingBuffer, CompressionLevel.Fastest, leaveOpen: true)) { deflateStream.Write(raw, rawOffset, rawLength); } int num; int num2; int num3; checked { num = (int)workingBuffer.Length - 24; num2 = 24 + num; num3 = Clamp(NetworkConfig.CompressionMinimumSavingsPercent.Value, 1, 50); } int num4 = rawLength - rawLength * num3 / 100; if (num2 >= num4) { TrimWorkingBuffer(); return false; } byte[] buffer = workingBuffer.GetBuffer(); WriteInt(buffer, 0, 1464746802); WriteInt(buffer, 4, -760794773); WriteInt(buffer, 8, 2); WriteInt(buffer, 12, rawLength); WriteInt(buffer, 16, (int)Checksum(raw, rawOffset, rawLength)); WriteInt(buffer, 20, num); package = new ZPackage(buffer, num2); wireLength = num2; TrimWorkingBuffer(); return true; } private static bool TryDecompress(byte[] wrapped, int wrappedOffset, int wrappedLength, out ZPackage package) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown package = null; if (!HasHeader(wrapped, wrappedOffset, wrappedLength)) { return false; } int num = ReadInt(wrapped, wrappedOffset + 8); int num2 = ReadInt(wrapped, wrappedOffset + 12); uint num3 = (uint)ReadInt(wrapped, wrappedOffset + 16); int num4 = ReadInt(wrapped, wrappedOffset + 20); int num5 = Math.Max(4, NetworkConfig.CompressionMaxPackageMb.Value) * 1024 * 1024; if (num != 2 || num2 < 0 || num2 > num5 || num4 < 0 || num4 != wrappedLength - 24) { throw new InvalidDataException("en-tête invalide"); } ZPackage val = new ZPackage(); if (!(PackageStreamField.GetValue(val) is MemoryStream memoryStream)) { throw new InvalidDataException("stream ZPackage introuvable"); } memoryStream.SetLength(num2); if (!memoryStream.TryGetBuffer(out var buffer) || buffer.Array == null) { throw new InvalidDataException("buffer ZPackage inaccessible"); } using (MemoryStream stream = new MemoryStream(wrapped, wrappedOffset + 24, num4, writable: false, publiclyVisible: true)) { using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress); int i; int num6; for (i = 0; i < num2; i += num6) { num6 = deflateStream.Read(buffer.Array, buffer.Offset + i, num2 - i); if (num6 <= 0) { break; } } if (i != num2 || deflateStream.ReadByte() != -1) { throw new InvalidDataException("taille décompressée invalide"); } } if (Checksum(buffer.Array, buffer.Offset, num2) != num3) { throw new InvalidDataException("checksum invalide"); } memoryStream.Position = 0L; package = val; return true; } private static MemoryStream GetWorkingBuffer(int rawLength) { MemoryStream memoryStream = _compressionOutput; if (memoryStream == null) { int capacity = Math.Max(256, Math.Min(1048576, rawLength / 2)); memoryStream = (_compressionOutput = new MemoryStream(capacity)); } return memoryStream; } private static void TrimWorkingBuffer() { MemoryStream compressionOutput = _compressionOutput; if (compressionOutput != null) { compressionOutput.SetLength(0L); compressionOutput.Position = 0L; if (compressionOutput.Capacity > 1048576) { ReleaseWorkingBuffer(); } } } private static bool TryGetPackageSegment(ZPackage package, out byte[] buffer, out int offset, out int count) { buffer = null; offset = 0; count = 0; if (package == null || PackageStreamField == null) { return false; } count = package.Size(); if (!(PackageStreamField.GetValue(package) is MemoryStream memoryStream) || !memoryStream.TryGetBuffer(out var buffer2) || buffer2.Array == null || count < 0 || count > buffer2.Count) { return false; } buffer = buffer2.Array; offset = buffer2.Offset; return true; } private static bool HasHeader(byte[] data, int offset, int count) { return data != null && offset >= 0 && count >= 24 && offset <= data.Length - count && ReadInt(data, offset) == 1464746802 && ReadInt(data, offset + 4) == -760794773; } private static uint Checksum(byte[] data, int offset, int count) { uint num = 2166136261u; int num2 = offset + count; for (int i = offset; i < num2; i++) { num ^= data[i]; num *= 16777619; } return num; } private static void WriteInt(byte[] data, int offset, int value) { data[offset] = (byte)value; data[offset + 1] = (byte)(value >> 8); data[offset + 2] = (byte)(value >> 16); data[offset + 3] = (byte)(value >> 24); } private static int ReadInt(byte[] data, int offset) { return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); } private static int Clamp(int value, int minimum, int maximum) { return Math.Max(minimum, Math.Min(maximum, value)); } private static void RecordFailure(CompressionPeer state, string operation, Exception ex) { if (state != null) { state.Failures++; } lock (Sync) { _failures++; } float unscaledTime = Time.unscaledTime; if (state == null || unscaledTime - state.LastFailureLog >= 10f) { if (state != null) { state.LastFailureLog = unscaledTime; } WarheimNetwork.Log.LogWarning((object)("[Compression] Échec de " + operation + " pour " + PeerLabel(state?.Peer) + " : " + ex.GetType().Name + ": " + ex.Message)); } } private static string PeerLabel(ZNetPeer peer) { string arg = (string.IsNullOrWhiteSpace(peer?.m_playerName) ? "peer" : peer.m_playerName); return $"{arg}/{peer?.m_uid ?? 0}"; } private static string FormatRatio(long raw, long wire) { if (raw <= 0) { return "0 Mio"; } return $"{(double)raw / 1048576.0:F1}->{(double)wire / 1048576.0:F1} Mio ({(double)wire * 100.0 / (double)raw:F0}%)"; } } internal static class TransportCompressionPatches { internal static void Install(Harmony harmony) { //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "OnNewConnection", new Type[1] { typeof(ZNetPeer) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ZSteamSocket), "Send", new Type[1] { typeof(ZPackage) }, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(ZSteamSocket), "Recv", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null) { WarheimNetwork.Log.LogError((object)($"[Compression] Installation refusée : OnNewConnection={methodInfo != null}, Disconnect={methodInfo2 != null}, " + $"Send={methodInfo3 != null}, Recv={methodInfo4 != null}. Transport brut conservé.")); return; } try { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(TransportCompressionPatches), "RecvPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(TransportCompressionPatches), "SendPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(TransportCompressionPatches), "DisconnectPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(TransportCompressionPatches), "OnNewConnectionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); WarheimNetwork.Log.LogInfo((object)"[Compression] Transport Steam négocié faible allocation installé avec validation stricte."); } catch (Exception ex) { WarheimNetwork.Log.LogError((object)("[Compression] Installation refusée : " + ex.GetType().Name + ": " + ex.Message)); } } private static void OnNewConnectionPostfix(ZNetPeer __0) { TransportCompression.OnNewConnection(__0); } private static void DisconnectPostfix(ZNetPeer __0) { TransportCompression.OnDisconnect(__0); } private static void SendPrefix(ZSteamSocket __instance, ref ZPackage __0) { TransportCompression.CompressForSend(__instance, ref __0); } private static void RecvPostfix(ZSteamSocket __instance, ref ZPackage __result) { TransportCompression.DecompressAfterReceive(__instance, ref __result); } } [BepInPlugin("dzk.warheimnetwork", "WarheimNetwork", "2.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInIncompatibility("VitByr.VBNetTweaks")] [BepInIncompatibility("CW_Jesse.BetterNetworking")] [BepInIncompatibility("sighsorry.SkadiNet")] [BepInIncompatibility("redseiko.valheim.returntosender")] [BepInIncompatibility("CacoFFF.valheim.LeanNet")] [BepInIncompatibility("redseiko.valheim.scenic")] [BepInIncompatibility("Searica.Valheim.NetworkTweaks")] [BepInIncompatibility("Searica.Valheim.OpenSesame")] [BepInIncompatibility("org.bepinex.plugins.network")] [BepInIncompatibility("com.Fire.FiresGhettoNetworkMod")] [BepInIncompatibility("com.maxsch.valheim.TimeoutLimit")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] internal sealed class WarheimNetwork : BaseUnityPlugin { public const string PluginGUID = "dzk.warheimnetwork"; public const string PluginName = "WarheimNetwork"; public const string PluginVersion = "2.2.1"; private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; NetworkConfig.Bind(((BaseUnityPlugin)this).Config); TransportCompression.Initialize(); _harmony = new Harmony("dzk.warheimnetwork"); _harmony.PatchAll(typeof(SteamTransportPatches)); TransportCompressionPatches.Install(_harmony); SavePressureGuard.Install(_harmony); _harmony.PatchAll(typeof(ZdoSchedulerPatches)); _harmony.PatchAll(typeof(TransformSyncPatches)); _harmony.PatchAll(typeof(ZNetSceneLifecycleGuard)); _harmony.PatchAll(typeof(ShipSyncPatches)); _harmony.PatchAll(typeof(MapSyncGuard)); _harmony.PatchAll(typeof(NetworkDiagnosticsPatches)); ZNetSceneLifecycleGuard.Validate(); ShipSyncPatches.Validate(); SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized; NetworkConfig.SteamSettingsChanged += OnSteamSettingsChanged; NetworkConfig.CompressionSettingsChanged += OnCompressionSettingsChanged; NetworkConfig.ControlSettingsChanged += OnControlSettingsChanged; NetworkDiagnostics.LogEffectiveSettings("démarrage"); NetworkDiagnostics.AuditLifecyclePatches("démarrage"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"WarheimNetwork 2.2.1 chargé. SaveGuard renforcé, sérialiseur ZDO portable, MapSync, ShipSync et ordonnanceur adaptatif actifs."); } private void Start() { ControlPlaneGuard.Install(_harmony); } private static void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (args != null && (args.UpdatedPluginGUIDs == null || args.UpdatedPluginGUIDs.Count <= 0 || args.UpdatedPluginGUIDs.Contains("dzk.warheimnetwork"))) { string reason = (args.InitialSynchronization ? "synchronisation initiale du serveur" : "mise à jour de configuration serveur"); SteamTransportPatches.ApplySettings(reason, force: true); TransportCompression.RefreshAll(); NetworkDiagnostics.LogEffectiveSettings(reason); } } private static void OnSteamSettingsChanged() { SteamTransportPatches.ApplySettings("modification de configuration", force: false); } private static void OnCompressionSettingsChanged() { TransportCompression.RefreshAll(); } private static void OnControlSettingsChanged() { ControlPlaneGuard.ApplyRuntimeSettings("modification de configuration"); } private void OnDestroy() { SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized; NetworkConfig.SteamSettingsChanged -= OnSteamSettingsChanged; NetworkConfig.CompressionSettingsChanged -= OnCompressionSettingsChanged; NetworkConfig.ControlSettingsChanged -= OnControlSettingsChanged; ShipSyncPatches.Reset(); SavePressureGuard.Reset(); TransportCompression.ReleaseWorkingBuffer(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } [HarmonyPatch] internal static class ZdoSchedulerPatches { private static bool _runtimeFallbackToVanilla; private static float _lastErrorLogTime = -999f; [HarmonyPatch(typeof(ZDOMan), "Update")] [HarmonyTranspiler] private static IEnumerable ZdoManUpdateTranspiler(IEnumerable instructions) { List list = new List(instructions); MethodInfo objB = AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", (Type[])null, (Type[])null); MethodInfo methodInfo = AccessTools.Method(typeof(ZdoSchedulerPatches), "DispatchSendZdos", (Type[])null, (Type[])null); List list2 = new List(); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Call && object.Equals(list[i].operand, objB)) { list2.Add(i); } } if (list2.Count != 1 || methodInfo == null) { WarheimNetwork.Log.LogError((object)("[ZDO] Patch ordonnanceur refusé : 1 appel SendZDOToPeers2 attendu, " + $"{list2.Count} trouvé(s). Vanilla conservé.")); return list; } list[list2[0]].operand = methodInfo; WarheimNetwork.Log.LogInfo((object)"[ZDO] Ordonnanceur installé avec validation stricte."); return list; } [HarmonyPatch(typeof(ZDOMan), "SendZDOs")] [HarmonyTranspiler] private static IEnumerable SendZdosQueueLimitTranspiler(IEnumerable instructions) { List list = new List(instructions); List list2 = new List(); for (int i = 0; i < list.Count; i++) { if (list[i].opcode == OpCodes.Ldc_I4 && list[i].operand is int num && num == 10240) { list2.Add(i); } } if (list2.Count != 2) { WarheimNetwork.Log.LogError((object)$"[ZDO] Patch QueueLimit refusé : 2 constantes 10240 attendues, {list2.Count} trouvée(s). Vanilla conservé."); return list; } if (!ContainsOpcode(list, list2[1] + 1, 4, OpCodes.Sub)) { WarheimNetwork.Log.LogError((object)"[ZDO] Patch QueueTarget refusé : soustraction de budget introuvable après la seconde constante. Vanilla conservé."); return list; } MethodInfo methodInfo = AccessTools.Method(typeof(NetworkConfig), "EffectiveZdoQueueGate", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(NetworkConfig), "EffectiveZdoQueueTarget", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { WarheimNetwork.Log.LogError((object)"[ZDO] Getters QueueGate/QueueTarget introuvables. Vanilla conservé."); return list; } list[list2[0]].opcode = OpCodes.Call; list[list2[0]].operand = methodInfo; list[list2[1]].opcode = OpCodes.Call; list[list2[1]].operand = methodInfo2; WarheimNetwork.Log.LogInfo((object)"[ZDO] QueueGate et QueueTarget adaptatifs installés avec validation stricte."); return list; } private static bool ContainsOpcode(List instructions, int start, int length, OpCode opcode) { int num = Math.Min(instructions.Count, start + length); for (int i = Math.Max(0, start); i < num; i++) { if (instructions[i].opcode == opcode) { return true; } } return false; } [HarmonyPatch(typeof(ZNet), "Start")] [HarmonyPostfix] private static void ResetRuntimeFallback() { _runtimeFallbackToVanilla = false; _lastErrorLogTime = -999f; PeerTrafficController.Reset(); } internal static void DispatchSendZdos(ZDOMan manager, float dt) { if (SavePressureGuard.IsActive) { return; } if (!NetworkConfig.IsModuleEnabled(NetworkConfig.ZdoSchedulerEnabled) || _runtimeFallbackToVanilla) { manager.SendZDOToPeers2(dt); return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; bool workLimited = false; try { PeerTrafficController.Maintenance(Time.unscaledTime); int count = manager.m_peers.Count; if (count == 0) { return; } manager.m_sendTimer += dt; float num5 = Mathf.Max(0.01f, NetworkConfig.ZdoSendInterval.Value); if (manager.m_sendTimer < num5) { return; } manager.m_sendTimer = 0f; int num6 = Mathf.Max(manager.m_nextSendPeer, 0) % count; int num7 = Mathf.Clamp(NetworkConfig.ZdoPeersPerUpdate.Value, 1, count); long timestamp = Stopwatch.GetTimestamp(); double num8 = Math.Max(0.5f, NetworkConfig.ZdoMaximumSchedulerWorkMs.Value); for (int i = 0; i < num7; i++) { if (i > 0 && ElapsedMilliseconds(timestamp) >= num8) { workLimited = true; break; } int num9 = (num6 + i) % count; num2++; manager.m_nextSendPeer = (num9 + 1) % count; ZDOPeer val = manager.m_peers[num9]; ZNetPeer val2 = val?.m_peer; ISocket val3 = val2?.m_socket; if (val3 == null || !val3.IsConnected() || !PeerTrafficController.TryPrepareSend(val2, out var state, out var queueBefore, out var flush, out var forced)) { continue; } PeerTrafficController.BeginSend(state); try { manager.SendZDOs(val, flush); num++; if (flush) { num3++; } if (forced) { num4++; } } finally { PeerTrafficController.EndSend(state, queueBefore); } } NetworkDiagnostics.RecordZdoCycle(num2, num, num3, num4, workLimited); } catch (Exception arg) { _runtimeFallbackToVanilla = true; if (Time.unscaledTime - _lastErrorLogTime > 10f) { _lastErrorLogTime = Time.unscaledTime; WarheimNetwork.Log.LogError((object)("[ZDO] Ordonnanceur désactivé pour cette session après erreur. " + $"attempted={num2}, sent={num}. Retour vanilla au prochain cycle. {arg}")); } if (num == 0) { manager.SendZDOToPeers2(dt); } } } private static double ElapsedMilliseconds(long started) { return (double)(Stopwatch.GetTimestamp() - started) * 1000.0 / (double)Stopwatch.Frequency; } } [HarmonyPatch] internal static class ZNetSceneLifecycleGuard { private static readonly FieldInfo InstancesField = AccessTools.Field(typeof(ZNetScene), "m_instances"); private static readonly FieldInfo TempRemovedField = AccessTools.Field(typeof(ZNetScene), "m_tempRemoved"); private static float _lastRecoveryLogTime = -999f; private static float _lastFailureLogTime = -999f; internal static void Validate() { if (InstancesField == null) { WarheimNetwork.Log.LogError((object)"[Lifecycle] ZNetScene.m_instances introuvable. La protection RemoveObjects ne pourra pas purger les vues mortes."); } if (TempRemovedField == null) { WarheimNetwork.Log.LogWarning((object)"[Lifecycle] ZNetScene.m_tempRemoved introuvable. La protection principale reste active via m_instances."); } if (InstancesField != null) { WarheimNetwork.Log.LogInfo((object)"[Lifecycle] Garde de récupération ZNetScene.RemoveObjects installée."); } } [HarmonyPatch(typeof(ZNetScene), "RemoveObjects")] [HarmonyFinalizer] private static Exception RemoveObjectsFinalizer(ZNetScene __instance, Exception __exception) { if (__exception == null || !NetworkConfig.IsModuleEnabled(NetworkConfig.LifecycleGuardEnabled)) { return __exception; } if (!(__exception is NullReferenceException) && !(__exception is MissingReferenceException)) { return __exception; } int num = 0; int num2 = 0; try { num = PurgeBrokenInstances(__instance); num2 = PurgeBrokenTemporaryEntries(__instance); } catch (Exception ex) { if (Time.unscaledTime - _lastFailureLogTime >= 10f) { _lastFailureLogTime = Time.unscaledTime; WarheimNetwork.Log.LogError((object)("[Lifecycle] Échec de la récupération RemoveObjects : " + ex.GetType().Name + ": " + ex.Message)); } return __exception; } if (num <= 0 && num2 <= 0) { if (Time.unscaledTime - _lastFailureLogTime >= 10f) { _lastFailureLogTime = Time.unscaledTime; WarheimNetwork.Log.LogError((object)("[Lifecycle] RemoveObjects a levé " + __exception.GetType().Name + ", mais aucune référence réseau morte n'a été trouvée. Exception conservée.")); } return __exception; } NetworkDiagnostics.RecordLifecycleRecovery(num, num2); if (Time.unscaledTime - _lastRecoveryLogTime >= 5f) { _lastRecoveryLogTime = Time.unscaledTime; WarheimNetwork.Log.LogWarning((object)("[Lifecycle] RemoveObjects récupéré sans interrompre ZNetScene : " + $"instances mortes purgées={num}, entrées temporaires purgées={num2}.")); } return null; } private static int PurgeBrokenInstances(ZNetScene scene) { if ((Object)(object)scene == (Object)null || InstancesField == null) { return 0; } if (!(InstancesField.GetValue(scene) is IDictionary { Count: not 0 } dictionary)) { return 0; } List list = null; foreach (DictionaryEntry item in dictionary) { object? value = item.Value; if (IsBrokenView((ZNetView)((value is ZNetView) ? value : null))) { if (list == null) { list = new List(); } list.Add(item.Key); } } if (list == null) { return 0; } int num = 0; foreach (object item2 in list) { if (dictionary.Contains(item2)) { dictionary.Remove(item2); num++; } } return num; } private static int PurgeBrokenTemporaryEntries(ZNetScene scene) { if ((Object)(object)scene == (Object)null || TempRemovedField == null) { return 0; } if (!(TempRemovedField.GetValue(scene) is IList { Count: not 0 } list)) { return 0; } int num = 0; for (int num2 = list.Count - 1; num2 >= 0; num2--) { object? obj = list[num2]; if (IsBrokenView((ZNetView)((obj is ZNetView) ? obj : null))) { list.RemoveAt(num2); num++; } } return num; } private static bool IsBrokenView(ZNetView view) { if (view == null || (Object)(object)view == (Object)null) { return true; } try { return view.GetZDO() == null; } catch (MissingReferenceException) { return true; } catch (NullReferenceException) { return true; } } }