Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of FullHouse v1.1.0
FullHouse.dll
Decompiled 5 hours agousing 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 { } } } }