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 SimpleLobby v0.1.4
SimpleLobby.dll
Decompiled 2 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Netcode.Transports.Facepunch; using SimpleLobby.Commands; using SimpleLobby.Core; using SimpleLobby.Features; using SimpleLobby.Patches; using Steamworks; using Steamworks.Data; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("ClientNetworkTransform")] [assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")] [assembly: IgnoresAccessChecksTo("DissonanceVoip")] [assembly: IgnoresAccessChecksTo("DunGen")] [assembly: IgnoresAccessChecksTo("DunGen.Integration.ASPP")] [assembly: IgnoresAccessChecksTo("DunGen.Integration.UnityNav")] [assembly: IgnoresAccessChecksTo("EasyTextEffects")] [assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")] [assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")] [assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")] [assembly: IgnoresAccessChecksTo("Unity.Burst")] [assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")] [assembly: IgnoresAccessChecksTo("Unity.Collections")] [assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")] [assembly: IgnoresAccessChecksTo("Unity.Jobs")] [assembly: IgnoresAccessChecksTo("Unity.Mathematics")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")] [assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")] [assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")] [assembly: IgnoresAccessChecksTo("Unity.Services.QoS")] [assembly: IgnoresAccessChecksTo("Unity.Services.Relay")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("Unity.Timeline")] [assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.XR.CoreUtils")] [assembly: IgnoresAccessChecksTo("Unity.XR.Management")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.ConformanceAutomation")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MetaQuestSupport")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.MockRuntime")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.OculusQuestSupport")] [assembly: IgnoresAccessChecksTo("Unity.XR.OpenXR.Features.RuntimeDebugger")] [assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.SpatialTracking")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: IgnoresAccessChecksTo("UnityEngine.XR.LegacyInputHelpers")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.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 SimpleLobby { internal static class ChatUtil { internal static bool BroadcastReplies; internal static string InvokerName; internal static PlayerControllerB Invoker; internal static void SendPrivate(string message) { bool broadcastReplies = BroadcastReplies; BroadcastReplies = false; try { Send(message); } finally { BroadcastReplies = broadcastReplies; } } internal static void Send(string message, bool broadcast = false) { try { if (BroadcastReplies) { broadcast = true; } HUDManager instance = HUDManager.Instance; if (!((Object)(object)instance == (Object)null)) { string text = "<color=#4fd1c5>SimpleLobby</color>: <color=#e8e86a>" + message + "</color>"; if (broadcast) { instance.AddTextMessageServerRpc(text); } else { instance.AddChatMessage(text, "", -1, false); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ChatUtil.Send] {arg}"); } } internal static void SendRaw(string message, bool broadcast = false) { try { HUDManager instance = HUDManager.Instance; if (!((Object)(object)instance == (Object)null)) { if (broadcast) { instance.AddTextMessageServerRpc(message); } else { instance.AddChatMessage(message, "", -1, false); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ChatUtil.SendRaw] {arg}"); } } internal static void SendChunked(string message, bool broadcast = false, int maxLen = 120) { if (string.IsNullOrEmpty(message)) { return; } while (message.Length > maxLen) { int num = message.LastIndexOfAny(new char[2] { '·', ',' }, maxLen); if (num < maxLen / 2) { num = message.LastIndexOf(' ', maxLen); } if (num < maxLen / 2) { num = maxLen; } Send(message.Substring(0, num).TrimEnd(), broadcast); message = message.Substring(num).TrimStart(' ', ',', '·'); } if (message.Length > 0) { Send(message, broadcast); } } } internal static class CommandRouter { internal static bool IsSenderTheHost(int playerId) { try { if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsHost) { return false; } if (playerId < 0) { Plugin.Logger.LogWarning((object)$"[CommandRouter] playerId={playerId} on host — allowing (local chat path)."); return true; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null) { return false; } return playerId == (int)val.playerClientId || playerId == (int)val.actualClientId; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CommandRouter.IsSenderTheHost] {arg}"); return false; } } internal static string CanonicalVerb(string cmd) { switch (cmd) { case "sell": case "s": return "sell"; case "stock": case "st": return "stock"; case "m": case "merge": return "merge"; case "gear": case "g": return "gear"; case "e": case "equipment": return "equipment"; case "o": case "organize": return "organize"; case "time": case "t": return "time"; case "ot": case "overtime": return "overtime"; case "b": case "briefing": return "briefing"; case "l": case "lobby": return "lobby"; case "scrap": return "scrap"; case "players": return "players"; case "kick": return "kick"; case "guard": return "guard"; case "whitelist": return "whitelist"; case "ban": return "ban"; case "unban": return "unban"; case "bans": return "bans"; case "return": return "return"; case "buy": return "buy"; case "cr": case "credits": return "credits"; case "help": return "help"; default: return null; } } internal static bool IsLobbyVerb(string canonical) { switch (canonical) { case "guard": case "lobby": case "unban": case "bans": case "kick": case "players": case "whitelist": case "ban": return true; default: return false; } } internal static bool IsCommandAllowed(string canonical) { if (canonical == "help") { return true; } string[] array = (ModConfig.WhitelistedCommands.Value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { string text2 = CanonicalVerb(text.Trim().ToLowerInvariant()) ?? text.Trim().ToLowerInvariant(); if (text2 == canonical) { return true; } } return false; } private static string JoinName(string[] parts, int startIndex) { if (parts == null || startIndex >= parts.Length) { return string.Empty; } string text = string.Join(" ", parts, startIndex, parts.Length - startIndex).Trim(); if (text.Length >= 2 && text[0] == '"' && text[text.Length - 1] == '"') { text = text.Substring(1, text.Length - 2).Trim(); } return text; } internal static bool Route(string raw) { try { if (string.IsNullOrWhiteSpace(raw)) { return false; } string[] array = raw.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string cmd = array[0].ToLowerInvariant(); string text = CanonicalVerb(cmd); if (text == null || (!IsLobbyVerb(text) && text != "help")) { return false; } if (TournamentGate.Active && text != "help") { ChatUtil.Send("Tournament mode: mod commands are off until the run ends."); return true; } switch (text) { case "lobby": { string text3 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : ""); switch (text3) { case "hide": case "h": LobbyTagFeature.Apply(hide: true); break; case "show": case "s": LobbyTagFeature.Apply(hide: false); break; case "status": LobbyTagFeature.Status(); break; case "name": LobbyTagFeature.Rename(string.Join(" ", array, 2, array.Length - 2).Trim()); break; case "public": case "friends": case "private": LobbyTagFeature.SetVisibility(text3); break; case "late": case "open": { string text4 = ((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); if (text4 == "on" || text4 == "off") { ModConfig.AllowLateJoin.Value = text4 == "on"; LateJoin.ResetAttempts(); LateJoin.Refresh(force: true); ChatUtil.Send(LateJoin.Status()); } else { ChatUtil.Send(LateJoin.Status() + " Usage: /lobby late on|off"); } break; } case "invite": case "inv": LateJoin.Invite(); break; case "log": LobbyLog.Command(string.Join(" ", array, 2, array.Length - 2)); break; default: ChatUtil.Send("Usage: /lobby hide | show | status | name <new name> | public | friends | private | late on|off | invite | log [name|file]"); break; } return true; } case "players": if (array.Length >= 2 && array[1].Equals("ids", StringComparison.OrdinalIgnoreCase)) { GuardCommands.PlayersIds(); } else { GuardCommands.Players(); } return true; case "kick": GuardCommands.Kick(JoinName(array, 1)); return true; case "guard": { string text2 = ((array.Length >= 2) ? array[1].ToLowerInvariant() : ""); if (text2 == "scrap") { ScrapGuardCommand((array.Length >= 3) ? array[2].ToLowerInvariant() : ""); } else { GuardCommands.Guard(text2); } return true; } case "whitelist": GuardCommands.Whitelist((array.Length >= 2) ? array[1].ToLowerInvariant() : "", JoinName(array, 2)); return true; case "ban": GuardCommands.Ban(JoinName(array, 1)); return true; case "unban": GuardCommands.Unban(JoinName(array, 1)); return true; case "bans": GuardCommands.Bans(); return true; case "help": return Help((array.Length >= 2) ? array[1].ToLowerInvariant() : ""); default: return false; } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CommandRouter.Route] {arg}"); ChatUtil.Send("Command failed — see BepInEx log."); return true; } } private static bool Help(string topic) { if (topic == "") { ChatUtil.Send("SimpleLobby — /help lobby"); return true; } if (topic != "lobby" && topic != "simplelobby" && topic != "l") { return false; } if ((Object)(object)ChatUtil.Invoker != (Object)null) { string text = string.Join(", ", from x in (ModConfig.WhitelistedCommands.Value ?? "").Split(',') select "/" + x.Trim() into x where x.Length > 1 select x); ChatUtil.Send("For you: " + ((text.Length == 0) ? "nothing yet" : text) + (string.IsNullOrEmpty(ModConfig.AltCommandPrefix.Value) ? "" : (" (or " + ModConfig.AltCommandPrefix.Value + " if / doesn't work)"))); return true; } ChatUtil.Send("Lobby: /lobby hide|show|status · /lobby name <name> · /lobby public|friends|private · /lobby late on|off · /lobby invite"); ChatUtil.Send("Players: /players [ids] · /kick <name> · /whitelist add|remove <name> | view · /ban <name|steam id> · /unban · /bans"); ChatUtil.Send("Guard: /guard [warn|kick] · scrap guard: /guard scrap on|off|log|warn|kick"); return true; } private static void ScrapGuardCommand(string tok) { switch (tok) { case "on": ModConfig.ScrapGuardEnabled.Value = true; break; case "off": ModConfig.ScrapGuardEnabled.Value = false; break; case "log": ModConfig.ScrapGuardEnabled.Value = true; ModConfig.ScrapGuardMode.Value = ScrapGuardAction.Log; break; case "warn": ModConfig.ScrapGuardEnabled.Value = true; ModConfig.ScrapGuardMode.Value = ScrapGuardAction.Warn; break; case "kick": ModConfig.ScrapGuardEnabled.Value = true; ModConfig.ScrapGuardMode.Value = ScrapGuardAction.Kick; break; } ChatUtil.Send((!ModConfig.ScrapGuardEnabled.Value) ? "Scrap guard: OFF. /guard scrap on to turn it back on." : ("Scrap guard: ON (" + ModConfig.ScrapGuardMode.Value.ToString().ToLowerInvariant() + "). Blocks scrap counts other players' mods post to the lobby" + (ModConfig.VanillaSystemLinesOnly.Value ? "" : " (system lines check off in cfg)") + "; a counter that only shows on their own screen can't be stopped by the host.")); } } internal enum GuardEnforcement { Warn, Kick } internal static class ModConfig { internal static ConfigEntry<string> CommandPrefix { get; private set; } internal static ConfigEntry<bool> AnnounceBans { get; private set; } internal static ConfigEntry<bool> FixCrewList { get; private set; } internal static ConfigEntry<bool> RefuseForgedSteamId { get; private set; } internal static ConfigEntry<bool> VerifySteamIdentity { get; private set; } internal static ConfigEntry<bool> ReserveJoinSlots { get; private set; } internal static ConfigEntry<bool> RefuseJoinWithoutSlot { get; private set; } internal static ConfigEntry<bool> DropStuckJoiners { get; private set; } internal static ConfigEntry<float> StuckJoinSeconds { get; private set; } internal static ConfigEntry<bool> RepairJoinSync { get; private set; } internal static ConfigEntry<bool> FixInvisibleJoiners { get; private set; } internal static ConfigEntry<bool> ReopenSteamLobby { get; private set; } internal static ConfigEntry<bool> AllowLateJoin { get; private set; } internal static ConfigEntry<bool> HideLobbyModTag { get; private set; } internal static ConfigEntry<bool> LobbyHideDefaultMigrated { get; private set; } internal static ConfigEntry<GuardEnforcement> GuardMode { get; private set; } internal static ConfigEntry<string> WhitelistedPlayers { get; private set; } internal static ConfigEntry<string> BannedPlayers { get; private set; } internal static ConfigEntry<bool> OvertimeWhitelistAdded { get; private set; } internal static ConfigEntry<bool> BuyWhitelistAdded { get; private set; } internal static ConfigEntry<string> WhitelistedCommands { get; private set; } internal static ConfigEntry<string> AltCommandPrefix { get; private set; } internal static ConfigEntry<int> HostChatLimit { get; private set; } internal static ConfigEntry<float> DeskGuardRange { get; private set; } internal static ConfigEntry<bool> FirewallEnabled { get; private set; } internal static ConfigEntry<bool> BanForgedRpc { get; private set; } internal static ConfigEntry<bool> BanLeverlessStart { get; private set; } internal static ConfigEntry<bool> FirewallLogAllowed { get; private set; } internal static ConfigEntry<float> LeverPullWindowSeconds { get; private set; } internal static ConfigEntry<float> LeverRange { get; private set; } internal static ConfigEntry<float> TerminalRange { get; private set; } internal static ConfigEntry<int> MaxItemsPerOrder { get; private set; } internal static ConfigEntry<int> MaxChatLength { get; private set; } internal static ConfigEntry<float> FurnitureBoundsMargin { get; private set; } internal static ConfigEntry<bool> ItemFirewall { get; private set; } internal static ConfigEntry<float> ItemReach { get; private set; } internal static ConfigEntry<float> ItemPlaceReach { get; private set; } internal static ConfigEntry<float> ItemThrowReach { get; private set; } internal static ConfigEntry<float> ItemForgedDistance { get; private set; } internal static ConfigEntry<bool> DeskJanitor { get; private set; } internal static ConfigEntry<bool> BeltBagVanillaRule { get; private set; } internal static ConfigEntry<string> BeltBagAllowedScrap { get; private set; } internal static ConfigEntry<int> BeltBagStrikes { get; private set; } internal static ConfigEntry<bool> ScrapGuardEnabled { get; private set; } internal static ConfigEntry<bool> VanillaSystemLinesOnly { get; private set; } internal static ConfigEntry<bool> BlockScrapReadoutChat { get; private set; } internal static ConfigEntry<ScrapGuardAction> ScrapGuardMode { get; private set; } internal static ConfigEntry<bool> ImportedFromSimpleSell { get; private set; } internal static void Init(ConfigFile file) { //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Expected O, but got Unknown CommandPrefix = file.Bind<string>("General", "CommandPrefix", "/", "Prefix for chat commands (e.g. /lobby). Copied from SimpleSell on the first run; SimpleSell keeps its own."); AnnounceBans = file.Bind<bool>("Lobby", "AnnounceBans", false, "If true, a successful /ban broadcasts 'X was banned by the host.' to the crew. Default off: ban confirmations stay host-only."); FixCrewList = file.Bind<bool>("Lobby", "FixCrewList", true, "Remove leftover names from the pause menu crew list. The game adds a name as soon as someone enters the Steam lobby and never removes it if they're turned away or give up, so public lobbies fill the list with strangers."); RefuseForgedSteamId = file.Bind<bool>("Lobby", "RefuseForgedSteamId", true, "Refuse a player whose client reports a different Steam ID than Steam itself does. That mismatch is how a modded client dodges a kick or a ban, since the game checks the id the client sends. Vanilla players never trigger it."); VerifySteamIdentity = file.Bind<bool>("Lobby", "VerifySteamIdentity", true, "Read each joining player's Steam ID from the Steam connection itself (what Steam says), not only from what their game reports. Bans, kicks and RefuseForgedSteamId then use the real ID. If the real ID can't be read for someone, SimpleLobby falls back to the reported ID exactly like 0.1.36. Off = always use the reported ID (0.1.36 behaviour)."); ReserveJoinSlots = file.Bind<bool>("Lobby", "ReserveJoinSlots", true, "Count players who were let in but are still connecting when deciding whether the lobby is full. Without this, two people joining in the same moment can both be let into the last free slot, which crashed the whole lobby (Oct 17). Uses the game's real player slot count, so bigger lobbies from other mods keep working."); RefuseJoinWithoutSlot = file.Bind<bool>("Lobby", "RefuseJoinWithoutSlot", true, "Safety net behind ReserveJoinSlots: if a joiner gets through but there is no free player slot left, turn just that player away with \"Lobby is full!\". Vanilla would give them the host's own player, hit an error and shut the whole lobby down."); DropStuckJoiners = file.Bind<bool>("Lobby", "DropStuckJoiners", true, "If a joining player gets stuck (the game fails to send them the world, or they never finish connecting), disconnect just that player so they can try again. Without this a stuck joiner is retried every frame forever, flooding the log and blocking the slot."); StuckJoinSeconds = file.Bind<float>("Lobby", "StuckJoinSeconds", 45f, "How long a player may take from being let in to fully joining before DropStuckJoiners drops them. Generous on purpose: slow PCs need time to load. Minimum 15."); RepairJoinSync = file.Bind<bool>("Lobby", "RepairJoinSync", true, "Before a player is let in, fix spawned objects that still point at a parent object that no longer exists. A stale link like that makes the game fail to send the world to the joiner. Only those stale links are touched."); FixInvisibleJoiners = file.Bind<bool>("Lobby", "FixInvisibleJoiners", true, "Fix joiners who show up as just a floating name with no body. If a player dies and then leaves, the game hides their body and never shows it again, so the next person given that player slot is invisible (Oct 22: LitCedar). Shows the body again when that slot is freed and when it is taken. Works on your own screen; other players need SimpleLobby (or SimpleSell 0.1.38-0.1.39) too to see it fixed."); ReopenSteamLobby = file.Bind<bool>("Lobby", "ReopenSteamLobby", true, "Vanilla closes the Steam lobby the first time you land. With late join on (/lobby late on), this opens a new Steam lobby for the game you're already in whenever the ship is in orbit, so friends can find it, use Join Game and be invited again. Same name, privacy and version as before. Nothing happens unless late join is on."); AllowLateJoin = file.Bind<bool>("Lobby", "AllowLateJoin", false, "OFF by default and switched back OFF every time the game starts — it reopens the lobby mid-run, which shouldn't happen unless you ask for it. Turn on with /lobby late on when someone needs to join; it lasts until you quit. Never while SimpleHQ tournament mode is on. Lets someone join after the game has started — ONLY while in orbit (ship phase, not leaving, not travelling). Landed or mid-flight stays closed: the joiner would arrive into a generated level that vanilla never expected to sync to a newcomer. Vanilla's version check, kicked-player check and SimpleLobby's bans all still apply. Toggle in-game: /lobby late on|off."); HideLobbyModTag = file.Bind<bool>("Lobby", "HideLobbyModTag", true, "If true, newly created lobbies present as vanilla (no mods tag in the browser, no mods warning on join). Can be toggled live with /lobby hide|show."); LobbyHideDefaultMigrated = file.Bind<bool>("Lobby", "HideDefaultMigrated", false, "Internal flag — do not edit. (Round 26 one-time switch to hidden-by-default.)"); GuardMode = file.Bind<GuardEnforcement>("Lobby", "GuardMode", GuardEnforcement.Warn, "What happens when a client sends modded network traffic. Warn = private alert to the host only; Kick = auto-kick on first modded packet (also kicks networked cosmetic mods - they are indistinguishable on the wire). SimpleSell's sell-desk beam block is always active regardless of mode. Live toggle: /guard warn | kick."); WhitelistedPlayers = file.Bind<string>("Lobby", "WhitelistedPlayers", "", "Trusted players as 'steamid|name;...' — they may run the WhitelistedCommands from vanilla chat (no mods needed on their end). With SimpleLobby installed on their side they can also pull the lever to start the game and move equipment (not scrap) before the game has started; the host accepts both from them. Managed in-game via /whitelist add|remove."); BannedPlayers = file.Bind<string>("Lobby", "BannedPlayers", "", "Banned players as 'steamid|name;...' — rejected at connection approval, before spawning. Name changes don't help; the Steam ID is what's enforced. Managed in-game via /ban, /unban, /bans."); OvertimeWhitelistAdded = file.Bind<bool>("Lobby", "OvertimeWhitelistAdded", false, "Internal: one-time addition of overtime to WhitelistedCommands. Remove overtime from that list to take it away again; this won't put it back."); BuyWhitelistAdded = file.Bind<bool>("Lobby", "BuyWhitelistAdded", false, "Internal: one-time addition of buy and credits to WhitelistedCommands. Remove them from that list to take them away again; this won't put them back."); WhitelistedCommands = file.Bind<string>("Lobby", "WhitelistedCommands", "stock, time, overtime, gear, organize, merge, buy, credits", "Comma-separated commands whitelisted players may use (canonical names; aliases resolve automatically). buy/credits let them order from the store with /buy (works before the game starts too; the host's credits are spent). Selling, lobby, guard, and recall stay host-only unless you add them here."); AltCommandPrefix = file.Bind<string>("Lobby", "AltCommandPrefix", "!", "Second prefix whitelisted players may use (e.g. !merge). Friends whose own chat-command mods swallow '/' messages locally can still reach the host this way. Empty disables."); HostChatLimit = file.Bind<int>("Lobby", "HostChatLimit", 120, new ConfigDescription("Characters the HOST can type in chat (vanilla: 49), so /lobby name and other commands can be longer. Only your own chat box; other players and what they're allowed to send are unchanged. Ordinary chat over 49 characters still only shows on your screen (the game drops it).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(49, 250), Array.Empty<object>())); DeskGuardRange = file.Bind<float>("Lobby", "DeskGuardRange", 12f, "Item firewall: max distance (meters) between another player and the sell desk when they put an item on the counter. Vanilla physical placement is always within a few meters; modded clients beaming items from the ship get blocked. (SimpleSell keeps its own copy in [Selling] for its desk-beam check, used when the item firewall is off.)"); FirewallEnabled = file.Bind<bool>("Firewall", "Enabled", true, "Check the game's own server RPCs from other players: landing, takeoff, purchases, routing, furniture and chat. The host's own actions are never checked. Refusals are logged as [RpcFirewall] REFUSED / FORGED in the BepInEx log."); BanForgedRpc = file.Bind<bool>("Firewall", "BanForgedRpc", true, "Automatically ban (by real Steam id, same list as /ban) a player who sends an RPC no unmodded game can send: a spoofed player id, a debug RPC, a lever-less landing, or a lever pull before the host has started the game this session. Whitelisted players are never banned. Off = refuse and flag through GuardMode instead."); BanLeverlessStart = file.Bind<bool>("Firewall", "BanLeverlessStart", true, "Ban a player whose landing request comes when NOBODY (not them, not anyone else, not the host) pulled the lever within LeverPullWindowSeconds. A request from a player who didn't pull it while someone else did is only refused. Off = refuse only."); FirewallLogAllowed = file.Bind<bool>("Firewall", "LogAllowed", false, "Also log ALLOWED landings, purchases and routes from other players. Turn on while testing."); LeverPullWindowSeconds = file.Bind<float>("Firewall", "LeverPullWindowSeconds", 20f, "How recently a player must have pulled the lever for their landing request to count."); LeverRange = file.Bind<float>("Firewall", "LeverRange", 10f, "Max distance (m) from the lever for another player's landing / takeoff request."); TerminalRange = file.Bind<float>("Firewall", "TerminalRange", 10f, "Max distance (m) from the terminal for another player's purchase or route."); MaxItemsPerOrder = file.Bind<int>("Firewall", "MaxItemsPerOrder", 12, "Most items another player may order in one terminal purchase (vanilla's dropship holds 12)."); MaxChatLength = file.Bind<int>("Firewall", "MaxChatLength", 80, "Longest chat message accepted from another player (vanilla's chat box stops at 50)."); FurnitureBoundsMargin = file.Bind<float>("Firewall", "FurnitureBoundsMargin", 1.5f, "How far (m) outside the ship room another player may place furniture before it's refused."); ItemFirewall = file.Bind<bool>("Firewall", "ItemFirewall", true, "Check other players' item grabs, drops, placements, desk deposits and belt-bag moves: they must be holding the item and be near where it goes. Stops client sell / teleport mods moving items."); ItemReach = file.Bind<float>("Firewall", "ItemReach", 10f, "Max distance (m) between another player and an item they grab or put in a belt bag (vanilla reach is 5 m; bodies get 3x)."); ItemPlaceReach = file.Bind<float>("Firewall", "ItemPlaceReach", 12f, "Max distance (m) from another player to where they place a held item (shelf, desk, cruiser). Farther = the item is dropped at their feet instead."); ItemThrowReach = file.Bind<float>("Firewall", "ItemThrowReach", 30f, "Max horizontal distance (m) from another player to where their dropped/thrown item lands (a stun grenade flies ~12 m). Farther = dropped at their feet instead."); ItemForgedDistance = file.Bind<float>("Firewall", "ItemForgedDistance", 25f, "Dropping, placing or depositing an item the player isn't holding is refused. If the item is also farther than this (m) from them, it counts as forged (auto-ban when BanForgedRpc is on)."); DeskJanitor = file.Bind<bool>("Firewall", "DeskJanitor", true, "At the Company, every few seconds put any item sitting on the sell desk that the desk hasn't registered (so it can never be sold or picked up) back on the ship."); BeltBagVanillaRule = file.Bind<bool>("Firewall", "BeltBagVanillaRule", true, "Belt bags only take what a vanilla client can put in them: non-scrap items (tools, shells, keys, other bags...), not the Maneater baby / its egg item ids, at most 15, into the bag the player holds. Scrap or those items sent anyway count as forged (auto-ban when BanForgedRpc is on), and anything like that already in a bag is dropped back out for everyone. Turn off if the whole lobby, host included, runs a mod that lets bags hold scrap."); BeltBagAllowedScrap = file.Bind<string>("Firewall", "BeltBagAllowedScrap", "51020=Gold nugget", "0.1.1: scrap that IS allowed in belt bags despite BeltBagVanillaRule, because a moon mod makes it bag-able natively (SimpleMoons' Vesper Gold nugget). Comma-separated entries, each 'id=name' (both must match), 'id' (item id only) or 'name' (item name only, case-insensitive). Default: '51020=Gold nugget'. Everything else that is scrap stays forbidden. Empty = vanilla rule only."); BeltBagStrikes = file.Bind<int>("Firewall", "BeltBagStrikes", 3, "Forbidden belt-bag attempts (scrap, the two excluded items, a bag into itself): the first one is refused with a private warning; this many more get an auto-ban (needs BanForgedRpc). Attempts within 1 s count as one. 0 = never ban, only refuse and warn."); ScrapGuardEnabled = file.Bind<bool>("ScrapGuard", "Enabled", true, "Host-only guard against client mods that report the interior's scrap (count / value) to the lobby. Nothing is sent to anyone's game; vanilla players are unaffected. It can stop readouts that go THROUGH the host (chat lines, mods that ask the host) but not a mod that only shows numbers on its own player's screen: every game already has the scrap objects, the vanilla terminal scan counts them too. Works with [Firewall] Enabled on or off."); VanillaSystemLinesOnly = file.Bind<bool>("ScrapGuard", "VanillaSystemLinesOnly", true, "Other players' games may only post the system chat lines a vanilla game sends ('X joined the ship.', 'X was left behind.', '[playerNumN] started the ship.'). Anything else (e.g. abu's AutoScan posting 'Inside: 23 ≈1450' with HostOnly off) is not shown to anyone. Whitelisted players' non-scrap lines still go through. The host's own lines (every suite plugin's) are never checked."); BlockScrapReadoutChat = file.Bind<bool>("ScrapGuard", "BlockScrapReadoutChat", true, "Don't relay another player's chat line when it is only a scrap readout ('12 1450', what AutoTerminalScanClassic posts in Everyone mode). The first one is never a kick (someone could type it)."); ScrapGuardMode = file.Bind<ScrapGuardAction>("ScrapGuard", "Action", ScrapGuardAction.Warn, "What happens to a player caught by the scrap guard (a blocked scrap readout, or a known interior-info mod such as LCBridgeOverlay saying hello). Log = BepInEx log only; Warn = private note to the host, once per player per lobby; Kick = kick them. Whitelisted players are never kicked by this guard. With [Lobby] GuardMode = Kick, Warn acts as Kick."); ImportedFromSimpleSell = file.Bind<bool>("Migration", "ImportedFromSimpleSell", false, "Internal flag — do not edit. (One-time copy of the lobby, whitelist, ban and firewall settings from SimpleSell's local.simplesell.cfg, done the first time SimpleLobby runs.)"); ImportFromSimpleSell(file); if (!LobbyHideDefaultMigrated.Value) { HideLobbyModTag.Value = true; LobbyHideDefaultMigrated.Value = true; } if (!OvertimeWhitelistAdded.Value) { string text = (WhitelistedCommands.Value ?? "").Trim(); if (!text.Split(',').Any(delegate(string x) { string text4 = x.Trim().ToLowerInvariant(); return (text4 == "overtime" || text4 == "ot") ? true : false; })) { WhitelistedCommands.Value = ((text.Length == 0) ? "overtime" : (text + ", overtime")); } OvertimeWhitelistAdded.Value = true; } if (BuyWhitelistAdded.Value) { return; } string text2 = (WhitelistedCommands.Value ?? "").Trim(); HashSet<string> hashSet = new HashSet<string>(from x in text2.Split(',') select x.Trim().ToLowerInvariant()); string[] array = new string[2] { "buy", "credits" }; foreach (string text3 in array) { bool num2; if (!(text3 == "credits")) { num2 = hashSet.Contains(text3); } else { if (hashSet.Contains("credits")) { continue; } num2 = hashSet.Contains("cr"); } if (!num2) { text2 = ((text2.Length == 0) ? text3 : (text2 + ", " + text3)); } } WhitelistedCommands.Value = text2; BuyWhitelistAdded.Value = true; } private static void ImportFromSimpleSell(ConfigFile file) { if (ImportedFromSimpleSell.Value) { return; } bool saveOnConfigSet = file.SaveOnConfigSet; file.SaveOnConfigSet = false; try { string path = Path.Combine(Paths.ConfigPath, "local.simplesell.cfg"); int num = 0; if (File.Exists(path)) { Dictionary<(string, string), string> dictionary = ReadCfg(path); foreach (ConfigDefinition item in file.Keys.ToList()) { if (!(item.Section == "Migration") && !(item.Section == "ScrapGuard") && (!(item.Section == "General") || !(item.Key != "CommandPrefix")) && dictionary.TryGetValue((item.Section, item.Key), out var value)) { ConfigEntryBase val = file[item]; if (!(val.GetSerializedValue() == value)) { val.SetSerializedValue(value); num++; } } } Plugin.Logger.LogInfo((object)($"[Migration] copied {num} changed setting(s) from local.simplesell.cfg " + $"(whitelist {Count(WhitelistedPlayers.Value)}, bans {Count(BannedPlayers.Value)}).")); } else { Plugin.Logger.LogInfo((object)"[Migration] no local.simplesell.cfg: starting with SimpleLobby's defaults."); } PlayerJoinLog.MigrateFromSimpleSell(); ImportedFromSimpleSell.Value = true; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[Migration] couldn't import SimpleSell's settings: {arg}"); } finally { file.SaveOnConfigSet = saveOnConfigSet; file.Save(); } } private static int Count(string list) { return (list ?? "").Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries).Length; } internal static Dictionary<(string, string), string> ReadCfg(string path) { Dictionary<(string, string), string> dictionary = new Dictionary<(string, string), string>(); string item = ""; string[] array = File.ReadAllLines(path); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length == 0 || text2.StartsWith("#") || text2.StartsWith(";")) { continue; } if (text2.StartsWith("[") && text2.EndsWith("]")) { item = text2.Substring(1, text2.Length - 2).Trim(); continue; } int num = text2.IndexOf('='); if (num > 0) { dictionary[(item, text2.Substring(0, num).Trim())] = text2.Substring(num + 1).Trim(); } } return dictionary; } } public static class LobbyApi { public static bool Active => Plugin.Active; internal static bool IsHost { get { if ((Object)(object)NetworkManager.Singleton != (Object)null) { return NetworkManager.Singleton.IsHost; } return false; } } public static bool IsWhitelisted(ulong steamId) { try { return Plugin.Active && steamId != 0L && PersistedIdList.Whitelist.Contains(steamId); } catch { return false; } } public static bool IsTrustedClient(ulong clientId) { try { if (!Plugin.Active) { return false; } PlayerControllerB p = LobbyGuard.FindByClientId(clientId); ulong num = TrueSteamId.Best(clientId, p); return num != 0L && PersistedIdList.Whitelist.Contains(num); } catch { return false; } } public static string CommandText(string chatMessage) { try { if (!Plugin.Active || string.IsNullOrEmpty(chatMessage)) { return null; } string value = ModConfig.CommandPrefix.Value; string value2 = ModConfig.AltCommandPrefix.Value; if (!string.IsNullOrEmpty(value) && chatMessage.StartsWith(value)) { return chatMessage.Substring(value.Length).Trim(); } if (!string.IsNullOrEmpty(value2) && chatMessage.StartsWith(value2)) { return chatMessage.Substring(value2.Length).Trim(); } return null; } catch { return null; } } public static string AltCommandPrefix() { if (!Plugin.Active) { return ""; } return ModConfig.AltCommandPrefix.Value ?? ""; } public static string WhitelistedCommands() { if (!Plugin.Active) { return ""; } return ModConfig.WhitelistedCommands.Value ?? ""; } public static bool IsCommandAllowed(string canonical) { try { return Plugin.Active && !string.IsNullOrEmpty(canonical) && CommandRouter.IsCommandAllowed(canonical.ToLowerInvariant()); } catch { return false; } } public static bool ItemFirewallActive() { if (Plugin.Active && ModConfig.FirewallEnabled.Value) { return ModConfig.ItemFirewall.Value; } return false; } public static ulong CurrentRemoteSender() { try { ulong sender; return (Plugin.Active && RpcFirewall.RemoteSender(out sender)) ? sender : ulong.MaxValue; } catch { return ulong.MaxValue; } } public static void ReportModdedTraffic(ulong clientId, string kind) { try { if (Plugin.Active) { LobbyGuard.OnModdedTraffic(clientId, kind ?? "modded traffic"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LobbyApi.ReportModdedTraffic] {arg}"); } } public static string PlayerName(ulong clientId) { try { return LobbyGuard.ResolveName(clientId); } catch { return $"client {clientId}"; } } public static ulong SteamIdOf(ulong clientId) { try { return TrueSteamId.Best(clientId, LobbyGuard.FindByClientId(clientId)); } catch { return 0uL; } } } [BepInPlugin("local.simplelobby", "SimpleLobby", "0.1.4")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("local.simplelobby"); internal static readonly Version FirstSplitSimpleSell = new Version(0, 1, 40); public static Plugin Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } internal static bool Active { get; private set; } private void Awake() { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown Instance = this; Logger = ((BaseUnityPlugin)this).Logger; if (Chainloader.PluginInfos.TryGetValue("local.simplesell", out var value)) { object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Version : null); } if ((Version?)obj != null && value.Metadata.Version < FirstSplitSimpleSell) { Active = false; Logger.LogError((object)(string.Format("{0} v{1} is STANDING DOWN: SimpleSell {2} ", "SimpleLobby", "0.1.4", value.Metadata.Version) + "still has its own lobby features (whitelist, bans, guard, firewall, late join). Update SimpleSell to 0.1.40 or newer. Until then SimpleSell's own copy runs, and SimpleLobby does nothing.")); try { _harmony.Patch((MethodBase)AccessTools.Method(typeof(StartOfRound), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(StandDownNotice), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return; } catch (Exception ex) { Logger.LogError((object)("[StandDown] " + ex.Message)); return; } } } Active = true; ModConfig.Init(((BaseUnityPlugin)this).Config); ModConfig.AllowLateJoin.Value = false; _harmony.PatchAll(); RpcFirewall.Install(_harmony); Logger.LogInfo((object)("SimpleLobby v0.1.4 loaded. Lobby, whitelist, guard and firewall armed" + (ModConfig.ScrapGuardEnabled.Value ? ", scrap guard on." : "."))); } } public static class PluginInfo { public const string Guid = "local.simplelobby"; public const string Name = "SimpleLobby"; public const string Version = "0.1.4"; } } namespace SimpleLobby.Patches { [HarmonyPatch(typeof(GameNetworkManager), "ConnectionApproval")] internal static class BanApprovalPatch { [HarmonyPostfix] private static void Postfix(ConnectionApprovalRequest request, ConnectionApprovalResponse response) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) try { if (response == null || !response.Approved) { return; } ulong num = TrueSteamId.Claimed(request.Payload); ulong num2 = TrueSteamId.For(request.ClientNetworkId); TrueSteamId.LogJoin(request.ClientNetworkId, num); TrueSteamId.RememberClaim(request.ClientNetworkId, num); ulong num3 = HardBan.Match(num2, num); if (num3 != 0L) { response.Approved = false; response.Reason = "You are banned from this lobby."; Plugin.Logger.LogWarning((object)$"[Ban] refused PERMANENTLY banned {HardBan.NameOf(num3)} ({num3}; real {num2}, claimed {num})."); LobbyLog.JoinRefused(request.ClientNetworkId, num2, num, "permanent ban"); LobbyLog.Write("permaban", null, null, LobbyLog.F("steam", num3), LobbyLog.F("why", "join refused")); ChatUtil.SendPrivate("⚠ Blocked permanently banned " + HardBan.NameOf(num3) + " from joining."); } else if (num2 != 0L && num != 0L && num2 != num && ModConfig.RefuseForgedSteamId.Value) { response.Approved = false; response.Reason = "Your client reported a different Steam ID than Steam did."; Plugin.Logger.LogWarning((object)$"[Ban] refused a join claiming {num} from real id {num2}."); LobbyLog.JoinRefused(request.ClientNetworkId, num2, num, "forged steam id"); ChatUtil.Send("⚠ Blocked a player whose client lied about its Steam ID."); } else if (PersistedIdList.Bans.Count != 0) { ulong num4 = ((num2 != 0L) ? num2 : num); if (num4 != 0L && (PersistedIdList.Bans.Contains(num4) || (num != 0L && PersistedIdList.Bans.Contains(num)))) { response.Approved = false; response.Reason = "You are banned from this lobby."; string text = PersistedIdList.Bans.NameOf(num4); Plugin.Logger.LogInfo((object)$"[Ban] rejected join from {text} ({num4})."); LobbyLog.JoinRefused(request.ClientNetworkId, num2, num, "banned"); ChatUtil.Send("⚠ Blocked banned player " + text + " from joining."); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[BanApprovalPatch] {arg}"); } } } [HarmonyPatch(typeof(HUDManager), "AddPlayerChatMessageServerRpc")] internal static class WhitelistChatPatch { [HarmonyPrefix] private static bool Prefix(string chatMessage, int playerId) { try { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsHost) { return true; } string text = LobbyApi.CommandText(chatMessage); if (text == null) { return true; } if (CommandRouter.IsSenderTheHost(playerId)) { return true; } StartOfRound instance = StartOfRound.Instance; if (instance?.allPlayerScripts == null || playerId < 0 || playerId >= instance.allPlayerScripts.Length) { return true; } PlayerControllerB val = instance.allPlayerScripts[playerId]; if ((Object)(object)val == (Object)null || !val.isPlayerControlled) { return true; } if (!LobbyApi.IsTrustedClient(val.actualClientId)) { return true; } string text2 = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? ""; string text3 = CommandRouter.CanonicalVerb(text2.ToLowerInvariant()); if (text3 == null || (!CommandRouter.IsLobbyVerb(text3) && text3 != "help")) { return true; } if (!CommandRouter.IsCommandAllowed(text3)) { ChatUtil.Send("'" + text3 + "' is host-only.", broadcast: true); return false; } ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)("[Whitelist] " + val.playerUsername + " ran /" + text)); } LobbyLog.Cmd(text3, val.playerUsername); ChatUtil.BroadcastReplies = true; ChatUtil.InvokerName = val.playerUsername; ChatUtil.Invoker = val; bool flag; try { flag = CommandRouter.Route(text); } finally { ChatUtil.BroadcastReplies = false; ChatUtil.InvokerName = null; ChatUtil.Invoker = null; } return !flag; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[WhitelistChatPatch] {arg}"); return true; } } } [HarmonyPatch(typeof(StartOfRound), "KickPlayer")] internal static class KickIdentityPatch { [HarmonyPostfix] private static void Postfix(StartOfRound __instance, int playerObjToKick) { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsServer && __instance.allPlayerScripts != null && playerObjToKick >= 0 && playerObjToKick < __instance.allPlayerScripts.Length) { PlayerControllerB val = __instance.allPlayerScripts[playerObjToKick]; ulong num = TrueSteamId.For(val.actualClientId); if (num != 0L && !__instance.KickedClientIds.Contains(num)) { __instance.KickedClientIds.Add(num); Plugin.Logger.LogInfo((object)$"[Kick] also barred Steam id {num} (the client reported {val.playerSteamId})."); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[KickIdentityPatch] {arg}"); } } } [HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")] internal static class ChatCommandPatch { [HarmonyPrefix] private static bool Prefix(string chatMessage, int playerId) { try { Plugin.Logger.LogDebug((object)$"[ChatPatch] playerId={playerId} msg={chatMessage}"); if (string.IsNullOrEmpty(chatMessage)) { return true; } if (!chatMessage.StartsWith(ModConfig.CommandPrefix.Value)) { return true; } if (!CommandRouter.IsSenderTheHost(playerId)) { return true; } string text = chatMessage.Substring(ModConfig.CommandPrefix.Value.Length).Trim(); bool flag = CommandRouter.Route(text); if (flag) { LobbyLog.Cmd(CommandRouter.CanonicalVerb(text.Split(' ')[0].ToLowerInvariant()) ?? "?", "host"); } return !flag; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ChatCommandPatch] {arg}"); return true; } } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class StartOfRoundStartPatch { [HarmonyPostfix] private static void Postfix() { try { LobbyGuard.Reset(); LobbyLog.SessionStart(); PlayerJoinLog.ResetSession(); LateJoin.ResetForNewSession(); LateJoin.StartWatch(); LateJoinSync.Register(); WhitelistPerks.Register(); ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(StartupLine()); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StartOfRoundStartPatch] {arg}"); } } private static IEnumerator StartupLine() { yield return (object)new WaitForSeconds(3f); SuiteAnnounce.MaybeAnnounce("local.simplelobby"); } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")] internal static class PlayerConnectedPatch { [HarmonyPostfix] private static void Postfix() { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost) { PlayerJoinLog.LogLobbyPlayers(); LobbyGuard.KickAnyBanned(); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[PlayerConnectedPatch] {arg}"); } } } [HarmonyPatch(typeof(RoundManager), "FinishGeneratingNewLevelClientRpc")] internal static class ShipLandedPatch { [HarmonyPostfix] private static void Postfix() { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost) { LateJoin.Refresh(); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ShipLandedPatch] {arg}"); } } } [HarmonyPatch(typeof(PlayerControllerB), "SendNewPlayerValuesClientRpc")] internal static class PlayerValuesJoinLogPatch { [HarmonyPostfix] private static void Postfix() { try { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost) { PlayerJoinLog.LogLobbyPlayers(); LobbyLog.NoteLobbyPlayers(); LobbyGuard.KickAnyBanned(); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[PlayerValuesJoinLogPatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "OnDestroy")] internal static class LobbyLogEndPatch { [HarmonyPrefix] private static void Prefix() { try { LobbyLog.SessionEnd("left the lobby"); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LobbyLogEndPatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "StartGame")] internal static class LobbyLogLandingPatch { [HarmonyPostfix] private static void Postfix() { try { if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsServer) { LobbyLog.Ship("landing", null); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LobbyLogLandingPatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "ShipHasLeft")] internal static class LobbyLogLeftPatch { [HarmonyPostfix] private static void Postfix() { try { if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsServer) { LobbyLog.Ship("left moon", null); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LobbyLogLeftPatch] {arg}"); } } } [HarmonyPatch] internal static class HostChatPatch { private static int _prefabLimit = int.MinValue; private static bool IsHost { get { if ((Object)(object)NetworkManager.Singleton != (Object)null) { return NetworkManager.Singleton.IsHost; } return false; } } [HarmonyPatch(typeof(HUDManager), "EnableChat_performed")] [HarmonyPostfix] private static void OpenChat(HUDManager __instance) { try { TMP_InputField chatTextField = __instance.chatTextField; if (!((Object)(object)chatTextField == (Object)null)) { if (_prefabLimit == int.MinValue) { _prefabLimit = chatTextField.characterLimit; } chatTextField.characterLimit = (IsHost ? ModConfig.HostChatLimit.Value : _prefabLimit); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[HostChatPatch.OpenChat] {arg}"); } } [HarmonyPatch(typeof(HUDManager), "SubmitChat_performed")] [HarmonyPrefix] [HarmonyPriority(800)] private static void Submit(HUDManager __instance) { try { if (!IsHost || (Object)(object)__instance.chatTextField == (Object)null) { return; } PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController; if ((Object)(object)val == (Object)null || !val.isTypingChat || !val.isHostPlayerObject || val.isPlayerDead) { return; } string text = __instance.chatTextField.text; if (!string.IsNullOrEmpty(text) && text.Length >= 50) { int value = ModConfig.HostChatLimit.Value; if (text.Length > value) { text = text.Substring(0, value); } __instance.chatTextField.text = ""; __instance.AddTextToChatOnServer(text, (int)val.playerClientId); string value2 = ModConfig.CommandPrefix.Value; if (!text.StartsWith(value2) && !text.StartsWith("!") && !text.StartsWith("/")) { ChatUtil.SendRaw("<color=#8a8a8a>(Only you see that: the game doesn't send chat over 50 characters.)</color>"); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[HostChatPatch.Submit] {arg}"); } } } [HarmonyPatch(typeof(GameNetworkManager), "ConnectionApproval")] internal static class JoinReservePatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void Prefix(ConnectionApprovalRequest request) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) try { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && singleton.IsServer && request.ClientNetworkId != singleton.LocalClientId) { JoinGuard.RepairStaleParents($"before client {request.ClientNetworkId} joins"); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JoinReservePatch.Prefix] {arg}"); } } [HarmonyPostfix] [HarmonyPriority(100)] private static void Postfix(GameNetworkManager __instance, ConnectionApprovalRequest request, ConnectionApprovalResponse response) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) JoinGuard.OnApproval(__instance, request, response); } } [HarmonyPatch(typeof(GameNetworkManager), "Singleton_OnClientConnectedCallback")] internal static class JoinConnectedCallbackPatch { [HarmonyPrefix] private static void Prefix(ulong clientId) { try { JoinGuard.OnConnectedCallback(clientId); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JoinConnectedCallbackPatch] {arg}"); } } } [HarmonyPatch(typeof(GameNetworkManager), "Singleton_OnClientDisconnectCallback")] internal static class JoinDisconnectCallbackPatch { [HarmonyPrefix] private static void Prefix(ulong clientId) { try { LobbyLog.Left(clientId); JoinGuard.OnDisconnectCallback(clientId); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JoinDisconnectCallbackPatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "OnClientConnect")] internal static class JoinSlotGuardPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(StartOfRound __instance, ulong clientId) { return JoinGuard.AllowConnect(__instance, clientId); } [HarmonyPostfix] private static void Postfix(ulong clientId) { try { JoinGuard.OnRegistered(clientId); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JoinSlotGuardPatch.Postfix] {arg}"); } } } [HarmonyPatch(typeof(NetworkConnectionManager), "HandleConnectionApproval")] internal static class JoinSyncFailurePatch { [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, ulong ownerClientId) { return JoinGuard.OnApprovalHandled(__exception, ownerClientId); } } [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class JoinGuardResetPatch { [HarmonyPostfix] private static void Postfix() { try { JoinGuard.ResetAll(); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[JoinGuardResetPatch] {arg}"); } } } [HarmonyPatch(typeof(GameNetworkManager), "ConnectionApproval")] internal static class LateJoinApprovalPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(GameNetworkManager __instance, out bool __state) { __state = false; try { if (LateJoin.WindowOpen() && __instance.gameHasStarted) { __state = true; __instance.gameHasStarted = false; Plugin.Logger.LogInfo((object)"[LateJoin] allowing a join attempt (in orbit)."); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinApprovalPatch.Prefix] {arg}"); } } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(GameNetworkManager __instance, bool __state, ConnectionApprovalResponse response) { try { if (__state) { __instance.gameHasStarted = true; } if (response != null && !response.Approved && response.Reason == "Game has already started!") { string text = LateJoin.RefusalReason(); if (text != null) { response.Reason = text; Plugin.Logger.LogInfo((object)("[LateJoin] refused a join: " + text)); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinApprovalPatch.Postfix] {arg}"); } } } [HarmonyPatch(typeof(GameNetworkManager), "LeaveLobbyAtGameStart")] internal static class LateJoinKeepLobbyPatch { [HarmonyPrefix] private static bool Prefix() { try { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsHost || !LateJoin.Enabled) { return true; } Plugin.Logger.LogInfo((object)"[LateJoin] keeping the Steam lobby alive past game start."); return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinKeepLobbyPatch] {arg}"); return true; } } } [HarmonyPatch(typeof(GameNetworkManager), "SetLobbyJoinable")] internal static class LateJoinJoinablePatch { [HarmonyPrefix] private static void Prefix(ref bool joinable) { try { if (!joinable && LateJoin.WindowOpen()) { joinable = true; } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinJoinablePatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")] internal static class LateJoinWeatherPatch { internal struct Snapshot { internal LevelWeatherType[] Levels; internal LevelWeatherType Current; } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(StartOfRound __instance, ulong clientId, out Snapshot __state) { //IL_007e: 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_00bb: Unknown result type (might be due to invalid IL or missing references) __state = default(Snapshot); try { if ((Object)(object)GameNetworkManager.Instance == (Object)null || !GameNetworkManager.Instance.gameHasStarted) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((!((Object)(object)singleton != (Object)null) || singleton.LocalClientId != clientId) && __instance.levels != null) { LevelWeatherType[] array = (LevelWeatherType[])(object)new LevelWeatherType[__instance.levels.Length]; for (int i = 0; i < array.Length; i++) { array[i] = (LevelWeatherType)(((Object)(object)__instance.levels[i] != (Object)null) ? ((int)__instance.levels[i].currentWeather) : (-1)); } __state = new Snapshot { Levels = array, Current = (LevelWeatherType)((!((Object)(object)TimeOfDay.Instance != (Object)null)) ? (-1) : ((int)TimeOfDay.Instance.currentLevelWeather)) }; } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinWeatherPatch.Prefix] {arg}"); } } [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(StartOfRound __instance, Snapshot __state) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Invalid comparison between Unknown and I4 //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) try { if (__state.Levels == null || __instance.levels == null) { return; } bool flag = false; int num = Math.Min(__state.Levels.Length, __instance.levels.Length); for (int i = 0; i < num; i++) { if (!((Object)(object)__instance.levels[i] == (Object)null) && (int)__instance.levels[i].currentWeather != (int)__state.Levels[i]) { __instance.levels[i].currentWeather = __state.Levels[i]; flag = true; } } if ((Object)(object)TimeOfDay.Instance != (Object)null && TimeOfDay.Instance.currentLevelWeather != __state.Current) { TimeOfDay.Instance.currentLevelWeather = __state.Current; flag = true; } if (flag) { Plugin.Logger.LogInfo((object)"[LateJoin] restored weather after a mid-run join (vanilla re-rolls it on connect)."); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinWeatherPatch.Postfix] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "StartGame")] internal static class LateJoinLandingPatch { [HarmonyPostfix] private static void Postfix() { LateJoin.Refresh(); } } [HarmonyPatch(typeof(StartOfRound), "SetShipReadyToLand")] internal static class LateJoinOrbitPatch { [HarmonyPostfix] private static void Postfix() { LateJoin.Refresh(); } } [HarmonyPatch(typeof(StartOfRound), "ShipLeave")] internal static class LateJoinDepartPatch { [HarmonyPostfix] private static void Postfix() { LateJoin.Refresh(); } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")] internal static class LateJoinSlotPatch { [HarmonyPostfix] private static void Postfix(StartOfRound __instance, int assignedPlayerObjectId) { try { if (__instance.inShipPhase && __instance.allPlayerScripts != null && assignedPlayerObjectId >= 0 && assignedPlayerObjectId < __instance.allPlayerScripts.Length) { PlayerControllerB val = __instance.allPlayerScripts[assignedPlayerObjectId]; if ((Object)(object)val != (Object)null && val.disconnectedMidGame) { val.disconnectedMidGame = false; Plugin.Logger.LogInfo((object)$"[LateJoin] cleared a stale 'disconnected mid game' mark on slot {assignedPlayerObjectId}."); } } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LateJoinSlotPatch] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "OnClientConnect")] internal static class LateJoinConnectPatch { [HarmonyPostfix] private static void Postfix(ulong clientId) { if (!JoinGuard.WasRefused(clientId)) { LateJoinSync.OnHostClientConnected(clientId); WhitelistPerks.OnHostClientConnected(clientId); } } } [HarmonyPatch(typeof(CustomMessagingManager), "InvokeNamedMessage")] internal static class NamedMessageGuardPatch { [HarmonyPrefix] private static bool Prefix(ulong sender, ulong hash, FastBufferReader reader) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { if (!NetGuard.AllowCustomMessage(sender, reader, "named message")) { return false; } if (LobbyGuard.IsSuiteMessage(hash)) { LobbyLog.Handshake(sender, "suite", suite: true); return true; } string text = KnownMods.Describe(hash); if (text != null) { LobbyLog.Handshake(sender, text, suite: false); } if (text != null && ScrapGuard.OnKnownInfoMod(sender, hash, text)) { return true; } LobbyGuard.OnModdedTraffic(sender, (text != null) ? ("named message: " + text) : "named message"); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[NamedMessageGuardPatch] {arg}"); } return true; } } [HarmonyPatch(typeof(CustomMessagingManager), "InvokeUnnamedMessage")] internal static class UnnamedMessageGuardPatch { [HarmonyPrefix] private static bool Prefix(ulong clientId, FastBufferReader reader) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { if (!NetGuard.AllowCustomMessage(clientId, reader, "unnamed message")) { return false; } LobbyGuard.OnModdedTraffic(clientId, "unnamed message"); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[UnnamedMessageGuardPatch] {arg}"); } return true; } } [HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyCreated")] internal static class LobbyCreatedPatch { [HarmonyPostfix] private static void Postfix(Result result, Lobby lobby) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) try { if ((int)result == 1 && ModConfig.HideLobbyModTag.Value) { LobbyTagFeature.Apply(hide: true, lobby); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LobbyCreatedPatch] {arg}"); } } } internal static class StandDownNotice { internal static void Postfix() { try { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton == (Object)null) && singleton.IsHost) { ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(Say()); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[StandDownNotice] {arg}"); } } private static IEnumerator Say() { yield return (object)new WaitForSeconds(4f); ChatUtil.Send("⚠ SimpleLobby is off: this SimpleSell is older than 0.1.40 and still runs its own lobby features. Update SimpleSell."); } } [HarmonyPatch(typeof(StartMatchLever), "Update")] internal static class TrustedLeverPatch { [HarmonyPostfix] private static void Postfix(StartMatchLever __instance) { try { WhitelistPerks.LeverUpdate(__instance); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TrustedLeverPatch] {arg}"); } } } [HarmonyPatch(typeof(StartMatchLever), "StartGame")] internal static class TrustedLeverStartPatch { [HarmonyPrefix] private static void Prefix(StartMatchLever __instance) { try { WhitelistPerks.BeforeClientStartGame(__instance); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TrustedLeverStartPatch] {arg}"); } } } [HarmonyPatch] internal static class EarlyEquipmentGrabPatch { private static readonly FieldInfo Flag = AccessTools.Field(typeof(Item), "canBeGrabbedBeforeGameStart"); private static readonly MethodInfo Check = AccessTools.Method(typeof(WhitelistPerks), "CanGrabBeforeStart", (Type[])null, (Type[])null); private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(PlayerControllerB), "BeginGrabObject", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(PlayerControllerB), "SetHoverTipAndCurrentInteractTrigger", (Type[])null, (Type[])null); } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { int n = 0; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.LoadsField(instruction, Flag, false)) { CodeInstruction val = new CodeInstruction(OpCodes.Call, (object)Check); val.labels.AddRange(instruction.labels); val.blocks.AddRange(instruction.blocks); n++; yield return val; } else { yield return instruction; } } if (n == 0) { Plugin.Logger.LogWarning((object)("[EarlyEquipmentGrabPatch] no canBeGrabbedBeforeGameStart read in " + original?.Name + "; pre-start equipment allowance inactive there.")); } else { Plugin.Logger.LogDebug((object)$"[EarlyEquipmentGrabPatch] {original?.Name}: {n} read(s) redirected."); } } } } namespace SimpleLobby.Features { internal static class CrewListFix { [HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyMemberJoined")] private static class LobbyMemberJoinedPatch { [HarmonyPostfix] private static void Postfix() { Reconcile(); } } [HarmonyPatch(typeof(GameNetworkManager), "SteamMatchmaking_OnLobbyMemberLeave")] private static class LobbyMemberLeavePatch { [HarmonyPostfix] private static void Postfix() { Reconcile(); } } [HarmonyPatch(typeof(GameNetworkManager), "Singleton_OnClientDisconnectCallback")] private static class ClientDisconnectPatch { [HarmonyPostfix] private static void Postfix() { Reconcile(); } } internal static void Reconcile() { try { if (!ModConfig.FixCrewList.Value) { return; } StartOfRound instance = StartOfRound.Instance; if (instance?.allPlayerScripts == null || instance.ClientPlayerList == null) { return; } QuickMenuManager val = Object.FindObjectOfType<QuickMenuManager>(); if (val?.playerListSlots == null) { return; } HashSet<int> hashSet = new HashSet<int>(instance.ClientPlayerList.Values); int num = Mathf.Min(val.playerListSlots.Length, instance.allPlayerScripts.Length); int num2 = 0; int num3 = 0; for (int i = 0; i < num; i++) { PlayerListSlot val2 = val.playerListSlots[i]; if (val2 == null) { continue; } PlayerControllerB val3 = instance.allPlayerScripts[i]; if (hashSet.Contains(i) && (Object)(object)val3 != (Object)null && val3.playerSteamId != 0L) { if (!val2.isConnected || val2.playerSteamId != val3.playerSteamId) { val.AddUserToPlayerList(val3.playerSteamId, val3.playerUsername, i); num2++; } } else if (val2.isConnected || ((Object)(object)val2.slotContainer != (Object)null && val2.slotContainer.activeSelf)) { val.RemoveUserFromPlayerList(i); num3++; } } if (num2 + num3 > 0) { Plugin.Logger.LogInfo((object)$"[CrewList] tidied the pause menu list: {num2} restored, {num3} leftover name(s) removed."); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[CrewListFix.Reconcile] {arg}"); } } } internal static class Gaps012 { internal static int Install(Harmony h) { int num = 0; num += RpcFirewall.Guard(h, typeof(Terminal), "SetTerminalInUseServerRpc", typeof(TerminalInUseGuard)); num += RpcFirewall.Guard(h, typeof(Terminal), "SyncTerminalValuesServerRpc", typeof(TerminalValuesGuard)); num += RpcFirewall.Guard(h, typeof(Terminal), "PlayTerminalAudioServerRpc", typeof(TerminalAudioGuard)); num += RpcFirewall.Guard(h, typeof(RoundManager), "DespawnEnemyServerRpc", typeof(DespawnEnemyGuard)); num += RpcFirewall.Guard(h, typeof(PlayerControllerB), "DespawnHeldObjectServerRpc", typeof(DespawnHeldGuard)); num += RpcFirewall.Guard(h, typeof(PlayerControllerB), "DestroyItemInSlotServerRpc", typeof(DestroySlotGuard)); num += RpcFirewall.Guard(h, typeof(DepositItemsDesk), "CheckAllPlayersSoldItemsServerRpc", typeof(SoldItemsGuard)); num += RpcFirewall.Guard(h, typeof(StartOfRound), "SetDoorsClosedServerRpc", typeof(DoorsGuard)); num += RpcFirewall.Guard(h, typeof(StartOfRound), "SetMagnetOnServerRpc", typeof(MagnetGuard)); num += RpcFirewall.Guard(h, typeof(StartOfRound), "SyncShipUnlockablesServerRpc", typeof(SyncUnlockablesGuard)); num += RpcFirewall.Guard(h, typeof(StartOfRound), "SyncSuitsServerRpc", typeof(SyncSuitsGuard)); num += RpcFirewall.Guard(h, typeof(ShipLights), "SetShipLightsServerRpc", typeof(LightsGuard)); num += RpcFirewall.Guard(h, typeof(ShipTeleporter), "PressTeleportButtonServerRpc", typeof(TeleporterGuard)); return num + RpcFirewall.Guard(h, typeof(HUDManager), "AlarmHornServerRpc", typeof(AlarmGuard)); } internal static bool TryGet(NetworkObjectReference r, out NetworkObject no) { no = null; try { return ((NetworkObjectReference)(ref r)).TryGet(ref no, (NetworkManager)null) && (Object)(object)no != (Object)null; } catch { return false; } } internal static bool IsTerminal(NetworkObjectReference r) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!TryGet(r, out var no)) { return false; } if ((Object)(object)((Component)no).GetComponentInChildren<Terminal>(true) != (Object)null) { return true; } Terminal val = Object.FindObjectOfType<Terminal>(); if ((Object)(object)val != (Object)null && ((Component)val).transform.IsChildOf(((Component)no).transform)) { return true; } PlaceableShipObject componentInChildren = ((Component)no).GetComponentInChildren<PlaceableShipObject>(true); List<UnlockableItem> list = StartOfRound.Instance?.unlockablesList?.unlockables; if ((Object)(object)componentInChildren != (Object)null && list != null && componentInChildren.unlockableID >= 0 && componentInChildren.unlockableID < list.Count) { return string.Equals(list[componentInChildren.unlockableID].unlockableName, "Terminal", StringComparison.OrdinalIgnoreCase); } return false; } internal static string UnstorableName(NetworkObjectReference r) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!TryGet(r, out var no)) { return null; } PlaceableShipObject componentInChildren = ((Component)no).GetComponentInChildren<PlaceableShipObject>(true); List<UnlockableItem> list = StartOfRound.Instance?.unlockablesList?.unlockables; if (IsTerminal(r)) { return "the terminal"; } if ((Object)(object)componentInChildren == (Object)null || list == null || componentInChildren.unlockableID < 0 || componentInChildren.unlockableID >= list.Count) { return null; } if (!list[componentInChildren.unlockableID].canBeStored) { return list[componentInChildren.unlockableID].unlockableName; } return null; } internal static bool InsideShipRoomBy(Vector3 pos, float margin) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: 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) Collider val = StartOfRound.Instance?.shipInnerRoomBounds; if ((Object)(object)val == (Object)null) { return true; } Bounds bounds = val.bounds; ((Bounds)(ref bounds)).Expand(margin * 2f); return ((Bounds)(ref bounds)).Contains(pos); } internal static bool Decide(ulong s, PlayerControllerB p, string rpc, string why) { if (why == null) { return true; } RpcFirewall.Refuse(s, p, rpc, why); return false; } internal static string Basic(ulong s, PlayerControllerB p, string key, int max, float window, bool needInShip) { if ((Object)(object)p == (Object)null) { return "sender is not a registered player yet"; } if (RpcFirewall.OverRate(s, key, max, window)) { return $"rate limit {max} / {window:0} s"; } if (p.isPlayerDead) { return "sender is dead"; } if (needInShip && !RpcFirewall.InShip(p)) { return "sender is not in the ship"; } return null; } } internal static class TerminalInUseGuard { internal static bool Prefix(Terminal __instance, bool inUse) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string why = (((Object)(object)player == (Object)null) ? "sender is not a registered player yet" : (RpcFirewall.OverRate(sender, "term-inuse", 8, 5f) ? "rate limit 8 / 5 s" : ((!inUse) ? null : (player.isPlayerDead ? "sender is dead" : ((RpcFirewall.TerminalDist(player, __instance) > ModConfig.TerminalRange.Value) ? "sender is not at the terminal" : null))))); return Gaps012.Decide(sender, player, "SetTerminalInUseServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TerminalInUseGuard] {arg}"); return true; } } } internal static class TerminalValuesGuard { internal static bool Prefix() { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string why = (((Object)(object)player == (Object)null) ? "sender is not a registered player yet" : (RpcFirewall.OverRate(sender, "term-values", 3, 60f) ? "rate limit 3 / 60 s (vanilla sends it once a session)" : null)); return Gaps012.Decide(sender, player, "SyncTerminalValuesServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TerminalValuesGuard] {arg}"); return true; } } } internal static class TerminalAudioGuard { internal static bool Prefix(Terminal __instance, int clipIndex) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } AudioClip[] syncedAudios = __instance.syncedAudios; int num = ((syncedAudios != null) ? syncedAudios.Length : 0); string why = (((Object)(object)player == (Object)null) ? "sender is not a registered player yet" : ((clipIndex < 0 || clipIndex >= num) ? $"no terminal sound #{clipIndex} (has {num})" : (RpcFirewall.OverRate(sender, "term-audio", 8, 5f) ? "rate limit 8 / 5 s" : ((RpcFirewall.TerminalDist(player, __instance) > ModConfig.TerminalRange.Value) ? "sender is not at the terminal" : null)))); return Gaps012.Decide(sender, player, "PlayTerminalAudioServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TerminalAudioGuard] {arg}"); return true; } } } internal static class DespawnEnemyGuard { internal static bool Prefix(NetworkObjectReference enemyNetworkObject) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } NetworkObject no; string text = (Gaps012.TryGet(enemyNetworkObject, out no) ? ((Object)no).name : "unknown object"); if ((Object)(object)player != (Object)null && RpcFirewall.OverRate(sender, "despawn-enemy", 3, 10f)) { RpcFirewall.Forged(sender, player, "DespawnEnemyServerRpc", "mass enemy despawn (" + text + ")"); } else { RpcFirewall.Refuse(sender, player, "DespawnEnemyServerRpc", "clients can't despawn enemies (" + text + ")"); } return false; } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DespawnEnemyGuard] {arg}"); return true; } } } internal static class DespawnHeldGuard { internal static bool Prefix(PlayerControllerB __instance) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } GrabbableObject currentlyHeldObjectServer = __instance.currentlyHeldObjectServer; string why = (RpcFirewall.OverRate(sender, "despawn-held", 4, 10f) ? "rate limit 4 / 10 s" : (((Object)(object)currentlyHeldObjectServer != (Object)null && !(currentlyHeldObjectServer is KeyItem)) ? ("tried to delete " + ItemFirewall.Name(currentlyHeldObjectServer) + " (only a used key is removed this way)") : null)); return Gaps012.Decide(sender, player ?? __instance, "DespawnHeldObjectServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DespawnHeldGuard] {arg}"); return true; } } } internal static class DestroySlotGuard { internal static bool Prefix(PlayerControllerB __instance, int itemSlot) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } GrabbableObject val = ((itemSlot == 50) ? __instance.ItemOnlySlot : ((__instance.ItemSlots != null && itemSlot >= 0 && itemSlot < __instance.ItemSlots.Length) ? __instance.ItemSlots[itemSlot] : null)); string why = (RpcFirewall.OverRate(sender, "destroy-slot", 6, 10f) ? "rate limit 6 / 10 s" : (((Object)(object)val != (Object)null && !(val is GunAmmo)) ? $"tried to destroy {ItemFirewall.Name(val)} in slot {itemSlot} (only shotgun shells are used up this way)" : null)); return Gaps012.Decide(sender, player ?? __instance, "DestroyItemInSlotServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DestroySlotGuard] {arg}"); return true; } } } internal static class SoldItemsGuard { private static readonly HashSet<ulong> _thisSale = new HashSet<ulong>(); internal static bool Prefix(DepositItemsDesk __instance) { try { if (__instance.clientsRecievedSellItemsRPC == 0) { _thisSale.Clear(); } if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string why = (_thisSale.Add(sender) ? null : "already confirmed this sale"); return Gaps012.Decide(sender, player, "CheckAllPlayersSoldItemsServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SoldItemsGuard] {arg}"); return true; } } } internal static class DoorsGuard { internal static bool Prefix(bool closed) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string text = Gaps012.Basic(sender, player, "doors", 12, 5f, needInShip: true); if (text == null) { LobbyLog.Ship(closed ? "doors closed" : "doors opened", sender); } return Gaps012.Decide(sender, player, "SetDoorsClosedServerRpc", text); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[DoorsGuard] {arg}"); return true; } } } internal static class MagnetGuard { internal static bool Prefix(bool on) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string text = Gaps012.Basic(sender, player, "magnet", 6, 5f, needInShip: true); if (text == null) { LobbyLog.Ship(on ? "magnet on" : "magnet off", sender); } return Gaps012.Decide(sender, player, "SetMagnetOnServerRpc", text); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[MagnetGuard] {arg}"); return true; } } } internal static class LightsGuard { internal static bool Prefix(bool setLightsOn) { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string text = Gaps012.Basic(sender, player, "lights", 15, 5f, needInShip: true); if (text == null) { LobbyLog.Ship(setLightsOn ? "lights on" : "lights off", sender); } return Gaps012.Decide(sender, player, "SetShipLightsServerRpc", text); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[LightsGuard] {arg}"); return true; } } } internal static class TeleporterGuard { internal static bool Prefix() { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string text = Gaps012.Basic(sender, player, "teleporter", 4, 5f, needInShip: true); if (text == null) { LobbyLog.Ship("teleporter", sender); } return Gaps012.Decide(sender, player, "PressTeleportButtonServerRpc", text); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[TeleporterGuard] {arg}"); return true; } } } internal static class AlarmGuard { internal static bool Prefix() { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string why = Gaps012.Basic(sender, player, "alarm", 4, 3f, needInShip: true); return Gaps012.Decide(sender, player, "AlarmHornServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[AlarmGuard] {arg}"); return true; } } } internal static class SyncUnlockablesGuard { internal static bool Prefix() { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string why = (RpcFirewall.OverRate(sender, "sync-unlockables", 3, 30f) ? "rate limit 3 / 30 s (vanilla sends it once, when joining)" : null); return Gaps012.Decide(sender, player, "SyncShipUnlockablesServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SyncUnlockablesGuard] {arg}"); return true; } } } internal static class SyncSuitsGuard { internal static bool Prefix() { try { if (!RpcFirewall.Remote(out var sender, out var player)) { return true; } string why = (RpcFirewall.OverRate(sender, "sync-suits", 3, 30f) ? "rate limit 3 / 30 s" : null); return Gaps012.Decide(sender, player, "SyncSuitsServerRpc", why); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[SyncSuitsGuard] {arg}"); return true; } } } internal static class InvisibleJoinerFix { internal static void Restore(int slot, string why) { try { if (ModConfig.FixInvisibleJoiners == null || !ModConfig.FixInvisibleJoiners.Value) { return; } StartOfRound instance = StartOfRound.Instance; if (instance?.allPlayerScripts == null || slot < 0 || slot >= instance.allPlayerScripts.Length) { return; } PlayerControllerB val = instance.allPlayerScripts[slot]; if ((Object)(object)val == (Object)null || val.isPlayerDead || (Object)(object)val == (Object)(object)GameNetworkManager.Instance?.localPlayerController) { return; } int num = 0; SkinnedMeshRenderer[] componentsInChildren = ((Component)val).gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true); foreach (SkinnedMeshRenderer val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2 == (Object)(object)val.thisPlayerModelArms) && !((Renderer)val2).enabled) { ((Renderer)val2).enabled = true; num++; } } if ((Object)(object)val.thisPlayerModelArms != (Object)null) { ((Renderer)val.thisPlayerModelArms).enabled = false; } if (num > 0) { Plugin.Logger.LogInfo((object)($"[LateJoin] slot {slot}: body was still hidden from a player who left while dead; " + $"showed {num} renderer(s) again ({why}).")); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[InvisibleJoinerFix] {arg}"); } } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerDC")] internal static class InvisibleJoinerDcPatch { [HarmonyPostfix] private static void Postfix(int playerObjectNumber) { StartOfRound instance = StartOfRound.Instance; if (instance?.allPlayerScripts != null && playerObjectNumber >= 0 && playerObjectNumber < instance.allPlayerScripts.Length) { PlayerControllerB val = instance.allPlayerScripts[playerObjectNumber]; if ((Object)(object)val != (Object)null && !val.isPlayerControlled) { InvisibleJoinerFix.Restore(playerObjectNumber, "slot freed"); } } } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")] internal static class InvisibleJoinerConnectPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(int assignedPlayerObjectId) { InvisibleJoinerFix.Restore(assignedPlayerObjectId, "slot taken"); } } internal static class ItemFirewall { internal const string DeskWarning = "Your sell mod's teleporting won't work here: carry items to the desk, or ask the host to /sell."; internal const string ItemWarning = "Your item mod's moves are blocked in this lobby: only items in your hands can be dropped or placed."; private static readonly HashSet<GrabbableObject> _pending = new HashSet<GrabbableObject>(); private static bool _flushQueued; internal static bool On => ModConfig.ItemFirewall.Value; internal static int Install(Harmony harmony) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown int num = 0; num += RpcFirewall.Guard(harmony, typeof(PlayerControllerB), "GrabObjectServerRpc", typeof(GrabGuard)); num += RpcFirewall.Guard(harmony, typeof(PlayerControllerB), "ThrowObjectServerRpc", typeof(ThrowGuard)); num += RpcFirewall.Guard(harmony, typeof(PlayerControllerB), "PlaceObjectServerRpc", typeof(PlaceGuard)); num += RpcFirewall.Guard(harmony, typeof(DepositItemsDesk), "AddObjectToDeskServerRpc", typeof(DeskAddGuard)); num += RpcFirewall.Guard(harmony, typeof(BeltBagItem), "TryAddObjectToBagServerRpc", typeof(BagAddGuard)); num += RpcFirewall.Guard(harmony, typeof(BeltBagItem), "RemoveFromBagServerRpc", typeof(BagRemoveGuard)); num += RpcFirewall.Guard(harmony, typeof(BeltBagItem), "RemoveFromBagNonElevatorParentServerRpc", typeof(BagRemoveParentGuard)); try { harmony.Patch((MethodBase)AccessTools.Method(typeof(StartOfRound), "Update", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(BagRule), "Tick", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { Plugin.Logger.LogError((object)("[ItemFirewall] belt bag clean-up NOT installed: " + ex.Message)); } try { harmony.Patch((MethodBase)AccessTools.Method(typeof(DepositItemsDesk), "Update", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(DeskJanitor), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex2) { Plugin.Logger.LogError((object)("[ItemFirewall] desk janitor NOT installed: " + ex2.Message)); } return num; } internal static void Reset() { DeskJanitor.Reset(); _pending.Clear(); } internal static GrabbableObject Item(NetworkObjectReference r) { NetworkObject val = default(NetworkObject); if (!((NetworkObjectReference)(ref r)).TryGet(ref val, (NetworkManager)null) || !((Object)(object)val != (Object)null)) { return null; } return ((Component)val).GetComponentInChildren<GrabbableObject>(); } internal static string Name(GrabbableObject o) { if (!((Object)(object)o == (Object)null)) { if (!((Object)(object)o.itemProperties != (Object)null)) { return ((Object)o).name; } return o.itemProperties.itemName; } return "?"; } internal static bool Possesses(PlayerControllerB p, GrabbableObject o) { if ((Object)(object)p == (Object)null || (Object)(object)o == (Object)null) { return false; } if ((Object)(object)o.playerHeldBy == (Object)(object)p || (Object)(object)p.currentlyHeldObjectServer == (Object)(object)o || (Object)(object)p.ItemOnlySlot == (Object)(object)o) { return true; } if (p.ItemSlots != null) { GrabbableObject[] itemSlots = p.ItemSlots; foreach (GrabbableObject val in itemSlots) { if ((Object)(object)val == (Object)(object)o) { return true; } } } return false; } internal static float Dist(PlayerControllerB p, Vector3 pos) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)p == (Object)null)) { return Vector3.Distance(((Component)p).transform.position, pos); } return float.MaxValue; } internal static float Flat(Vector3 a, Vector3 b) { //IL_0000: 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_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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) return Vector2.Distance(new Vector2(a.x, a.z), new Vector2(b.x, b.z)); } internal static float Slack(GrabbableObject o) { if (!(o is RagdollGrabbableObject)) { return 1f; } return 3f; } internal static Transform PlaceParent(Transform parent) { if ((Object)(object)parent == (Object)null) { return null; } PlayerPhysicsRegion componentInChildren = ((Component)parent).GetComponentInChildren<PlayerPhysicsRegion>(); if (!((Object)(object)componentInChildren != (Object)null) || !componentInChildren.allowDroppingItems || !((Object)(object)componentInChildren.physicsTransform != (Object)null)) { return parent; } return componentInChildren.physicsTransform; } internal static bool IsDeskPart(DepositItemsDesk desk, Transform t) { if ((Object)(object)desk != (Object)null && (Object)(object)t != (Object)null) { if (!t.IsChildOf(((Component)desk).transform)) { if ((Object)(object)desk.deskObjectsContainer != (Object)null) { return t.IsChildOf(((Component)desk.deskObjectsContainer).transform); } return false; } return true; } return false; } internal static void NotHeld(ulong s, PlayerControllerB p, string rpc, GrabbableObject o) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) float num = Dist(p, ((Component)o).transform.position); float num2 = ModConfig.ItemForgedDistance.Value * Slack(o); if (num > num2) { RpcFirewall.Forged(s, p, rpc, $"moved {Name(o)}, which they aren't holding, from {num:F0} m away"); return; } RpcFirewall.Refuse(s, p, rpc, $"{Name(o)} isn't in their hands ({num:F0} m away)"); Strikes.WarnOnce(s, p, "items", "Your item mod's moves are blocked in this lobby: only items in your hands can be dropped or placed."); } internal static void DropAtFeet(PlayerControllerB p, GrabbableObject o) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) try { StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)p == (Object)null) && !((Object)(object)((o != null) ? ((NetworkBehaviour)o).NetworkObject : null) == (Object)null) && !((Object)(object)instance == (Object)null)) { bool isInElevator = p.isInElevator; Transform val = (isInElevator ? instance.elevatorTransform : instance.propsContainer); Vector3 val2 = val.InverseTransformPoint(ItemPlacer.SnapToFloor(((Component)p).transform.position)); RpcFirewall.ToSendStage((NetworkBehaviour)(object)p); p.ThrowObjectClientRpc(isInElevator, p.isInHangarShipRoom, val2, NetworkObjectReference.op_Implicit(((NetworkBehaviour)o).NetworkObject), -1); QueueResync(o); Plugin.Logger.LogInfo((object)("[ItemFirewall] " + Name(o) + " dropped at " + p.playerUsername + "'s feet instead.")); } } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ItemFirewall.DropAtFeet] {arg}"); } } internal static void QueueResync(GrabbableObject o) { if (!((Object)(object)o == (Object)null)) { _pending.Add(o); if (!_flushQueued) { _flushQueued = true; ((MonoBehaviour)CoroutineRunner.Instance).StartCoroutine(Flush()); } } } private static IEnumerator Flush() { yield return (object)new WaitForSeconds(0.3f); _flushQueued = false; List<GrabbableObject> list = _pending.ToList(); _pending.Clear(); int n = 0; foreach (GrabbableObject item in list) { try { ResyncInPlace(item); } catch (Exception arg) { Plugin.Logger.LogError((object)$"[ItemFirewall.Resync] {arg}"); } int num = n + 1; n = num; if (num % 15 == 0) { yield return null; } } } internal static void ResyncInPlace(GrabbableObject o) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (mig