Decompiled source of FullHouse v1.1.0

FullHouse.dll

Decompiled 5 hours ago
using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using DooDesch.FullHouse;
using HarmonyLib;
using Il2Cpp;
using Il2CppFishySteamworks;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Networking;
using Il2CppScheduleOne.UI.Multiplayer;
using Il2CppSteamworks;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "FullHouse", "1.1.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-FullHouse")]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("DooDesch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © DooDesch")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+68172c920a1f7b30f70d0bd9e54d442511a2987e")]
[assembly: AssemblyProduct("FullHouse")]
[assembly: AssemblyTitle("FullHouse")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyVersion("1.1.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 DooDesch.FullHouse
{
	public sealed class Core : MelonMod
	{
		public override void OnInitializeMelon()
		{
			Lobbies.Install();
		}
	}
	internal static class Lobbies
	{
		internal const int DefaultCapacity = 32;

		private const int HardMax = 250;

		private const int SafeMax = 32;

		private const string GuardName = "___DooDesch_FullHouse___";

		private static bool _installed;

		private static MelonPreferences_Entry<int> _capEntry;

		private static int _hostCap;

		internal static int Capacity
		{
			get
			{
				int num = ((_capEntry != null) ? _capEntry.Value : 32);
				if (num < 2)
				{
					num = 2;
				}
				if (num > 250)
				{
					num = 250;
				}
				return num;
			}
		}

		internal static int EffectiveCap => Math.Min(250, Math.Max(Capacity, _hostCap));

		internal static ulong CurrentLobbyId
		{
			get
			{
				try
				{
					SteamLobbyService val = Service();
					return (val != null) ? val._lobbyID : 0;
				}
				catch
				{
					return 0uL;
				}
			}
		}

		internal static int SeatCount
		{
			get
			{
				try
				{
					SteamLobbyService obj = Service();
					return ((Il2CppArrayBase<CSteamID>)(object)((obj != null) ? obj._players : null))?.Length ?? 0;
				}
				catch
				{
					return 0;
				}
			}
		}

		internal static void Install()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Expected O, but got Unknown
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Expected O, but got Unknown
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Expected O, but got Unknown
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0199: Expected O, but got Unknown
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Expected O, but got Unknown
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Expected O, but got Unknown
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Expected O, but got Unknown
			//IL_0204: Expected O, but got Unknown
			if (_installed)
			{
				return;
			}
			_installed = true;
			try
			{
				if ((Object)(object)GameObject.Find("___DooDesch_FullHouse___") != (Object)null)
				{
					MelonLogger.Msg("[FullHouse] another copy is already active - standing down.");
					return;
				}
				GameObject val = new GameObject("___DooDesch_FullHouse___");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
			}
			catch
			{
			}
			try
			{
				_capEntry = MelonPreferences.CreateCategory("FullHouse", "FullHouse").CreateEntry<int>("Capacity", 32, "Max lobby players", "Maximum co-op players FullHouse seats (2-250). Values above 32 are unsupported and may destabilise the game.", false, false, (ValueValidator)null, (string)null);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] preference registration failed: " + ex.Message);
			}
			if (Capacity > 32)
			{
				MelonLogger.Warning($"[FullHouse] capacity {Capacity} exceeds the tested maximum of {32} - expect instability.");
			}
			try
			{
				Harmony val2 = new Harmony("com.doodesch.fullhouse");
				Patch(val2, typeof(SteamLobbyService), "Initialize", null, "Service_Initialize_Postfix");
				Patch(val2, typeof(SteamLobbyService), "UpdateLobbyMembers", "Service_UpdateLobbyMembers_Prefix");
				Patch(val2, typeof(SteamLobbyService), "CreateLobby", "Service_CreateLobby_Prefix");
				Patch(val2, typeof(SteamLobbyService), "OnLobbyCreated", null, "Service_OnLobbyCreated_Postfix");
				Patch(val2, typeof(SteamLobbyService), "OnLobbyEntered", null, "Service_OnLobbyEntered_Postfix");
				Patch(val2, typeof(SteamLobbyService), "OpenInviteUI", "Service_OpenInviteUI_Prefix");
				Patch(val2, typeof(SteamLobbyService), "LeaveLobby", null, "Service_LeaveLobby_Postfix");
				Patch(val2, typeof(LobbyInterface), "Start", null, "LobbyInterface_Start_Postfix");
				Patch(val2, typeof(LobbyInterface), "UpdateUI", null, "LobbyInterface_UpdateUI_Postfix");
				Patch(val2, typeof(LobbyInterface), "UpdateButtons", null, "LobbyInterface_UpdateButtons_Postfix");
				MelonLogger.Msg($"[FullHouse] active - lobby cap raised to {Capacity}.");
			}
			catch (Exception ex2)
			{
				MelonLogger.Error("[FullHouse] patch install failed: " + ex2);
			}
		}

		private static void Patch(Harmony h, Type type, string method, string prefix = null, string postfix = null)
		{
			MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				MelonLogger.Warning($"[FullHouse] {type.Name}.{method} not found - skipped.");
			}
			else
			{
				h.Patch((MethodBase)methodInfo, Hook(prefix), Hook(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static HarmonyMethod Hook(string name)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (name != null)
			{
				return new HarmonyMethod(typeof(Lobbies).GetMethod(name, AccessTools.all));
			}
			return null;
		}

		private static SteamLobbyService Service()
		{
			try
			{
				Lobby instance = Singleton<Lobby>.Instance;
				ILobbyService val = ((instance != null) ? instance._lobbyService : null);
				if (val == null)
				{
					return null;
				}
				return ((Il2CppObjectBase)val).TryCast<SteamLobbyService>();
			}
			catch
			{
				return null;
			}
		}

		private static void EnsurePlayers(SteamLobbyService svc, int target)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (svc == null || target < 2)
				{
					return;
				}
				Il2CppStructArray<CSteamID> players = svc._players;
				int num = ((Il2CppArrayBase<CSteamID>)(object)players)?.Length ?? 0;
				if (num < target)
				{
					Il2CppStructArray<CSteamID> val = new Il2CppStructArray<CSteamID>((long)target);
					for (int i = 0; i < num; i++)
					{
						((Il2CppArrayBase<CSteamID>)(object)val)[i] = ((Il2CppArrayBase<CSteamID>)(object)players)[i];
					}
					svc._players = val;
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] resizing the lobby seat array failed: " + ex.Message);
			}
		}

		private static void Service_Initialize_Postfix(SteamLobbyService __instance)
		{
			EnsurePlayers(__instance, EffectiveCap);
			RaiseTransportCap();
		}

		private static void RaiseTransportCap()
		{
			try
			{
				FishySteamworks val = Object.FindObjectOfType<FishySteamworks>();
				if (!((Object)(object)val == (Object)null))
				{
					int effectiveCap = EffectiveCap;
					ushort maximumClients = val._maximumClients;
					if (maximumClients < effectiveCap)
					{
						val._maximumClients = (ushort)effectiveCap;
						MelonLogger.Msg($"[FullHouse] transport client limit {maximumClients} -> {effectiveCap}.");
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] raising the transport client limit failed: " + ex.Message);
			}
		}

		private static void Service_UpdateLobbyMembers_Prefix(SteamLobbyService __instance)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				int val = 0;
				try
				{
					ulong num = ((__instance != null) ? __instance._lobbyID : 0);
					if (num != 0L)
					{
						val = SteamMatchmaking.GetNumLobbyMembers(new CSteamID(num));
					}
				}
				catch
				{
				}
				EnsurePlayers(__instance, Math.Max(EffectiveCap, val));
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] seat guard failed: " + ex.Message);
			}
		}

		private static void Service_CreateLobby_Prefix(ref int maxPlayers)
		{
			int effectiveCap = EffectiveCap;
			if (maxPlayers < effectiveCap)
			{
				maxPlayers = effectiveCap;
			}
		}

		private static void Service_OnLobbyCreated_Postfix(LobbyCreated_t result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((int)result.m_eResult == 1)
				{
					RaiseTransportCap();
					CSteamID val = (CSteamID)result.m_ulSteamIDLobby;
					int effectiveCap = EffectiveCap;
					if (SteamMatchmaking.GetLobbyMemberLimit(val) < effectiveCap)
					{
						SteamMatchmaking.SetLobbyMemberLimit(val, effectiveCap);
					}
					int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val);
					if (lobbyMemberLimit < effectiveCap)
					{
						MelonLogger.Warning($"[FullHouse] Steam kept the lobby member limit at {lobbyMemberLimit} (requested {effectiveCap}); advertising {lobbyMemberLimit}.");
					}
					SteamMatchmaking.SetLobbyData(val, "max_players", lobbyMemberLimit.ToString());
					SteamMatchmaking.SetLobbyData(val, "num_slots", lobbyMemberLimit.ToString());
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] OnLobbyCreated failed: " + ex.Message);
			}
		}

		private static void Service_OnLobbyEntered_Postfix(SteamLobbyService __instance, LobbyEnter_t result)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance == null || __instance._lobbyID == 0L)
				{
					return;
				}
				CSteamID val = (CSteamID)result.m_ulSteamIDLobby;
				int result2 = 0;
				int.TryParse(SteamMatchmaking.GetLobbyData(val, "max_players"), out result2);
				int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val);
				if (lobbyMemberLimit > 0 && result2 > lobbyMemberLimit)
				{
					result2 = lobbyMemberLimit;
				}
				if (result2 > _hostCap)
				{
					if (result2 > 32)
					{
						MelonLogger.Warning($"[FullHouse] this lobby seats {result2}, past the tested maximum of {32} - expect instability.");
					}
					_hostCap = result2;
					int effectiveCap = EffectiveCap;
					EnsurePlayers(__instance, effectiveCap);
					EnsureSlots(Singleton<LobbyInterface>.Instance, effectiveCap);
					MelonLogger.Msg($"[FullHouse] adopted host lobby cap {effectiveCap}.");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] host-cap sync failed: " + ex.Message);
			}
		}

		private static void Service_LeaveLobby_Postfix()
		{
			if (_hostCap != 0)
			{
				_hostCap = 0;
				MelonLogger.Msg($"[FullHouse] left the lobby - back to the local cap of {Capacity}.");
			}
		}

		private static bool Service_OpenInviteUI_Prefix(SteamLobbyService __instance)
		{
			//IL_0037: 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_008b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (__instance == null)
				{
					return true;
				}
				if (__instance._lobbyID == 0L)
				{
					__instance.CreateLobby(EffectiveCap);
					return false;
				}
				CSteamID val = default(CSteamID);
				((CSteamID)(ref val))..ctor(__instance._lobbyID);
				int num = EffectiveCap;
				int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val);
				if (lobbyMemberLimit > 0 && lobbyMemberLimit < num)
				{
					num = lobbyMemberLimit;
				}
				if (SteamMatchmaking.GetNumLobbyMembers(val) >= num)
				{
					MelonLogger.Warning($"[FullHouse] lobby already at max capacity ({num}).");
					return false;
				}
				SteamFriends.ActivateGameOverlayInviteDialog(val);
				return false;
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] invite gate failed, falling back to vanilla: " + ex.Message);
				return true;
			}
		}

		private static void LobbyInterface_Start_Postfix(LobbyInterface __instance)
		{
			try
			{
				MelonCoroutines.Start(BuildUi(__instance));
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] UI coroutine start failed: " + ex.Message);
			}
		}

		private static void LobbyInterface_UpdateUI_Postfix(LobbyInterface __instance)
		{
			try
			{
				Lobby instance = Singleton<Lobby>.Instance;
				int num = (((Object)(object)instance != (Object)null) ? instance.PlayerCount : 0);
				if ((Object)(object)((__instance != null) ? __instance.LobbyTitle : null) != (Object)null)
				{
					((TMP_Text)__instance.LobbyTitle).text = "Lobby (" + num + "/" + EffectiveCap + ")";
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] title sync failed: " + ex.Message);
			}
		}

		[HarmonyPriority(600)]
		private static void LobbyInterface_UpdateButtons_Postfix(LobbyInterface __instance)
		{
			try
			{
				Lobby instance = Singleton<Lobby>.Instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)((__instance != null) ? __instance.InviteButton : null) == (Object)null))
				{
					((Component)__instance.InviteButton).gameObject.SetActive(instance.IsHost && instance.PlayerCount < EffectiveCap);
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] invite button sync failed: " + ex.Message);
			}
		}

		private static IEnumerator BuildUi(LobbyInterface ui)
		{
			while ((Object)(object)Singleton<Lobby>.Instance == (Object)null)
			{
				yield return null;
			}
			bool flag = true;
			try
			{
				flag = SteamManager.Initialized;
			}
			catch
			{
			}
			if (flag)
			{
				EnsurePlayers(Service(), EffectiveCap);
				EnsureSlots(ui, EffectiveCap);
			}
		}

		private static void EnsureSlots(LobbyInterface ui, int target)
		{
			if ((Object)(object)ui == (Object)null)
			{
				return;
			}
			try
			{
				Il2CppReferenceArray<RectTransform> playerSlots = ui.PlayerSlots;
				if (playerSlots == null || ((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length == 0)
				{
					MelonLogger.Warning("[FullHouse] lobby PlayerSlots not found.");
					return;
				}
				int length = ((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length;
				if (length >= target)
				{
					RefreshPlayers(ui);
					return;
				}
				RectTransform val = ((Il2CppArrayBase<RectTransform>)(object)playerSlots)[length - 1];
				Transform parent = ((Transform)val).parent;
				RectTransform[] array = (RectTransform[])(object)new RectTransform[target];
				for (int i = 0; i < length; i++)
				{
					array[i] = ((Il2CppArrayBase<RectTransform>)(object)playerSlots)[i];
				}
				for (int j = length; j < target; j++)
				{
					GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent, false);
					((Object)val2).name = ((Object)((Component)val).gameObject).name + " (" + j + ")";
					array[j] = val2.GetComponent<RectTransform>();
				}
				ui.PlayerSlots = Il2CppReferenceArray<RectTransform>.op_Implicit(array);
				RefreshPlayers(ui);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[FullHouse] building lobby UI failed: " + ex.Message);
			}
		}

		private static void RefreshPlayers(LobbyInterface ui)
		{
			try
			{
				ui.UpdatePlayers();
			}
			catch
			{
			}
		}
	}
}