Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of LittleLooneys BiggerLobbies v1.4.0
Mods/ScheduleI.PlayerLimit.dll
Decompiled 16 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Authenticating; using Il2CppFishNet.Connection; using Il2CppFishNet.Managing.Server; using Il2CppFishNet.Managing.Transporting; using Il2CppFishNet.Object; using Il2CppFishNet.Transporting; using Il2CppFishNet.Transporting.Multipass; using Il2CppFishySteamworks; using Il2CppFishySteamworks.Server; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.Platform; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Quests; using Il2CppScheduleOne.State; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Multiplayer; using Il2CppSteamworks; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using ScheduleIPlayerLimit; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Mod), "Schedule I Player Limit", "1.4.0", "LittleLooney", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ScheduleI.PlayerLimit")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.4.0.0")] [assembly: AssemblyInformationalVersion("1.4.0")] [assembly: AssemblyProduct("ScheduleI.PlayerLimit")] [assembly: AssemblyTitle("ScheduleI.PlayerLimit")] [assembly: AssemblyVersion("1.4.0.0")] 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; } } } namespace ScheduleIPlayerLimit { [HarmonyPatch(typeof(PlatformAuth), "BeginAuthSession")] internal static class TraceSteamAuthentication { private static void Postfix(EBeginAuthSessionResult __result) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) ModLog.Msg($"[PlayerLimit] Steam BeginAuthSession result: {__result}."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "SetAuthMode")] internal static class TraceAuthenticationMode { private static void Postfix(FishNetSteamAuthenticator __instance) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) ModLog.Msg($"[PlayerLimit] Authentication mode set: {__instance._authMode}."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "OnSteamAuthBroadcast")] internal static class TraceAuthenticationTicket { private static void Prefix(FishNetSteamAuthenticator __instance) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) ModLog.Msg($"[PlayerLimit] Host received authentication ticket; mode={__instance._authMode}."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "SendAuthenticationResponse")] internal static class TraceAuthenticationResponse { private static void Prefix(bool __1) { ModLog.Msg($"[PlayerLimit] Host sending authentication response: accepted={__1}."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "OnHostAuthenticationResult")] internal static class TraceHostAuthentication { private static void Prefix(bool __1) { ModLog.Msg($"[PlayerLimit] Host authentication result: accepted={__1}."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "OnResponseBroadcast")] internal static class TraceClientAuthenticationResponse { private static void Prefix() { ModLog.Msg("[PlayerLimit] Client received authentication response."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "ClientManager_OnClientConnectionState")] internal static class TraceClientConnection { private static void Prefix(ClientConnectionStateArgs __0) { //IL_0018: 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) ModLog.Msg($"Client connection transition: {__0.ConnectionState}."); } } [HarmonyPatch(typeof(FishNetSteamAuthenticator), "ServerManager_OnRemoteConnectionState")] internal static class TraceRemoteConnection { private static void Prefix(RemoteConnectionStateArgs __1) { //IL_0018: 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) ModLog.Msg($"Host remote connection transition: {__1.ConnectionState}."); } } [HarmonyPatch] internal static class TraceAuthenticationExceptions { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(FishNetSteamAuthenticator), "OnSteamAuthBroadcast", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(FishNetSteamAuthenticator), "OnResponseBroadcast", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(PlatformAuth), "BeginAuthSession", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(SteamLobbyService), "JoinAsClient", (Type[])null, (Type[])null); } private static Exception? Finalizer(Exception? __exception, MethodBase __originalMethod) { if (__exception != null) { ModLog.Error("Handshake exception in " + __originalMethod.Name, __exception); } return __exception; } } internal static class DealDiagnostics { private static readonly Dictionary<IntPtr, float> reported = new Dictionary<IntPtr, float>(); public static string State(Customer customer) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(96, 7); defaultInterpolatedStringHandler.AppendLiteral("customer="); NPC nPC = customer.NPC; defaultInterpolatedStringHandler.AppendFormatted((nPC != null) ? nPC.BakedGUID : null); defaultInterpolatedStringHandler.AppendLiteral(", contract="); Contract currentContract = customer.CurrentContract; defaultInterpolatedStringHandler.AppendFormatted((currentContract != null) ? new Guid?(((Quest)currentContract).GUID) : ((Guid?)null)); defaultInterpolatedStringHandler.AppendLiteral(", "); defaultInterpolatedStringHandler.AppendLiteral("awaitingDelivery="); defaultInterpolatedStringHandler.AppendFormatted(customer.IsAwaitingDelivery); defaultInterpolatedStringHandler.AppendLiteral(", offered="); defaultInterpolatedStringHandler.AppendFormatted(customer.OfferedContractInfo != null); defaultInterpolatedStringHandler.AppendLiteral(", "); defaultInterpolatedStringHandler.AppendLiteral("clientInitialized="); defaultInterpolatedStringHandler.AppendFormatted(((NetworkBehaviour)customer).IsClientInitialized); defaultInterpolatedStringHandler.AppendLiteral(", serverInitialized="); defaultInterpolatedStringHandler.AppendFormatted(((NetworkBehaviour)customer).IsServerInitialized); defaultInterpolatedStringHandler.AppendLiteral(", time="); TimeManager instance = NetworkSingleton<TimeManager>.Instance; defaultInterpolatedStringHandler.AppendFormatted((instance != null) ? new int?(instance.CurrentTime) : ((int?)null)); return defaultInterpolatedStringHandler.ToStringAndClear(); } public static void Gate(Customer customer, string gate) { if (ModLog.Enabled && (!reported.TryGetValue(((Il2CppObjectBase)customer).Pointer, out var value) || !(Time.realtimeSinceStartup - value < 10f))) { if (reported.Count > 256) { reported.Clear(); } reported[((Il2CppObjectBase)customer).Pointer] = Time.realtimeSinceStartup; ModLog.Msg($"Deal dialogue gate: {gate}; {State(customer)}."); } } } [HarmonyPatch(typeof(Customer), "IsHandoverChoiceValid")] internal static class TraceHandoverChoice { private static void Postfix(Customer __instance, bool __result, string __0) { DealDiagnostics.Gate(__instance, $"choiceValid={__result}, reason={__0}"); } } [HarmonyPatch(typeof(Customer), "IsReadyForHandover")] internal static class TraceHandoverReadiness { private static void Postfix(Customer __instance, bool __result) { DealDiagnostics.Gate(__instance, $"readyForHandover={__result}"); } } [HarmonyPatch] internal static class TraceDealFlow { private static IEnumerable<MethodBase> TargetMethods() { foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(Customer))) { bool flag; switch (declaredMethod.Name) { case "AcceptContractClicked": case "ContractAccepted": case "SendCounteroffer": case "ProcessHandover": flag = true; break; default: flag = false; break; } if (flag || declaredMethod.Name.StartsWith("RpcLogic___ProcessCounterOfferServerSide_", StringComparison.Ordinal) || declaredMethod.Name.StartsWith("RpcLogic___ProcessHandoverServerSide_", StringComparison.Ordinal) || declaredMethod.Name.StartsWith("RpcLogic___ProcessHandoverClient_", StringComparison.Ordinal) || declaredMethod.Name.StartsWith("RpcLogic___ReceiveCustomerData_", StringComparison.Ordinal)) { yield return declaredMethod; } } foreach (MethodInfo declaredMethod2 in AccessTools.GetDeclaredMethods(typeof(QuestManager))) { if (declaredMethod2.Name.StartsWith("RpcLogic___CreateContract_Networked_", StringComparison.Ordinal)) { yield return declaredMethod2; } } } private static void Prefix(object __instance, MethodBase __originalMethod) { if (ModLog.Enabled) { Customer val = (Customer)((__instance is Customer) ? __instance : null); string value = ((val != null) ? DealDiagnostics.State(val) : "native quest replication"); ModLog.Msg($"Deal flow: {__originalMethod.Name}; {value}."); } } private static Exception? Finalizer(Exception? __exception, MethodBase __originalMethod) { if (__exception != null) { ModLog.Error("Deal flow failed: " + __originalMethod.Name, __exception); } return __exception; } } [HarmonyPatch(typeof(Customer), "EvaluateCounteroffer")] internal static class TraceCounterofferResult { private static void Postfix(Customer __instance, bool __result) { ModLog.Msg($"Native counteroffer evaluation: accepted={__result}; {DealDiagnostics.State(__instance)}."); } } internal static class DealSync { private static readonly Dictionary<string, string> sent = new Dictionary<string, string>(); private static readonly Dictionary<string, string> outgoing = new Dictionary<string, string>(); private static readonly Dictionary<string, (string Contract, bool Awaiting)> incoming = new Dictionary<string, (string, bool)>(); private static readonly Dictionary<ulong, (float Time, IntPtr Connection)> refreshes = new Dictionary<ulong, (float, IntPtr)>(); private static float nextPoll; private static float nextSend; private static float nextRequest; private static ulong lobbyId; private static IEnumerable<Customer> Customers() { HashSet<IntPtr> seen = new HashSet<IntPtr>(); List<Customer>[] array = new List<Customer>[2] { Customer.UnlockedCustomers, Customer.LockedCustomers }; foreach (List<Customer> list in array) { if (list == null) { continue; } for (int j = 0; j < list.Count; j++) { Customer val = list[j]; if ((Object)(object)val != (Object)null && seen.Add(((Il2CppObjectBase)val).Pointer)) { yield return val; } } } } public static void Tick() { Lobby instance = Singleton<Lobby>.Instance; object obj; if (instance == null) { obj = null; } else { ILobbyService lobbyService = instance._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; LoadManager instance2 = Singleton<LoadManager>.Instance; if (val == null || !val.IsInLobby || instance2 == null || !instance2.IsGameLoaded || instance2.IsLoading) { Reset(); return; } if (lobbyId != val._lobbyID) { Reset(); lobbyId = val._lobbyID; } if (Time.realtimeSinceStartup >= nextPoll) { nextPoll = Time.realtimeSinceStartup + 2f; foreach (Customer item in Customers()) { if (val.IsHost) { Queue(item, 0uL, force: false); continue; } RepairCallbacks(item); Apply(item); } if (!val.IsHost && Time.realtimeSinceStartup >= nextRequest) { nextRequest = Time.realtimeSinceStartup + 30f; JoinExchange.Send(val, "deal-refresh"); } } if (val.IsHost && outgoing.Count > 0 && Time.realtimeSinceStartup >= nextSend) { nextSend = Time.realtimeSinceStartup + 0.15f; KeyValuePair<string, string> keyValuePair = outgoing.First(); JoinExchange.Send(val, keyValuePair.Value); outgoing.Remove(keyValuePair.Key); } } private static void Reset() { lobbyId = 0uL; sent.Clear(); outgoing.Clear(); incoming.Clear(); refreshes.Clear(); nextPoll = (nextSend = (nextRequest = 0f)); } private static void Queue(Customer customer, ulong target, bool force) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) NPC nPC = customer.NPC; string text = ((nPC != null) ? nPC.BakedGUID : null) ?? ""; if (!Guid.TryParse(text, out var _)) { return; } Contract currentContract = customer.CurrentContract; string text2 = (((Object)(object)currentContract != (Object)null && (int)((Quest)currentContract).State == 1) ? ((object)((Quest)currentContract).GUID/*cast due to .constrained prefix*/).ToString() : "-"); bool flag = text2 != "-" && customer.IsAwaitingDelivery; string text3 = text2 + "|" + (flag ? "1" : "0"); if (force || !(sent.TryGetValue(text, out string value) ? (value == text3) : (text2 == "-"))) { if (target == 0L) { sent[text] = text3; } outgoing[$"{target}|{text}"] = $"deal|{target}|{text}|{text3}"; } } public static void Receive(SteamLobbyService service, ulong sender, string command) { //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) if (service.IsHost) { if ((!(command == "loaded") && !(command == "deal-refresh")) || 1 == 0) { return; } LoadManager instance = Singleton<LoadManager>.Instance; if (instance == null || !instance.IsGameLoaded || instance.IsLoading || !InstanceFinder.IsServer) { return; } Player val = null; for (int i = 0; i < Player.PlayerList.Count; i++) { Player val2 = Player.PlayerList[i]; if ((Object)(object)val2 != (Object)null && val2.PlayerCode == sender.ToString() && val2.PlayerLoaded && ((NetworkBehaviour)val2).Owner != (NetworkConnection)null && ((NetworkBehaviour)val2).Owner.Authenticated && ((NetworkBehaviour)val2).Owner.IsActive) { val = val2; break; } } if ((Object)(object)val == (Object)null) { return; } (float, IntPtr) value; bool flag = !refreshes.TryGetValue(sender, out value) || value.Item2 != ((Il2CppObjectBase)((NetworkBehaviour)val).Owner).Pointer; if (!flag && Time.realtimeSinceStartup - value.Item1 < 15f) { return; } refreshes[sender] = (Time.realtimeSinceStartup, ((Il2CppObjectBase)((NetworkBehaviour)val).Owner).Pointer); if (flag) { QuestManager instance2 = NetworkSingleton<QuestManager>.Instance; if (instance2 != null) { ((NetworkBehaviour)instance2).OnSpawnServer(((NetworkBehaviour)val).Owner); } MessagingManager instance3 = NetworkSingleton<MessagingManager>.Instance; if (instance3 != null) { ((NetworkBehaviour)instance3).OnSpawnServer(((NetworkBehaviour)val).Owner); } foreach (Customer item in Customers()) { if (((NetworkBehaviour)item).IsServerInitialized) { item.ReceiveCustomerData(((NetworkBehaviour)val).Owner, item.GetCustomerData()); } } ModLog.Msg($"Deal sync: refreshed native quests, phone messages and customer data for authenticated loaded client {sender}."); } { foreach (Customer item2 in Customers()) { Queue(item2, sender, force: true); } return; } } if (sender == SteamMatchmaking.GetLobbyOwner(service._lobbySteamID).m_SteamID && SessionPolicy.TryReadDeal(command, SteamUser.GetSteamID().m_SteamID, out string customer, out string contract, out bool awaiting)) { incoming[customer] = (contract, awaiting); } } private static void Apply(Customer customer) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) NPC nPC = customer.NPC; string text = ((nPC != null) ? nPC.BakedGUID : null) ?? ""; if (!incoming.TryGetValue(text, out (string, bool) value)) { return; } Contract currentContract = customer.CurrentContract; bool flag = false; if (value.Item1.Length > 0 && ((Object)(object)currentContract == (Object)null || !string.Equals(((object)((Quest)currentContract).GUID/*cast due to .constrained prefix*/).ToString(), value.Item1, StringComparison.OrdinalIgnoreCase))) { Contract val = null; for (int i = 0; i < Quest.ActiveQuests.Count; i++) { Quest val2 = Quest.ActiveQuests[i]; if (!((Object)(object)val2 == (Object)null) && string.Equals(((object)val2.GUID/*cast due to .constrained prefix*/).ToString(), value.Item1, StringComparison.OrdinalIgnoreCase)) { Contract val3 = ((Il2CppObjectBase)val2).TryCast<Contract>(); if ((Object)(object)((val3 != null) ? val3.Customer : null) != (Object)null && (Object)(object)((NetworkBehaviour)customer).NetworkObject != (Object)null && ((Il2CppObjectBase)val3.Customer).Pointer == ((Il2CppObjectBase)((NetworkBehaviour)customer).NetworkObject).Pointer) { val = val3; } } } if ((Object)(object)val == (Object)null) { return; } customer.AssignContract(val); flag = true; ModLog.Msg($"Deal sync: restored customer contract link {text}/{value.Item1} from an existing native quest."); } if (flag || customer.IsAwaitingDelivery != value.Item2) { customer.SetIsAwaitingDelivery(value.Item2); ModLog.Msg($"Deal sync: customer {text} delivery-ready={value.Item2} from host snapshot."); } } public static void RepairCallbacks(Customer customer) { if (InstanceFinder.IsServer) { return; } NPC nPC = customer.NPC; object obj; if (nPC == null) { obj = null; } else { MSGConversation mSGConversation = nPC.MSGConversation; obj = ((mSGConversation != null) ? mSGConversation.currentResponses : null); } if (obj == null) { return; } List<Response> currentResponses = customer.NPC.MSGConversation.currentResponses; List<(Response, Action, bool)> list = new List<(Response, Action, bool)>(); bool flag = false; for (int i = 0; i < currentResponses.Count; i++) { Response val = currentResponses[i]; if (val == null) { continue; } if ((Delegate)(object)val.callback != (Delegate)null) { list.Add((val, val.callback, val.disableDefaultResponseBehaviour)); continue; } bool flag2; switch (val.label) { case "ACCEPT_CONTRACT": case "REJECT_CONTRACT": case "COUNTEROFFER": flag2 = true; break; default: flag2 = false; break; } if (flag2) { flag = true; } } if (!flag) { return; } try { customer.RpcLogic___SetUpResponseCallbacks_2166136261(); } finally { foreach (var item in list) { item.Item1.callback = item.Item2; item.Item1.disableDefaultResponseBehaviour = item.Item3; } } ModLog.Msg("Deal sync: repaired missing native phone response callbacks for " + customer.NPC.BakedGUID + "."); } } internal static class HostSession { private static float nextStart; public static bool EnsureReady() { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Invalid comparison between Unknown and I4 //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Invalid comparison between Unknown and I4 //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Invalid comparison between Unknown and I4 ServerManager serverManager = InstanceFinder.ServerManager; TransportManager transportManager = InstanceFinder.TransportManager; if ((Object)(object)serverManager == (Object)null || (Object)(object)transportManager == (Object)null) { return false; } Multipass obj = ((Il2CppObjectBase)transportManager.Transport).TryCast<Multipass>(); FishySteamworks val = ((obj != null) ? obj.GetTransport<FishySteamworks>() : null); if ((Object)(object)val == (Object)null) { ModLog.Warning("Cannot prepare host: Steam transport not found."); return false; } Authenticator authenticator = serverManager.Authenticator; if ((Object)(object)((authenticator != null) ? ((Il2CppObjectBase)authenticator).TryCast<FishNetSteamAuthenticator>() : null) == (Object)null) { FishNetSteamAuthenticator val2 = ((Component)serverManager).gameObject.GetComponent<FishNetSteamAuthenticator>(); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)serverManager).gameObject.AddComponent<FishNetSteamAuthenticator>(); } serverManager.Authenticator = (Authenticator)(object)val2; ModLog.Msg("Installed native Steam authenticator for active host."); } LocalConnectionState connectionState = ((Transport)val).GetConnectionState(true); if ((int)connectionState == 2) { return true; } if ((int)connectionState == 1) { return false; } if ((int)connectionState != 0 || Time.realtimeSinceStartup < nextStart) { return false; } nextStart = Time.realtimeSinceStartup + 5f; ((Transport)val).SetServerBindAddress(((object)SteamUser.GetSteamID()/*cast due to .constrained prefix*/).ToString(), (IPAddressType)0); ((Transport)val).SetMaximumClients(Mod.Limit); bool flag = ((Transport)val).StartConnection(true); ModLog.Msg($"Steam listener start requested: accepted={flag}, state={((Transport)val).GetConnectionState(true)}."); if (flag) { return (int)((Transport)val).GetConnectionState(true) == 2; } return false; } } internal static class JoinExchange { private const string Prefix = "LLBL/1|"; private static ulong lobbyId; private static float nextRequest; private static int requests; private static bool reportedLoaded; private static readonly Dictionary<ulong, float> replies = new Dictionary<ulong, float>(); public static void Send(SteamLobbyService service, string command) { service.SendMessage("LLBL/1|" + command); } public static void Tick() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Invalid comparison between Unknown and I4 Lobby instance = Singleton<Lobby>.Instance; object obj; if (instance == null) { obj = null; } else { ILobbyService lobbyService = instance._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; if (val == null || !val.IsInLobby) { lobbyId = 0uL; return; } if (lobbyId != val._lobbyID) { lobbyId = val._lobbyID; requests = 0; nextRequest = 0f; reportedLoaded = false; replies.Clear(); } if (val.IsHost) { return; } LoadManager instance2 = Singleton<LoadManager>.Instance; if (instance2 != null && instance2.IsGameLoaded) { if (!reportedLoaded) { reportedLoaded = true; val.SendMessage("LLBL/1|loaded"); ModLog.Msg("Client -> host: game loaded acknowledgement sent."); } } else if ((!((Object)(object)instance2 != (Object)null) || !instance2.IsLoading || (int)instance2.LoadStatus == 5) && !(Time.realtimeSinceStartup < nextRequest)) { LateJoin.Watch(val); nextRequest = Time.realtimeSinceStartup + (float)((requests < 12) ? 5 : 15); requests++; val.SendMessage("LLBL/1|request"); ModLog.Msg($"Client -> host: readiness request {requests} sent."); } } public static bool Receive(SteamLobbyService service, LobbyChatMsg_t message) { //IL_0008: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Invalid comparison between Unknown and I4 //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) if (!service.IsInLobby || message.m_ulSteamIDLobby != service._lobbyID) { return false; } Il2CppStructArray<byte> val = new Il2CppStructArray<byte>(256L); CSteamID val2 = default(CSteamID); EChatEntryType val3 = default(EChatEntryType); int lobbyChatEntry = SteamMatchmaking.GetLobbyChatEntry(service._lobbySteamID, (int)message.m_iChatID, ref val2, val, 256, ref val3); if (lobbyChatEntry <= 0 || lobbyChatEntry > 256 || (int)val3 != 1) { return false; } byte[] array = new byte[lobbyChatEntry]; for (int i = 0; i < lobbyChatEntry; i++) { array[i] = ((Il2CppArrayBase<byte>)(object)val)[i]; } string text = Encoding.UTF8.GetString(array).TrimEnd('\0'); if (!text.StartsWith("LLBL/1|", StringComparison.Ordinal)) { return false; } bool flag = false; for (int j = 0; j < SteamMatchmaking.GetNumLobbyMembers(service._lobbySteamID); j++) { if (SteamMatchmaking.GetLobbyMemberByIndex(service._lobbySteamID, j).m_SteamID == val2.m_SteamID) { flag = true; } } if (!flag) { return true; } string text2 = text; int length = "LLBL/1|".Length; string text3 = text2.Substring(length, text2.Length - length); DealSync.Receive(service, val2.m_SteamID, text3); int num; if (service.IsHost && val2.m_SteamID != SteamUser.GetSteamID().m_SteamID) { if (text3 == "loaded") { ModLog.Msg($"Client {val2.m_SteamID} -> host: game loaded acknowledgement received."); } if (text3 == "request" && (!replies.TryGetValue(val2.m_SteamID, out var value) || Time.realtimeSinceStartup - value >= 4f)) { replies[val2.m_SteamID] = Time.realtimeSinceStartup; ModLog.Msg($"Client {val2.m_SteamID} -> host: readiness request received."); LateJoin.Publish(service, notify: false); LoadManager instance = Singleton<LoadManager>.Instance; if (instance != null && instance.IsGameLoaded) { LoadManager instance2 = Singleton<LoadManager>.Instance; if (instance2 != null && !instance2.IsLoading) { num = (HostSession.EnsureReady() ? 1 : 0); goto IL_0237; } } num = 0; goto IL_0237; } } else if (!service.IsHost && val2.m_SteamID == SteamMatchmaking.GetLobbyOwner(service._lobbySteamID).m_SteamID) { if (text3 == $"reply|{SteamUser.GetSteamID().m_SteamID}|ready") { ModLog.Msg("Host -> client: ready reply received; checking native loading path."); LateJoin.Watch(service); } else if (text3 == $"reply|{SteamUser.GetSteamID().m_SteamID}|waiting") { ModLog.Msg("Host -> client: host is still preparing session."); } } goto IL_03c0; IL_0237: bool flag2 = (byte)num != 0; service.SendMessage("LLBL/1|" + $"reply|{val2.m_SteamID}|{(flag2 ? "ready" : "waiting")}"); ModLog.Msg($"Host -> client {val2.m_SteamID}: {(flag2 ? "ready" : "waiting")} reply sent."); goto IL_03c0; IL_03c0: return true; } } [HarmonyPatch(typeof(SteamLobbyService), "OnLobbyChatMessage")] internal static class ReceiveJoinExchange { private static bool Prefix(SteamLobbyService __instance, LobbyChatMsg_t __0) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { return !JoinExchange.Receive(__instance, __0); } catch (Exception error) { ModLog.Error("Lobby-message processing failed", error); return true; } } } internal static class LateJoin { private static readonly HashSet<ulong> pending = new HashSet<ulong>(); private static float nextPoll; private static ulong announcedLobby; private static bool announcedReady; public static void BeginHostLoad() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) Lobby instance = Singleton<Lobby>.Instance; object obj; if (instance == null) { obj = null; } else { ILobbyService lobbyService = instance._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; if (val != null && val.IsInLobby && val.IsHost) { announcedReady = false; announcedLobby = val._lobbyID; SteamMatchmaking.SetLobbyData(val._lobbySteamID, "ready", "false"); ModLog.Msg("[PlayerLimit] Host began loading; lobby readiness withdrawn."); } } public static void Tick() { if (!(Time.realtimeSinceStartup < nextPoll)) { nextPoll = Time.realtimeSinceStartup + 1f; Lobby instance = Singleton<Lobby>.Instance; object obj; if (instance == null) { obj = null; } else { ILobbyService lobbyService = instance._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; if (val == null || !val.IsInLobby || !val.IsHost) { announcedReady = false; announcedLobby = 0uL; } else { Publish(val, notify: false); } } } public static void Publish(SteamLobbyService service, bool notify) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) LoadManager instance = Singleton<LoadManager>.Instance; if (!service.IsInLobby || !service.IsHost || (Object)(object)instance == (Object)null) { return; } if (announcedLobby != service._lobbyID) { announcedLobby = service._lobbyID; announcedReady = false; } if (!instance.IsGameLoaded || instance.IsLoading) { announcedReady = false; if (service.GetLobbyData("ready") == "true") { SteamMatchmaking.SetLobbyData(service._lobbySteamID, "ready", "false"); } } else { if (!InstanceFinder.IsServer || !HostSession.EnsureReady()) { return; } bool flag = service.GetLobbyData("ready") != "true"; if (flag) { if (!SteamMatchmaking.SetLobbyData(service._lobbySteamID, "ready", "true")) { ModLog.Warning("[PlayerLimit] Steam rejected publishing active-session readiness."); return; } ModLog.Msg($"[PlayerLimit] Published active-session readiness for lobby {service._lobbyID}."); } if (flag || notify || !announcedReady) { service.SendMessage(Lobby.JoinReadyMessage); announcedReady = true; ModLog.Msg("[PlayerLimit] Sent native ready message to lobby members."); } } } public static void Watch(SteamLobbyService service) { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) LoadManager instance = Singleton<LoadManager>.Instance; ModLog.Msg($"[PlayerLimit] Lobby entry: id={service._lobbyID}, inLobby={service.IsInLobby}, owner={service.IsHost}, ready={service.GetLobbyData("ready")}, loaded={((instance != null) ? new bool?(instance.IsGameLoaded) : ((bool?)null))}, loading={((instance != null) ? new bool?(instance.IsLoading) : ((bool?)null))}, status={((instance != null) ? new ELoadStatus?(instance.LoadStatus) : ((ELoadStatus?)null))}."); if (service.IsInLobby && !service.IsHost && pending.Add(service._lobbyID)) { MelonCoroutines.Start(WaitForHost(service, service._lobbyID)); } } private static IEnumerator WaitForHost(SteamLobbyService service, ulong id) { float nextWaitLog = Time.realtimeSinceStartup + 60f; try { while (service.IsInLobby && service._lobbyID == id && !service.IsHost) { LoadManager instance = Singleton<LoadManager>.Instance; if ((Object)(object)instance != (Object)null && (instance.IsGameLoaded || (instance.IsLoading && (int)instance.LoadStatus != 5))) { ModLog.Msg($"[PlayerLimit] Client fallback defers to native loader: loaded={instance.IsGameLoaded}, loading={instance.IsLoading}, status={instance.LoadStatus}."); break; } if ((Object)(object)instance != (Object)null && service.GetLobbyData("ready") == "true") { string sessionConnectionIdentifier = service.GetSessionConnectionIdentifier(); if (!string.IsNullOrWhiteSpace(sessionConnectionIdentifier)) { ModLog.Msg($"[PlayerLimit] Client fallback starting the native load flow for lobby {id}."); service.JoinAsClient(sessionConnectionIdentifier); break; } } if (Time.realtimeSinceStartup >= nextWaitLog) { nextWaitLog = Time.realtimeSinceStartup + 60f; ModLog.Msg("[PlayerLimit] Client remains queued for host readiness; waiting continues until loading starts or the lobby is left."); } yield return null; } } finally { pending.Remove(id); } } } [HarmonyPatch(typeof(SteamLobbyService), "OnLobbyCreated")] internal static class PublishCreatedSession { private static void Postfix(SteamLobbyService __instance) { LateJoin.Publish(__instance, notify: false); } } [HarmonyPatch(typeof(SteamLobbyService), "PlayerEnterOrLeave")] internal static class NotifyJoiningPlayer { private static void Postfix(SteamLobbyService __instance) { LateJoin.Publish(__instance, notify: true); } } [HarmonyPatch(typeof(SteamLobbyService), "OnLobbyEntered")] internal static class ClientJoinBackup { private static void Postfix(SteamLobbyService __instance) { LateJoin.Watch(__instance); } } [HarmonyPatch(typeof(SteamLobbyService), "JoinAsClient")] internal static class TraceClientLoad { private static string? pendingConnection; private static bool Prefix(string __0) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_00d2: Unknown result type (might be due to invalid IL or missing references) LoadManager instance = Singleton<LoadManager>.Instance; if ((Object)(object)instance != (Object)null && instance.IsLoading && (int)instance.LoadStatus != 5 && pendingConnection == __0) { ModLog.Msg("[PlayerLimit] Suppressed duplicate JoinAsClient request while native loading is active for the same host."); return false; } pendingConnection = __0; ModLog.Msg($"[PlayerLimit] Native JoinAsClient requested: connection={__0}, loaded={((instance != null) ? new bool?(instance.IsGameLoaded) : ((bool?)null))}, loading={((instance != null) ? new bool?(instance.IsLoading) : ((bool?)null))}, status={((instance != null) ? new ELoadStatus?(instance.LoadStatus) : ((ELoadStatus?)null))}."); return true; } } [HarmonyPatch(typeof(LoadManager), "StartGame")] internal static class WithdrawHostReadiness { private static void Prefix() { LateJoin.BeginHostLoad(); } } public sealed class Mod : MelonMod { private static MelonPreferences_Entry<int> limit; public static int Limit => Math.Clamp(limit.Value, 4, 32); public override void OnUpdate() { try { PauseTimeGuard.Tick(); LateJoin.Tick(); JoinExchange.Tick(); ModLog.Observe(); } catch (Exception error) { ModLog.Error("Mod update failed", error); } try { DealSync.Tick(); } catch (Exception error2) { ModLog.Error("Deal synchronization update failed", error2); } try { SleepGuard.Tick(); } catch (Exception error3) { ModLog.Error("Sleep guard update failed", error3); } } public override void OnInitializeMelon() { limit = MelonPreferences.CreateCategory("ScheduleIPlayerLimit").CreateEntry<int>("MaxPlayers", 8, "Maximum players", "Includes the host. Restart the game after changing. Allowed range: 4–32.", false, false, (ValueValidator)null, (string)null); ModLog.Initialize(); if (ModLog.Enabled) { MelonLogger.Msg(ConsoleColor.Magenta, "+----------------------------------------+"); MelonLogger.Msg(ConsoleColor.Cyan, "| LittleLooney's Mods |"); MelonLogger.Msg(ConsoleColor.Magenta, "| SCHEDULE I / PLAYER LIMIT |"); MelonLogger.Msg(ConsoleColor.Cyan, $"| READY FOR {Limit,2} PLAYERS |"); MelonLogger.Msg(ConsoleColor.Magenta, "+----------------------------------------+"); ModLog.Msg($"Player limit configured to {Limit}. Experimental; test with a fifth client."); } } } [HarmonyPatch(typeof(SteamLobbyService), "OnLobbyCreated")] internal static class VerifySteamCapacity { private static void Postfix(SteamLobbyService __instance) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsInLobby) { return; } CSteamID lobbySteamID = __instance._lobbySteamID; int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(lobbySteamID); bool isHost = __instance.IsHost; ModLog.Msg($"[PlayerLimit] Lobby {__instance._lobbyID}: capacity before enforcement={lobbyMemberLimit}, local owner={isHost}."); if (isHost) { if (lobbyMemberLimit != Mod.Limit) { bool value = SteamMatchmaking.SetLobbyMemberLimit(lobbySteamID, Mod.Limit); ModLog.Msg($"[PlayerLimit] Lobby {__instance._lobbyID}: SetLobbyMemberLimit({Mod.Limit}) returned {value}."); lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(lobbySteamID); } ModLog.Msg($"[PlayerLimit] Lobby {__instance._lobbyID}: Steam reports lobby capacity: {lobbyMemberLimit}."); if (lobbyMemberLimit != Mod.Limit) { ModLog.Warning($"[PlayerLimit] Requested {Mod.Limit} slots but Steam reports {lobbyMemberLimit}."); } } } } [HarmonyPatch(typeof(LobbyInterface), "LateUpdate")] internal static class LobbyCapacityLabel { private static void Postfix(LobbyInterface __instance) { LobbyUi.Refresh(__instance); } } [HarmonyPatch(typeof(LobbyInterface), "Start")] internal static class InitialLobbyUi { private static void Postfix(LobbyInterface __instance) { LobbyUi.Refresh(__instance); } } [HarmonyPatch(typeof(LobbyInterface), "UpdateUI")] internal static class UpdatedLobbyUi { private static void Postfix(LobbyInterface __instance) { LobbyUi.Refresh(__instance); } } internal static class LobbyUi { private static readonly List<LobbyInterface> interfaces = new List<LobbyInterface>(); public static bool NativePanelVisible => interfaces.Any((LobbyInterface ui) => (Object)(object)ui != (Object)null && ((Behaviour)ui).enabled && (Object)(object)ui.Container != (Object)null && ((Component)ui.Container).gameObject.activeInHierarchy && (Object)(object)ui.LobbyTitle != (Object)null && ((Component)ui.LobbyTitle).gameObject.activeInHierarchy); public static void Refresh(LobbyInterface ui) { interfaces.RemoveAll((LobbyInterface item) => (Object)(object)item == (Object)null); if (!interfaces.Contains(ui)) { interfaces.Add(ui); } int num = Capacity(ui); int capacity = ((num > 0) ? num : Mod.Limit); Expand(ui, capacity); if ((Object)(object)ui.LobbyTitle == (Object)null) { return; } string text = ((TMP_Text)ui.LobbyTitle).text; if (text != null) { string text2 = Regex.Replace(text, "(?<=/)\\s*\\d+(?=\\s*\\))", capacity.ToString()); if (text2 != text) { ((TMP_Text)ui.LobbyTitle).text = text2; } } } public static int Capacity(LobbyInterface ui) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) Lobby lobby = ui.Lobby; object obj; if (lobby == null) { obj = null; } else { ILobbyService lobbyService = lobby._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; if (val == null || !val.IsInLobby) { return 0; } return SteamMatchmaking.GetLobbyMemberLimit(val._lobbySteamID); } public static void Expand(LobbyInterface ui, int capacity) { Il2CppReferenceArray<RectTransform> playerSlots = ui.PlayerSlots; if (playerSlots == null || ((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length == 0 || capacity <= ((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length || capacity > 32) { return; } RectTransform val = ((Il2CppArrayBase<RectTransform>)(object)playerSlots)[((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length - 1]; if (!((Object)(object)val == (Object)null)) { Il2CppReferenceArray<RectTransform> val2 = new Il2CppReferenceArray<RectTransform>((long)capacity); for (int i = 0; i < ((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length; i++) { ((Il2CppArrayBase<RectTransform>)(object)val2)[i] = ((Il2CppArrayBase<RectTransform>)(object)playerSlots)[i]; } for (int j = ((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length; j < capacity; j++) { GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Transform)val).parent); ((Object)val3).name = $"PlayerLimitSlot_{j}"; val3.SetActive(false); ((Il2CppArrayBase<RectTransform>)(object)val2)[j] = val3.GetComponent<RectTransform>(); } ui.PlayerSlots = val2; GridLayoutGroup component = ((Component)((Transform)val).parent).GetComponent<GridLayoutGroup>(); if ((Object)(object)component != (Object)null) { component.constraint = (Constraint)1; component.constraintCount = 4; } ModLog.Msg($"[PlayerLimit] Expanded lobby UI from {((Il2CppArrayBase<RectTransform>)(object)playerSlots).Length} to {capacity} portrait slots."); } } } [HarmonyPatch(typeof(SteamLobbyService), "CreateLobby")] internal static class SteamLobbyCapacity { private static void Prefix(ref int __0) { __0 = Mod.Limit; ModLog.Msg($"[PlayerLimit] Creating Steam lobby with {__0} player slots."); } } [HarmonyPatch(typeof(SteamLobbyService), "UpdateLobbyMembers")] internal static class LobbyMemberStorage { private static void Prefix(SteamLobbyService __instance) { if (__instance._players == null || ((Il2CppArrayBase<CSteamID>)(object)__instance._players).Length < 32) { __instance._players = new Il2CppStructArray<CSteamID>(32L); } } } [HarmonyPatch(typeof(FishySteamworks), "SetMaximumClients")] internal static class TransportCapacity { private static void Prefix(ref int __0) { __0 = Mod.Limit; } } [HarmonyPatch(typeof(ServerSocket), "StartConnection")] internal static class SocketCapacity { private static void Prefix(ref int __2) { __2 = Mod.Limit; ModLog.Msg($"[PlayerLimit] Starting server socket with {__2} client slots."); } } [HarmonyPatch(typeof(LobbyInterface), "UpdateButtons")] internal static class InviteCapacity { private static void Postfix(LobbyInterface __instance) { Lobby lobby = __instance.Lobby; if (!((Object)(object)lobby == (Object)null) && !((Object)(object)__instance.InviteButton == (Object)null)) { int num = LobbyUi.Capacity(__instance); bool flag = lobby.IsHost && lobby.PlayerCount < ((num > 0) ? num : Mod.Limit); ((Component)__instance.InviteButton).gameObject.SetActive(flag); ((Selectable)__instance.InviteButton).interactable = flag; } } } [HarmonyPatch(typeof(LobbyInterface), "UpdatePlayers")] internal static class SafeLobbyRows { private static bool Prefix(LobbyInterface __instance) { Lobby lobby = __instance.Lobby; if ((Object)(object)lobby == (Object)null || !lobby.IsInLobby || __instance.PlayerSlots == null) { return true; } List<string> lobbyMemberIDs = lobby.GetLobbyMemberIDs(); if (lobbyMemberIDs == null) { return true; } LobbyUi.Expand(__instance, LobbyUi.Capacity(__instance)); for (int i = 0; i < ((Il2CppArrayBase<RectTransform>)(object)__instance.PlayerSlots).Length; i++) { if (i < lobbyMemberIDs.Count) { __instance.DisplayPlayer(i, lobbyMemberIDs[i]); } else { __instance.ClearPlayer(i); } } return false; } } internal static class ModLog { private static MelonPreferences_Entry<bool> enabled = null; private static MelonPreferences_Entry<bool> stacks = null; private static string lastState = ""; private static string lastError = ""; private static float errorTime; private static float stateTime; private static bool stalled; public static bool Enabled => enabled?.Value ?? false; public static void Initialize() { MelonPreferences_Category obj = MelonPreferences.CreateCategory("ScheduleIPlayerLimit"); enabled = obj.CreateEntry<bool>("EnableLogging", true, "Enable diagnostic logging", (string)null, false, false, (ValueValidator)null, (string)null); stacks = obj.CreateEntry<bool>("IncludeStackTraces", true, "Include exception stack traces", (string)null, false, false, (ValueValidator)null, (string)null); } private static string Context() { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) Lobby instance = Singleton<Lobby>.Instance; object obj; if (instance == null) { obj = null; } else { ILobbyService lobbyService = instance._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; LoadManager instance2 = Singleton<LoadManager>.Instance; return $"[LLBL lobby={((val != null) ? new ulong?(val._lobbyID) : ((ulong?)null))} role={((val != null && val.IsHost) ? "host" : "client")} state={((instance2 != null) ? new ELoadStatus?(instance2.LoadStatus) : ((ELoadStatus?)null))}]"; } public static void Msg(string message) { MelonPreferences_Entry<bool> obj = enabled; if (obj != null && obj.Value) { MelonLogger.Msg(Context() + " " + message); } } public static void Warning(string message) { MelonPreferences_Entry<bool> obj = enabled; if (obj != null && obj.Value) { MelonLogger.Warning(Context() + " " + message); } } public static void Error(string message, Exception error) { MelonPreferences_Entry<bool> obj = enabled; if (obj != null && obj.Value) { string text = message + error.Message; if (!(text == lastError) || !(Time.realtimeSinceStartup - errorTime < 10f)) { lastError = text; errorTime = Time.realtimeSinceStartup; MelonLogger.Error(message + ": " + (stacks.Value ? error.ToString() : error.Message)); } } } public static void Observe() { MelonPreferences_Entry<bool> obj = enabled; if (obj == null || !obj.Value) { return; } LoadManager instance = Singleton<LoadManager>.Instance; string text = $"{Context()} loaded={((instance != null) ? new bool?(instance.IsGameLoaded) : ((bool?)null))} loading={((instance != null) ? new bool?(instance.IsLoading) : ((bool?)null))} server={InstanceFinder.IsServer} client={InstanceFinder.IsClient} timeScale={Time.timeScale}"; if (text == lastState) { if (!stalled && instance != null && instance.IsLoading && Time.realtimeSinceStartup - stateTime >= 60f) { stalled = true; Warning("Loading state has not changed for 60 seconds. Compare the host and client handshake logs for this lobby."); } } else { lastState = text; stateTime = Time.realtimeSinceStartup; stalled = false; MelonLogger.Msg(text); } } } [HarmonyPatch] internal static class TraceLoadingCalls { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(LoadManager), "StartGame", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(LoadManager), "LoadAsClient", (Type[])null, (Type[])null); } private static void Prefix(MethodBase __originalMethod) { ModLog.Msg("Loading entry: " + __originalMethod.Name + "."); } private static Exception? Finalizer(Exception? __exception, MethodBase __originalMethod) { if (__exception != null) { ModLog.Error("Loading exception in " + __originalMethod.Name, __exception); } return __exception; } } [HarmonyPatch(typeof(PauseMenu), "Pause")] internal static class ShowPauseInvite { private static void Postfix(PauseMenu __instance) { PauseInvites.Refresh(__instance); } } [HarmonyPatch(typeof(PauseMenu), "LateUpdate")] internal static class RefreshPauseInvite { private static void Postfix(PauseMenu __instance) { if (__instance.IsPaused) { PauseInvites.Refresh(__instance); } } } [HarmonyPatch(typeof(PauseMenu), "Resume")] internal static class HidePauseInvite { private static void Postfix() { PauseInvites.Hide(); } } [HarmonyPatch(typeof(PauseMenu), "OnDestroy")] internal static class CleanupPauseInvite { private static void Postfix() { PauseInvites.Cleanup(); } } internal static class PauseInvites { private static Button? button; private static TextMeshProUGUI? title; private static int canvasId; private static bool creating; private static bool LocalServerReady() { LoadManager instance = Singleton<LoadManager>.Instance; if (InstanceFinder.IsServer && (Object)(object)instance != (Object)null && instance.IsGameLoaded) { return !instance.IsLoading; } return false; } private static Lobby? ActiveLobby() { return Singleton<Lobby>.Instance; } private static SteamLobbyService? HostService(Lobby? lobby) { if ((Object)(object)lobby == (Object)null || !lobby.IsInLobby) { return null; } ILobbyService lobbyService = lobby._lobbyService; SteamLobbyService val = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); if (val == null || !val.IsHost) { return null; } return val; } public static void Refresh(PauseMenu pause) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) Lobby val = ActiveLobby(); SteamLobbyService val2 = HostService(val); if (!LocalServerReady() || (Object)(object)pause.Canvas == (Object)null || (val2 != null && LobbyUi.NativePanelVisible)) { Hide(); return; } if ((Object)(object)button == (Object)null || canvasId != ((Object)pause.Canvas).GetInstanceID()) { Create(pause); } if ((Object)(object)button == (Object)null) { return; } ((Component)button).gameObject.SetActive(true); if (val2 == null) { ((Selectable)button).interactable = !creating && (Object)(object)val != (Object)null; if ((Object)(object)title != (Object)null) { ((TMP_Text)title).text = (creating ? "Creating lobby..." : "Invite friends"); } return; } int lobbyMemberLimit = SteamMatchmaking.GetLobbyMemberLimit(val2._lobbySteamID); int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(val2._lobbySteamID); ((Selectable)button).interactable = !creating && lobbyMemberLimit > numLobbyMembers; if ((Object)(object)title != (Object)null) { ((TMP_Text)title).text = $"Lobby ({numLobbyMembers}/{lobbyMemberLimit})"; } } private static void Create(PauseMenu pause) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) Cleanup(); GameObject val = new GameObject("LittleLooneyPauseInvite"); RectTransform val2 = val.AddComponent<RectTransform>(); ((Transform)val2).SetParent(((Component)pause.Canvas).transform, false); Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(1f, 1f); val2.anchorMax = val3; val2.anchorMin = val3; val2.pivot = new Vector2(1f, 1f); val2.anchoredPosition = new Vector2(-32f, -60f); val2.sizeDelta = new Vector2(64f, 64f); Image val4 = val.AddComponent<Image>(); ((Graphic)val4).color = new Color(0.1f, 0.12f, 0.17f, 0.95f); button = val.AddComponent<Button>(); ((Selectable)button).targetGraphic = (Graphic)(object)val4; ((UnityEvent)button.onClick).AddListener(UnityAction.op_Implicit((Action)Invite)); TextMeshProUGUI val5 = Label("Plus", (Transform)(object)val2, pause); ((TMP_Text)val5).text = "+"; ((TMP_Text)val5).fontSize = 52f; ((TMP_Text)val5).rectTransform.anchorMin = Vector2.zero; ((TMP_Text)val5).rectTransform.anchorMax = Vector2.one; val3 = (((TMP_Text)val5).rectTransform.offsetMin = (((TMP_Text)val5).rectTransform.offsetMax = Vector2.zero)); title = Label("LobbyCount", (Transform)(object)val2, pause); ((TMP_Text)title).fontSize = 20f; RectTransform rectTransform = ((TMP_Text)title).rectTransform; RectTransform rectTransform2 = ((TMP_Text)title).rectTransform; ((Vector2)(ref val3))..ctor(1f, 1f); rectTransform2.anchorMax = val3; rectTransform.anchorMin = val3; ((TMP_Text)title).rectTransform.pivot = new Vector2(1f, 0f); ((TMP_Text)title).rectTransform.anchoredPosition = new Vector2(0f, 8f); ((TMP_Text)title).rectTransform.sizeDelta = new Vector2(180f, 30f); ((TMP_Text)title).alignment = (TextAlignmentOptions)516; ((Transform)val2).SetAsLastSibling(); canvasId = ((Object)pause.Canvas).GetInstanceID(); ModLog.Msg("[PlayerLimit] Pause-menu invite button created. Enabled when the host session is ready and has space."); } private static TextMeshProUGUI Label(string name, Transform parent, PauseMenu pause) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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) GameObject val = new GameObject(name); ((Transform)val.AddComponent<RectTransform>()).SetParent(parent, false); TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>(); if ((Object)(object)pause.CartelNameLabel != (Object)null) { ((TMP_Text)val2).font = ((TMP_Text)pause.CartelNameLabel).font; } ((Graphic)val2).color = Color.white; ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; ((Graphic)val2).raycastTarget = false; return val2; } private static void Invite() { if (!LocalServerReady() || creating) { return; } Lobby val = ActiveLobby(); SteamLobbyService val2 = HostService(val); if (!((Object)(object)val == (Object)null)) { if (val2 == null) { creating = true; MelonCoroutines.Start(CreateAndInvite(val)); } else { Open(val2, val); } } } private static IEnumerator CreateAndInvite(Lobby lobby) { try { ModLog.Msg("[PlayerLimit] Creating a Steam lobby for the loaded local server."); lobby.CreateLobby(); float deadline = Time.realtimeSinceStartup + 15f; SteamLobbyService val; while ((val = HostService(lobby)) == null && Time.realtimeSinceStartup < deadline && LocalServerReady()) { yield return null; } if (val != null && LocalServerReady()) { Open(val, lobby); } else { ModLog.Warning("[PlayerLimit] Host lobby creation did not complete within 15 seconds."); } } finally { creating = false; } } private static void Open(SteamLobbyService service, Lobby lobby) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (!HostSession.EnsureReady()) { ModLog.Warning("Steam listener is not ready yet; wait a moment and click invite again."); return; } LateJoin.Publish(service, notify: false); ModLog.Msg($"[PlayerLimit] Active-session readiness reported by game: {lobby.IsSessionReadyForClient()}."); CSteamID lobbySteamID = service._lobbySteamID; if (SteamMatchmaking.GetNumLobbyMembers(lobbySteamID) < SteamMatchmaking.GetLobbyMemberLimit(lobbySteamID)) { if (!SteamMatchmaking.SetLobbyJoinable(lobbySteamID, true)) { ModLog.Warning("[PlayerLimit] Steam did not accept making the host lobby joinable."); return; } ModLog.Msg($"[PlayerLimit] Opening active-session invite dialog for lobby {service._lobbyID}."); service.OpenInviteUI(); } } public static void Hide() { if ((Object)(object)button != (Object)null) { ((Component)button).gameObject.SetActive(false); } } public static void Cleanup() { if ((Object)(object)button != (Object)null) { Object.Destroy((Object)(object)((Component)button).gameObject); } button = null; title = null; canvasId = 0; } } internal static class PauseTimeGuard { private static bool ownsFreeze; private static float previousScale; public static void Capture(float before) { if (!(before <= 0f) && Time.timeScale == 0f) { previousScale = before; ownsFreeze = true; ModLog.Msg($"Pause menu froze simulation: timeScale={Time.timeScale}, previous={before}."); } } public static void Tick() { if (!ownsFreeze) { return; } LoadManager instance = Singleton<LoadManager>.Instance; if ((Object)(object)instance == (Object)null || !instance.IsGameLoaded || instance.IsLoading) { Clear(); return; } Lobby instance2 = Singleton<Lobby>.Instance; object obj; if (instance2 == null) { obj = null; } else { ILobbyService lobbyService = instance2._lobbyService; obj = ((lobbyService != null) ? ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>() : null); } SteamLobbyService val = (SteamLobbyService)obj; if (val != null && val.IsInLobby && val.IsHost && InstanceFinder.IsServer) { Restore("Solo pause transitioned to multiplayer lobby"); } } public static void Resumed() { if (ownsFreeze) { Restore("Pause menu resumed"); } } private static void Restore(string reason) { if (Time.timeScale == 0f) { Time.timeScale = previousScale; ModLog.Msg($"{reason}: restored pause-owned timeScale to {previousScale}."); } Clear(); } public static void Clear() { ownsFreeze = false; } } [HarmonyPatch(typeof(PauseMenu), "Pause")] internal static class CapturePauseTime { private static void Prefix(out float __state) { __state = Time.timeScale; } private static void Postfix(float __state) { PauseTimeGuard.Capture(__state); } } [HarmonyPatch(typeof(PauseMenu), "Resume")] internal static class RestorePauseTime { private static void Postfix() { PauseTimeGuard.Resumed(); } } [HarmonyPatch(typeof(PauseMenu), "OnDestroy")] internal static class ClearPauseTime { private static void Postfix() { PauseTimeGuard.Clear(); } } internal static class SessionPolicy { public static bool SleepHours(int time) { if (time < 1800 || time >= 2400) { if (time >= 0) { return time <= 400; } return false; } return true; } public static bool TryReadDeal(string text, ulong localUser, out string customer, out string contract, out bool awaiting) { customer = (contract = ""); awaiting = false; string[] array = text.Split('|'); ulong result; Guid result2; bool flag = array.Length != 5 || array[0] != "deal" || !ulong.TryParse(array[1], out result) || (result != 0L && result != localUser) || !Guid.TryParse(array[2], out result2) || (array[3] != "-" && !Guid.TryParse(array[3], out result2)); if (!flag) { string text2 = array[4]; bool flag2 = ((text2 == "0" || text2 == "1") ? true : false); flag = !flag2; } if (flag) { return false; } customer = array[2]; contract = ((array[3] == "-") ? "" : array[3]); awaiting = array[4] == "1"; if (awaiting) { return contract.Length > 0; } return true; } } internal static class SleepGuard { private static IntPtr controller; private static EPhase phase; private static bool observedSleep; private static float phaseSince; private static float nextPoll; private static float nextWarning; public static bool Allowed() { TimeManager instance = NetworkSingleton<TimeManager>.Instance; if (!GameManager.IS_TUTORIAL && !((Object)(object)instance == (Object)null)) { return SessionPolicy.SleepHours(instance.CurrentTime); } return true; } public static void ClearReady(SleepController sleep) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) if (!InstanceFinder.IsServer) { return; } LoadManager instance = Singleton<LoadManager>.Instance; if (instance == null || !instance.IsGameLoaded) { return; } int num = 0; for (int i = 0; i < Player.PlayerList.Count; i++) { Player val = Player.PlayerList[i]; if (!((Object)(object)val == (Object)null) && val.IsReadyToSleep) { val.IsReadyToSleep = false; num++; } } if (num > 0) { ModLog.Msg($"Sleep guard: cleared {num} authoritative ready flags after wake; phase={sleep.CurrentPhase}."); } } public static void Tick() { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) if (Time.realtimeSinceStartup < nextPoll) { return; } nextPoll = Time.realtimeSinceStartup + 1f; SleepController instance = NetworkSingleton<SleepController>.Instance; LoadManager instance2 = Singleton<LoadManager>.Instance; if ((Object)(object)instance == (Object)null || instance2 == null || !instance2.IsGameLoaded || instance2.IsLoading) { controller = IntPtr.Zero; observedSleep = false; return; } if (controller != ((Il2CppObjectBase)instance).Pointer) { controller = ((Il2CppObjectBase)instance).Pointer; phase = instance.CurrentPhase; observedSleep = instance.IsSleepInProgress; phaseSince = Time.realtimeSinceStartup; nextWarning = phaseSince + 90f; } if (phase != instance.CurrentPhase) { DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(73, 7); defaultInterpolatedStringHandler.AppendLiteral("Sleep phase: "); defaultInterpolatedStringHandler.AppendFormatted<EPhase>(phase); defaultInterpolatedStringHandler.AppendLiteral(" -> "); defaultInterpolatedStringHandler.AppendFormatted<EPhase>(instance.CurrentPhase); defaultInterpolatedStringHandler.AppendLiteral("; time="); TimeManager instance3 = NetworkSingleton<TimeManager>.Instance; defaultInterpolatedStringHandler.AppendFormatted((instance3 != null) ? new int?(instance3.CurrentTime) : ((int?)null)); defaultInterpolatedStringHandler.AppendLiteral(", hostReady="); defaultInterpolatedStringHandler.AppendFormatted(instance.IsHostReadyToProceed); defaultInterpolatedStringHandler.AppendLiteral(", sleepingState="); MonoStateMachine sleepingState = instance.SleepingState; defaultInterpolatedStringHandler.AppendFormatted((sleepingState != null) ? new bool?(((MonoState)sleepingState).IsActive) : ((bool?)null)); defaultInterpolatedStringHandler.AppendLiteral(", canMove="); PlayerMovement instance4 = PlayerSingleton<PlayerMovement>.Instance; defaultInterpolatedStringHandler.AppendFormatted((instance4 != null) ? new bool?(instance4.CanMove) : ((bool?)null)); defaultInterpolatedStringHandler.AppendLiteral(", canLook="); PlayerCamera instance5 = PlayerSingleton<PlayerCamera>.Instance; defaultInterpolatedStringHandler.AppendFormatted((instance5 != null) ? new bool?(instance5.CanLook) : ((bool?)null)); defaultInterpolatedStringHandler.AppendLiteral("."); ModLog.Msg(defaultInterpolatedStringHandler.ToStringAndClear()); phase = instance.CurrentPhase; phaseSince = Time.realtimeSinceStartup; nextWarning = phaseSince + 90f; } if (observedSleep && !instance.IsSleepInProgress) { ClearReady(instance); MonoStateMachine sleepingState2 = instance.SleepingState; if (sleepingState2 != null && ((MonoState)sleepingState2).IsActive) { ((MonoState)instance.SleepingState).RemoveFromDefaultParent(); ModLog.Warning("Sleep guard: removed the still-active sleeping state after the native sleep phase ended."); } } observedSleep = instance.IsSleepInProgress; if (observedSleep && ModLog.Enabled && Time.realtimeSinceStartup >= nextWarning) { nextWarning = Time.realtimeSinceStartup + 60f; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(99, 7); defaultInterpolatedStringHandler.AppendLiteral("Sleep has stayed in "); defaultInterpolatedStringHandler.AppendFormatted<EPhase>(phase); defaultInterpolatedStringHandler.AppendLiteral(" for "); defaultInterpolatedStringHandler.AppendFormatted(Time.realtimeSinceStartup - phaseSince, "F0"); defaultInterpolatedStringHandler.AppendLiteral("s; timeScale="); defaultInterpolatedStringHandler.AppendFormatted(Time.timeScale); defaultInterpolatedStringHandler.AppendLiteral(", hostReady="); defaultInterpolatedStringHandler.AppendFormatted(instance.IsHostReadyToProceed); defaultInterpolatedStringHandler.AppendLiteral(", sleepingState="); MonoStateMachine sleepingState3 = instance.SleepingState; defaultInterpolatedStringHandler.AppendFormatted((sleepingState3 != null) ? new bool?(((MonoState)sleepingState3).IsActive) : ((bool?)null)); defaultInterpolatedStringHandler.AppendLiteral(". Pending events: "); defaultInterpolatedStringHandler.AppendFormatted(Events(instance.sleepEventQueue)); defaultInterpolatedStringHandler.AppendLiteral("; post-sleep: "); defaultInterpolatedStringHandler.AppendFormatted(Events(instance.postSleepEventQueue)); defaultInterpolatedStringHandler.AppendLiteral("."); ModLog.Warning(defaultInterpolatedStringHandler.ToStringAndClear()); } } private static string Events(List<ISleepEvent> events) { if (events == null) { return "none"; } List<string> list = new List<string>(); for (int i = 0; i < events.Count; i++) { ISleepEvent val = events[i]; if (val != null) { list.Add($"{((object)((Il2CppObjectBase)val).TryCast<Object>())?.GetType().FullName}(running={val.IsInProgress})"); } } return string.Join(", ", list.Take(12)); } } [HarmonyPatch(typeof(SleepController), "CheckSleepStart")] internal static class CheckSleepHours { private static bool Prefix(SleepController __instance) { if (InstanceFinder.IsServer && !__instance.IsSleepInProgress) { return SleepGuard.Allowed(); } return true; } } [HarmonyPatch(typeof(SleepController), "RpcLogic___StartSleep_2166136261")] internal static class PreventDuplicateSleep { private static bool Prefix(SleepController __instance) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsSleepInProgress) { return true; } ModLog.Warning($"Suppressed duplicate sleep start while phase={__instance.CurrentPhase}."); return false; } } [HarmonyPatch] internal static class SleepMenuHours { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(SleepMenu), "OpenMenu", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(SleepMenu), "SleepButtonPressed", (Type[])null, (Type[])null); } private static bool Prefix(SleepMenu __instance) { if (SleepGuard.Allowed()) { return true; } DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(55, 1); defaultInterpolatedStringHandler.AppendLiteral("Sleep guard: ignored morning/daytime sleep request at "); TimeManager instance = NetworkSingleton<TimeManager>.Instance; defaultInterpolatedStringHandler.AppendFormatted((instance != null) ? new int?(instance.CurrentTime) : ((int?)null)); defaultInterpolatedStringHandler.AppendLiteral("."); ModLog.Msg(defaultInterpolatedStringHandler.ToStringAndClear()); if (__instance.IsMenuOpen) { MonoState menuState = __instance.MenuState; if (menuState != null) { menuState.RemoveFromDefaultParent(); } if (__instance.IsMenuOpen) { __instance.OnMenuClosed(); } Player local = Player.Local; if (local != null) { local.SetReadyToSleep(false); } } return false; } } [HarmonyPatch] internal static class TraceSleepCoroutines { private static IEnumerable<MethodBase> TargetMethods() { Type[] nestedTypes = typeof(SleepController).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < nestedTypes.Length; i++) { MethodInfo methodInfo = AccessTools.Method(nestedTypes[i], "MoveNext", (Type[])null, (Type[])null); if (methodInfo != null) { yield return methodInfo; } } } private static Exception? Finalizer(Exception? __exception, MethodBase __originalMethod) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (__exception != null) { DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(32, 2); defaultInterpolatedStringHandler.AppendLiteral("Sleep coroutine failed: "); defaultInterpolatedStringHandler.AppendFormatted(__originalMethod.DeclaringType?.Name); defaultInterpolatedStringHandler.AppendLiteral("; phase="); SleepController instance = NetworkSingleton<SleepController>.Instance; defaultInterpolatedStringHandler.AppendFormatted((instance != null) ? new EPhase?(instance.CurrentPhase) : ((EPhase?)null)); ModLog.Error(defaultInterpolatedStringHandler.ToStringAndClear(), __exception); } return __exception; } } }