Decompiled source of BadScoutWatcher v1.1.0
BadScoutWatcher.dll
Decompiled 15 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.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BadScoutWatcher; using BepInEx; using BepInEx.Configuration; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } [HarmonyPatch(typeof(Player), "AddItem")] public static class AntiCheatInventoryAddPatch { [HarmonyPostfix] public static void Postfix(Player __instance, bool __result, ItemInstanceData __1, ref ItemSlot __2) { BadScoutWatcherPlugin.MonitorInventoryAdd(__instance, __result, __1, __2); } } [HarmonyPatch(typeof(Player), "SyncInventoryRPC")] public static class AntiCheatInventorySyncPatch { [HarmonyPrefix] public static void Prefix(Player __instance, ref HashSet<Guid> __state) { __state = BadScoutWatcherPlugin.CaptureInventoryItemGuids(__instance); } [HarmonyPostfix] public static void Postfix(Player __instance, HashSet<Guid> __state) { BadScoutWatcherPlugin.MonitorInventorySync(__instance, __state); } } [HarmonyPatch(typeof(Item), "SetItemInstanceDataRPC")] public static class AntiCheatItemOriginPatch { [HarmonyPostfix] public static void Postfix(Item __instance, ItemInstanceData __0) { BadScoutWatcherPlugin.MonitorItemInstanceData(__instance, __0); } } [HarmonyPatch] public static class AntiCheatItemSpawnPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(PhotonNetwork), "NetworkInstantiate", new Type[2] { typeof(Hashtable), typeof(Player) }, (Type[])null); } [HarmonyPostfix] public static void Postfix(Hashtable __0, Player __1, GameObject __result) { BadScoutWatcherPlugin.MonitorNetworkInstantiation(__1, __result); } } public struct AntiCheatDropSlotState { public bool HadItem; public ItemInstanceData? ItemData; } [HarmonyPatch(typeof(CharacterItems), "DropItemRpc")] public static class AntiCheatDropItemRpcPatch { [HarmonyPrefix] public static void Prefix(CharacterItems __instance, ref bool __state) { __state = BadScoutWatcherPlugin.HasItemToDrop(__instance); } [HarmonyPostfix] public static void Postfix(CharacterItems __instance, bool __state, ItemInstanceData __5) { BadScoutWatcherPlugin.MonitorDropFromCharacterItems(__instance, __state, __5); } } [HarmonyPatch(typeof(CharacterItems), "DropItemFromSlotRPC")] public static class AntiCheatDropItemFromSlotPatch { [HarmonyPrefix] public static void Prefix(CharacterItems __instance, byte __0, ref AntiCheatDropSlotState __state) { __state.HadItem = BadScoutWatcherPlugin.HasItemInDropSlot(__instance, __0); __state.ItemData = BadScoutWatcherPlugin.GetItemDataInDropSlot(__instance, __0); } [HarmonyPostfix] public static void Postfix(CharacterItems __instance, AntiCheatDropSlotState __state) { BadScoutWatcherPlugin.MonitorDropFromCharacterItems(__instance, __state.HadItem, __state.ItemData); } } [HarmonyPatch] public static class AntiCheatLandingInjuryPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(CharacterAfflictions), "AddStatus", (Type[])null, (Type[])null); } [HarmonyPostfix] public static void Postfix(CharacterAfflictions __instance, object[] __args, bool __result) { try { if (!__result || (Object)(object)__instance == (Object)null || __args == null || __args.Length < 2 || Convert.ToInt32(__args[0]) != 0 || Convert.ToSingle(__args[1]) <= 0.001f) { return; } object obj = AccessTools.Field(typeof(CharacterAfflictions), "character")?.GetValue(__instance); Character val = (Character)((obj is Character) ? obj : null); if ((Object)(object)val == (Object)null) { return; } Player val2 = (val.IsLocal ? PhotonNetwork.LocalPlayer : (((Object)(object)((MonoBehaviourPun)val).photonView != (Object)null) ? ((MonoBehaviourPun)val).photonView.Owner : null)); if (val2 != null && val2.ActorNumber > 0) { int actorNumber = val2.ActorNumber; float time = Time.time; if (!BadScoutWatcherPlugin.Players.TryGetValue(actorNumber, out BadScoutWatcherPlugin.PlayerData value) || value == null) { value = new BadScoutWatcherPlugin.PlayerData { PlayerName = ((!string.IsNullOrEmpty(val2.NickName)) ? val2.NickName : $"Scout #{actorNumber}"), PlayerId = ((!string.IsNullOrEmpty(val2.UserId)) ? val2.UserId : "0"), ActorNumber = actorNumber, NetPlayer = val2, LastDecayTime = time, LastCheckTime = time }; BadScoutWatcherPlugin.Players[actorNumber] = value; } value.InjuryEventCount++; if (value.AirborneStartedAt >= 0f) { value.AirborneInjuryObserved = true; value.FlingAirborneSuspected = false; } if (value.IsAirborneForLanding || value.LandingCheckPending) { value.LandingDamageObserved = true; } } } catch { } } } [HarmonyPatch] public static class AntiCheatRpcShieldPatch { private sealed class RpcRateWindow { public readonly Queue<float> AcceptedCalls = new Queue<float>(); public float NextWarningAt; } private const int MaxRpcCallsPerSecond = 60; private const float RpcRateWindowSeconds = 1f; private const float BlockLogCooldownSeconds = 5f; private static readonly Dictionary<int, RpcRateWindow> RateWindows = new Dictionary<int, RpcRateWindow>(); private static readonly Dictionary<int, float> NextBlockLogAt = new Dictionary<int, float>(); private static MethodBase TargetMethod() { return AccessTools.Method(typeof(PhotonNetwork), "ExecuteRpc", new Type[2] { typeof(Hashtable), typeof(Player) }, (Type[])null); } [HarmonyPrefix] public static bool Prefix(Hashtable? __0, Player? __1, ref int __state) { __state = BadScoutWatcherPlugin.CurrentRpcSenderActorNumber; BadScoutWatcherPlugin.CurrentRpcSenderActorNumber = ((__1 != null) ? __1.ActorNumber : 0); if (BadScoutWatcherPlugin.ConfigShieldEnabled == null || !BadScoutWatcherPlugin.ConfigShieldEnabled.Value) { return true; } try { if (__1 == null) { return Reject(0, "unknown sender", "missing sender"); } if (__1.IsLocal) { return true; } int actorNumber = __1.ActorNumber; string senderName = ((!string.IsNullOrEmpty(__1.NickName)) ? __1.NickName : $"actor {actorNumber}"); if (actorNumber <= 0) { return Reject(actorNumber, senderName, "invalid actor number"); } if (__0 == null) { return Reject(actorNumber, senderName, "missing payload"); } if (!__0.ContainsKey((byte)0) || !(__0[(byte)0] is int num) || num <= 0) { return Reject(actorNumber, senderName, "invalid view id"); } if (__0.ContainsKey((byte)1) && !(__0[(byte)1] is short)) { return Reject(actorNumber, senderName, "invalid view prefix"); } if (__0.ContainsKey((byte)5)) { if (!(__0[(byte)5] is byte)) { return Reject(actorNumber, senderName, "invalid RPC method index"); } } else if (!__0.ContainsKey((byte)3) || !(__0[(byte)3] is string { Length: not 0 })) { return Reject(actorNumber, senderName, "missing RPC method name"); } if (__0.ContainsKey((byte)4) && __0[(byte)4] != null && !(__0[(byte)4] is object[])) { return Reject(actorNumber, senderName, "invalid RPC argument list"); } return AllowWithinRate(actorNumber, senderName); } catch (Exception ex) { Debug.LogWarning((object)("[BadScoutWatcher] RPC Shield inspection skipped after an error: " + ex.Message)); return true; } } [HarmonyFinalizer] public static Exception? Finalizer(Exception? __exception, int __state) { BadScoutWatcherPlugin.CurrentRpcSenderActorNumber = __state; return __exception; } private static bool AllowWithinRate(int actorNumber, string senderName) { float unscaledTime = Time.unscaledTime; if (!RateWindows.TryGetValue(actorNumber, out RpcRateWindow value) || value == null) { value = new RpcRateWindow(); RateWindows[actorNumber] = value; } while (value.AcceptedCalls.Count > 0 && unscaledTime - value.AcceptedCalls.Peek() >= 1f) { value.AcceptedCalls.Dequeue(); } if (value.AcceptedCalls.Count >= 60) { if (unscaledTime >= value.NextWarningAt) { Debug.LogWarning((object)$"[BadScoutWatcher] RPC Shield observed a high RPC rate from {senderName} (actor {actorNumber}); valid game RPCs are still being allowed."); value.NextWarningAt = unscaledTime + 5f; } return true; } value.AcceptedCalls.Enqueue(unscaledTime); return true; } private static bool Reject(int actorNumber, string senderName, string reason) { float unscaledTime = Time.unscaledTime; if (!NextBlockLogAt.TryGetValue(actorNumber, out var value) || unscaledTime >= value) { Debug.LogWarning((object)$"[BadScoutWatcher] RPC Shield blocked malformed incoming RPC from {senderName} (actor {actorNumber}): {reason}."); NextBlockLogAt[actorNumber] = unscaledTime + 5f; } return false; } public static void ForgetActor(int actorNumber) { RateWindows.Remove(actorNumber); NextBlockLogAt.Remove(actorNumber); } } namespace BadScoutWatcher { [BepInPlugin("com.custom.peak.badscoutwatcher", "BadScoutWatcher", "1.1.0")] public class BadScoutWatcherPlugin : BaseUnityPlugin, IInRoomCallbacks { private sealed class ItemOriginRecord { public int ActorNumber; public float LastSeenAt; } private sealed class AntiFlingCollisionPair { public Collider? Local; public Collider? Remote; } private sealed class AntiFlingCollisionState { public readonly List<AntiFlingCollisionPair> Pairs = new List<AntiFlingCollisionPair>(); public readonly HashSet<long> PairKeys = new HashSet<long>(); } public class Offense { public string Detector { get; set; } = string.Empty; public string Severity { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Timestamp { get; set; } = string.Empty; public int Points { get; set; } } public class PlayerData { public int ActorNumber = -1; public int ThreatScore = 0; public List<Offense> Offenses = new List<Offense>(); public bool ShowEvidenceHistory; public bool IsTrusted = false; public bool IsMuted = false; public Vector3 LastPosition; public float LastCheckTime = 0f; public float AirborneTimer = 0f; public float StationaryUngroundedTimer = 0f; public float AirborneStartedAt = -1f; public bool AirborneFlagged = false; public bool AirborneInjuryObserved = false; public float AirborneStartY = float.NaN; public float AirbornePeakY = float.NegativeInfinity; public Vector3 LastGroundedPosition; public bool HasLastGroundedPosition = false; public Vector3 LastObservedPosition; public bool HasLastObservedPosition = false; public bool FlingAirborneSuspected = false; public bool AirborneSnapbackActive = false; public bool AirborneSnapbackApplied = false; public bool AirborneSnapbackWarned = false; public Vector3 AirborneSnapbackPosition; public float FlingingUntil = -1f; public int SuspiciousFrameBuffer = 0; public readonly Queue<float> RecentSpeeds = new Queue<float>(); public float LastVelocityFlagTime = -999f; public Vector3 LastFramePosition; public bool HasLastFramePosition = false; public float LastAirborneFlagTime = -999f; public float LandingPeakY = float.NegativeInfinity; public float LandingGroundY = float.NaN; public bool HasLandingGroundSample = false; public float LandingStartedAt = -1f; public float LandingAirborneDuration = 0f; public float LandingCandidateHeight = 0f; public float LandingCandidatePeakY = float.NegativeInfinity; public float LandingCandidateLandingY = float.NaN; public float InjuryBeforeLanding = 0f; public float LandingMaxInjury = 0f; public bool LandingDamageObserved = false; public int InjuryEventCount = 0; public int InjuryEventsAtLandingStart = 0; public float LandingAt = -1f; public bool IsAirborneForLanding = false; public bool LandingCheckPending = false; public bool LandingExempt = false; public float LastLandingFlagTime = -999f; public int SpawnedItemsCount = 0; public int DroppedItemsCount = 0; public bool ItemActivityObserved = false; public bool InventoryActivityObserved = false; public bool InventoryBaselineInitialized = false; public bool LocalMovementMonitorLogged = false; public readonly Queue<float> ItemSpawnTimes = new Queue<float>(); public readonly Queue<float> ItemDropTimes = new Queue<float>(); public readonly Queue<float> InventoryAddTimes = new Queue<float>(); public readonly Queue<float> UnattributedInventoryTimes = new Queue<float>(); public readonly Dictionary<int, float> RecentItemInstanceIds = new Dictionary<int, float>(); public readonly Dictionary<int, float> RecentItemDropIds = new Dictionary<int, float>(); public int InventoryAddedItemsCount = 0; public float LastItemFloodFlagTime = -999f; public float LastItemDeleteLogTime = -999f; public float LastItemThresholdLogTime = -999f; public float LastInventoryThresholdLogTime = -999f; public float LastTrustedSuppressionLogTime = -999f; public float LastMuteRefreshTime = -999f; public float LastDecayTime = 0f; public float LastSyncSampleTime = -1f; public int LastSpeedSampleFrame = -1; public Component? PlayerComponent; public bool IsFlinging = false; public string PlayerName { get; set; } = string.Empty; public string PlayerId { get; set; } = string.Empty; public Player? NetPlayer { get; set; } } public const string PluginGUID = "com.custom.peak.badscoutwatcher"; public const string PluginName = "BadScoutWatcher"; public const string PluginVersion = "1.1.0"; public const float AirborneFlagThresholdSeconds = 10f; public const float AntiFlingAirborneRecoverySeconds = 5f; public const float SafeLandingMinFallHeightMeters = 22f; public const int ItemFloodTriggerCount = 6; public const float ItemFloodRapidWindowSeconds = 0.5f; private const float ItemFloodFlagCooldownSeconds = 6f; private const int BackpackDataEntryKey = 7; public static int CurrentRpcSenderActorNumber; public static BadScoutWatcherPlugin? Instance; public static ConfigEntry<KeyCode>? ConfigToggleKey; public static ConfigEntry<Color>? ConfigBgColor; public static ConfigEntry<Color>? ConfigBubbleColor; public static ConfigEntry<Color>? ConfigHeaderColor; public static ConfigEntry<int>? ConfigCornerRadius; public static ConfigEntry<bool>? ConfigAutoKick; public static ConfigEntry<bool>? ConfigItemDeletion; public static ConfigEntry<bool>? ConfigShieldEnabled; public static ConfigEntry<bool>? ConfigAntiFlingEnabled; public static ConfigEntry<bool>? ConfigOpenOnStartup; public static bool ShowUI = true; private int currentPage = 0; private Rect windowRect = new Rect(40f, 40f, 500f, 360f); private Vector2 scrollPos; private bool isListeningForKey = false; private bool pickerTargetsBackground = true; private bool pickerStateInitialized = false; private float pickerHue; private float pickerSaturation; private float pickerBrightness; private string pickerHex = ""; private Texture2D? hueWheelTexture; private Texture2D? saturationBrightnessTexture; private float saturationBrightnessTextureHue = -1f; private static GUIStyle? windowStyle; private static GUIStyle? bubbleStyle; private static GUIStyle? flatButtonStyle; private static GUIStyle? tabButtonStyle; private static GUIStyle? warningStyle; private static Texture2D? windowBgTexture; private static Texture2D? bubbleBgTexture; private static Texture2D? buttonNormalTexture; private static Texture2D? buttonHoverTexture; private static Texture2D? buttonActiveTexture; private static Texture2D? tabNormalTexture; private static Texture2D? tabHoverTexture; private float pollTimer = 0f; private static string latestWarning = string.Empty; private static float latestWarningUntil = 0f; private const float POLL_INTERVAL = 0.2f; private static readonly float[] ThreatBarThresholds = new float[4] { 0.2f, 0.45f, 0.7f, 0.95f }; public static Dictionary<int, PlayerData> Players = new Dictionary<int, PlayerData>(); private static readonly Dictionary<int, AntiFlingCollisionState> AntiFlingCollisionStates = new Dictionary<int, AntiFlingCollisionState>(); private static readonly Dictionary<Guid, ItemOriginRecord> ItemOrigins = new Dictionary<Guid, ItemOriginRecord>(); private static readonly Dictionary<Guid, float> RecentInventoryGuids = new Dictionary<Guid, float>(); private static readonly Dictionary<Guid, float> BackpackContentIgnoreUntil = new Dictionary<Guid, float>(); private static readonly Dictionary<int, ItemOriginRecord> RecentItemInstanceCreators = new Dictionary<int, ItemOriginRecord>(); private void Awake() { //IL_006f: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown Instance = this; ConfigToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ToggleKey", (KeyCode)283, "Keybind to toggle the UI overlay."); ConfigOpenOnStartup = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "OpenOnStartup", true, "Open the UI automatically when the game starts."); ConfigBgColor = ((BaseUnityPlugin)this).Config.Bind<Color>("UI Theme", "BackgroundColor", new Color(0.1f, 0.1f, 0.12f, 0.95f), "Window background color."); ConfigBubbleColor = ((BaseUnityPlugin)this).Config.Bind<Color>("UI Theme", "BubbleColor", new Color(0.18f, 0.18f, 0.22f, 0.9f), "Item bubble/box background color."); ConfigHeaderColor = ((BaseUnityPlugin)this).Config.Bind<Color>("UI Theme", "HeaderColor", new Color(0.25f, 0.25f, 0.3f, 1f), "Top header color."); ConfigCornerRadius = ((BaseUnityPlugin)this).Config.Bind<int>("UI Theme", "CornerRadius", 8, "Corner roundness radius (px)."); ConfigAutoKick = ((BaseUnityPlugin)this).Config.Bind<bool>("Host Rules", "AutoKick", false, "Auto-kick confirmed cheaters when hosting."); ConfigItemDeletion = ((BaseUnityPlugin)this).Config.Bind<bool>("Host Rules", "DeleteItems", true, "Delete explosives and item floods when hosting."); ConfigShieldEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Protection", "ShieldEnabled", true, "Reject malformed incoming RPCs and warn about unusually high RPC rates without dropping valid game RPCs."); ConfigAntiFlingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Protection", "AntiFlingEnabled", true, "Ignore fling collisions and recover after 5 seconds of suspected fling airtime."); if (ConfigOpenOnStartup != null) { ShowUI = ConfigOpenOnStartup.Value; } UpdateCursorState(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"BadScoutWatcher v1.1.0 loaded!"); try { Harmony val = new Harmony("com.custom.peak.badscoutwatcher"); val.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Harmony patches registered."); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Harmony patching error: {arg}"); } } private void OnEnable() { PhotonNetwork.AddCallbackTarget((object)this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget((object)this); RestoreAllAntiFlingCollisions(); } public void OnPlayerEnteredRoom(Player newPlayer) { if (newPlayer != null) { float time = Time.time; int actorNumber = newPlayer.ActorNumber; string playerName = ((!string.IsNullOrEmpty(newPlayer.NickName)) ? newPlayer.NickName : $"Scout #{actorNumber}"); if (!Players.TryGetValue(actorNumber, out PlayerData value) || value == null) { Players[actorNumber] = new PlayerData { PlayerName = playerName, PlayerId = (string.IsNullOrEmpty(newPlayer.UserId) ? "0" : newPlayer.UserId), ActorNumber = actorNumber, NetPlayer = newPlayer, LastDecayTime = time, LastCheckTime = time }; } } } public void OnPlayerLeftRoom(Player otherPlayer) { if (otherPlayer != null && Players.ContainsKey(otherPlayer.ActorNumber)) { Players.Remove(otherPlayer.ActorNumber); } if (otherPlayer != null) { AntiCheatRpcShieldPatch.ForgetActor(otherPlayer.ActorNumber); } } public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { } public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { } public void OnMasterClientSwitched(Player newMasterClient) { } private void Update() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) UpdateLocalAntiFlingRecovery(); UpdateAntiFlingCollisionGuard(); ResetExpiredItemFloodWindows(); RefreshMutedPlayers(); if (isListeningForKey) { return; } KeyCode val = (KeyCode)((ConfigToggleKey == null) ? 283 : ((int)ConfigToggleKey.Value)); if (Input.GetKeyDown(val)) { ShowUI = !ShowUI; UpdateCursorState(); } pollTimer += Time.deltaTime; if (pollTimer >= 0.2f) { pollTimer = 0f; PollPhotonLobbyPlayers(); CleanupDisconnectedPlayers(); } float time = Time.time; foreach (KeyValuePair<int, PlayerData> player in Players) { PlayerData value = player.Value; if (value != null && value.ThreatScore > 0) { if (value.LastDecayTime <= 0f) { value.LastDecayTime = time; } else if (time - value.LastDecayTime >= 60f) { int num = Mathf.FloorToInt((time - value.LastDecayTime) / 60f); value.ThreatScore = Mathf.Max(0, value.ThreatScore - num * 10); value.LastDecayTime += (float)num * 60f; } } } } private void UpdateLocalAntiFlingRecovery() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_0583: Unknown result type (might be due to invalid IL or missing references) //IL_0433: Unknown result type (might be due to invalid IL or missing references) //IL_040c: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) bool flag = ConfigAntiFlingEnabled != null && ConfigAntiFlingEnabled.Value; Character localCharacter = Character.localCharacter; if ((Object)(object)localCharacter == (Object)null) { return; } CharacterMovement component = ((Component)localCharacter).GetComponent<CharacterMovement>(); if ((Object)(object)component == (Object)null || !TryGetPeakGrounded((Component)(object)component, out var grounded)) { return; } Player localPlayer = PhotonNetwork.LocalPlayer; int num = ((localPlayer != null && localPlayer.ActorNumber > 0) ? localPlayer.ActorNumber : (-1)); float time = Time.time; Vector3 position = ((Component)localCharacter).transform.position; string text = ((localPlayer != null && !string.IsNullOrEmpty(localPlayer.NickName)) ? localPlayer.NickName : "Local player"); string playerId = ((localPlayer != null && !string.IsNullOrEmpty(localPlayer.UserId)) ? localPlayer.UserId : "0"); if (!Players.TryGetValue(num, out PlayerData value) || value == null) { value = new PlayerData { PlayerName = text, PlayerId = playerId, ActorNumber = num, NetPlayer = localPlayer, LastDecayTime = time, LastCheckTime = time }; Players[num] = value; } value.PlayerName = text; value.PlayerId = playerId; value.NetPlayer = localPlayer; if ((Object)(object)value.PlayerComponent != (Object)(object)component) { value.PlayerComponent = (Component?)(object)component; value.LastFramePosition = position; value.LastPosition = position; value.LastObservedPosition = position; value.HasLastFramePosition = true; value.HasLastObservedPosition = true; value.LastSyncSampleTime = time; value.LastGroundedPosition = Vector3.zero; value.HasLastGroundedPosition = false; value.AirborneStartedAt = -1f; value.AirborneTimer = 0f; value.AirborneFlagged = false; value.AirborneStartY = float.NaN; value.AirbornePeakY = float.NegativeInfinity; value.StationaryUngroundedTimer = 0f; value.AirborneInjuryObserved = false; value.FlingAirborneSuspected = false; value.AirborneSnapbackActive = false; value.AirborneSnapbackApplied = false; value.AirborneSnapbackWarned = false; value.RecentSpeeds.Clear(); } float injury = 0f; bool flag2 = TryGetPeakInjury((Component)(object)component, out injury); bool flag3 = IsFlightExempt((Component)(object)component); if (!value.LocalMovementMonitorLogged) { value.LocalMovementMonitorLogged = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Local movement checks active for " + text + ": grounded data available; injury status " + (flag2 ? "available" : "unavailable") + "; Anti-Fling " + (flag ? "enabled" : "disabled") + ".")); } value.LastObservedPosition = position; value.HasLastObservedPosition = true; if (grounded) { value.LastGroundedPosition = localCharacter.Center; value.HasLastGroundedPosition = true; value.AirborneStartedAt = -1f; value.AirborneTimer = 0f; value.AirborneFlagged = false; value.AirborneStartY = float.NaN; value.AirbornePeakY = float.NegativeInfinity; value.AirborneInjuryObserved = false; value.FlingAirborneSuspected = false; value.AirborneSnapbackActive = false; value.AirborneSnapbackApplied = false; value.AirborneSnapbackWarned = false; } else if (flag3) { value.AirborneStartedAt = -1f; value.AirborneTimer = 0f; value.AirborneFlagged = false; value.AirborneStartY = float.NaN; value.AirbornePeakY = float.NegativeInfinity; value.AirborneInjuryObserved = false; value.FlingAirborneSuspected = false; value.AirborneSnapbackActive = false; value.AirborneSnapbackApplied = false; value.AirborneSnapbackWarned = false; } else { if (value.AirborneStartedAt < 0f) { value.AirborneStartedAt = time; value.AirborneInjuryObserved = false; value.InjuryBeforeLanding = (flag2 ? injury : 0f); value.AirborneStartY = position.y; value.AirbornePeakY = position.y; } else { value.AirbornePeakY = Mathf.Max(value.AirbornePeakY, position.y); if (flag2 && injury > value.InjuryBeforeLanding + 0.001f) { value.AirborneInjuryObserved = true; value.FlingAirborneSuspected = false; } } value.AirborneTimer = Mathf.Max(0f, time - value.AirborneStartedAt); if (PhotonNetwork.InRoom && num > 0 && !value.AirborneFlagged && value.AirborneTimer >= 10f && !value.AirborneInjuryObserved) { value.AirborneFlagged = true; float num2 = (float.IsNaN(value.AirborneStartY) ? 0f : Mathf.Max(0f, value.AirbornePeakY - value.AirborneStartY)); FlagPlayer(num, value.PlayerName, value.PlayerId, "Airborne", "High risk", $"Non-grounded for {value.AirborneTimer:F1}s without landing; highest point Y {value.AirbornePeakY:F1}m (+{num2:F1}m from start)", 10); } if (flag && value.HasLastGroundedPosition && value.AirborneTimer >= 5f) { ActivateAntiFlingSnapback(value); } } value.LastFramePosition = position; value.LastSyncSampleTime = time; } public static void ActivateAntiFlingSnapback(PlayerData player) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (player == null || !player.HasLastGroundedPosition || ConfigAntiFlingEnabled == null || !ConfigAntiFlingEnabled.Value) { return; } Character val = ResolvePlayerCharacter(player.PlayerComponent); if (!((Object)(object)val != (Object)null) || val.IsLocal || PhotonNetwork.IsMasterClient) { if (!player.AirborneSnapbackActive) { player.AirborneSnapbackPosition = player.LastGroundedPosition; player.AirborneSnapbackApplied = false; } player.AirborneSnapbackActive = true; if (!player.AirborneSnapbackWarned) { player.AirborneSnapbackWarned = true; string arg = (string.IsNullOrEmpty(player.PlayerName) ? "Player" : player.PlayerName); ShowNoticeBanner($"Anti-Fling: {arg} returned to last ground point after {player.AirborneTimer:F0}s airborne"); } } } private static Character? ResolvePlayerCharacter(Component? component) { if ((Object)(object)component == (Object)null) { return null; } Character val = (Character)(object)((component is Character) ? component : null); if (val != null) { return val; } object obj = AccessTools.Field(((object)component).GetType(), "character")?.GetValue(component); Character val2 = (Character)((obj is Character) ? obj : null); if (val2 != null) { return val2; } return component.GetComponent<Character>() ?? component.GetComponentInParent<Character>(); } private static long GetAntiFlingPairKey(Collider local, Collider remote) { return ((long)((Object)local).GetInstanceID() << 32) | (uint)((Object)remote).GetInstanceID(); } private static void UpdateAntiFlingCollisionGuard() { bool flag = ConfigAntiFlingEnabled != null && ConfigAntiFlingEnabled.Value; Character val = (flag ? Character.localCharacter : null); UpdateAirborneSnapbacks(flag && (Object)(object)val != (Object)null); HashSet<int> hashSet = new HashSet<int>(); if ((Object)(object)val != (Object)null) { Collider[] componentsInChildren = ((Component)val).GetComponentsInChildren<Collider>(true); foreach (KeyValuePair<int, PlayerData> player in Players) { PlayerData value = player.Value; if (value == null) { continue; } if (value.IsFlinging && Time.time >= value.FlingingUntil) { value.IsFlinging = false; } if (!value.IsFlinging) { continue; } Character val2 = ResolvePlayerCharacter(value.PlayerComponent); if ((Object)(object)val2 == (Object)null || (Object)(object)val2 == (Object)(object)val || val2.IsLocal) { continue; } Collider[] componentsInChildren2 = ((Component)val2).GetComponentsInChildren<Collider>(true); if (componentsInChildren2.Length == 0) { continue; } hashSet.Add(player.Key); if (!AntiFlingCollisionStates.TryGetValue(player.Key, out AntiFlingCollisionState value2) || value2 == null) { value2 = new AntiFlingCollisionState(); AntiFlingCollisionStates[player.Key] = value2; } Collider[] array = componentsInChildren; foreach (Collider val3 in array) { if ((Object)(object)val3 == (Object)null || !val3.enabled || !((Component)val3).gameObject.activeInHierarchy || val3.isTrigger) { continue; } Collider[] array2 = componentsInChildren2; foreach (Collider val4 in array2) { if ((Object)(object)val4 == (Object)null || !val4.enabled || !((Component)val4).gameObject.activeInHierarchy || val4.isTrigger) { continue; } long antiFlingPairKey = GetAntiFlingPairKey(val3, val4); if (!value2.PairKeys.Add(antiFlingPairKey)) { continue; } try { if (!Physics.GetIgnoreCollision(val3, val4)) { Physics.IgnoreCollision(val3, val4, true); value2.Pairs.Add(new AntiFlingCollisionPair { Local = val3, Remote = val4 }); } } catch { } } } } } List<int> list = new List<int>(); foreach (int key in AntiFlingCollisionStates.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (int item in list) { RestoreAntiFlingCollisions(item); } } private static void UpdateAirborneSnapbacks(bool enabled) { //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) bool grounded = default(bool); foreach (PlayerData value in Players.Values) { if (value == null || !value.AirborneSnapbackActive) { continue; } Character val = ResolvePlayerCharacter(value.PlayerComponent); if ((Object)(object)val == (Object)null) { value.AirborneSnapbackActive = false; continue; } try { if (value.AirborneSnapbackApplied && !val.warping && (Object)(object)value.PlayerComponent != (Object)null && TryGetPeakGrounded(value.PlayerComponent, out grounded) && grounded) { value.AirborneSnapbackActive = false; value.AirborneSnapbackApplied = false; value.AirborneSnapbackWarned = false; value.AirborneStartedAt = -1f; value.AirborneTimer = 0f; } else if (enabled) { if (!val.IsLocal && !PhotonNetwork.IsMasterClient) { value.AirborneSnapbackActive = false; value.AirborneSnapbackApplied = false; value.AirborneSnapbackWarned = false; } else { if (value.AirborneSnapbackApplied) { continue; } if (val.IsLocal) { val.WarpPlayerRPC(value.AirborneSnapbackPosition, false); } else { PhotonView photonView = ((MonoBehaviourPun)val).photonView; if ((Object)(object)photonView != (Object)null) { photonView.RPC("WarpPlayerRPC", (RpcTarget)0, new object[2] { value.AirborneSnapbackPosition, false }); } else { val.WarpPlayerRPC(value.AirborneSnapbackPosition, false); } } value.AirborneSnapbackApplied = true; continue; } } else { value.AirborneSnapbackActive = false; value.AirborneSnapbackApplied = false; value.AirborneSnapbackWarned = false; } } catch { value.AirborneSnapbackActive = false; } } } private static void RestoreAntiFlingCollisions(int actor) { if (!AntiFlingCollisionStates.TryGetValue(actor, out AntiFlingCollisionState value) || value == null) { return; } foreach (AntiFlingCollisionPair pair in value.Pairs) { try { if ((Object)(object)pair.Local != (Object)null && (Object)(object)pair.Remote != (Object)null) { Physics.IgnoreCollision(pair.Local, pair.Remote, false); } } catch { } } AntiFlingCollisionStates.Remove(actor); } private static void RestoreAllAntiFlingCollisions() { List<int> list = new List<int>(AntiFlingCollisionStates.Keys); foreach (int item in list) { RestoreAntiFlingCollisions(item); } } private void OnDestroy() { RestoreAllAntiFlingCollisions(); if ((Object)(object)hueWheelTexture != (Object)null) { Object.Destroy((Object)(object)hueWheelTexture); } if ((Object)(object)saturationBrightnessTexture != (Object)null) { Object.Destroy((Object)(object)saturationBrightnessTexture); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private static void PollPhotonLobbyPlayers() { //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_066c: Unknown result type (might be due to invalid IL or missing references) //IL_066e: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.IsConnected) { return; } Player[] playerList = PhotonNetwork.PlayerList; if (playerList == null) { return; } float time = Time.time; Player[] array = playerList; foreach (Player val in array) { if (val == null || (val.IsLocal && val.ActorNumber <= 0)) { continue; } int actorNumber = val.ActorNumber; string nickName = val.NickName; string userId = val.UserId; string playerName = ((!string.IsNullOrEmpty(nickName)) ? nickName : ((!string.IsNullOrEmpty(userId)) ? userId : $"Scout #{actorNumber}")); string playerId = ((!string.IsNullOrEmpty(userId)) ? userId : "0"); if (!Players.TryGetValue(actorNumber, out PlayerData value) || value == null) { value = new PlayerData { PlayerName = playerName, PlayerId = playerId, ActorNumber = actorNumber, NetPlayer = val, LastDecayTime = time, LastCheckTime = time }; Players[actorNumber] = value; } else { if (!string.IsNullOrEmpty(nickName)) { value.PlayerName = nickName; } value.PlayerId = playerId; value.NetPlayer = val; } if (!((Object)(object)value.PlayerComponent != (Object)null)) { continue; } Transform transform = value.PlayerComponent.transform; Vector3 position = transform.position; float num = time - value.LastCheckTime; if (!(num >= 0.5f)) { continue; } float num2 = Vector3.Distance(position, value.LastPosition); if (num2 > 50f && num < 2f && !IsRecentlyWarped(value.PlayerComponent)) { FlagPlayer(actorNumber, value.PlayerName, value.PlayerId, "Teleport", "Low risk", $"Warped distance: {num2:F1}m", 5); } bool grounded; bool flag = !TryGetPeakGrounded(value.PlayerComponent, out grounded) || grounded; float injury; bool flag2 = TryGetPeakInjury(value.PlayerComponent, out injury); float y = position.y; if (!flag) { if (!value.IsAirborneForLanding) { value.IsAirborneForLanding = true; value.LandingPeakY = y; value.LandingGroundY = float.NaN; value.HasLandingGroundSample = false; value.LandingStartedAt = time; value.LandingAirborneDuration = 0f; value.LandingCandidateHeight = 0f; value.InjuryBeforeLanding = (flag2 ? injury : 0f); value.LandingMaxInjury = value.InjuryBeforeLanding; value.LandingDamageObserved = false; value.InjuryEventsAtLandingStart = value.InjuryEventCount; value.LandingExempt = IsLandingExempt(value.PlayerComponent); value.LandingCheckPending = false; } else if (y > value.LandingPeakY) { value.LandingPeakY = y; } if (value.LandingStartedAt >= 0f) { value.LandingAirborneDuration = time - value.LandingStartedAt; } if (flag2) { value.LandingMaxInjury = Mathf.Max(value.LandingMaxInjury, injury); if (injury > value.InjuryBeforeLanding + 0.001f) { value.LandingDamageObserved = true; value.AirborneInjuryObserved = true; } } if (value.InjuryEventCount > value.InjuryEventsAtLandingStart) { value.LandingDamageObserved = true; } } else if (value.IsAirborneForLanding) { float num3 = ((value.HasLandingGroundSample && !float.IsNaN(value.LandingGroundY)) ? value.LandingGroundY : y); float num4 = (float.IsNegativeInfinity(value.LandingPeakY) ? y : value.LandingPeakY); float num5 = Mathf.Max(0f, num4 - num3); float landingAirborneDuration = ((value.LandingStartedAt >= 0f) ? (time - value.LandingStartedAt) : value.LandingAirborneDuration); value.IsAirborneForLanding = false; value.LandingStartedAt = -1f; value.HasLandingGroundSample = false; if (flag2 && injury > value.InjuryBeforeLanding + 0.001f) { value.LandingDamageObserved = true; value.AirborneInjuryObserved = true; } if (value.InjuryEventCount > value.InjuryEventsAtLandingStart) { value.LandingDamageObserved = true; } if (num5 >= 22f && !value.LandingExempt && flag2) { value.LandingAt = time; value.LandingCheckPending = true; value.LandingCandidateHeight = num5; value.LandingCandidatePeakY = num4; value.LandingCandidateLandingY = num3; value.LandingAirborneDuration = landingAirborneDuration; value.LandingMaxInjury = Mathf.Max(value.LandingMaxInjury, injury); BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)$"Landing check started for actor {actorNumber}: fall {num5:F1}m; waiting 1.5s to confirm no injury."); } } } if (value.LandingCheckPending && flag2) { value.LandingMaxInjury = Mathf.Max(value.LandingMaxInjury, injury); if (injury > value.InjuryBeforeLanding + 0.001f || value.InjuryEventCount > value.InjuryEventsAtLandingStart) { value.LandingDamageObserved = true; value.AirborneInjuryObserved = true; } if (time - value.LandingAt >= 1.5f) { if (!value.LandingDamageObserved && value.LandingMaxInjury <= value.InjuryBeforeLanding + 0.001f && time - value.LastLandingFlagTime >= 5f) { value.LastLandingFlagTime = time; FlagPlayer(actorNumber, value.PlayerName, value.PlayerId, "Landing / Height Damageable Fall", "Low risk", $"Landed after {value.LandingAirborneDuration:F1}s; highest point Y {value.LandingCandidatePeakY:F1}m, {value.LandingCandidateHeight:F1}m above landing, without fall injury", 5); } value.LandingCheckPending = false; } } value.LastPosition = position; value.LastCheckTime = time; } } private static bool TryGetCharacterData(Component component, out object? character, out object? data) { character = null; data = null; try { character = ((object)component).GetType().GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(component); if (character == null) { return false; } data = character.GetType().GetField("data", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(character); return data != null; } catch { character = null; data = null; return false; } } public static bool TryGetPeakGrounded(Component component, out bool grounded) { grounded = true; try { if (!TryGetCharacterData(component, out object _, out object data) || data == null) { return false; } if (data.GetType().GetField("isGrounded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data) is bool flag) { grounded = flag; return true; } } catch { } return false; } public static bool IsFlightExempt(Component component) { try { if (!TryGetCharacterData(component, out object character, out object data) || character == null || data == null) { return true; } string[] array = new string[5] { "isClimbing", "isRopeClimbing", "isVineClimbing", "isCarried", "hasParachute" }; string[] array2 = array; bool flag = default(bool); foreach (string name in array2) { object obj = data.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } } if (data.GetType().GetField("timeInAntigrav", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data) is float num2 && num2 > 0f) { return true; } object obj2 = data.GetType().GetField("currentClimbHandle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data); if (obj2 != null) { return true; } object obj3 = character?.GetType().GetField("refs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(character); object obj4 = obj3?.GetType().GetField("balloons", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj3); if (obj4?.GetType().GetProperty("currentBalloonCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj4) is int num3 && num3 > 0) { return true; } object obj5 = obj4?.GetType().GetField("_isParachuteOpen", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj4); bool flag2 = default(bool); int num4; if (obj5 is bool) { flag2 = (bool)obj5; num4 = 1; } else { num4 = 0; } if (((uint)num4 & (flag2 ? 1u : 0u)) != 0) { return true; } object obj6 = data.GetType().GetProperty("currentItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data); if (obj6 != null && (obj6.GetType().Name.Contains("Jetpack") || obj6.GetType().Name.Contains("Parasol"))) { return true; } object obj7 = data.GetType().GetField("isInvincible", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data); bool flag3 = default(bool); int num5; if (obj7 is bool) { flag3 = (bool)obj7; num5 = 1; } else { num5 = 0; } if (((uint)num5 & (flag3 ? 1u : 0u)) != 0) { return true; } return false; } catch { return true; } } internal static bool TryGetPeakInjury(Component component, out float injury) { injury = 0f; try { if (!TryGetCharacterData(component, out object character, out object _) || character == null) { return false; } object obj = character?.GetType().GetField("refs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(character); object obj2 = obj?.GetType().GetField("afflictions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); MethodInfo methodInfo = obj2?.GetType().GetMethod("GetCurrentStatus", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (methodInfo == null || methodInfo.GetParameters().Length != 1) { return false; } Type parameterType = methodInfo.GetParameters()[0].ParameterType; object obj3 = Enum.ToObject(parameterType, 0); if (methodInfo.Invoke(obj2, new object[1] { obj3 }) is float num) { injury = num; return true; } } catch { } return false; } internal static bool IsLandingExempt(Component component) { try { if (IsFlightExempt(component)) { return true; } if (!TryGetCharacterData(component, out object _, out object data) || data == null) { return true; } object obj = data?.GetType().GetField("hasParachute", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(data); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } return IsRecentlyWarped(component); } catch { return false; } } private static bool IsRecentlyWarped(Component component) { try { object obj = ((object)component).GetType().GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(component); if (obj == null) { return false; } object obj2 = obj.GetType().GetProperty("warping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); bool flag = default(bool); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } return obj.GetType().GetProperty("timeLastWarped", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) is float num2 && Time.time - num2 < 2f; } catch { return false; } } public static void MonitorPlayerInstantiation(PhotonView pv, GameObject instantiatedObj) { if (!((Object)(object)pv == (Object)null) && pv.Owner != null) { MonitorNetworkInstantiation(pv.Owner, instantiatedObj); } } public static void MonitorNetworkInstantiation(Player owner, GameObject instantiatedObj) { if (owner == null || (Object)(object)instantiatedObj == (Object)null || owner.ActorNumber <= 0 || (Object)(object)instantiatedObj.GetComponentInChildren<Item>(true) == (Object)null) { return; } int num = owner.ActorNumber; int num2 = ((PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1)); int currentRpcSenderActorNumber = CurrentRpcSenderActorNumber; if (num == num2 && currentRpcSenderActorNumber > 0 && currentRpcSenderActorNumber != num2) { Player netPlayerByActor = GetNetPlayerByActor(currentRpcSenderActorNumber); if (netPlayerByActor != null) { owner = netPlayerByActor; num = currentRpcSenderActorNumber; } } Guid itemGuid = GetItemGuid(instantiatedObj); RememberItemOrigin(num, itemGuid, overwrite: false); float time = Time.time; Item[] componentsInChildren = instantiatedObj.GetComponentsInChildren<Item>(true); foreach (Item val in componentsInChildren) { RecentItemInstanceCreators[((Object)val).GetInstanceID()] = new ItemOriginRecord { ActorNumber = num, LastSeenAt = time }; RememberBackpackContents(GetItemInstanceData(val), num, overwrite: false); } List<int> list = new List<int>(); foreach (KeyValuePair<int, ItemOriginRecord> recentItemInstanceCreator in RecentItemInstanceCreators) { if (time - recentItemInstanceCreator.Value.LastSeenAt > 30f) { list.Add(recentItemInstanceCreator.Key); } } foreach (int item in list) { RecentItemInstanceCreators.Remove(item); } if ((Object)(object)Instance != (Object)null) { ((MonoBehaviour)Instance).StartCoroutine(RecordSpawnAfterBackpackData(owner, num, instantiatedObj, time)); } else if (!IsRecentBackpackContent(itemGuid)) { RecordItemActivity(GetOrCreatePlayerData(owner), num, instantiatedObj, scorePlayer: true, time); } } private static IEnumerator RecordSpawnAfterBackpackData(Player owner, int actorNum, GameObject spawnedObject, float eventTime) { yield return (object)new WaitForSeconds(0.15f); if (!((Object)(object)spawnedObject == (Object)null)) { Item[] componentsInChildren = spawnedObject.GetComponentsInChildren<Item>(true); foreach (Item item in componentsInChildren) { RememberBackpackContents(GetItemInstanceData(item), actorNum, overwrite: false); } if (!IsRecentBackpackContent(GetItemGuid(spawnedObject))) { PlayerData playerData = GetOrCreatePlayerData(owner); RecordItemActivity(playerData, actorNum, spawnedObject, scorePlayer: true, eventTime); } } } private static Guid GetItemGuid(ItemInstanceData? instanceData) { if (instanceData == null) { return Guid.Empty; } return (AccessTools.Field(typeof(ItemInstanceData), "guid")?.GetValue(instanceData) is Guid guid) ? guid : Guid.Empty; } private static Guid GetItemGuid(GameObject? itemObject) { if ((Object)(object)itemObject == (Object)null) { return Guid.Empty; } Item val = itemObject.GetComponent<Item>() ?? itemObject.GetComponentInChildren<Item>(true); if ((Object)(object)val == (Object)null) { return Guid.Empty; } return GetItemGuid(GetItemInstanceData(val)); } private static ItemInstanceData? GetItemInstanceData(Item item) { object? obj = AccessTools.Field(typeof(Item), "data")?.GetValue(item); return (ItemInstanceData?)((obj is ItemInstanceData) ? obj : null); } public static void MonitorItemInstanceData(Item item, ItemInstanceData? instanceData) { if ((Object)(object)item == (Object)null || instanceData == null) { return; } try { Guid itemGuid = GetItemGuid(instanceData); int currentRpcSenderActorNumber = CurrentRpcSenderActorNumber; int num = ((PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1)); object? obj = AccessTools.Field(typeof(Item), "view")?.GetValue(item); PhotonView val = (PhotonView)((obj is PhotonView) ? obj : null); int num2 = 0; bool overwrite = false; ItemOriginRecord value; if (currentRpcSenderActorNumber > 0 && currentRpcSenderActorNumber != num) { num2 = currentRpcSenderActorNumber; overwrite = true; } else if (RecentItemInstanceCreators.TryGetValue(((Object)item).GetInstanceID(), out value) && value != null && Time.time - value.LastSeenAt <= 30f) { num2 = value.ActorNumber; overwrite = true; } else if (((val != null) ? val.Owner : null) != null) { num2 = val.Owner.ActorNumber; } RememberItemOrigin(num2, itemGuid, overwrite); RememberBackpackContents(instanceData, num2, overwrite); } catch { } } private static void RememberBackpackContents(ItemInstanceData? instanceData, int originActor, bool overwrite) { float time = Time.time; foreach (Guid backpackContentGuid in GetBackpackContentGuids(instanceData)) { BackpackContentIgnoreUntil[backpackContentGuid] = time + 10f; RememberItemOrigin(originActor, backpackContentGuid, overwrite); } } private static HashSet<Guid> GetBackpackContentGuids(Player player) { return ((Object)(object)player != (Object)null && player.backpackSlot != null) ? GetBackpackContentGuids(((ItemSlot)player.backpackSlot).data) : new HashSet<Guid>(); } private static HashSet<Guid> GetBackpackContentGuids(ItemInstanceData? instanceData) { HashSet<Guid> hashSet = new HashSet<Guid>(); if (instanceData == null) { return hashSet; } try { MethodInfo methodInfo = typeof(ItemInstanceData).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "TryGetDataEntry" && method.IsGenericMethodDefinition && method.GetParameters().Length == 2); if (methodInfo == null) { return hashSet; } Type parameterType = methodInfo.GetParameters()[0].ParameterType; object obj = (parameterType.IsEnum ? Enum.ToObject(parameterType, 7) : Convert.ChangeType(7, parameterType)); object[] array = new object[2] { obj, null }; MethodInfo methodInfo2 = methodInfo.MakeGenericMethod(typeof(BackpackData)); object obj2 = methodInfo2.Invoke(instanceData, array); if (!(obj2 is bool) || !(bool)obj2 || array[1] == null) { return hashSet; } object obj3 = AccessTools.Field(typeof(BackpackData), "itemSlots")?.GetValue(array[1]); if (!(obj3 is IEnumerable enumerable)) { return hashSet; } foreach (object item in enumerable) { ItemSlot val = (ItemSlot)((item is ItemSlot) ? item : null); if (val != null) { AddInventoryItemGuid(hashSet, val); } } } catch { } return hashSet; } private static bool IsRecentBackpackContent(Guid guid) { if (guid == Guid.Empty || !BackpackContentIgnoreUntil.TryGetValue(guid, out var value)) { return false; } if (Time.time <= value) { return true; } BackpackContentIgnoreUntil.Remove(guid); return false; } private static Player? GetNetPlayerByActor(int actorNum) { Player[] playerList = PhotonNetwork.PlayerList; if (playerList == null) { return null; } Player[] array = playerList; foreach (Player val in array) { if (val != null && val.ActorNumber == actorNum) { return val; } } return null; } private static void RememberItemOrigin(int actorNum, Guid guid, bool overwrite) { if (actorNum <= 0 || guid == Guid.Empty) { return; } float time = Time.time; if (!overwrite && ItemOrigins.TryGetValue(guid, out ItemOriginRecord value) && value != null) { value.LastSeenAt = time; return; } ItemOrigins[guid] = new ItemOriginRecord { ActorNumber = actorNum, LastSeenAt = time }; List<Guid> list = new List<Guid>(); foreach (KeyValuePair<Guid, ItemOriginRecord> itemOrigin in ItemOrigins) { if (time - itemOrigin.Value.LastSeenAt > 1800f) { list.Add(itemOrigin.Key); } } foreach (Guid item in list) { ItemOrigins.Remove(item); } } private static bool TryGetItemOrigin(Guid guid, out int actorNum) { actorNum = 0; if (guid == Guid.Empty || !ItemOrigins.TryGetValue(guid, out ItemOriginRecord value) || value == null) { return false; } if (Time.time - value.LastSeenAt > 1800f) { ItemOrigins.Remove(guid); return false; } actorNum = value.ActorNumber; return actorNum > 0; } public static void MonitorDrop(Player owner, GameObject? droppedObj) { if (owner == null || owner.ActorNumber <= 0) { return; } PlayerData orCreatePlayerData = GetOrCreatePlayerData(owner); if ((Object)(object)droppedObj != (Object)null) { Guid itemGuid = GetItemGuid(droppedObj); BackpackContentIgnoreUntil.Remove(itemGuid); RememberItemOrigin(owner.ActorNumber, itemGuid, overwrite: true); } if ((Object)(object)droppedObj == (Object)null && Time.time - orCreatePlayerData.LastItemDeleteLogTime >= 1f) { orCreatePlayerData.LastItemDeleteLogTime = Time.time; BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)$"Drop event from actor {owner.ActorNumber} did not expose the item object; the dropper is still tracked, but host item deletion is unavailable for this drop."); } } RecordItemDropActivity(orCreatePlayerData, owner.ActorNumber, droppedObj); } private static PlayerData GetOrCreatePlayerData(Player owner) { int actorNumber = owner.ActorNumber; if (!Players.TryGetValue(actorNumber, out PlayerData value) || value == null) { value = new PlayerData { PlayerName = (string.IsNullOrEmpty(owner.NickName) ? $"Scout #{actorNumber}" : owner.NickName), PlayerId = (string.IsNullOrEmpty(owner.UserId) ? "0" : owner.UserId), ActorNumber = actorNumber, NetPlayer = owner, LastDecayTime = Time.time, LastCheckTime = Time.time }; Players[actorNumber] = value; } else { if (!string.IsNullOrEmpty(owner.NickName)) { value.PlayerName = owner.NickName; } value.PlayerId = (string.IsNullOrEmpty(owner.UserId) ? "0" : owner.UserId); value.NetPlayer = owner; } return value; } public static bool HasItemToDrop(object characterItems) { try { object obj = characterItems.GetType().GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(characterItems); object obj2 = obj?.GetType().GetField("data", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); object obj3 = obj2?.GetType().GetProperty("currentItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); return obj3 != null; } catch { return false; } } public static bool HasItemInDropSlot(object characterItems, byte slotNumber) { try { object obj = characterItems.GetType().GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(characterItems); object obj2 = obj?.GetType().GetProperty("player", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); object obj3 = (obj2?.GetType().GetMethod("GetItemSlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))?.Invoke(obj2, new object[1] { slotNumber }); bool? flag = obj3?.GetType().GetMethod("IsEmpty", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(obj3, null) as bool?; return obj3 != null && flag == false; } catch { return false; } } public static ItemInstanceData? GetItemDataInDropSlot(object characterItems, byte slotNumber) { try { object obj = characterItems.GetType().GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(characterItems); object obj2 = obj?.GetType().GetProperty("player", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); object obj3 = (obj2?.GetType().GetMethod("GetItemSlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))?.Invoke(obj2, new object[1] { slotNumber }); object? obj4 = obj3?.GetType().GetProperty("data", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj3); return (ItemInstanceData?)((obj4 is ItemInstanceData) ? obj4 : null); } catch { return null; } } public static void MonitorDropFromCharacterItems(object characterItems, bool validDrop, ItemInstanceData? droppedData = null) { //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Expected O, but got Unknown if (!validDrop) { return; } try { object obj = characterItems.GetType().GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(characterItems); Character val = (Character)((obj is Character) ? obj : null); PhotonView val2 = (((Object)(object)val != (Object)null) ? ((MonoBehaviourPun)val).photonView : null); if ((Object)(object)val2 == (Object)null) { object? obj2 = obj?.GetType().GetField("view", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); val2 = (PhotonView)((obj2 is PhotonView) ? obj2 : null); } Player val3 = (((Object)(object)val2 != (Object)null) ? val2.Owner : null); if (val3 == null && (Object)(object)val != (Object)null && val.IsLocal) { val3 = PhotonNetwork.LocalPlayer; } int currentRpcSenderActorNumber = CurrentRpcSenderActorNumber; int num = ((PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1)); if (currentRpcSenderActorNumber > 0 && currentRpcSenderActorNumber != num) { Player[] playerList = PhotonNetwork.PlayerList; if (playerList != null) { Player[] array = playerList; foreach (Player val4 in array) { if (val4 != null && val4.ActorNumber == currentRpcSenderActorNumber) { val3 = val4; break; } } } } if (val3 == null) { return; } if (droppedData != null) { RememberItemOrigin(val3.ActorNumber, GetItemGuid(droppedData), overwrite: true); } GameObject val5 = null; object obj3 = characterItems.GetType().GetField("droppedItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(characterItems); if (obj3 is IEnumerable enumerable) { { IEnumerator enumerator = enumerable.GetEnumerator(); try { GameObject val9; for (; enumerator.MoveNext(); val5 = val9) { object current = enumerator.Current; object obj4 = current; PhotonView val6 = (PhotonView)((obj4 is PhotonView) ? obj4 : null); Component val7; if (val6 == null) { val7 = (Component)((obj4 is Component) ? obj4 : null); if (val7 == null) { GameObject val8 = (GameObject)((obj4 is GameObject) ? obj4 : null); if (val8 == null || !((Object)(object)val8 != (Object)null)) { goto IL_0240; } val9 = val8; continue; } } else { if ((Object)(object)val6 != (Object)null) { val9 = ((Component)val6).gameObject; continue; } val7 = (Component)obj4; } if (!((Object)(object)val7 != (Object)null)) { goto IL_0240; } val9 = val7.gameObject; continue; IL_0240: val9 = val5; } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } } } MonitorDrop(val3, val5); } catch (Exception ex) { Debug.LogWarning((object)("[BadScoutWatcher] Could not inspect dropped item: " + ex.Message)); } } public static HashSet<Guid> CaptureInventoryItemGuids(Player player) { HashSet<Guid> hashSet = new HashSet<Guid>(); if ((Object)(object)player == (Object)null) { return hashSet; } if (player.itemSlots != null) { ItemSlot[] itemSlots = player.itemSlots; foreach (ItemSlot slot in itemSlots) { AddInventoryItemGuid(hashSet, slot); } } AddInventoryItemGuid(hashSet, player.tempFullSlot); AddInventoryItemGuid(hashSet, (ItemSlot?)(object)player.backpackSlot); return hashSet; } private static void AddInventoryItemGuid(HashSet<Guid> guids, ItemSlot? slot) { if (slot != null && !slot.IsEmpty()) { Guid itemGuid = GetItemGuid(slot.data); if (itemGuid != Guid.Empty) { guids.Add(itemGuid); } } } private static Player? GetInventoryOwner(Player player) { try { object? obj = typeof(Player).GetField("view", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(player); PhotonView val = (PhotonView)((obj is PhotonView) ? obj : null); if (((val != null) ? val.Owner : null) != null) { return val.Owner; } if ((Object)(object)player.character != (Object)null && player.character.IsLocal) { return PhotonNetwork.LocalPlayer; } } catch { } return null; } public static void MonitorInventoryAdd(Player player, bool itemAdded, ItemInstanceData? instanceData, ItemSlot? resultSlot) { if (!itemAdded || (Object)(object)player == (Object)null || !PhotonNetwork.IsMasterClient) { return; } Player inventoryOwner = GetInventoryOwner(player); if (inventoryOwner == null || inventoryOwner.ActorNumber <= 0) { return; } Guid itemGuid = GetItemGuid(instanceData); if (itemGuid == Guid.Empty && resultSlot != null) { itemGuid = GetItemGuid(resultSlot.data); } if (itemGuid != Guid.Empty && (IsRecentBackpackContent(itemGuid) || GetBackpackContentGuids(player).Contains(itemGuid))) { return; } PlayerData orCreatePlayerData = GetOrCreatePlayerData(inventoryOwner); int currentRpcSenderActorNumber = CurrentRpcSenderActorNumber; int num = ((PhotonNetwork.MasterClient != null) ? PhotonNetwork.MasterClient.ActorNumber : (-1)); if (currentRpcSenderActorNumber > 0 && currentRpcSenderActorNumber != num) { PlayerData playerDataByActor = GetPlayerDataByActor(currentRpcSenderActorNumber); if (playerDataByActor != null) { if (!(itemGuid != Guid.Empty) || TryMarkRecentInventoryGuid(itemGuid)) { RecordAttributedInventoryChange(playerDataByActor, currentRpcSenderActorNumber, orCreatePlayerData.PlayerName); } return; } } RecordInventoryAdditionByOrigin(itemGuid, orCreatePlayerData, inventoryOwner.ActorNumber); } public static void MonitorInventorySync(Player player, HashSet<Guid>? itemGuidsBeforeSync) { if ((Object)(object)player == (Object)null || PhotonNetwork.IsMasterClient) { return; } Player inventoryOwner = GetInventoryOwner(player); if (inventoryOwner == null || inventoryOwner.ActorNumber <= 0) { return; } PlayerData orCreatePlayerData = GetOrCreatePlayerData(inventoryOwner); if (!orCreatePlayerData.InventoryBaselineInitialized) { orCreatePlayerData.InventoryBaselineInitialized = true; return; } HashSet<Guid> hashSet = CaptureInventoryItemGuids(player); if (itemGuidsBeforeSync == null) { return; } foreach (Guid item in hashSet) { if (!itemGuidsBeforeSync.Contains(item)) { RecordInventoryAdditionByOrigin(item, orCreatePlayerData, inventoryOwner.ActorNumber); } } } private static void RecordInventoryAdditionByOrigin(Guid itemGuid, PlayerData recipientData, int recipientActorNum) { if (IsRecentBackpackContent(itemGuid) || !TryMarkRecentInventoryGuid(itemGuid)) { return; } if (!TryGetItemOrigin(itemGuid, out var actorNum) || (PhotonNetwork.MasterClient != null && actorNum == PhotonNetwork.MasterClient.ActorNumber)) { RecordUnattributedInventoryChange(recipientData, recipientActorNum); return; } PlayerData playerDataByActor = GetPlayerDataByActor(actorNum); if (playerDataByActor == null) { RecordUnattributedInventoryChange(recipientData, recipientActorNum); } else { RecordAttributedInventoryChange(playerDataByActor, actorNum, recipientData.PlayerName); } } private static bool TryMarkRecentInventoryGuid(Guid itemGuid) { if (itemGuid == Guid.Empty) { return false; } float time = Time.time; if (RecentInventoryGuids.TryGetValue(itemGuid, out var value) && time - value < 2f) { return false; } RecentInventoryGuids[itemGuid] = time; List<Guid> list = new List<Guid>(); foreach (KeyValuePair<Guid, float> recentInventoryGuid in RecentInventoryGuids) { if (time - recentInventoryGuid.Value > 2f) { list.Add(recentInventoryGuid.Key); } } foreach (Guid item in list) { RecentInventoryGuids.Remove(item); } return true; } private static PlayerData? GetPlayerDataByActor(int actorNum) { if (Players.TryGetValue(actorNum, out PlayerData value) && value != null) { return value; } Player[] playerList = PhotonNetwork.PlayerList; if (playerList == null) { return null; } Player[] array = playerList; foreach (Player val in array) { if (val != null && val.ActorNumber == actorNum) { return GetOrCreatePlayerData(val); } } return null; } private static void RecordUnattributedInventoryChange(PlayerData recipientData, int recipientActorNum) { float time = Time.time; recipientData.UnattributedInventoryTimes.Enqueue(time); PruneItemFloodWindow(recipientData.UnattributedInventoryTimes, time); if (recipientData.UnattributedInventoryTimes.Count >= 6 && !(time - recipientData.LastInventoryThresholdLogTime < 1f)) { recipientData.LastInventoryThresholdLogTime = time; BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)$"Inventory flood observed for recipient {recipientData.PlayerName} (actor {recipientActorNum}), but no non-host source actor could be confirmed. The recipient was not flagged."); } } } private static void RecordAttributedInventoryChange(PlayerData sourceData, int sourceActorNum, string recipientName) { float time = Time.time; if (!sourceData.InventoryActivityObserved) { sourceData.InventoryActivityObserved = true; BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)$"Inventory flood attribution observed item origins from {sourceData.PlayerName} (actor {sourceActorNum})."); } } sourceData.InventoryAddTimes.Enqueue(time); PruneItemFloodWindow(sourceData.InventoryAddTimes, time); sourceData.InventoryAddedItemsCount = sourceData.InventoryAddTimes.Count; if (sourceData.InventoryAddedItemsCount < 6) { return; } if (time - sourceData.LastInventoryThresholdLogTime >= 1f) { sourceData.LastInventoryThresholdLogTime = time; BadScoutWatcherPlugin? instance2 = Instance; if (instance2 != null) { ((BaseUnityPlugin)instance2).Logger.LogWarning((object)$"Inventory flood attributed to {sourceData.PlayerName} (actor {sourceActorNum}): {sourceData.InventoryAddedItemsCount} items delivered to {recipientName} within {500f:F0} ms."); } } if (time - sourceData.LastItemFloodFlagTime >= 6f) { sourceData.LastItemFloodFlagTime = time; FlagPlayer(sourceActorNum, sourceData.PlayerName, sourceData.PlayerId, "Item Flood", "High risk", $"{sourceData.InventoryAddedItemsCount} inventory additions within {500f:F0} ms to {recipientName}", 30); } } private static void RecordItemActivity(PlayerData p, int actorNum, GameObject? itemObject, bool scorePlayer = true, float eventTime = -1f) { if ((Object)(object)itemObject == (Object)null || IsRecentBackpackContent(GetItemGuid(itemObject))) { return; } float time = Time.time; float num = ((eventTime >= 0f) ? eventTime : time); int instanceID = ((Object)itemObject).GetInstanceID(); if (p.RecentItemInstanceIds.TryGetValue(instanceID, out var value) && time - value < 2f) { return; } p.RecentItemInstanceIds[instanceID] = time; List<int> list = new List<int>(); foreach (KeyValuePair<int, float> recentItemInstanceId in p.RecentItemInstanceIds) { if (time - recentItemInstanceId.Value > 2f) { list.Add(recentItemInstanceId.Key); } } foreach (int item in list) { p.RecentItemInstanceIds.Remove(item); } if (!p.ItemActivityObserved) { p.ItemActivityObserved = true; BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)$"Item-spawn monitoring observed a PEAK item for {p.PlayerName} (actor {actorNum})."); } } p.ItemSpawnTimes.Enqueue(num); PruneItemFloodWindow(p.ItemSpawnTimes, num); p.SpawnedItemsCount = p.ItemSpawnTimes.Count; if (p.SpawnedItemsCount >= 6) { HandleItemFloodResponse(p, actorNum, p.SpawnedItemsCount, $"{p.SpawnedItemsCount} distinct item spawns within {500f:F0} ms", itemObject, $"Item-spam threshold reached for actor {actorNum}", scorePlayer); } } private static void RecordItemDropActivity(PlayerData p, int actorNum, GameObject? droppedObject) { float time = Time.time; if ((Object)(object)droppedObject != (Object)null) { int instanceID = ((Object)droppedObject).GetInstanceID(); if (p.RecentItemDropIds.TryGetValue(instanceID, out var value) && time - value < 0.2f) { return; } p.RecentItemDropIds[instanceID] = time; List<int> list = new List<int>(); foreach (KeyValuePair<int, float> recentItemDropId in p.RecentItemDropIds) { if (time - recentItemDropId.Value > 2f) { list.Add(recentItemDropId.Key); } } foreach (int item in list) { p.RecentItemDropIds.Remove(item); } } p.ItemDropTimes.Enqueue(time); PruneItemFloodWindow(p.ItemDropTimes, time); p.DroppedItemsCount = p.ItemDropTimes.Count; if (p.DroppedItemsCount >= 6) { HandleItemFloodResponse(p, actorNum, p.DroppedItemsCount, $"{p.DroppedItemsCount} item drops within {500f:F0} ms", droppedObject, $"Item-drop flood threshold reached for actor {actorNum}", scorePlayer: true); } } private static void HandleItemFloodResponse(PlayerData p, int actorNum, int eventCount, string description, GameObject? itemObject, string logPrefix, bool scorePlayer) { float time = Time.time; if (time - p.LastItemThresholdLogTime >= 1f) { p.LastItemThresholdLogTime = time; BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)$"{logPrefix}: {eventCount} item events within {500f:F0} ms."); } } bool isMasterClient = PhotonNetwork.IsMasterClient; if (isMasterClient && ConfigItemDeletion != null && ConfigItemDeletion.Value) { if ((Object)(object)itemObject != (Object)null) { PhotonView val = itemObject.GetComponent<PhotonView>() ?? itemObject.GetComponentInChildren<PhotonView>(true); bool flag = TryDeleteFloodedItem(itemObject); if (time - p.LastItemDeleteLogTime >= 1f) { p.LastItemDeleteLogTime = time; BadScoutWatcherPlugin? instance2 = Instance; if (instance2 != null) { ((BaseUnityPlugin)instance2).Logger.LogInfo((object)((!flag) ? $"Item flood from actor {actorNum}: item removal could not be requested; see the preceding warning." : (((Object)(object)val == (Object)null) ? $"Item flood from actor {actorNum}: removed the host's local copy; item had no PhotonView." : $"Item flood from actor {actorNum}: requested network removal of the item."))); } } } else if (time - p.LastItemDeleteLogTime >= 1f) { p.LastItemDeleteLogTime = time; BadScoutWatcherPlugin? instance3 = Instance; if (instance3 != null) { ((BaseUnityPlugin)instance3).Logger.LogWarning((object)$"Item flood from actor {actorNum}: no item object was available to remove."); } } } else if (time - p.LastItemDeleteLogTime >= 1f) { p.LastItemDeleteLogTime = time; BadScoutWatcherPlugin? instance4 = Instance; if (instance4 != null) { ((BaseUnityPlugin)instance4).Logger.LogWarning((object)((!isMasterClient) ? $"Item flood from actor {actorNum} was detected, but only the room host can remove network items." : $"Item flood from actor {actorNum} was detected, but Delete Items is disabled.")); } } if (scorePlayer && time - p.LastItemFloodFlagTime >= 6f) { p.LastItemFloodFlagTime = time; FlagPlayer(actorNum, p.PlayerName, p.PlayerId, "Item Flood", "High risk", description, 30); } } private static void PruneItemFloodWindow(Queue<float> eventTimes, float now) { while (eventTimes.Count > 0 && now - eventTimes.Peek() >= 0.5f) { eventTimes.Dequeue(); } } private static void ResetExpiredItemFloodWindows() { float time = Time.time; foreach (PlayerData value in Players.Values) { if (value != null) { PruneItemFloodWindow(value.ItemSpawnTimes, time); value.SpawnedItemsCount = value.ItemSpawnTimes.Count; PruneItemFloodWindow(value.ItemDropTimes, time); value.DroppedItemsCount = value.ItemDropTimes.Count; PruneItemFloodWindow(value.InventoryAddTimes, time); value.InventoryAddedItemsCount = value.InventoryAddTimes.Count; PruneItemFloodWindow(value.UnattributedInventoryTimes, time); } } } private static bool TryDeleteFloodedItem(GameObject itemObject) { if ((Object)(object)itemObject == (Object)null || !PhotonNetwork.IsMasterClient || !PhotonNetwork.InRoom) { return false; } PhotonView val = itemObject.GetComponent<PhotonView>() ?? itemObject.GetComponentInChildren<PhotonView>(true); if ((Object)(object)val == (Object)null) { Object.Destroy((Object)(object)itemObject); return true; } try { PhotonNetwork.Destroy(((Component)val).gameObject); if ((Object)(object)Instance != (Object)null) { ((MonoBehaviour)Instance).StartCoroutine(VerifyFloodItemRemoval(val)); } return true; } catch (Exception ex) { BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("Network removal of spam item failed: " + ex.Message)); } return false; } } private static IEnumerator VerifyFloodItemRemoval(PhotonView spawnedView) { yield return null; if ((Object)(object)spawnedView != (Object)null && (Object)(object)((Component)spawnedView).gameObject != (Object)null) { BadScoutWatcherPlugin? instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)"Photon did not remove the spam item after the destroy request; check the room host and item ownership."); } } } private static void UpdateCursorState() { if (ShowUI) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } else { Cursor.lockState = (CursorLockMode)1; Cursor.visible = false; } } private void OnGUI() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Expected O, but got Unknown //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Expected O, but got Unknown //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Unknown result type (might be due to invalid IL or missing references) if (isListeningForKey && Event.current.isKey && (int)Event.current.keyCode > 0) { if (ConfigToggleKey != null) { ConfigToggleKey.Value = Event.current.keyCode; } isListeningForKey = false; Event.current.Use(); } if (Time.time < latestWarningUntil) { if (warningStyle == null) { Color textColor = Color32.op_Implicit(new Color32(byte.MaxValue, (byte)59, (byte)59, byte.MaxValue)); warningStyle = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)3, wordWrap = true, fontSize = 14, fontStyle = (FontStyle)1, padding = new RectOffset(0, 0, 0, 0), stretchHeight = true }; warningStyle.normal.textColor = textColor; warningStyle.onNormal.textColor = textColor; } GUIContent val = new GUIContent(latestWarning); float num = Mathf.Max(1f, Mathf.Min((float)Screen.width - 40f, 700f)); float num2 = Mathf.Clamp(warningStyle.CalcSize(val).x, Mathf.Min(220f, num), num); float num3 = Mathf.Max(1f, num2 - 24f); float num4 = warningStyle.CalcHeight(val, num3); float num5 = Mathf.Max(42f, num4 + 24f); GUI.color = Color.white; GUI.Box(new Rect(20f, 20f, num2, num5), GUIContent.none); GUI.Label(new Rect(32f, 28f, num3, num5 - 16f), val, warningStyle); GUI.color = Color.white; } if (ShowUI) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; InitStyles(); GUI.BringWindowToFront(8888); ((Rect)(ref windowRect)).width = Mathf.Min(((Rect)(ref windowRect)).width, Mathf.Max(280f, (float)Screen.width - 20f)); ((Rect)(ref windowRect)).height = Mathf.Min(((Rect)(ref windowRect)).height, Mathf.Max(260f, (float)Screen.height - 20f)); ((Rect)(ref windowRect)).x = Mathf.Clamp(((Rect)(ref windowRect)).x, 0f, Mathf.Max(0f, (float)Screen.width - ((Rect)(ref windowRect)).width)); ((Rect)(ref windowRect)).y = Mathf.Clamp(((Rect)(ref windowRect)).y, 0f, Mathf.Max(0f, (float)Screen.height - ((Rect)(ref windowRect)).height)); KeyCode val2 = (KeyCode)((ConfigToggleKey == null) ? 283 : ((int)ConfigToggleKey.Value)); windowRect = GUI.Window(8888, windowRect, new WindowFunction(DrawWindowContent), string.Format("BadScoutWatcher v{0} [Press {1} to Toggle]", "1.1.0", val2), windowStyle ?? GUI.skin.window); } } private void InitStyles() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Expected O, but got Unknown //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Expected O, but got Unknown //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Expected O, but got Unknown //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Expected O, but got Unknown //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Expected O, but got Unknown //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Expected O, but got Unknown //IL_0366: Unknown result type (might be due to invalid IL or missing references) //IL_0370: Expected O, but got Unknown //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_040d: Expected O, but got Unknown //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Expected O, but got Unknown //IL_043f: Unknown result type (might be due to invalid IL or missing references) //IL_0449: Expected O, but got Unknown //IL_0492: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_04dc: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Expected O, but got Unknown //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Expected O, but got Unknown int num = ((ConfigCornerRadius != null) ? ConfigCornerRadius.Value : 8); int num2 = Mathf.Clamp(num - 2, 2, 8); Color color = (Color)((ConfigBgColor != null) ? ConfigBgColor.Value : new Color(0.1f, 0.1f, 0.12f, 0.95f)); Color val = (Color)((ConfigBubbleColor != null) ? ConfigBubbleColor.Value : new Color(0.18f, 0.18f, 0.22f, 0.9f)); if ((Object)(object)windowBgTexture == (Object)null) { windowBgTexture = CreateRoundedTexture(64, 64, color, num); } if ((Object)(object)bubbleBgTexture == (Object)null) { bubbleBgTexture = CreateRoundedTexture(64, 64, val, Mathf.Max(2, num - 2)); } if ((Object)(object)buttonNormalTexture == (Object)null) { buttonNormalTexture = CreateRoundedTexture(64, 32, new Color(val.r * 1.15f, val.g * 1.15f, val.b * 1.15f, 0.95f), num2); } if ((Object)(object)buttonHoverTexture == (Object)null) { buttonHoverTexture = CreateRoundedTexture(64, 32, new Color(val.r * 1.45f, val.g * 1.45f, val.b * 1.45f, 1f), num2); } if ((Object)(object)buttonActiveTexture == (Object)null) { buttonActiveTexture = CreateRoundedTexture(64, 32, new Color(val.r * 0.85f, val.g * 0.85f, val.b * 0.85f, 1f), num2); } if ((Object)(object)tabNormalTexture == (Object)null) { tabNormalTexture = CreateRoundedTexture(64, 32, new Color(val.r * 1.6f, val.g * 1.6f, val.b * 1.6f, 0.95f), num); } if ((Object)(object)tabHoverTexture == (Object)null) { tabHoverTexture = CreateRoundedTexture(64, 32, new Color(val.r * 2f, val.g * 2f, val.b * 2f, 1f), num); } if (windowStyle == null) { windowStyle = new GUIStyle(GUI.skin.window); windowStyle.normal.background = windowBgTexture; windowStyle.onNormal.background = windowBgTexture; windowStyle.padding = new RectOffset(10, 10, 24, 10); windowStyle.border = new RectOffset(num + 2, num + 2, num + 2, num + 2); } if (bubbleStyle == null) { bubbleStyle = new GUIStyle(GUI.skin.box); bubbleStyle.normal.background = bubbleBgTexture; bubbleStyle.onNormal.background = bubbleBgTexture; bubbleStyle.padding = new RectOffset(8, 8, 6, 6); bubbleStyle.border = new RectOffset(num, num, num, num); } if (flatButtonStyle == null) { flatButtonStyle = new GUIStyle(GUI.skin.button); flatButtonStyle.normal.background = buttonNormalTexture; flatButtonStyle.hover.background = buttonHoverTexture; flatButtonStyle.active.background = buttonActiveTexture; flatButtonStyle.normal.textColor = Color.white; flatButtonStyle.hover.textColor = Color.white; flatButtonStyle.active.textColor = Color.white; flatButtonStyle.alignment = (TextAnchor)4; flatButtonStyle.border = new RectOffset(num2, num2, num2, num2); flatButtonStyle.padding = new RectOffset(6, 6, 1, 1); } if (tabButtonStyle == null) { tabButtonStyle = new GUIStyle(GUI.skin.button); tabButtonStyle.normal.background = tabNormalTexture; tabButtonStyle.hover.background = tabHoverTexture; tabButtonStyle.active.background = tabHoverTexture; tabButtonStyle.normal.textColor = Color.white; tabButtonStyle.hover.textColor = Color.white; tabButtonStyle.active.textColor = Color.white; tabButtonStyle.alignment = (TextAnchor)4; tabButtonStyle.border = new RectOffset(num, num, num, num); tabButtonStyle.padding = new RectOffset(8, 8, 2, 2); } } private Texture2D CreateRoundedTexture(int width, int height, Color color, int radius) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(width, height, (TextureFormat)4, false); Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { float num = 0f; if (j < radius && i < radius) { num = Vector2.Distance(new Vector2((float)j, (float)i), new Vector2((float)radius, (float)radius)); } else if (j >= width - radius && i < radius) { num = Vector2.Distance(new Vector2((float)j, (float)i), new Vector2((float)(width - radius - 1), (float)radius)); } else if (j < radius && i >= height - radius) { num = Vector2.Distance(new Vector2((float)j, (float)i), new Vector2((float)radius, (float)(height - radius - 1))); } else if (j >= width - radius && i >= height - radius) { num = Vector2.Distance(new Vector2((float)j, (float)i), new Vector2((float)(width - radius - 1), (float)(height - radius - 1))); } if (num > (float)radius) { array[i * width + j] = Color.clear; } else if (num > (float)radius - 1f) { array[i * width + j] = new Color(color.r, color.g, color.b, color.a * (1f - (num - ((float)radius - 1f)))); } else { array[i * width + j] = color; } } } val.SetPixels(array); val.Apply(); return val; } private void RebuildUIStyles() { if ((Object)(object)windowBgTexture != (Object)null) { Object.Destroy((Object)(object)windowBgTexture); } if ((Object)(object)bubbleBgTexture != (Object)null) { Object.Destroy((Object)(object)bubbleBgTexture); } if ((Object)(object)buttonNormalTexture != (Object)null) { Object.Destroy((Object)(object)buttonNormalTexture); } if ((Object)(object)buttonHoverTexture != (Object)null) { Object.Destroy((Object)(object)buttonHoverTexture); } if ((Object)(object)buttonActiveTexture != (Object)null) { Object.Destroy((Object)(object)buttonActiveTexture); } if ((Object)(object)tabNormalTexture != (Object)null) { Object.Destroy((Object)(object)tabNormalTexture); } if ((Object)(object)tabHoverTexture != (Object)null) { Object.Destroy((Object)(object)tabHoverTexture); } windowBgTexture = null; bubbleBgTexture = null; buttonNormalTexture = null; buttonHoverTexture = null; buttonActiveTexture = null; tabNormalTexture = null; tabHoverTexture = null; windowStyle = null; bubbleStyle = null; flatButtonStyle = null; tabButtonStyle = null; } private bool DrawCustomToggle(bool value, string label) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); bool result = GUILayout.Toggle(value, "", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(20f), GUILayout.Height(20f) }); if (GUILayout.Button(label, GUI.skin.label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(20f) })) { result = !value; } GUILayout.EndHorizontal(); return result; } private void DrawWindowContent(int windowID) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Lobby Watch", tabButtonStyle ?? GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f) })) { currentPage = 0; } if (GUILayout.Button("Color & Settings", tabButtonStyle ?? GUI.skin.button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f) })) { currentPage = 1; } GUILayout.EndHorizontal(); GUILayout.Space(6f); if (currentPage == 0) { DrawLobbyPage(); } else if (currentPage == 1) { DrawSettingsPage(); } GUI.DragWindow(); } private void DrawLobbyPage() { //IL_0227: Un