Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LobbyGuard v0.2.4
plugins/com.github.lethalcompanymodding.lobbyguard.dll
Decompiled 10 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Logging; using Dissonance; using Dissonance.Integrations.Unity_NFGO; using Dissonance.Networking; using Dissonance.Networking.Server; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using MonoMod.Cil; using MonoMod.RuntimeDetour; using MonoMod.Utils; using Netcode.Transports.Facepunch; using Steamworks; using Steamworks.Data; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LethalCompanyModding")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 LethalCompanyModding Contributors")] [assembly: AssemblyFileVersion("0.2.4.0")] [assembly: AssemblyInformationalVersion("0.2.4+56a63b59875acf955589965b66c637d6fc800825")] [assembly: AssemblyProduct("com.github.lethalcompanymodding.lobbyguard")] [assembly: AssemblyTitle("LobbyGuard")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/LethalCompanyModding/LobbyGuard")] [assembly: InternalsVisibleTo("LobbyGuard.Tests")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.4.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsUnmanagedAttribute : Attribute { } [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; } } [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 LobbyGuard { internal static class Approval { private delegate void Orig(GameNetworkManager self, ConnectionApprovalRequest request, ConnectionApprovalResponse response); private static Hook? hook; public static void Hook() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown hook = new Hook((MethodBase)typeof(GameNetworkManager).GetMethod("ConnectionApproval", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), typeof(Approval).GetMethod("Approve", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); } private static void Approve(Orig orig, GameNetworkManager self, ConnectionApprovalRequest request, ConnectionApprovalResponse response) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; ulong num = ((request.ClientNetworkId == singleton.LocalClientId) ? 0 : Plugin.SteamIdOf(singleton, request.ClientNetworkId)); if (num == 0L || (Object)(object)StartOfRound.Instance == (Object)null || !StartOfRound.Instance.KickedClientIds.Contains(num)) { orig(self, request, response); return; } Plugin.Log.LogWarning((object)$"Turned away Steam user {num}: kicked earlier"); response.Reason = "You cannot rejoin after being kicked."; response.CreatePlayerObject = false; response.Approved = false; response.Pending = false; } } internal static class Batch { private const ushort Magic = 4448; private const int HeaderSize = 16; public unsafe static string? Check(ArraySegment<byte> data, Func<ArraySegment<byte>, ulong> hash, uint? connectRequest = null) { if (data.Array == null || data.Count < 16) { return "packet too short"; } fixed (byte* array = data.Array) { byte* ptr = array + data.Offset; if (*(ushort*)ptr != 4448 || ((int*)ptr)[1] != data.Count) { return "not a Netcode packet"; } if (hash(new ArraySegment<byte>(data.Array, data.Offset + 16, data.Count - 16)) != (ulong)((long*)ptr)[1]) { return "packet hash doesn't match"; } int pos = 16; for (int i = 0; i < ((ushort*)ptr)[1]; i++) { if (!Packed(ptr, data.Count, ref pos, out var value) || !Packed(ptr, data.Count, ref pos, out var value2)) { return "bad message header"; } if (value2 > (uint)(data.Count - pos)) { return "message bigger than its packet"; } if (value == connectRequest && !ConnectionData(ptr, pos, pos + (int)value2)) { return "bad connection request"; } pos += (int)value2; } } return null; } private unsafe static bool ConnectionData(byte* p, int pos, int end) { if (!Packed(p, end, ref pos, out var value)) { return false; } int num = (int)((value >> 1) ^ (0 - (value & 1))); for (int i = 0; i < num; i++) { pos += 4; if (pos > end || !Packed(p, end, ref pos, out var _)) { return false; } } if (end - pos < 12) { return false; } int num2 = ((int*)(p + pos))[2]; if (num2 >= 0) { return num2 <= end - pos - 12; } return false; } private unsafe static bool Packed(byte* p, int length, ref int pos, out uint value) { value = 0u; if (pos >= length) { return false; } int num = p[pos] & 7; if (num == 0 || num > 5 || num > length - pos) { return false; } if (num == 5) { value = (uint)(p[pos + 1] | (p[pos + 2] << 8) | (p[pos + 3] << 16) | (p[pos + 4] << 24)); } else { for (int i = 0; i < num; i++) { value |= (uint)(p[pos + i] << 8 * i); } value >>= 3; } pos += num; return true; } } internal static class Lengths { [CompilerGenerated] private static class <>O { public static Manipulator <0>__Guard; } private const BindingFlags Statics = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly List<ILHook> hooks = new List<ILHook>(); private static readonly MethodInfo check = typeof(Lengths).GetMethod("Check", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); public static void HookAll() { AppDomain.CurrentDomain.AssemblyLoad += delegate(object _, AssemblyLoadEventArgs e) { Hook(e.LoadedAssembly); }; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Hook(assembly); } } public static bool Fits(int count, int size, int remaining) { if (count >= 0) { return (long)count * (long)size <= remaining; } return false; } private unsafe static void Check(ref FastBufferReader reader, int size, __RpcParams rpc) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) int num = ((FastBufferReader)(ref reader)).Length - ((FastBufferReader)(ref reader)).Position - 4; if (num < 0 || Fits(*(int*)((FastBufferReader)(ref reader)).GetUnsafePtrAtCurrentPosition(), size, num)) { return; } ulong senderClientId = rpc.Server.Receive.SenderClientId; NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && singleton.IsServer && senderClientId != 0L) { Plugin.Kick(singleton, senderClientId, "RPC array longer than its message"); } throw new OverflowException("RPC array longer than its message"); } private static void Hook(Assembly assembly) { try { HookTypes(assembly); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Skipped RPC length checks in " + assembly.GetName().Name + ": " + ex.Message)); } } private static void HookTypes(Assembly assembly) { if (assembly.IsDynamic || assembly == typeof(NetworkBehaviour).Assembly || !References(assembly)) { return; } Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } Type[] array = types; foreach (Type type in array) { if (type == null || !Derives(type, typeof(NetworkBehaviour))) { continue; } try { hooks.AddRange((from handler in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where handler.Name.StartsWith("__rpc_handler_", StringComparison.Ordinal) && ReadsLength(handler) select handler).Select((Func<MethodInfo, ILHook>)delegate(MethodInfo handler) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown object obj = <>O.<0>__Guard; if (obj == null) { Manipulator val = Guard; <>O.<0>__Guard = val; obj = (object)val; } return new ILHook((MethodBase)handler, (Manipulator)obj); })); } catch (Exception e) when (Missing(e)) { } } } internal static bool Derives(Type type, Type from) { try { return from.IsAssignableFrom(type); } catch (Exception e) when (Missing(e)) { return false; } } private static bool Missing(Exception e) { if (e is TypeLoadException || e is FileNotFoundException || e is FileLoadException) { return true; } return false; } private static bool References(Assembly assembly) { return assembly.GetReferencedAssemblies().Any((AssemblyName a) => a.Name == typeof(NetworkBehaviour).Assembly.GetName().Name); } private static bool ReadsLength(MethodInfo handler) { byte[] array = handler.GetMethodBody()?.GetILAsByteArray(); if (array == null) { return false; } for (int i = 0; i + 4 < array.Length; i++) { if (array[i] != 40) { continue; } try { if (ElementSize(handler.Module.ResolveMethod(BitConverter.ToInt32(array, i + 1))).HasValue) { return true; } } catch (ArgumentException) { } } return false; } private static void Guard(ILContext il) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown ParameterDefinition val = ((MethodReference)il.Method).Parameters[1]; ParameterDefinition val2 = ((MethodReference)il.Method).Parameters[2]; ILCursor val3 = new ILCursor(il); while (val3.TryGotoNext((MoveType)0, new Func<Instruction, bool>[1] { (Instruction i) => i.OpCode == OpCodes.Call && i.Operand is MethodReference })) { MethodBase read = null; try { read = ReflectionHelper.ResolveReflection((MethodReference)val3.Next.Operand); } catch (Exception) { } int? num = ElementSize(read); if (num.HasValue) { int valueOrDefault = num.GetValueOrDefault(); val3.Emit(OpCodes.Ldarga, val); val3.Emit(OpCodes.Ldc_I4, valueOrDefault); val3.Emit(OpCodes.Ldarg, val2); val3.Emit(OpCodes.Call, (MethodBase)check); } int index = val3.Index; val3.Index = index + 1; } } private static int? ElementSize(MethodBase? read) { if (read?.DeclaringType != typeof(FastBufferReader) || !read.Name.StartsWith("Read", StringComparison.Ordinal)) { return null; } ParameterInfo[] parameters = read.GetParameters(); if (parameters.Length == 0 || !parameters[0].ParameterType.IsByRef) { return null; } Type elementType = parameters[0].ParameterType.GetElementType(); if (elementType == typeof(string)) { return 1; } Type type = (elementType.IsArray ? elementType.GetElementType() : ((elementType.IsGenericType && elementType.GetGenericTypeDefinition().FullName == "Unity.Collections.NativeArray`1") ? elementType.GetGenericArguments()[0] : null)); if (type == null) { return null; } if (!type.IsValueType || typeof(INetworkSerializable).IsAssignableFrom(type)) { return 1; } return (int)typeof(Lengths).GetMethod("SizeOf", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).MakeGenericMethod(type).Invoke(null, null); } private unsafe static int SizeOf<T>() where T : unmanaged { return sizeof(T); } } [BepInPlugin("com.github.lethalcompanymodding.lobbyguard", "LobbyGuard", "0.2.4")] [BepInProcess("Lethal Company.exe")] public class Plugin : BaseUnityPlugin { private delegate void DataEvent(NetworkConnectionManager self, ulong transportId, ref ArraySegment<byte> payload, float receiveTime); private const int MaxPacketsPerSecond = 2000; private const int MaxMessagesPerSecond = 20000; private static readonly Dictionary<ulong, (float Start, int Packets, int Messages)> rates = new Dictionary<ulong, (float, int, int)>(); private Hook? hook; internal static ManualLogSource Log { get; private set; } = null; private void Awake() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; hook = new Hook((MethodBase)typeof(NetworkConnectionManager).GetMethod("DataEventHandler", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), typeof(Plugin).GetMethod("OnData", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); Lengths.HookAll(); Approval.Hook(); Voice.Hook(); Vanilla.Hook(); Log.LogInfo((object)"LobbyGuard 0.2.4 loaded"); } private static void OnData(DataEvent orig, NetworkConnectionManager self, ulong transportId, ref ArraySegment<byte> payload, float receiveTime) { ulong value; if (!self.LocalClient.IsServer) { orig(self, transportId, ref payload, receiveTime); } else if (self.TransportIdToClientIdMap.TryGetValue(transportId, out value)) { uint? connectRequest = (self.NetworkManager.NetworkConfig.ConnectionApproval ? new uint?(self.MessageManager.GetMessageType(typeof(ConnectionRequestMessage))) : ((uint?)null)); string text = Batch.Check(payload, Hash, connectRequest) ?? (Flooding(transportId, BitConverter.ToUInt16(payload.Array, payload.Offset + 2)) ? "too many packets or messages" : null); if (text == null) { orig(self, transportId, ref payload, receiveTime); return; } rates.Remove(transportId); Kick(self.NetworkManager, value, text); } } internal static void Kick(NetworkManager manager, ulong clientId, string why) { ulong num = SteamIdOf(manager, clientId); Log.LogWarning((object)("Disconnected " + ((num != 0L) ? $"Steam user {num}" : $"client {clientId}") + ": " + why)); manager.DisconnectClient(clientId); } internal static ulong SteamIdOf(NetworkManager manager, ulong clientId) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) ConnectionInfo val = default(ConnectionInfo); NetworkTransport networkTransport = manager.NetworkConfig.NetworkTransport; FacepunchTransport val2 = (FacepunchTransport)(object)((networkTransport is FacepunchTransport) ? networkTransport : null); if (val2 == null || !val2.connectedClients.TryGetValue(manager.ConnectionManager.ClientIdToTransportId(clientId), out var value) || !SteamNetworkingSockets.Internal.GetConnectionInfo(value.connection, ref val) || !((NetIdentity)(ref val.identity)).IsSteamId) { return 0uL; } return ((NetIdentity)(ref val.identity)).SteamId.Value; } private unsafe static ulong Hash(ArraySegment<byte> body) { fixed (byte* array = body.Array) { return XXHash.Hash64(array + body.Offset, body.Count, 0u); } } private static bool Flooding(ulong transportId, int messages) { float realtimeSinceStartup = Time.realtimeSinceStartup; float item; int num; int num2; if (rates.TryGetValue(transportId, out (float, int, int) value) && realtimeSinceStartup - value.Item1 < 1f) { (item, num, num2) = value; } else { num2 = 0; num = 0; item = realtimeSinceStartup; } rates[transportId] = (item, ++num, num2 += messages); if (num <= 2000) { return num2 > 20000; } return true; } } internal static class Vanilla { private delegate void Orig(GameNetworkManager self, Result result, Lobby lobby); private static Hook? hook; public static void Hook() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown hook = new Hook((MethodBase)typeof(GameNetworkManager).GetMethod("SteamMatchmaking_OnLobbyCreated", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), typeof(Vanilla).GetMethod("Created", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); } private static void Created(Orig orig, GameNetworkManager self, Result result, Lobby lobby) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 orig(self, result, lobby); if ((int)result == 1) { ((Lobby)(ref lobby)).SetData("dmods", "f"); } } } internal static class Voice { private delegate void Orig(BroadcastingClientCollection<NfgoConn> self, NfgoConn source, ref PacketReader reader); private static readonly int[] opusFrameSizes = new int[6] { 120, 240, 480, 960, 1920, 2880 }; private static Hook? handshake; private static Hook? state; public static void Hook() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown Type typeFromHandle = typeof(BroadcastingClientCollection<NfgoConn>); handshake = new Hook((MethodBase)typeFromHandle.GetMethod("ProcessHandshakeRequest", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), typeof(Voice).GetMethod("Handshake", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); state = new Hook((MethodBase)typeFromHandle.GetMethod("ProcessClientState", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), typeof(Voice).GetMethod("State", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); } public static bool Allowed(byte codec, uint frameSize, int sampleRate) { return codec switch { 1 => sampleRate == 48000 && Array.IndexOf(opusFrameSizes, (int)frameSize) >= 0, 0 => sampleRate == 44100 && frameSize == 441, _ => false, }; } private static void Handshake(Orig orig, BroadcastingClientCollection<NfgoConn> self, NfgoConn source, ref PacketReader reader) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) PacketReader copy = reader; if (Check(source, delegate { //IL_000f: Unknown result type (might be due to invalid IL or missing references) string text = default(string); CodecSettings result = default(CodecSettings); ((PacketReader)(ref copy)).ReadHandshakeRequest(ref text, ref result); return result; })) { orig(self, source, ref reader); } } private static void State(Orig orig, BroadcastingClientCollection<NfgoConn> self, NfgoConn source, ref PacketReader reader) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) PacketReader copy = reader; if (Check(source, delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) ClientInfo val = ((PacketReader)(ref copy)).ReadClientStateHeader(); return ((ClientInfo)(ref val)).CodecSettings; })) { orig(self, source, ref reader); } } private static bool Check(NfgoConn source, Func<CodecSettings> read) { //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_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected I4, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) string text; try { CodecSettings val = read(); if (Allowed((byte)(int)((CodecSettings)(ref val)).Codec, ((CodecSettings)(ref val)).FrameSize, ((CodecSettings)(ref val)).SampleRate)) { return true; } text = "bad voice codec settings"; } catch (Exception) { text = "broken voice packet"; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && singleton.IsServer && source.ClientId != singleton.LocalClientId) { Plugin.Kick(singleton, source.ClientId, text); } else { Plugin.Log.LogWarning((object)("Dropped a voice packet from this game: " + text)); } return false; } } internal static class MyPluginInfo { public const string Id = "com.github.lethalcompanymodding.lobbyguard"; public const string Name = "LobbyGuard"; public const string Version = "0.2.4"; } }