Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of WarheimNetwork v2.2.1
WarheimNetwork.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
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<bool> 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<Assembly> hashSet = new HashSet<Assembly>(); 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<Type> types = GetLoadableTypes(assembly).ToList(); List<Type> 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<bool> 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<bool> 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<CodeInstruction> 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<Type> FindServerSyncStateMachines(IEnumerable<Type> types) { List<Type> list = new List<Type>(); 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<Type> GetNestedTypesRecursive(Type root) { Stack<Type> pending; try { pending = new Stack<Type>(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<Type> FindAzuWaitForQueueStateMachines(IEnumerable<Type> 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<Type>)Array.Empty<Type>()) : ((IEnumerable<Type>)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<Type>(); } } private static IEnumerable<Type> GetLoadableTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type type) => type != null); } catch { return Array.Empty<Type>(); } } private static IEnumerable<CodeInstruction> ZRpcTimeoutTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); List<int> list2 = FindFloatConstants(list, 30f); List<int> 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<CodeInstruction> ConfigSyncTimeoutTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { List<CodeInstruction> list = new List<CodeInstruction>(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<int> list2 = new List<int>(); 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<CodeInstruction> 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<CodeInstruction> 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<int> list = new List<int>(); 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<int> FindFloatConstants(List<CodeInstruction> instructions, float value) { List<int> list = new List<int>(); 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<bool> MasterEnabled; internal static ConfigEntry<bool> SteamTransportEnabled; internal static ConfigEntry<bool> TransportCompressionEnabled; internal static ConfigEntry<bool> ZdoSchedulerEnabled; internal static ConfigEntry<bool> ZdoQueueLimitEnabled; internal static ConfigEntry<bool> AdaptiveBackpressureEnabled; internal static ConfigEntry<bool> ControlPlaneGuardEnabled; internal static ConfigEntry<bool> TransformSyncEnabled; internal static ConfigEntry<bool> LifecycleGuardEnabled; internal static ConfigEntry<bool> ShipSyncEnabled; internal static ConfigEntry<bool> SaveGuardEnabled; internal static ConfigEntry<int> SteamSendRateMaxKb; internal static ConfigEntry<int> SteamSendRateMinKb; internal static ConfigEntry<int> SteamSendBufferKb; internal static ConfigEntry<int> SteamReceiveBufferKb; internal static ConfigEntry<int> SteamReceiveMaxMessageKb; internal static ConfigEntry<int> CompressionThresholdBytes; internal static ConfigEntry<int> CompressionMinimumSavingsPercent; internal static ConfigEntry<int> CompressionMaxPackageMb; internal static ConfigEntry<float> ZdoSendInterval; internal static ConfigEntry<int> ZdoPeersPerUpdate; internal static ConfigEntry<int> ZdoQueueLimit; internal static ConfigEntry<int> ZdoMinimumPackageBytes; internal static ConfigEntry<int> ZdoMaximumPackageBytes; internal static ConfigEntry<int> ZdoQueueSoftLimit; internal static ConfigEntry<int> ZdoQueueHardLimit; internal static ConfigEntry<int> ZdoQueueEmergencyLimit; internal static ConfigEntry<float> ZdoMinimumPeerInterval; internal static ConfigEntry<float> ZdoMaximumPeerInterval; internal static ConfigEntry<float> ZdoMaximumSchedulerWorkMs; internal static ConfigEntry<float> ZdoMaximumStarvationSeconds; internal static ConfigEntry<float> ZdoCatchUpSeconds; internal static ConfigEntry<int> ZdoFlushThresholdPercent; internal static ConfigEntry<float> ControlPlaneTimeoutSeconds; internal static ConfigEntry<float> PeerDiagnosticsIntervalSeconds; internal static ConfigEntry<float> PositionSmooth; internal static ConfigEntry<float> RotationSmooth; internal static ConfigEntry<float> MicroMovementThreshold; internal static ConfigEntry<float> ClientDistanceThreshold; internal static ConfigEntry<bool> 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<bool>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); SteamTransportEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "SteamTransport", true, "Augmente les débits et buffers de SteamNetworkingSockets.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); TransportCompressionEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "TransportCompression", true, "Compresse les paquets Steam suffisamment gros après négociation avec chaque peer.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ZdoSchedulerEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "ZdoScheduler", true, "Distribue équitablement les envois ZDO avec rattrapage des nouveaux peers.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ZdoQueueLimitEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "ZdoQueueLimit", true, "Applique un budget ZDO dynamique propre à chaque peer.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AdaptiveBackpressureEnabled = ConfigFileExtensions.BindConfig<bool>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ControlPlaneGuardEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "ControlPlaneGuard", true, "Réserve de la place aux synchronisations de configuration et prolonge leurs timeouts.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); TransformSyncEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "TransformSync", true, "Applique les réglages de lissage réseau validés pour le PvP.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); LifecycleGuardEnabled = ConfigFileExtensions.BindConfig<bool>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ShipSyncEnabled = ConfigFileExtensions.BindConfig<bool>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); SaveGuardEnabled = ConfigFileExtensions.BindConfig<bool>(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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); AcceptableValueBase val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(256, 32768); SteamSendRateMaxKb = ConfigFileExtensions.BindConfig<int>(config, "02 - Steam", "SendRateMaxKB", 16384, "Débit d'envoi maximal Steam en Ko/s.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(128, 32768); SteamSendRateMinKb = ConfigFileExtensions.BindConfig<int>(config, "02 - Steam", "SendRateMinKB", 256, "Débit d'envoi minimal demandé à Steam en Ko/s.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(512, 16384); SteamSendBufferKb = ConfigFileExtensions.BindConfig<int>(config, "02 - Steam", "SendBufferKB", 8192, "Taille du buffer d'envoi Steam en Ko.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(512, 16384); SteamReceiveBufferKb = ConfigFileExtensions.BindConfig<int>(config, "02 - Steam", "ReceiveBufferKB", 4096, "Taille du buffer de réception Steam en Ko.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(1024, 32768); SteamReceiveMaxMessageKb = ConfigFileExtensions.BindConfig<int>(config, "02 - Steam", "ReceiveMaxMessageKB", 8192, "Taille maximale d'un message reçu par Steam en Ko.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(256, 65536); CompressionThresholdBytes = ConfigFileExtensions.BindConfig<int>(config, "03 - Compression", "ThresholdBytes", 1024, "Taille minimale d'un paquet avant tentative de compression.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 50); CompressionMinimumSavingsPercent = ConfigFileExtensions.BindConfig<int>(config, "03 - Compression", "MinimumSavingsPercent", 10, "Gain minimal exigé pour envoyer un paquet compressé.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(4, 256); CompressionMaxPackageMb = ConfigFileExtensions.BindConfig<int>(config, "03 - Compression", "MaximumPackageMB", 64, "Taille maximale acceptée après décompression.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.1f); ZdoSendInterval = ConfigFileExtensions.BindConfig<float>(config, "04 - ZDO", "SendInterval", 0.02f, "Intervalle global entre cycles ZDO. Vanilla : 0.05 seconde.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 200); ZdoPeersPerUpdate = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "PeersPerUpdate", 50, "Nombre maximal de peers examinés par cycle ZDO.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(10240, 262144); ZdoQueueLimit = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "InitialPackageBytes", 65536, "Budget ZDO initial par envoi et par peer.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(8192, 65536); ZdoMinimumPackageBytes = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "MinimumPackageBytes", 12288, "Budget ZDO minimal sous forte congestion.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(16384, 524288); ZdoMaximumPackageBytes = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "MaximumPackageBytes", 131072, "Budget ZDO maximal pendant un rattrapage sain.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(32768, 2097152); ZdoQueueSoftLimit = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "QueueSoftLimit", 131072, "File Steam à partir de laquelle la croissance ZDO ralentit.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(65536, 8388608); ZdoQueueHardLimit = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "QueueHardLimit", 524288, "File Steam au-dessus de laquelle aucun nouveau paquet ZDO n'est ajouté.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(262144, 33554432); ZdoQueueEmergencyLimit = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "QueueEmergencyLimit", 2097152, "File Steam signalant une congestion critique dans les diagnostics.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.1f); ZdoMinimumPeerInterval = ConfigFileExtensions.BindConfig<float>(config, "04 - ZDO", "MinimumPeerInterval", 0.015f, "Intervalle minimal d'un peer sain en rattrapage.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.03f, 1f); ZdoMaximumPeerInterval = ConfigFileExtensions.BindConfig<float>(config, "04 - ZDO", "MaximumPeerInterval", 0.15f, "Intervalle maximal d'un peer réellement congestionné.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f); ZdoMaximumSchedulerWorkMs = ConfigFileExtensions.BindConfig<float>(config, "04 - ZDO", "MaximumSchedulerWorkMs", 4f, "Temps CPU maximal consacré à un cycle ZDO.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f); ZdoMaximumStarvationSeconds = ConfigFileExtensions.BindConfig<float>(config, "04 - ZDO", "MaximumStarvationSeconds", 0.5f, "Délai maximal avant de prioriser un peer servi trop tard.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 120f); ZdoCatchUpSeconds = ConfigFileExtensions.BindConfig<float>(config, "04 - ZDO", "CatchUpSeconds", 30f, "Durée du profil de rattrapage après connexion.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 90); ZdoFlushThresholdPercent = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "FlushThresholdPercent", 40, "Seuil de file autorisant un envoi ZDO complet pendant le rattrapage.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(60f, 300f); ControlPlaneTimeoutSeconds = ConfigFileExtensions.BindConfig<float>(config, "05 - Contrôle", "TimeoutSeconds", 120f, "Timeout des RPC longs et des synchronisations ServerSync intégrées aux mods.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f); PeerDiagnosticsIntervalSeconds = ConfigFileExtensions.BindConfig<float>(config, "05 - Contrôle", "PeerDiagnosticsInterval", 10f, "Intervalle des diagnostics détaillés par peer lorsque VerboseLogging est actif.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f); PositionSmooth = ConfigFileExtensions.BindConfig<float>(config, "06 - Transform", "PositionSmooth", 0.22f, "Valeur de lissage de position distante. Vanilla : 0.20.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 1f); RotationSmooth = ConfigFileExtensions.BindConfig<float>(config, "06 - Transform", "RotationSmooth", 0.45f, "Valeur de lissage de rotation distante. Vanilla : 0.50.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.0001f, 0.05f); MicroMovementThreshold = ConfigFileExtensions.BindConfig<float>(config, "06 - Transform", "MicroMovementThreshold", 0.004f, "Seuil des micro-mouvements réseau. Vanilla : 0.001.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.0001f, 0.05f); ClientDistanceThreshold = ConfigFileExtensions.BindConfig<float>(config, "06 - Transform", "ClientDistanceThreshold", 0.005f, "Seuil de distance de synchronisation client. Vanilla : 0.01.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); VerboseLogging = ConfigFileExtensions.BindConfig<bool>(config, "07 - Diagnostic", "VerboseLogging", false, "Ajoute des informations de diagnostic agrégées.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WatchSteamEntry<bool>(MasterEnabled); WatchCompressionEntry<bool>(MasterEnabled); WatchControlEntry<bool>(MasterEnabled); WatchSteamEntry<bool>(SteamTransportEnabled); WatchSteamEntry<int>(SteamSendRateMaxKb); WatchSteamEntry<int>(SteamSendRateMinKb); WatchSteamEntry<int>(SteamSendBufferKb); WatchSteamEntry<int>(SteamReceiveBufferKb); WatchSteamEntry<int>(SteamReceiveMaxMessageKb); WatchCompressionEntry<bool>(TransportCompressionEnabled); WatchCompressionEntry<int>(CompressionThresholdBytes); WatchCompressionEntry<int>(CompressionMinimumSavingsPercent); WatchControlEntry<bool>(ControlPlaneGuardEnabled); WatchControlEntry<float>(ControlPlaneTimeoutSeconds); } private static void WatchSteamEntry<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { NetworkConfig.SteamSettingsChanged?.Invoke(); }; } private static void WatchCompressionEntry<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { NetworkConfig.CompressionSettingsChanged?.Invoke(); }; } private static void WatchControlEntry<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { NetworkConfig.ControlSettingsChanged?.Invoke(); }; } internal static bool IsModuleEnabled(ConfigEntry<bool> module) { ConfigEntry<bool> 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<string> hashSet = new HashSet<string>(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<string> hashSet = new HashSet<string>(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<string> owners, IEnumerable<Patch> 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<ZNetPeer, PeerState> States = new Dictionary<ZNetPeer, PeerState>(); [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<ZNetPeer> list = null; foreach (KeyValuePair<ZNetPeer, PeerState> 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<ZNetPeer>(); } list.Add(state.Key); LogPeerSummary(value, "retrait"); continue; } ConfigEntry<bool> 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<int, float>[] Floats; internal int FloatCount; internal KeyValuePair<int, Vector3>[] Vec3; internal int Vec3Count; internal KeyValuePair<int, Quaternion>[] Quats; internal int QuatCount; internal KeyValuePair<int, int>[] Ints; internal int IntCount; internal KeyValuePair<int, long>[] Longs; internal int LongCount; internal KeyValuePair<int, string>[] Strings; internal int StringCount; internal KeyValuePair<int, byte[]>[] ByteArrays; internal int ByteArrayCount; } private static class BlockAccessor<T> { private static KeyValuePair<int, T>[] _buffer = Array.Empty<KeyValuePair<int, T>>(); internal static bool Validate() { return typeof(ICollection<KeyValuePair<int, T>>).IsAssignableFrom(typeof(BinarySearchDictionary<int, T>)); } internal static KeyValuePair<int, T>[] Rent(int count) { KeyValuePair<int, T>[] 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<int, T>[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<ZDO, int> _zdoPrefab; private static FieldRef<ZDO, Vector2s> _zdoSector; private static FieldRef<ZDO, Vector3> _zdoRotation; private static FieldRef<ZDO, Vector3> _zdoPosition; private static Dictionary<ZDOID, BinarySearchDictionary<int, float>> _snapshotFloats; private static Dictionary<ZDOID, BinarySearchDictionary<int, Vector3>> _snapshotVec3; private static Dictionary<ZDOID, BinarySearchDictionary<int, Quaternion>> _snapshotQuats; private static Dictionary<ZDOID, BinarySearchDictionary<int, int>> _snapshotInts; private static Dictionary<ZDOID, BinarySearchDictionary<int, long>> _snapshotLongs; private static Dictionary<ZDOID, BinarySearchDictionary<int, string>> _snapshotStrings; private static Dictionary<ZDOID, BinarySearchDictionary<int, byte[]>> _snapshotByteArrays; private static Dictionary<ZDOID, ZDOConnectionHashData> _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<ZDOID, BinarySearchDictionary<int, float>> dictionary = (Dictionary<ZDOID, BinarySearchDictionary<int, float>>)FloatsField.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, Vector3>> dictionary2 = (Dictionary<ZDOID, BinarySearchDictionary<int, Vector3>>)Vec3Field.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, Quaternion>> dictionary3 = (Dictionary<ZDOID, BinarySearchDictionary<int, Quaternion>>)QuatsField.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, int>> dictionary4 = (Dictionary<ZDOID, BinarySearchDictionary<int, int>>)IntsField.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, long>> dictionary5 = (Dictionary<ZDOID, BinarySearchDictionary<int, long>>)LongsField.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, string>> dictionary6 = (Dictionary<ZDOID, BinarySearchDictionary<int, string>>)StringsField.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, byte[]>> dictionary7 = (Dictionary<ZDOID, BinarySearchDictionary<int, byte[]>>)ByteArraysField.GetValue(null); Dictionary<ZDOID, ZDOConnectionHashData> dictionary8 = (Dictionary<ZDOID, ZDOConnectionHashData>)ConnectionsField.GetValue(null); Dictionary<ZDOID, BinarySearchDictionary<int, float>> dictionary9 = new Dictionary<ZDOID, BinarySearchDictionary<int, float>>(dictionary); Dictionary<ZDOID, BinarySearchDictionary<int, Vector3>> dictionary10 = new Dictionary<ZDOID, BinarySearchDictionary<int, Vector3>>(dictionary2); Dictionary<ZDOID, BinarySearchDictionary<int, Quaternion>> dictionary11 = new Dictionary<ZDOID, BinarySearchDictionary<int, Quaternion>>(dictionary3); Dictionary<ZDOID, BinarySearchDictionary<int, int>> dictionary12 = new Dictionary<ZDOID, BinarySearchDictionary<int, int>>(dictionary4); Dictionary<ZDOID, BinarySearchDictionary<int, long>> dictionary13 = new Dictionary<ZDOID, BinarySearchDictionary<int, long>>(dictionary5); Dictionary<ZDOID, BinarySearchDictionary<int, string>> dictionary14 = new Dictionary<ZDOID, BinarySearchDictionary<int, string>>(dictionary6); Dictionary<ZDOID, BinarySearchDictionary<int, byte[]>> dictionary15 = new Dictionary<ZDOID, BinarySearchDictionary<int, byte[]>>(dictionary7); Dictionary<ZDOID, ZDOConnectionHashData> dictionary16 = new Dictionary<ZDOID, ZDOConnectionHashData>(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<ZDOID, BinarySearchDictionary<int, float>> snapshotFloats = _snapshotFloats; Dictionary<ZDOID, BinarySearchDictionary<int, Vector3>> snapshotVec = _snapshotVec3; Dictionary<ZDOID, BinarySearchDictionary<int, Quaternion>> snapshotQuats = _snapshotQuats; Dictionary<ZDOID, BinarySearchDictionary<int, int>> snapshotInts = _snapshotInts; Dictionary<ZDOID, BinarySearchDictionary<int, long>> snapshotLongs = _snapshotLongs; Dictionary<ZDOID, BinarySearchDictionary<int, string>> snapshotStrings = _snapshotStrings; Dictionary<ZDOID, BinarySearchDictionary<int, byte[]>> snapshotByteArrays = _snapshotByteArrays; Dictionary<ZDOID, ZDOConnectionHashData> 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<int, float>[] 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<int, Vector3>[] 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<int, Quaternion>[] 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<int, int>[] 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<int, long>[] 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<int, string>[] 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<int, string>); } } } private static void WriteByteArrayData(ZPackage pkg, KeyValuePair<int, byte[]>[] 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<int, byte[]>); } } } private static void CaptureBlock<T>(BinarySearchDictionary<int, T> dictionary, out KeyValuePair<int, T>[] 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<T>.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<string> hashSet = new HashSet<string>(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<string> owners, IEnumerable<Patch> 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<ZDO, int>("m_prefab"); _zdoSector = AccessTools.FieldRefAccess<ZDO, Vector2s>("m_sector"); _zdoRotation = AccessTools.FieldRefAccess<ZDO, Vector3>("m_rotation"); _zdoPosition = AccessTools.FieldRefAccess<ZDO, Vector3>("m_position"); if (!BlockAccessor<float>.Validate() || !BlockAccessor<Vector3>.Validate() || !BlockAccessor<Quaternion>.Validate() || !BlockAccessor<int>.Validate() || !BlockAccessor<long>.Validate() || !BlockAccessor<string>.Validate() || !BlockAccessor<byte[]>.Validate()) { WarheimNetwork.Log.LogError((object)"[SaveGuard] Structure BinarySearchDictionary incompatible. Sérialiseur vanilla conservé."); return false; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal) { "ZDOExtraData.GetSaveFloats", "ZDOExtraData.GetSaveVec3s", "ZDOExtraData.GetSaveQuaternions", "ZDOExtraData.GetSaveInts", "ZDOExtraData.GetSaveLongs", "ZDOExtraData.GetSaveStrings", "ZDOExtraData.GetSaveByteArrays", "ZDOExtraData.GetSaveConnections", "ZDODataHelper.WriteData" }; HashSet<string> 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<string> ReadCalledMethods(MethodInfo method) { HashSet<string> hashSet = new HashSet<string>(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<int, Vector3> PlayerVelocities = new Dictionary<int, Vector3>(); 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<string> UnsupportedKeysLogged = new HashSet<string>(); 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<CodeInstruction> RegisterGlobalCallbacksTranspiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); List<int> list2 = new List<int>(); 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