Decompiled source of MoreCriminal MaxPlayers v1.1.1

BepInEx/plugins/MoreCriminal.MaxPlayers/MoreCriminal.MaxPlayers.dll

Decompiled 5 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MoreCriminal.MaxPlayers")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Configurable max player count mod for MoreCriminal online sessions.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("MoreCriminal.MaxPlayers")]
[assembly: AssemblyTitle("MoreCriminal.MaxPlayers")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace MoreCriminal.MaxPlayers;

[BepInPlugin("morecriminal.maxplayers", "MoreCriminal Max Players", "1.1.0")]
public sealed class Plugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	private static int _configuredMaxPlayers;

	private void Awake()
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		ConfigEntry<int> val = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxPlayers", 8, new ConfigDescription("Online host room size override.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
		_configuredMaxPlayers = Math.Max(1, val.Value);
		try
		{
			new Harmony("morecriminal.maxplayers").PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)$"[MaxPlayers] Loaded. Configured max players: {_configuredMaxPlayers}");
		}
		catch (Exception arg)
		{
			Log.LogError((object)$"[MaxPlayers] Failed to apply patches: {arg}");
		}
	}

	internal static int GetConfiguredMaxPlayers()
	{
		return Math.Max(1, _configuredMaxPlayers);
	}

	internal static void UpdateSteamLobbyMetadata(object steamManagerInstance, int currentPlayers)
	{
		try
		{
			if (steamManagerInstance == null)
			{
				return;
			}
			object instanceMemberValue = ReflectionHelper.GetInstanceMemberValue(steamManagerInstance, "current_lobbyID");
			if (instanceMemberValue == null || Convert.ToUInt64(instanceMemberValue) == 0L)
			{
				return;
			}
			Type type = AccessTools.TypeByName("Steamworks.CSteamID");
			Type type2 = AccessTools.TypeByName("Steamworks.SteamMatchmaking");
			if (!(type == null) && !(type2 == null))
			{
				object obj = Activator.CreateInstance(type, Convert.ToUInt64(instanceMemberValue));
				int configuredMaxPlayers = GetConfiguredMaxPlayers();
				MethodInfo methodInfo = AccessTools.Method(type2, "SetLobbyData", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					methodInfo.Invoke(null, new object[3]
					{
						obj,
						"playerCount",
						$"{currentPlayers}/{configuredMaxPlayers}"
					});
					methodInfo.Invoke(null, new object[3]
					{
						obj,
						"maxPlayers",
						configuredMaxPlayers.ToString()
					});
				}
				MethodInfo methodInfo2 = AccessTools.Method(type2, "SetLobbyMemberLimit", (Type[])null, (Type[])null);
				if (methodInfo2 != null)
				{
					methodInfo2.Invoke(null, new object[2] { obj, configuredMaxPlayers });
				}
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[MaxPlayers] Failed to update Steam lobby metadata: " + ex.Message));
		}
	}

	internal static MethodInfo FindMethod(Type type, string name, int parameterCount)
	{
		if (type == null)
		{
			return null;
		}
		MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (MethodInfo methodInfo in methods)
		{
			if (methodInfo.Name == name && methodInfo.GetParameters().Length == parameterCount)
			{
				return methodInfo;
			}
		}
		return null;
	}
}
internal static class ReflectionHelper
{
	private const BindingFlags AllInstanceBindings = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private const BindingFlags AllStaticBindings = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	internal static object GetInstanceMemberValue(object instance, string memberName)
	{
		Type type = instance.GetType();
		while (type != null)
		{
			FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(instance);
			}
			PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null && property.CanRead)
			{
				return property.GetValue(instance, null);
			}
			type = type.BaseType;
		}
		return null;
	}

	internal static object GetStaticMemberValue(Type type, string memberName)
	{
		Type type2 = type;
		while (type2 != null)
		{
			FieldInfo field = type2.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(null);
			}
			PropertyInfo property = type2.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null && property.CanRead)
			{
				return property.GetValue(null, null);
			}
			type2 = type2.BaseType;
		}
		return null;
	}

	internal static bool SetInstanceMemberValue(object instance, string memberName, object value)
	{
		Type type = instance.GetType();
		while (type != null)
		{
			FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				field.SetValue(instance, value);
				return true;
			}
			PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null && property.CanWrite)
			{
				property.SetValue(instance, value, null);
				return true;
			}
			type = type.BaseType;
		}
		return false;
	}
}
[HarmonyPatch]
internal static class PlayOnlinePlayerCountPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("MainMenu+<PlayOnline>d__101");
		if (!(type == null))
		{
			return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null);
		}
		return null;
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		ConstructorInfo objB = AccessTools.Constructor(typeof(int?), new Type[1] { typeof(int) }, false);
		for (int i = 1; i < list.Count; i++)
		{
			if (object.Equals(list[i].operand, objB) && LoadsInt(list[i - 1], 4))
			{
				list[i - 1] = new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Plugin), "GetConfiguredMaxPlayers", (Type[])null, (Type[])null));
				Plugin.Log.LogInfo((object)"[MaxPlayers] Patched MainMenu.PlayOnline room size.");
				return list;
			}
		}
		Plugin.Log.LogWarning((object)"[MaxPlayers] Failed to patch MainMenu.PlayOnline room size.");
		return list;
	}

	private static bool LoadsInt(CodeInstruction instruction, int value)
	{
		if (instruction.opcode == OpCodes.Ldc_I4)
		{
			if (instruction.operand is int num)
			{
				return num == value;
			}
			return false;
		}
		if (instruction.opcode == OpCodes.Ldc_I4_S)
		{
			if (instruction.operand is sbyte b)
			{
				return b == value;
			}
			return false;
		}
		if (instruction.opcode == OpCodes.Ldc_I4_4)
		{
			return value == 4;
		}
		return false;
	}
}
[HarmonyPatch]
internal static class SteamCreateLobbyPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("MainMenu");
		if (!(type == null))
		{
			return AccessTools.Method(type, "Update", (Type[])null, (Type[])null);
		}
		return null;
	}

	private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
	{
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Expected O, but got Unknown
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		bool flag = false;
		for (int i = 1; i < list.Count; i++)
		{
			MethodInfo methodInfo = list[i].operand as MethodInfo;
			if (!(methodInfo == null) && !(methodInfo.Name != "CreateLobby") && LoadsInt(list[i - 1], 4))
			{
				list[i - 1] = new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(Plugin), "GetConfiguredMaxPlayers", (Type[])null, (Type[])null));
				flag = true;
			}
		}
		if (flag)
		{
			Plugin.Log.LogInfo((object)"[MaxPlayers] Patched Steam lobby creation size.");
		}
		else
		{
			Plugin.Log.LogWarning((object)"[MaxPlayers] Failed to patch Steam lobby creation size.");
		}
		return list;
	}

	private static bool LoadsInt(CodeInstruction instruction, int value)
	{
		if (instruction.opcode == OpCodes.Ldc_I4)
		{
			if (instruction.operand is int num)
			{
				return num == value;
			}
			return false;
		}
		if (instruction.opcode == OpCodes.Ldc_I4_S)
		{
			if (instruction.operand is sbyte b)
			{
				return b == value;
			}
			return false;
		}
		if (instruction.opcode == OpCodes.Ldc_I4_4)
		{
			return value == 4;
		}
		return false;
	}
}
[HarmonyPatch]
internal static class SteamLobbyCreatedPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("SteamManager");
		if (!(type == null))
		{
			return AccessTools.Method(type, "OnLobbyCreated", (Type[])null, (Type[])null);
		}
		return null;
	}

	private static void Postfix(object __instance)
	{
		Plugin.UpdateSteamLobbyMetadata(__instance, 1);
	}
}
[HarmonyPatch]
internal static class LobbyPlayerCountPatch
{
	private static MethodBase TargetMethod()
	{
		Type type = AccessTools.TypeByName("Lobby");
		if (!(type == null))
		{
			return AccessTools.Method(type, "SetPlayerCount", (Type[])null, (Type[])null);
		}
		return null;
	}

	private static void Postfix(object __instance)
	{
		try
		{
			int num = ((ReflectionHelper.GetInstanceMemberValue(__instance, "players") is ICollection collection) ? collection.Count : 0);
			if (ReflectionHelper.GetInstanceMemberValue(__instance, "playerSpawns") is List<Transform> list && list.Count > 0)
			{
				int num2 = num % list.Count;
				if (ReflectionHelper.SetInstanceMemberValue(__instance, "spawnedPlayers", num2))
				{
					Plugin.Log.LogInfo((object)$"[MaxPlayers] Lobby spawnedPlayers wrapped to {num2} (players={num}, spawns={list.Count}).");
				}
			}
			Type type = AccessTools.TypeByName("SteamManager");
			if (!(type == null))
			{
				object staticMemberValue = ReflectionHelper.GetStaticMemberValue(type, "Instance");
				if (staticMemberValue != null)
				{
					Plugin.UpdateSteamLobbyMetadata(staticMemberValue, num);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[MaxPlayers] Failed to sync lobby player count: " + ex.Message));
		}
	}
}