using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dissonance.Integrations.Unity_NFGO; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.AI.Navigation; using Unity.Netcode; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 V81ErrorFix { internal enum PatchEnableMode { Auto, Enabled, Disabled } internal static class ErrorFixConfig { internal static ConfigEntry EnableGlobalDestroyGuard; internal static ConfigEntry GlobalDestroyGuardMode; internal static ConfigEntry AllowDestroyDuringSceneUnload; internal static ConfigEntry LifecycleDestroyWindowSeconds; internal static ConfigEntry LogBlockedDestroyStackTraceOnce; internal static ConfigEntry AudioSourcePlaybackGuardMode; internal static ConfigEntry KnownUnityWarningFilterMode; internal static ConfigEntry PlayerRagdollGlobalTagGuardMode; internal static ConfigEntry ParticleMeshShapeGuardMode; internal static ConfigEntry ParticleMeshShapeGuardDryRun; internal static ConfigEntry EntranceTeleportUpdateGuardMode; internal static ConfigEntry FindMainEntrancePositionFallbackMode; internal static ConfigEntry ThrowObjectClientRpcGuardMode; internal static ConfigEntry VoiceRefreshFallbackMode; internal static ConfigEntry UnlockableSuitGuardMode; internal static ConfigEntry NetworkObjectParentGuardMode; internal static ConfigEntry SteamValveDamageTriggerSpawnGuardMode; internal static ConfigEntry EnemyHealthBarsLateUpdateGuardMode; internal static ConfigEntry ShipLootPlusUiHelperGuardMode; internal static ConfigEntry NightVisionInsideLightingPostfixGuardMode; internal static ConfigEntry ChatCommandsStartHostPostfixGuardMode; internal static ConfigEntry EnableEnemyAINavMeshGuard; internal static ConfigEntry AllowEnemyAIWarp; internal static ConfigEntry EnemyAINavMeshMaxWarpRadius; internal static ConfigEntry EnemyAINavMeshHostServerOnly; internal static void Bind(ConfigFile config) { EnableGlobalDestroyGuard = config.Bind("NetworkObjectDestroy", "EnableGlobalDestroyGuard", true, "Legacy switch for the global Destroy guard. GlobalDestroyGuardMode can also disable this patch."); GlobalDestroyGuardMode = config.Bind("NetworkObjectDestroy", "GlobalDestroyGuardMode", PatchEnableMode.Disabled, "Only Enabled installs the global UnityEngine.Object.Destroy hook. Auto is treated as disabled for this global hook to avoid upgrade-time performance surprises. Requires restart."); AllowDestroyDuringSceneUnload = config.Bind("NetworkObjectDestroy", "AllowDestroyDuringSceneUnload", true, "Allows Destroy during network shutdown, ship scene unload, or lobby transitions."); LifecycleDestroyWindowSeconds = config.Bind("NetworkObjectDestroy", "LifecycleDestroyWindowSeconds", 3f, "Seconds after scene load/unload/active-scene changes where lifecycle Destroy calls are allowed. Values are clamped from 0 to 15."); LogBlockedDestroyStackTraceOnce = config.Bind("NetworkObjectDestroy", "LogBlockedDestroyStackTraceOnce", true, "Logs one stack trace for the first blocked spawned ragdoll Destroy call."); AudioSourcePlaybackGuardMode = config.Bind("Performance", "AudioSourcePlaybackGuardMode", PatchEnableMode.Disabled, "Only Enabled installs global AudioSource.Play* hooks. Auto is treated as disabled for this global hook. Requires restart."); KnownUnityWarningFilterMode = config.Bind("Performance", "KnownUnityWarningFilterMode", PatchEnableMode.Enabled, "Enabled by default to suppress high-frequency Unity log spam for missing audio spatializer plugin setup, BoxCollider negative scale/size asset warnings, the SteamValve(Clone) custom-filter AudioSource warning, and duplicate Static Lighting Sky baking warnings. This is log-only and does not repair the underlying audio plugin, collider geometry, AudioSource setup, or lighting setup. It filters only those exact warning prefixes, does not filter Netcode NetworkVariable lifecycle warnings, and requires restart."); PlayerRagdollGlobalTagGuardMode = config.Bind("Performance", "PlayerRagdollGlobalTagGuardMode", PatchEnableMode.Disabled, "Only Enabled installs global GameObject/Component tag lookup, CompareTag, and tag setter guards. Auto is treated as disabled for these global hooks. The targeted DeadBodyInfo ragdoll guard remains active. Requires restart."); ParticleMeshShapeGuardMode = config.Bind("Performance", "ParticleMeshShapeGuardMode", PatchEnableMode.Disabled, "Only Enabled starts ParticleSystem mesh shape scans after scene load. Auto is treated as disabled for this scene scan. Requires restart."); ParticleMeshShapeGuardDryRun = config.Bind("Performance", "ParticleMeshShapeGuardDryRun", false, "When true, ParticleMeshShapeGuard logs invalid particle mesh shapes without disabling them. Requires restart."); EntranceTeleportUpdateGuardMode = config.Bind("EntranceTeleport", "EntranceTeleportUpdateGuardMode", PatchEnableMode.Auto, "Auto enables the guarded EntranceTeleport.Update replacement only for the verified game assembly."); FindMainEntrancePositionFallbackMode = config.Bind("EntranceTeleport", "FindMainEntrancePositionFallbackMode", PatchEnableMode.Auto, "Auto enables the guarded RoundManager.FindMainEntrancePosition fallback only for the verified game assembly. Disabled preserves vanilla origin fallback."); ThrowObjectClientRpcGuardMode = config.Bind("PlayerControllerB", "ThrowObjectClientRpcGuardMode", PatchEnableMode.Auto, "Auto enables the ThrowObjectClientRpc guard only for the verified game assembly. Enabled forces it on; Disabled turns it off."); VoiceRefreshFallbackMode = config.Bind("Voice", "VoiceRefreshFallbackMode", PatchEnableMode.Auto, "Auto enables the voice refresh fallback only for the verified game assembly. Enabled forces it on; Disabled turns it off."); UnlockableSuitGuardMode = config.Bind("UnlockableSuit", "UnlockableSuitGuardMode", PatchEnableMode.Auto, "Auto enables suit/unlockable sync guards only for the verified game assembly. Enabled forces them on; Disabled turns them off."); NetworkObjectParentGuardMode = config.Bind("NetworkObjectParent", "NetworkObjectParentGuardMode", PatchEnableMode.Auto, "Auto suppresses only the known Netcode unspawned reparent SpawnStateException on the verified game assembly. Enabled forces it on; Disabled turns it off."); SteamValveDamageTriggerSpawnGuardMode = config.Bind("SteamValve", "SteamValveDamageTriggerSpawnGuardMode", PatchEnableMode.Disabled, "Disabled by default because the Netcode warning \"damageTrigger is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours\" is usually a one-time spawn lifecycle warning, not a performance issue. Only Enabled installs the experimental SteamValveHazard damageTrigger spawn guard, which temporarily activates the inactive damageTrigger InteractTrigger during Netcode spawn and then restores it inactive. Auto is treated as disabled for this guard to avoid changing object activation unless a SteamValve damageTrigger gameplay issue is confirmed. Requires restart."); EnemyHealthBarsLateUpdateGuardMode = config.Bind("OptionalCompatibility", "EnemyHealthBarsLateUpdateGuardMode", PatchEnableMode.Auto, "Auto enables the EnemyHealthBars HealthBar.LateUpdate compatibility guard only on a verified game assembly when the expected target signature is present. Enabled forces it on; Disabled turns it off."); ShipLootPlusUiHelperGuardMode = config.Bind("OptionalCompatibility", "ShipLootPlusUiHelperGuardMode", PatchEnableMode.Auto, "Auto enables the ShipLootPlus UiHelper compatibility guard only on a verified game assembly when at least one expected target signature is present. Enabled forces it on; Disabled turns it off."); NightVisionInsideLightingPostfixGuardMode = config.Bind("OptionalCompatibility", "NightVisionInsideLightingPostfixGuardMode", PatchEnableMode.Auto, "Auto enables the ToggleableNightVision InsideLightingPostfix compatibility guard only on a verified game assembly when the expected target signature is present. Enabled forces it on; Disabled turns it off."); ChatCommandsStartHostPostfixGuardMode = config.Bind("OptionalCompatibility", "ChatCommandsStartHostPostfixGuardMode", PatchEnableMode.Auto, "Auto enables the ChatCommands StartHost postfix compatibility guard only on a verified game assembly when the expected target signature is present. Enabled forces it on; Disabled turns it off."); EnableEnemyAINavMeshGuard = config.Bind("EnemyAI.NavMesh", "EnableEnemyAINavMeshGuard", true, "Suppresses known EnemyAI SetDestination errors when an agent is off the NavMesh."); AllowEnemyAIWarp = config.Bind("EnemyAI.NavMesh", "AllowEnemyAIWarp", false, "Allows the guard to warp an off-mesh enemy back to a nearby valid NavMesh point."); EnemyAINavMeshMaxWarpRadius = config.Bind("EnemyAI.NavMesh", "EnemyAINavMeshMaxWarpRadius", 16f, "Maximum radius used when looking for a nearby NavMesh recovery point."); EnemyAINavMeshHostServerOnly = config.Bind("EnemyAI.NavMesh", "EnemyAINavMeshHostServerOnly", true, "Only allows active EnemyAI NavMesh recovery on host/server. Non-authority clients only suppress the unsafe tick."); } } internal static class GameAssemblyIdentity { internal const string VerifiedAssemblySha256 = "5f7db5538b78dc408845a3002907619785ac9f9c6b6059d13dc9a602d9b65731"; internal const string VerifiedAssemblyMvid = "aca1e98d-6f84-4d3f-85cd-22b6f7be2f9b"; internal static string CurrentAssemblySha256 { get; private set; } internal static string CurrentAssemblyMvid { get; private set; } internal static bool IsVerified { get; private set; } internal static void Initialize() { Assembly assembly = typeof(StartOfRound).Assembly; CurrentAssemblyMvid = assembly.ManifestModule.ModuleVersionId.ToString(); CurrentAssemblySha256 = TryComputeSha256(assembly.Location); IsVerified = string.Equals(CurrentAssemblyMvid, "aca1e98d-6f84-4d3f-85cd-22b6f7be2f9b", StringComparison.OrdinalIgnoreCase) && string.Equals(CurrentAssemblySha256, "5f7db5538b78dc408845a3002907619785ac9f9c6b6059d13dc9a602d9b65731", StringComparison.OrdinalIgnoreCase); } private static string TryComputeSha256(string path) { try { if (string.IsNullOrEmpty(path) || !File.Exists(path)) { return null; } using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(path); byte[] array = sHA.ComputeHash(inputStream); return BitConverter.ToString(array).Replace("-", string.Empty).ToLowerInvariant(); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not compute Assembly-CSharp SHA256: " + ex.GetType().Name + ".")); } return null; } } } internal static class NullRefGuard { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly HashSet LoggedExceptionDetails = new HashSet(); internal static Exception Suppress(Exception exception, string key, Func isKnownSafeCase) { if (exception == null) { return null; } if (!(exception is NullReferenceException)) { return exception; } if (!IsKnownSafeCase(key, isKnownSafeCase)) { return exception; } Warnings.Warn(key, () => LoggedExceptionDetails.Add(key) ? $"Suppressed known NullReferenceException in {key}. First exception detail: {exception}" : ("Suppressed known NullReferenceException in " + key + "; the object will safely retry on later updates.")); return null; } internal static void Clear() { Warnings.Clear(); LoggedExceptionDetails.Clear(); } private static bool IsKnownSafeCase(string key, Func isKnownSafeCase) { if (isKnownSafeCase == null) { return false; } try { return isKnownSafeCase(); } catch (Exception ex) { Warnings.Warn("classifier-failed|" + key, "NullRefGuard classifier failed for " + key + "; returning original exception: " + ex.GetType().Name + "."); return false; } } } internal sealed class ParticleMeshShapeGuard : MonoBehaviour { private sealed class ParticleMeshWarningBatch { internal readonly string MeshName; internal readonly string Reason; internal readonly bool DryRun; internal readonly HashSet ParticleSystemNames = new HashSet(); internal int OmittedParticleSystemCount; internal ParticleMeshWarningBatch(string meshName, string reason, bool dryRun) { MeshName = meshName; Reason = reason; DryRun = dryRun; } internal void AddParticleSystemName(string particleSystemName) { if (ParticleSystemNames.Count < 16) { ParticleSystemNames.Add(particleSystemName); } else if (!ParticleSystemNames.Contains(particleSystemName)) { OmittedParticleSystemCount++; } } } private sealed class CachedMeshInspection { internal readonly Mesh Mesh; internal readonly MeshInspectionStatus Status; internal readonly string InvalidReason; internal CachedMeshInspection(Mesh mesh, MeshInspectionStatus status, string invalidReason) { Mesh = mesh; Status = status; InvalidReason = invalidReason; } } private sealed class MeshInspectionProgress { internal readonly Mesh Mesh; internal readonly int VertexCount; internal readonly int SubMeshCount; internal readonly int SubMeshIndex; internal readonly int TriangleIndex; internal readonly double Area; internal MeshInspectionProgress(Mesh mesh, int vertexCount, int subMeshCount, int subMeshIndex, int triangleIndex, double area) { Mesh = mesh; VertexCount = vertexCount; SubMeshCount = subMeshCount; SubMeshIndex = subMeshIndex; TriangleIndex = triangleIndex; Area = area; } } private enum MeshInspectionStatus { Valid, Invalid, Incomplete } private const int ScanBatchSize = 64; private const float ScanFrameBudgetSeconds = 0.0025f; private const int MeshInspectionCacheCleanupThreshold = 256; private const int MaxFullAreaScanVertices = 32768; private const int MaxFullAreaScanIndices = 98304; private const int MaxPendingWarningBatches = 64; private const int MaxParticleSystemNamesPerWarning = 16; private static ParticleMeshShapeGuard _instance; private bool _scanRequested; private ParticleSystem[] _scanQueue; private int _scanIndex; private readonly Dictionary _patchedParticleSystems = new Dictionary(); private readonly WarningLimiter _warnings = new WarningLimiter(); private readonly Dictionary _pendingWarnings = new Dictionary(); private readonly Dictionary _meshInspectionCache = new Dictionary(); private readonly Dictionary _meshInspectionProgress = new Dictionary(); private List _meshVertices = new List(); private List _meshTriangles = new List(); internal static void EnsureCreated() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown ParticleMeshShapeGuard particleMeshShapeGuard = Object.FindObjectOfType(); if ((Object)(object)particleMeshShapeGuard != (Object)null) { _instance = particleMeshShapeGuard; _instance.RequestSceneScan(); return; } GameObject val = new GameObject("V81ErrorFix.ParticleMeshShapeGuard"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; _instance = val.AddComponent(); _instance.RequestSceneScan(); } internal static void NotifySceneLoaded() { if (!((Object)(object)_instance == (Object)null)) { _instance.RequestSceneScan(); } } internal static void NotifySceneUnloaded() { if (!((Object)(object)_instance == (Object)null)) { _instance.ClearSceneCaches(); } } private void Awake() { _instance = this; } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } } private void Update() { if (_scanQueue != null) { ContinueParticleSystemScan(); } else if (_scanRequested) { _scanRequested = false; BeginParticleSystemScan(); ContinueParticleSystemScan(); } } private void RequestSceneScan() { _scanRequested = true; } private void BeginParticleSystemScan() { _pendingWarnings.Clear(); _scanQueue = Object.FindObjectsOfType(true); _scanIndex = 0; } private void ContinueParticleSystemScan() { if (_scanQueue != null) { int num = Math.Min(_scanIndex + 64, _scanQueue.Length); float num2 = Time.realtimeSinceStartup + 0.0025f; while (_scanIndex < num && Time.realtimeSinceStartup < num2) { TryPatchParticleSystem(_scanQueue[_scanIndex], num2); _scanIndex++; } if (_scanIndex >= _scanQueue.Length) { _scanQueue = null; _scanIndex = 0; FlushParticleMeshWarnings(); } } } private void TryPatchParticleSystem(ParticleSystem particleSystem, float scanDeadline) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)particleSystem == (Object)null) { return; } int instanceID = ((Object)particleSystem).GetInstanceID(); if (_patchedParticleSystems.TryGetValue(instanceID, out var value) && (Object)(object)value == (Object)(object)particleSystem) { return; } try { ShapeModule shape = particleSystem.shape; if (!((ShapeModule)(ref shape)).enabled || !TryGetShapeMesh(shape, out var mesh) || (Object)(object)mesh == (Object)null) { return; } string invalidReason; switch (GetInvalidMeshReasonCached(mesh, scanDeadline, out invalidReason)) { case MeshInspectionStatus.Incomplete: _scanIndex--; return; case MeshInspectionStatus.Valid: return; } if (!IsDryRun()) { ((ShapeModule)(ref shape)).enabled = false; } _patchedParticleSystems[instanceID] = particleSystem; QueueParticleMeshWarning(((Object)mesh).name, invalidReason, ((Object)particleSystem).name, IsDryRun()); } catch (Exception ex) { _patchedParticleSystems[instanceID] = particleSystem; QueueParticleMeshWarning("inspection", "could not be inspected safely: " + ex.GetType().Name, ((Object)particleSystem).name); } } private void ClearSceneCaches() { _scanQueue = null; _scanIndex = 0; _scanRequested = false; _pendingWarnings.Clear(); _patchedParticleSystems.Clear(); _meshInspectionCache.Clear(); _meshInspectionProgress.Clear(); _warnings.Clear(); } private void QueueParticleMeshWarning(string meshName, string reason, string particleSystemName, bool dryRun = false) { string key = $"{meshName}|{reason}|{dryRun}"; if (!_warnings.CanWarn(key)) { return; } if (!_pendingWarnings.TryGetValue(key, out var value)) { if (_pendingWarnings.Count >= 64) { return; } value = new ParticleMeshWarningBatch(meshName, reason, dryRun); _pendingWarnings[key] = value; } value.AddParticleSystemName(particleSystemName); } private void FlushParticleMeshWarnings() { foreach (KeyValuePair pendingWarning in _pendingWarnings) { ParticleMeshWarningBatch warningBatch = pendingWarning.Value; _warnings.Warn(pendingWarning.Key, delegate { string text = string.Join(", ", warningBatch.ParticleSystemNames); if (warningBatch.OmittedParticleSystemCount > 0) { text = $"{text}, and {warningBatch.OmittedParticleSystemCount} more"; } string text2 = (warningBatch.DryRun ? "Would disable" : "Disabled"); return text2 + " mesh shape because mesh '" + warningBatch.MeshName + "' " + warningBatch.Reason + " on particle systems: " + text + "."; }); } } private MeshInspectionStatus GetInvalidMeshReasonCached(Mesh mesh, float scanDeadline, out string invalidReason) { invalidReason = null; int instanceID = ((Object)mesh).GetInstanceID(); if (_meshInspectionCache.TryGetValue(instanceID, out var value) && (Object)(object)value.Mesh == (Object)(object)mesh) { invalidReason = value.InvalidReason; return value.Status; } MeshInspectionStatus invalidMeshReason = GetInvalidMeshReason(mesh, instanceID, scanDeadline, out invalidReason); if (invalidMeshReason == MeshInspectionStatus.Incomplete) { return invalidMeshReason; } _meshInspectionProgress.Remove(instanceID); _meshInspectionCache[instanceID] = new CachedMeshInspection(mesh, invalidMeshReason, invalidReason); CleanupMeshInspectionCacheIfNeeded(); return invalidMeshReason; } private void CleanupMeshInspectionCacheIfNeeded() { if (_meshInspectionCache.Count < 256) { return; } List list = null; foreach (KeyValuePair item in _meshInspectionCache) { if (!((Object)(object)item.Value.Mesh != (Object)null)) { if (list == null) { list = new List(); } list.Add(item.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { _meshInspectionCache.Remove(list[i]); } } } private static bool TryGetShapeMesh(ShapeModule shape, out Mesh mesh) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 mesh = null; ParticleSystemShapeType shapeType = ((ShapeModule)(ref shape)).shapeType; ParticleSystemShapeType val = shapeType; if ((int)val != 6) { if ((int)val != 13) { if ((int)val == 14) { mesh = (((Object)(object)((ShapeModule)(ref shape)).skinnedMeshRenderer != (Object)null) ? ((ShapeModule)(ref shape)).skinnedMeshRenderer.sharedMesh : null); return (Object)(object)mesh != (Object)null; } return false; } object obj; if (!((Object)(object)((ShapeModule)(ref shape)).meshRenderer != (Object)null)) { obj = null; } else { MeshFilter component = ((Component)((ShapeModule)(ref shape)).meshRenderer).GetComponent(); obj = ((component != null) ? component.sharedMesh : null); } mesh = (Mesh)obj; return (Object)(object)mesh != (Object)null; } mesh = ((ShapeModule)(ref shape)).mesh; return (Object)(object)mesh != (Object)null; } private MeshInspectionStatus GetInvalidMeshReason(Mesh mesh, int meshId, float scanDeadline, out string invalidReason) { //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_024a: 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_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) invalidReason = null; if (!mesh.isReadable) { invalidReason = "is not readable"; return MeshInspectionStatus.Invalid; } int vertexCount = mesh.vertexCount; int subMeshCount = mesh.subMeshCount; if (vertexCount == 0 || subMeshCount <= 0) { invalidReason = "has zero surface area"; return MeshInspectionStatus.Invalid; } try { bool flag = false; ulong num = 0uL; for (int i = 0; i < subMeshCount; i++) { uint indexCount = mesh.GetIndexCount(i); if (indexCount >= 3) { flag = true; } num += indexCount; } if (!flag) { invalidReason = "has zero surface area"; return MeshInspectionStatus.Invalid; } if (vertexCount > 32768 || num > 98304) { return MeshInspectionStatus.Valid; } MeshInspectionProgress meshInspectionProgress = null; if (_meshInspectionProgress.TryGetValue(meshId, out var value) && (Object)(object)value.Mesh == (Object)(object)mesh && value.VertexCount == vertexCount && value.SubMeshCount == subMeshCount) { meshInspectionProgress = value; } _meshVertices.Clear(); _meshTriangles.Clear(); mesh.GetVertices(_meshVertices); int num2 = Math.Min(meshInspectionProgress?.SubMeshIndex ?? 0, subMeshCount - 1); for (int j = num2; j < subMeshCount; j++) { _meshTriangles.Clear(); mesh.GetTriangles(_meshTriangles, j); if (_meshTriangles.Count < 3) { continue; } double num3 = ((j != num2) ? 0.0 : (meshInspectionProgress?.Area ?? 0.0)); int num4 = ((j == num2) ? (meshInspectionProgress?.TriangleIndex ?? 0) : 0); num4 -= num4 % 3; for (int k = num4; k + 2 < _meshTriangles.Count; k += 3) { Vector3 val = _meshVertices[_meshTriangles[k]]; Vector3 val2 = _meshVertices[_meshTriangles[k + 1]]; Vector3 val3 = _meshVertices[_meshTriangles[k + 2]]; double num5 = num3; Vector3 val4 = Vector3.Cross(val2 - val, val3 - val); num3 = num5 + (double)((Vector3)(ref val4)).magnitude * 0.5; if (num3 > 0.0001) { return MeshInspectionStatus.Valid; } if (Time.realtimeSinceStartup >= scanDeadline) { _meshInspectionProgress[meshId] = new MeshInspectionProgress(mesh, vertexCount, subMeshCount, j, Math.Min(k + 3, _meshTriangles.Count), num3); return MeshInspectionStatus.Incomplete; } } } } catch { invalidReason = "could not be inspected safely"; return MeshInspectionStatus.Invalid; } finally { TrimMeshScratchLists(); } invalidReason = "has zero surface area"; return MeshInspectionStatus.Invalid; } private void TrimMeshScratchLists() { if (_meshVertices.Capacity > 65536) { _meshVertices = new List(); } else { _meshVertices.Clear(); } if (_meshTriangles.Capacity > 65536) { _meshTriangles = new List(); } else { _meshTriangles.Clear(); } } private static bool IsDryRun() { return ErrorFixConfig.ParticleMeshShapeGuardDryRun != null && ErrorFixConfig.ParticleMeshShapeGuardDryRun.Value; } } internal sealed class WarningLimiter { private const string OverflowKey = "__overflow__"; private static readonly List SceneScopedLimiters = new List(); private readonly int _maxWarnings; private readonly int _maxKeyCount; private readonly Dictionary _warningCounts = new Dictionary(); internal WarningLimiter(int maxWarnings = 5, int maxKeyCount = 512, bool clearOnSceneChange = true) { _maxWarnings = maxWarnings; _maxKeyCount = Math.Max(1, maxKeyCount); if (clearOnSceneChange) { SceneScopedLimiters.Add(this); } } internal void Warn(string key, string message) { if (TryIncrement(key, out var warningCount)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{message} ({warningCount}/{_maxWarnings})"); } } } internal void Warn(string key, Func messageFactory) { if (TryIncrement(key, out var warningCount)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{messageFactory()} ({warningCount}/{_maxWarnings})"); } } } internal bool CanWarn(string key) { _warningCounts.TryGetValue(GetEffectiveKeyForRead(NormalizeKey(key)), out var value); return value < _maxWarnings; } internal int KeyCount() { return _warningCounts.Count; } internal void Clear() { _warningCounts.Clear(); } internal void ClearPrefix(string prefix) { if (string.IsNullOrEmpty(prefix) || _warningCounts.Count == 0) { return; } List list = null; foreach (string key in _warningCounts.Keys) { if (key.StartsWith(prefix, StringComparison.Ordinal)) { if (list == null) { list = new List(); } list.Add(key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { _warningCounts.Remove(list[i]); } } } internal static void ClearSceneScopedLimiters() { for (int i = 0; i < SceneScopedLimiters.Count; i++) { SceneScopedLimiters[i]?.Clear(); } } private bool TryIncrement(string key, out int warningCount) { key = NormalizeKey(key); key = ApplyKeyLimit(key); _warningCounts.TryGetValue(key, out warningCount); if (warningCount >= _maxWarnings) { return false; } warningCount++; _warningCounts[key] = warningCount; return true; } private string ApplyKeyLimit(string incomingKey) { if (_warningCounts.ContainsKey(incomingKey) || _warningCounts.Count < _maxKeyCount) { return incomingKey; } if (_warningCounts.ContainsKey("__overflow__") || _warningCounts.Count < _maxKeyCount + 1) { return "__overflow__"; } string text = null; foreach (string key in _warningCounts.Keys) { if (key == "__overflow__") { continue; } text = key; break; } if (text != null) { _warningCounts.Remove(text); } return "__overflow__"; } private string GetEffectiveKeyForRead(string incomingKey) { if (_warningCounts.ContainsKey(incomingKey) || _warningCounts.Count < _maxKeyCount) { return incomingKey; } return "__overflow__"; } private static string NormalizeKey(string key) { return string.IsNullOrEmpty(key) ? "unknown" : key; } } [HarmonyPatch] internal static class DisabledAudioSourcePlayGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter GuardFailureWarnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PatchModeUtility.IsExplicitlyEnabled(ErrorFixConfig.AudioSourcePlaybackGuardMode); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(AudioSource).GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo method in methods) { if (method.DeclaringType == typeof(AudioSource) && IsPlaybackMethod(method)) { yield return method; } } } private static bool Prefix(AudioSource __instance, MethodBase __originalMethod) { try { return ShouldAllowPlayback(__instance, __originalMethod); } catch (Exception ex) { GuardFailureWarnings.Warn("guard-failure", "Disabled AudioSource guard failed safely and allowed original playback: " + ex.GetType().Name + "."); return true; } } private static bool ShouldAllowPlayback(AudioSource audioSource, MethodBase originalMethod) { if ((Object)(object)audioSource == (Object)null || ((Behaviour)audioSource).isActiveAndEnabled) { return true; } string methodName = originalMethod?.Name ?? "Play"; string key = $"{((Object)audioSource).GetInstanceID()}|{methodName}"; Warnings.Warn(key, delegate { string transformPath = GetTransformPath(((Component)audioSource).transform); return $"Suppressed disabled AudioSource {methodName} on '{transformPath}' because AudioSource.enabled={((Behaviour)audioSource).enabled}, activeInHierarchy={(Object)(object)((Component)audioSource).gameObject != (Object)null && ((Component)audioSource).gameObject.activeInHierarchy}."; }); return false; } private static bool IsPlaybackMethod(MethodInfo method) { return method.ReturnType == typeof(void) && (method.Name == "Play" || method.Name == "PlayDelayed" || method.Name == "PlayScheduled" || method.Name == "PlayOneShot"); } internal static string GetTransformPath(Transform transform) { if ((Object)(object)transform == (Object)null) { return "unknown"; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } [HarmonyPatch] internal static class AudioSourcePlayOneShotNullClipGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter GuardFailureWarnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PatchModeUtility.IsExplicitlyEnabled(ErrorFixConfig.AudioSourcePlaybackGuardMode); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { MethodInfo playOneShot = AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[1] { typeof(AudioClip) }, (Type[])null); if (playOneShot != null) { yield return playOneShot; } MethodInfo playOneShotWithScale = AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[2] { typeof(AudioClip), typeof(float) }, (Type[])null); if (playOneShotWithScale != null) { yield return playOneShotWithScale; } } private static bool Prefix(AudioSource __instance, AudioClip __0) { try { if ((Object)(object)__0 != (Object)null) { return true; } WarnNullClip(__instance); return false; } catch (Exception ex) { GuardFailureWarnings.Warn("guard-failure", "AudioSource null AudioClip guard failed safely and allowed original PlayOneShot: " + ex.GetType().Name + "."); return true; } } private static void WarnNullClip(AudioSource audioSource) { string key = (((Object)(object)audioSource != (Object)null) ? $"null-clip|{((Object)audioSource).GetInstanceID()}" : "null-clip|unknown"); Warnings.Warn(key, delegate { string text = (((Object)(object)audioSource != (Object)null) ? DisabledAudioSourcePlayGuardPatch.GetTransformPath(((Component)audioSource).transform) : "unknown"); return "Suppressed AudioSource.PlayOneShot on '" + text + "' because AudioClip was null."; }); } } [HarmonyPatch(typeof(BushWolfEnemy), "Update")] internal static class BushWolfEnemyUpdatePatch { private static bool Prefix(BushWolfEnemy __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null) { return false; } if ((Object)(object)((EnemyAI)__instance).agent == (Object)null || (Object)(object)((EnemyAI)__instance).creatureAnimator == (Object)null || (Object)(object)__instance.animationContainer == (Object)null) { return false; } return true; } private static Exception Finalizer(BushWolfEnemy __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } if ((Object)(object)__instance != (Object)null) { try { ((EnemyAI)__instance).targetPlayer = null; ((EnemyAI)__instance).movingTowardsTargetPlayer = false; } catch { } } return NullRefGuard.Suppress(__exception, "BushWolfEnemy.Update", () => (Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)((EnemyAI)__instance).agent == (Object)null || (Object)(object)((EnemyAI)__instance).creatureAnimator == (Object)null || (Object)(object)__instance.animationContainer == (Object)null); } } [HarmonyPatch(typeof(BushWolfEnemy), "LateUpdate")] internal static class BushWolfEnemyLateUpdatePatch { private static bool Prefix(BushWolfEnemy __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null) { return false; } if ((Object)(object)__instance.tongue == (Object)null || (Object)(object)__instance.tongueStartPoint == (Object)null || (Object)(object)__instance.animationContainer == (Object)null || (Object)(object)__instance.bendHeadBack == (Object)null || __instance.proceduralBodyTargets == null || __instance.IKTargetContainers == null || __instance.IKTargetContainers.Length < __instance.proceduralBodyTargets.Length) { return false; } return true; } private static Exception Finalizer(BushWolfEnemy __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "BushWolfEnemy.LateUpdate", () => (Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)__instance.tongue == (Object)null || (Object)(object)__instance.tongueStartPoint == (Object)null || (Object)(object)__instance.animationContainer == (Object)null || (Object)(object)__instance.bendHeadBack == (Object)null || __instance.proceduralBodyTargets == null || __instance.IKTargetContainers == null || __instance.IKTargetContainers.Length < __instance.proceduralBodyTargets.Length); } } [HarmonyPatch(typeof(DocileLocustBeesAI), "Update")] internal static class DocileLocustBeesAIUpdatePatch { private static bool Prefix(DocileLocustBeesAI __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.activeCamera == (Object)null || (Object)(object)__instance.bugsEffect == (Object)null || (Object)(object)((EnemyAI)__instance).creatureVoice == (Object)null || (Object)(object)__instance.scanNode == (Object)null) { return false; } if (((EnemyAI)__instance).currentBehaviourStateIndex == 1 && ((Object)(object)((EnemyAI)__instance).creatureSFX == (Object)null || (Object)(object)((EnemyAI)__instance).enemyType == (Object)null || ((EnemyAI)__instance).enemyType.audioClips == null || ((EnemyAI)__instance).enemyType.audioClips.Length == 0 || (Object)(object)RoundManager.Instance == (Object)null)) { return false; } return true; } private static Exception Finalizer(DocileLocustBeesAI __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "DocileLocustBeesAI.Update", () => (Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.activeCamera == (Object)null || (Object)(object)__instance.bugsEffect == (Object)null || (Object)(object)((EnemyAI)__instance).creatureVoice == (Object)null || (Object)(object)__instance.scanNode == (Object)null || (((EnemyAI)__instance).currentBehaviourStateIndex == 1 && ((Object)(object)((EnemyAI)__instance).creatureSFX == (Object)null || (Object)(object)((EnemyAI)__instance).enemyType == (Object)null || ((EnemyAI)__instance).enemyType.audioClips == null || ((EnemyAI)__instance).enemyType.audioClips.Length == 0 || (Object)(object)RoundManager.Instance == (Object)null))); } } [HarmonyPatch(typeof(CrawlerAI), "Update")] internal static class CrawlerAIUpdatePatch { private static bool Prefix(CrawlerAI __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null) { return false; } if ((Object)(object)((EnemyAI)__instance).agent == (Object)null || (Object)(object)((EnemyAI)__instance).creatureAnimator == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null || __instance.searchForPlayers == null) { return false; } return true; } private static Exception Finalizer(CrawlerAI __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } if ((Object)(object)__instance != (Object)null) { try { ((EnemyAI)__instance).movingTowardsTargetPlayer = false; } catch { } } return NullRefGuard.Suppress(__exception, "CrawlerAI.Update", () => (Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null || (Object)(object)((EnemyAI)__instance).agent == (Object)null || (Object)(object)((EnemyAI)__instance).creatureAnimator == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null || __instance.searchForPlayers == null); } } [HarmonyPatch(typeof(EntranceTeleport), "Update")] internal static class EntranceTeleportUpdatePatch { private sealed class HoverTipState { internal string DefaultHoverTip; } private const float EnemyNearDistanceSqr = 59.289997f; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly ConditionalWeakTable HoverTipStates = new ConditionalWeakTable(); private static bool Prefix(EntranceTeleport __instance) { //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) if (!IsPatchEnabled()) { return true; } if ((Object)(object)__instance == (Object)null || !__instance.isEntranceToBuilding || (Object)(object)RoundManager.Instance == (Object)null) { return false; } InteractTrigger triggerScript = __instance.triggerScript; if ((Object)(object)triggerScript == (Object)null) { Warnings.Warn("missing-trigger", "Skipped EntranceTeleport.Update guard because triggerScript was missing."); return false; } if (__instance.checkForEnemiesInterval > 0f) { __instance.checkForEnemiesInterval -= Time.deltaTime; return false; } if (!__instance.gotExitPoint) { if (__instance.FindExitPoint()) { __instance.gotExitPoint = true; } else { Warnings.Warn("missing-exit|" + GetEntranceName(__instance), "Skipped EntranceTeleport.Update for '" + GetEntranceName(__instance) + "' because no exit point was found."); } return false; } if (((Object)(object)__instance.exitScript == (Object)null || (Object)(object)__instance.exitScript.entrancePoint == (Object)null) && !__instance.FindExitPoint()) { Warnings.Warn("missing-exit-point|" + GetEntranceName(__instance), "Skipped EntranceTeleport.Update for '" + GetEntranceName(__instance) + "' because exitScript or entrancePoint was missing."); return false; } __instance.checkForEnemiesInterval = 1f; bool flag = false; if (RoundManager.Instance.SpawnedEnemies != null) { foreach (EnemyAI spawnedEnemy in RoundManager.Instance.SpawnedEnemies) { if (!((Object)(object)spawnedEnemy == (Object)null) && !((Object)(object)((Component)spawnedEnemy).transform == (Object)null) && !spawnedEnemy.isEnemyDead) { if ((Object)(object)__instance.exitScript == (Object)null || (Object)(object)__instance.exitScript.entrancePoint == (Object)null) { break; } Vector3 val = ((Component)spawnedEnemy).transform.position - __instance.exitScript.entrancePoint.position; if (((Vector3)(ref val)).sqrMagnitude < 59.289997f) { flag = true; break; } } } } if (flag && !__instance.enemyNearLastCheck) { __instance.enemyNearLastCheck = true; SaveDefaultHoverTip(__instance, triggerScript); triggerScript.hoverTip = "[Near activity detected!]"; } else if (!flag && __instance.enemyNearLastCheck) { __instance.enemyNearLastCheck = false; triggerScript.hoverTip = GetDefaultHoverTip(__instance, triggerScript); } return false; } private static Exception Finalizer(EntranceTeleport __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "EntranceTeleport.Update", () => IsPatchEnabled() && ((Object)(object)__instance == (Object)null || (Object)(object)RoundManager.Instance == (Object)null || (Object)(object)__instance.triggerScript == (Object)null || (Object)(object)__instance.exitScript == (Object)null || (Object)(object)__instance.exitScript.entrancePoint == (Object)null)); } private static bool IsPatchEnabled() { return PatchModeUtility.IsEnabled(ErrorFixConfig.EntranceTeleportUpdateGuardMode); } private static void SaveDefaultHoverTip(EntranceTeleport entrance, InteractTrigger trigger) { if (!((Object)(object)entrance == (Object)null) && !((Object)(object)trigger == (Object)null)) { HoverTipState orCreateValue = HoverTipStates.GetOrCreateValue(entrance); if (!string.Equals(trigger.hoverTip, "[Near activity detected!]", StringComparison.Ordinal)) { orCreateValue.DefaultHoverTip = trigger.hoverTip; } } } private static string GetDefaultHoverTip(EntranceTeleport entrance, InteractTrigger trigger) { if ((Object)(object)entrance != (Object)null && HoverTipStates.TryGetValue(entrance, out var value) && !string.IsNullOrEmpty(value.DefaultHoverTip)) { return value.DefaultHoverTip; } return ((Object)(object)trigger != (Object)null) ? trigger.hoverTip : string.Empty; } private static string GetEntranceName(EntranceTeleport entrance) { return ((Object)(object)entrance != (Object)null && (Object)(object)((Component)entrance).gameObject != (Object)null) ? ((Object)((Component)entrance).gameObject).name : "unknown"; } } [HarmonyPatch(typeof(RoundManager), "FindMainEntrancePosition")] internal static class RoundManagerFindMainEntrancePositionPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static EntranceTeleport[] cachedEntrances; private static int cachedSceneHandle = int.MinValue; [HarmonyPrepare] private static bool Prepare() { return PatchModeUtility.IsEnabled(ErrorFixConfig.FindMainEntrancePositionFallbackMode); } private static bool Prefix(bool getTeleportPosition, bool getOutsideEntrance, ref Vector3 __result) { //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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) EntranceTeleport[] array = GetCachedEntrances(); EntranceTeleport fallbackEntrance = null; EntranceTeleport fallbackMainEntrance = null; if (TryFindEntrance(array, getTeleportPosition, getOutsideEntrance, ref __result, ref fallbackEntrance, ref fallbackMainEntrance)) { return false; } if ((Object)(object)fallbackEntrance == (Object)null && array.Length != 0) { array = RefreshEntranceCache(); if (TryFindEntrance(array, getTeleportPosition, getOutsideEntrance, ref __result, ref fallbackEntrance, ref fallbackMainEntrance)) { return false; } } EntranceTeleport val = fallbackMainEntrance ?? fallbackEntrance; if ((Object)(object)val != (Object)null) { __result = GetEntrancePosition(val, getTeleportPosition); Warn("Main entrance position was missing; using the first available EntranceTeleport instead of origin."); return false; } __result = Vector3.zero; if (!IsCompanyLevel()) { Warn("Main entrance position was missing and no EntranceTeleport fallback existed; returning origin."); } return false; } private static bool TryFindEntrance(EntranceTeleport[] entrances, bool getTeleportPosition, bool getOutsideEntrance, ref Vector3 result, ref EntranceTeleport fallbackEntrance, ref EntranceTeleport fallbackMainEntrance) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) foreach (EntranceTeleport val in entrances) { if (!((Object)(object)val == (Object)null)) { if (fallbackEntrance == null) { fallbackEntrance = val; } if (val.entranceId == 0 && fallbackMainEntrance == null) { fallbackMainEntrance = val; } if (val.entranceId == 0 && val.isEntranceToBuilding == getOutsideEntrance) { result = GetEntrancePosition(val, getTeleportPosition); return true; } } } return false; } private static EntranceTeleport[] GetCachedEntrances() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); int handle = ((Scene)(ref activeScene)).handle; if (cachedEntrances == null || cachedSceneHandle != handle || cachedEntrances.Length == 0) { return RefreshEntranceCache(handle); } return cachedEntrances; } private static EntranceTeleport[] RefreshEntranceCache() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); return RefreshEntranceCache(((Scene)(ref activeScene)).handle); } private static EntranceTeleport[] RefreshEntranceCache(int activeSceneHandle) { cachedEntrances = Object.FindObjectsOfType(false); cachedSceneHandle = activeSceneHandle; return cachedEntrances; } internal static void ClearCache() { cachedEntrances = null; cachedSceneHandle = int.MinValue; Warnings.Clear(); } private static Vector3 GetEntrancePosition(EntranceTeleport entrance, bool getTeleportPosition) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)entrance == (Object)null) { return Vector3.zero; } if (getTeleportPosition && (Object)(object)entrance.entrancePoint != (Object)null) { return entrance.entrancePoint.position; } return ((Object)(object)((Component)entrance).transform != (Object)null) ? ((Component)entrance).transform.position : Vector3.zero; } private static bool IsCompanyLevel() { SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null); if ((Object)(object)val == (Object)null) { return false; } return val.levelID == 3 || string.Equals(val.sceneName, "CompanyBuilding", StringComparison.OrdinalIgnoreCase) || (val.PlanetName != null && val.PlanetName.IndexOf("company", StringComparison.OrdinalIgnoreCase) >= 0); } private static void Warn(string message) { Warnings.Warn("FindMainEntrancePosition", message); } } [HarmonyPatch(typeof(HUDManager), "AddChatMessage")] internal static class HUDManagerAddChatMessagePatch { private const int MaxWarnings = 5; private static int _warningCount; private static bool Prefix(HUDManager __instance) { if (IsHudChatReady(__instance, out var missingDependency)) { return true; } Warn("Skipped HUDManager.AddChatMessage because chat HUD was not ready: " + missingDependency + "."); return false; } private static Exception Finalizer(HUDManager __instance, Exception __exception) { if (__exception is NullReferenceException && !IsHudChatReady(__instance, out var _)) { Warn("Suppressed HUDManager.AddChatMessage NullReferenceException while chat HUD was not ready."); return null; } return __exception; } internal static bool IsHudChatReady(HUDManager hudManager, out string missingDependency) { if ((Object)(object)hudManager == (Object)null) { missingDependency = "HUDManager"; return false; } if (hudManager.Chat == null) { missingDependency = "HUDManager.Chat"; return false; } if ((Object)(object)hudManager.chatText == (Object)null) { missingDependency = "HUDManager.chatText"; return false; } if (hudManager.ChatMessageHistory == null) { missingDependency = "HUDManager.ChatMessageHistory"; return false; } if ((Object)(object)GameNetworkManager.Instance == (Object)null) { missingDependency = "GameNetworkManager.Instance"; return false; } if ((Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null) { missingDependency = "GameNetworkManager.Instance.localPlayerController"; return false; } if ((Object)(object)StartOfRound.Instance == (Object)null) { missingDependency = "StartOfRound.Instance"; return false; } if (StartOfRound.Instance.allPlayerScripts == null || StartOfRound.Instance.allPlayerScripts.Length < 4) { missingDependency = "StartOfRound.Instance.allPlayerScripts"; return false; } int num = Math.Min(4, StartOfRound.Instance.allPlayerScripts.Length); for (int i = 0; i < num; i++) { if ((Object)(object)StartOfRound.Instance.allPlayerScripts[i] == (Object)null) { missingDependency = $"StartOfRound.Instance.allPlayerScripts[{i}]"; return false; } } missingDependency = string.Empty; return true; } internal static void Warn(string message) { if (_warningCount < 5) { _warningCount++; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{message} ({_warningCount}/{5})"); } } } } [HarmonyPatch(typeof(HUDManager), "AddTextMessageClientRpc")] internal static class HUDManagerAddTextMessageClientRpcPatch { private static readonly WarningLimiter FinalizerWarnings = new WarningLimiter(1); private static bool Prefix(HUDManager __instance) { if (!IsExecutingClientRpc(__instance)) { return true; } if (HUDManagerAddChatMessagePatch.IsHudChatReady(__instance, out var missingDependency)) { return true; } HUDManagerAddChatMessagePatch.Warn("Skipped HUDManager.AddTextMessageClientRpc because chat HUD was not ready: " + missingDependency + "."); return false; } private static Exception Finalizer(HUDManager __instance, Exception __exception) { if (!(__exception is NullReferenceException)) { return __exception; } if (!RpcExecStageUtility.ShouldAllowClientRpcSuppression((NetworkBehaviour)(object)__instance, "HUDManager.AddTextMessageClientRpc", __exception, FinalizerWarnings)) { return __exception; } if (!HUDManagerAddChatMessagePatch.IsHudChatReady(__instance, out var _)) { HUDManagerAddChatMessagePatch.Warn("Suppressed HUDManager.AddTextMessageClientRpc NullReferenceException while chat HUD was not ready."); return null; } return __exception; } private static bool IsExecutingClientRpc(HUDManager hudManager) { bool isExecuting; return RpcExecStageUtility.TryIsExecuting((NetworkBehaviour)(object)hudManager, out isExecuting) && isExecuting; } } [HarmonyPatch(typeof(HUDManager), "SyncAllPlayerLevelsServerRpc", new Type[] { })] internal static class HUDManagerSyncAllPlayerLevelsServerRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(HUDManager __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)((NetworkBehaviour)__instance).NetworkManager == (Object)null || !((NetworkBehaviour)__instance).NetworkManager.IsListening) { return false; } if ((((NetworkBehaviour)__instance).NetworkManager.IsClient || ((NetworkBehaviour)__instance).NetworkManager.IsHost) && ((NetworkBehaviour)__instance).OwnerClientId != ((NetworkBehaviour)__instance).NetworkManager.LocalClientId) { Warnings.Warn("not-owner", "Skipped HUDManager.SyncAllPlayerLevelsServerRpc because the local client does not own HUDManager."); return false; } return true; } } [HarmonyPatch(typeof(InteractTrigger), "UpdateUsedByPlayerClientRpc")] internal static class InteractTriggerUpdateUsedByPlayerClientRpcPatch { private const int MaxWarnings = 5; private static int _warningCount; private static readonly WarningLimiter FinalizerWarnings = new WarningLimiter(1); private static bool Prefix(InteractTrigger __instance, int playerNum) { if (!IsExecutingClientRpc(__instance)) { return true; } if (IsInteractRpcReady(__instance, playerNum, out var missingDependency)) { return true; } Warn($"Skipped InteractTrigger.UpdateUsedByPlayerClientRpc for player {playerNum} because dependencies were not ready: {missingDependency}."); return false; } private static Exception Finalizer(InteractTrigger __instance, int playerNum, Exception __exception) { if (!(__exception is NullReferenceException)) { return __exception; } if (!RpcExecStageUtility.ShouldAllowClientRpcSuppression((NetworkBehaviour)(object)__instance, "InteractTrigger.UpdateUsedByPlayerClientRpc", __exception, FinalizerWarnings)) { return __exception; } if (!IsInteractRpcReady(__instance, playerNum, out var _)) { Warn("Suppressed InteractTrigger.UpdateUsedByPlayerClientRpc NullReferenceException while trigger dependencies were not ready."); return null; } return __exception; } private static bool IsInteractRpcReady(InteractTrigger trigger, int playerNum, out string missingDependency) { if ((Object)(object)trigger == (Object)null) { missingDependency = "InteractTrigger"; return false; } if (trigger.onInteractEarlyOtherClients == null) { missingDependency = GetTriggerName(trigger) + ".onInteractEarlyOtherClients"; return false; } if (!TryGetPlayer(playerNum, out var player, out missingDependency)) { return false; } if ((Object)(object)GameNetworkManager.Instance == (Object)null) { missingDependency = "GameNetworkManager.Instance"; return false; } PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController; if ((Object)(object)localPlayerController == (Object)null || (Object)(object)((Component)localPlayerController).transform == (Object)null) { missingDependency = "GameNetworkManager.Instance.localPlayerController"; return false; } if (trigger.specialCharacterAnimation && trigger.setVehicleAnimation && (Object)(object)player.gameplayCamera == (Object)null) { missingDependency = $"player {playerNum} gameplayCamera"; return false; } missingDependency = string.Empty; return true; } private static bool TryGetPlayer(int playerNum, out PlayerControllerB player, out string missingDependency) { player = null; if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null || playerNum < 0 || playerNum >= StartOfRound.Instance.allPlayerScripts.Length) { missingDependency = "StartOfRound.Instance.allPlayerScripts"; return false; } player = StartOfRound.Instance.allPlayerScripts[playerNum]; if ((Object)(object)player == (Object)null || (Object)(object)((Component)player).transform == (Object)null) { missingDependency = $"StartOfRound.Instance.allPlayerScripts[{playerNum}]"; return false; } missingDependency = string.Empty; return true; } private static string GetTriggerName(InteractTrigger trigger) { return ((Object)(object)trigger != (Object)null && (Object)(object)((Component)trigger).gameObject != (Object)null) ? ((Object)((Component)trigger).gameObject).name : "unknown trigger"; } private static void Warn(string message) { if (_warningCount < 5) { _warningCount++; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{message} ({_warningCount}/{5})"); } } } private static bool IsExecutingClientRpc(InteractTrigger trigger) { bool isExecuting; return RpcExecStageUtility.TryIsExecuting((NetworkBehaviour)(object)trigger, out isExecuting) && isExecuting; } } [HarmonyPatch(typeof(RadMechAI), "SetExplosion")] internal static class RadMechAISetExplosionPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static Exception Finalizer(RadMechAI __instance, Exception __exception) { return SuppressKnownException(__exception, "RadMechAI.SetExplosion", Warnings, () => HasKnownMissingExplosionDependency(__instance)); } internal static Exception SuppressKnownException(Exception exception, string key, WarningLimiter warnings, Func isKnownSafeCase) { if (!(exception is NullReferenceException) || isKnownSafeCase == null) { return exception; } bool flag; try { flag = isKnownSafeCase(); } catch (Exception ex) { warnings.Warn(key + "|classifier-failed", "Known dependency classifier failed for " + key + "; returning original NullReferenceException: " + ex.GetType().Name + "."); return exception; } if (flag) { warnings.Warn(key, "Suppressed " + key + " NullReferenceException for a known missing dependency."); return null; } return exception; } internal static bool HasKnownMissingExplosionDependency(RadMechAI mech) { return (Object)(object)mech == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null || (Object)(object)((Component)GameNetworkManager.Instance.localPlayerController).transform == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)mech.explosionAudio == (Object)null || mech.largeExplosionSFX == null; } } [HarmonyPatch(typeof(RadMechAI), "SetExplosionClientRpc")] internal static class RadMechAISetExplosionClientRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter NonExecuteStageWarnings = new WarningLimiter(1); private static Exception Finalizer(RadMechAI __instance, Exception __exception) { if (__exception is NullReferenceException && !RpcExecStageUtility.ShouldAllowClientRpcSuppression((NetworkBehaviour)(object)__instance, "RadMechAI.SetExplosionClientRpc", __exception, NonExecuteStageWarnings)) { return __exception; } return RadMechAISetExplosionPatch.SuppressKnownException(__exception, "RadMechAI.SetExplosionClientRpc", Warnings, () => RadMechAISetExplosionPatch.HasKnownMissingExplosionDependency(__instance)); } } [HarmonyPatch(typeof(JetpackItem), "DeactivateJetpack")] internal static class JetpackItemDeactivateJetpackPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static Exception Finalizer(JetpackItem __instance, Exception __exception) { return RadMechAISetExplosionPatch.SuppressKnownException(__exception, "JetpackItem.DeactivateJetpack", Warnings, () => JetpackItemKnownDependencyGuard.HasKnownMissingDeactivateDependency(__instance)); } } [HarmonyPatch(typeof(JetpackItem), "ItemActivate")] internal static class JetpackItemItemActivatePatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static Exception Finalizer(JetpackItem __instance, Exception __exception) { return RadMechAISetExplosionPatch.SuppressKnownException(__exception, "JetpackItem.ItemActivate", Warnings, () => JetpackItemKnownDependencyGuard.HasKnownMissingActivateDependency(__instance)); } } [HarmonyPatch(typeof(GrabbableObject), "ActivateItemRpc")] internal static class GrabbableObjectActivateItemRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static Exception Finalizer(GrabbableObject __instance, Exception __exception) { JetpackItem jetpack = (JetpackItem)(object)((__instance is JetpackItem) ? __instance : null); if (jetpack == null) { return __exception; } return RadMechAISetExplosionPatch.SuppressKnownException(__exception, "GrabbableObject.ActivateItemRpc", Warnings, () => JetpackItemKnownDependencyGuard.HasKnownMissingActivateDependency(jetpack)); } } internal static class JetpackItemKnownDependencyGuard { internal static bool HasKnownMissingDeactivateDependency(JetpackItem jetpack) { return (Object)(object)jetpack == (Object)null || (Object)(object)jetpack.previousPlayerHeldBy == (Object)null || (Object)(object)jetpack.jetpackBeepsAudio == (Object)null || (Object)(object)jetpack.jetpackAudio == (Object)null || (Object)(object)jetpack.smokeTrailParticle == (Object)null; } internal static bool HasKnownMissingActivateDependency(JetpackItem jetpack) { return (Object)(object)jetpack == (Object)null || (Object)(object)((GrabbableObject)jetpack).playerHeldBy == (Object)null || (Object)(object)jetpack.jetpackAudio == (Object)null || (Object)(object)jetpack.smokeTrailParticle == (Object)null || (jetpack.streamlineJetpack && ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)); } } [HarmonyPatch(typeof(LobbySlot), "SetModdedIcon")] internal static class LobbySlotSetModdedIconPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(LobbySlot __instance, ModdedState moddedState) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (IsKnownMissingIcon(__instance, moddedState, out var reason)) { Warnings.Warn($"missing-icon|{moddedState}|{reason}", "Skipped LobbySlot.SetModdedIcon because " + reason + "."); return false; } return true; } private static Exception Finalizer(LobbySlot __instance, ModdedState moddedState, Exception __exception) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } string reason; return NullRefGuard.Suppress(__exception, "LobbySlot.SetModdedIcon", () => IsKnownMissingIcon(__instance, moddedState, out reason)); } private static bool IsKnownMissingIcon(LobbySlot slot, ModdedState moddedState, out string reason) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Invalid comparison between Unknown and I4 if ((Object)(object)slot == (Object)null) { reason = "slot was null"; return true; } if ((int)moddedState == 0 && (Object)(object)slot.modStateUnknownIcon == (Object)null) { reason = "modStateUnknownIcon was missing"; return true; } if ((int)moddedState == 2 && (Object)(object)slot.modStateTrueIcon == (Object)null) { reason = "modStateTrueIcon was missing"; return true; } reason = string.Empty; return false; } } [HarmonyPatch(typeof(DisplayPlayerMicVolume), "InitMic")] internal static class DisplayPlayerMicVolumeInitMicPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(DisplayPlayerMicVolume __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)IngamePlayerSettings.Instance == (Object)null || IngamePlayerSettings.Instance.unsavedSettings == null) { return false; } IngamePlayerSettings.Instance.RefreshAndDisplayCurrentMicrophone(false); string micDevice = IngamePlayerSettings.Instance.unsavedSettings.micDevice; if (!IsValidMicrophoneDevice(micDevice)) { return false; } string device = __instance._device; if (IsValidMicrophoneDevice(device) && Microphone.IsRecording(device)) { Microphone.End(device); } try { int num = default(int); int num2 = default(int); Microphone.GetDeviceCaps(micDevice, ref num, ref num2); int num3 = ((num2 <= 0) ? 44100 : Mathf.Clamp(44100, Mathf.Max(num, 1), num2)); __instance._device = micDevice; __instance._clipRecord = Microphone.Start(micDevice, true, 1, num3); } catch (Exception ex) when (ex is ArgumentException || ex is InvalidOperationException) { Warn("Skipping microphone preview for unavailable device '" + micDevice + "': " + ex.Message); __instance._clipRecord = null; } return false; } internal static void Warn(string message) { Warnings.Warn("DisplayPlayerMicVolume", message); } internal static bool IsValidMicrophoneDevice(string device) { if (string.IsNullOrWhiteSpace(device) || device == "none" || device == "LCNoMic" || Microphone.devices == null) { return false; } return Array.IndexOf(Microphone.devices, device) >= 0; } } [HarmonyPatch(typeof(DisplayPlayerMicVolume), "StopMicrophone")] internal static class DisplayPlayerMicVolumeStopMicrophonePatch { private static bool Prefix(DisplayPlayerMicVolume __instance) { if ((Object)(object)__instance == (Object)null) { return false; } try { string device = __instance._device; if (DisplayPlayerMicVolumeInitMicPatch.IsValidMicrophoneDevice(device) && Microphone.IsRecording(device)) { Microphone.End(device); } } catch (Exception ex) { DisplayPlayerMicVolumeInitMicPatch.Warn("Skipping microphone stop because it failed safely: " + ex.GetType().Name); } return false; } } [HarmonyPatch(typeof(DisplayPlayerMicVolume), "LevelMax")] internal static class DisplayPlayerMicVolumeLevelMaxPatch { private static bool Prefix(DisplayPlayerMicVolume __instance, ref float __result) { __result = 0f; if ((Object)(object)__instance == (Object)null || (Object)(object)IngamePlayerSettings.Instance == (Object)null || IngamePlayerSettings.Instance.unsavedSettings == null) { return false; } string micDevice = IngamePlayerSettings.Instance.unsavedSettings.micDevice; if (!DisplayPlayerMicVolumeInitMicPatch.IsValidMicrophoneDevice(micDevice) || !Microphone.IsRecording(micDevice)) { return false; } try { return (Object)(object)__instance._clipRecord != (Object)null; } catch (Exception ex) { DisplayPlayerMicVolumeInitMicPatch.Warn("Skipping microphone level preview because it failed safely: " + ex.GetType().Name); return false; } } } [HarmonyPatch(typeof(NavMeshSurface), "CollectSources")] internal static class NavMeshSurfaceCollectSourcesPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter GuardFailureWarnings = new WarningLimiter(); private static void Postfix(NavMeshSurface __instance, ref List __result) { try { FilterUnreadableMeshSources(__instance, __result); } catch (Exception ex) { GuardFailureWarnings.Warn("guard-failure", "NavMeshSurface source filter failed safely and left sources unchanged: " + ex.GetType().Name + "."); } } private static void FilterUnreadableMeshSources(NavMeshSurface surface, List sources) { //IL_0048: 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_004f: Unknown result type (might be due to invalid IL or missing references) if (sources == null || sources.Count == 0) { return; } int num = 0; string surfaceName = GetSurfaceName(surface); bool flag = ShouldLog(surfaceName); HashSet hashSet = (flag ? new HashSet() : null); for (int num2 = sources.Count - 1; num2 >= 0; num2--) { NavMeshBuildSource source = sources[num2]; if (TryGetUnreadableMesh(source, out var mesh)) { sources.RemoveAt(num2); num++; hashSet?.Add(GetMeshName(mesh)); } } if (num > 0 && flag) { Warn(surfaceName, hashSet, num); } } private static bool TryGetUnreadableMesh(NavMeshBuildSource source, out Mesh mesh) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) mesh = null; if ((int)((NavMeshBuildSource)(ref source)).shape == 0) { Object sourceObject = ((NavMeshBuildSource)(ref source)).sourceObject; Mesh val = (Mesh)(object)((sourceObject is Mesh) ? sourceObject : null); if (val != null && !((Object)(object)val == (Object)null)) { mesh = val; return !val.isReadable; } } return false; } private static bool ShouldLog(string surfaceName) { string key = "unreadable-navmesh-sources|" + surfaceName; return Warnings.CanWarn(key); } private static void Warn(string surfaceName, HashSet meshNames, int removedCount) { string key = "unreadable-navmesh-sources|" + surfaceName; Warnings.Warn(key, delegate { string arg = ((meshNames != null && meshNames.Count > 0) ? string.Join(", ", meshNames) : "unknown"); return $"Filtered {removedCount} unreadable mesh source(s) from NavMeshSurface '{surfaceName}' before runtime NavMesh build: {arg}."; }); } private static string GetSurfaceName(NavMeshSurface surface) { return ((Object)(object)surface != (Object)null && (Object)(object)((Component)surface).gameObject != (Object)null) ? ((Object)((Component)surface).gameObject).name : "unknown"; } private static string GetMeshName(Mesh mesh) { return ((Object)(object)mesh != (Object)null && !string.IsNullOrEmpty(((Object)mesh).name)) ? ((Object)mesh).name : "unnamed mesh"; } } [HarmonyPatch(typeof(EnemyAI), "DoAIInterval")] internal static class EnemyAINavMeshGuardPatch { private const float RecoveryAttemptCooldown = 0.5f; private const float RecoveryCacheCleanupInterval = 30f; private static readonly float[] SampleRadii = new float[5] { 2f, 4f, 8f, 16f, 32f }; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly Dictionary NextRecoveryAttemptTimes = new Dictionary(); private static float _nextRecoveryCacheCleanupTime; private static bool Prefix(EnemyAI __instance) { try { return GuardEnemyAIInterval(__instance); } catch (Exception ex) { string enemyName = GetEnemyName(__instance); Warn(enemyName, "NavMesh guard failed safely for " + enemyName + ": " + ex.GetType().Name + "."); return true; } } private static bool GuardEnemyAIInterval(EnemyAI enemy) { if (ErrorFixConfig.EnableEnemyAINavMeshGuard != null && !ErrorFixConfig.EnableEnemyAINavMeshGuard.Value) { return true; } if ((Object)(object)enemy == (Object)null || enemy.inSpecialAnimation || (Object)(object)enemy.agent == (Object)null || !enemy.moveTowardsDestination || !((Behaviour)enemy.agent).enabled || enemy.agent.isOnNavMesh) { return true; } string enemyName = GetEnemyName(enemy); if (!CanAttemptRecovery(enemy)) { return false; } if (ShouldRestrictRecoveryToHostServer() && !IsHostOrServer()) { Warn(enemyName, "Suppressed non-host/client " + enemyName + " SetDestination while its NavMeshAgent is off the NavMesh."); return false; } if (!((NetworkBehaviour)enemy).IsOwner) { Warn(enemyName, "Suppressed non-owner " + enemyName + " SetDestination while its NavMeshAgent is off the NavMesh."); return false; } if (enemy.isEnemyDead) { enemy.moveTowardsDestination = false; TrySyncPositionToClients(enemy); Warn(enemyName, "Suppressed " + enemyName + " SetDestination while it is dead and its NavMeshAgent is off the NavMesh."); return false; } if (ShouldAllowWarp() && TryWarpToNearbyNavMesh(enemy)) { Warn(enemyName, "Recovered " + enemyName + " NavMeshAgent by warping it back onto the NavMesh."); return true; } enemy.moveTowardsDestination = false; TrySyncPositionToClients(enemy); Warn(enemyName, "Suppressed " + enemyName + " SetDestination while its NavMeshAgent is off the NavMesh and no nearby NavMesh point was found."); return false; } private static bool TryWarpToNearbyNavMesh(EnemyAI enemy) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) int areaMask = enemy.agent.areaMask; Vector3 position = ((Component)enemy).transform.position; if (TryWarpToNearbyNavMesh(enemy, position, areaMask)) { return true; } return areaMask != -1 && TryWarpToNearbyNavMesh(enemy, position, -1); } private static bool TryWarpToNearbyNavMesh(EnemyAI enemy, Vector3 position, int areaMask) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) NavMeshHit val = default(NavMeshHit); for (int i = 0; i < SampleRadii.Length; i++) { float num = SampleRadii[i]; if (num > GetMaxWarpRadius()) { break; } if (NavMesh.SamplePosition(position, ref val, num, areaMask) && IsUsableRecoveryPoint(enemy, ((NavMeshHit)(ref val)).position, areaMask) && enemy.agent.Warp(((NavMeshHit)(ref val)).position)) { if (enemy.agent.isOnNavMesh) { enemy.agent.ResetPath(); } return true; } } return false; } private static bool ShouldAllowWarp() { return ErrorFixConfig.AllowEnemyAIWarp == null || ErrorFixConfig.AllowEnemyAIWarp.Value; } private static bool ShouldRestrictRecoveryToHostServer() { return ErrorFixConfig.EnemyAINavMeshHostServerOnly == null || ErrorFixConfig.EnemyAINavMeshHostServerOnly.Value; } private static bool IsHostOrServer() { NetworkManager singleton = NetworkManager.Singleton; return (Object)(object)singleton == (Object)null || singleton.IsHost || singleton.IsServer; } private static float GetMaxWarpRadius() { float num = ErrorFixConfig.EnemyAINavMeshMaxWarpRadius?.Value ?? 32f; return Mathf.Clamp(num, 0f, 64f); } private static bool IsUsableRecoveryPoint(EnemyAI enemy, Vector3 position, int areaMask) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Invalid comparison between Unknown and I4 if (enemy.destination == Vector3.zero) { return true; } NavMeshPath val = enemy.path1; if (val == null) { val = (enemy.path1 = new NavMeshPath()); } return NavMesh.CalculatePath(position, enemy.destination, areaMask, val) && (int)val.status != 2; } private static bool CanAttemptRecovery(EnemyAI enemy) { int instanceID = ((Object)enemy).GetInstanceID(); float realtimeSinceStartup = Time.realtimeSinceStartup; CleanupRecoveryCacheIfNeeded(realtimeSinceStartup); if (NextRecoveryAttemptTimes.TryGetValue(instanceID, out var value) && realtimeSinceStartup < value) { return false; } NextRecoveryAttemptTimes[instanceID] = realtimeSinceStartup + 0.5f; return true; } private static void Warn(string enemyName, string message) { Warnings.Warn(enemyName, message); } private static void CleanupRecoveryCacheIfNeeded(float now) { if (now < _nextRecoveryCacheCleanupTime || NextRecoveryAttemptTimes.Count < 128) { return; } _nextRecoveryCacheCleanupTime = now + 30f; List list = null; foreach (KeyValuePair nextRecoveryAttemptTime in NextRecoveryAttemptTimes) { if (!(nextRecoveryAttemptTime.Value >= now)) { if (list == null) { list = new List(); } list.Add(nextRecoveryAttemptTime.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { NextRecoveryAttemptTimes.Remove(list[i]); } } } private static string GetEnemyName(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return "Unknown Enemy"; } if ((Object)(object)enemy.enemyType != (Object)null && !string.IsNullOrEmpty(enemy.enemyType.enemyName)) { return enemy.enemyType.enemyName; } return ((Object)enemy).name; } private static void TrySyncPositionToClients(EnemyAI enemy) { try { enemy.SyncPositionToClients(); } catch (Exception ex) { Warn(GetEnemyName(enemy), "Skipped EnemyAI position sync after NavMesh guard because it failed safely: " + ex.GetType().Name + "."); } } } [HarmonyPatch] internal static class NetworkObjectDestroyGuardPatch { private const float WarningInterval = 5f; private const float WarningCacheCleanupInterval = 30f; private static readonly Dictionary LastWarningTimes = new Dictionary(); private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter GuardFailureWarnings = new WarningLimiter(); private static bool _loggedBlockedDestroyStackTrace; private static float _nextWarningCacheCleanupTime; [HarmonyPrepare] private static bool Prepare() { return ShouldPatch(ErrorFixConfig.GlobalDestroyGuardMode?.Value ?? PatchEnableMode.Disabled, ErrorFixConfig.EnableGlobalDestroyGuard?.Value ?? false); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { MethodInfo destroy = AccessTools.Method(typeof(Object), "Destroy", new Type[1] { typeof(Object) }, (Type[])null); if (destroy != null) { yield return destroy; } MethodInfo destroyDelayed = AccessTools.Method(typeof(Object), "Destroy", new Type[2] { typeof(Object), typeof(float) }, (Type[])null); if (destroyDelayed != null) { yield return destroyDelayed; } } private static bool Prefix(Object obj) { try { return ShouldAllowDestroy(obj); } catch (Exception ex) { GuardFailureWarnings.Warn("guard-failure", "NetworkObject destroy guard failed safely and allowed original Destroy: " + ex.GetType().Name + "."); return true; } } private static bool ShouldAllowDestroy(Object obj) { if (obj == (Object)null || (Object)(object)NetworkManager.Singleton == (Object)null || NetworkManager.Singleton.IsServer) { return true; } if (ShouldAllowLifecycleDestroy()) { return true; } bool flag = obj is GameObject; GameObject val = (GameObject)(object)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { Component val2 = (Component)(object)((obj is Component) ? obj : null); if (val2 != null) { val = val2.gameObject; } } if ((Object)(object)val == (Object)null) { return true; } RagdollGrabbableObject val3 = val.GetComponent() ?? val.GetComponentInParent(); if ((Object)(object)val3 == (Object)null && flag) { val3 = val.GetComponentInChildren(); } if ((Object)(object)val3 == (Object)null) { return true; } NetworkObject component = ((Component)val3).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsSpawned) { return true; } WarnBlockedDestroy(component); return false; } internal static bool ShouldPatch(PatchEnableMode mode, bool legacySwitchEnabled) { return legacySwitchEnabled && mode == PatchEnableMode.Enabled; } internal static void ClearCaches() { LastWarningTimes.Clear(); Warnings.Clear(); GuardFailureWarnings.Clear(); _loggedBlockedDestroyStackTrace = false; } private static bool ShouldAllowLifecycleDestroy() { if (ErrorFixConfig.AllowDestroyDuringSceneUnload != null && !ErrorFixConfig.AllowDestroyDuringSceneUnload.Value) { return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || singleton.ShutdownInProgress || !singleton.IsListening) { return true; } if (SceneLifecycle.IsLifecycleDestroyAllowed) { return true; } StartOfRound instance = StartOfRound.Instance; return (Object)(object)instance == (Object)null || instance.shipIsLeaving || instance.inShipPhase; } private static void WarnBlockedDestroy(NetworkObject networkObject) { float realtimeSinceStartup = Time.realtimeSinceStartup; CleanupWarningCacheIfNeeded(realtimeSinceStartup); if (!LastWarningTimes.TryGetValue(networkObject.NetworkObjectId, out var value) || !(realtimeSinceStartup - value < 5f)) { LastWarningTimes[networkObject.NetworkObjectId] = realtimeSinceStartup; Warnings.Warn($"blocked-destroy|{networkObject.NetworkObjectId}", "Blocked client-side Destroy on spawned " + ((Object)networkObject).name + "; waiting for server despawn."); LogStackTraceOnceIfEnabled(); } } private static void CleanupWarningCacheIfNeeded(float now) { if (now < _nextWarningCacheCleanupTime || LastWarningTimes.Count < 128) { return; } _nextWarningCacheCleanupTime = now + 30f; List list = null; foreach (KeyValuePair lastWarningTime in LastWarningTimes) { if (!(now - lastWarningTime.Value < 5f)) { if (list == null) { list = new List(); } list.Add(lastWarningTime.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { LastWarningTimes.Remove(list[i]); } } } private static void LogStackTraceOnceIfEnabled() { if (!_loggedBlockedDestroyStackTrace && ErrorFixConfig.LogBlockedDestroyStackTraceOnce != null && ErrorFixConfig.LogBlockedDestroyStackTraceOnce.Value) { _loggedBlockedDestroyStackTrace = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("First blocked spawned ragdoll Destroy stack trace: " + Environment.StackTrace)); } } } } [HarmonyPatch] internal static class NetworkObjectParentChangedPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PatchModeUtility.IsEnabled(ErrorFixConfig.NetworkObjectParentGuardMode) && TargetMethod() != null; } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return AccessTools.Method(typeof(NetworkObject), "OnTransformParentChanged", (Type[])null, (Type[])null); } private static Exception Finalizer(NetworkObject __instance, Exception __exception) { if (IsKnownSpawnStateException(__exception)) { Warnings.Warn(GetWarningKey(__instance), () => "Suppressed NetworkObject reparent SpawnStateException for '" + GetObjectName(__instance) + "'."); return null; } return __exception; } private static bool IsKnownSpawnStateException(Exception exception) { return exception != null && exception.GetType().Name == "SpawnStateException" && exception.Message != null && exception.Message.Contains("NetworkObject can only be reparented after being spawned"); } private static string GetWarningKey(NetworkObject networkObject) { return ((Object)(object)networkObject != (Object)null) ? $"network-parent|{((Object)networkObject).GetInstanceID()}" : "network-parent|unknown"; } private static string GetObjectName(NetworkObject networkObject) { return ((Object)(object)networkObject != (Object)null) ? ((Object)networkObject).name : "unknown"; } } [HarmonyPatch] internal static class EnemyHealthBarsLateUpdatePatch { private const string TargetKey = "EnemyHealthBars.HealthBar.LateUpdate"; private const string TargetSignature = "EnemyHealthBars.Scripts.HealthBar::LateUpdate() : void instance"; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return OptionalCompatibilityUtility.ShouldResolveTarget(ErrorFixConfig.EnemyHealthBarsLateUpdateGuardMode, "EnemyHealthBars.Scripts.HealthBar::LateUpdate() : void instance") && OptionalCompatibilityUtility.ShouldPatchResolvedTarget(TargetMethod() != null, "EnemyHealthBars.HealthBar.LateUpdate", "EnemyHealthBars.Scripts.HealthBar::LateUpdate() : void instance"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { Type typeByName = OptionalCompatibilityUtility.GetTypeByName("EnemyHealthBars.Scripts.HealthBar"); return OptionalPatchTargetResolver.FindMethod(typeByName, "LateUpdate", false, typeof(void)); } private static Exception Finalizer(object __instance, MethodBase __originalMethod, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return OptionalCompatibilityUtility.HandleNullReference("EnemyHealthBars.HealthBar.LateUpdate", __originalMethod, __exception, () => IsKnownSafeNullReference(__instance), Warnings, UnknownWarnings); } private static bool IsKnownSafeNullReference(object healthBar) { if (OptionalCompatibilityUtility.IsUnityObjectMissing(healthBar)) { return true; } if (!OptionalCompatibilityUtility.TryGetInstanceMemberValue("EnemyHealthBars.HealthBar.LateUpdate", healthBar, "CurLayout", out var value)) { return false; } if (OptionalCompatibilityUtility.IsUnityObjectMissing(value)) { return true; } Component val = (Component)((value is Component) ? value : null); return val != null && (Object)(object)val.gameObject == (Object)null; } } internal static class ShipLootPlusUiHelperPatchSupport { internal const string TargetKey = "ShipLootPlus.UiHelper"; private const string CalculateLootValueSignature = "ShipLootPlus.Utils.UiHelper::CalculateLootValue(List|List, string) : unknown static"; private const string GeneratedMethodsSignature = "ShipLootPlus.Utils.UiHelper generated CalculateLootValue/UpdateDatapoints methods"; private const string RefreshElementValuesSignature = "ShipLootPlus.Utils.UiHelper::RefreshElementValues() : void static"; private const double HeldScrapScanCacheSeconds = 0.25; private static readonly Type[] IgnoredLootListTypes = new Type[2] { typeof(List), typeof(List) }; internal static readonly WarningLimiter Warnings = new WarningLimiter(); internal static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static readonly object HeldScrapScanLock = new object(); private static DateTime heldScrapLastScanUtc = DateTime.MinValue; private static bool heldScrapLastScanResult; private static MethodBase[] calculateLootValueTargets; private static MethodBase[] generatedTargets; private static bool refreshElementValuesTargetResolved; private static MethodBase refreshElementValuesTarget; internal static bool ShouldPatchCalculateLootValue() { return OptionalCompatibilityUtility.ShouldResolveTarget(ErrorFixConfig.ShipLootPlusUiHelperGuardMode, "ShipLootPlus.Utils.UiHelper::CalculateLootValue(List|List, string) : unknown static") && OptionalCompatibilityUtility.ShouldPatchResolvedTarget(GetCalculateLootValueTargets().Length != 0, "ShipLootPlus.UiHelper", "ShipLootPlus.Utils.UiHelper::CalculateLootValue(List|List, string) : unknown static"); } internal static bool ShouldPatchRefreshElementValues() { return OptionalCompatibilityUtility.ShouldResolveTarget(ErrorFixConfig.ShipLootPlusUiHelperGuardMode, "ShipLootPlus.Utils.UiHelper::RefreshElementValues() : void static") && OptionalCompatibilityUtility.ShouldPatchResolvedTarget(GetRefreshElementValuesTarget() != null, "ShipLootPlus.UiHelper", "ShipLootPlus.Utils.UiHelper::RefreshElementValues() : void static"); } internal static bool ShouldPatchGeneratedMethods() { return OptionalCompatibilityUtility.ShouldResolveTarget(ErrorFixConfig.ShipLootPlusUiHelperGuardMode, "ShipLootPlus.Utils.UiHelper generated CalculateLootValue/UpdateDatapoints methods") && OptionalCompatibilityUtility.ShouldPatchResolvedTarget(GetGeneratedTargets().Length != 0, "ShipLootPlus.UiHelper", "ShipLootPlus.Utils.UiHelper generated CalculateLootValue/UpdateDatapoints methods"); } internal static IEnumerable ResolveCalculateLootValueTargets(bool logResolvedTargets) { MethodBase[] targets = GetCalculateLootValueTargets(); for (int i = 0; i < targets.Length; i++) { if (logResolvedTargets) { OptionalCompatibilityUtility.LogResolvedTarget("ShipLootPlus.UiHelper", targets[i]); } yield return targets[i]; } } internal static MethodBase ResolveRefreshElementValuesTarget(bool logResolvedTarget) { MethodBase methodBase = GetRefreshElementValuesTarget(); if (methodBase != null && logResolvedTarget) { OptionalCompatibilityUtility.LogResolvedTarget("ShipLootPlus.UiHelper", methodBase); } return methodBase; } internal static IEnumerable ResolveGeneratedTargets(bool logResolvedTargets) { MethodBase[] targets = GetGeneratedTargets(); for (int i = 0; i < targets.Length; i++) { if (logResolvedTargets) { OptionalCompatibilityUtility.LogResolvedTarget("ShipLootPlus.UiHelper", targets[i]); } yield return targets[i]; } } private static MethodBase[] GetCalculateLootValueTargets() { if (calculateLootValueTargets != null) { return calculateLootValueTargets; } Type typeByName = OptionalCompatibilityUtility.GetTypeByName("ShipLootPlus.Utils.UiHelper"); if (typeByName == null) { calculateLootValueTargets = Array.Empty(); return calculateLootValueTargets; } List list = new List(IgnoredLootListTypes.Length); HashSet hashSet = new HashSet(); for (int i = 0; i < IgnoredLootListTypes.Length; i++) { MethodBase methodBase = OptionalPatchTargetResolver.FindMethod(typeByName, "CalculateLootValue", true, null, IgnoredLootListTypes[i], typeof(string)); if (methodBase != null && hashSet.Add(methodBase)) { list.Add(methodBase); } } calculateLootValueTargets = list.ToArray(); return calculateLootValueTargets; } private static MethodBase GetRefreshElementValuesTarget() { if (refreshElementValuesTargetResolved) { return refreshElementValuesTarget; } refreshElementValuesTargetResolved = true; Type typeByName = OptionalCompatibilityUtility.GetTypeByName("ShipLootPlus.Utils.UiHelper"); refreshElementValuesTarget = ((typeByName != null) ? OptionalPatchTargetResolver.FindMethod(typeByName, "RefreshElementValues", true, typeof(void)) : null); return refreshElementValuesTarget; } private static MethodBase[] GetGeneratedTargets() { if (generatedTargets != null) { return generatedTargets; } Type typeByName = OptionalCompatibilityUtility.GetTypeByName("ShipLootPlus.Utils.UiHelper"); if (typeByName == null) { generatedTargets = Array.Empty(); return generatedTargets; } List list = new List(); HashSet hashSet = new HashSet(); Type[] nestedTypes = typeByName.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type in nestedTypes) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (ShouldPatchGeneratedMethod(type, methodInfo) && hashSet.Add(methodInfo)) { list.Add(methodInfo); } } } generatedTargets = list.ToArray(); return generatedTargets; } internal static bool IsKnownSafeCalculateLootValueNullReference(MethodBase originalMethod, object ignoredList, string ignoredCategory) { if (!string.Equals(originalMethod?.Name, "CalculateLootValue", StringComparison.Ordinal)) { return false; } if (ignoredList == null || string.IsNullOrEmpty(ignoredCategory)) { return true; } if (HasNullOrDestroyedLootEntry(ignoredList)) { return true; } return string.Equals(ignoredCategory, "Inv", StringComparison.OrdinalIgnoreCase) && HasHeldScrapWithoutHolder(); } internal static bool IsKnownSafeGeneratedNullReference(MethodBase originalMethod) { string text = ((originalMethod != null) ? originalMethod.Name : string.Empty); return text.Contains("", StringComparison.Ordinal) ? HasHeldScrapWithoutHolder() : IsKnownSafeUiStateNullReference(); } internal static bool IsKnownSafeUiStateNullReference() { Type typeByName = OptionalCompatibilityUtility.GetTypeByName("ShipLootPlus.Utils.UiHelper"); if (typeByName == null) { OptionalCompatibilityUtility.LogMemberMismatch("ShipLootPlus.UiHelper", null, "ShipLootPlus.Utils.UiHelper"); return false; } if (!OptionalCompatibilityUtility.TryGetStaticMemberValue("ShipLootPlus.UiHelper", typeByName, "UiElementList", out var value) || !OptionalCompatibilityUtility.TryGetStaticMemberValue("ShipLootPlus.UiHelper", typeByName, "ElementsToUpdate", out var value2) || !OptionalCompatibilityUtility.TryGetStaticMemberValue("ShipLootPlus.UiHelper", typeByName, "DataPoints", out var value3) || !OptionalCompatibilityUtility.TryGetStaticMemberValue("ShipLootPlus.UiHelper", typeByName, "ContainerObject", out var value4)) { return false; } if (value == null || value2 == null || value3 == null || OptionalCompatibilityUtility.IsUnityObjectMissing(value4)) { return true; } return HasMissingUiElementReference(typeByName); } private static bool HasNullOrDestroyedLootEntry(object lootList) { if (!(lootList is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item == null || OptionalCompatibilityUtility.IsUnityObjectMissing(item)) { return true; } } return false; } private static bool HasHeldScrapWithoutHolder() { DateTime utcNow = DateTime.UtcNow; lock (HeldScrapScanLock) { if ((utcNow - heldScrapLastScanUtc).TotalSeconds < 0.25) { return heldScrapLastScanResult; } } bool flag = false; PlayerControllerB[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.allPlayerScripts : null); if (array != null) { for (int i = 0; i < array.Length; i++) { GrabbableObject[] array2 = (((Object)(object)array[i] != (Object)null) ? array[i].ItemSlots : null); if (array2 == null) { continue; } foreach (GrabbableObject val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)val.itemProperties != (Object)null && val.itemProperties.isScrap && (val.isHeld || val.isPocketed) && (Object)(object)val.playerHeldBy == (Object)null) { flag = true; break; } } if (flag) { break; } } } lock (HeldScrapScanLock) { heldScrapLastScanUtc = utcNow; heldScrapLastScanResult = flag; } return flag; } private static bool HasMissingUiElementReference(Type uiHelperType) { if (!OptionalCompatibilityUtility.TryGetStaticMemberValue("ShipLootPlus.UiHelper", uiHelperType, "ElementsToUpdate", out var value)) { return false; } if (!(value is IEnumerable enumerable)) { return false; } foreach (object item in enumerable) { if (item == null) { return true; } if (!TryGetUiElementGameObject(item, out var gameObject) || !OptionalCompatibilityUtility.TryGetInstanceMemberValue("ShipLootPlus.UiHelper", item, "textMeshProUGui", out var value2)) { return false; } if (OptionalCompatibilityUtility.IsUnityObjectMissing(gameObject) || OptionalCompatibilityUtility.IsUnityObjectMissing(value2)) { return true; } } return false; } private static bool TryGetUiElementGameObject(object element, out object gameObject) { if (OptionalCompatibilityUtility.TryGetInstanceMemberValueSilently(element, "gameOjbect", out gameObject) || OptionalCompatibilityUtility.TryGetInstanceMemberValueSilently(element, "gameObject", out gameObject)) { return true; } OptionalCompatibilityUtility.LogMemberMismatch("ShipLootPlus.UiHelper", element?.GetType(), "gameOjbect/gameObject"); gameObject = null; return false; } private static bool ShouldPatchGeneratedMethod(Type nestedType, MethodInfo method) { if (nestedType == null || method == null) { return false; } return method.ReturnType == typeof(bool) && (method.Name.Contains("") || (method.Name == "MoveNext" && nestedType.Name.Contains(""))); } } [HarmonyPatch] internal static class ShipLootPlusCalculateLootValuePatch { [HarmonyPrepare] private static bool Prepare() { return ShipLootPlusUiHelperPatchSupport.ShouldPatchCalculateLootValue(); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { return ShipLootPlusUiHelperPatchSupport.ResolveCalculateLootValueTargets(logResolvedTargets: true); } private static Exception Finalizer(MethodBase __originalMethod, object __0, string __1, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return OptionalCompatibilityUtility.HandleNullReference("ShipLootPlus.UiHelper", __originalMethod, __exception, () => ShipLootPlusUiHelperPatchSupport.IsKnownSafeCalculateLootValueNullReference(__originalMethod, __0, __1), ShipLootPlusUiHelperPatchSupport.Warnings, ShipLootPlusUiHelperPatchSupport.UnknownWarnings); } } [HarmonyPatch] internal static class ShipLootPlusRefreshElementValuesPatch { [HarmonyPrepare] private static bool Prepare() { return ShipLootPlusUiHelperPatchSupport.ShouldPatchRefreshElementValues(); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return ShipLootPlusUiHelperPatchSupport.ResolveRefreshElementValuesTarget(logResolvedTarget: true); } private static Exception Finalizer(MethodBase __originalMethod, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return OptionalCompatibilityUtility.HandleNullReference("ShipLootPlus.UiHelper", __originalMethod, __exception, ShipLootPlusUiHelperPatchSupport.IsKnownSafeUiStateNullReference, ShipLootPlusUiHelperPatchSupport.Warnings, ShipLootPlusUiHelperPatchSupport.UnknownWarnings); } } [HarmonyPatch] internal static class ShipLootPlusGeneratedUiHelperPatch { [HarmonyPrepare] private static bool Prepare() { return ShipLootPlusUiHelperPatchSupport.ShouldPatchGeneratedMethods(); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { return ShipLootPlusUiHelperPatchSupport.ResolveGeneratedTargets(logResolvedTargets: true); } private static Exception Finalizer(MethodBase __originalMethod, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return OptionalCompatibilityUtility.HandleNullReference("ShipLootPlus.UiHelper", __originalMethod, __exception, () => ShipLootPlusUiHelperPatchSupport.IsKnownSafeGeneratedNullReference(__originalMethod), ShipLootPlusUiHelperPatchSupport.Warnings, ShipLootPlusUiHelperPatchSupport.UnknownWarnings); } } [HarmonyPatch] internal static class NightVisionInsideLightingPostfixPatch { private const string TargetKey = "NightVision.InsideLightingPostfix"; private const string TargetSignature = "NightVision.Patches.NightVisionOutdoors::InsideLightingPostfix(TimeOfDay) : void static"; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return OptionalCompatibilityUtility.ShouldResolveTarget(ErrorFixConfig.NightVisionInsideLightingPostfixGuardMode, "NightVision.Patches.NightVisionOutdoors::InsideLightingPostfix(TimeOfDay) : void static") && OptionalCompatibilityUtility.ShouldPatchResolvedTarget(TargetMethod() != null, "NightVision.InsideLightingPostfix", "NightVision.Patches.NightVisionOutdoors::InsideLightingPostfix(TimeOfDay) : void static"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { Type typeByName = OptionalCompatibilityUtility.GetTypeByName("NightVision.Patches.NightVisionOutdoors"); return OptionalPatchTargetResolver.FindMethod(typeByName, "InsideLightingPostfix", true, typeof(void), typeof(TimeOfDay)); } private static Exception Finalizer(MethodBase __originalMethod, TimeOfDay __0, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return OptionalCompatibilityUtility.HandleNullReference("NightVision.InsideLightingPostfix", __originalMethod, __exception, () => IsKnownSafeNullReference(__0), Warnings, UnknownWarnings); } private static bool IsKnownSafeNullReference(TimeOfDay timeOfDay) { if ((Object)(object)timeOfDay == (Object)null || (Object)(object)timeOfDay.sunIndirect == (Object)null) { return true; } Type typeByName = OptionalCompatibilityUtility.GetTypeByName("UnityEngine.Rendering.HighDefinition.HDAdditionalLightData"); return typeByName != null && (Object)(object)((Component)timeOfDay.sunIndirect).GetComponent(typeByName) == (Object)null; } } [HarmonyPatch] internal static class ChatCommandApiStartHostPostfixPatch { private const string TargetKey = "ChatCommandAPI.GameNetworkManager_StartHost.Postfix"; private const string TargetSignature = "ChatCommandAPI.Patches.GameNetworkManager_StartHost::Postfix() : void static"; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return OptionalCompatibilityUtility.ShouldResolveTarget(ErrorFixConfig.ChatCommandsStartHostPostfixGuardMode, "ChatCommandAPI.Patches.GameNetworkManager_StartHost::Postfix() : void static") && OptionalCompatibilityUtility.ShouldPatchResolvedTarget(TargetMethod() != null, "ChatCommandAPI.GameNetworkManager_StartHost.Postfix", "ChatCommandAPI.Patches.GameNetworkManager_StartHost::Postfix() : void static"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { Type typeByName = OptionalCompatibilityUtility.GetTypeByName("ChatCommandAPI.Patches.GameNetworkManager_StartHost"); return OptionalPatchTargetResolver.FindMethod(typeByName, "Postfix", true, typeof(void)); } private static Exception Finalizer(MethodBase __originalMethod, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return OptionalCompatibilityUtility.HandleNullReference("ChatCommandAPI.GameNetworkManager_StartHost.Postfix", __originalMethod, __exception, IsKnownSafeNullReference, Warnings, UnknownWarnings); } private static bool IsKnownSafeNullReference() { Type typeByName = OptionalCompatibilityUtility.GetTypeByName("ChatCommandAPI.ChatCommandAPI"); if (typeByName == null) { OptionalCompatibilityUtility.LogMemberMismatch("ChatCommandAPI.GameNetworkManager_StartHost.Postfix", null, "ChatCommandAPI.ChatCommandAPI"); return false; } if (!OptionalCompatibilityUtility.TryGetStaticMemberValue("ChatCommandAPI.GameNetworkManager_StartHost.Postfix", typeByName, "confirmationRequests", out var value)) { return false; } return value == null; } } internal static class OptionalCompatibilityUtility { private static readonly WarningLimiter TargetWarnings = new WarningLimiter(); private static readonly object ReflectionCacheLock = new object(); private static readonly Dictionary TypeCache = new Dictionary(); private static readonly Dictionary, MemberInfo> MemberCache = new Dictionary, MemberInfo>(); private static readonly Dictionary SignatureCache = new Dictionary(); internal static bool ShouldPatch(ConfigEntry modeEntry, MethodBase target, string targetKey, string expectedSignature) { return ShouldPatch(modeEntry, target != null, targetKey, expectedSignature); } internal static bool ShouldPatch(ConfigEntry modeEntry, bool targetExists, string targetKey, string expectedSignature) { return ShouldResolveTarget(modeEntry, expectedSignature) && ShouldPatchResolvedTarget(targetExists, targetKey, expectedSignature); } internal static bool ShouldResolveTarget(ConfigEntry modeEntry, string expectedSignature) { PatchEnableMode patchEnableMode = modeEntry?.Value ?? PatchEnableMode.Auto; if (!PatchModeUtility.IsEnabled(patchEnableMode, GameAssemblyIdentity.IsVerified)) { if (patchEnableMode == PatchEnableMode.Auto && !GameAssemblyIdentity.IsVerified) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Optional compatibility target disabled until the game assembly is verified: " + expectedSignature + ".")); } } return false; } return true; } internal static bool ShouldPatchResolvedTarget(bool targetExists, string targetKey, string expectedSignature) { if (!targetExists) { TargetWarnings.Warn("optional-target-missing|" + targetKey, "Optional compatibility target skipped because the expected signature was not found: " + expectedSignature + "."); return false; } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Optional compatibility target enabled: " + expectedSignature + ".")); } return true; } internal static void LogResolvedTarget(string targetKey, MethodBase method) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Optional compatibility target resolved: " + FormatSignature(method) + ".")); } } internal static void LogMemberMismatch(string targetKey, Type type, string memberName) { string text = ((type != null) ? type.FullName : "missing type"); TargetWarnings.Warn("optional-member-mismatch|" + targetKey + "|" + text + "|" + memberName, "Optional compatibility member mismatch for " + targetKey + ": " + text + "::" + memberName + " was not found; returning original NullReferenceException."); } internal static Type GetTypeByName(string typeName) { lock (ReflectionCacheLock) { if (TypeCache.TryGetValue(typeName, out var value)) { return value; } } Type type = FindTypeByName(typeName); lock (ReflectionCacheLock) { TypeCache[typeName] = type; } return type; } private static Type FindTypeByName(string typeName) { if (string.IsNullOrEmpty(typeName)) { return null; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(typeName, throwOnError: false); if (type != null) { return type; } } return null; } internal static Exception HandleNullReference(string targetKey, MethodBase originalMethod, Exception exception, Func isKnownSafe, WarningLimiter knownWarnings, WarningLimiter unknownWarnings) { if (exception == null) { return null; } if (!(exception is NullReferenceException)) { return exception; } bool flag; try { flag = isKnownSafe?.Invoke() ?? false; } catch (Exception ex) { unknownWarnings.Warn(targetKey + "|classifier|" + ex.GetType().Name, "Optional compatibility classifier failed for " + FormatSignature(originalMethod) + "; returning original NullReferenceException: " + ex.GetType().Name + "."); return exception; } if (flag) { string signature = FormatSignature(originalMethod); knownWarnings.Warn(targetKey + "|known|" + signature, () => "Suppressed known-safe optional mod NullReferenceException in " + signature + "."); return null; } unknownWarnings.Warn(targetKey + "|unknown|" + exception.GetType().Name, () => $"Unhandled optional mod NullReferenceException in {FormatSignature(originalMethod)}; returning original exception. First stack fingerprint: {Fingerprint(exception)}. First detail: {exception}"); return exception; } internal static bool TryGetInstanceMemberValue(string targetKey, object instance, string memberName, out object value) { if (instance == null) { value = null; return true; } bool flag = TryGetMemberValue(instance.GetType(), instance, memberName, out value); if (!flag) { LogMemberMismatch(targetKey, instance.GetType(), memberName); } return flag; } internal static bool TryGetInstanceMemberValueSilently(object instance, string memberName, out object value) { if (instance == null) { value = null; return true; } return TryGetMemberValue(instance.GetType(), instance, memberName, out value); } internal static bool TryGetStaticMemberValue(string targetKey, Type type, string memberName, out object value) { bool flag = TryGetMemberValue(type, null, memberName, out value); if (!flag) { LogMemberMismatch(targetKey, type, memberName); } return flag; } internal static bool IsUnityObjectMissing(object value) { if (value == null) { return true; } Object val = (Object)((value is Object) ? value : null); return val != null && val == (Object)null; } internal static bool TryGetMemberValue(Type type, object instance, string memberName, out object value) { value = null; if (type == null || string.IsNullOrEmpty(memberName)) { return false; } MemberInfo cachedMember = GetCachedMember(type, memberName); if (cachedMember is PropertyInfo propertyInfo) { value = propertyInfo.GetValue(instance, null); return true; } if (cachedMember is FieldInfo fieldInfo) { value = fieldInfo.GetValue(instance); return true; } return false; } private static MemberInfo GetCachedMember(Type type, string memberName) { Tuple key = Tuple.Create(type, memberName); lock (ReflectionCacheLock) { if (MemberCache.TryGetValue(key, out var value)) { return value; } } PropertyInfo propertyInfo = AccessTools.Property(type, memberName); if (propertyInfo != null) { lock (ReflectionCacheLock) { MemberCache[key] = propertyInfo; } return propertyInfo; } FieldInfo fieldInfo = AccessTools.Field(type, memberName); lock (ReflectionCacheLock) { MemberCache[key] = fieldInfo; } return fieldInfo; } private static string FormatSignature(MethodBase method) { if (method == null) { return "unknown"; } lock (ReflectionCacheLock) { if (SignatureCache.TryGetValue(method, out var value)) { return value; } } string text = string.Join(", ", from parameter in method.GetParameters() select parameter.ParameterType.Name); string text2 = ((method is MethodInfo methodInfo) ? methodInfo.ReturnType.Name : "void"); string text3 = (method.IsStatic ? "static" : "instance"); string text4 = (method.DeclaringType?.FullName ?? "unknown") + "::" + method.Name + "(" + text + ") : " + text2 + " " + text3; lock (ReflectionCacheLock) { SignatureCache[method] = text4; } return text4; } private static string Fingerprint(Exception exception) { if (exception == null) { return "unknown"; } string text = exception.StackTrace?.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); return string.IsNullOrEmpty(text) ? exception.GetType().Name : text; } } internal static class OptionalPatchTargetResolver { internal static MethodBase FindMethod(Type type, string methodName, bool isStatic, Type returnType, params Type[] parameterTypes) { if (type == null) { return null; } MethodInfo methodInfo = AccessTools.Method(type, methodName, parameterTypes, (Type[])null); if (methodInfo == null || methodInfo.IsStatic != isStatic || (returnType != null && methodInfo.ReturnType != returnType)) { return null; } return methodInfo; } } [HarmonyPatch(typeof(PlayerControllerB), "NearOtherPlayers")] internal static class PlayerControllerBNearOtherPlayersPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(PlayerControllerB __instance, float checkRadius, ref bool __result) { try { if (!HasUnsafeDependencies(__instance, out var reason)) { return true; } bool result = CheckNearOtherPlayersSafely(__instance, checkRadius, ref __result); Warnings.Warn("near-other-players|" + reason, "Used safe PlayerControllerB.NearOtherPlayers fallback because " + reason + "."); return result; } catch (Exception ex) { __result = false; Warnings.Warn("near-other-players|guard-failure", "Skipped PlayerControllerB.NearOtherPlayers because the guard failed safely: " + ex.GetType().Name + "."); return false; } } private static bool HasUnsafeDependencies(PlayerControllerB player, out string reason) { if ((Object)(object)player == (Object)null) { reason = "player was null"; return true; } if ((Object)(object)((Component)player).transform == (Object)null) { reason = "player transform was missing"; return true; } if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null) { reason = "StartOfRound player list was missing"; return true; } PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if ((Object)(object)val == (Object)null) { reason = $"allPlayerScripts[{i}] was null"; return true; } if ((Object)(object)val != (Object)(object)player && val.isPlayerControlled && (Object)(object)((Component)val).transform == (Object)null) { reason = $"allPlayerScripts[{i}].transform was missing"; return true; } } reason = string.Empty; return false; } private static bool CheckNearOtherPlayersSafely(PlayerControllerB __instance, float checkRadius, ref bool __result) { //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_00b2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null) { __result = false; return false; } float num = checkRadius * checkRadius; PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)__instance) && val.isPlayerControlled && !((Object)(object)((Component)val).transform == (Object)null)) { Vector3 val2 = ((Component)__instance).transform.position - ((Component)val).transform.position; if (((Vector3)(ref val2)).sqrMagnitude < num) { __result = true; return false; } } } __result = false; return false; } } [HarmonyPatch(typeof(PlayerControllerB), "PlayJumpAudio")] internal static class PlayerControllerBPlayJumpAudioPatch { private const int MaxWarnings = 5; private static int _warningCount; private static bool Prefix(PlayerControllerB __instance) { try { if (!TryGetUnsafeJumpAudioFallback(__instance, out var fallbackClip, out var reason)) { return true; } PlayJumpAudioSafely(__instance, fallbackClip, reason); } catch (Exception ex) { Warn("Skipped PlayerControllerB.PlayJumpAudio because the guard failed safely: " + ex.GetType().Name + "."); } return false; } private static void PlayJumpAudioSafely(PlayerControllerB player, AudioClip fallbackClip, string reason) { if ((Object)(object)player == (Object)null || (Object)(object)player.movementAudio == (Object)null || (Object)(object)fallbackClip == (Object)null) { Warn("Skipped PlayerControllerB.PlayJumpAudio because audio dependencies were not ready: " + reason + "."); return; } Warn("Used default jump audio because vanilla suit jump audio dependencies were unsafe: " + reason + "."); player.movementAudio.PlayOneShot(fallbackClip); } private static Exception Finalizer(PlayerControllerB __instance, Exception __exception) { if ((__exception is ArgumentOutOfRangeException || __exception is NullReferenceException) && TryGetUnsafeJumpAudioFallback(__instance, out var _, out var reason)) { Warn("Suppressed PlayerControllerB.PlayJumpAudio " + __exception.GetType().Name + " after known unsafe jump audio dependencies were detected: " + reason + "."); return null; } return __exception; } internal static bool TryGetUnsafeJumpAudioFallback(PlayerControllerB player, out AudioClip fallbackClip, out string reason) { fallbackClip = TryGetDefaultJumpAudio(); if ((Object)(object)player == (Object)null) { reason = "player was null"; return true; } if ((Object)(object)player.movementAudio == (Object)null) { reason = "movementAudio was missing"; return true; } if ((Object)(object)StartOfRound.Instance == (Object)null) { reason = "StartOfRound.Instance was missing"; return true; } if ((Object)(object)StartOfRound.Instance.unlockablesList == (Object)null || StartOfRound.Instance.unlockablesList.unlockables == null) { reason = "StartOfRound.Instance.unlockablesList was missing"; return true; } if (player.currentSuitID < 0 || player.currentSuitID >= StartOfRound.Instance.unlockablesList.unlockables.Count) { reason = $"currentSuitID {player.currentSuitID} outside unlockables count {StartOfRound.Instance.unlockablesList.unlockables.Count}"; return true; } UnlockableItem val = StartOfRound.Instance.unlockablesList.unlockables[player.currentSuitID]; if (val == null) { reason = $"unlockables[{player.currentSuitID}] was null"; return true; } if ((Object)(object)val.jumpAudio == (Object)null) { reason = (((Object)(object)fallbackClip != (Object)null) ? $"unlockables[{player.currentSuitID}].jumpAudio was null; using StartOfRound.playerJumpSFX" : $"unlockables[{player.currentSuitID}].jumpAudio was null and StartOfRound.playerJumpSFX was missing"); return true; } reason = string.Empty; return false; } private static AudioClip TryGetDefaultJumpAudio() { return ((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.playerJumpSFX : null; } internal static void Warn(string message) { if (_warningCount < 5) { _warningCount++; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{message} ({_warningCount}/{5})"); } } } } [HarmonyPatch(typeof(PlayerControllerB), "PlayerJumpedClientRpc")] internal static class PlayerControllerBPlayerJumpedClientRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static readonly WarningLimiter NonExecuteStageWarnings = new WarningLimiter(1); private static Exception Finalizer(PlayerControllerB __instance, Exception __exception) { if (!(__exception is ArgumentOutOfRangeException) && !(__exception is NullReferenceException)) { return __exception; } if (!RpcExecStageUtility.ShouldAllowClientRpcSuppression((NetworkBehaviour)(object)__instance, "PlayerControllerB.PlayerJumpedClientRpc", __exception, NonExecuteStageWarnings)) { return __exception; } if (IsKnownSafeJumpRpcException(__instance, out var reason)) { Warnings.Warn("PlayerJumpedClientRpc|" + reason, "Suppressed PlayerControllerB.PlayerJumpedClientRpc " + __exception.GetType().Name + " after known unsafe jump dependencies were detected: " + reason + "."); return null; } UnknownWarnings.Warn("PlayerJumpedClientRpc|" + __exception.GetType().Name, () => $"Unhandled PlayerControllerB.PlayerJumpedClientRpc {__exception.GetType().Name}; returning original exception. First stack fingerprint: {Fingerprint(__exception)}. First detail: {__exception}"); return __exception; } private static bool IsKnownSafeJumpRpcException(PlayerControllerB player, out string reason) { if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).IsOwner) { reason = "owner path does not call jump audio"; return false; } if ((Object)(object)StartOfRound.Instance == (Object)null) { reason = "StartOfRound.Instance was missing"; return true; } if (StartOfRound.Instance.PlayerJumpEvent == null) { reason = "StartOfRound.PlayerJumpEvent was missing"; return true; } AudioClip fallbackClip; return PlayerControllerBPlayJumpAudioPatch.TryGetUnsafeJumpAudioFallback(player, out fallbackClip, out reason); } private static string Fingerprint(Exception exception) { string text = exception?.StackTrace; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0]; } } return exception?.GetType().Name ?? "unknown"; } } [HarmonyPatch(typeof(PlayerControllerB), "ThrowObjectClientRpc")] internal static class PlayerControllerBThrowObjectClientRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(PlayerControllerB __instance, bool droppedInElevator, bool droppedInShipRoom, Vector3 targetFloorPosition, NetworkObjectReference grabbedObject, int floorYRot) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) if (!PatchModeUtility.IsEnabled(ErrorFixConfig.ThrowObjectClientRpcGuardMode)) { return true; } if (!RpcExecStageUtility.TryIsExecuting((NetworkBehaviour)(object)__instance, out var isExecuting) || !isExecuting) { return true; } object stage = null; bool rpcStageChanged = false; bool stateMutated = false; try { if (!ShouldHandleThrowObjectClientRpc(__instance, grabbedObject, out var reason)) { return true; } RpcExecStageUtility.TryGetStage((NetworkBehaviour)(object)__instance, out stage); Warnings.Warn("guarded-throw|" + GetPlayerId(__instance) + "|" + reason, "Using guarded ThrowObjectClientRpc path for player #" + GetPlayerId(__instance) + " because " + reason + "."); HandleThrowObjectClientRpcSafely(__instance, droppedInElevator, droppedInShipRoom, targetFloorPosition, grabbedObject, floorYRot, ref rpcStageChanged, ref stateMutated); } catch (Exception ex) { if (stateMutated) { SetRpcExecStageSend(__instance); TryFinishThrowingIfOwner(__instance); Warnings.Warn("guard-failed-after-mutation", "ThrowObjectClientRpc guard failed after partial state changes; skipped vanilla to avoid double-discard: " + ex.GetType().Name + "."); return false; } if (rpcStageChanged) { RpcExecStageUtility.TrySetStage((NetworkBehaviour)(object)__instance, stage); } Warnings.Warn("guard-failed-before-mutation", "ThrowObjectClientRpc guard failed before changing held object state and allowed vanilla to run: " + ex.GetType().Name + "."); return true; } return false; } private static bool ShouldHandleThrowObjectClientRpc(PlayerControllerB player, NetworkObjectReference grabbedObject, out string reason) { reason = string.Empty; if ((Object)(object)player == (Object)null || (Object)(object)((NetworkBehaviour)player).NetworkManager == (Object)null || !((NetworkBehaviour)player).NetworkManager.IsListening || (!((NetworkBehaviour)player).NetworkManager.IsClient && !((NetworkBehaviour)player).NetworkManager.IsHost)) { return false; } NetworkObject val = default(NetworkObject); if (!((NetworkObjectReference)(ref grabbedObject)).TryGet(ref val, (NetworkManager)null) || (Object)(object)val == (Object)null) { reason = "grabbed NetworkObject reference could not be resolved"; return true; } GrabbableObject component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { reason = "NetworkObject had no GrabbableObject"; return true; } if ((Object)(object)component.itemProperties == (Object)null) { reason = "GrabbableObject.itemProperties was missing"; return true; } if (!((NetworkBehaviour)player).IsOwner && HasUnsafeRemoteThrowDependencies(player, component, out reason)) { return true; } if ((Object)(object)component != (Object)(object)player.currentlyHeldObjectServer) { reason = "grabbed object did not match currentlyHeldObjectServer"; return true; } return false; } private static bool HasUnsafeRemoteThrowDependencies(PlayerControllerB player, GrabbableObject grabbableObject, out string reason) { if (player.ItemSlots == null) { reason = "player ItemSlots were missing"; return true; } if ((Object)(object)player.playersManager == (Object)null) { reason = "player manager was missing"; return true; } if ((Object)(object)player.playersManager.elevatorTransform == (Object)null || (Object)(object)player.playersManager.propsContainer == (Object)null) { reason = "drop parent transforms were missing"; return true; } if ((Object)(object)grabbableObject == (Object)null || (Object)(object)((Component)grabbableObject).transform == (Object)null) { reason = "drop object transform was missing"; return true; } reason = string.Empty; return false; } private static void HandleThrowObjectClientRpcSafely(PlayerControllerB player, bool droppedInElevator, bool droppedInShipRoom, Vector3 targetFloorPosition, NetworkObjectReference grabbedObject, int floorYRot, ref bool rpcStageChanged, ref bool stateMutated) { //IL_0168: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)((NetworkBehaviour)player).NetworkManager == (Object)null || !((NetworkBehaviour)player).NetworkManager.IsListening || (!((NetworkBehaviour)player).NetworkManager.IsClient && !((NetworkBehaviour)player).NetworkManager.IsHost)) { return; } SetRpcExecStageSend(player); rpcStageChanged = true; NetworkObject val = default(NetworkObject); if (((NetworkObjectReference)(ref grabbedObject)).TryGet(ref val, (NetworkManager)null) && (Object)(object)val != (Object)null) { GrabbableObject component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { Warnings.Warn($"missing-grabbable|{player.playerClientId}", $"Skipped ThrowObjectClientRpc for player #{player.playerClientId} because the NetworkObject had no GrabbableObject."); FinishThrowingIfOwner(player); return; } if ((Object)(object)component != (Object)(object)player.currentlyHeldObjectServer) { Warnings.Warn($"held-mismatch|{player.playerClientId}", $"Suppressed ThrowObjectClientRpc held-object mismatch for player #{player.playerClientId}; currentlyHeldObjectServer was {GetHeldObjectName(player.currentlyHeldObjectServer)}."); FinishThrowingIfOwner(player); return; } string reason = string.Empty; if (!((NetworkBehaviour)player).IsOwner && (Object)(object)component.itemProperties != (Object)null && !HasUnsafeRemoteThrowDependencies(player, component, out reason)) { stateMutated = true; player.SetObjectAsNoLongerHeld(droppedInElevator, droppedInShipRoom, targetFloorPosition, component, floorYRot); } else if (!((NetworkBehaviour)player).IsOwner) { reason = (((Object)(object)component.itemProperties == (Object)null) ? "itemProperties was missing" : reason); Warnings.Warn($"unsafe-remote-drop|{player.playerClientId}|{reason}", $"Skipped remote SetObjectAsNoLongerHeld in ThrowObjectClientRpc for player #{player.playerClientId} because {reason}."); } if ((Object)(object)component.itemProperties == (Object)null || !component.itemProperties.syncDiscardFunction) { stateMutated = true; component.playerHeldBy = null; } stateMutated = true; player.currentlyHeldObjectServer = null; } else { Warnings.Warn($"missing-network-object|{player.playerClientId}", $"Suppressed ThrowObjectClientRpc because the server object reference was missing for player #{player.playerClientId}."); } stateMutated = true; FinishThrowingIfOwner(player); } private static string GetHeldObjectName(GrabbableObject heldObject) { if ((Object)(object)heldObject == (Object)null) { return "null"; } return ((Object)(object)((Component)heldObject).gameObject != (Object)null) ? ((Object)((Component)heldObject).gameObject).name : "missing GameObject"; } private static void SetRpcExecStageSend(PlayerControllerB player) { if (RpcExecStageUtility.TryGetSendStage(out var sendStage)) { RpcExecStageUtility.TrySetStage((NetworkBehaviour)(object)player, sendStage); } } private static void FinishThrowingIfOwner(PlayerControllerB player) { if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).IsOwner) { SetThrowingObject(player, value: false); } } private static void TryFinishThrowingIfOwner(PlayerControllerB player) { try { FinishThrowingIfOwner(player); } catch (Exception ex) { Warnings.Warn("finish-throwing-failed|" + GetPlayerId(player), "Skipped ThrowObjectClientRpc owner throwing cleanup after guard failure because cleanup failed safely: " + ex.GetType().Name + "."); } } private static string GetPlayerId(PlayerControllerB player) { return ((Object)(object)player != (Object)null) ? player.playerClientId.ToString() : "unknown"; } private static void SetThrowingObject(PlayerControllerB player, bool value) { if ((Object)(object)player != (Object)null) { player.throwingObject = value; } } } [HarmonyPatch(typeof(PlayerControllerB), "GrabObjectClientRpc")] internal static class PlayerControllerBGrabObjectClientRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static readonly WarningLimiter NonExecuteStageWarnings = new WarningLimiter(1); private static Exception Finalizer(PlayerControllerB __instance, bool grabValidated, NetworkObjectReference grabbedObject, Exception __exception) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!(__exception is NullReferenceException)) { return __exception; } if (!RpcExecStageUtility.ShouldAllowClientRpcSuppression((NetworkBehaviour)(object)__instance, "PlayerControllerB.GrabObjectClientRpc", __exception, NonExecuteStageWarnings)) { return __exception; } if (IsKnownSafeGrabNre(__instance, grabValidated, grabbedObject, out var reason)) { Warnings.Warn("GrabObjectClientRpc|" + GetPlayerId(__instance) + "|" + reason, () => $"Suppressed PlayerControllerB.GrabObjectClientRpc NullReferenceException for player #{GetPlayerId(__instance)} after known unsafe grab dependencies were detected: {reason}. First stack fingerprint: {Fingerprint(__exception)}. First detail: {__exception}"); return null; } UnknownWarnings.Warn("GrabObjectClientRpc|" + GetPlayerId(__instance) + "|" + __exception.GetType().Name, () => $"Unhandled PlayerControllerB.GrabObjectClientRpc NullReferenceException for player #{GetPlayerId(__instance)}; returning original exception. First stack fingerprint: {Fingerprint(__exception)}. First detail: {__exception}"); return __exception; } private static bool IsKnownSafeGrabNre(PlayerControllerB player, bool grabValidated, NetworkObjectReference grabbedObject, out string reason) { if ((Object)(object)player == (Object)null) { reason = "player was null"; return true; } if (!grabValidated) { reason = "grab was not validated"; return false; } NetworkObject val = default(NetworkObject); if (!((NetworkObjectReference)(ref grabbedObject)).TryGet(ref val, (NetworkManager)null) || (Object)(object)val == (Object)null) { reason = "grabbed NetworkObject reference could not be resolved"; return true; } GrabbableObject val2 = (((Object)(object)((Component)val).gameObject != (Object)null) ? ((Component)val).gameObject.GetComponentInChildren() : ((Component)val).GetComponentInChildren()); if ((Object)(object)val2 == (Object)null) { reason = "grabbed NetworkObject had no GrabbableObject"; return true; } if ((Object)(object)player.currentlyHeldObjectServer == (Object)null) { reason = "currentlyHeldObjectServer was null after grab validation"; return true; } if ((Object)(object)player.currentlyHeldObjectServer.itemProperties == (Object)null) { reason = "currentlyHeldObjectServer.itemProperties was null"; return true; } if ((Object)(object)player.playersManager == (Object)null || (Object)(object)player.playersManager.propsContainer == (Object)null) { reason = "player props container was missing"; return true; } if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.elevatorTransform == (Object)null) { reason = "StartOfRound elevator dependencies were missing"; return true; } if (!((NetworkBehaviour)player).IsOwner && (Object)(object)player.serverItemHolder == (Object)null) { reason = "remote player serverItemHolder was missing"; return true; } if (!((NetworkBehaviour)player).IsOwner && (Object)(object)player.itemAudio == (Object)null) { reason = "remote player itemAudio was missing"; return true; } if (((NetworkBehaviour)player).IsOwner && player.currentItemSlot == 50 && ((Object)(object)HUDManager.Instance == (Object)null || (Object)(object)HUDManager.Instance.itemOnlySlotIconFrame == (Object)null)) { reason = "utility belt tutorial HUD dependencies were missing"; return true; } reason = string.Empty; return false; } private static string GetPlayerId(PlayerControllerB player) { return ((Object)(object)player != (Object)null) ? player.playerClientId.ToString() : "unknown"; } private static string Fingerprint(Exception exception) { string text = exception?.StackTrace; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0]; } } return exception?.GetType().Name ?? "unknown"; } } [HarmonyPatch(typeof(QuicksandTrigger), "OnExit")] internal static class QuicksandTriggerOnExitPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(QuicksandTrigger __instance, Collider other) { try { return ShouldRunVanilla(__instance, other); } catch (Exception ex) { Warnings.Warn("guard-failure", "Skipped QuicksandTrigger.OnExit because the guard failed safely before vanilla execution: " + ex.GetType().Name + "."); return false; } } private static bool ShouldRunVanilla(QuicksandTrigger quicksandTrigger, Collider other) { if ((Object)(object)quicksandTrigger == (Object)null || (Object)(object)other == (Object)null) { Warnings.Warn("missing-trigger-or-collider", "Skipped QuicksandTrigger.OnExit because the trigger or collider was missing."); return false; } if (!((Component)other).CompareTag("Player")) { return true; } if ((Object)(object)GameNetworkManager.Instance == (Object)null) { Warnings.Warn("missing-network-manager", "Skipped QuicksandTrigger.OnExit because GameNetworkManager.Instance was missing."); return false; } PlayerControllerB val = (((Object)(object)((Component)other).gameObject != (Object)null) ? (((Component)other).gameObject.GetComponent() ?? ((Component)other).GetComponentInParent()) : ((Component)other).GetComponentInParent()); if ((Object)(object)val == (Object)null) { Warnings.Warn("missing-player", "Skipped QuicksandTrigger.OnExit for '" + ((Object)other).name + "' because no PlayerControllerB was found on the player collider."); return false; } return true; } } [HarmonyPatch(typeof(RedLocustBees), "IsHiveMissing")] internal static class RedLocustBeesIsHiveMissingPatch { private const float HiveNearbyDistanceSqr = 16f; private const float HiveVisibleDistanceSqr = 64f; private const float HiveMovedDistanceSqr = 36f; private static bool Prefix(RedLocustBees __instance, ref bool __result) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)((EnemyAI)__instance).eye == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null) { __result = false; return false; } if ((Object)(object)__instance.hive == (Object)null) { __result = true; return false; } Vector3 val = ((EnemyAI)__instance).eye.position - __instance.lastKnownHivePosition; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (!__instance.syncedLastKnownHivePosition) { __result = false; return false; } bool flag = sqrMagnitude < 64f && !Physics.Linecast(((EnemyAI)__instance).eye.position, __instance.lastKnownHivePosition, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1); if (sqrMagnitude < 16f || flag) { val = ((Component)__instance.hive).transform.position - __instance.lastKnownHivePosition; if ((((Vector3)(ref val)).sqrMagnitude > 36f && !IsHivePlacedAndInLOS(__instance)) || __instance.hive.isHeld) { __result = true; return false; } __instance.lastKnownHivePosition = ((Component)__instance.hive).transform.position + Vector3.up * 0.5f; } __result = false; return false; } private static Exception Finalizer(RedLocustBees __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "RedLocustBees.IsHiveMissing", () => (Object)(object)__instance == (Object)null || (Object)(object)((EnemyAI)__instance).eye == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)__instance.hive == (Object)null); } private static bool IsHivePlacedAndInLOS(RedLocustBees bees) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bees.hive == (Object)null || (Object)(object)((EnemyAI)bees).eye == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || bees.hive.isHeld) { return false; } Vector3 val = ((EnemyAI)bees).eye.position - ((Component)bees.hive).transform.position; return ((Vector3)(ref val)).sqrMagnitude <= 81f && !Physics.Linecast(((EnemyAI)bees).eye.position, ((Component)bees.hive).transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1); } } [HarmonyPatch(typeof(RedLocustBees), "IsHivePlacedAndInLOS")] internal static class RedLocustBeesIsHivePlacedAndInLOSPatch { private const float HiveLineOfSightDistanceSqr = 81f; private static bool Prefix(RedLocustBees __instance, ref bool __result) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.hive == (Object)null || (Object)(object)((EnemyAI)__instance).eye == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || __instance.hive.isHeld) { __result = false; return false; } Vector3 val = ((EnemyAI)__instance).eye.position - ((Component)__instance.hive).transform.position; __result = ((Vector3)(ref val)).sqrMagnitude <= 81f && !Physics.Linecast(((EnemyAI)__instance).eye.position, ((Component)__instance.hive).transform.position, StartOfRound.Instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1); return false; } private static Exception Finalizer(RedLocustBees __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "RedLocustBees.IsHivePlacedAndInLOS", () => (Object)(object)__instance == (Object)null || (Object)(object)__instance.hive == (Object)null || (Object)(object)((EnemyAI)__instance).eye == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null); } } [HarmonyPatch(typeof(RedLocustBees), "DoAIInterval")] internal static class RedLocustBeesDoAIIntervalPatch { private static bool Prefix(RedLocustBees __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null) { return false; } if (!__instance.hasSpawnedHive || (Object)(object)__instance.hive != (Object)null) { return true; } ((EnemyAI)__instance).targetPlayer = null; ((EnemyAI)__instance).movingTowardsTargetPlayer = false; if (((EnemyAI)__instance).currentBehaviourStateIndex != 2) { ((EnemyAI)__instance).SwitchToBehaviourState(2); } return false; } private static Exception Finalizer(RedLocustBees __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "RedLocustBees.DoAIInterval", () => (Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)__instance.hive == (Object)null); } } [HarmonyPatch(typeof(RedLocustBees), "Update")] internal static class RedLocustBeesUpdatePatch { private static bool Prefix(RedLocustBees __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null) { return false; } if ((Object)(object)((EnemyAI)__instance).agent == (Object)null || (Object)(object)__instance.beeParticles == (Object)null || (Object)(object)__instance.beeParticlesTarget == (Object)null) { return false; } return true; } private static Exception Finalizer(RedLocustBees __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "RedLocustBees.Update", () => (Object)(object)__instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)((EnemyAI)__instance).agent == (Object)null || (Object)(object)__instance.beeParticles == (Object)null || (Object)(object)__instance.beeParticlesTarget == (Object)null); } } [HarmonyPatch(typeof(SandSpiderAI), "PlayerLeaveWebClientRpc")] internal static class SandSpiderAIPlayerLeaveWebClientRpcPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static readonly WarningLimiter NonExecuteStageWarnings = new WarningLimiter(1); private static Exception Finalizer(SandSpiderAI __instance, int trapID, int playerNum, Exception __exception) { if (!(__exception is ArgumentOutOfRangeException) && !(__exception is IndexOutOfRangeException)) { return __exception; } if (!RpcExecStageUtility.ShouldAllowClientRpcSuppression((NetworkBehaviour)(object)__instance, "SandSpiderAI.PlayerLeaveWebClientRpc", __exception, NonExecuteStageWarnings)) { return __exception; } if (IsKnownDesync(__instance, trapID, playerNum, out var reason)) { Warnings.Warn("PlayerLeaveWebClientRpc|" + reason, "Suppressed SandSpiderAI.PlayerLeaveWebClientRpc " + __exception.GetType().Name + " for known desync: " + reason + "."); return null; } UnknownWarnings.Warn($"PlayerLeaveWebClientRpc|trap:{trapID}|player:{playerNum}", $"Unhandled SandSpiderAI.PlayerLeaveWebClientRpc {__exception.GetType().Name}; returning original exception for trap {trapID}, player {playerNum}."); return __exception; } private static bool IsKnownDesync(SandSpiderAI spider, int trapID, int playerNum, out string reason) { if ((Object)(object)spider == (Object)null || spider.webTraps == null) { reason = "spider web trap list was missing"; return false; } if (trapID < 0 || trapID >= spider.webTraps.Count) { reason = $"trapID {trapID} outside webTraps count {spider.webTraps.Count}"; return true; } if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.allPlayerScripts == null) { reason = "player list was missing"; return false; } if (playerNum < 0 || playerNum >= StartOfRound.Instance.allPlayerScripts.Length) { reason = $"playerNum {playerNum} outside allPlayerScripts length {StartOfRound.Instance.allPlayerScripts.Length}"; return true; } reason = string.Empty; return false; } } [HarmonyPatch(typeof(GrabbableObjectPhysicsTrigger), "OnTriggerEnter")] internal static class GrabbableObjectPhysicsTriggerOnTriggerEnterPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static bool Prefix(GrabbableObjectPhysicsTrigger __instance, Collider other) { if ((Object)(object)__instance?.itemScript != (Object)null && (Object)(object)((other != null) ? ((Component)other).gameObject : null) != (Object)null) { return true; } Warnings.Warn("missing-trigger-dependency", "Skipped GrabbableObjectPhysicsTrigger.OnTriggerEnter because itemScript or collider was missing."); return false; } private static Exception Finalizer(GrabbableObjectPhysicsTrigger __instance, Collider other, Exception __exception) { if (!(__exception is NullReferenceException)) { return __exception; } if ((Object)(object)__instance?.itemScript == (Object)null || (Object)(object)((other != null) ? ((Component)other).gameObject : null) == (Object)null) { Warnings.Warn("nre|missing-trigger-dependency", "Suppressed GrabbableObjectPhysicsTrigger.OnTriggerEnter NullReferenceException after known missing itemScript or collider dependencies were detected."); return null; } UnknownWarnings.Warn("nre|unknown|" + __exception.GetType().Name, () => $"Unhandled GrabbableObjectPhysicsTrigger.OnTriggerEnter NullReferenceException; returning original exception. First stack fingerprint: {Fingerprint(__exception)}. First detail: {__exception}"); return __exception; } private static string Fingerprint(Exception exception) { string text = exception?.StackTrace; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0]; } } return exception?.GetType().Name ?? "unknown"; } } [HarmonyPatch(typeof(SoccerBallProp), "ActivatePhysicsTrigger")] internal static class SoccerBallPropActivatePhysicsTriggerPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static bool Prefix(SoccerBallProp __instance, Collider other) { if ((Object)(object)__instance == (Object)null || (Object)(object)((other != null) ? ((Component)other).gameObject : null) == (Object)null) { Warnings.Warn("missing-trigger", "Skipped SoccerBallProp.ActivatePhysicsTrigger because the ball or collider was missing."); return false; } GameObject gameObject = ((Component)other).gameObject; if (!IsPlayerOrEnemy(gameObject)) { return true; } if ((Object)(object)StartOfRound.Instance == (Object)null) { Warnings.Warn("missing-startofround", "Skipped SoccerBallProp.ActivatePhysicsTrigger because StartOfRound was not ready."); return false; } return true; } private static Exception Finalizer(SoccerBallProp __instance, Collider other, Exception __exception) { if (!(__exception is NullReferenceException)) { return __exception; } if (IsKnownSafeActivateNre(__instance, other, out var reason)) { Warnings.Warn("nre|" + reason, "Suppressed SoccerBallProp.ActivatePhysicsTrigger NullReferenceException after known unsafe trigger dependencies were detected: " + reason + "."); return null; } UnknownWarnings.Warn("nre|unknown|" + __exception.GetType().Name, () => $"Unhandled SoccerBallProp.ActivatePhysicsTrigger NullReferenceException; returning original exception. First stack fingerprint: {Fingerprint(__exception)}. First detail: {__exception}"); return __exception; } private static bool IsKnownSafeActivateNre(SoccerBallProp ball, Collider other, out string reason) { if ((Object)(object)ball == (Object)null) { reason = "ball was null"; return true; } if ((Object)(object)((other != null) ? ((Component)other).gameObject : null) == (Object)null) { reason = "collider or collider GameObject was missing"; return true; } if (IsPlayerOrEnemy(((Component)other).gameObject) && (Object)(object)StartOfRound.Instance == (Object)null) { reason = "StartOfRound.Instance was missing for a player/enemy trigger"; return true; } reason = string.Empty; return false; } private static bool IsPlayerOrEnemy(GameObject gameObject) { return (Object)(object)gameObject != (Object)null && (gameObject.CompareTag("Player") || gameObject.CompareTag("Enemy")); } private static string Fingerprint(Exception exception) { string text = exception?.StackTrace; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0]; } } return exception?.GetType().Name ?? "unknown"; } } [HarmonyPatch(typeof(SoccerBallProp), "BeginKickBall")] internal static class SoccerBallPropBeginKickBallPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static bool Prefix(SoccerBallProp __instance, bool hitByEnemy) { if ((Object)(object)__instance != (Object)null && ((hitByEnemy && !((NetworkBehaviour)__instance).IsServer) || ((GrabbableObject)__instance).isHeld || (Object)(object)((GrabbableObject)__instance).parentObject != (Object)null)) { return false; } if (HasRequiredKickDependencies(__instance)) { return true; } Warnings.Warn("missing-kick-dependency", "Skipped SoccerBallProp.BeginKickBall because required ball, round, player, or ship references were missing."); return false; } private static Exception Finalizer(SoccerBallProp __instance, Exception __exception) { if (!(__exception is NullReferenceException)) { return __exception; } if (HasKnownUnsafeKickDependencies(__instance, out var reason)) { Warnings.Warn("nre|" + reason, "Suppressed SoccerBallProp.BeginKickBall NullReferenceException after known unsafe kick dependencies were detected: " + reason + "."); return null; } UnknownWarnings.Warn("nre|unknown|" + __exception.GetType().Name, () => $"Unhandled SoccerBallProp.BeginKickBall NullReferenceException; returning original exception. First stack fingerprint: {Fingerprint(__exception)}. First detail: {__exception}"); return __exception; } private static bool HasRequiredKickDependencies(SoccerBallProp ball) { string reason; return !HasKnownUnsafeKickDependencies(ball, out reason); } private static bool HasKnownUnsafeKickDependencies(SoccerBallProp ball, out string reason) { if ((Object)(object)ball == (Object)null || (Object)(object)((Component)ball).transform == (Object)null || (Object)(object)((GrabbableObject)ball).itemProperties == (Object)null) { reason = "ball, transform, or itemProperties was missing"; return true; } StartOfRound instance = StartOfRound.Instance; RoundManager instance2 = RoundManager.Instance; GameNetworkManager instance3 = GameNetworkManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || (Object)(object)instance3?.localPlayerController == (Object)null) { reason = "round, start-of-round, or local player dependencies were missing"; return true; } if ((Object)(object)instance.elevatorTransform == (Object)null || (Object)(object)instance.propsContainer == (Object)null || (Object)(object)instance.shipBounds == (Object)null || (Object)(object)instance.shipInnerRoomBounds == (Object)null || (Object)(object)instance2.spawnedScrapContainer == (Object)null) { reason = "ship bounds or scrap container dependencies were missing"; return true; } reason = string.Empty; return false; } private static string Fingerprint(Exception exception) { string text = exception?.StackTrace; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0]; } } return exception?.GetType().Name ?? "unknown"; } } [HarmonyPatch(typeof(SoundManager), "PlayAmbienceClipLocal")] internal static class SoundManagerPlayAmbienceClipLocalPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(SoundManager __instance, int soundType, int clipIndex, float soundVolume, bool playInsanitySounds) { try { return IsAmbienceClipValid(__instance, soundType, clipIndex, playInsanitySounds); } catch (Exception ex) { Warnings.Warn("guard-failed", "SoundManager.PlayAmbienceClipLocal guard failed safely and allowed original playback: " + ex.GetType().Name + "."); return true; } } private static Exception Finalizer(SoundManager __instance, int soundType, int clipIndex, bool playInsanitySounds, Exception __exception) { if (__exception is IndexOutOfRangeException && IsAmbienceClipInvalid(__instance, soundType, clipIndex, playInsanitySounds, out var clipCount)) { Warnings.Warn($"index-exception|{soundType}|{playInsanitySounds}", $"Suppressed SoundManager.PlayAmbienceClipLocal IndexOutOfRangeException for known invalid clip soundType {soundType}, clipIndex {clipIndex}, playInsanitySounds={playInsanitySounds}, clipCount={clipCount}."); return null; } return __exception; } private static bool IsAmbienceClipValid(SoundManager soundManager, int soundType, int clipIndex, bool playInsanitySounds) { if ((Object)(object)soundManager == (Object)null || (Object)(object)soundManager.currentLevelAmbience == (Object)null) { Warnings.Warn("missing-ambience", "Skipped SoundManager.PlayAmbienceClipLocal because currentLevelAmbience was missing."); return false; } if (clipIndex < 0) { Warnings.Warn($"negative-index|{soundType}|{playInsanitySounds}", $"Skipped SoundManager.PlayAmbienceClipLocal because clipIndex {clipIndex} was negative for soundType {soundType}."); return false; } LevelAmbienceLibrary currentLevelAmbience = soundManager.currentLevelAmbience; if (playInsanitySounds) { return IsInsanityClipValid(currentLevelAmbience, soundType, clipIndex); } return IsNormalClipValid(currentLevelAmbience, soundType, clipIndex); } private static bool IsNormalClipValid(LevelAmbienceLibrary ambience, int soundType, int clipIndex) { if (1 == 0) { } AudioClip[] array = soundType switch { 0 => ambience.insideAmbience, 1 => ambience.outsideAmbience, 2 => ambience.shipAmbience, _ => null, }; if (1 == 0) { } AudioClip[] array2 = array; if (array2 != null && clipIndex < array2.Length && (Object)(object)array2[clipIndex] != (Object)null) { return true; } WarnInvalidIndex(soundType, clipIndex, playInsanitySounds: false, (array2 != null) ? array2.Length : 0); return false; } private static bool IsInsanityClipValid(LevelAmbienceLibrary ambience, int soundType, int clipIndex) { if (1 == 0) { } RandomAudioClip[] array = soundType switch { 0 => ambience.insideAmbienceInsanity, 1 => ambience.outsideAmbienceInsanity, 2 => ambience.shipAmbienceInsanity, _ => null, }; if (1 == 0) { } RandomAudioClip[] array2 = array; if (array2 != null && clipIndex < array2.Length && array2[clipIndex] != null && (Object)(object)array2[clipIndex].audioClip != (Object)null) { return true; } WarnInvalidIndex(soundType, clipIndex, playInsanitySounds: true, (array2 != null) ? array2.Length : 0); return false; } private static void WarnInvalidIndex(int soundType, int clipIndex, bool playInsanitySounds, int clipCount) { Warnings.Warn($"invalid-index|{soundType}|{playInsanitySounds}", $"Skipped SoundManager.PlayAmbienceClipLocal because soundType {soundType}, clipIndex {clipIndex}, playInsanitySounds={playInsanitySounds} was outside clip count {clipCount} or the clip was missing."); } private static bool IsAmbienceClipInvalid(SoundManager soundManager, int soundType, int clipIndex, bool playInsanitySounds, out int clipCount) { clipCount = 0; if ((Object)(object)soundManager == (Object)null || (Object)(object)soundManager.currentLevelAmbience == (Object)null) { return false; } LevelAmbienceLibrary currentLevelAmbience = soundManager.currentLevelAmbience; if (playInsanitySounds) { if (1 == 0) { } RandomAudioClip[] array = soundType switch { 0 => currentLevelAmbience.insideAmbienceInsanity, 1 => currentLevelAmbience.outsideAmbienceInsanity, 2 => currentLevelAmbience.shipAmbienceInsanity, _ => null, }; if (1 == 0) { } RandomAudioClip[] array2 = array; clipCount = ((array2 != null) ? array2.Length : 0); return array2 != null && (clipIndex < 0 || clipIndex >= array2.Length); } if (1 == 0) { } AudioClip[] array3 = soundType switch { 0 => currentLevelAmbience.insideAmbience, 1 => currentLevelAmbience.outsideAmbience, 2 => currentLevelAmbience.shipAmbience, _ => null, }; if (1 == 0) { } AudioClip[] array4 = array3; clipCount = ((array4 != null) ? array4.Length : 0); return array4 != null && (clipIndex < 0 || clipIndex >= array4.Length); } } [HarmonyPatch] internal static class SteamValveDamageTriggerSpawnPatch { private const string DamageTriggerName = "damageTrigger"; private static bool loggedActivation; [HarmonyPrepare] private static bool Prepare() { return PatchModeUtility.IsExplicitlyEnabled(ErrorFixConfig.SteamValveDamageTriggerSpawnGuardMode) && TargetMethod() != null; } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return AccessTools.Method(typeof(NetworkObject), "InvokeBehaviourNetworkSpawn", (Type[])null, (Type[])null); } private static void Prefix(NetworkObject __instance, ref List __state) { if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).transform == (Object)null) { return; } Transform val = FindSelfOrChildByName(((Component)__instance).transform, "damageTrigger"); if ((Object)(object)val == (Object)null) { return; } InteractTrigger component = ((Component)val).GetComponent(); if (IsSteamValveDamageTrigger(__instance, component)) { if (__state == null) { __state = new List(1); } ActivateInactiveHierarchy(val, ((Component)__instance).transform, __state); LogActivationOnce(__instance, component); } } private static Exception Finalizer(List __state, Exception __exception) { RestoreInactive(__state); return __exception; } private static void RestoreInactive(List gameObjects) { if (gameObjects == null) { return; } for (int i = 0; i < gameObjects.Count; i++) { GameObject val = gameObjects[i]; if ((Object)(object)val != (Object)null) { val.SetActive(false); } } } private static void ActivateInactiveHierarchy(Transform target, Transform root, List activatedObjects) { Transform val = target; while ((Object)(object)val != (Object)null) { GameObject gameObject = ((Component)val).gameObject; if ((Object)(object)gameObject != (Object)null && !gameObject.activeSelf) { activatedObjects.Add(gameObject); gameObject.SetActive(true); } if ((Object)(object)val == (Object)(object)root) { break; } val = val.parent; } } private static void LogActivationOnce(NetworkObject networkObject, InteractTrigger trigger) { if (!loggedActivation) { loggedActivation = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"Temporarily activated SteamValve damageTrigger '{GetPath(((Component)trigger).transform)}' during Netcode spawn for NetworkObject #{networkObject.NetworkObjectId}."); } } } private static bool IsSteamValveDamageTrigger(NetworkObject networkObject, InteractTrigger trigger) { if ((Object)(object)trigger == (Object)null || (Object)(object)((NetworkBehaviour)trigger).NetworkObject != (Object)(object)networkObject || (Object)(object)((Component)trigger).gameObject == (Object)null || ((Component)trigger).gameObject.activeInHierarchy || !string.Equals(((Object)((Component)trigger).gameObject).name, "damageTrigger", StringComparison.Ordinal)) { return false; } return HasSteamValveHazardParent(((Component)trigger).transform); } private static Transform FindSelfOrChildByName(Transform root, string childName) { if ((Object)(object)root == (Object)null) { return null; } if (string.Equals(((Object)root).name, childName, StringComparison.Ordinal)) { return root; } for (int i = 0; i < root.childCount; i++) { Transform child = root.GetChild(i); if (!((Object)(object)child == (Object)null)) { if (string.Equals(((Object)child).name, childName, StringComparison.Ordinal)) { return child; } Transform val = FindSelfOrChildByName(child, childName); if ((Object)(object)val != (Object)null) { return val; } } } return null; } private static bool HasSteamValveHazardParent(Transform transform) { Transform val = transform; while ((Object)(object)val != (Object)null) { if ((Object)(object)((Component)val).GetComponent() != (Object)null) { return true; } val = val.parent; } return false; } private static string GetPath(Transform transform) { if ((Object)(object)transform == (Object)null) { return "unknown"; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } [HarmonyPatch] internal static class StingrayCreateSlimePatch { private const float WarningCooldownSeconds = 5f; private const int MaxWarnings = 5; private static float _nextWarningTime; private static int _warningCount; [HarmonyTargetMethods] private static IEnumerable TargetMethods() { MethodInfo createSlimeSingle = AccessTools.Method(typeof(StingrayAI), "CreateSlime", new Type[1] { typeof(Vector3) }, (Type[])null); MethodInfo createSlimeArray = AccessTools.Method(typeof(StingrayAI), "CreateSlime", new Type[1] { typeof(Vector3[]) }, (Type[])null); if (createSlimeSingle != null) { yield return createSlimeSingle; } if (createSlimeArray != null) { yield return createSlimeArray; } } [HarmonyPrefix] private static bool Prefix(StingrayAI __instance, MethodBase __originalMethod) { if ((Object)(object)__instance == (Object)null) { ThrottledWarning((__originalMethod?.Name ?? "CreateSlime") + " skipped because dependency was not ready: __instance"); return false; } if (TryGetMissingDependency(__instance, out var missingDependency)) { ThrottledWarning((__originalMethod?.Name ?? "CreateSlime") + " skipped because dependency was not ready: " + missingDependency); return false; } return true; } private static bool TryGetMissingDependency(StingrayAI stingray, out string missingDependency) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { missingDependency = "StartOfRound.Instance"; return true; } RoundManager instance2 = RoundManager.Instance; if ((Object)(object)instance2 == (Object)null) { missingDependency = "RoundManager.Instance"; return true; } if ((Object)(object)instance2.mapPropsContainer == (Object)null) { missingDependency = "RoundManager.Instance.mapPropsContainer"; return true; } if ((Object)(object)stingray.slimePrefab == (Object)null) { missingDependency = "__instance.slimePrefab"; return true; } if (instance.slimeDecals == null) { missingDependency = "StartOfRound.Instance.slimeDecals"; return true; } if (instance.slimeDecalsFadingIn == null) { missingDependency = "StartOfRound.Instance.slimeDecalsFadingIn"; return true; } if (instance.slimeDecalsFadingIn.Length == 0) { missingDependency = "StartOfRound.Instance.slimeDecalsFadingIn.Length <= 0"; return true; } missingDependency = string.Empty; return false; } private static void ThrottledWarning(string message) { if (_warningCount < 5 && !(Time.realtimeSinceStartup < _nextWarningTime)) { _warningCount++; _nextWarningTime = Time.realtimeSinceStartup + 5f; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"{message} ({_warningCount}/{5})"); } } } } internal static class PlayerRagdollGlobalTagGuard { internal static bool ShouldPatch() { return PatchModeUtility.IsExplicitlyEnabled(ErrorFixConfig.PlayerRagdollGlobalTagGuardMode); } } [HarmonyPatch] internal static class PlayerRagdollCompareTagGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PlayerRagdollGlobalTagGuard.ShouldPatch(); } [HarmonyTargetMethods] private static IEnumerable TargetMethods() { MethodInfo gameObjectCompareTag = AccessTools.Method(typeof(GameObject), "CompareTag", new Type[1] { typeof(string) }, (Type[])null); if (gameObjectCompareTag != null) { yield return gameObjectCompareTag; } MethodInfo componentCompareTag = AccessTools.Method(typeof(Component), "CompareTag", new Type[1] { typeof(string) }, (Type[])null); if (componentCompareTag != null) { yield return componentCompareTag; } } private static Exception Finalizer(Exception __exception, string __0, ref bool __result) { if (__exception == null) { return null; } if (!PlayerRagdollTagUtility.IsPlayerRagdollTag(__0) || !PlayerRagdollTagUtility.IsUndefinedPlayerRagdollTagException(__exception, __0)) { return __exception; } __result = false; Warnings.Warn(__0, "Suppressed undefined tag comparison for '" + __0 + "'."); return null; } } [HarmonyPatch(typeof(GameObject), "FindWithTag")] internal static class PlayerRagdollFindWithTagGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PlayerRagdollGlobalTagGuard.ShouldPatch(); } private static Exception Finalizer(string tag, Exception __exception, ref GameObject __result) { if (__exception == null) { return null; } if (!PlayerRagdollTagUtility.IsPlayerRagdollTag(tag) || !PlayerRagdollTagUtility.IsUndefinedPlayerRagdollTagException(__exception, tag)) { return __exception; } __result = null; Warnings.Warn(tag, "Suppressed undefined tag lookup GameObject.FindWithTag('" + tag + "') and returned null."); return null; } } [HarmonyPatch(typeof(GameObject), "FindGameObjectWithTag")] internal static class PlayerRagdollFindGameObjectWithTagGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PlayerRagdollGlobalTagGuard.ShouldPatch(); } private static Exception Finalizer(string tag, Exception __exception, ref GameObject __result) { if (__exception == null) { return null; } if (!PlayerRagdollTagUtility.IsPlayerRagdollTag(tag) || !PlayerRagdollTagUtility.IsUndefinedPlayerRagdollTagException(__exception, tag)) { return __exception; } __result = null; Warnings.Warn(tag, "Suppressed undefined tag lookup GameObject.FindGameObjectWithTag('" + tag + "') and returned null."); return null; } } [HarmonyPatch(typeof(GameObject), "FindGameObjectsWithTag")] internal static class PlayerRagdollFindGameObjectsWithTagGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PlayerRagdollGlobalTagGuard.ShouldPatch(); } private static Exception Finalizer(string tag, Exception __exception, ref GameObject[] __result) { if (__exception == null) { return null; } if (!PlayerRagdollTagUtility.IsPlayerRagdollTag(tag) || !PlayerRagdollTagUtility.IsUndefinedPlayerRagdollTagException(__exception, tag)) { return __exception; } __result = Array.Empty(); Warnings.Warn(tag, "Suppressed undefined tag lookup GameObject.FindGameObjectsWithTag('" + tag + "') and returned an empty array."); return null; } } [HarmonyPatch(typeof(DeadBodyInfo), "Start")] internal static class DeadBodyInfoPlayerRagdollTagGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static Exception Finalizer(DeadBodyInfo __instance, Exception __exception) { if (__exception == null) { return null; } if (__exception is ArgumentOutOfRangeException) { if (IsKnownPlayerRagdollIndexFailure(__instance)) { TryApplyFallbackRagdollTags(__instance); Warn("Suppressed DeadBodyInfo.Start ArgumentOutOfRangeException for known ragdoll/player index path: " + GetRagdollContext(__instance) + "."); return null; } return __exception; } if (!PlayerRagdollTagUtility.IsUndefinedPlayerRagdollTagException(__exception)) { return __exception; } TryApplyFallbackRagdollTags(__instance); Warn("Suppressed undefined PlayerRagdoll tag assignment and fell back to 'PlayerRagdoll'."); return null; } private static void TryApplyFallbackRagdollTags(DeadBodyInfo deadBody) { if ((Object)(object)deadBody == (Object)null || deadBody.bodyParts == null) { return; } for (int i = 0; i < deadBody.bodyParts.Length; i++) { Rigidbody val = deadBody.bodyParts[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)null)) { try { ((Component)val).gameObject.tag = "PlayerRagdoll"; } catch { break; } } } } private static void Warn(string message) { Warnings.Warn("DeadBodyInfo.Start", message); } private static bool IsKnownPlayerRagdollIndexFailure(DeadBodyInfo deadBody) { if ((Object)(object)deadBody == (Object)null || deadBody.bodyParts == null || deadBody.bodyParts.Length == 0) { return false; } int playerObjectId = GetPlayerObjectId(deadBody); int bodyPartsLength = GetBodyPartsLength(deadBody); int allPlayerScriptsLength = GetAllPlayerScriptsLength(); bool playerScriptMissing = (Object)(object)deadBody.playerScript == (Object)null; bool numericRagdollTagMissing = playerObjectId >= 4 && !PlayerRagdollTagUtility.TagExists($"PlayerRagdoll{playerObjectId}"); return IsKnownPlayerRagdollIndexFailure(playerObjectId, bodyPartsLength, allPlayerScriptsLength, playerScriptMissing, numericRagdollTagMissing); } private static int GetPlayerObjectId(DeadBodyInfo deadBody) { return ((Object)(object)deadBody != (Object)null) ? deadBody.playerObjectId : (-1); } internal static bool IsKnownPlayerRagdollIndexFailure(int playerObjectId, int bodyPartsLength, int allPlayerScriptsLength, bool playerScriptMissing, bool numericRagdollTagMissing) { if (playerObjectId < 0 || bodyPartsLength <= 0 || allPlayerScriptsLength < 0) { return false; } if (playerObjectId < allPlayerScriptsLength) { return false; } return playerScriptMissing || numericRagdollTagMissing || playerObjectId >= 4; } private static int GetBodyPartsLength(DeadBodyInfo deadBody) { return ((Object)(object)deadBody != (Object)null && deadBody.bodyParts != null) ? deadBody.bodyParts.Length : (-1); } private static int GetAllPlayerScriptsLength() { return ((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.allPlayerScripts != null) ? StartOfRound.Instance.allPlayerScripts.Length : (-1); } private static string GetRagdollContext(DeadBodyInfo deadBody) { return $"playerObjectId={GetPlayerObjectId(deadBody)}, bodyParts.Length={GetBodyPartsLength(deadBody)}, allPlayerScripts.Length={GetAllPlayerScriptsLength()}, playerScriptMissing={(Object)(object)deadBody?.playerScript == (Object)null}"; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class GameObjectPlayerRagdollTagSetterGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); [HarmonyPrepare] private static bool Prepare() { return PlayerRagdollGlobalTagGuard.ShouldPatch(); } private static Exception Finalizer(GameObject __instance, string value, Exception __exception) { if (__exception == null) { return null; } if (PlayerRagdollTagUtility.IsTagExistenceProbe) { return __exception; } if (!PlayerRagdollTagUtility.IsPlayerRagdollTag(value) || !PlayerRagdollTagUtility.IsUndefinedPlayerRagdollTagException(__exception, value)) { return __exception; } if (PlayerRagdollTagUtility.TagExists(value)) { return __exception; } TrySetFallbackTag(__instance, value); return null; } private static void TrySetFallbackTag(GameObject gameObject, string originalTag) { if ((Object)(object)gameObject == (Object)null) { return; } try { gameObject.tag = "PlayerRagdoll"; Warnings.Warn(originalTag, "Replaced undefined tag '" + originalTag + "' with fallback tag 'PlayerRagdoll' on '" + ((Object)gameObject).name + "'."); } catch { Warnings.Warn(originalTag, "Suppressed undefined tag '" + originalTag + "', but fallback tag 'PlayerRagdoll' could not be assigned."); } } } [HarmonyPatch(typeof(TerminalAccessibleObject), "Update")] internal static class TerminalAccessibleObjectUpdatePatch { private const float InitializeRetryCooldownSeconds = 0.5f; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly Dictionary NextInitializeAttemptTimes = new Dictionary(); private static bool Prefix(TerminalAccessibleObject __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.mapScreen == (Object)null) { return false; } if ((Object)(object)StartOfRound.Instance.mapScreen.mapCamera == (Object)null) { return false; } return (Object)(object)__instance.mapRadarObject != (Object)null || TryInitializeValues(__instance); } private static Exception Finalizer(TerminalAccessibleObject __instance, Exception __exception) { if (__exception == null) { return null; } if (!(__exception is NullReferenceException)) { return __exception; } return NullRefGuard.Suppress(__exception, "TerminalAccessibleObject.Update", () => (Object)(object)__instance == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.mapScreen == (Object)null || (Object)(object)StartOfRound.Instance.mapScreen.mapCamera == (Object)null || (Object)(object)__instance.mapRadarObject == (Object)null); } private static bool TryInitializeValues(TerminalAccessibleObject terminalObject) { try { if (!CanAttemptInitialize(terminalObject)) { return false; } if ((Object)(object)StartOfRound.Instance.objectCodePrefab == (Object)null || (Object)(object)StartOfRound.Instance.mapScreen.mapCameraStationaryUI == (Object)null) { return false; } terminalObject.InitializeValues(); bool flag = (Object)(object)terminalObject.mapRadarObject != (Object)null; if (!flag) { DelayInitializeRetry(terminalObject); } return flag; } catch (Exception exception) { bool flag2 = ShouldContinueAfterInitializeFailure(exception, () => (Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.objectCodePrefab == (Object)null || (Object)(object)StartOfRound.Instance.mapScreen == (Object)null || (Object)(object)StartOfRound.Instance.mapScreen.mapCameraStationaryUI == (Object)null || (Object)(object)terminalObject.mapRadarObject == (Object)null); if (!flag2) { DelayInitializeRetry(terminalObject); } return flag2; } } internal static void ClearCache() { NextInitializeAttemptTimes.Clear(); } private static bool CanAttemptInitialize(TerminalAccessibleObject terminalObject) { if ((Object)(object)terminalObject == (Object)null) { return false; } int instanceID = ((Object)terminalObject).GetInstanceID(); float value; return !NextInitializeAttemptTimes.TryGetValue(instanceID, out value) || Time.realtimeSinceStartup >= value; } private static void DelayInitializeRetry(TerminalAccessibleObject terminalObject) { if ((Object)(object)terminalObject != (Object)null) { NextInitializeAttemptTimes[((Object)terminalObject).GetInstanceID()] = Time.realtimeSinceStartup + 0.5f; } } internal static bool ShouldContinueAfterInitializeFailure(Exception exception, Func isKnownSafeCase) { Exception ex = NullRefGuard.Suppress(exception, "TerminalAccessibleObject.InitializeValues", isKnownSafeCase); if (ex != null) { Warnings.Warn(GetInitializeFailureWarningKey(exception), "Returning original TerminalAccessibleObject.InitializeValues " + (exception?.GetType().Name ?? "null exception") + " because it did not match a known safe null-reference initialization failure."); throw ex; } return false; } internal static string GetInitializeFailureWarningKey(Exception exception) { return "initialize-values-unknown|" + (exception?.GetType().Name ?? "null"); } } [HarmonyPatch] internal static class UnityKnownWarningFilterPatch { private enum KnownUnityWarning { None, AudioSpatializer, BoxColliderNegativeScale, SteamValveEmptyAudioSource, StaticLightingSky } private const string AudioSpatializerWarningPrefix = "Audio source failed to initialize audio spatializer."; private const string BoxColliderNegativeScaleWarningPrefix = "BoxCollider does not support negative scale or size."; private const string SteamValveEmptyAudioSourceWarningPrefix = "Only custom filters can be played. Please add a custom filter or an audioclip to the audiosource (SteamValve(Clone))."; private const string StaticLightingSkyWarningPrefix = "One Static Lighting Sky component was already set for baking, only the latest one will be used."; private static int audioSpatializerFilteredCount; private static int boxColliderFilteredCount; private static int steamValveEmptyAudioSourceFilteredCount; private static int staticLightingSkyFilteredCount; [HarmonyPrepare] private static bool Prepare() { if (!PatchModeUtility.IsExplicitlyEnabled(ErrorFixConfig.KnownUnityWarningFilterMode)) { return false; } bool flag = TargetMethod() != null; if (!flag) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"Known Unity warning filter disabled because BepInEx UnityLogSource.OnUnityLogMessageReceived was not found."); } } return flag; } [HarmonyTargetMethod] private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("BepInEx.Logging.UnityLogSource"); return (type != null) ? AccessTools.Method(type, "OnUnityLogMessageReceived", new Type[3] { typeof(string), typeof(string), typeof(LogType) }, (Type[])null) : null; } private static bool Prefix(string message, LogType type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 if ((int)type != 2 || !ShouldFilter(message, out var warning)) { return true; } IncrementFilteredCount(warning); return false; } internal static void FlushSummary() { int num = Interlocked.Exchange(ref audioSpatializerFilteredCount, 0); int num2 = Interlocked.Exchange(ref boxColliderFilteredCount, 0); int num3 = Interlocked.Exchange(ref steamValveEmptyAudioSourceFilteredCount, 0); int num4 = Interlocked.Exchange(ref staticLightingSkyFilteredCount, 0); if (num != 0 || num2 != 0 || num3 != 0 || num4 != 0) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Known Unity warning filter suppressed warnings since the last scene change: " + $"audioSpatializer={num}, " + $"boxColliderNegativeScale={num2}, " + $"steamValveEmptyAudioSource={num3}, " + $"staticLightingSky={num4}.")); } } } private static bool ShouldFilter(string message, out KnownUnityWarning warning) { warning = KnownUnityWarning.None; if (string.IsNullOrEmpty(message)) { return false; } switch (message[0]) { case 'A': if (!message.StartsWith("Audio source failed to initialize audio spatializer.", StringComparison.Ordinal)) { break; } warning = KnownUnityWarning.AudioSpatializer; return true; case 'B': if (!message.StartsWith("BoxCollider does not support negative scale or size.", StringComparison.Ordinal)) { break; } warning = KnownUnityWarning.BoxColliderNegativeScale; return true; case 'O': if (message.StartsWith("Only custom filters can be played. Please add a custom filter or an audioclip to the audiosource (SteamValve(Clone)).", StringComparison.Ordinal)) { warning = KnownUnityWarning.SteamValveEmptyAudioSource; return true; } if (!message.StartsWith("One Static Lighting Sky component was already set for baking, only the latest one will be used.", StringComparison.Ordinal)) { break; } warning = KnownUnityWarning.StaticLightingSky; return true; } return false; } private static void IncrementFilteredCount(KnownUnityWarning warning) { switch (warning) { case KnownUnityWarning.AudioSpatializer: Interlocked.Increment(ref audioSpatializerFilteredCount); break; case KnownUnityWarning.BoxColliderNegativeScale: Interlocked.Increment(ref boxColliderFilteredCount); break; case KnownUnityWarning.SteamValveEmptyAudioSource: Interlocked.Increment(ref steamValveEmptyAudioSourceFilteredCount); break; case KnownUnityWarning.StaticLightingSky: Interlocked.Increment(ref staticLightingSkyFilteredCount); break; } } } [HarmonyPatch(typeof(UnlockableSuit), "Update")] internal static class UnlockableSuitUpdatePatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(UnlockableSuit __instance) { if (!IsPatchEnabled()) { return true; } if ((Object)(object)__instance == (Object)null || (Object)(object)NetworkManager.Singleton == (Object)null || NetworkManager.Singleton.ShutdownInProgress) { return false; } if (__instance.syncedSuitID == null) { Warnings.Warn("missing-syncedSuitID", "Skipped UnlockableSuit.Update because syncedSuitID was missing."); return false; } int value = __instance.syncedSuitID.Value; if (__instance.suitID == value) { return true; } if (!IsValidSuitId(value)) { Warnings.Warn($"invalid-suit|{value}", $"Skipped UnlockableSuit.Update because synced suit id {value} was outside unlockables list."); return false; } if ((Object)(object)__instance.suitRenderer == (Object)null) { Warnings.Warn($"missing-renderer|{value}", $"Skipped UnlockableSuit.Update for suit id {value} because suitRenderer was missing."); return false; } return true; } private static Exception Finalizer(UnlockableSuit __instance, Exception __exception) { if (!IsPatchEnabled()) { return __exception; } if (__exception is ArgumentOutOfRangeException || __exception is NullReferenceException) { int num = (((Object)(object)__instance != (Object)null && __instance.syncedSuitID != null) ? __instance.syncedSuitID.Value : (-1)); if ((Object)(object)__instance == (Object)null || __instance.syncedSuitID == null || (Object)(object)__instance.suitRenderer == (Object)null || !IsValidSuitId(num)) { Warnings.Warn($"exception|{num}|{__exception.GetType().Name}", $"Suppressed UnlockableSuit.Update {__exception.GetType().Name} for invalid or incomplete suit id {num}."); return null; } } return __exception; } internal static bool IsValidSuitId(int suitId) { return suitId >= 0 && (Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.unlockablesList != (Object)null && StartOfRound.Instance.unlockablesList.unlockables != null && suitId < StartOfRound.Instance.unlockablesList.unlockables.Count && StartOfRound.Instance.unlockablesList.unlockables[suitId] != null; } internal static bool IsPatchEnabled() { return PatchModeUtility.IsEnabled(ErrorFixConfig.UnlockableSuitGuardMode); } } [HarmonyPatch(typeof(UnlockableSuit), "SwitchSuitForPlayer")] internal static class UnlockableSuitSwitchSuitForPlayerPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static bool Prefix(PlayerControllerB player, int suitID) { if (!UnlockableSuitUpdatePatch.IsPatchEnabled()) { return true; } if ((Object)(object)player == (Object)null) { Warnings.Warn("null-player", "Skipped UnlockableSuit.SwitchSuitForPlayer because player was null."); return false; } if (!UnlockableSuitUpdatePatch.IsValidSuitId(suitID)) { Warnings.Warn($"invalid-suit|{suitID}", $"Skipped UnlockableSuit.SwitchSuitForPlayer because suit id {suitID} was outside unlockables list."); return false; } return true; } private static Exception Finalizer(PlayerControllerB player, int suitID, Exception __exception) { if (!UnlockableSuitUpdatePatch.IsPatchEnabled()) { return __exception; } if (__exception is ArgumentOutOfRangeException || __exception is NullReferenceException) { if ((Object)(object)player == (Object)null || !UnlockableSuitUpdatePatch.IsValidSuitId(suitID)) { Warnings.Warn($"exception|{suitID}|{__exception.GetType().Name}", $"Suppressed UnlockableSuit.SwitchSuitForPlayer {__exception.GetType().Name} for known invalid player or suit id {suitID}."); return null; } UnknownWarnings.Warn($"unknown-exception|{suitID}|{__exception.GetType().Name}", () => $"Unhandled UnlockableSuit.SwitchSuitForPlayer {__exception.GetType().Name} for valid suit id {suitID}; returning original exception. First detail: {__exception}"); } return __exception; } } [HarmonyPatch(typeof(UnlockableSuit), "SwitchSuitForAllPlayers")] internal static class UnlockableSuitSwitchSuitForAllPlayersPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static readonly WarningLimiter UnknownWarnings = new WarningLimiter(); private static bool Prefix(int suitID) { if (!UnlockableSuitUpdatePatch.IsPatchEnabled()) { return true; } if (UnlockableSuitUpdatePatch.IsValidSuitId(suitID)) { return true; } Warnings.Warn($"invalid-suit|{suitID}", $"Skipped UnlockableSuit.SwitchSuitForAllPlayers because suit id {suitID} was outside unlockables list."); return false; } private static Exception Finalizer(int suitID, Exception __exception) { if (!UnlockableSuitUpdatePatch.IsPatchEnabled()) { return __exception; } if (__exception is ArgumentOutOfRangeException || __exception is NullReferenceException) { if (!UnlockableSuitUpdatePatch.IsValidSuitId(suitID)) { Warnings.Warn($"exception|{suitID}|{__exception.GetType().Name}", $"Suppressed UnlockableSuit.SwitchSuitForAllPlayers {__exception.GetType().Name} for known invalid suit id {suitID}."); return null; } UnknownWarnings.Warn($"unknown-exception|{suitID}|{__exception.GetType().Name}", () => $"Unhandled UnlockableSuit.SwitchSuitForAllPlayers {__exception.GetType().Name} for valid suit id {suitID}; returning original exception. First detail: {__exception}"); } return __exception; } } [HarmonyPatch] internal static class StartOfRoundSpawnUnlockableSuitNetworkVariablePatch { [HarmonyPrepare] private static bool Prepare() { return UnlockableSuitUpdatePatch.IsPatchEnabled() && TargetMethod() != null && AccessTools.PropertySetter(typeof(NetworkVariable), "Value") != null && AccessTools.Method(typeof(NetworkVariable), "Reset", new Type[1] { typeof(int) }, (Type[])null) != null && AccessTools.Field(typeof(UnlockableSuit), "syncedSuitID") != null; } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return AccessTools.Method(typeof(StartOfRound), "SpawnUnlockable", new Type[2] { typeof(int), typeof(bool) }, (Type[])null); } private static IEnumerable Transpiler(IEnumerable instructions) { MethodInfo methodInfo = AccessTools.PropertySetter(typeof(NetworkVariable), "Value"); MethodInfo methodInfo2 = AccessTools.Method(typeof(NetworkVariable), "Reset", new Type[1] { typeof(int) }, (Type[])null); FieldInfo fieldInfo = AccessTools.Field(typeof(UnlockableSuit), "syncedSuitID"); List list = new List(instructions); if (methodInfo == null || methodInfo2 == null || fieldInfo == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"StartOfRound.SpawnUnlockable suit NetworkVariable patch was skipped because required Netcode or UnlockableSuit members were not found."); } return list; } List list2 = new List(); for (int i = 0; i < list.Count; i++) { if (CodeInstructionExtensions.Calls(list[i], methodInfo) && LoadsSyncedSuitId(list, i, fieldInfo)) { list2.Add(i); } } if (list2.Count != 1) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)$"StartOfRound.SpawnUnlockable suit NetworkVariable patch expected one Value setter but found {list2.Count}; leaving generated method unchanged."); } return list; } list[list2[0]].opcode = OpCodes.Callvirt; list[list2[0]].operand = methodInfo2; ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)"Patched StartOfRound.SpawnUnlockable suit NetworkVariable initialization to use Reset before NetworkObject.Spawn."); } return list; } private static bool LoadsSyncedSuitId(List codes, int setterIndex, FieldInfo syncedSuitIdField) { return setterIndex >= 2 && codes[setterIndex - 2].opcode == OpCodes.Ldfld && object.Equals(codes[setterIndex - 2].operand, syncedSuitIdField); } } [HarmonyPatch(typeof(StartOfRound), "SyncShipUnlockablesClientRpc")] internal static class StartOfRoundSyncShipUnlockablesClientRpcSuitGuardPatch { private static readonly WarningLimiter Warnings = new WarningLimiter(); private static bool Prefix(StartOfRound __instance, ref int[] playerSuitIDs, ref Vector3[] placeableObjectPositions, ref Vector3[] placeableObjectRotations, ref bool[] unlockedObjects, ref int[] storedItems, ref int[] scrapValues, ref int[] itemSaveData) { if (!UnlockableSuitUpdatePatch.IsPatchEnabled()) { return true; } if (!IsExecutingClientRpc(__instance)) { return true; } int num = (((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.allPlayerScripts != null) ? StartOfRound.Instance.allPlayerScripts.Length : 4); int requiredLength = (((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.unlockablesList != (Object)null && StartOfRound.Instance.unlockablesList.unlockables != null) ? StartOfRound.Instance.unlockablesList.unlockables.Count : 0); if (!HasRequiredLength(playerSuitIDs, num, "playerSuitIDs") || !HasRequiredLength(placeableObjectPositions, requiredLength, "placeableObjectPositions") || !HasRequiredLength(placeableObjectRotations, requiredLength, "placeableObjectRotations") || !HasRequiredLength(unlockedObjects, requiredLength, "unlockedObjects")) { return false; } int num2 = Math.Min(playerSuitIDs.Length, num); int unlockablesCount = GetUnlockablesCount(); for (int i = 0; i < num2; i++) { int num3 = playerSuitIDs[i]; if (!UnlockableSuitUpdatePatch.IsValidSuitId(num3)) { if (!UnlockableSuitUpdatePatch.IsValidSuitId(0)) { Warnings.Warn($"invalid-player-suit-no-fallback|{i}|{num3}", $"Skipped SyncShipUnlockablesClientRpc because player slot {i} had invalid suit id {num3} and fallback suit id 0 was unavailable; unlockables count was {unlockablesCount}."); return false; } Warnings.Warn($"invalid-player-suit|{i}|{num3}", $"Replaced invalid synced suit id {num3} for player slot {i} with suit id 0; unlockables count was {unlockablesCount}."); playerSuitIDs[i] = 0; } } return true; } private static bool HasRequiredLength(T[] values, int requiredLength, string arrayName) { if (requiredLength <= 0) { return true; } if (values != null && values.Length >= requiredLength) { return true; } int num = ((values != null) ? values.Length : (-1)); Warnings.Warn($"malformed-sync-array|{arrayName}|{num}|{requiredLength}", $"Skipped SyncShipUnlockablesClientRpc because {arrayName} length {num} was shorter than required length {requiredLength}; preserving original data instead of default-expanding unknown RPC payloads."); return false; } private static int GetUnlockablesCount() { return ((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.unlockablesList != (Object)null && StartOfRound.Instance.unlockablesList.unlockables != null) ? StartOfRound.Instance.unlockablesList.unlockables.Count : 0; } private static bool IsExecutingClientRpc(StartOfRound startOfRound) { bool isExecuting; return RpcExecStageUtility.TryIsExecuting((NetworkBehaviour)(object)startOfRound, out isExecuting) && isExecuting; } } [HarmonyPatch(typeof(StartOfRound), "RefreshPlayerVoicePlaybackObjects")] internal static class StartOfRoundRefreshPlayerVoicePlaybackObjectsPatch { private const float VoiceSettingsCacheSeconds = 1f; private static readonly WarningLimiter Warnings = new WarningLimiter(); private static PlayerVoiceIngameSettings[] _cachedVoiceSettings; private static float _cachedVoiceSettingsUntil; private static int _cachedPlayerSlots = -1; private static bool Prefix(StartOfRound __instance) { if (!IsPatchEnabled()) { return true; } if (!HasKnownBrokenVoiceObjects(__instance)) { return true; } TryRefreshFallback(__instance, "known-missing-components"); return false; } private static Exception Finalizer(StartOfRound __instance, Exception __exception) { if (__exception == null) { return null; } if (!IsPatchEnabled() || !(__exception is NullReferenceException)) { return __exception; } return TryRefreshFallback(__instance, "vanilla-nre") ? null : __exception; } internal static void ClearCache() { _cachedVoiceSettings = null; _cachedVoiceSettingsUntil = 0f; _cachedPlayerSlots = -1; Warnings.ClearPrefix("voice-"); } private static bool TryRefreshFallback(StartOfRound startOfRound, string reason) { try { RefreshPlayerVoicePlaybackObjectsSafely(startOfRound); Warnings.Warn("voice-fallback|" + reason, "Used safe voice playback refresh fallback because " + reason + " was detected."); return true; } catch (Exception ex) { Warnings.Warn("voice-fallback-failed|" + reason, "Voice playback fallback failed; returning original exception path: " + ex.GetType().Name + "."); return false; } } private static bool HasKnownBrokenVoiceObjects(StartOfRound startOfRound) { if ((Object)(object)startOfRound == (Object)null || startOfRound.allPlayerScripts == null) { return false; } PlayerVoiceIngameSettings[] voiceSettings = GetVoiceSettings(startOfRound); if (voiceSettings == null) { return false; } for (int i = 0; i < voiceSettings.Length; i++) { PlayerVoiceIngameSettings val = voiceSettings[i]; if ((Object)(object)val == (Object)null) { ClearCache(); return true; } if ((Object)(object)val.voiceAudio == (Object)null || (Object)(object)val._playbackComponent == (Object)null || (Object)(object)val._dissonanceComms == (Object)null) { Warnings.Warn($"voice-known-broken|{((Object)val).GetInstanceID()}", $"Detected incomplete PlayerVoiceIngameSettings object #{i}; using safe voice refresh fallback."); return true; } } return false; } private static void RefreshPlayerVoicePlaybackObjectsSafely(StartOfRound startOfRound) { if ((Object)(object)startOfRound == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null || startOfRound.allPlayerScripts == null) { return; } PlayerVoiceIngameSettings[] voiceSettings = GetVoiceSettings(startOfRound); if (voiceSettings == null || voiceSettings.Length == 0) { Warnings.Warn("voice-no-settings", "Skipped voice playback refresh because no PlayerVoiceIngameSettings objects were found."); return; } for (int i = 0; i < startOfRound.allPlayerScripts.Length; i++) { PlayerControllerB val = startOfRound.allPlayerScripts[i]; if (!((Object)(object)val == (Object)null) && (val.isPlayerControlled || val.isPlayerDead)) { string text = TryGetNfgoPlayerId(val); if (string.IsNullOrEmpty(text)) { Warnings.Warn($"voice-missing-nfgo-player|{i}", $"Skipped voice playback refresh for player #{i} because NfgoPlayer was missing."); } else { TryConnectVoiceObject(val, text, voiceSettings, i); } } } } private static void TryConnectVoiceObject(PlayerControllerB player, string playerId, PlayerVoiceIngameSettings[] voiceSettings, int playerIndex) { for (int i = 0; i < voiceSettings.Length; i++) { PlayerVoiceIngameSettings val = voiceSettings[i]; if ((Object)(object)val == (Object)null) { continue; } try { if (TryConnectVoiceObject(player, playerId, val, playerIndex, i)) { break; } } catch (Exception ex) { Warnings.Warn($"voice-object-failed|{((Object)val).GetInstanceID()}", $"Skipped voice object #{i} for player #{playerIndex} because inspection failed safely: {ex.GetType().Name}."); } } } private static bool TryConnectVoiceObject(PlayerControllerB player, string playerId, PlayerVoiceIngameSettings voiceSetting, int playerIndex, int voiceIndex) { if ((Object)(object)voiceSetting.voiceAudio == (Object)null || (Object)(object)voiceSetting._playbackComponent == (Object)null || (Object)(object)voiceSetting._dissonanceComms == (Object)null) { voiceSetting.InitializeComponents(); } int instanceID = ((Object)voiceSetting).GetInstanceID(); if (!((Behaviour)voiceSetting).isActiveAndEnabled) { Warnings.Warn($"voice-disabled|{instanceID}", $"Skipped voice object #{voiceIndex} for player #{playerIndex} because it was disabled."); return false; } if (voiceSetting._playerState == null) { voiceSetting.FindPlayerIfNull(); if (voiceSetting._playerState == null) { Warnings.Warn($"voice-state-missing|{instanceID}", $"Skipped voice object #{voiceIndex} for player #{playerIndex} because its player state was not ready."); return false; } } if ((Object)(object)voiceSetting.voiceAudio == (Object)null) { Warnings.Warn($"voice-audio-missing|{instanceID}", $"Skipped voice object #{voiceIndex} for player #{playerIndex} because its AudioSource was missing."); return false; } if (voiceSetting._playerState.Name != playerId) { return false; } player.voicePlayerState = voiceSetting._playerState; player.currentVoiceChatAudioSource = voiceSetting.voiceAudio; player.currentVoiceChatIngameSettings = voiceSetting; TryAssignVoiceMixer(player); return true; } private static string TryGetNfgoPlayerId(PlayerControllerB player) { try { NfgoPlayer val = (((Object)(object)((Component)player).gameObject != (Object)null) ? ((Component)player).gameObject.GetComponentInChildren() : null); return ((Object)(object)val != (Object)null) ? val.PlayerId : null; } catch { return null; } } private static void TryAssignVoiceMixer(PlayerControllerB player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player.currentVoiceChatAudioSource == (Object)null) && !((Object)(object)SoundManager.Instance == (Object)null) && SoundManager.Instance.playerVoiceMixers != null) { int num = (int)player.playerClientId; if (num < 0 || num >= SoundManager.Instance.playerVoiceMixers.Length) { Warnings.Warn($"voice-mixer-index|{num}", $"Skipped voice mixer assignment because player client id {num} was outside mixer count {SoundManager.Instance.playerVoiceMixers.Length}."); } else { player.currentVoiceChatAudioSource.outputAudioMixerGroup = SoundManager.Instance.playerVoiceMixers[num]; } } } private static PlayerVoiceIngameSettings[] GetVoiceSettings(StartOfRound startOfRound) { int num = (((Object)(object)startOfRound != (Object)null && startOfRound.allPlayerScripts != null) ? startOfRound.allPlayerScripts.Length : (-1)); float realtimeSinceStartup = Time.realtimeSinceStartup; if (_cachedVoiceSettings != null && realtimeSinceStartup < _cachedVoiceSettingsUntil && _cachedPlayerSlots == num && !ContainsDestroyedVoiceSettings(_cachedVoiceSettings)) { return _cachedVoiceSettings; } _cachedVoiceSettings = Object.FindObjectsOfType(true); _cachedVoiceSettingsUntil = realtimeSinceStartup + 1f; _cachedPlayerSlots = num; return _cachedVoiceSettings; } private static bool ContainsDestroyedVoiceSettings(PlayerVoiceIngameSettings[] voiceSettings) { if (voiceSettings == null) { return false; } for (int i = 0; i < voiceSettings.Length; i++) { if ((Object)(object)voiceSettings[i] == (Object)null) { return true; } } return false; } private static bool IsPatchEnabled() { return PatchModeUtility.IsEnabled(ErrorFixConfig.VoiceRefreshFallbackMode); } } [BepInPlugin("codex.v81errorfix", "V81 Error Fix", "0.0.4")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "codex.v81errorfix"; public const string PluginName = "V81 Error Fix"; public const string PluginVersion = "0.0.4"; internal static ManualLogSource Log; private void Awake() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ErrorFixConfig.Bind(((BaseUnityPlugin)this).Config); GameAssemblyIdentity.Initialize(); SceneLifecycle.Register(); PatchAllWithIsolation(new Harmony("codex.v81errorfix")); if (PatchModeUtility.IsExplicitlyEnabled(ErrorFixConfig.ParticleMeshShapeGuardMode)) { ParticleMeshShapeGuard.EnsureCreated(); } ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Performance-sensitive guards: AudioSource={ErrorFixConfig.AudioSourcePlaybackGuardMode.Value}; " + $"KnownUnityWarningFilter={ErrorFixConfig.KnownUnityWarningFilterMode.Value}; " + $"PlayerRagdollGlobalTag={ErrorFixConfig.PlayerRagdollGlobalTagGuardMode.Value}; " + $"ParticleMeshShape={ErrorFixConfig.ParticleMeshShapeGuardMode.Value}; " + $"GlobalDestroy={ErrorFixConfig.GlobalDestroyGuardMode.Value} " + $"(installed={NetworkObjectDestroyGuardPatch.ShouldPatch(ErrorFixConfig.GlobalDestroyGuardMode.Value, ErrorFixConfig.EnableGlobalDestroyGuard.Value)}).")); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Assembly verified: {2}; MVID: {3}; SHA256: {4}.", "V81 Error Fix", "0.0.4", GameAssemblyIdentity.IsVerified, GameAssemblyIdentity.CurrentAssemblyMvid, GameAssemblyIdentity.CurrentAssemblySha256 ?? "unknown")); } private void PatchAllWithIsolation(Harmony harmony) { //IL_008b: Expected O, but got Unknown int num = 0; int num2 = 0; foreach (Type item in from type in typeof(Plugin).Assembly.GetTypes().Where(HasHarmonyPatchAttribute) orderby type.FullName select type) { try { harmony.CreateClassProcessor(item).Patch(); num++; } catch (HarmonyException val) { HarmonyException val2 = val; num2++; ((BaseUnityPlugin)this).Logger.LogError((object)("Disabled Harmony patch class " + item.FullName + " because Harmony failed to install it: " + ((Exception)(object)val2).Message)); } catch (Exception ex) { num2++; ((BaseUnityPlugin)this).Logger.LogError((object)("Disabled Harmony patch class " + item.FullName + " because it failed to install: " + ex.GetType().Name + ": " + ex.Message)); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Harmony patch classes processed with isolation: {num} succeeded, {num2} failed."); } private static bool HasHarmonyPatchAttribute(Type type) { return type != null && type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0; } } internal static class SceneLifecycle { private const float DefaultLifecycleDestroyWindowSeconds = 3f; private const float MaxLifecycleDestroyWindowSeconds = 15f; internal static float LifecycleDestroyAllowedUntil { get; private set; } internal static bool IsSceneUnloading => IsLifecycleDestroyAllowed; internal static bool IsLifecycleDestroyAllowed => LifecycleDestroyAllowedUntil > 0f && Time.realtimeSinceStartup <= LifecycleDestroyAllowedUntil; internal static void Register() { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; SceneManager.activeSceneChanged -= OnActiveSceneChanged; SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { UnityKnownWarningFilterPatch.FlushSummary(); AllowLifecycleDestroyWindow(); StartOfRoundRefreshPlayerVoicePlaybackObjectsPatch.ClearCache(); TerminalAccessibleObjectUpdatePatch.ClearCache(); RoundManagerFindMainEntrancePositionPatch.ClearCache(); ParticleMeshShapeGuard.NotifySceneLoaded(); NetworkObjectDestroyGuardPatch.ClearCaches(); WarningLimiter.ClearSceneScopedLimiters(); NullRefGuard.Clear(); } private static void OnSceneUnloaded(Scene scene) { UnityKnownWarningFilterPatch.FlushSummary(); AllowLifecycleDestroyWindow(); StartOfRoundRefreshPlayerVoicePlaybackObjectsPatch.ClearCache(); TerminalAccessibleObjectUpdatePatch.ClearCache(); RoundManagerFindMainEntrancePositionPatch.ClearCache(); ParticleMeshShapeGuard.NotifySceneUnloaded(); NetworkObjectDestroyGuardPatch.ClearCaches(); WarningLimiter.ClearSceneScopedLimiters(); NullRefGuard.Clear(); } private static void OnActiveSceneChanged(Scene previousScene, Scene newScene) { UnityKnownWarningFilterPatch.FlushSummary(); AllowLifecycleDestroyWindow(); TerminalAccessibleObjectUpdatePatch.ClearCache(); RoundManagerFindMainEntrancePositionPatch.ClearCache(); WarningLimiter.ClearSceneScopedLimiters(); NullRefGuard.Clear(); } private static void AllowLifecycleDestroyWindow() { float lifecycleDestroyWindowSeconds = GetLifecycleDestroyWindowSeconds(); LifecycleDestroyAllowedUntil = ((lifecycleDestroyWindowSeconds > 0f) ? (Time.realtimeSinceStartup + lifecycleDestroyWindowSeconds) : 0f); } private static float GetLifecycleDestroyWindowSeconds() { return ClampLifecycleDestroyWindowSeconds(ErrorFixConfig.LifecycleDestroyWindowSeconds?.Value ?? 3f); } internal static float ClampLifecycleDestroyWindowSeconds(float value) { return Mathf.Clamp(value, 0f, 15f); } } internal static class ArrayUtility { internal static T[] EnsureLength(T[] values, int requiredLength, T defaultValue) { if (requiredLength <= 0) { return values ?? Array.Empty(); } if (values != null && values.Length >= requiredLength) { return values; } T[] array = new T[requiredLength]; for (int i = 0; i < array.Length; i++) { array[i] = ((i < ((values != null) ? values.Length : 0)) ? values[i] : defaultValue); } return array; } } internal static class PatchModeUtility { internal static bool IsEnabled(ConfigEntry modeEntry, bool requireVerifiedAssembly = true) { return IsEnabled(modeEntry, PatchEnableMode.Auto, requireVerifiedAssembly); } internal static bool IsEnabled(ConfigEntry modeEntry, PatchEnableMode defaultMode, bool requireVerifiedAssembly = true) { PatchEnableMode mode = modeEntry?.Value ?? defaultMode; return IsEnabled(mode, GameAssemblyIdentity.IsVerified, requireVerifiedAssembly); } internal static bool IsEnabled(PatchEnableMode mode, bool isVerifiedAssembly, bool requireVerifiedAssembly = true) { return mode switch { PatchEnableMode.Auto => !requireVerifiedAssembly || isVerifiedAssembly, PatchEnableMode.Enabled => true, _ => false, }; } internal static bool IsExplicitlyEnabled(ConfigEntry modeEntry, PatchEnableMode defaultMode = PatchEnableMode.Disabled) { return (modeEntry?.Value ?? defaultMode) == PatchEnableMode.Enabled; } } internal static class PlayerRagdollTagUtility { private const string Prefix = "PlayerRagdoll"; private static readonly Dictionary TagExistsCache = new Dictionary(); [ThreadStatic] private static bool _isTagExistenceProbe; internal static bool IsTagExistenceProbe => _isTagExistenceProbe; internal static bool IsPlayerRagdollTag(string tag) { int ragdollIndex; return TryGetPlayerRagdollIndex(tag, out ragdollIndex); } internal static bool TryGetPlayerRagdollIndex(string tag, out int ragdollIndex) { ragdollIndex = -1; if (string.IsNullOrEmpty(tag) || !tag.StartsWith("PlayerRagdoll", StringComparison.Ordinal) || tag.Length == "PlayerRagdoll".Length) { return false; } for (int i = "PlayerRagdoll".Length; i < tag.Length; i++) { if (!char.IsDigit(tag[i])) { return false; } } return int.TryParse(tag.Substring("PlayerRagdoll".Length), out ragdollIndex); } internal static bool IsUndefinedPlayerRagdollTagException(Exception exception, string expectedTag = null) { return exception is UnityException && IsUndefinedPlayerRagdollTagMessage(exception.Message, expectedTag); } internal static bool IsUndefinedPlayerRagdollTagMessage(string message, string expectedTag = null) { if (string.IsNullOrEmpty(message) || !message.StartsWith("Tag: PlayerRagdoll", StringComparison.Ordinal) || !message.Contains("not defined")) { return false; } if (!string.IsNullOrEmpty(expectedTag)) { return IsPlayerRagdollTag(expectedTag) && message.IndexOf(expectedTag, StringComparison.Ordinal) >= 0; } int length = "Tag: ".Length; int num = message.IndexOf(' ', length); if (num <= length) { return false; } return IsPlayerRagdollTag(message.Substring(length, num - length)); } internal static bool TagExists(string tag) { return TagExistsCached(tag, ProbeUnityTagExists); } internal static bool TagExistsCached(string tag, Func probe) { if (string.IsNullOrEmpty(tag) || probe == null) { return false; } if (TagExistsCache.TryGetValue(tag, out var value)) { return value; } value = probe(tag); TagExistsCache[tag] = value; return value; } private static bool ProbeUnityTagExists(string tag) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown GameObject val = null; try { _isTagExistenceProbe = true; val = new GameObject("V81ErrorFix.TagProbe") { hideFlags = (HideFlags)61 }; val.tag = tag; return true; } catch (object obj) when (((Func)delegate { // Could not convert BlockContainer to single expression object obj2 = ((obj is UnityException) ? obj : null); return obj2 != null && IsUndefinedPlayerRagdollTagException((Exception)obj2, tag); }).Invoke()) { return false; } catch { return false; } finally { _isTagExistenceProbe = false; if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } } internal static class RpcExecStageUtility { private static readonly FieldInfo RpcExecStageField = AccessTools.Field(typeof(NetworkBehaviour), "__rpc_exec_stage"); private static bool _initialized; private static bool _ready; private static object _executeStage; private static object _sendStage; internal static bool TryIsExecuting(NetworkBehaviour behaviour, out bool isExecuting) { isExecuting = false; if ((Object)(object)behaviour == (Object)null || !EnsureReady()) { return false; } object value = RpcExecStageField.GetValue(behaviour); isExecuting = object.Equals(value, _executeStage); return true; } internal static bool TryGetSendStage(out object sendStage) { sendStage = null; if (!EnsureReady()) { return false; } sendStage = _sendStage; return true; } internal static bool TryGetStage(NetworkBehaviour behaviour, out object stage) { stage = null; if ((Object)(object)behaviour == (Object)null || !EnsureReady()) { return false; } stage = RpcExecStageField.GetValue(behaviour); return true; } internal static bool TrySetStage(NetworkBehaviour behaviour, object stage) { if ((Object)(object)behaviour == (Object)null || stage == null || !EnsureReady()) { return false; } RpcExecStageField.SetValue(behaviour, stage); return true; } internal static bool ShouldAllowClientRpcSuppression(NetworkBehaviour behaviour, string targetKey, Exception exception, WarningLimiter warnings) { if (TryIsExecuting(behaviour, out var isExecuting) && isExecuting) { return true; } warnings?.Warn(targetKey + "|not-execute-stage", () => "Returning original " + targetKey + " " + (exception?.GetType().Name ?? "exception") + " because the generated RPC stage was not confirmed Execute; send-stage RPC exceptions must not be suppressed. First stack fingerprint: " + Fingerprint(exception) + "."); return false; } private static bool EnsureReady() { if (_initialized) { return _ready; } _initialized = true; Type enumType = RpcExecStageField?.FieldType; _ready = TryParseEnumValue(enumType, "Execute", out _executeStage) && TryParseEnumValue(enumType, "Send", out _sendStage); if (!_ready) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"RPC stage utility disabled because NetworkBehaviour.__rpc_exec_stage or expected enum values were not found."); } } return _ready; } internal static bool TryParseEnumValue(Type enumType, string name, out object value) { value = null; if (enumType == null || string.IsNullOrEmpty(name) || !enumType.IsEnum) { return false; } try { if (!Enum.IsDefined(enumType, name)) { return false; } value = Enum.Parse(enumType, name); return true; } catch { value = null; return false; } } private static string Fingerprint(Exception exception) { string text = exception?.StackTrace; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new string[1] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0]; } } return exception?.GetType().Name ?? "unknown"; } } internal static class VersionInfo { internal const string PluginGuid = "codex.v81errorfix"; internal const string PluginName = "V81 Error Fix"; internal const string PluginVersion = "0.0.4"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }