Decompiled source of McPack v1.0.0

McPack.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("McPack")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("McPack")]
[assembly: AssemblyTitle("McPack")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace McPack
{
	[BepInPlugin("joaorodrigues.valheim.mcpack", "McPack", "1.0.0")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class McPackPlugin : BaseUnityPlugin
	{
		public const string Guid = "joaorodrigues.valheim.mcpack";

		public const string Name = "McPack";

		public const string Version = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> ModEnabled;

		internal static ConfigEntry<bool> FairScheduler;

		internal static ConfigEntry<float> SendInterval;

		internal static ConfigEntry<int> TotalPassesPerSecond;

		internal static ConfigEntry<int> MaxPeersPerFrame;

		internal static ConfigEntry<bool> TuneSteam;

		internal static ConfigEntry<int> SendRateMax;

		internal static ConfigEntry<int> SendRateMin;

		internal static ConfigEntry<int> SendBufferSize;

		internal static bool Active => ModEnabled.Value;

		private void Awake()
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Expected O, but got Unknown
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Expected O, but got Unknown
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Expected O, but got Unknown
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Expected O, but got Unknown
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Expected O, but got Unknown
			//IL_0230: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Master switch. Turning this off restores stock behaviour without uninstalling.");
			FairScheduler = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Send Scheduler", "FairScheduler", true, "Give every connected player an evenly paced turn to receive world updates, instead of one player per frame always starting from the same player.");
			SendInterval = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Send Scheduler", "SendInterval", 0.05f, new ConfigDescription("Best-case seconds between send passes for one player. The scheduler never goes faster than this, and slows down below it when TotalPassesPerSecond would be exceeded.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.5f), Array.Empty<object>()));
			TotalPassesPerSecond = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Send Scheduler", "TotalPassesPerSecond", 150, new ConfigDescription("Ceiling on send passes per second across all players combined. This is the setting that bounds CPU and upload. Each pass rebuilds and sorts one player's nearby-object list, so this is real work. Stock Valheim lands near 50/sec no matter the player count; 150 is roughly three times that. Raise it only after measuring headroom on the host.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 2000), Array.Empty<object>()));
			MaxPeersPerFrame = ((BaseUnityPlugin)this).Config.Bind<int>("2 - Send Scheduler", "MaxPeersPerFrame", 8, new ConfigDescription("Most players served in one frame. Caps the size of a single spike. 0 removes the cap and is not recommended on a busy server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 128), Array.Empty<object>()));
			TuneSteam = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Steam", "TuneSteam", true, "Let Steam pick its own send rate. The game pins the minimum and maximum to the same value, which stops Steam adapting to the connection at all. Has no effect on crossplay connections, which do not use Steam sockets.");
			SendRateMax = ((BaseUnityPlugin)this).Config.Bind<int>("3 - Steam", "SendRateMax", 1048576, new ConfigDescription("Highest rate in bytes/sec Steam may choose. A ceiling, not a constant send rate. The game sets 153600.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(153600, 50000000), Array.Empty<object>()));
			SendRateMin = ((BaseUnityPlugin)this).Config.Bind<int>("3 - Steam", "SendRateMin", 153600, new ConfigDescription("Lowest rate in bytes/sec Steam may choose. Keeping this low is good: it is the floor Steam backs off to on a struggling connection. Raising it does not make a bad connection faster.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16384, 50000000), Array.Empty<object>()));
			SendBufferSize = ((BaseUnityPlugin)this).Config.Bind<int>("3 - Steam", "SendBufferSize", 1048576, new ConfigDescription("Bytes Steam may hold queued for one connection. Too small rejects bursts; too large lets stale updates pile up during congestion.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(524288, 16777216), Array.Empty<object>()));
			Harmony val = new Harmony("joaorodrigues.valheim.mcpack");
			if (!TryPatch(val, typeof(Patch_SendZDOToPeers2), "ZDOMan.SendZDOToPeers2"))
			{
				FairScheduler.Value = false;
			}
			if (!TryPatch(val, typeof(Patch_RegisterGlobalCallbacks), "ZSteamSocket.RegisterGlobalCallbacks"))
			{
				TuneSteam.Value = false;
			}
			TryPatch(val, typeof(Patch_ZDOMan_Ctor), "ZDOMan..ctor");
			if (FairScheduler.Value && !Scheduler.Ready)
			{
				FairScheduler.Value = false;
				Log.LogError((object)"McPack: could not resolve ZDOMan.SendZDOs; scheduler disabled.");
			}
			Log.LogInfo((object)("McPack 1.0.0 loaded. Scheduler: " + (FairScheduler.Value ? (SendInterval.Value + "s/player, <=" + TotalPassesPerSecond.Value + " passes/s total") : "off") + ". Steam tuning: " + (TuneSteam.Value ? "requested" : "off") + "."));
		}

		private static bool TryPatch(Harmony harmony, Type patchClass, string target)
		{
			try
			{
				harmony.CreateClassProcessor(patchClass).Patch();
				return true;
			}
			catch (Exception ex)
			{
				Log.LogError((object)("McPack: failed to patch " + target + "; that feature is off. " + ex.Message));
				return false;
			}
		}
	}
	internal static class Scheduler
	{
		private static readonly FieldInfo PeersField = AccessTools.Field(typeof(ZDOMan), "m_peers");

		private static readonly FieldInfo NextSendPeerField = AccessTools.Field(typeof(ZDOMan), "m_nextSendPeer");

		private static readonly FieldInfo SendTimerField = AccessTools.Field(typeof(ZDOMan), "m_sendTimer");

		private static readonly MethodInfo SendZDOsMethod = ResolveSendZDOs();

		private static readonly FastInvokeHandler SendZDOsInvoke = ((SendZDOsMethod != null) ? MethodInvoker.GetHandler(SendZDOsMethod, false) : null);

		private const int ConsecutiveFailureLimit = 10;

		private static float _credit;

		private static int _cursor;

		private static int _consecutiveFailures;

		private static bool _broken;

		internal static bool Ready
		{
			get
			{
				if (PeersField != null)
				{
					return SendZDOsInvoke != null;
				}
				return false;
			}
		}

		private static MethodInfo ResolveSendZDOs()
		{
			Type type = AccessTools.Inner(typeof(ZDOMan), "ZDOPeer");
			if (type == null)
			{
				return null;
			}
			MethodInfo[] methods = typeof(ZDOMan).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (!(methodInfo.Name != "SendZDOs") && !(methodInfo.ReturnType != typeof(bool)))
				{
					ParameterInfo[] parameters = methodInfo.GetParameters();
					if (parameters.Length == 2 && parameters[0].ParameterType == type && parameters[1].ParameterType == typeof(bool))
					{
						return methodInfo;
					}
				}
			}
			return null;
		}

		internal static void Reset()
		{
			_credit = 0f;
			_cursor = 0;
			_consecutiveFailures = 0;
			_broken = false;
		}

		internal static bool Tick(ZDOMan man, float dt)
		{
			if (_broken || !Ready)
			{
				return false;
			}
			IList list;
			int count;
			try
			{
				list = PeersField.GetValue(man) as IList;
				if (list == null)
				{
					return false;
				}
				count = list.Count;
			}
			catch (Exception ex)
			{
				_broken = true;
				McPackPlugin.Log.LogError((object)("McPack: cannot read the peer list; scheduler off. " + ex));
				return false;
			}
			if (count == 0)
			{
				_credit = 0f;
				_cursor = 0;
				return true;
			}
			float num = McPackPlugin.SendInterval.Value;
			int value = McPackPlugin.TotalPassesPerSecond.Value;
			if (value > 0)
			{
				float num2 = (float)count / (float)value;
				if (num < num2)
				{
					num = num2;
				}
			}
			if (num < 0.001f)
			{
				num = 0.001f;
			}
			_credit += dt * (float)count / num;
			int num3 = (int)_credit;
			if (num3 <= 0)
			{
				return true;
			}
			if (num3 > count)
			{
				num3 = count;
			}
			int value2 = McPackPlugin.MaxPeersPerFrame.Value;
			if (value2 > 0 && num3 > value2)
			{
				num3 = value2;
			}
			_credit -= num3;
			if (_credit > (float)count)
			{
				_credit = count;
			}
			if (_credit < 0f)
			{
				_credit = 0f;
			}
			int num4 = count;
			int num5 = 0;
			for (int i = 0; i < num3; i++)
			{
				if (num5 >= num4)
				{
					break;
				}
				if (list.Count == 0)
				{
					break;
				}
				if (_cursor >= list.Count)
				{
					_cursor = 0;
				}
				object obj = list[_cursor];
				_cursor++;
				num5++;
				if (obj == null)
				{
					continue;
				}
				try
				{
					SendZDOsInvoke.Invoke((object)man, new object[2] { obj, false });
					_consecutiveFailures = 0;
				}
				catch (Exception ex2)
				{
					if (++_consecutiveFailures > 10)
					{
						_broken = true;
						McPackPlugin.Log.LogError((object)($"McPack: {10} consecutive send failures; " + "scheduler disabled for this world, stock scheduling resumes. " + ex2));
						return false;
					}
					McPackPlugin.Log.LogWarning((object)("McPack: a send pass threw for one peer; continuing. " + (ex2.InnerException ?? ex2).Message));
				}
			}
			try
			{
				NextSendPeerField?.SetValue(man, -1);
				SendTimerField?.SetValue(man, 0f);
			}
			catch
			{
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "SendZDOToPeers2")]
	[HarmonyPriority(200)]
	internal static class Patch_SendZDOToPeers2
	{
		private static bool Prefix(ZDOMan __instance, float dt)
		{
			try
			{
				if (!McPackPlugin.Active)
				{
					return true;
				}
				if (!McPackPlugin.FairScheduler.Value)
				{
					return true;
				}
				return !Scheduler.Tick(__instance, dt);
			}
			catch (Exception)
			{
				return true;
			}
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class Patch_ZDOMan_Ctor
	{
		private static void Postfix()
		{
			Scheduler.Reset();
		}
	}
	internal static class SteamTuning
	{
		private static bool _broken;

		private static bool _loggedOnce;

		internal static void Apply()
		{
			if (_broken || !McPackPlugin.Active || !McPackPlugin.TuneSteam.Value)
			{
				return;
			}
			try
			{
				ApplyCore();
			}
			catch (Exception ex)
			{
				_broken = true;
				McPackPlugin.Log.LogError((object)("McPack: Steam tuning failed and is off for this session; the game's own settings stand. " + ex));
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void ApplyCore()
		{
			int value = McPackPlugin.SendRateMax.Value;
			int num = McPackPlugin.SendRateMin.Value;
			if (num > value)
			{
				num = value;
			}
			bool flag = SetInt((ESteamNetworkingConfigValue)11, value) & SetInt((ESteamNetworkingConfigValue)10, num) & SetInt((ESteamNetworkingConfigValue)9, McPackPlugin.SendBufferSize.Value);
			if (!_loggedOnce)
			{
				_loggedOnce = true;
				if (flag)
				{
					McPackPlugin.Log.LogInfo((object)($"McPack: Steam send rate opened to {num}-{value} B/s (the game pins both at " + $"153600), buffer {McPackPlugin.SendBufferSize.Value} B."));
				}
				else
				{
					McPackPlugin.Log.LogWarning((object)"McPack: Steam refused one or more settings; leaving them as the game set them.");
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool SetInt(ESteamNetworkingConfigValue key, int value)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
			try
			{
				return SteamNetworkingUtils.SetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject());
			}
			finally
			{
				gCHandle.Free();
			}
		}
	}
	[HarmonyPatch(typeof(ZSteamSocket), "RegisterGlobalCallbacks")]
	internal static class Patch_RegisterGlobalCallbacks
	{
		private static void Postfix()
		{
			SteamTuning.Apply();
		}
	}
}