using System; using System.Collections; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using MorePlayersMod; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "MorePlayers", "0.3.0", "leesky", null)] [assembly: MelonGame("Keepsake Games", "Jump Space")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("MorePlayersMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("MorePlayersMod")] [assembly: AssemblyTitle("MorePlayersMod")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 MorePlayersMod { public class Core : MelonMod { public static MelonPreferences_Entry MaxPlayers; public static MelonPreferences_Entry PatchSetters; private static bool _patchesApplied; public override void OnInitializeMelon() { MelonPreferences_Category obj = MelonPreferences.CreateCategory("MorePlayers", "More Players"); MaxPlayers = obj.CreateEntry("MaxPlayers", 6, "Max Players", "Maximum players allowed in a lobby.", false, false, (ValueValidator)null, (string)null); PatchSetters = obj.CreateEntry("PatchSetters", true, "Patch Setters", "Patch lobby max-player setters that are capped at 4.", false, false, (ValueValidator)null, (string)null); MelonLogger.Msg($"MorePlayers v0.3.0 loaded. Target max players: {MaxPlayers.Value}"); MelonLogger.Warning("Experimental mod. Everyone in the lobby must install the same version."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (!_patchesApplied) { MelonLogger.Msg($"Scene loaded: {sceneName} (index {buildIndex}). Applying deferred patches..."); MelonCoroutines.Start(ApplyPatchesAfterScene()); } } private static IEnumerator ApplyPatchesAfterScene() { for (int i = 0; i < 120; i++) { yield return null; } if (!_patchesApplied) { HarmonyPatcher.Apply(); _patchesApplied = true; } } } internal static class HarmonyPatcher { private static readonly Harmony HarmonyInstance = new Harmony("com.cursoragent.jumpspace.moreplayers"); private static bool _applied; internal static void Apply() { if (!_applied) { _applied = true; int num = 0; num += TryPatchExactSetter("Il2CppEpic.OnlineServices.Lobby.CreateLobbyOptions", "set_MaxLobbyMembers", typeof(uint)); num += TryPatchKeepsakeMaxMemberCount(); MelonLogger.Msg($"MorePlayers applied {num} Harmony patches (deferred)."); } } private static int TryPatchExactSetter(string typeName, string methodName, Type parameterType) { Type type = AccessTools.TypeByName(typeName); if (type == null) { MelonLogger.Warning("Type not found: " + typeName); return 0; } MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { MelonLogger.Warning("Method not found: " + typeName + "." + methodName); return 0; } if (methodInfo.GetParameters()[0].ParameterType != parameterType) { MelonLogger.Warning("Unexpected parameter type on " + typeName + "." + methodName); return 0; } return TryPatchSetter(methodInfo) ? 1 : 0; } private static int TryPatchKeepsakeMaxMemberCount() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly.GetName().Name != "Il2CppKeepsake.Online.Epic") { continue; } Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).Cast().ToArray(); } catch { continue; } Type[] array2 = array; foreach (Type type in array2) { MethodInfo methodInfo = AccessTools.Method(type, "set_MaxMemberCount", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(type, "get_CurrentMemberCount", (Type[])null, (Type[])null); if (!(methodInfo == null) && !(methodInfo2 == null) && !(methodInfo.GetParameters()[0].ParameterType != typeof(int)) && TryPatchSetter(methodInfo)) { MelonLogger.Msg("Patched Keepsake setter: " + type.FullName + ".set_MaxMemberCount"); return 1; } } } MelonLogger.Warning("Keepsake set_MaxMemberCount target not found."); return 0; } private static bool TryPatchSetter(MethodInfo method) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown Type parameterType = method.GetParameters()[0].ParameterType; HarmonyMethod val = null; if (parameterType == typeof(int)) { val = new HarmonyMethod(typeof(HarmonyPatcher), "MaxPlayerSetterPrefixInt", (Type[])null); } else { if (!(parameterType == typeof(uint))) { return false; } val = new HarmonyMethod(typeof(HarmonyPatcher), "MaxPlayerSetterPrefixUInt", (Type[])null); } try { HarmonyInstance.Patch((MethodBase)method, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MelonLogger.Msg($"Patched setter: {method.DeclaringType?.FullName}.{method.Name} ({parameterType.Name})"); return true; } catch (Exception ex) { MelonLogger.Warning($"Failed to patch setter {method.DeclaringType?.FullName}.{method.Name}: {ex.Message}"); return false; } } public static void MaxPlayerSetterPrefixInt(ref int __0) { if (Core.PatchSetters.Value) { int num = __0; if (num > 0 && num <= 4) { __0 = Core.MaxPlayers.Value; } } } public static void MaxPlayerSetterPrefixUInt(ref uint __0) { if (Core.PatchSetters.Value) { uint num = __0; if (num != 0 && num <= 4) { __0 = (uint)Core.MaxPlayers.Value; } } } } }