Decompiled source of ValheimCommunityPatchGC v1.0.0

plugins/ValheimCommunityPatchGC.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Scripting;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ValheimCommunityPatchGC")]
[assembly: AssemblyDescription("Garbage collector tuning for Valheim dedicated servers.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ValheimCommunityPatchGC")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: AssemblyFileVersion("1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ValheimCommunityPatchGC;

internal static class Logger
{
	internal static void LogDebug(string message)
	{
		ManualLogSource log = ValheimCommunityPatchGC.Log;
		if (log != null)
		{
			log.LogDebug((object)message);
		}
	}

	internal static void LogInfo(string message)
	{
		ManualLogSource log = ValheimCommunityPatchGC.Log;
		if (log != null)
		{
			log.LogInfo((object)message);
		}
	}

	internal static void LogWarning(string message)
	{
		ManualLogSource log = ValheimCommunityPatchGC.Log;
		if (log != null)
		{
			log.LogWarning((object)message);
		}
	}
}
internal static class MonoGcNative
{
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	private delegate byte GetByteFn();

	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	private delegate long GetInt64Fn();

	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	private delegate ulong GetUInt64Fn();

	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	private delegate UIntPtr GetWordFn();

	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	private delegate void SetWordFn(UIntPtr value);

	private static class Kernel32
	{
		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
		internal static extern IntPtr GetModuleHandleW(string moduleName);

		[DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Ansi, ExactSpelling = true)]
		internal static extern IntPtr GetProcAddress(IntPtr module, string procName);
	}

	private static class LibDl
	{
		[DllImport("libdl.so.2")]
		internal static extern IntPtr dlopen(string file, int mode);

		[DllImport("libdl.so.2")]
		internal static extern IntPtr dlsym(IntPtr handle, string name);
	}

	private static class LibC
	{
		[DllImport("libc.so.6")]
		internal static extern IntPtr dlopen(string file, int mode);

		[DllImport("libc.so.6")]
		internal static extern IntPtr dlsym(IntPtr handle, string name);
	}

	internal static readonly bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;

	internal static readonly bool IsLinux = Environment.OSVersion.Platform == PlatformID.Unix && File.Exists("/proc/self/maps");

	private const string WindowsModule = "mono-2.0-bdwgc.dll";

	private static bool _resolved;

	private static string _failure;

	private static string _library;

	private static IntPtr _handle;

	private static bool _dlsymFromLibC;

	private static long _lo;

	private static long _hi;

	private static IntPtr _module;

	private static GetByteFn _monoIsIncremental;

	private static GetInt64Fn _monoGetTimeSliceNs;

	private static GetWordFn _getFreeSpaceDivisor;

	private static SetWordFn _setFreeSpaceDivisor;

	private static GetWordFn _getGcNo;

	private static GetWordFn _getHeapSize;

	private static GetWordFn _getBytesSinceGc;

	private static GetUInt64Fn _getTimeLimitNs;

	private static IntPtr _markStackSize;

	private static IntPtr _markStackTooSmall;

	private static IntPtr _printStats;

	private static IntPtr _rescuingPages;

	internal static bool HasDivisor
	{
		get
		{
			if (EnsureResolved() && _getFreeSpaceDivisor != null)
			{
				return _setFreeSpaceDivisor != null;
			}
			return false;
		}
	}

	internal static bool HasMarkStack
	{
		get
		{
			if (EnsureResolved() && _markStackSize != IntPtr.Zero && _markStackTooSmall != IntPtr.Zero)
			{
				return _getGcNo != null;
			}
			return false;
		}
	}

	internal static bool HasPrintStats
	{
		get
		{
			if (EnsureResolved())
			{
				return _printStats != IntPtr.Zero;
			}
			return false;
		}
	}

	internal static string Describe()
	{
		if (!EnsureResolved())
		{
			return "unavailable (" + _failure + ")";
		}
		return (IsLinux ? "Linux, " : "Windows, ") + _library;
	}

	internal static bool? IsIncremental()
	{
		if (!EnsureResolved() || _monoIsIncremental == null)
		{
			return null;
		}
		try
		{
			return _monoIsIncremental() != 0;
		}
		catch (Exception ex)
		{
			Fail(ex);
			return null;
		}
	}

	internal static long? MonoTimeSliceNs()
	{
		if (!EnsureResolved() || _monoGetTimeSliceNs == null)
		{
			return null;
		}
		try
		{
			return _monoGetTimeSliceNs();
		}
		catch (Exception ex)
		{
			Fail(ex);
			return null;
		}
	}

	internal static long? FreeSpaceDivisor()
	{
		return ReadWord(() => _getFreeSpaceDivisor);
	}

	internal static bool SetFreeSpaceDivisor(long value)
	{
		if (value < 1)
		{
			return false;
		}
		if (!EnsureResolved() || _setFreeSpaceDivisor == null)
		{
			return false;
		}
		try
		{
			_setFreeSpaceDivisor(new UIntPtr((ulong)value));
			return true;
		}
		catch (Exception ex)
		{
			Fail(ex);
			return false;
		}
	}

	internal static long? GcNo()
	{
		return ReadWord(() => _getGcNo);
	}

	internal static long? HeapSize()
	{
		return ReadWord(() => _getHeapSize);
	}

	internal static long? BytesSinceGc()
	{
		return ReadWord(() => _getBytesSinceGc);
	}

	internal static long? TimeLimitNs()
	{
		if (!EnsureResolved() || _getTimeLimitNs == null)
		{
			return null;
		}
		try
		{
			return (long)_getTimeLimitNs();
		}
		catch (Exception ex)
		{
			Fail(ex);
			return null;
		}
	}

	internal static long? MarkStackSize()
	{
		return ReadInt64(() => _markStackSize);
	}

	internal static long? RescuingPages()
	{
		return ReadInt64(() => _rescuingPages);
	}

	internal static bool RequestMarkStackGrowth()
	{
		if (!EnsureResolved() || _markStackTooSmall == IntPtr.Zero)
		{
			return false;
		}
		try
		{
			if (Marshal.ReadInt32(_markStackTooSmall) == 0)
			{
				Marshal.WriteInt32(_markStackTooSmall, 1);
			}
			return true;
		}
		catch (Exception ex)
		{
			Fail(ex);
			return false;
		}
	}

	internal static int? PrintStats()
	{
		if (!EnsureResolved() || _printStats == IntPtr.Zero)
		{
			return null;
		}
		try
		{
			return Marshal.ReadInt32(_printStats);
		}
		catch (Exception ex)
		{
			Fail(ex);
			return null;
		}
	}

	internal static bool SetPrintStats(int level)
	{
		if (!EnsureResolved() || _printStats == IntPtr.Zero)
		{
			return false;
		}
		try
		{
			Marshal.WriteInt32(_printStats, level);
			return true;
		}
		catch (Exception ex)
		{
			Fail(ex);
			return false;
		}
	}

	private static long? ReadWord(Func<GetWordFn> field)
	{
		if (!EnsureResolved())
		{
			return null;
		}
		GetWordFn getWordFn = field();
		if (getWordFn == null)
		{
			return null;
		}
		try
		{
			return (long)getWordFn().ToUInt64();
		}
		catch (Exception ex)
		{
			Fail(ex);
			return null;
		}
	}

	private static long? ReadInt64(Func<IntPtr> field)
	{
		if (!EnsureResolved())
		{
			return null;
		}
		IntPtr intPtr = field();
		if (intPtr == IntPtr.Zero)
		{
			return null;
		}
		try
		{
			return Marshal.ReadInt64(intPtr);
		}
		catch (Exception ex)
		{
			Fail(ex);
			return null;
		}
	}

	private static void Fail(Exception ex)
	{
		if (_failure == null)
		{
			_failure = ex.GetType().Name + ": " + ex.Message;
			Logger.LogWarning("Server GC: a call into the Mono runtime's collector failed (" + _failure + "), so the settings that need it are off for the rest of this run.");
		}
	}

	private static bool EnsureResolved()
	{
		if (_resolved)
		{
			return _failure == null;
		}
		_resolved = true;
		try
		{
			if (IsWindows)
			{
				ResolveWindows();
			}
			else if (IsLinux)
			{
				ResolveLinux();
			}
			else
			{
				_failure = "unsupported platform " + Environment.OSVersion.Platform;
			}
		}
		catch (Exception ex)
		{
			_failure = ex.GetType().Name + ": " + ex.Message;
		}
		if (_failure != null)
		{
			Logger.LogWarning("Server GC: the Mono runtime's collector could not be reached (" + _failure + "), so the garbage collector settings that need it are unavailable.");
			return false;
		}
		_monoIsIncremental = Fn<GetByteFn>("mono_gc_is_incremental");
		_monoGetTimeSliceNs = Fn<GetInt64Fn>("mono_gc_get_max_time_slice_ns");
		if (IsLinux)
		{
			_getFreeSpaceDivisor = Fn<GetWordFn>("GC_get_free_space_divisor");
			_setFreeSpaceDivisor = Fn<SetWordFn>("GC_set_free_space_divisor");
			_getGcNo = Fn<GetWordFn>("GC_get_gc_no");
			_getHeapSize = Fn<GetWordFn>("GC_get_heap_size");
			_getBytesSinceGc = Fn<GetWordFn>("GC_get_bytes_since_gc");
			_getTimeLimitNs = Fn<GetUInt64Fn>("GC_get_time_limit_ns");
			_markStackSize = Symbol("GC_mark_stack_size");
			_markStackTooSmall = Symbol("GC_mark_stack_too_small");
			_printStats = Symbol("GC_print_stats");
			_rescuingPages = Symbol("GC_n_rescuing_pages");
		}
		Logger.LogDebug("Server GC: collector found: " + Describe() + ".");
		return true;
	}

	private static void ResolveWindows()
	{
		_module = Kernel32.GetModuleHandleW("mono-2.0-bdwgc.dll");
		if (_module == IntPtr.Zero)
		{
			_failure = "mono-2.0-bdwgc.dll is not loaded";
		}
		else
		{
			_library = "mono-2.0-bdwgc.dll";
		}
	}

	private static void ResolveLinux()
	{
		string text = null;
		long num = long.MaxValue;
		long num2 = 0L;
		string[] array = File.ReadAllLines("/proc/self/maps");
		foreach (string text2 in array)
		{
			int num3 = text2.IndexOf('-');
			int num4 = text2.IndexOf(' ');
			if (num3 <= 0 || num4 <= num3)
			{
				continue;
			}
			long num5 = Convert.ToInt64(text2.Substring(0, num3), 16);
			long num6 = Convert.ToInt64(text2.Substring(num3 + 1, num4 - num3 - 1), 16);
			int num7 = text2.IndexOf('/');
			if (num7 < 0)
			{
				if (text != null && num5 == num2 && text2.IndexOf('[') < 0)
				{
					num2 = num6;
				}
				continue;
			}
			string text3 = text2.Substring(num7).TrimEnd(Array.Empty<char>());
			string fileName = Path.GetFileName(text3);
			if (fileName.StartsWith("libmonobdwgc", StringComparison.Ordinal) || fileName.StartsWith("libmonoboehm", StringComparison.Ordinal))
			{
				if (text == null)
				{
					text = text3;
				}
				else if (text3 != text)
				{
					continue;
				}
				if (num5 < num)
				{
					num = num5;
				}
				if (num6 > num2)
				{
					num2 = num6;
				}
			}
		}
		if (text == null)
		{
			_failure = "no Mono runtime library in /proc/self/maps";
			return;
		}
		try
		{
			_handle = LibDl.dlopen(text, 5);
		}
		catch (Exception ex) when (ex is DllNotFoundException || ex is EntryPointNotFoundException)
		{
			_dlsymFromLibC = true;
			_handle = LibC.dlopen(text, 5);
		}
		if (_handle == IntPtr.Zero)
		{
			_failure = "dlopen found no loaded copy of " + text;
			return;
		}
		_lo = num;
		_hi = num2;
		_library = text;
	}

	private static IntPtr Symbol(string name)
	{
		IntPtr intPtr;
		if (IsWindows)
		{
			intPtr = Kernel32.GetProcAddress(_module, name);
		}
		else
		{
			intPtr = (_dlsymFromLibC ? LibC.dlsym(_handle, name) : LibDl.dlsym(_handle, name));
			long num = intPtr.ToInt64();
			if (num < _lo || num >= _hi)
			{
				intPtr = IntPtr.Zero;
			}
		}
		if (intPtr == IntPtr.Zero)
		{
			Logger.LogDebug("Server GC: " + name + " not found in " + _library + ".");
		}
		return intPtr;
	}

	private static T Fn<T>(string name) where T : class
	{
		IntPtr intPtr = Symbol(name);
		if (!(intPtr == IntPtr.Zero))
		{
			return (T)(object)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(T));
		}
		return null;
	}
}
internal static class ServerGcTuning
{
	[HarmonyPatch(typeof(ZDOMan))]
	internal static class UpdateHook
	{
		[HarmonyPostfix]
		[HarmonyPatch("Update")]
		private static void UpdatePostfix()
		{
			if (_active)
			{
				if (_pending)
				{
					_pending = false;
					ApplyAll();
				}
				PollMarkStack();
			}
		}
	}

	[HarmonyPatch(typeof(Terminal))]
	internal static class TerminalHook
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__0_0;

			public static ConsoleOptionsFetcher <>9__0_1;

			internal void <InitTerminalPostfix>b__0_0(ConsoleEventArgs args)
			{
				string text = Run(args);
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(text);
				}
				Logger.LogInfo(text);
			}

			internal List<string> <InitTerminalPostfix>b__0_1()
			{
				return new List<string> { "slice", "divisor", "markstack", "stats" };
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("InitTerminal")]
		private static void InitTerminalPostfix()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			object obj = <>c.<>9__0_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					string text = Run(args);
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString(text);
					}
					Logger.LogInfo(text);
				};
				<>c.<>9__0_0 = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__0_1;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => new List<string> { "slice", "divisor", "markstack", "stats" };
				<>c.<>9__0_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("vcp_gc", "[slice <ms> | divisor <n> | markstack <entries> | stats on|off] Prints or changes the server's garbage collector settings (Valheim Community Patch GC).", (ConsoleEvent)obj, false, false, true, false, false, false, (ConsoleOptionsFetcher)obj2, false, true, false);
		}
	}

	internal static ConfigEntry<float> TimeSliceMs;

	internal static ConfigEntry<int> FreeSpaceDivisor;

	internal static ConfigEntry<int> MarkStackTarget;

	internal const string Command = "vcp_gc";

	private const string Section = "Server - Garbage Collector";

	private const float MaxSliceMs = 50f;

	private const int MaxDivisor = 16;

	private const int MaxMarkStack = 16777216;

	private const int BytesPerMarkStackEntry = 16;

	private static readonly bool IsHeadless = (int)SystemInfo.graphicsDeviceType == 4;

	private static bool _active;

	private static int _mainThread;

	private static volatile bool _pending;

	private static ulong _originalSliceNs;

	private static bool _haveOriginalSlice;

	private static long _originalDivisor;

	private static bool _haveOriginalDivisor;

	private static bool _growing;

	private static long _lastGcNo = -1L;

	private static long _gcNoAtLastGrowth;

	private static long _sizeAtLastGrowth;

	private static float _nextPoll;

	private static bool _warnedSlice;

	private static bool _warnedDivisor;

	private static bool _warnedMarkStack;

	internal static void BindConfig(ConfigFile cfg)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Expected O, but got Unknown
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Expected O, but got Unknown
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Expected O, but got Unknown
		TimeSliceMs = cfg.Bind<float>("Server - Garbage Collector", "GC Time Slice Milliseconds", 0f, new ConfigDescription("Dedicated server only. Milliseconds Unity's incremental garbage collector may run each frame; Valheim ships 3. On a busy server it did not change the pauses: the final stop-the-world pass of each collection measured the same at 3 and 10 ms, and a longer slice only lengthens the short pause that opens each collection. 0 leaves the game's value. Applies immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 50f), Array.Empty<object>()));
		FreeSpaceDivisor = cfg.Bind<int>("Server - Garbage Collector", "GC Free Space Divisor", 0, new ConfigDescription("Dedicated server, Linux only. How far the heap grows between collections: about the collector's working set divided by this, halved while it collects incrementally; the collector's own value is 3. The final pause of each collection grows with the heap, about 55 ms per GB on a large world, while collections come less often, so this trades pause length for frequency and barely changes the total time paused. Lower gives fewer, longer pauses and more memory; higher gives shorter, more frequent ones. 0 leaves the collector's value. Takes effect after the next collection.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16), Array.Empty<object>()));
		MarkStackTarget = cfg.Bind<int>("Server - Garbage Collector", "GC Mark Stack Target Entries", 4194304, new ConfigDescription("Dedicated server, Linux only. Grows the collector's mark stack to at least this many 16-byte entries, one doubling per collection, instead of waiting for a pass that nearly overflows it; a pass that does overflow it aborts the server with 'Unexpected mark stack overflow'. 4194304 (64 MiB) is 8x the size one such abort happened at. The stack doubles, so it stops at the first power of two at or above this, and it never shrinks. 0 leaves it to the collector.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 16777216), Array.Empty<object>()));
		TimeSliceMs.SettingChanged += delegate
		{
			OnChanged();
		};
		FreeSpaceDivisor.SettingChanged += delegate
		{
			OnChanged();
		};
		MarkStackTarget.SettingChanged += delegate
		{
			OnChanged();
		};
	}

	internal static void Apply()
	{
		if (!IsHeadless)
		{
			if (AnyChanged())
			{
				Logger.LogInfo("The 'Server - Garbage Collector' settings are ignored: this process has a graphics device, so it is not a dedicated server.");
			}
		}
		else
		{
			_mainThread = Thread.CurrentThread.ManagedThreadId;
			_active = true;
			ApplyAll();
		}
	}

	internal static void Restore()
	{
		if (!_active)
		{
			return;
		}
		_active = false;
		_growing = false;
		try
		{
			if (_haveOriginalSlice)
			{
				GarbageCollector.incrementalTimeSliceNanoseconds = _originalSliceNs;
			}
			if (_haveOriginalDivisor)
			{
				MonoGcNative.SetFreeSpaceDivisor(_originalDivisor);
			}
		}
		catch (Exception ex)
		{
			Logger.LogWarning("Server GC: could not restore the collector's settings: " + ex.Message);
		}
	}

	private static bool AnyChanged()
	{
		if (object.Equals(((ConfigEntryBase)TimeSliceMs).BoxedValue, ((ConfigEntryBase)TimeSliceMs).DefaultValue) && object.Equals(((ConfigEntryBase)FreeSpaceDivisor).BoxedValue, ((ConfigEntryBase)FreeSpaceDivisor).DefaultValue))
		{
			return !object.Equals(((ConfigEntryBase)MarkStackTarget).BoxedValue, ((ConfigEntryBase)MarkStackTarget).DefaultValue);
		}
		return true;
	}

	private static void OnChanged()
	{
		if (_active)
		{
			if (Thread.CurrentThread.ManagedThreadId == _mainThread)
			{
				ApplyAll();
			}
			else
			{
				_pending = true;
			}
		}
	}

	private static void ApplyAll()
	{
		ApplySlice();
		ApplyDivisor();
		ApplyMarkStack();
	}

	private static void ApplySlice()
	{
		try
		{
			float value = TimeSliceMs.Value;
			ulong incrementalTimeSliceNanoseconds = GarbageCollector.incrementalTimeSliceNanoseconds;
			ulong num;
			if (value <= 0f)
			{
				if (!_haveOriginalSlice)
				{
					return;
				}
				num = _originalSliceNs;
			}
			else
			{
				if (!_haveOriginalSlice)
				{
					_originalSliceNs = incrementalTimeSliceNanoseconds;
					_haveOriginalSlice = true;
				}
				num = (ulong)Math.Round((double)value * 1000000.0);
			}
			if (num != incrementalTimeSliceNanoseconds)
			{
				GarbageCollector.incrementalTimeSliceNanoseconds = num;
				Logger.LogInfo("Server GC: incremental time slice " + Ms(incrementalTimeSliceNanoseconds) + " -> " + Ms(num) + " ms.");
			}
		}
		catch (Exception ex)
		{
			WarnOnce(ref _warnedSlice, "'GC Time Slice Milliseconds' could not be applied: " + ex.Message);
		}
	}

	private static void ApplyDivisor()
	{
		int value = FreeSpaceDivisor.Value;
		if (!MonoGcNative.IsLinux)
		{
			if (value > 0 && !object.Equals(((ConfigEntryBase)FreeSpaceDivisor).BoxedValue, ((ConfigEntryBase)FreeSpaceDivisor).DefaultValue))
			{
				WarnOnce(ref _warnedDivisor, "'GC Free Space Divisor' only works on a Linux server; left alone.");
			}
		}
		else
		{
			if (value <= 0 && !_haveOriginalDivisor)
			{
				return;
			}
			long? num = MonoGcNative.FreeSpaceDivisor();
			if (!num.HasValue || !MonoGcNative.HasDivisor)
			{
				WarnOnce(ref _warnedDivisor, "'GC Free Space Divisor' is not available on this runtime; left alone.");
				return;
			}
			long num2;
			if (value <= 0)
			{
				num2 = _originalDivisor;
			}
			else
			{
				if (!_haveOriginalDivisor)
				{
					_originalDivisor = num.Value;
					_haveOriginalDivisor = true;
				}
				num2 = value;
			}
			if (num2 != num.Value && MonoGcNative.SetFreeSpaceDivisor(num2))
			{
				Logger.LogInfo($"Server GC: free space divisor {num.Value} -> {num2}, from the next collection on.");
			}
		}
	}

	private static void ApplyMarkStack()
	{
		int value = MarkStackTarget.Value;
		if (!MonoGcNative.IsLinux)
		{
			if (value > 0 && !object.Equals(((ConfigEntryBase)MarkStackTarget).BoxedValue, ((ConfigEntryBase)MarkStackTarget).DefaultValue))
			{
				WarnOnce(ref _warnedMarkStack, "'GC Mark Stack Target Entries' only works on a Linux server; left alone.");
			}
			return;
		}
		if (value <= 0)
		{
			if (_growing)
			{
				_growing = false;
				Logger.LogInfo("Server GC: stopped growing the mark stack; it keeps its current size.");
			}
			return;
		}
		long? num = MonoGcNative.MarkStackSize();
		if (!num.HasValue || !MonoGcNative.HasMarkStack)
		{
			WarnOnce(ref _warnedMarkStack, "'GC Mark Stack Target Entries' is not available on this runtime; left alone.");
			return;
		}
		if (num.Value >= value)
		{
			_growing = false;
			return;
		}
		if (!_growing)
		{
			_growing = true;
			_lastGcNo = MonoGcNative.GcNo() ?? (-1);
			_gcNoAtLastGrowth = _lastGcNo;
			_sizeAtLastGrowth = num.Value;
			Logger.LogInfo($"Server GC: growing the mark stack from {num.Value:N0} to {Reached(num.Value, value):N0} " + "entries, one doubling per collection.");
		}
		MonoGcNative.RequestMarkStackGrowth();
	}

	private static void PollMarkStack()
	{
		if (!_growing)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup < _nextPoll)
		{
			return;
		}
		_nextPoll = realtimeSinceStartup + 1f;
		long? num = MonoGcNative.GcNo();
		if (!num.HasValue)
		{
			_growing = false;
		}
		else
		{
			if (num.Value == _lastGcNo)
			{
				return;
			}
			_lastGcNo = num.Value;
			int value = MarkStackTarget.Value;
			long valueOrDefault = MonoGcNative.MarkStackSize().GetValueOrDefault();
			if (value <= 0)
			{
				_growing = false;
				return;
			}
			if (valueOrDefault >= value)
			{
				_growing = false;
				Logger.LogInfo($"Server GC: mark stack reached {valueOrDefault:N0} entries ({valueOrDefault * 16 / 1048576:N0} MiB).");
				return;
			}
			if (valueOrDefault != _sizeAtLastGrowth)
			{
				_sizeAtLastGrowth = valueOrDefault;
				_gcNoAtLastGrowth = num.Value;
			}
			else if (num.Value - _gcNoAtLastGrowth >= 3)
			{
				_growing = false;
				Logger.LogWarning($"Server GC: the mark stack stayed at {valueOrDefault:N0} entries for three collections after " + "being asked to grow, so the request was dropped.");
				return;
			}
			MonoGcNative.RequestMarkStackGrowth();
		}
	}

	private static string Run(ConsoleEventArgs args)
	{
		if (!_active)
		{
			return "vcp_gc: the garbage collector settings only apply on a dedicated server.";
		}
		if (args.Length < 2)
		{
			return BuildStatus();
		}
		string text = args[1].ToLowerInvariant();
		string text2 = ((args.Length > 2) ? args[2].ToLowerInvariant() : string.Empty);
		switch (text)
		{
		case "slice":
		{
			float num2 = default(float);
			if (!args.TryParameterFloat(2, ref num2) || num2 < 0f || num2 > 50f)
			{
				return "vcp_gc slice <milliseconds>: up to " + 50f + "; 0 restores the game's value.";
			}
			TimeSliceMs.Value = num2;
			return "Server GC: incremental time slice now " + Ms(GarbageCollector.incrementalTimeSliceNanoseconds) + " ms (saved).";
		}
		case "divisor":
		{
			if (!MonoGcNative.IsLinux)
			{
				return "vcp_gc divisor: Linux servers only.";
			}
			int num = default(int);
			if (!args.TryParameterInt(2, ref num) || num < 0 || num > 16)
			{
				return "vcp_gc divisor <n>: 1 to " + 16 + "; 0 restores the collector's value.";
			}
			FreeSpaceDivisor.Value = num;
			return "Server GC: free space divisor now " + (MonoGcNative.FreeSpaceDivisor()?.ToString() ?? "unknown") + ", from the next collection on (saved).";
		}
		case "markstack":
		{
			if (!MonoGcNative.IsLinux)
			{
				return "vcp_gc markstack: Linux servers only.";
			}
			if (!long.TryParse(text2.Replace(",", string.Empty).Replace("_", string.Empty), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result > 16777216)
			{
				return "vcp_gc markstack <entries>: up to " + 16777216.ToString("N0") + "; 0 leaves it to the collector.";
			}
			MarkStackTarget.Value = (int)result;
			long valueOrDefault = MonoGcNative.MarkStackSize().GetValueOrDefault();
			if (result == 0L)
			{
				return $"Server GC: mark stack left to the collector at {valueOrDefault:N0} entries (saved).";
			}
			return $"Server GC: mark stack {valueOrDefault:N0} entries, target {result:N0}; it will stop at " + $"{Reached(valueOrDefault, result):N0} (saved).";
		}
		case "stats":
			if (!MonoGcNative.IsLinux || !MonoGcNative.HasPrintStats)
			{
				return "vcp_gc stats: Linux servers only.";
			}
			if (text2 != "on" && text2 != "off")
			{
				return "vcp_gc stats on|off";
			}
			MonoGcNative.SetPrintStats((text2 == "on") ? 1 : 0);
			if (!(text2 == "on"))
			{
				return "Server GC: the collector's own log is off.";
			}
			return "Server GC: the collector's own log is on (not saved). It writes a few lines per collection to the server's standard error, or to GC_LOG_FILE if that was set at launch, not to the BepInEx log.";
		default:
			return "vcp_gc [slice <ms> | divisor <n> | markstack <entries> | stats on|off]";
		}
	}

	internal static string BuildStatus()
	{
		StringBuilder stringBuilder = new StringBuilder(384);
		stringBuilder.Append("Server GC (").Append(MonoGcNative.Describe()).Append("): ");
		bool? flag = MonoGcNative.IsIncremental();
		stringBuilder.Append((!flag.HasValue) ? "incremental unknown" : (flag.Value ? "incremental" : "not incremental"));
		stringBuilder.Append(" | slice ").Append(Ms(GarbageCollector.incrementalTimeSliceNanoseconds)).Append(" ms");
		long? num = MonoGcNative.MonoTimeSliceNs();
		long? num2 = MonoGcNative.TimeLimitNs();
		if (num.HasValue || num2.HasValue)
		{
			stringBuilder.Append(" (runtime ").Append(num.HasValue ? Ms((ulong)num.Value) : "?");
			if (num2.HasValue)
			{
				stringBuilder.Append(", collector ").Append(Ms((ulong)num2.Value));
			}
			stringBuilder.Append(')');
		}
		if (MonoGcNative.IsLinux)
		{
			long? num3 = MonoGcNative.FreeSpaceDivisor();
			long? num4 = MonoGcNative.HeapSize();
			long? num5 = MonoGcNative.BytesSinceGc();
			long? num6 = MonoGcNative.GcNo();
			long? num7 = MonoGcNative.RescuingPages();
			long? num8 = MonoGcNative.MarkStackSize();
			int? num9 = MonoGcNative.PrintStats();
			stringBuilder.Append(" | divisor ").Append(num3?.ToString() ?? "?");
			if (num4.HasValue)
			{
				stringBuilder.Append(" | heap ").Append((num4.Value / 1048576).ToString("N0")).Append(" MB");
			}
			if (num5.HasValue)
			{
				stringBuilder.Append(", ").Append((num5.Value / 1048576).ToString("N0")).Append(" MB since last collection");
			}
			if (num6.HasValue)
			{
				stringBuilder.Append(", collection #").Append(num6.Value.ToString("N0"));
			}
			if (num7.HasValue)
			{
				stringBuilder.Append(" | last rescue ").Append(num7.Value.ToString("N0")).Append(" dirty pages");
			}
			if (num8.HasValue)
			{
				stringBuilder.Append(" | mark stack ").Append(num8.Value.ToString("N0")).Append(" entries (")
					.Append((num8.Value * 16 / 1048576).ToString("N0"))
					.Append(" MiB)");
				if (MarkStackTarget.Value > 0)
				{
					stringBuilder.Append(", target ").Append(MarkStackTarget.Value.ToString("N0"));
					if (_growing)
					{
						stringBuilder.Append(", growing");
					}
				}
			}
			if (num9.HasValue)
			{
				stringBuilder.Append(" | collector log ").Append((num9.Value != 0) ? "on" : "off");
			}
		}
		else
		{
			long monoHeapSizeLong = Profiler.GetMonoHeapSizeLong();
			if (monoHeapSizeLong > 0)
			{
				stringBuilder.Append(" | heap ").Append((monoHeapSizeLong / 1048576).ToString("N0")).Append(" MB");
			}
			stringBuilder.Append(" | divisor and mark stack: Linux only");
		}
		stringBuilder.Append(" | collections ").Append(GC.CollectionCount(0).ToString("N0"));
		return stringBuilder.ToString();
	}

	private static long Reached(long size, long target)
	{
		long num;
		for (num = Math.Max(size, 1L); num < target; num *= 2)
		{
		}
		return num;
	}

	private static string Ms(ulong ns)
	{
		return ((double)ns / 1000000.0).ToString("0.###", CultureInfo.InvariantCulture);
	}

	private static void WarnOnce(ref bool warned, string message)
	{
		if (!warned)
		{
			warned = true;
			Logger.LogWarning("Server GC: " + message);
		}
	}
}
[BepInPlugin("MidnightsFX.ValheimCommunityPatchGC", "ValheimCommunityPatchGC", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
internal class ValheimCommunityPatchGC : BaseUnityPlugin
{
	public const string PluginGUID = "MidnightsFX.ValheimCommunityPatchGC";

	public const string PluginName = "ValheimCommunityPatchGC";

	public const string PluginVersion = "1.0.0";

	internal const string CommunityPatchGuid = "MidnightsFX.ValheimCommunityPatch";

	internal static ManualLogSource Log;

	private readonly Harmony harmony = new Harmony("MidnightsFX.ValheimCommunityPatchGC");

	public void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		ServerGcTuning.BindConfig(((BaseUnityPlugin)this).Config);
		if (Chainloader.PluginInfos.TryGetValue("MidnightsFX.ValheimCommunityPatch", out var value) && HasOwnGcSettings(value.Metadata.Version))
		{
			Log.LogWarning((object)($"Valheim Community Patch {value.Metadata.Version} still has its own garbage " + "collector settings, so this plugin does nothing. Update the Community Patch to 0.32.0 or later."));
			return;
		}
		ServerGcTuning.Apply();
		harmony.PatchAll(typeof(ValheimCommunityPatchGC).Assembly);
	}

	private static bool HasOwnGcSettings(Version communityPatch)
	{
		if (communityPatch.Major == 0)
		{
			return communityPatch.Minor == 31;
		}
		return false;
	}

	public void OnDestroy()
	{
		ServerGcTuning.Restore();
		Harmony obj = harmony;
		if (obj != null)
		{
			obj.UnpatchSelf();
		}
	}
}