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.4.0
WarheimNetwork.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
#define DEBUG using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; 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.4.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.4.0.0")] namespace WarheimNetwork; internal readonly struct AsyncCompressionMetrics { internal readonly long JobsProduced; internal readonly long WorkJobsProduced; internal readonly long JobsCompleted; internal readonly long CompressedJobs; internal readonly long PassThroughJobs; internal readonly long RejectedJobs; internal readonly long FailedJobs; internal readonly long DroppedJobs; internal readonly long BackpressureEvents; internal readonly long SynchronousFallbacks; internal readonly long SnapshotBytes; internal readonly long WireBytes; internal readonly long SnapshotTicks; internal readonly long WorkerBusyTicks; internal readonly long BackpressureWaitTicks; internal readonly long TotalLatencyTicks; internal readonly long MaximumLatencyTicks; internal readonly int QueueDepth; internal readonly int Outstanding; internal readonly int ActiveWorkers; internal readonly int HighWatermark; internal readonly long InFlightBytes; internal AsyncCompressionMetrics(long jobsProduced, long workJobsProduced, long jobsCompleted, long compressedJobs, long passThroughJobs, long rejectedJobs, long failedJobs, long droppedJobs, long backpressureEvents, long synchronousFallbacks, long snapshotBytes, long wireBytes, long snapshotTicks, long workerBusyTicks, long backpressureWaitTicks, long totalLatencyTicks, long maximumLatencyTicks, int queueDepth, int outstanding, int activeWorkers, int highWatermark, long inFlightBytes) { JobsProduced = jobsProduced; WorkJobsProduced = workJobsProduced; JobsCompleted = jobsCompleted; CompressedJobs = compressedJobs; PassThroughJobs = passThroughJobs; RejectedJobs = rejectedJobs; FailedJobs = failedJobs; DroppedJobs = droppedJobs; BackpressureEvents = backpressureEvents; SynchronousFallbacks = synchronousFallbacks; SnapshotBytes = snapshotBytes; WireBytes = wireBytes; SnapshotTicks = snapshotTicks; WorkerBusyTicks = workerBusyTicks; BackpressureWaitTicks = backpressureWaitTicks; TotalLatencyTicks = totalLatencyTicks; MaximumLatencyTicks = maximumLatencyTicks; QueueDepth = queueDepth; Outstanding = outstanding; ActiveWorkers = activeWorkers; HighWatermark = highWatermark; InFlightBytes = inFlightBytes; } } internal static class AsyncCompressionWorkers { private sealed class CompressionJob { internal long Generation; internal long SocketId; internal long Sequence; internal byte[] Raw; internal int MinimumSavingsPercent; internal long CreatedTicks; } private sealed class CompressionResult { internal long Generation; internal long SocketId; internal long Sequence; internal byte[] Payload; internal int RawLength; internal bool Compressed; internal bool Rejected; internal Exception Error; internal long CreatedTicks; } private sealed class SocketState { internal long Id; internal ZSteamSocket Socket; internal long NextSequence; internal long NextToSend; internal int Pending; internal long PendingBytes; internal readonly SortedDictionary<long, CompressionResult> Ready = new SortedDictionary<long, CompressionResult>(); } private const int WorkerCount = 2; private const int MinimumQueueCapacity = 8; private const int MaximumQueueCapacity = 2048; private static readonly object LifecycleSync = new object(); private static readonly Dictionary<ZSteamSocket, SocketState> StatesBySocket = new Dictionary<ZSteamSocket, SocketState>(); private static readonly Dictionary<long, SocketState> StatesById = new Dictionary<long, SocketState>(); private static readonly AutoResetEvent ResultReady = new AutoResetEvent(initialState: false); private static BlockingCollection<CompressionJob> _workQueue; private static BlockingCollection<CompressionResult> _results; private static Thread[] _workers; private static int _mainThreadId; private static int _running; private static int _capacity; private static long _byteCapacity; private static int _outstanding; private static long _inFlightBytes; private static int _activeWorkers; private static int _highWatermark; private static long _nextSocketId; private static long _generation; private static long _jobsProduced; private static long _workJobsProduced; private static long _jobsCompleted; private static long _compressedJobs; private static long _passThroughJobs; private static long _rejectedJobs; private static long _failedJobs; private static long _droppedJobs; private static long _backpressureEvents; private static long _synchronousFallbacks; private static long _snapshotBytes; private static long _wireBytes; private static long _snapshotTicks; private static long _workerBusyTicks; private static long _backpressureWaitTicks; private static long _totalLatencyTicks; private static long _maximumLatencyTicks; [ThreadStatic] private static bool _dispatchingCompletedPacket; internal static bool IsDispatchingCompletedPacket => _dispatchingCompletedPacket; internal static void InitializeMainThread() { if (_mainThreadId == 0) { _mainThreadId = Thread.CurrentThread.ManagedThreadId; } } internal static void Start() { if (!IsMainThread()) { return; } lock (LifecycleSync) { if (Volatile.Read(in _running) != 0 || !NetworkConfig.IsModuleEnabled(NetworkConfig.AsyncCompressionWorkersEnabled)) { return; } _capacity = Math.Max(8, Math.Min(2048, NetworkConfig.AsyncCompressionQueueCapacity.Value)); _byteCapacity = (long)Math.Max(8, NetworkConfig.AsyncCompressionMaxInFlightMb.Value) * 1048576L; _workQueue = new BlockingCollection<CompressionJob>(new ConcurrentQueue<CompressionJob>(), _capacity); _results = new BlockingCollection<CompressionResult>(new ConcurrentQueue<CompressionResult>(), _capacity); _workers = new Thread[2]; Interlocked.Increment(ref _generation); Volatile.Write(ref _running, 1); try { for (int i = 0; i < 2; i++) { int workerNumber = i + 1; Thread thread = new Thread((ThreadStart)delegate { WorkerLoop(workerNumber); }) { IsBackground = true, Name = $"WarheimNetwork.Compression.{workerNumber}" }; try { thread.Priority = ThreadPriority.BelowNormal; } catch { } _workers[i] = thread; thread.Start(); } } catch (Exception ex) { Volatile.Write(ref _running, 0); _workQueue.CompleteAdding(); Thread[] workers = _workers; foreach (Thread thread2 in workers) { if (thread2 != null && thread2.IsAlive) { thread2.Join(1000); } } _workQueue.Dispose(); _results.Dispose(); _workQueue = null; _results = null; _workers = null; WarheimNetwork.Log.LogError((object)("[AsyncCompression] Démarrage refusé, compression synchrone conservée : " + ex.GetType().Name + ": " + ex.Message)); return; } WarheimNetwork.Log.LogInfo((object)($"[AsyncCompression] {2} workers démarrés, capacité bornée=" + $"{_capacity} paquets/{_byteCapacity / 1048576} Mio.")); } } internal static void RefreshEnabledState() { if (!IsMainThread()) { return; } bool flag = NetworkConfig.IsModuleEnabled(NetworkConfig.AsyncCompressionWorkersEnabled); bool flag2 = Volatile.Read(in _running) != 0; if (flag) { if (!flag2) { Start(); } } else if (flag2) { FlushAllBlocking(); Stop(); } } internal static void Stop() { lock (LifecycleSync) { if (Volatile.Read(in _running) == 0) { return; } DropAllMainThread(); Volatile.Write(ref _running, 0); Interlocked.Increment(ref _generation); _workQueue.CompleteAdding(); ResultReady.Set(); Thread[] workers = _workers; foreach (Thread thread in workers) { if (thread != null && thread.IsAlive && !thread.Join(2000)) { WarheimNetwork.Log.LogWarning((object)("[AsyncCompression] " + thread.Name + " n'a pas terminé dans le délai d'arrêt.")); } } CompressionResult item; while (_results.TryTake(out item)) { } _workQueue.Dispose(); _results.Dispose(); _workQueue = null; _results = null; _workers = null; _outstanding = 0; _inFlightBytes = 0L; _activeWorkers = 0; } } internal static bool HasPending(ZSteamSocket socket) { SocketState value; return IsMainThread() && socket != null && StatesBySocket.TryGetValue(socket, out value) && value.Pending > 0; } internal static bool TryQueue(ZSteamSocket socket, byte[] source, int offset, int count, bool attemptCompression, int minimumSavingsPercent) { if (!IsMainThread() || Volatile.Read(in _running) == 0 || socket == null || source == null || count <= 0 || offset < 0 || offset > source.Length - count) { return false; } StatesBySocket.TryGetValue(socket, out var value); bool flag = value != null && value.Pending > 0; if (!attemptCompression && !flag) { return false; } if (!TryReserveOutstanding(count)) { Interlocked.Increment(ref _backpressureEvents); if (flag) { long timestamp = Stopwatch.GetTimestamp(); FlushSocketBlocking(value); Interlocked.Add(ref _backpressureWaitTicks, Stopwatch.GetTimestamp() - timestamp); } Interlocked.Increment(ref _synchronousFallbacks); return false; } long timestamp2 = Stopwatch.GetTimestamp(); byte[] array; try { array = new byte[count]; Buffer.BlockCopy(source, offset, array, 0, count); } catch (Exception ex) { Interlocked.Decrement(ref _outstanding); Interlocked.Add(ref _inFlightBytes, -count); Interlocked.Increment(ref _synchronousFallbacks); if (flag) { FlushSocketBlocking(value); } WarheimNetwork.Log.LogWarning((object)("[AsyncCompression] Snapshot impossible, repli synchrone : " + ex.GetType().Name + ": " + ex.Message)); return false; } Interlocked.Add(ref _snapshotTicks, Stopwatch.GetTimestamp() - timestamp2); Interlocked.Add(ref _snapshotBytes, count); SocketState socketState = value ?? CreateState(socket); long sequence = socketState.NextSequence++; socketState.Pending++; socketState.PendingBytes += count; Interlocked.Increment(ref _jobsProduced); UpdateHighWatermark(Volatile.Read(in _outstanding)); CompressionJob compressionJob = new CompressionJob { Generation = Interlocked.Read(in _generation), SocketId = socketState.Id, Sequence = sequence, Raw = array, MinimumSavingsPercent = minimumSavingsPercent, CreatedTicks = timestamp2 }; if (!attemptCompression) { Interlocked.Increment(ref _passThroughJobs); EnqueueResult(new CompressionResult { Generation = compressionJob.Generation, SocketId = compressionJob.SocketId, Sequence = compressionJob.Sequence, Payload = compressionJob.Raw, RawLength = compressionJob.Raw.Length, CreatedTicks = compressionJob.CreatedTicks }); return true; } Interlocked.Increment(ref _workJobsProduced); if (_workQueue.TryAdd(compressionJob)) { UpdateHighWatermark(Math.Max(Volatile.Read(in _outstanding), _workQueue.Count)); return true; } Interlocked.Increment(ref _synchronousFallbacks); MemoryStream output = null; EnqueueResult(ProcessJob(compressionJob, ref output)); output?.Dispose(); return true; } internal static void DrainMainThread() { if (!IsMainThread() || _results == null) { return; } CompressionResult item; while (_results.TryTake(out item)) { if (!StatesById.TryGetValue(item.SocketId, out var value)) { ReleaseReservation(item.RawLength); continue; } value.Ready[item.Sequence] = item; DispatchReady(value); } } internal static void OnSocketDisconnected(ZSteamSocket socket) { if (IsMainThread() && socket != null && StatesBySocket.TryGetValue(socket, out var value)) { DropState(value); } } internal static int GetPendingBytes(ZSteamSocket socket) { if (!IsMainThread() || socket == null || !StatesBySocket.TryGetValue(socket, out var value)) { return 0; } return (int)((value.PendingBytes >= int.MaxValue) ? int.MaxValue : Math.Max(0L, value.PendingBytes)); } internal static void FlushBeforeDirectSend(ZSteamSocket socket) { if (IsMainThread() && socket != null && StatesBySocket.TryGetValue(socket, out var value) && value.Pending > 0) { long timestamp = Stopwatch.GetTimestamp(); FlushSocketBlocking(value); Interlocked.Add(ref _backpressureWaitTicks, Stopwatch.GetTimestamp() - timestamp); } } internal static void ResetSession() { if (IsMainThread()) { FlushAllBlocking(); ResetMetrics(); } } internal static void DiscardSession() { if (!IsMainThread()) { return; } DropAllMainThread(); if (_results != null) { CompressionResult item; while (_results.TryTake(out item)) { ReleaseReservation(item.RawLength); } } } internal static AsyncCompressionMetrics GetMetrics() { BlockingCollection<CompressionJob> workQueue = _workQueue; int queueDepth = 0; if (workQueue != null) { try { queueDepth = workQueue.Count; } catch (ObjectDisposedException) { } } return new AsyncCompressionMetrics(Interlocked.Read(in _jobsProduced), Interlocked.Read(in _workJobsProduced), Interlocked.Read(in _jobsCompleted), Interlocked.Read(in _compressedJobs), Interlocked.Read(in _passThroughJobs), Interlocked.Read(in _rejectedJobs), Interlocked.Read(in _failedJobs), Interlocked.Read(in _droppedJobs), Interlocked.Read(in _backpressureEvents), Interlocked.Read(in _synchronousFallbacks), Interlocked.Read(in _snapshotBytes), Interlocked.Read(in _wireBytes), Interlocked.Read(in _snapshotTicks), Interlocked.Read(in _workerBusyTicks), Interlocked.Read(in _backpressureWaitTicks), Interlocked.Read(in _totalLatencyTicks), Interlocked.Read(in _maximumLatencyTicks), queueDepth, Volatile.Read(in _outstanding), Volatile.Read(in _activeWorkers), Volatile.Read(in _highWatermark), Interlocked.Read(in _inFlightBytes)); } internal static void LogSessionSummary() { AsyncCompressionMetrics metrics = GetMetrics(); if (metrics.JobsProduced != 0L || metrics.BackpressureEvents != 0L || metrics.DroppedJobs != 0) { double num = TicksToMilliseconds(metrics.SnapshotTicks); double num2 = TicksToMilliseconds(metrics.WorkerBusyTicks); double num3 = TicksToMilliseconds(metrics.BackpressureWaitTicks); double num4 = ((metrics.JobsCompleted > 0) ? (TicksToMilliseconds(metrics.TotalLatencyTicks) / (double)metrics.JobsCompleted) : 0.0); double num5 = TicksToMilliseconds(metrics.MaximumLatencyTicks); WarheimNetwork.Log.LogInfo((object)($"[AsyncCompression] Session | produits={metrics.JobsProduced}, workers={metrics.WorkJobsProduced}, " + $"terminés={metrics.JobsCompleted}, compressés={metrics.CompressedJobs}, passthrough={metrics.PassThroughJobs}, " + $"rejetés={metrics.RejectedJobs}, erreurs={metrics.FailedJobs}, abandonnés={metrics.DroppedJobs}, " + $"backpressure={metrics.BackpressureEvents}, fallback sync={metrics.SynchronousFallbacks}, " + $"snapshot={(double)metrics.SnapshotBytes / 1048576.0:F1} Mio/{num:F1} ms, " + $"worker={num2:F1} ms, attente={num3:F1} ms, " + $"latence={num4:F2}/{num5:F2} ms moy/max, " + $"fileMax={metrics.HighWatermark}/{_capacity}, mémoireEnVol=" + $"{(double)metrics.InFlightBytes / 1048576.0:F1}/{(double)_byteCapacity / 1048576.0:F0} Mio.")); } } private static void WorkerLoop(int workerNumber) { MemoryStream output = null; try { foreach (CompressionJob item in _workQueue.GetConsumingEnumerable()) { Interlocked.Increment(ref _activeWorkers); long timestamp = Stopwatch.GetTimestamp(); CompressionResult result; try { result = ProcessJob(item, ref output); } finally { Interlocked.Add(ref _workerBusyTicks, Stopwatch.GetTimestamp() - timestamp); Interlocked.Decrement(ref _activeWorkers); } EnqueueResult(result); } } catch (ObjectDisposedException) { } catch (InvalidOperationException) { } catch (Exception ex3) { Interlocked.Increment(ref _failedJobs); Debug.WriteLine($"WarheimNetwork worker {workerNumber} stopped: {ex3.GetType().Name}: {ex3.Message}"); } finally { output?.Dispose(); } } private static CompressionResult ProcessJob(CompressionJob job, ref MemoryStream output) { try { byte[] payload; bool flag = TransportCompression.TryCompressSnapshot(job.Raw, job.MinimumSavingsPercent, ref output, out payload); return new CompressionResult { Generation = job.Generation, SocketId = job.SocketId, Sequence = job.Sequence, Payload = (flag ? payload : job.Raw), RawLength = job.Raw.Length, Compressed = flag, Rejected = !flag, CreatedTicks = job.CreatedTicks }; } catch (Exception error) { output?.Dispose(); output = null; return new CompressionResult { Generation = job.Generation, SocketId = job.SocketId, Sequence = job.Sequence, Payload = job.Raw, RawLength = job.Raw.Length, Error = error, CreatedTicks = job.CreatedTicks }; } } private static void EnqueueResult(CompressionResult result) { if (result == null || result.Generation != Interlocked.Read(in _generation)) { return; } BlockingCollection<CompressionResult> results = _results; if (results == null) { return; } if (IsMainThread()) { if (!results.TryAdd(result)) { DrainMainThread(); if (!results.TryAdd(result)) { throw new InvalidOperationException("file de résultats saturée sur le main thread"); } } ResultReady.Set(); return; } while (Volatile.Read(in _running) != 0) { try { if (results.TryAdd(result, 10)) { ResultReady.Set(); break; } } catch (ObjectDisposedException) { break; } catch (InvalidOperationException) { break; } } } private static void DispatchReady(SocketState state) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown CompressionResult value; while (state.Pending > 0 && state.Ready.TryGetValue(state.NextToSend, out value)) { state.Ready.Remove(state.NextToSend); state.NextToSend++; state.Pending--; state.PendingBytes = Math.Max(0L, state.PendingBytes - value.RawLength); ReleaseReservation(value.RawLength); bool flag = false; try { if (state.Socket != null && state.Socket.IsConnected()) { _dispatchingCompletedPacket = true; state.Socket.Send(new ZPackage(value.Payload, value.Payload.Length)); flag = true; } } catch (Exception ex) { value.Error = value.Error ?? ex; } finally { _dispatchingCompletedPacket = false; } if (!flag) { Interlocked.Increment(ref _droppedJobs); if (value.Error != null) { Interlocked.Increment(ref _failedJobs); TransportCompression.RecordAsyncSend(state.Socket, value.RawLength, value.Payload.Length, compressed: false, rejected: false, value.Error); } continue; } long value2 = Math.Max(0L, Stopwatch.GetTimestamp() - value.CreatedTicks); Interlocked.Increment(ref _jobsCompleted); Interlocked.Add(ref _wireBytes, value.Payload.Length); Interlocked.Add(ref _totalLatencyTicks, value2); UpdateMaximum(ref _maximumLatencyTicks, value2); if (value.Compressed) { Interlocked.Increment(ref _compressedJobs); } if (value.Rejected) { Interlocked.Increment(ref _rejectedJobs); } if (value.Error != null) { Interlocked.Increment(ref _failedJobs); } TransportCompression.RecordAsyncSend(state.Socket, value.RawLength, value.Payload.Length, value.Compressed, value.Rejected, value.Error); } } private static SocketState CreateState(ZSteamSocket socket) { SocketState socketState = new SocketState { Id = ++_nextSocketId, Socket = socket }; StatesBySocket.Add(socket, socketState); StatesById.Add(socketState.Id, socketState); return socketState; } private static bool TryReserveOutstanding(int bytes) { int num = Volatile.Read(in _outstanding); long num2 = Interlocked.Read(in _inFlightBytes); if (bytes <= 0 || num >= _capacity || num2 > _byteCapacity - bytes) { return false; } Interlocked.Increment(ref _outstanding); Interlocked.Add(ref _inFlightBytes, bytes); return true; } private static void FlushSocketBlocking(SocketState state) { if (state == null || state.Pending <= 0) { return; } long timestamp = Stopwatch.GetTimestamp(); bool flag = false; int num = Math.Max(1, NetworkConfig.AsyncCompressionBackpressureWarningMs.Value); while (state.Pending > 0 && Volatile.Read(in _running) != 0) { DrainMainThread(); if (state.Pending <= 0) { break; } if (!flag && TicksToMilliseconds(Stopwatch.GetTimestamp() - timestamp) >= (double)num) { flag = true; WarheimNetwork.Log.LogWarning((object)($"[AsyncCompression] Backpressure : attente ordonnée >={num} ms. " + $"file={GetMetrics().QueueDepth}, outstanding={Volatile.Read(in _outstanding)}/{_capacity}, " + $"mémoire={(double)Interlocked.Read(in _inFlightBytes) / 1048576.0:F1}/{(double)_byteCapacity / 1048576.0:F0} Mio.")); } ResultReady.WaitOne(1); } } private static void FlushAllBlocking() { if (Volatile.Read(in _running) == 0) { return; } while (Volatile.Read(in _outstanding) > 0) { DrainMainThread(); if (Volatile.Read(in _outstanding) <= 0) { break; } ResultReady.WaitOne(1); } } private static void DropState(SocketState state) { StatesBySocket.Remove(state.Socket); StatesById.Remove(state.Id); int pending = state.Pending; int count = state.Ready.Count; long num = 0L; foreach (CompressionResult value in state.Ready.Values) { num += value.RawLength; } state.Pending = 0; state.PendingBytes = 0L; state.Ready.Clear(); if (pending > 0) { Interlocked.Add(ref _droppedJobs, pending); } if (count > 0) { Interlocked.Add(ref _outstanding, -count); Interlocked.Add(ref _inFlightBytes, -num); } } private static void DropAllMainThread() { if (!IsMainThread()) { return; } if (StatesBySocket.Count > 0) { List<SocketState> list = new List<SocketState>(StatesBySocket.Values); foreach (SocketState item in list) { DropState(item); } } StatesBySocket.Clear(); StatesById.Clear(); } private static void ResetMetrics() { _jobsProduced = 0L; _workJobsProduced = 0L; _jobsCompleted = 0L; _compressedJobs = 0L; _passThroughJobs = 0L; _rejectedJobs = 0L; _failedJobs = 0L; _droppedJobs = 0L; _backpressureEvents = 0L; _synchronousFallbacks = 0L; _snapshotBytes = 0L; _wireBytes = 0L; _snapshotTicks = 0L; _workerBusyTicks = 0L; _backpressureWaitTicks = 0L; _totalLatencyTicks = 0L; _maximumLatencyTicks = 0L; _highWatermark = Volatile.Read(in _outstanding); } private static bool IsMainThread() { return _mainThreadId != 0 && Thread.CurrentThread.ManagedThreadId == _mainThreadId; } private static void UpdateHighWatermark(int value) { int num; do { num = Volatile.Read(in _highWatermark); } while (value > num && Interlocked.CompareExchange(ref _highWatermark, value, num) != num); } private static void UpdateMaximum(ref long target, long value) { long num; do { num = Interlocked.Read(in target); } while (value > num && Interlocked.CompareExchange(ref target, value, num) != num); } private static void ReleaseReservation(int bytes) { Interlocked.Decrement(ref _outstanding); Interlocked.Add(ref _inFlightBytes, -Math.Max(0, bytes)); } private static double TicksToMilliseconds(long ticks) { return (double)ticks * 1000.0 / (double)Stopwatch.Frequency; } } 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; } } internal static class EwpGlobalLimitGuard { private static bool _installed; private static bool _runtimeFallback; private static Func<object, int> _getMin; private static Func<object, int> _getMax; private static Func<object, ZDO, object, bool> _isValid; private static Func<object, int> _getWeight; private static float _lastErrorLogTime = -999f; private static float _lastSlowLogTime = -999f; internal static void TryInstall(Harmony harmony, string reason) { //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Expected O, but got Unknown if (_installed || harmony == null) { return; } try { Type type = AccessTools.TypeByName("ExpandWorld.Prefab.ObjectsFiltering"); if (type == null) { WarheimNetwork.Log.LogInfo((object)("[EWPGuard] Expand World Prefabs non disponible pendant " + reason + ". Nouvelle tentative au démarrage du monde.")); return; } MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic); MethodInfo methodInfo = null; foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "HasLimitObjects") && !(methodInfo2.ReturnType != typeof(bool))) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 5 && parameters[0].ParameterType.FullName != null && parameters[0].ParameterType.FullName.IndexOf("ValueCollection", StringComparison.Ordinal) >= 0 && parameters[2].ParameterType.IsArray && !(parameters[3].ParameterType != typeof(ZDO))) { methodInfo = methodInfo2; break; } } } if (methodInfo == null) { WarheimNetwork.Log.LogError((object)"[EWPGuard] Surcharge globale HasLimitObjects introuvable. EWP vanilla conservé."); return; } ParameterInfo[] parameters2 = methodInfo.GetParameters(); Type parameterType = parameters2[1].ParameterType; Type elementType = parameters2[2].ParameterType.GetElementType(); Type parameterType2 = parameters2[4].ParameterType; FieldInfo fieldInfo = AccessTools.Field(parameterType, "Min"); FieldInfo fieldInfo2 = AccessTools.Field(parameterType, "Max"); FieldInfo fieldInfo3 = AccessTools.Field(elementType, "Weight"); MethodInfo methodInfo3 = AccessTools.Method(elementType, "IsValid", new Type[2] { typeof(ZDO), parameterType2 }, (Type[])null); if (fieldInfo == null || fieldInfo2 == null || fieldInfo3 == null || methodInfo3 == null || fieldInfo.FieldType != typeof(int) || fieldInfo2.FieldType != typeof(int) || fieldInfo3.FieldType != typeof(int)) { WarheimNetwork.Log.LogError((object)"[EWPGuard] Contrat Range/Object incompatible. EWP vanilla conservé."); return; } _getMin = CompileIntFieldGetter(parameterType, fieldInfo); _getMax = CompileIntFieldGetter(parameterType, fieldInfo2); _getWeight = CompileIntFieldGetter(elementType, fieldInfo3); _isValid = CompileIsValid(elementType, parameterType2, methodInfo3); if (_getMin == null || _getMax == null || _getWeight == null || _isValid == null) { WarheimNetwork.Log.LogError((object)"[EWPGuard] Génération des accès rapides impossible. EWP vanilla conservé."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(EwpGlobalLimitGuard), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _installed = true; WarheimNetwork.Log.LogInfo((object)"[EWPGuard] HasLimitObjects global remplacé par une boucle sans LINQ, sans closure et avec arrêt anticipé."); } catch (Exception ex) { LogError("installation", ex); } } private static bool Prefix(object[] __args, ref bool __result) { if (!_installed || _runtimeFallback || !NetworkConfig.IsModuleEnabled(NetworkConfig.EwpGlobalLimitGuardEnabled) || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return true; } Stopwatch stopwatch = Stopwatch.StartNew(); int num = 0; try { if (__args != null && __args.Length == 5 && __args[0] is Dictionary<ZDOID, ZDO>.ValueCollection valueCollection && __args[1] != null && __args[2] is object[] array) { object obj = __args[3]; ZDO val = (ZDO)((obj is ZDO) ? obj : null); if (val != null && __args[4] != null) { int num2 = _getMin(__args[1]); int num3 = _getMax(__args[1]); bool flag = num3 > 0; int num4 = 0; object arg = __args[4]; foreach (ZDO item in valueCollection) { num++; if (item == null) { continue; } object obj2 = null; foreach (object obj3 in array) { if (obj3 != null && _isValid(obj3, item, arg) && item != val) { obj2 = obj3; break; } } if (obj2 != null) { num4 += _getWeight(obj2); if (flag && num3 < num4) { __result = false; RecordScan(num, stopwatch.Elapsed.TotalMilliseconds, earlyExit: true); return false; } if (!flag && num2 <= num4) { __result = true; RecordScan(num, stopwatch.Elapsed.TotalMilliseconds, earlyExit: true); return false; } } } __result = num2 <= num4 && num4 <= num3; RecordScan(num, stopwatch.Elapsed.TotalMilliseconds, earlyExit: false); return false; } } return true; } catch (Exception ex) { _runtimeFallback = true; LogError("exécution, garde désactivée pour cette session", ex); return true; } } private static Func<object, int> CompileIntFieldGetter(Type ownerType, FieldInfo field) { ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "input"); UnaryExpression expression = Expression.Convert(parameterExpression, ownerType); MemberExpression body = Expression.Field(expression, field); return Expression.Lambda<Func<object, int>>(body, new ParameterExpression[1] { parameterExpression }).Compile(); } private static Func<object, ZDO, object, bool> CompileIsValid(Type objectType, Type parametersType, MethodInfo method) { ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "filter"); ParameterExpression parameterExpression2 = Expression.Parameter(typeof(ZDO), "zdo"); ParameterExpression parameterExpression3 = Expression.Parameter(typeof(object), "parameters"); MethodCallExpression body = Expression.Call(Expression.Convert(parameterExpression, objectType), method, parameterExpression2, Expression.Convert(parameterExpression3, parametersType)); return Expression.Lambda<Func<object, ZDO, object, bool>>(body, new ParameterExpression[3] { parameterExpression, parameterExpression2, parameterExpression3 }).Compile(); } private static void RecordScan(int scanned, double milliseconds, bool earlyExit) { NetworkDiagnostics.RecordEwpGlobalScan(scanned, milliseconds, earlyExit); float num = Math.Max(1f, NetworkConfig.EwpSlowScanWarningMs.Value); if (!(milliseconds < (double)num) && !(Time.unscaledTime - _lastSlowLogTime < 5f)) { _lastSlowLogTime = Time.unscaledTime; WarheimNetwork.Log.LogWarning((object)$"[EWPGuard] Scan global lent : {scanned:N0} ZDO, {milliseconds:F1} ms, arrêt anticipé={earlyExit}."); } } private static void LogError(string operation, Exception ex) { if (!(Time.unscaledTime - _lastErrorLogTime < 10f)) { _lastErrorLogTime = Time.unscaledTime; Exception ex2 = ((ex is TargetInvocationException && ex.InnerException != null) ? ex.InnerException : ex); WarheimNetwork.Log.LogError((object)("[EWPGuard] Échec " + operation + ". EWP vanilla conservé : " + ex2.GetType().Name + ": " + ex2.Message)); } } internal static void Reset() { _runtimeFallback = false; _lastErrorLogTime = -999f; _lastSlowLogTime = -999f; } } internal static class MapSyncGuard { private static int _globalKeySyncDepth; private static int _blockedDuringCurrentSync; private static MapMode _modeBeforeGlobalKeySync = (MapMode)0; private static float _lastLogTime = -999f; internal static void Install(Harmony harmony) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00be: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown if (harmony != null) { MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "RPC_GlobalKeys", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Game), "UpdateNoMap", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(Minimap), "SetMapMode", new Type[1] { typeof(MapMode) }, (Type[])null); bool flag = false; bool flag2 = false; bool flag3 = false; if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(MapSyncGuard), "RpcGlobalKeysPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MapSyncGuard), "RpcGlobalKeysFinalizer", (Type[])null, (Type[])null)), (HarmonyMethod)null); flag = true; } else { WarheimNetwork.Log.LogWarning((object)"[MapSync] ZoneSystem.RPC_GlobalKeys introuvable. Garde transactionnelle indisponible."); } if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.Method(typeof(MapSyncGuard), "UpdateNoMapPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); flag2 = true; } else { WarheimNetwork.Log.LogWarning((object)"[MapSync] Game.UpdateNoMap introuvable. Protection préventive indisponible."); } if (methodInfo3 != null) { HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(MapSyncGuard), "SetMapModePrefix", (Type[])null, (Type[])null)) { priority = 800 }; harmony.Patch((MethodBase)methodInfo3, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); flag3 = true; } else { WarheimNetwork.Log.LogWarning((object)"[MapSync] Minimap.SetMapMode introuvable. Protection de secours indisponible."); } WarheimNetwork.Log.LogInfo((object)($"[MapSync] Protection installée : transaction GlobalKeys={flag}, " + $"blocage UpdateNoMap={flag2}, filtre SetMapMode={flag3}.")); } } internal static void Reset() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) _globalKeySyncDepth = 0; _blockedDuringCurrentSync = 0; _modeBeforeGlobalKeySync = (MapMode)0; _lastLogTime = -999f; } private static void RpcGlobalKeysPrefix() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (_globalKeySyncDepth == 0) { _modeBeforeGlobalKeySync = (MapMode)(((Object)(object)Minimap.instance != (Object)null) ? ((int)Minimap.instance.m_mode) : 0); _blockedDuringCurrentSync = 0; } _globalKeySyncDepth++; } private static Exception RpcGlobalKeysFinalizer(Exception __exception) { FinishGlobalKeySync(); return __exception; } private static bool UpdateNoMapPrefix() { if (!ShouldPreserveLargeMap()) { return true; } RecordBlockedDowngrade(); return false; } private static bool SetMapModePrefix(Minimap __instance, MapMode mode) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 if (_globalKeySyncDepth <= 0 || (Object)(object)__instance == (Object)null || (int)__instance.m_mode != 2 || (int)mode != 1 || MapIsDisabled()) { return true; } RecordBlockedDowngrade(); return false; } private static void FinishGlobalKeySync() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 //IL_00ef: Unknown result type (might be due to invalid IL or missing references) if (_globalKeySyncDepth <= 0) { Reset(); return; } _globalKeySyncDepth--; if (_globalKeySyncDepth <= 0) { NetworkDiagnostics.RecordGlobalKeySync(); bool flag = false; if ((int)_modeBeforeGlobalKeySync == 2 && (Object)(object)Minimap.instance != (Object)null && (int)Minimap.instance.m_mode != 2 && !MapIsDisabled()) { Minimap.instance.SetMapMode((MapMode)2); NetworkDiagnostics.RecordMapModeFallbackRestore(); flag = true; } if ((_blockedDuringCurrentSync > 0 || flag) && Time.unscaledTime - _lastLogTime >= 5f) { _lastLogTime = Time.unscaledTime; WarheimNetwork.Log.LogInfo((object)("[MapSync] Synchronisation GlobalKeys neutralisée sans bascule visuelle : " + $"rabattements bloqués={_blockedDuringCurrentSync}, restauration secours={flag}.")); } _modeBeforeGlobalKeySync = (MapMode)0; _blockedDuringCurrentSync = 0; } } private static void RecordBlockedDowngrade() { if (_globalKeySyncDepth > 0) { _blockedDuringCurrentSync++; } NetworkDiagnostics.RecordMapModeDowngradeBlocked(); } private static bool ShouldPreserveLargeMap() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 return (Object)(object)Minimap.instance != (Object)null && (int)Minimap.instance.m_mode == 2 && !MapIsDisabled(); } 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; } } [HarmonyPatch] internal static class MemoryWatch { private static bool _hasSample; private static float _startedAt; private static float _nextSample; private static float _lastSampleAt; private static long _lastWorkingSet; private static long _lastManagedBytes; private static long _lastProcessorTicks; private static int _lastZdoCount; private static int _lastGen0; private static int _lastGen1; private static int _lastGen2; private static long _lastWorkerJobs; private static long _lastWorkerRawBytes; private static long _lastWorkerWireBytes; private static long _lastWorkerBusyTicks; private static float _lastErrorAt = -999f; [HarmonyPatch(typeof(ZNet), "Update")] [HarmonyPostfix] private static void ZNetUpdatePostfix() { if (NetworkConfig.IsModuleEnabled(NetworkConfig.MemoryWatchEnabled) && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { float unscaledTime = Time.unscaledTime; if (!(unscaledTime < _nextSample)) { _nextSample = unscaledTime + Math.Max(30f, NetworkConfig.MemoryWatchIntervalSeconds.Value); Capture(unscaledTime); } } } internal static void Reset() { _hasSample = false; _startedAt = Time.unscaledTime; _nextSample = _startedAt + 10f; _lastSampleAt = 0f; _lastWorkingSet = 0L; _lastManagedBytes = 0L; _lastProcessorTicks = 0L; _lastZdoCount = 0; _lastGen0 = 0; _lastGen1 = 0; _lastGen2 = 0; _lastWorkerJobs = 0L; _lastWorkerRawBytes = 0L; _lastWorkerWireBytes = 0L; _lastWorkerBusyTicks = 0L; _lastErrorAt = -999f; } private static void Capture(float now) { try { int num = -1; long workingSet; long privateMemorySize; long virtualMemorySize; long ticks; using (Process process = Process.GetCurrentProcess()) { process.Refresh(); workingSet = process.WorkingSet64; privateMemorySize = process.PrivateMemorySize64; virtualMemorySize = process.VirtualMemorySize64; ticks = process.TotalProcessorTime.Ticks; try { num = process.Threads.Count; } catch { } } long totalMemory = GC.GetTotalMemory(forceFullCollection: false); int num2 = GC.CollectionCount(0); int num3 = GC.CollectionCount(1); int num4 = GC.CollectionCount(2); ZDOMan instance = ZDOMan.instance; int num5 = ((instance != null) ? instance.NrOfObjects() : 0); PeerTrafficController.GetQueueSnapshot(out var peers, out var totalQueue, out var maximumQueue); AsyncCompressionMetrics metrics = AsyncCompressionWorkers.GetMetrics(); float num6 = (_hasSample ? Math.Max(0.001f, now - _lastSampleAt) : 0f); long bytes = (_hasSample ? (workingSet - _lastWorkingSet) : 0); long bytes2 = (_hasSample ? (totalMemory - _lastManagedBytes) : 0); int num7 = (_hasSample ? (num5 - _lastZdoCount) : 0); double num8 = (_hasSample ? ((double)num7 * 3600.0 / (double)num6) : 0.0); double num9 = (_hasSample ? ((double)Math.Max(0L, ticks - _lastProcessorTicks) / 10000000.0 / (double)num6) : 0.0); double num10 = (_hasSample ? ((double)((float)Math.Max(0L, metrics.JobsCompleted - _lastWorkerJobs) / num6)) : 0.0); double num11 = (_hasSample ? ((double)((float)Math.Max(0L, metrics.SnapshotBytes - _lastWorkerRawBytes) / num6) / 1048576.0) : 0.0); double num12 = (_hasSample ? ((double)((float)Math.Max(0L, metrics.WireBytes - _lastWorkerWireBytes) / num6) / 1048576.0) : 0.0); double num13 = (_hasSample ? ((double)Math.Max(0L, metrics.WorkerBusyTicks - _lastWorkerBusyTicks) / (double)Stopwatch.Frequency / (double)num6 / 2.0 * 100.0) : 0.0); string arg = (_hasSample ? ("deltaRSS=" + FormatSignedMiB(bytes) + " Mio, deltaGéré=" + FormatSignedMiB(bytes2) + " Mio, " + $"GC=+{num2 - _lastGen0}/+{num3 - _lastGen1}/+{num4 - _lastGen2}, " + $"deltaZDO={num7:+#,#;-#,#;0} ({num8:+0;-0;0}/h), cpu={num9:F2} cœur") : "référence initiale"); WarheimNetwork.Log.LogInfo((object)("[MemoryWatch] uptime=" + FormatDuration(now - _startedAt) + ", RSS=" + FormatGiB(workingSet) + " Gio, privé=" + FormatGiB(privateMemorySize) + " Gio, virtuel=" + FormatGiB(virtualMemorySize) + " Gio, " + $"géré={FormatGiB(totalMemory)} Gio, threads={num}, ZDO={num5:N0}, " + $"peers={peers}, queue={totalQueue:N0}/{maximumQueue:N0}o, save={SavePressureGuard.IsActive}, " + $"snapshot={SavePressureGuard.SnapshotEntryCount:N0}, growthReady={ZdoGrowthProfiler.TrackingReady}, " + $"pending={ZdoGrowthProfiler.PendingCount:N0}, expirés={ZdoGrowthProfiler.ExpiredUnresolvedCount:N0}, " + $"workers={metrics.ActiveWorkers}/2, workerQueue={metrics.QueueDepth}/{metrics.Outstanding} " + $"(max={metrics.HighWatermark}, mémoire={(double)metrics.InFlightBytes / 1048576.0:F1} Mio), " + $"workerRate={num10:F1}/s " + $"{num11:F2}->{num12:F2} Mio/s, workerBusy={num13:F0}%, " + $"workerBackpressure={metrics.BackpressureEvents}, {arg}.")); _hasSample = true; _lastSampleAt = now; _lastWorkingSet = workingSet; _lastManagedBytes = totalMemory; _lastProcessorTicks = ticks; _lastZdoCount = num5; _lastGen0 = num2; _lastGen1 = num3; _lastGen2 = num4; _lastWorkerJobs = metrics.JobsCompleted; _lastWorkerRawBytes = metrics.SnapshotBytes; _lastWorkerWireBytes = metrics.WireBytes; _lastWorkerBusyTicks = metrics.WorkerBusyTicks; } catch (Exception ex) { if (now - _lastErrorAt >= 300f) { _lastErrorAt = now; WarheimNetwork.Log.LogWarning((object)("[MemoryWatch] Mesure impossible : " + ex.GetType().Name + ": " + ex.Message)); } } } private static string FormatGiB(long bytes) { return ((double)bytes / 1073741824.0).ToString("F2", CultureInfo.InvariantCulture); } private static string FormatSignedMiB(long bytes) { return ((double)bytes / 1048576.0).ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture); } private static string FormatDuration(float seconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(Math.Max(0f, seconds)); return $"{(int)timeSpan.TotalHours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}"; } } 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> AsyncCompressionWorkersEnabled; 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<bool> ZdoAllocationGuardEnabled; internal static ConfigEntry<bool> ZdoGrowthProfilerEnabled; internal static ConfigEntry<bool> MemoryWatchEnabled; internal static ConfigEntry<bool> EwpGlobalLimitGuardEnabled; 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<int> AsyncCompressionQueueCapacity; internal static ConfigEntry<int> AsyncCompressionMaxInFlightMb; internal static ConfigEntry<int> AsyncCompressionBackpressureWarningMs; 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<int> ZdoMaximumCycleBytes; internal static ConfigEntry<int> ZdoLargeObjectWarningBytes; internal static ConfigEntry<float> ZdoGrowthReportSeconds; internal static ConfigEntry<int> ZdoGrowthTopCount; internal static ConfigEntry<float> EwpSlowScanWarningMs; internal static ConfigEntry<float> MemoryWatchIntervalSeconds; 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); AsyncCompressionWorkersEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "AsyncCompressionWorkers", false, "Déporte uniquement la compression de snapshots byte[] sur exactement deux workers. Expérimental : activer d'abord sur un serveur de test et surveiller les diagnostics.", false, (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); ZdoAllocationGuardEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "ZdoAllocationGuard", true, "Réduit les copies temporaires pendant la sérialisation réseau ZDO et plafonne le travail mémoire par cycle.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); ZdoGrowthProfilerEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "ZdoGrowthProfiler", true, "Mesure côté serveur les créations et destructions de ZDO par prefab sans scanner le monde à chaque rapport.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); MemoryWatchEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "MemoryWatch", true, "Journalise la mémoire native et gérée, les collections GC, les ZDO et les files réseau côté serveur.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); EwpGlobalLimitGuardEnabled = ConfigFileExtensions.BindConfig<bool>(config, "01 - Modules", "EwpGlobalLimitGuard", true, "Remplace le scan global objectsLimit d'Expand World Prefabs par une boucle sans LINQ et à arrêt anticipé.", 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<int>(8, 2048); AsyncCompressionQueueCapacity = ConfigFileExtensions.BindConfig<int>(config, "03 - Compression", "AsyncQueueCapacity", 128, "Nombre maximal de paquets possédés simultanément par le pipeline asynchrone. La valeur est lue au démarrage du plugin.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 512); AsyncCompressionMaxInFlightMb = ConfigFileExtensions.BindConfig<int>(config, "03 - Compression", "AsyncMaxInFlightMB", 32, "Volume brut maximal possédé simultanément par le pipeline asynchrone. Au-delà, l'ordre est vidé puis la compression redevient synchrone.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100); AsyncCompressionBackpressureWarningMs = ConfigFileExtensions.BindConfig<int>(config, "03 - Compression", "AsyncBackpressureWarningMs", 5, "Durée d'attente ordonnée avant d'émettre un avertissement de backpressure.", false, (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<int>(65536, 4194304); ZdoMaximumCycleBytes = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "MaximumCycleBytes", 524288, "Budget cumulé maximal de sérialisation ZDO dans un même cycle, tous peers confondus.", true, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(16384, 4194304); ZdoLargeObjectWarningBytes = ConfigFileExtensions.BindConfig<int>(config, "04 - ZDO", "LargeObjectWarningBytes", 131072, "Taille d'un ZDO sérialisé déclenchant un diagnostic de gros objet.", false, (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>(30f, 3600f); ZdoGrowthReportSeconds = ConfigFileExtensions.BindConfig<float>(config, "05 - Contrôle", "ZdoGrowthReportSeconds", 300f, "Intervalle des rapports de croissance ZDO côté serveur.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<int>(3, 50); ZdoGrowthTopCount = ConfigFileExtensions.BindConfig<int>(config, "05 - Contrôle", "ZdoGrowthTopCount", 12, "Nombre de prefabs affichés dans chaque rapport de croissance ZDO.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 1000f); EwpSlowScanWarningMs = ConfigFileExtensions.BindConfig<float>(config, "05 - Contrôle", "EwpSlowScanWarningMs", 25f, "Durée d'un scan global EWP déclenchant un avertissement.", false, (int?)null, val, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); val = (AcceptableValueBase)(object)new AcceptableValueRange<float>(30f, 600f); MemoryWatchIntervalSeconds = ConfigFileExtensions.BindConfig<float>(config, "07 - Diagnostic", "MemoryWatchIntervalSeconds", 60f, "Intervalle des mesures de mémoire, GC, ZDO et files réseau côté serveur.", 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<bool>(AsyncCompressionWorkersEnabled); 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 _zdoByteLimitedCycles; private static long _zdoCycleBytes; private static long _zdoSchedulerMicroseconds; private static long _zdoSendCallMicroseconds; private static long _zdoMaximumCycleMicroseconds; private static long _zdoDirectPackageCopies; private static long _zdoFallbackPackageCopies; private static long _zdoCopiedBytes; private static long _largeSerializedZdos; private static long _largestSerializedZdoBytes; private static int _largestSerializedZdoPrefab; private static long _ewpGlobalScans; private static long _ewpGlobalScannedZdos; private static long _ewpGlobalEarlyExits; private static long _ewpGlobalScanMicroseconds; 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 _globalKeySyncs; private static long _mapModeDowngradesBlocked; private static long _mapModeFallbackRestores; internal static void RecordZdoCycle(int attempted, int sent, int flushed, int forced, int serializedBytes, bool workLimited, bool byteLimited, double schedulerMilliseconds, double sendCallMilliseconds) { _zdoCycles++; _zdoPeersAttempted += attempted; _zdoPeersSent += sent; _zdoFlushes += flushed; _zdoForced += forced; _zdoCycleBytes += Math.Max(0, serializedBytes); long num = Math.Max(0L, (long)(schedulerMilliseconds * 1000.0)); _zdoSchedulerMicroseconds += num; _zdoSendCallMicroseconds += Math.Max(0L, (long)(sendCallMilliseconds * 1000.0)); if (num > _zdoMaximumCycleMicroseconds) { _zdoMaximumCycleMicroseconds = num; } if (workLimited) { _zdoWorkLimitedCycles++; } if (byteLimited) { _zdoByteLimitedCycles++; } } internal static void RecordZdoPackageCopy(int bytes, bool direct) { if (direct) { Interlocked.Increment(ref _zdoDirectPackageCopies); } else { Interlocked.Increment(ref _zdoFallbackPackageCopies); } Interlocked.Add(ref _zdoCopiedBytes, Math.Max(0, bytes)); } internal static void RecordLargeSerializedZdo(int bytes, int prefab) { Interlocked.Increment(ref _largeSerializedZdos); long num; do { num = Interlocked.Read(in _largestSerializedZdoBytes); if (bytes <= num) { return; } } while (Interlocked.CompareExchange(ref _largestSerializedZdoBytes, bytes, num) != num); _largestSerializedZdoPrefab = prefab; } internal static void RecordEwpGlobalScan(int scanned, double milliseconds, bool earlyExit) { Interlocked.Increment(ref _ewpGlobalScans); Interlocked.Add(ref _ewpGlobalScannedZdos, Math.Max(0, scanned)); Interlocked.Add(ref _ewpGlobalScanMicroseconds, Math.Max(0L, (long)(milliseconds * 1000.0))); if (earlyExit) { Interlocked.Increment(ref _ewpGlobalEarlyExits); } } 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 RecordGlobalKeySync() { Interlocked.Increment(ref _globalKeySyncs); } internal static void RecordMapModeDowngradeBlocked() { Interlocked.Increment(ref _mapModeDowngradesBlocked); } internal static void RecordMapModeFallbackRestore() { Interlocked.Increment(ref _mapModeFallbackRestores); } internal static void ResetSession() { _zdoCycles = 0L; _zdoPeersAttempted = 0L; _zdoPeersSent = 0L; _zdoFlushes = 0L; _zdoForced = 0L; _zdoWorkLimitedCycles = 0L; _zdoByteLimitedCycles = 0L; _zdoCycleBytes = 0L; _zdoSchedulerMicroseconds = 0L; _zdoSendCallMicroseconds = 0L; _zdoMaximumCycleMicroseconds = 0L; _zdoDirectPackageCopies = 0L; _zdoFallbackPackageCopies = 0L; _zdoCopiedBytes = 0L; _largeSerializedZdos = 0L; _largestSerializedZdoBytes = 0L; _largestSerializedZdoPrefab = 0; _ewpGlobalScans = 0L; _ewpGlobalScannedZdos = 0L; _ewpGlobalEarlyExits = 0L; _ewpGlobalScanMicroseconds = 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; _globalKeySyncs = 0L; _mapModeDowngradesBlocked = 0L; _mapModeFallbackRestores = 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}, sérialisé={(double)_zdoCycleBytes / 1048576.0:F1} Mio, " + $"main={(double)_zdoSchedulerMicroseconds / 1000.0:F1} ms total/" + $"{(double)_zdoSchedulerMicroseconds / Math.Max(1.0, _zdoCycles) / 1000.0:F2} ms moy/" + $"{(double)_zdoMaximumCycleMicroseconds / 1000.0:F2} ms max, " + $"SendZDOs={(double)_zdoSendCallMicroseconds / 1000.0:F1} ms, " + $"cycles limités CPU={_zdoWorkLimitedCycles}, mémoire={_zdoByteLimitedCycles}.")); } if (_zdoDirectPackageCopies > 0 || _zdoFallbackPackageCopies > 0) { string text = ((_largeSerializedZdos > 0) ? ($"gros ZDO={_largeSerializedZdos}, maximum={_largestSerializedZdoBytes} octets " + $"({ZdoGrowthProfiler.ResolvePrefabName(_largestSerializedZdoPrefab)}/{_largestSerializedZdoPrefab})") : "gros ZDO=0"); WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session ZDOAlloc : copies directes={_zdoDirectPackageCopies}, " + $"fallback vanilla={_zdoFallbackPackageCopies}, données={(double)_zdoCopiedBytes / 1048576.0:F1} Mio, " + text + ".")); } if (_ewpGlobalScans > 0) { WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session EWPGuard : scans={_ewpGlobalScans}, ZDO examinés={_ewpGlobalScannedZdos:N0}, " + $"arrêts anticipés={_ewpGlobalEarlyExits}, temps={(double)_ewpGlobalScanMicroseconds / 1000.0:F1} ms.")); } 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 (_globalKeySyncs > 0 || _mapModeDowngradesBlocked > 0 || _mapModeFallbackRestores > 0) { WarheimNetwork.Log.LogInfo((object)($"[Diagnostic] Session MapSync : synchronisations GlobalKeys={_globalKeySyncs}, " + $"rabattements bloqués={_mapModeDowngradesBlocked}, " + $"restaurations de secours={_mapModeFallbackRestores}.")); } TransportCompression.LogSessionSummary(); AsyncCompressionWorkers.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; double num2 = (NetworkConfig.IsModuleEnabled(NetworkConfig.ZdoAllocationGuardEnabled) ? ((double)((float)NetworkConfig.ZdoMaximumCycleBytes.Value / Math.Max(NetworkConfig.ZdoSendInterval.Value, 0.01f)) / 1048576.0) : num); 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}%), " + $"workersCompression={NetworkConfig.IsModuleEnabled(NetworkConfig.AsyncCompressionWorkersEnabled)} " + $"(2 threads, capacité={NetworkConfig.AsyncCompressionQueueCapacity.Value}/" + $"{NetworkConfig.AsyncCompressionMaxInFlightMb.Value} Mio), " + $"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}, " + $"cycleMax={NetworkConfig.ZdoMaximumCycleBytes.Value}, " + $"adaptive={NetworkConfig.IsModuleEnabled(NetworkConfig.AdaptiveBackpressureEnabled)}, " + $"allocGuard={NetworkConfig.IsModuleEnabled(NetworkConfig.ZdoAllocationGuardEnabled)}, " + $"growth={NetworkConfig.IsModuleEnabled(NetworkConfig.ZdoGrowthProfilerEnabled)}, " + $"memoryWatch={NetworkConfig.IsModuleEnabled(NetworkConfig.MemoryWatchEnabled)} " + $"({NetworkConfig.MemoryWatchIntervalSeconds.Value:F0}s), " + $"ewpGuard={NetworkConfig.IsModuleEnabled(NetworkConfig.EwpGlobalLimitGuardEnabled)}, " + $"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 brut={num:F1} Mio/s, protégé={num2: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(); AsyncCompressionWorkers.ResetSession(); TransportCompression.Reset(); ShipSyncPatches.Reset(); SavePressureGuard.Reset(); MapSyncGuard.Reset(); ZPackageWriteGuard.Reset(); EwpGlobalLimitGuard.Reset(); MemoryWatch.Reset(); EwpGlobalLimitGuard.TryInstall(WarheimNetwork.HarmonyInstance, "Game.Start"); NetworkDiagnostics.AuditLifecyclePatches("Game.Start"); NetworkDiagnostics.AuditSavePatches("Game.Start"); SavePressureGuard.FinalizeCompatibilityAudit(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] [HarmonyPrefix] private static void ZNetShutdownPrefix() { if (ZDOM