Decompiled source of MaxPlayerCount v1.2.5

MaxPlayerCount.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
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: AssemblyTitle("MaxPlayerCount")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Azumatt")]
[assembly: AssemblyProduct("MaxPlayerCount")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")]
[assembly: AssemblyFileVersion("1.2.5")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.5.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 MaxPlayerCount
{
	[BepInPlugin("Azumatt.MaxPlayerCount", "MaxPlayerCount", "1.2.5")]
	[BepInIncompatibility("org.bepinex.plugins.valheim_plus")]
	public class MaxPlayerCountPlugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		internal class MaxPlayersCount
		{
			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> MaxPlayersPatch(IEnumerable<CodeInstruction> instructions)
			{
				List<CodeInstruction> list = new List<CodeInstruction>(instructions);
				for (int i = 0; i < list.Count; i++)
				{
					if ((list[i].opcode != OpCodes.Call && list[i].opcode != OpCodes.Callvirt) || !(list[i].operand is MethodInfo methodInfo) || methodInfo.Name != "GetNrOfPlayers")
					{
						continue;
					}
					for (int j = i; j < list.Count; j++)
					{
						if (!(list[j].opcode != OpCodes.Ldc_I4_S))
						{
							int num = Convert.ToInt32(list[j].operand);
							MaxPlayerCountLogger.LogIfDebugBuild($"Patched ZNet.RPC_PeerInfo, the full server check goes from {num} to {ReplacePlayerLimit(num)}");
							list.Insert(j + 1, ReplacePlayerLimitCall());
							return list.AsEnumerable();
						}
					}
					break;
				}
				MaxPlayerCountLogger.LogError((object)"Couldn't find the full server check in ZNet.RPC_PeerInfo, the limit is still vanilla");
				return list.AsEnumerable();
			}

			internal static CodeInstruction ReplacePlayerLimitCall()
			{
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0021: Expected O, but got Unknown
				return new CodeInstruction(OpCodes.Call, (object)AccessTools.DeclaredMethod(typeof(MaxPlayersCount), "ReplacePlayerLimit", (Type[])null, (Type[])null));
			}

			internal static int ReplacePlayerLimit(int vanillaLimit)
			{
				int value = _maxPlayers.Value;
				if (value >= 1)
				{
					return value + (vanillaLimit - 10);
				}
				return vanillaLimit;
			}
		}

		[HarmonyPatch]
		internal class MaxPlayerPlayfabPatch
		{
			private static IEnumerable<MethodBase> TargetMethods()
			{
				yield return AccessTools.DeclaredMethod(typeof(ZPlayFabMatchmaking), "CreateLobby", (Type[])null, (Type[])null);
				yield return AccessTools.DeclaredMethod(typeof(ZPlayFabMatchmaking), "CreateAndJoinNetwork", (Type[])null, (Type[])null);
			}

			[HarmonyTranspiler]
			private static IEnumerable<CodeInstruction> MaxPlayerPlayfabTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
			{
				List<CodeInstruction> codes = new List<CodeInstruction>(instructions);
				bool patched = false;
				int i = 0;
				while (i < codes.Count)
				{
					yield return codes[i];
					if (!patched && i + 1 < codes.Count && !(codes[i].opcode != OpCodes.Ldc_I4_S) && StoresPlayerLimit(codes[i + 1]))
					{
						int num = Convert.ToInt32(codes[i].operand);
						MaxPlayerCountLogger.LogIfDebugBuild($"Patched Playfab {original.Name}, the crossplay limit goes from {num} to {MaxPlayersCount.ReplacePlayerLimit(num)}");
						yield return MaxPlayersCount.ReplacePlayerLimitCall();
						patched = true;
					}
					int num2 = i + 1;
					i = num2;
				}
				if (!patched)
				{
					MaxPlayerCountLogger.LogError((object)("Couldn't find the player limit in " + original.Name + ", crossplay is still capped at vanilla"));
				}
			}

			private static bool StoresPlayerLimit(CodeInstruction instruction)
			{
				if (instruction.opcode == OpCodes.Stfld)
				{
					if (instruction.operand is FieldInfo fieldInfo)
					{
						return fieldInfo.Name == "MaxPlayers";
					}
					return false;
				}
				if (instruction.operand is MethodInfo methodInfo)
				{
					return methodInfo.Name == "set_MaxPlayerCount";
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(SteamGameServer), "SetMaxPlayerCount")]
		internal class MaxPlayerSteamPatch
		{
			[HarmonyPrefix]
			private static void SetMaxPlayerSteamPrefix(ref int cPlayersMax)
			{
				int value = _maxPlayers.Value;
				if (value >= 1)
				{
					MaxPlayerCountLogger.LogIfDebugBuild($"SteamGameServer.SetMaxPlayerCount asked for {cPlayersMax}, giving it {value}");
					cPlayersMax = value;
				}
			}
		}

		internal const string ModName = "MaxPlayerCount";

		internal const string ModVersion = "1.2.5";

		internal const string Author = "Azumatt";

		private const string ModGUID = "Azumatt.MaxPlayerCount";

		private static string ConfigFileName = "Azumatt.MaxPlayerCount.cfg";

		private static string ConfigFileFullPath;

		private readonly Harmony _harmony = new Harmony("Azumatt.MaxPlayerCount");

		public static MaxPlayerCountPlugin instance;

		private static readonly ManualLogSource MaxPlayerCountLogger;

		private FileSystemWatcher _watcher;

		private readonly object _reloadLock = new object();

		private DateTime _lastConfigReloadTime;

		private const long RELOAD_DELAY = 10000000L;

		private static ConfigEntry<int> _maxPlayers;

		public void Awake()
		{
			instance = this;
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			_maxPlayers = config("1 - General", "MaxPlayerCount", 20, "Override the player count that valheim checks for. Default is double the vanilla max of 10. Steam allows 64 max, setting it higher than that might cause issues. I\"m unsure of the limit for Playfab/crossplay.");
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			_harmony.PatchAll(executingAssembly);
			SetupWatcher();
			((BaseUnityPlugin)this).Config.Save();
			if (saveOnConfigSet)
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private void OnDestroy()
		{
			SaveWithRespectToConfigSet();
			_watcher?.Dispose();
		}

		private void SetupWatcher()
		{
			_watcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName);
			_watcher.Changed += ReadConfigValues;
			_watcher.Created += ReadConfigValues;
			_watcher.Renamed += ReadConfigValues;
			_watcher.IncludeSubdirectories = true;
			_watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			_watcher.EnableRaisingEvents = true;
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			DateTime now = DateTime.Now;
			if (now.Ticks - _lastConfigReloadTime.Ticks < 10000000)
			{
				return;
			}
			lock (_reloadLock)
			{
				if (!File.Exists(ConfigFileFullPath))
				{
					MaxPlayerCountLogger.LogWarning((object)"Config file does not exist. Skipping reload.");
					return;
				}
				try
				{
					MaxPlayerCountLogger.LogDebug((object)"Reloading configuration...");
					SaveWithRespectToConfigSet(reload: true);
					MaxPlayerCountLogger.LogInfo((object)"Configuration reload complete.");
				}
				catch (Exception ex)
				{
					MaxPlayerCountLogger.LogError((object)("Error reloading configuration: " + ex.Message));
				}
			}
			_lastConfigReloadTime = now;
		}

		private void SaveWithRespectToConfigSet(bool reload = false)
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			if (reload)
			{
				((BaseUnityPlugin)this).Config.Reload();
			}
			((BaseUnityPlugin)this).Config.Save();
			if (saveOnConfigSet)
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description);
		}

		private ConfigEntry<T> config<T>(string group, string name, T value, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		static MaxPlayerCountPlugin()
		{
			string configPath = Paths.ConfigPath;
			char directorySeparatorChar = Path.DirectorySeparatorChar;
			ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName;
			instance = null;
			MaxPlayerCountLogger = Logger.CreateLogSource("MaxPlayerCount");
			_maxPlayers = null;
		}
	}
	public static class LoggerExtensions
	{
		public static void LogIfDebugBuild(this ManualLogSource logger, string message)
		{
		}
	}
}