Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LightGuard v1.1.0
LightGuard.dll
Decompiled 16 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using Microsoft.CodeAnalysis; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LightGuard { [BepInPlugin("local.lightguard", "LightGuard", "1.1.0")] public class LightGuardPlugin : BaseUnityPlugin { private class Seen { public float LastHeard; public string LastVerdict = "clean"; public int Flags; } public const string PluginGuid = "local.lightguard"; public const string PluginName = "LightGuard"; public const string PluginVersion = "1.1.0"; private const string MSG_REPORT = "LightGuard_Report"; private ManualLogSource _log; private ConfigEntry<bool> _enabled; private ConfigEntry<bool> _requireForJoining; private ConfigEntry<float> _checkSeconds; private ConfigEntry<float> _quietPlayerSeconds; private ConfigEntry<string> _reportKey; private ConfigEntry<float> _maxNightVisionRange; private ConfigEntry<float> _maxNightVisionIntensity; private ConfigEntry<float> _maxAmbientIntensity; private ConfigEntry<bool> _watchFog; private ConfigEntry<bool> _watchScanNodes; private ConfigEntry<float> _maxScanNodeRange; private ConfigEntry<bool> _watchCamera; private ConfigEntry<float> _scanCheckSeconds; private ConfigEntry<bool> _logObservedValues; private Key _reportKeyParsed = (Key)99; private float _nextCheck; private bool _registeredMessage; private bool _loggedBaseline; private float _nextScanSweep; private int _cameraMaskBaseline = int.MinValue; private float _cameraFarBaseline = -1f; private int _stickyScanFlags; private readonly Dictionary<ulong, Seen> _heard = new Dictionary<ulong, Seen>(); private bool _loggedScanBaseline; private void Awake() { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Expected O, but got Unknown //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Expected O, but got Unknown //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Expected O, but got Unknown //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) _log = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", false, "OFF by default. This is only worth switching on once every player in your group has this mod, because its whole method is 'every machine reports in, and silence is suspicious'. Turn it on when you have shared it around."); _requireForJoining = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "RequireForJoining", true, "true = if LobbyCompatibility is installed, tell it this mod is required for everyone, so somebody without it is stopped at the join screen. Has no effect if LobbyCompatibility is missing."); _checkSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "CheckSeconds", 5f, new ConfigDescription("How often each machine checks its own lighting and reports in.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>())); _quietPlayerSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "QuietPlayerSeconds", 25f, new ConfigDescription("How long a player may go without reporting before the host lists them as quiet. Make it comfortably longer than CheckSeconds so a lag spike is not treated as suspicious.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 600f), Array.Empty<object>())); _reportKey = ((BaseUnityPlugin)this).Config.Bind<string>("1 - General", "ReportKey", "F6", "Press this as host to print who has reported in, who is quiet, and who has flagged themselves. Set to None to disable."); _maxNightVisionRange = ((BaseUnityPlugin)this).Config.Bind<float>("2 - What counts as tampering", "MaxNightVisionRange", 40f, new ConfigDescription("Night vision lighting up further than this is not normal. Turn on LogObservedValues for one round and read the real numbers before trusting this.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 10000f), Array.Empty<object>())); _maxNightVisionIntensity = ((BaseUnityPlugin)this).Config.Bind<float>("2 - What counts as tampering", "MaxNightVisionIntensity", 10f, new ConfigDescription("Night vision brighter than this is not normal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1000f), Array.Empty<object>())); _maxAmbientIntensity = ((BaseUnityPlugin)this).Config.Bind<float>("2 - What counts as tampering", "MaxAmbientIntensity", 3f, new ConfigDescription("Overall scene brightness above this usually means fullbright. Calibrate with LogObservedValues.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 100f), Array.Empty<object>())); _watchFog = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What counts as tampering", "WatchFog", true, "true = also flag when the fog has been switched off, which is the usual way of seeing through the dark indoors."); _watchScanNodes = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What counts as tampering", "WatchScanNodes", true, "true = also look for tampering with the scanner. The cheapest see-through-walls cheat in this game does not draw anything of its own - it just tells the game's own scanner to ignore walls and to reach across the whole map. That is visible on each object, so it can be checked."); _maxScanNodeRange = ((BaseUnityPlugin)this).Config.Bind<float>("2 - What counts as tampering", "MaxScanNodeRange", 200f, new ConfigDescription("A scannable object reachable from further away than this has been tampered with. Vanilla objects use small numbers; modded ones vary, so leave LogObservedValues on for a round and set this above the largest you actually see.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 100000f), Array.Empty<object>())); _watchCamera = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - What counts as tampering", "WatchCamera", true, "true = also notice if the game camera is changed to draw things it normally would not, or to see much further than usual. Catches some overlay cheats, though not all of them."); _scanCheckSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - What counts as tampering", "ScanCheckSeconds", 15f, new ConfigDescription("How often to sweep the scannable objects. This one costs a little more than the others, so it runs less often.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 300f), Array.Empty<object>())); _logObservedValues = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Calibration", "LogObservedValues", true, "true = print the real lighting numbers once per round so you can set the limits above sensibly. The defaults are estimates, not measurements."); _reportKeyParsed = ParseKey(_reportKey.Value, (Key)99); if (_enabled.Value && _requireForJoining.Value) { TryRequireForJoining(); } _log.LogInfo((object)("LightGuard 1.1.0 loaded" + (_enabled.Value ? "." : " (disabled in config)."))); if (_enabled.Value) { _log.LogWarning((object)"[LightGuard] Remember: this is a deterrent, not proof. It cannot catch somebody who edits it out, so never kick on its word alone."); } } private Key ParseKey(string text, Key fallback) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (Enum.TryParse<Key>(text, ignoreCase: true, out Key result)) { return result; } _log.LogWarning((object)string.Format("[{0}] '{1}' is not a key name I recognise, using {2}.", "LightGuard", text, fallback)); return fallback; } private void TryRequireForJoining() { try { Type type = Type.GetType("LobbyCompatibility.Features.PluginHelper, BMX.LobbyCompatibility"); Assembly[] assemblies; if (type == null) { assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { type = assemblies[i].GetType("LobbyCompatibility.Features.PluginHelper"); if (type != null) { break; } } } if (type == null) { _log.LogInfo((object)"[LightGuard] LobbyCompatibility is not installed, so joining cannot be gated on this mod."); return; } Type type2 = null; Type type3 = null; assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { type2 = type2 ?? assembly.GetType("LobbyCompatibility.Enums.CompatibilityLevel"); type3 = type3 ?? assembly.GetType("LobbyCompatibility.Enums.VersionStrictness"); } if (type2 == null || type3 == null) { return; } MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.Name == "RegisterPlugin" && methodInfo2.GetParameters().Length == 4) { methodInfo = methodInfo2; break; } } if (!(methodInfo == null)) { methodInfo.Invoke(null, new object[4] { "local.lightguard", new Version("1.1.0"), Enum.Parse(type2, "Everyone"), Enum.Parse(type3, "Minor") }); _log.LogInfo((object)"[LightGuard] Registered as required - players without it cannot join."); } } catch (Exception ex) { _log.LogWarning((object)("[LightGuard] Could not register with LobbyCompatibility: " + ex.Message)); } } private void Update() { if (!_enabled.Value) { return; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsListening) { _registeredMessage = false; return; } EnsureListening(); HandleReportKey(instance); if (!(Time.unscaledTime < _nextCheck)) { _nextCheck = Time.unscaledTime + _checkSeconds.Value; CheckMyOwnLighting(instance); if (((NetworkBehaviour)instance).IsServer) { ReviewWhoIsQuiet(instance); } } } private void EnsureListening() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (_registeredMessage) { return; } _registeredMessage = true; try { if (NetworkManager.Singleton.IsServer) { NetworkManager.Singleton.CustomMessagingManager.RegisterNamedMessageHandler("LightGuard_Report", new HandleNamedMessageDelegate(OnReportReceived)); } } catch (Exception ex) { _log.LogWarning((object)("[LightGuard] Could not start listening for reports: " + ex.Message)); } } private void OnReportReceived(ulong senderClientId, FastBufferReader reader) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) try { int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); if (!_heard.TryGetValue(senderClientId, out var value)) { value = new Seen(); _heard[senderClientId] = value; } value.LastHeard = Time.unscaledTime; value.Flags = num; value.LastVerdict = ((num == 0) ? "clean" : DescribeFlags(num)); if (num != 0) { _log.LogWarning((object)("[LightGuard] " + NameOf(senderClientId) + " reported tampered lighting: " + value.LastVerdict)); } } catch (Exception ex) { _log.LogWarning((object)("[LightGuard] Unreadable report: " + ex.Message)); } } private void CheckMyOwnLighting(StartOfRound round) { PlayerControllerB localPlayerController = round.localPlayerController; if ((Object)(object)localPlayerController == (Object)null) { return; } int num = 0; float num2 = 0f; float num3 = 0f; if ((Object)(object)localPlayerController.nightVision != (Object)null) { num2 = localPlayerController.nightVision.range; num3 = localPlayerController.nightVision.intensity; if (((Behaviour)localPlayerController.nightVision).enabled && !localPlayerController.isPlayerDead) { if (num2 > _maxNightVisionRange.Value) { num |= 1; } if (num3 > _maxNightVisionIntensity.Value) { num |= 2; } } } float ambientIntensity = RenderSettings.ambientIntensity; if (ambientIntensity > _maxAmbientIntensity.Value) { num |= 4; } num |= CheckCamera(localPlayerController); if (_watchScanNodes.Value && Time.unscaledTime >= _nextScanSweep) { _nextScanSweep = Time.unscaledTime + _scanCheckSeconds.Value; _stickyScanFlags = CheckScanNodes(); } num |= _stickyScanFlags; if (_watchFog.Value && localPlayerController.isInsideFactory && !RenderSettings.fog) { num |= 8; } if (_logObservedValues.Value && !_loggedBaseline && localPlayerController.isInsideFactory) { _loggedBaseline = true; _log.LogInfo((object)("[LightGuard] Your real values inside the facility - " + $"night vision range {num2:F1}, intensity {num3:F2}, " + $"ambient {ambientIntensity:F2}, fog {RenderSettings.fog}. " + "Set the limits a little above these.")); } SendReport(num); } private int CheckCamera(PlayerControllerB me) { if (!_watchCamera.Value || (Object)(object)me.gameplayCamera == (Object)null) { return 0; } int cullingMask = me.gameplayCamera.cullingMask; float farClipPlane = me.gameplayCamera.farClipPlane; if (_cameraMaskBaseline == int.MinValue) { _cameraMaskBaseline = cullingMask; _cameraFarBaseline = farClipPlane; return 0; } int num = 0; if (cullingMask != _cameraMaskBaseline) { num |= 0x40; } if (_cameraFarBaseline > 0f && farClipPlane > _cameraFarBaseline * 2f) { num |= 0x80; } return num; } private int CheckScanNodes() { try { ScanNodeProperties[] array = Object.FindObjectsOfType<ScanNodeProperties>(); if (array == null || array.Length == 0) { return 0; } int num = 0; float num2 = 0f; int num3 = 0; int num4 = 0; ScanNodeProperties[] array2 = array; foreach (ScanNodeProperties val in array2) { if (!((Object)(object)val == (Object)null)) { num4++; if ((float)val.maxRange > num2) { num2 = val.maxRange; } if (!val.requiresLineOfSight) { num3++; } } } if (num2 > _maxScanNodeRange.Value) { num |= 0x10; } if (num4 > 10 && num3 > num4 / 2) { num |= 0x20; } if (_logObservedValues.Value && !_loggedScanBaseline && num4 > 10) { _loggedScanBaseline = true; _log.LogInfo((object)(string.Format("[{0}] Scanner check - {1} scannable objects, ", "LightGuard", num4) + $"furthest reach {num2:F0}, {num3} ignore walls. " + "Set MaxScanNodeRange above that reach.")); } return num; } catch (Exception ex) { _log.LogWarning((object)("[LightGuard] Scanner check failed: " + ex.Message)); return 0; } } private unsafe void SendReport(int flags) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) try { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { return; } if (singleton.IsServer) { OnReportSelf(flags); return; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(4, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref flags, default(ForPrimitives)); singleton.CustomMessagingManager.SendNamedMessage("LightGuard_Report", 0uL, val, (NetworkDelivery)2); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } catch (Exception ex) { _log.LogWarning((object)("[LightGuard] Could not send my report: " + ex.Message)); } } private void OnReportSelf(int flags) { ulong localClientId = NetworkManager.Singleton.LocalClientId; if (!_heard.TryGetValue(localClientId, out var value)) { value = new Seen(); _heard[localClientId] = value; } value.LastHeard = Time.unscaledTime; value.Flags = flags; value.LastVerdict = ((flags == 0) ? "clean" : DescribeFlags(flags)); } private void ReviewWhoIsQuiet(StartOfRound round) { float unscaledTime = Time.unscaledTime; PlayerControllerB[] allPlayerScripts = round.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val == (Object)null || !val.isPlayerControlled) { continue; } ulong actualClientId = val.actualClientId; Seen value; bool flag = _heard.TryGetValue(actualClientId, out value); if (!flag || !(unscaledTime - value.LastHeard <= _quietPlayerSeconds.Value)) { if (!flag) { _heard[actualClientId] = new Seen { LastHeard = 0f, LastVerdict = "never reported" }; } else if (value.LastVerdict != "quiet") { value.LastVerdict = "quiet"; _log.LogWarning((object)("[LightGuard] " + val.playerUsername + " has not reported in for " + $"{_quietPlayerSeconds.Value:F0}s. Either they do not have this mod, " + "they have switched it off, or their connection is struggling. Worth a look, not proof of anything.")); } } } } private void HandleReportKey(StartOfRound round) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((int)_reportKeyParsed == 0) { return; } Keyboard current = Keyboard.current; if (current == null) { return; } KeyControl val = current[_reportKeyParsed]; if (val == null || !((ButtonControl)val).wasPressedThisFrame) { return; } _log.LogInfo((object)"=================== LightGuard ==================="); if (!((NetworkBehaviour)round).IsServer) { _log.LogInfo((object)" You are not hosting, so only your own machine reports."); } PlayerControllerB[] allPlayerScripts = round.allPlayerScripts; foreach (PlayerControllerB val2 in allPlayerScripts) { if (!((Object)(object)val2 == (Object)null) && val2.isPlayerControlled) { string text = "never reported"; float num = -1f; if (_heard.TryGetValue(val2.actualClientId, out var value)) { text = value.LastVerdict; num = Time.unscaledTime - value.LastHeard; } _log.LogInfo((object)(" " + val2.playerUsername + ": " + text + ((num >= 0f) ? $" (heard {num:F0}s ago)" : ""))); } } _log.LogInfo((object)" Silence means the mod is missing, switched off, or the connection is poor."); _log.LogInfo((object)"=================================================="); } private static string DescribeFlags(int flags) { List<string> list = new List<string>(); if ((flags & 1) != 0) { list.Add("night vision reaches too far"); } if ((flags & 2) != 0) { list.Add("night vision too bright"); } if ((flags & 4) != 0) { list.Add("scene brightness raised"); } if ((flags & 8) != 0) { list.Add("fog switched off indoors"); } if ((flags & 0x10) != 0) { list.Add("scanner reaches impossibly far"); } if ((flags & 0x20) != 0) { list.Add("scanner ignoring walls"); } if ((flags & 0x40) != 0) { list.Add("camera drawing things it should not"); } if ((flags & 0x80) != 0) { list.Add("camera seeing much further than normal"); } if (list.Count != 0) { return string.Join(", ", list.ToArray()); } return "clean"; } private string NameOf(ulong clientId) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return clientId.ToString(); } PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val != (Object)null && val.isPlayerControlled && val.actualClientId == clientId) { return val.playerUsername; } } return clientId.ToString(); } } }