using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using HarmonyLib; using Il2Cpp; using Il2CppFusion; using Il2CppFusion.Photon.Realtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppSteamworks; using Il2CppSystem.Collections; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using ShiftLobbyPlus; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Mod), "ShiftLobbyPlus", "1.0.0", "fzc", null)] [assembly: MelonGame("Kwalee", "ShiftAtMidnight")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ShiftLobbyPlus")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ShiftLobbyPlus")] [assembly: AssemblyTitle("ShiftLobbyPlus")] [assembly: AssemblyVersion("1.0.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 ShiftLobbyPlus { public class Mod : MelonMod { private static MelonPreferences_Category _cat; private static MelonPreferences_Entry _maxPlayers; public static Mod Instance; public static int SelectedMax; private static int _nextStatusTick; public static int Target => Math.Clamp(_maxPlayers?.Value ?? 8, 1, 243); public static void Log(string msg) { Mod instance = Instance; if (instance != null) { ((MelonBase)instance).LoggerInstance.Msg(msg); } } public static void RaiseFusionPlayerSlots(string reason) { try { NetworkProjectConfig global = NetworkProjectConfig.Global; SimulationConfig val = ((global != null) ? global.Simulation : null); if (val != null && val.PlayerCount < Target) { Log($"[{reason}] Fusion Simulation.PlayerCount {val.PlayerCount} -> {Target}"); val.PlayerCount = Target; } } catch (Exception ex) { Log("RaiseFusionPlayerSlots(" + reason + "): " + ex.Message); } } public override void OnInitializeMelon() { Instance = this; _cat = MelonPreferences.CreateCategory("ShiftLobbyPlus"); _maxPlayers = _cat.CreateEntry("MaxPlayers", 8, (string)null, "Lobby max-players cap offered in the dropdown, 1-243 (game default: 3)", false, false, (ValueValidator)null, (string)null); ((MelonBase)this).LoggerInstance.Msg($"ShiftLobbyPlus init. MaxPlayers={Target}"); } public override void OnUpdate() { //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) if (Environment.TickCount < _nextStatusTick) { return; } _nextStatusTick = Environment.TickCount + 15000; try { FusionNetworkManager instance = FusionNetworkManager.Instance; NetworkRunner val = ((instance != null) ? instance.GetRunner() : null); if ((Object)(object)val == (Object)null || !val.IsRunning) { return; } SessionInfo sessionInfo = val.SessionInfo; string text = ((sessionInfo != null) ? $"Session '{sessionInfo.Name}' region={sessionInfo.Region} players={sessionInfo.PlayerCount}/{sessionInfo.MaxPlayers} open={sessionInfo.IsOpen}" : "Session info unavailable"); List list = new List(); IEnumerator enumerator = val.ActivePlayers.GetEnumerator(); IEnumerator val2 = ((Il2CppObjectBase)enumerator).Cast(); while (val2.MoveNext()) { PlayerRef current = enumerator.Current; double value = val.GetPlayerRtt(current) * 1000.0; list.Add($"P{((PlayerRef)(ref current)).PlayerId}:{value:F0}ms"); } if (list.Count > 0) { text = text + " | RTT " + string.Join(" ", list); } try { string text2 = text; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(12, 1); defaultInterpolatedStringHandler.AppendLiteral(" | simSlots="); NetworkProjectConfig global = NetworkProjectConfig.Global; int? value2; if (global == null) { value2 = null; } else { SimulationConfig simulation = global.Simulation; value2 = ((simulation != null) ? new int?(simulation.PlayerCount) : ((int?)null)); } defaultInterpolatedStringHandler.AppendFormatted(value2); text = text2 + defaultInterpolatedStringHandler.ToStringAndClear(); } catch { } ((MelonBase)this).LoggerInstance.Msg(text); } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Warning("status probe: " + ex.Message); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { try { int s_MAXPLAYERS = PlatformManager.s_MAXPLAYERS; if (s_MAXPLAYERS < Target) { PlatformManager.s_MAXPLAYERS = Target; ((MelonBase)this).LoggerInstance.Msg($"[{sceneName}] s_MAXPLAYERS {s_MAXPLAYERS} -> {Target}"); } RaiseFusionPlayerSlots(sceneName); } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Warning("s_MAXPLAYERS patch failed: " + ex.Message); } } } [HarmonyPatch(typeof(CreateLobbySettings), "OnEnable")] internal static class Patch_ExpandDropdown { private static void Postfix(CreateLobbySettings __instance) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown try { TMP_Dropdown maxPlayersDropdown = __instance.maxPlayersDropdown; if (!((Object)(object)maxPlayersDropdown == (Object)null)) { int count = maxPlayersDropdown.options.Count; while (maxPlayersDropdown.options.Count < Mod.Target) { maxPlayersDropdown.options.Add(new OptionData((maxPlayersDropdown.options.Count + 1).ToString())); } if (maxPlayersDropdown.options.Count != count) { maxPlayersDropdown.RefreshShownValue(); Mod.Log($"maxPlayersDropdown expanded {count} -> {maxPlayersDropdown.options.Count}"); } } } catch (Exception value) { Mod.Log($"ExpandDropdown error: {value}"); } } } [HarmonyPatch(typeof(CreateLobbySettings), "ChangeMaxPlayers")] internal static class Patch_ObserveChange { private static void Postfix(CreateLobbySettings __instance, int players) { Mod.Log($"ChangeMaxPlayers(arg={players}) -> field maxPlayers={__instance.maxPlayers}"); } } [HarmonyPatch(typeof(CreateLobbySettings), "StartLobby")] internal static class Patch_StartLobby { private static void Prefix(CreateLobbySettings __instance) { Mod.SelectedMax = Math.Max(1, __instance.maxPlayers); Mod.Log($"StartLobby: ui maxPlayers={__instance.maxPlayers} => SelectedMax={Mod.SelectedMax}"); Mod.RaiseFusionPlayerSlots("StartLobby"); } private static void Postfix() { Mod.Log($"StartLobby done: PendingLobbySettings.maxPlayers={PendingLobbySettings.maxPlayers}"); if (Mod.SelectedMax > PendingLobbySettings.maxPlayers) { Mod.Log($"Pending maxPlayers clamped? {PendingLobbySettings.maxPlayers} -> {Mod.SelectedMax}"); PendingLobbySettings.maxPlayers = Mod.SelectedMax; } } } [HarmonyPatch(typeof(SteamMatchmaking), "CreateLobby")] internal static class Patch_SteamCreateLobby { private static void Prefix(ELobbyType eLobbyType, ref int cMaxMembers) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (Mod.SelectedMax > cMaxMembers) { Mod.Log($"Steam CreateLobby({eLobbyType}): cMaxMembers {cMaxMembers} -> {Mod.SelectedMax}"); cMaxMembers = Mod.SelectedMax; } else { Mod.Log($"Steam CreateLobby({eLobbyType}): cMaxMembers={cMaxMembers} (no change)"); } } } [HarmonyPatch(typeof(LoadBalancingClient), "OpCreateRoom")] internal static class Patch_OpCreateRoom { private static void Prefix(EnterRoomParams enterRoomParams) { try { RoomOptions val = ((enterRoomParams != null) ? enterRoomParams.RoomOptions : null); if (val != null) { Mod.Log($"OpCreateRoom: RoomOptions.MaxPlayers={val.MaxPlayers}, SelectedMax={Mod.SelectedMax}"); if (Mod.SelectedMax > 0 && val.MaxPlayers > 0 && val.MaxPlayers < Mod.SelectedMax) { val.MaxPlayers = Mod.SelectedMax; Mod.Log($"OpCreateRoom: MaxPlayers -> {val.MaxPlayers}"); } } } catch (Exception value) { Mod.Log($"OpCreateRoom error: {value}"); } } } [HarmonyPatch(typeof(LoadBalancingClient), "OpJoinOrCreateRoom")] internal static class Patch_OpJoinOrCreateRoom { private static void Prefix(EnterRoomParams enterRoomParams) { try { RoomOptions val = ((enterRoomParams != null) ? enterRoomParams.RoomOptions : null); if (val != null) { Mod.Log($"OpJoinOrCreateRoom: RoomOptions.MaxPlayers={val.MaxPlayers}, SelectedMax={Mod.SelectedMax}"); if (Mod.SelectedMax > 0 && val.MaxPlayers > 0 && val.MaxPlayers < Mod.SelectedMax) { val.MaxPlayers = Mod.SelectedMax; Mod.Log($"OpJoinOrCreateRoom: MaxPlayers -> {val.MaxPlayers}"); } } } catch (Exception value) { Mod.Log($"OpJoinOrCreateRoom error: {value}"); } } } [HarmonyPatch(typeof(LoadBalancingClient), "OpJoinRandomOrCreateRoom")] internal static class Patch_OpJoinRandomOrCreateRoom { private static void Prefix(EnterRoomParams createRoomParams) { try { RoomOptions val = ((createRoomParams != null) ? createRoomParams.RoomOptions : null); if (val != null) { Mod.Log($"OpJoinRandomOrCreateRoom: RoomOptions.MaxPlayers={val.MaxPlayers}, SelectedMax={Mod.SelectedMax}"); if (Mod.SelectedMax > 0 && val.MaxPlayers > 0 && val.MaxPlayers < Mod.SelectedMax) { val.MaxPlayers = Mod.SelectedMax; Mod.Log($"OpJoinRandomOrCreateRoom: MaxPlayers -> {val.MaxPlayers}"); } } } catch (Exception value) { Mod.Log($"OpJoinRandomOrCreateRoom error: {value}"); } } } }