using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using PurrLobby; using PurrNet; using PurrNet.Modules; using PurrNet.Packing; using PurrNet.Transports; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")] [assembly: AssemblyCompany("BlueSagePatched_PlayerLimitLift")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.1.0")] [assembly: AssemblyInformationalVersion("1.1.1+beb00bc563a9599284cac5ddf8e96c64020715b1")] [assembly: AssemblyProduct("BlueSagePatched_PlayerLimitLift")] [assembly: AssemblyTitle("BlueSagePatched_PlayerLimitLift")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.1.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 BlueSagePatched.PlayerLimitLift { [BepInPlugin("com.bluesage.ontogether.playerlimitlift", "BlueSagePatched PlayerLimitLift", "1.1.1")] [BepInProcess("OnTogether.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.bluesage.ontogether.playerlimitlift"; public const string PluginName = "BlueSagePatched PlayerLimitLift"; public const string PluginVersion = "1.1.1"; private readonly Harmony _harmony = new Harmony("com.bluesage.ontogether.playerlimitlift"); internal static ManualLogSource Log { get; private set; } internal static ConfigEntry DefaultLobbySize { get; private set; } internal static ConfigEntry MaxLobbySize { get; private set; } internal static ConfigEntry ShiftSkipRate { get; private set; } internal static ConfigEntry EnableChatRelayPatch { get; private set; } internal static int EffectiveMaxLobbySize => Clamp(MaxLobbySize.Value, 16, 128); internal static int EffectiveDefaultLobbySize => Clamp(DefaultLobbySize.Value, 1, EffectiveMaxLobbySize); internal static int EffectiveShiftSkipRate => Clamp(ShiftSkipRate.Value, 1, EffectiveMaxLobbySize); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; BindConfig(); _harmony.PatchAll(); Log.LogInfo((object)string.Format("{0} {1} loaded. Lobby cap={2}, default={3}.", "BlueSagePatched PlayerLimitLift", "1.1.1", EffectiveMaxLobbySize, EffectiveDefaultLobbySize)); LogStartupDiagnostics(); } private void OnDestroy() { _harmony.UnpatchSelf(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"BlueSagePatched PlayerLimitLift unloaded and Harmony patches removed."); } } private void BindConfig() { DefaultLobbySize = ((BaseUnityPlugin)this).Config.Bind("General", "DefaultLobbySize", 16, "Lobby size shown on launch. Clamped between 1 and MaxLobbySize. Takes effect after restart."); MaxLobbySize = ((BaseUnityPlugin)this).Config.Bind("General", "MaxLobbySize", 128, "Maximum lobby size exposed by this replacement mod. On-Together currently works best at 128 or lower."); ShiftSkipRate = ((BaseUnityPlugin)this).Config.Bind("General", "ShiftSkipRate", 16, "Amount that shift+click changes lobby size by. Clamped between 1 and MaxLobbySize."); EnableChatRelayPatch = ((BaseUnityPlugin)this).Config.Bind("General", "EnableChatRelayPatch", true, "Keeps chat functional when sender indexes exceed the vanilla 16-player color/name assumptions."); } private static void LogStartupDiagnostics() { WarnIfClamped("DefaultLobbySize", DefaultLobbySize.Value, EffectiveDefaultLobbySize); WarnIfClamped("MaxLobbySize", MaxLobbySize.Value, EffectiveMaxLobbySize); WarnIfClamped("ShiftSkipRate", ShiftSkipRate.Value, EffectiveShiftSkipRate); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if (((value != null) ? value.Metadata : null) != null && !string.Equals(value.Metadata.GUID, "com.bluesage.ontogether.playerlimitlift", StringComparison.OrdinalIgnoreCase)) { string text = (value.Metadata.GUID + " " + value.Metadata.Name).Trim(); string text2 = text.ToLowerInvariant(); bool flag = text2.Contains("playerlimit") || text2.Contains("player limit"); bool flag2 = text2.Contains("officerballs") || text2.Contains("officer_balls"); if (flag && flag2) { Log.LogWarning((object)("Old officerballs PlayerLimitLift appears loaded (" + text + "). Remove or disable it so BlueSage PlayerLimitLift is the only PlayerLimitLift mod on the host; old client copies are the common NickName1/chat-glitch cause.")); } else if (flag) { Log.LogWarning((object)("Another PlayerLimitLift-like plugin appears loaded (" + text + "). Hosts should run only one lobby-size mod at a time.")); } } } } private static void WarnIfClamped(string name, int configured, int effective) { if (configured != effective) { Log.LogWarning((object)string.Format("{0}={1} was clamped to {2}. Check BepInEx/config/{3}.cfg if this was not intentional.", name, configured, effective, "com.bluesage.ontogether.playerlimitlift")); } } internal static int WrapLobbySize(int current, int delta) { int effectiveMaxLobbySize = EffectiveMaxLobbySize; return Mod(current - 1 + delta, effectiveMaxLobbySize) + 1; } internal static int WrapLobbyListFilter(int current, int delta) { int modulus = EffectiveMaxLobbySize + 1; return Mod(current + delta, modulus); } internal static int ClampSenderIndexForColor(int senderIndex) { return Clamp(senderIndex, 0, 15); } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } private static int Mod(int value, int modulus) { int num = value % modulus; if (num >= 0) { return num; } return num + modulus; } } } namespace BlueSagePatched.PlayerLimitLift.Patches { [HarmonyPatch(typeof(PlayerPanelController))] internal static class PanelPatch { [HarmonyPatch("UpdateServerPanel")] [HarmonyPostfix] private static void SizeMarker() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) LobbyManager val = MonoSingleton.I?._lobbyManager; if (val == null) { return; } _ = val.CurrentLobby; if (0 == 0 && !((Object)(object)NetworkSingleton.I == (Object)null)) { TextMeshProUGUI val2 = AccessTools.FieldRefAccess("_lobbyNameText").Invoke(NetworkSingleton.I); if (!((Object)(object)val2 == (Object)null)) { int num = val.CurrentLobby.MaxPlayers - 1; int count = val.CurrentLobby.Members.Count; ((TMP_Text)val2).text = $"{MonoSingleton.I.LobbyName} ({count}/{num})"; } } } } [HarmonyPatch(typeof(MainMenuUIController))] internal static class PlayerLimitPatch { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void AwakePatch(ref TextMeshProUGUI ____filterPlayerText) { MonoSingleton.I.FilterPlayerValue = Plugin.EffectiveDefaultLobbySize; if ((Object)(object)____filterPlayerText != (Object)null) { ((TMP_Text)____filterPlayerText).text = Plugin.EffectiveDefaultLobbySize.ToString(); } } [HarmonyPatch("ButtonChangeFilterPlayer")] [HarmonyPrefix] private static bool ButtonChangeFilterPlayerPatch(int value, ref TextMeshProUGUI ____filterPlayerText) { int num = ((!IsShiftHeld()) ? 1 : Plugin.EffectiveShiftSkipRate); int delta = ((value == 1) ? num : (-num)); int filterPlayerValue = Plugin.WrapLobbySize(MonoSingleton.I.FilterPlayerValue, delta); MonoSingleton.I.FilterPlayerValue = filterPlayerValue; if ((Object)(object)____filterPlayerText != (Object)null) { ((TMP_Text)____filterPlayerText).text = filterPlayerValue.ToString(); } return false; } [HarmonyPatch("ButtonChangeFilterListPlayer")] [HarmonyPrefix] private static bool ButtonChangeFilterListPlayerPatch(int value, ref TextMeshProUGUI ____filterListPlayerText) { int num = ((!IsShiftHeld()) ? 1 : Plugin.EffectiveShiftSkipRate); int delta = ((value == 1) ? num : (-num)); int num2 = Plugin.WrapLobbyListFilter(MonoSingleton.I.FilterListPlayerValue, delta); MonoSingleton.I.FilterListPlayerValue = num2; if ((Object)(object)____filterListPlayerText != (Object)null) { ((TMP_Text)____filterListPlayerText).text = ((num2 == 0) ? MonoSingleton.I.AllString.String : num2.ToString()); } return false; } private static bool IsShiftHeld() { if (!Input.GetKey((KeyCode)304)) { return Input.GetKey((KeyCode)303); } return true; } } [HarmonyPatch(typeof(MultiplayerManager))] internal static class ServerTagPatch { [HarmonyPatch("CreateLobby")] [HarmonyPrefix] private static void MarkCreatedLobbyAsModded(ref List ___FilterSocialTags) { if (___FilterSocialTags != null) { while (___FilterSocialTags.Count <= 4) { ___FilterSocialTags.Add(item: false); } ___FilterSocialTags[4] = true; } } } [HarmonyPatch(typeof(TextChannelManager))] internal static class TextChannelManagerPatch { private static readonly MethodInfo OnChannelMessageReceivedMethod = AccessTools.Method(typeof(TextChannelManager), "OnChannelMessageReceived", (Type[])null, (Type[])null); private static readonly MethodInfo AddMessageUiMethod = AccessTools.Method(typeof(TextChannelManager), "AddMessageUI", (Type[])null, (Type[])null); [HarmonyPatch(typeof(TextChannelManager), "SendMessageAsync_Original_0")] [HarmonyPrefix] private static bool SendMessagePatch(TextChannelManager __instance, ref byte[] textBytes, ref byte[] userName, ref bool isLocal, ref Vector3 pos, ref string playerID, ref RPCInfo info, ref string ____playerId) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.EnableChatRelayPatch.Value) { return true; } string message = Encoding.Unicode.GetString(textBytes); string senderName = Encoding.Unicode.GetString(userName); PlayerPanelController i = NetworkSingleton.I; int colorIndex = Plugin.ClampSenderIndexForColor(FindSenderIndex(i, info.sender, playerID)); if (!IsFromLocalPlayer(__instance, info.sender)) { InvokeOnChannelMessageReceived(__instance, senderName, message, pos, isLocal, colorIndex, playerID); return false; } if (playerID != ____playerId) { InvokeAddMessageUi(__instance, senderName, message, isLocal, colorIndex); ShowRemoteTextBubble(i, textBytes, isLocal, pos, playerID); return false; } InvokeAddMessageUi(__instance, senderName, message, isLocal, colorIndex); TextBubbleController mainTextBubble = __instance.MainTextBubble; if (mainTextBubble != null) { mainTextBubble.ShowTextBubble(textBytes, isLocal, pos, playerID); } return false; } [HarmonyPatch(typeof(TextChannelManager), "OnReceivedRpc")] [HarmonyPrefix] private static bool RPCInterceptPatcher(ref int id, ref RPCInfo info, ref RPCPacket packet, ref bool asServer, TextChannelManager __instance) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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) if (!Plugin.EnableChatRelayPatch.Value) { return true; } if (id != 0) { return true; } if (asServer && FindSenderIndex(NetworkSingleton.I, info.sender, null) > 15) { if (TryReadSendMessagePacket(packet, info, out var textBytes, out var userName, out var isLocal, out var pos, out var playerID)) { __instance.SendMessageAsync(textBytes, userName, isLocal, pos, playerID, default(RPCInfo)); } return false; } __instance.HandleRPCGenerated_0(packet, info, asServer); return false; } private unsafe static bool TryReadSendMessagePacket(RPCPacket packet, RPCInfo info, out byte[] textBytes, out byte[] userName, out bool isLocal, out Vector3 pos, out string playerID) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_0042: 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_0051: 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) textBytes = null; userName = null; isLocal = false; pos = Vector3.zero; playerID = null; try { info.compileTimeSignature = RPCSignature.Make((RPCType)1, (Channel)2, false, false, false, true, false, "SendMessageAsync", false, 5f, (CompressionLevel)0, false, false); BitData data = packet.data; RPCModule.PostProcessRpc(info, ref data); BitDataScope val = ((BitData)(ref data)).AutoScope(); try { BitPacker packer = data.packer; Packer.Read(packer, ref textBytes); Packer.Read(packer, ref userName); Packer.Read(packer, ref isLocal); Packer.Read(packer, ref pos); Packer.Read(packer, ref playerID); } finally { ((IDisposable)(*(BitDataScope*)(&val))/*cast due to .constrained prefix*/).Dispose(); } return true; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Failed to decode oversized-lobby chat RPC; dropping packet to avoid corrupt chat state. " + ex.GetType().Name + ": " + ex.Message)); } return false; } } private static int FindSenderIndex(PlayerPanelController panel, PlayerID sender, string playerID) { //IL_0016: 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) if ((Object)(object)panel == (Object)null) { return 0; } List playerIDs = panel.PlayerIDs; for (int i = 0; i < playerIDs.Count; i++) { if (sender == playerIDs[i]) { return i; } } if (!string.IsNullOrEmpty(playerID)) { int num = panel.PlayerSteamIDs.IndexOf(playerID); if (num >= 0) { return num; } } return 0; } private static bool IsFromLocalPlayer(TextChannelManager instance, PlayerID sender) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) PlayerID? localPlayer = ((NetworkIdentity)instance).localPlayer; if (localPlayer.HasValue) { return sender == localPlayer.GetValueOrDefault(); } return false; } private static void InvokeOnChannelMessageReceived(TextChannelManager instance, string senderName, string message, Vector3 pos, bool isLocal, int colorIndex, string playerID) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) OnChannelMessageReceivedMethod.Invoke(instance, new object[6] { senderName, message, pos, isLocal, colorIndex, playerID }); } private static void InvokeAddMessageUi(TextChannelManager instance, string senderName, string message, bool isLocal, int colorIndex) { AddMessageUiMethod.Invoke(instance, new object[4] { senderName, message, isLocal, colorIndex }); } private static void ShowRemoteTextBubble(PlayerPanelController panel, byte[] textBytes, bool isLocal, Vector3 pos, string playerID) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)panel == (Object)null) { return; } int num = panel.PlayerSteamIDs.IndexOf(playerID); if (num < 0 || num >= panel.PlayerTransforms.Count) { return; } NetworkTransform val = panel.PlayerTransforms[num]; if (!((Object)(object)val == (Object)null)) { TextBubbleController componentInChildren = ((Component)val).GetComponentInChildren(); if (componentInChildren != null) { componentInChildren.ShowTextBubble(textBytes, isLocal, pos, playerID); } } } } }