Decompiled source of ReksikPerformance v0.3.1
ReksikPerformance.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using ReksikPerformance.Rejoin; using ReksikPerformance.Reliability; using ReksikPerformance.ReliabilityGuards; using TMPro; using UnboundLib; using UnboundLib.GameModes; using UnboundLib.Utils.UI; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ReksikPerformance")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+f6034f7bbf3bfa1247464cb333b4b126defee508")] [assembly: AssemblyProduct("ReksikPerformance")] [assembly: AssemblyTitle("ReksikPerformance")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ReksikPerformance { internal sealed class AdaptivePerformanceController { private const int MaximumTier = 3; private const float MinimumTierHoldSeconds = 3.5f; private float _pressureSeconds; private float _recoverySeconds; private float _lastTierChangeAt = -1000f; private int _statusTier = -1; private string _statusState = string.Empty; public int Tier { get; private set; } public string StatusText { get; private set; } = "T0 STABLE"; public void Tick(PerformanceSettings settings, MetricsSampler metrics, VfxBudgetController vfx, CompatibilityGuard compatibility) { if (!settings.AdaptiveEnabled.Value || !settings.Enabled.Value || settings.Profile.Value == PerformanceProfile.Vanilla || compatibility.PerformanceImprovementsDetected || !metrics.HasSnapshot) { Reset(vfx, compatibility.PerformanceImprovementsDetected ? "DEFERRED" : "OFF"); return; } float num = ResolveTargetFps(settings.TargetFrameRate.Value); float num2 = num * 0.88f; float num3 = num * 0.97f; float num4 = Mathf.Min(Time.unscaledDeltaTime, 0.25f); bool flag = metrics.FramesPerSecond < num2 || metrics.OnePercentLowFps < num * 0.68f; bool flag2 = metrics.FramesPerSecond >= num3 && metrics.OnePercentLowFps >= num * 0.84f; if (Time.unscaledTime - _lastTierChangeAt < 3.5f) { _pressureSeconds = Mathf.Max(0f, _pressureSeconds - num4); _recoverySeconds = Mathf.Max(0f, _recoverySeconds - num4); UpdateStatus("HOLD"); return; } if (flag) { _pressureSeconds += num4; _recoverySeconds = Mathf.Max(0f, _recoverySeconds - num4 * 2f); } else if (flag2) { _recoverySeconds += num4; _pressureSeconds = Mathf.Max(0f, _pressureSeconds - num4); } else { _pressureSeconds = Mathf.Max(0f, _pressureSeconds - num4 * 0.5f); _recoverySeconds = Mathf.Max(0f, _recoverySeconds - num4 * 0.5f); } float num5 = Clamp(settings.AdaptivePressureSeconds.Value, 2f, 20f); float num6 = Clamp(settings.AdaptiveRecoverySeconds.Value, 5f, 60f); if (_pressureSeconds >= num5 && Tier < 3) { Tier++; OnTierChanged(vfx); } else if (_recoverySeconds >= num6 && Tier > 0) { Tier--; OnTierChanged(vfx); } UpdateStatus(flag ? "PRESSURE" : (flag2 ? "RECOVERING" : "STABLE")); } public void Reset(VfxBudgetController vfx, string reason = "OFF") { _pressureSeconds = 0f; _recoverySeconds = 0f; _lastTierChangeAt = -1000f; Tier = 0; vfx.SetAdaptiveTier(0); UpdateStatus(reason); } private void OnTierChanged(VfxBudgetController vfx) { _pressureSeconds = 0f; _recoverySeconds = 0f; _lastTierChangeAt = Time.unscaledTime; vfx.SetAdaptiveTier(Tier); } private void UpdateStatus(string state) { if (_statusTier != Tier || !string.Equals(_statusState, state, StringComparison.Ordinal)) { _statusTier = Tier; _statusState = state; StatusText = $"T{Tier} {state}"; } } private static float ResolveTargetFps(int configuredTarget) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (configuredTarget >= 30) { return configuredTarget; } Resolution currentResolution = Screen.currentResolution; int refreshRate = ((Resolution)(ref currentResolution)).refreshRate; if (refreshRate < 30) { return 60f; } return refreshRate; } private static float Clamp(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (!(value < min)) { if (!(value > max)) { return value; } return max; } return min; } } internal sealed class CompatibilityGuard { public const string PerformanceImprovementsGuid = "pykess.rounds.plugins.performanceimprovements"; public bool PerformanceImprovementsDetected { get; } public string StatusText { get { if (!PerformanceImprovementsDetected) { return "NATIVE MODE | NO OVERLAPPING PERFORMANCE PLUGIN DETECTED"; } return "COMPATIBILITY MODE | EXISTING PERFORMANCE IMPROVEMENTS DETECTED"; } } public CompatibilityGuard() { PerformanceImprovementsDetected = Chainloader.PluginInfos.ContainsKey("pykess.rounds.plugins.performanceimprovements"); } } internal sealed class GraphicsController { private readonly struct Snapshot { private bool RunInBackground { get; } private int TargetFrameRate { get; } private int VSyncCount { get; } private int AntiAliasing { get; } private ShadowQuality Shadows { get; } private float ShadowDistance { get; } private int PixelLightCount { get; } private AnisotropicFiltering AnisotropicFiltering { get; } private int MasterTextureLimit { get; } private float LodBias { get; } private Snapshot(bool runInBackground, int targetFrameRate, int vSyncCount, int antiAliasing, ShadowQuality shadows, float shadowDistance, int pixelLightCount, AnisotropicFiltering anisotropicFiltering, int masterTextureLimit, float lodBias) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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) RunInBackground = runInBackground; TargetFrameRate = targetFrameRate; VSyncCount = vSyncCount; AntiAliasing = antiAliasing; Shadows = shadows; ShadowDistance = shadowDistance; PixelLightCount = pixelLightCount; AnisotropicFiltering = anisotropicFiltering; MasterTextureLimit = masterTextureLimit; LodBias = lodBias; } public static Snapshot Capture() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) return new Snapshot(Application.runInBackground, Application.targetFrameRate, QualitySettings.vSyncCount, QualitySettings.antiAliasing, QualitySettings.shadows, QualitySettings.shadowDistance, QualitySettings.pixelLightCount, QualitySettings.anisotropicFiltering, QualitySettings.masterTextureLimit, QualitySettings.lodBias); } public void Restore() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) Application.runInBackground = RunInBackground; Application.targetFrameRate = TargetFrameRate; QualitySettings.vSyncCount = VSyncCount; QualitySettings.antiAliasing = AntiAliasing; QualitySettings.shadows = Shadows; QualitySettings.shadowDistance = ShadowDistance; QualitySettings.pixelLightCount = PixelLightCount; QualitySettings.anisotropicFiltering = AnisotropicFiltering; QualitySettings.masterTextureLimit = MasterTextureLimit; QualitySettings.lodBias = LodBias; } } private readonly ManualLogSource _logger; private readonly Snapshot _startup; public GraphicsController(ManualLogSource logger) { _logger = logger; _startup = Snapshot.Capture(); } public void Apply(PerformanceSettings settings, CompatibilityGuard compatibility) { Restore(); if (settings.ReliabilityEnabled.Value && settings.RunInBackgroundForReliability.Value) { Application.runInBackground = true; } if (settings.Enabled.Value && settings.Profile.Value != PerformanceProfile.Vanilla) { Application.targetFrameRate = NormalizeTargetFrameRate(settings.TargetFrameRate.Value); QualitySettings.vSyncCount = Clamp(settings.VSyncCount.Value, 0, 4); QualitySettings.antiAliasing = NormalizeAntiAliasing(settings.AntiAliasing.Value); QualitySettings.masterTextureLimit = Clamp(settings.TextureLimit.Value, 0, 3); QualitySettings.lodBias = Clamp(settings.LodBias.Value, 0.1f, 5f); QualitySettings.anisotropicFiltering = (AnisotropicFiltering)(settings.AnisotropicFiltering.Value ? 2 : 0); if (!compatibility.PerformanceImprovementsDetected) { QualitySettings.shadows = (ShadowQuality)(settings.Shadows.Value ? 2 : 0); QualitySettings.shadowDistance = Clamp(settings.ShadowDistance.Value, 0f, 500f); QualitySettings.pixelLightCount = Clamp(settings.PixelLightCount.Value, 0, 32); } _logger.LogDebug((object)($"Applied local profile {settings.Profile.Value}; target={Application.targetFrameRate}, " + $"vSync={QualitySettings.vSyncCount}, compatibility={compatibility.PerformanceImprovementsDetected}.")); } } public void Restore() { _startup.Restore(); } private static int NormalizeTargetFrameRate(int value) { if (value >= 0) { return Clamp(value, 30, 1000); } return -1; } private static int NormalizeAntiAliasing(int value) { if (value < 8) { if (value < 4) { if (value < 2) { return 0; } return 2; } return 4; } return 8; } private static int Clamp(int value, int min, int max) { if (value >= min) { if (value <= max) { return value; } return max; } return min; } private static float Clamp(float value, float min, float max) { if (!(value < min)) { if (!(value > max)) { return value; } return max; } return min; } } internal sealed class MetricsOverlay { private GUIStyle? _style; private Texture2D? _background; public void Draw(string text) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) EnsureStyle(); if (_style != null) { GUI.Label(new Rect((float)Screen.width - 390f, 18f, 372f, 76f), text, _style); } } public void Dispose() { if ((Object)(object)_background != (Object)null) { Object.Destroy((Object)(object)_background); _background = null; } _style = null; } private void EnsureStyle() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_003c: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown if (_style == null) { _background = new Texture2D(1, 1, (TextureFormat)4, false) { hideFlags = (HideFlags)61 }; _background.SetPixel(0, 0, new Color(0.035f, 0.04f, 0.065f, 0.92f)); _background.Apply(false, true); GUIStyle val = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)3, fontSize = 13, padding = new RectOffset(12, 12, 7, 7) }; val.normal.background = _background; val.normal.textColor = new Color(0.9f, 0.94f, 1f, 1f); _style = val; } } } internal sealed class MetricsSampler { private const int WindowCapacity = 600; private readonly float[] _frameTimes = new float[600]; private readonly float[] _sortBuffer = new float[600]; private int _count; private int _cursor; private float _nextSnapshotAt; private int _lastGcCollections; public float FramesPerSecond { get; private set; } public float AverageFrameMilliseconds { get; private set; } public float OnePercentLowFps { get; private set; } public float ManagedMemoryMegabytes { get; private set; } public int GcCollectionsSinceLastSnapshot { get; private set; } public bool HasSnapshot { get; private set; } public string OverlayText { get; private set; } = "Sampling..."; public MetricsSampler() { _lastGcCollections = TotalGcCollections(); } public void Tick(PerformanceProfile profile, int particles, int trails, string compatibility, string adaptiveStatus) { float unscaledDeltaTime = Time.unscaledDeltaTime; if (unscaledDeltaTime > 0f && unscaledDeltaTime < 5f && !float.IsNaN(unscaledDeltaTime)) { _frameTimes[_cursor] = unscaledDeltaTime; _cursor = (_cursor + 1) % 600; if (_count < 600) { _count++; } } if (!(Time.unscaledTime < _nextSnapshotAt) && _count != 0) { _nextSnapshotAt = Time.unscaledTime + 0.5f; Recalculate(profile, particles, trails, compatibility, adaptiveStatus); } } private void Recalculate(PerformanceProfile profile, int particles, int trails, string compatibility, string adaptiveStatus) { float num = 0f; for (int i = 0; i < _count; i++) { float num2 = _frameTimes[i]; _sortBuffer[i] = num2; num += num2; } Array.Sort(_sortBuffer, 0, _count); float num3 = num / (float)_count; int num4 = Math.Max(0, Math.Min(_count - 1, (int)Math.Ceiling((float)_count * 0.99f) - 1)); float num5 = _sortBuffer[num4]; AverageFrameMilliseconds = num3 * 1000f; FramesPerSecond = ((num3 > 0f) ? (1f / num3) : 0f); OnePercentLowFps = ((num5 > 0f) ? (1f / num5) : 0f); ManagedMemoryMegabytes = (float)GC.GetTotalMemory(forceFullCollection: false) / 1048576f; int num6 = TotalGcCollections(); GcCollectionsSinceLastSnapshot = Math.Max(0, num6 - _lastGcCollections); _lastGcCollections = num6; HasSnapshot = true; string text = (compatibility.StartsWith("COMPATIBILITY", StringComparison.Ordinal) ? "COMPAT" : "NATIVE"); OverlayText = "REKSIK PERFORMANCE | " + profile.ToString().ToUpperInvariant() + " | " + text + " | ADAPT " + adaptiveStatus + "\n" + $"FPS {FramesPerSecond:0} | 1% LOW {OnePercentLowFps:0} | {AverageFrameMilliseconds:0.0} ms\n" + $"MEM {ManagedMemoryMegabytes:0} MB | GC +{GcCollectionsSinceLastSnapshot} | VFX {particles}/{trails}"; } private static int TotalGcCollections() { return GC.CollectionCount(0) + GC.CollectionCount(1) + GC.CollectionCount(2); } } internal static class PerformanceMenu { private sealed class View { private sealed class TogglePresentation { public string BaseLabel { get; } public TextMeshProUGUI Label { get; } public TogglePresentation(string baseLabel, TextMeshProUGUI label) { BaseLabel = baseLabel; Label = label; } } private readonly ReksikPerformancePlugin _plugin; private readonly Dictionary<PerformanceProfile, Button> _profiles = new Dictionary<PerformanceProfile, Button>(); private readonly Dictionary<Toggle, TogglePresentation> _togglePresentations = new Dictionary<Toggle, TogglePresentation>(); private Toggle? _enabled; private Toggle? _overlay; private Toggle? _reliabilityEnabled; private Toggle? _writeDiagnostics; private Toggle? _runInBackground; private Toggle? _commonGuards; private Toggle? _experimentalRejoin; private Toggle? _adaptive; private Toggle? _shadows; private Toggle? _anisotropic; private TextMeshProUGUI? _compatibility; private TextMeshProUGUI? _reliabilityLive; private TextMeshProUGUI? _live; private TMP_InputField? _fps; private TMP_InputField? _vSync; private TMP_InputField? _aa; private TMP_InputField? _particles; private TMP_InputField? _trail; private bool _refreshing; public GameObject Root { get; } public View(GameObject root, ReksikPerformancePlugin plugin) { Root = root; _plugin = plugin; } public void Build() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) AddText("REKSIK PERFORMANCE / LOCAL CONTROL", 33, Accent, 42f); AddText("SESSION RELIABILITY | SQUEEZE FPS | LIVE EVIDENCE", 18, Muted, 27f); _compatibility = AddText(string.Empty, 18, Muted, 36f); AddText("PILLAR 1 / SESSION RELIABILITY", 22, Accent, 30f); _reliabilityEnabled = AddToggle("ENABLE PASSIVE SESSION RELIABILITY", _plugin.Settings.ReliabilityEnabled.Value, _plugin.Settings.SetReliabilityEnabled); _writeDiagnostics = AddToggle("WRITE BOUNDED DISCONNECT DIAGNOSTICS", _plugin.Settings.WriteReliabilityDiagnostics.Value, _plugin.Settings.SetWriteReliabilityDiagnostics); _runInBackground = AddToggle("KEEP CLIENT ACTIVE WHILE UNFOCUSED", _plugin.Settings.RunInBackgroundForReliability.Value, _plugin.Settings.SetRunInBackgroundForReliability); _commonGuards = AddToggle("COMMON DISCONNECT GUARDS (RESTART REQUIRED)", _plugin.Settings.CommonDisconnectGuards.Value, _plugin.Settings.SetCommonDisconnectGuards); _experimentalRejoin = AddToggle("EXPERIMENTAL NON-HOST REJOIN (RESTART / HOST + CLIENT)", _plugin.Settings.ExperimentalRejoinEnabled.Value, _plugin.Settings.SetExperimentalRejoinEnabled); _reliabilityLive = AddText(string.Empty, 17, Muted, 118f); AddText("PILLAR 2 / SQUEEZE FPS", 22, Accent, 30f); _enabled = AddToggle("ENABLE LOCAL PERFORMANCE PROFILE", _plugin.Settings.Enabled.Value, _plugin.Settings.SetEnabled); _adaptive = AddToggle("ADAPTIVE VFX PRESSURE CONTROL (DEFAULT ON)", _plugin.Settings.AdaptiveEnabled.Value, _plugin.Settings.SetAdaptiveEnabled); _overlay = AddToggle("SHOW LIVE METRICS OVERLAY", _plugin.Settings.ShowOverlay.Value, _plugin.Settings.SetOverlay); AddText("FPS / VISUAL PROFILE", 21, Text, 29f); AddProfile(PerformanceProfile.Vanilla, "VANILLA - RESTORE STARTUP VALUES"); AddProfile(PerformanceProfile.MaxFps, "MAX FPS - SQUEEZE EVERY LOCAL FRAME"); AddProfile(PerformanceProfile.Balanced, "BALANCED - LONG SESSION DEFAULT"); AddProfile(PerformanceProfile.Competitive, "COMPETITIVE - FRAME PACING FIRST"); AddProfile(PerformanceProfile.Potato, "POTATO - MAXIMUM LOCAL RELIEF"); AddProfile(PerformanceProfile.Cinematic, "CINEMATIC - VISUAL QUALITY"); _shadows = AddToggle("LOCAL SHADOWS", _plugin.Settings.Shadows.Value, _plugin.Settings.SetShadows); _anisotropic = AddToggle("ANISOTROPIC FILTERING", _plugin.Settings.AnisotropicFiltering.Value, _plugin.Settings.SetAnisotropicFiltering); _fps = AddNumberInput("MAX FPS (-1 = PLATFORM DEFAULT)", delegate(float value) { _plugin.Settings.SetTargetFrameRate((int)value); }); _vSync = AddNumberInput("VSYNC INTERVAL (0-4)", delegate(float value) { _plugin.Settings.SetVSyncCount((int)value); }); _aa = AddNumberInput("MSAA SAMPLES (0/2/4/8)", delegate(float value) { _plugin.Settings.SetAntiAliasing((int)value); }); _particles = AddNumberInput("PARTICLES PER SYSTEM (16-10000)", delegate(float value) { _plugin.Settings.SetParticleLimit((int)value); }); _trail = AddNumberInput("TRAIL LIFETIME MULTIPLIER (0-1.5)", _plugin.Settings.SetTrailLifetimeMultiplier); AddText("VFX MODE", 21, Text, 29f); AddButton("FULL - NO VFX CHANGES", delegate { _plugin.Settings.SetVfxBudget(LocalVfxBudget.Full); }); AddButton("REDUCED - SOFT PARTICLE / TRAIL BUDGET", delegate { _plugin.Settings.SetVfxBudget(LocalVfxBudget.Reduced); }); AddButton("MINIMAL - AGGRESSIVE LOCAL VFX BUDGET", delegate { _plugin.Settings.SetVfxBudget(LocalVfxBudget.Minimal); }); _live = AddText(string.Empty, 18, Muted, 72f); AddText("Reliability is passive and evidence-first. Graphics changes are client-local and restorable.", 16, Muted, 34f); } public void Refresh() { //IL_0306: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_0372: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_enabled == (Object)null || (Object)(object)_overlay == (Object)null || (Object)(object)_shadows == (Object)null || (Object)(object)_anisotropic == (Object)null || (Object)(object)_reliabilityEnabled == (Object)null || (Object)(object)_writeDiagnostics == (Object)null || (Object)(object)_runInBackground == (Object)null || (Object)(object)_commonGuards == (Object)null || (Object)(object)_experimentalRejoin == (Object)null || (Object)(object)_adaptive == (Object)null || (Object)(object)_reliabilityLive == (Object)null || (Object)(object)_compatibility == (Object)null || (Object)(object)_live == (Object)null || (Object)(object)_fps == (Object)null || (Object)(object)_vSync == (Object)null || (Object)(object)_aa == (Object)null || (Object)(object)_particles == (Object)null || (Object)(object)_trail == (Object)null) { return; } _refreshing = true; try { PerformanceSettings settings = _plugin.Settings; SetToggleState(_enabled, settings.Enabled.Value); SetToggleState(_overlay, settings.ShowOverlay.Value); SetToggleState(_reliabilityEnabled, settings.ReliabilityEnabled.Value); SetToggleState(_writeDiagnostics, settings.WriteReliabilityDiagnostics.Value); SetToggleState(_runInBackground, settings.RunInBackgroundForReliability.Value); SetToggleState(_commonGuards, settings.CommonDisconnectGuards.Value); SetToggleState(_experimentalRejoin, settings.ExperimentalRejoinEnabled.Value); SetToggleState(_adaptive, settings.AdaptiveEnabled.Value); SetToggleState(_shadows, settings.Shadows.Value); SetToggleState(_anisotropic, settings.AnisotropicFiltering.Value); _fps.SetTextWithoutNotify(settings.TargetFrameRate.Value.ToString(CultureInfo.InvariantCulture)); _vSync.SetTextWithoutNotify(settings.VSyncCount.Value.ToString(CultureInfo.InvariantCulture)); _aa.SetTextWithoutNotify(settings.AntiAliasing.Value.ToString(CultureInfo.InvariantCulture)); _particles.SetTextWithoutNotify(settings.ParticleLimit.Value.ToString(CultureInfo.InvariantCulture)); _trail.SetTextWithoutNotify(settings.TrailLifetimeMultiplier.Value.ToString("0.##", CultureInfo.InvariantCulture)); foreach (KeyValuePair<PerformanceProfile, Button> profile in _profiles) { ((Graphic)((Component)profile.Value).GetComponentInChildren<TextMeshProUGUI>()).color = ((profile.Key == settings.Profile.Value) ? Accent : Text); } bool performanceImprovementsDetected = _plugin.Compatibility.PerformanceImprovementsDetected; ((TMP_Text)_compatibility).text = _plugin.Compatibility.StatusText + (performanceImprovementsDetected ? "\nVFX AND SHADOW/LIGHT KNOBS ARE DEFERRED TO AVOID DOUBLE CONTROL." : string.Empty); ((Graphic)_compatibility).color = (performanceImprovementsDetected ? Warning : Accent); MetricsSampler metrics = _plugin.Metrics; SessionReliabilitySummary sessionReliabilitySummary = SessionReliabilitySummary.FromLatest(); ReliabilityGuardStatus status = ReliabilityGuard.GetStatus(); ExperimentalRejoinStatus status2 = ExperimentalRejoin.Status; long num = status.EntityLagRoundEndsSuppressed + status.NetworkPhysicsUpdatesSkipped + status.FollowPlayerUpdatesSkipped + status.SetPlayerSpriteLayerStartsSkipped + status.RwfFollowPlayerUpdatesSkipped + status.ModdingUtilsDestroyedPlayersSkipped + status.StaleProjectileHitsDropped + status.GearUpLifeforceTerminalCallsGuarded + status.GearUpArcaneNullHitsDropped; ((TMP_Text)_reliabilityLive).text = sessionReliabilitySummary.CompactLine + "\n" + string.Format("GUARDS: {0} | PATCHED {1} | INTERVENTIONS {2}\n", status.Installed ? "ACTIVE" : "OFF", status.InstalledPatchCount, num) + $"REJOIN: {status2.State} | ATTEMPTS {status2.AttemptsStarted} | SUCCESS {status2.SuccessfulRejoins}\n" + "WARM STATE: " + ExperimentalRejoinController.StatusText + (status2.NeedsWarmStateRestore ? "\nWAITING FOR SAFE POINT-BOUNDARY VALIDATION." : string.Empty); ((TMP_Text)_live).text = "LIVE | RELIABILITY " + (settings.ReliabilityEnabled.Value ? "ON" : "OFF") + " | " + settings.Profile.Value.ToString().ToUpperInvariant() + " | ADAPT " + _plugin.Adaptive.StatusText + " | " + $"FPS {metrics.FramesPerSecond:0} | 1% LOW {metrics.OnePercentLowFps:0} | " + $"{metrics.AverageFrameMilliseconds:0.0} ms\n" + $"MEM {metrics.ManagedMemoryMegabytes:0} MB | GC +{metrics.GcCollectionsSinceLastSnapshot} | " + $"TRACKED VFX {_plugin.Vfx.TrackedParticleSystems}/{_plugin.Vfx.TrackedTrails}"; } finally { _refreshing = false; } } private void AddProfile(PerformanceProfile profile, string label) { Button value = AddButton(label, delegate { _plugin.Settings.ApplyProfile(profile); }); _profiles.Add(profile, value); } private Toggle AddToggle(string label, bool initialValue, Action<bool> action) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) GameObject obj = MenuHandler.CreateToggle(initialValue, label, Root, (UnityAction<bool>)delegate(bool value) { if (!_refreshing) { action(value); } }, 22, false, (Color?)Text, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); Compact(obj, 38f); Toggle component = obj.GetComponent<Toggle>(); TextMeshProUGUI componentInChildren = obj.GetComponentInChildren<TextMeshProUGUI>(); _togglePresentations[component] = new TogglePresentation(label, componentInChildren); StyleToggle(component); SetToggleState(component, initialValue); return component; } private Button AddButton(string label, Action action) { //IL_0022: 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_0044: Expected O, but got Unknown GameObject obj = MenuHandler.CreateButton(label, Root, (UnityAction)delegate { if (!_refreshing) { action(); } }, 20, false, (Color?)Text, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); Compact(obj, 36f); Button component = obj.GetComponent<Button>(); Style((Selectable)(object)component); return component; } private TMP_InputField AddNumberInput(string label, Action<float> action) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) AddText(label, 18, Text, 26f); GameObject obj = MenuHandler.CreateInputField("value", 21, Root, (UnityAction<string>)delegate { }); Compact(obj, 39f); TMP_InputField componentInChildren = obj.GetComponentInChildren<TMP_InputField>(); ((UnityEventBase)componentInChildren.onValueChanged).RemoveAllListeners(); ((UnityEvent<string>)(object)componentInChildren.onEndEdit).AddListener((UnityAction<string>)delegate(string text) { if (!_refreshing) { if (TryParseFinite(text, out var value)) { action(value); } Refresh(); } }); Style((Selectable)(object)componentInChildren); Image component = ((Component)componentInChildren).GetComponent<Image>(); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = Dark; } return componentInChildren; } private TextMeshProUGUI AddText(string value, int size, Color color, float height) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI val = default(TextMeshProUGUI); MenuHandler.CreateText(value, Root, ref val, size, false, (Color?)color, (TMP_FontAsset)null, (Material)null, (TextAlignmentOptions?)null); Compact(((Component)val).gameObject, height); return val; } private static bool TryParseFinite(string text, out float value) { if ((float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value) || float.TryParse(text, NumberStyles.Float, CultureInfo.CurrentCulture, out value)) && !float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static void Compact(GameObject gameObject, float height) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) RectTransform component = gameObject.GetComponent<RectTransform>(); if ((Object)(object)component != (Object)null) { component.sizeDelta = new Vector2(component.sizeDelta.x, height); } LayoutElement component2 = gameObject.GetComponent<LayoutElement>(); if ((Object)(object)component2 != (Object)null) { component2.preferredHeight = height; component2.minHeight = height; } } private static void Style(Selectable selectable) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) ColorBlock colors = selectable.colors; ((ColorBlock)(ref colors)).normalColor = Dark; ((ColorBlock)(ref colors)).highlightedColor = DarkHighlight; ((ColorBlock)(ref colors)).pressedColor = Accent; ((ColorBlock)(ref colors)).disabledColor = new Color(0.04f, 0.04f, 0.055f, 0.45f); ((ColorBlock)(ref colors)).colorMultiplier = 1f; selectable.colors = colors; } private static void StyleToggle(Toggle toggle) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: 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) if ((Object)(object)toggle == (Object)null) { return; } Graphic graphic = toggle.graphic; if ((Object)(object)graphic != (Object)null && (Object)(object)((Selectable)toggle).targetGraphic == (Object)(object)graphic) { ColorBlock colors = ((Selectable)toggle).colors; ((ColorBlock)(ref colors)).normalColor = Accent; ((ColorBlock)(ref colors)).highlightedColor = new Color(0.52f, 1f, 1f, 1f); ((ColorBlock)(ref colors)).pressedColor = Color.white; ((ColorBlock)(ref colors)).disabledColor = new Color(Accent.r, Accent.g, Accent.b, 0.45f); ((ColorBlock)(ref colors)).colorMultiplier = 1f; ((Selectable)toggle).colors = colors; } else { Style((Selectable)(object)toggle); if ((Object)(object)graphic != (Object)null) { graphic.color = Accent; } } } private void SetToggleState(Toggle toggle, bool enabled) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) toggle.isOn = enabled; if (_togglePresentations.TryGetValue(toggle, out TogglePresentation value)) { ((TMP_Text)value.Label).text = (enabled ? "[ON] " : "[OFF] ") + value.BaseLabel; ((Graphic)value.Label).color = (enabled ? Accent : Text); } if ((Object)(object)toggle.graphic != (Object)null && (Object)(object)((Selectable)toggle).targetGraphic != (Object)(object)toggle.graphic) { toggle.graphic.color = Accent; } } } private static readonly Color Text = new Color(0.9f, 0.93f, 0.98f, 1f); private static readonly Color Muted = new Color(0.55f, 0.6f, 0.7f, 1f); private static readonly Color Accent = new Color(0.24f, 0.86f, 0.92f, 1f); private static readonly Color Warning = new Color(1f, 0.67f, 0.3f, 1f); private static readonly Color Dark = new Color(0.045f, 0.055f, 0.085f, 0.98f); private static readonly Color DarkHighlight = new Color(0.08f, 0.18f, 0.22f, 1f); private static readonly List<View> Views = new List<View>(); private static ReksikPerformancePlugin? _plugin; private static bool _registered; private static float _nextRefreshAt; public static void Register(ReksikPerformancePlugin plugin) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown _plugin = plugin; if (!_registered) { Unbound.RegisterMenu("REKSIK PERFORMANCE", new UnityAction(RefreshAll), (Action<GameObject>)Build, (GameObject)null, true); _registered = true; } } public static void Tick() { if (!(Time.unscaledTime < _nextRefreshAt)) { _nextRefreshAt = Time.unscaledTime + 0.5f; RefreshAll(); } } private static void Build(GameObject root) { if (!((Object)(object)_plugin == (Object)null)) { View view = new View(root, _plugin); Views.Add(view); view.Build(); view.Refresh(); } } private static void RefreshAll() { for (int num = Views.Count - 1; num >= 0; num--) { if ((Object)(object)Views[num].Root == (Object)null) { Views.RemoveAt(num); } else { Views[num].Refresh(); } } } } public enum PerformanceProfile { Vanilla, MaxFps, Balanced, Competitive, Potato, Cinematic, Custom } public enum LocalVfxBudget { Full, Reduced, Minimal } internal sealed class PerformanceSettings { private bool _suppressEvents; public ConfigEntry<bool> Enabled { get; } public ConfigEntry<PerformanceProfile> Profile { get; } public ConfigEntry<bool> ReliabilityEnabled { get; } public ConfigEntry<bool> WriteReliabilityDiagnostics { get; } public ConfigEntry<bool> RunInBackgroundForReliability { get; } public ConfigEntry<bool> CommonDisconnectGuards { get; } public ConfigEntry<bool> ExperimentalRejoinEnabled { get; } public ConfigEntry<int> RejoinPlayerTtlMilliseconds { get; } public ConfigEntry<int> RejoinEmptyRoomTtlMilliseconds { get; } public ConfigEntry<float> RejoinTimeoutSeconds { get; } public ConfigEntry<float> RejoinRetryDelaySeconds { get; } public ConfigEntry<bool> ShowOverlay { get; } public ConfigEntry<int> TargetFrameRate { get; } public ConfigEntry<int> VSyncCount { get; } public ConfigEntry<bool> AdaptiveEnabled { get; } public ConfigEntry<float> AdaptivePressureSeconds { get; } public ConfigEntry<float> AdaptiveRecoverySeconds { get; } public ConfigEntry<int> AntiAliasing { get; } public ConfigEntry<bool> Shadows { get; } public ConfigEntry<float> ShadowDistance { get; } public ConfigEntry<int> PixelLightCount { get; } public ConfigEntry<bool> AnisotropicFiltering { get; } public ConfigEntry<int> TextureLimit { get; } public ConfigEntry<float> LodBias { get; } public ConfigEntry<LocalVfxBudget> VfxBudget { get; } public ConfigEntry<int> ParticleLimit { get; } public ConfigEntry<float> TrailLifetimeMultiplier { get; } public ConfigEntry<float> VfxRescanSeconds { get; } public event Action? Changed; public PerformanceSettings(ConfigFile config) { Enabled = config.Bind<bool>("General", "Enabled", true, "Apply the selected local performance profile."); Profile = config.Bind<PerformanceProfile>("General", "Profile", PerformanceProfile.Balanced, "Vanilla restores the values captured at startup. Custom uses the fields below."); ReliabilityEnabled = config.Bind<bool>("Session Reliability", "Enabled", true, "Run the passive session recorder and reliability diagnostics independently of the FPS profile."); WriteReliabilityDiagnostics = config.Bind<bool>("Session Reliability", "WriteDiagnosticsToDisk", true, "Write bounded, privacy-safe disconnect diagnostics under BepInEx/config/ReksikPerformance."); RunInBackgroundForReliability = config.Bind<bool>("Session Reliability", "RunInBackground", true, "Keep the local Unity client active while ROUNDS is unfocused to reduce Alt-Tab stalls."); CommonDisconnectGuards = config.Bind<bool>("Session Reliability", "CommonDisconnectGuards", true, "Install narrow guards for confirmed disconnect-adjacent error storms. Restart required."); ExperimentalRejoinEnabled = config.Bind<bool>("Experimental Rejoin", "Enabled", false, "Opt-in non-host Photon transport rejoin experiment. Restart required; disabled by default."); RejoinPlayerTtlMilliseconds = config.Bind<int>("Experimental Rejoin", "PlayerTtlMilliseconds", 60000, "Inactive actor retention. Host and client must opt in before room creation."); RejoinEmptyRoomTtlMilliseconds = config.Bind<int>("Experimental Rejoin", "EmptyRoomTtlMilliseconds", 30000, "Empty Photon room retention for the experiment."); RejoinTimeoutSeconds = config.Bind<float>("Experimental Rejoin", "ReconnectTimeoutSeconds", 12f, "Bounded timeout per transport rejoin attempt."); RejoinRetryDelaySeconds = config.Bind<float>("Experimental Rejoin", "RetryDelaySeconds", 1f, "Delay before the one bounded retry."); ShowOverlay = config.Bind<bool>("Diagnostics", "ShowOverlay", false, "Show a compact local FPS, frame-time and memory overlay."); TargetFrameRate = config.Bind<int>("Frame Pacing", "TargetFrameRate", 120, "Custom target FPS. -1 uses Unity's platform default."); VSyncCount = config.Bind<int>("Frame Pacing", "VSyncCount", 0, "Custom VSync interval from 0 to 4."); AdaptiveEnabled = config.Bind<bool>("Adaptive", "Enabled", true, "Gradually reduce local VFX after sustained frame pressure."); AdaptivePressureSeconds = config.Bind<float>("Adaptive", "PressureSeconds", 4f, "Seconds below target before increasing the adaptive tier."); AdaptiveRecoverySeconds = config.Bind<float>("Adaptive", "RecoverySeconds", 10f, "Seconds of stable headroom before recovering one tier."); AntiAliasing = config.Bind<int>("Graphics", "AntiAliasing", 2, "Custom MSAA samples: 0, 2, 4, or 8."); Shadows = config.Bind<bool>("Graphics", "Shadows", true, "Enable local real-time shadows."); ShadowDistance = config.Bind<float>("Graphics", "ShadowDistance", 35f, "Local shadow distance."); PixelLightCount = config.Bind<int>("Graphics", "PixelLightCount", 2, "Maximum local pixel lights."); AnisotropicFiltering = config.Bind<bool>("Graphics", "AnisotropicFiltering", true, "Force anisotropic texture filtering."); TextureLimit = config.Bind<int>("Graphics", "TextureLimit", 0, "Texture downscale level from 0 to 3."); LodBias = config.Bind<float>("Graphics", "LodBias", 1f, "Local level-of-detail bias."); VfxBudget = config.Bind<LocalVfxBudget>("VFX", "Budget", LocalVfxBudget.Reduced, "Local-only particle and trail budget."); ParticleLimit = config.Bind<int>("VFX", "ParticleLimitPerSystem", 512, "Maximum particles per active particle system."); TrailLifetimeMultiplier = config.Bind<float>("VFX", "TrailLifetimeMultiplier", 0.75f, "Local trail lifetime multiplier."); VfxRescanSeconds = config.Bind<float>("VFX", "RescanSeconds", 12f, "Seconds between VFX discovery passes. Adaptive tier changes reuse tracked objects without rescanning."); Subscribe<bool>(Enabled); Subscribe<PerformanceProfile>(Profile); Subscribe<bool>(ReliabilityEnabled); Subscribe<bool>(WriteReliabilityDiagnostics); Subscribe<bool>(RunInBackgroundForReliability); Subscribe<bool>(CommonDisconnectGuards); Subscribe<bool>(ExperimentalRejoinEnabled); Subscribe<int>(RejoinPlayerTtlMilliseconds); Subscribe<int>(RejoinEmptyRoomTtlMilliseconds); Subscribe<float>(RejoinTimeoutSeconds); Subscribe<float>(RejoinRetryDelaySeconds); Subscribe<bool>(ShowOverlay); Subscribe<int>(TargetFrameRate); Subscribe<int>(VSyncCount); Subscribe<bool>(AdaptiveEnabled); Subscribe<float>(AdaptivePressureSeconds); Subscribe<float>(AdaptiveRecoverySeconds); Subscribe<int>(AntiAliasing); Subscribe<bool>(Shadows); Subscribe<float>(ShadowDistance); Subscribe<int>(PixelLightCount); Subscribe<bool>(AnisotropicFiltering); Subscribe<int>(TextureLimit); Subscribe<float>(LodBias); Subscribe<LocalVfxBudget>(VfxBudget); Subscribe<int>(ParticleLimit); Subscribe<float>(TrailLifetimeMultiplier); Subscribe<float>(VfxRescanSeconds); } public void SetEnabled(bool value) { Enabled.Value = value; } public void SetOverlay(bool value) { ShowOverlay.Value = value; } public void SetReliabilityEnabled(bool value) { ReliabilityEnabled.Value = value; } public void SetWriteReliabilityDiagnostics(bool value) { WriteReliabilityDiagnostics.Value = value; } public void SetRunInBackgroundForReliability(bool value) { RunInBackgroundForReliability.Value = value; } public void SetCommonDisconnectGuards(bool value) { CommonDisconnectGuards.Value = value; } public void SetExperimentalRejoinEnabled(bool value) { ExperimentalRejoinEnabled.Value = value; } public void SetAdaptiveEnabled(bool value) { AdaptiveEnabled.Value = value; } public void SetShadows(bool value) { SetCustom(Shadows, value); } public void SetAnisotropicFiltering(bool value) { SetCustom(AnisotropicFiltering, value); } public void SetTargetFrameRate(int value) { SetCustom(TargetFrameRate, (value < 0) ? (-1) : Clamp(value, 30, 1000)); } public void SetVSyncCount(int value) { SetCustom(VSyncCount, Clamp(value, 0, 4)); } public void SetAntiAliasing(int value) { int value2 = ((value >= 8) ? 8 : ((value >= 4) ? 4 : ((value >= 2) ? 2 : 0))); SetCustom(AntiAliasing, value2); } public void SetParticleLimit(int value) { SetCustom(ParticleLimit, Clamp(value, 16, 10000)); } public void SetTrailLifetimeMultiplier(float value) { SetCustom(TrailLifetimeMultiplier, Clamp(value, 0f, 1.5f)); } public void SetVfxBudget(LocalVfxBudget value) { SetCustom(VfxBudget, value); } public void ApplyProfile(PerformanceProfile profile) { _suppressEvents = true; try { Profile.Value = profile; switch (profile) { case PerformanceProfile.MaxFps: SetProfileValues(-1, 0, 0, shadows: false, 0f, 0, anisotropic: false, 0, 0.65f, LocalVfxBudget.Minimal, 128, 0.35f); break; case PerformanceProfile.Balanced: SetProfileValues(120, 0, 2, shadows: true, 35f, 2, anisotropic: true, 0, 1f, LocalVfxBudget.Reduced, 512, 0.75f); break; case PerformanceProfile.Competitive: SetProfileValues(240, 0, 0, shadows: false, 10f, 0, anisotropic: false, 0, 0.75f, LocalVfxBudget.Minimal, 192, 0.45f); break; case PerformanceProfile.Potato: SetProfileValues(90, 0, 0, shadows: false, 0f, 0, anisotropic: false, 1, 0.5f, LocalVfxBudget.Minimal, 96, 0.25f); break; case PerformanceProfile.Cinematic: SetProfileValues(120, 0, 8, shadows: true, 100f, 8, anisotropic: true, 0, 2f, LocalVfxBudget.Full, 10000, 1f); break; } } finally { _suppressEvents = false; } this.Changed?.Invoke(); } private void SetProfileValues(int targetFps, int vSync, int antiAliasing, bool shadows, float shadowDistance, int pixelLights, bool anisotropic, int textureLimit, float lodBias, LocalVfxBudget vfxBudget, int particleLimit, float trailMultiplier) { TargetFrameRate.Value = targetFps; VSyncCount.Value = vSync; AntiAliasing.Value = antiAliasing; Shadows.Value = shadows; ShadowDistance.Value = shadowDistance; PixelLightCount.Value = pixelLights; AnisotropicFiltering.Value = anisotropic; TextureLimit.Value = textureLimit; LodBias.Value = lodBias; VfxBudget.Value = vfxBudget; ParticleLimit.Value = particleLimit; TrailLifetimeMultiplier.Value = trailMultiplier; } private void SetCustom<T>(ConfigEntry<T> entry, T value) { _suppressEvents = true; try { Profile.Value = PerformanceProfile.Custom; entry.Value = value; } finally { _suppressEvents = false; } this.Changed?.Invoke(); } private void Subscribe<T>(ConfigEntry<T> entry) { entry.SettingChanged += delegate { if (!_suppressEvents) { this.Changed?.Invoke(); } }; } private static int Clamp(int value, int min, int max) { if (value >= min) { if (value <= max) { return value; } return max; } return min; } private static float Clamp(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (!(value < min)) { if (!(value > max)) { return value; } return max; } return min; } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.reksikperformance.rounds", "ReksikPerformance", "0.3.1")] [BepInProcess("Rounds.exe")] public sealed class ReksikPerformancePlugin : BaseUnityPlugin { public const string ModId = "com.reksikperformance.rounds"; public const string ModName = "ReksikPerformance"; public const string Version = "0.3.1"; private Harmony? _harmony; private GraphicsController? _graphics; private MetricsOverlay? _overlay; private bool _reliabilityStarted; private bool _lastReliabilityEnabled; private bool _lastWriteDiagnostics; internal PerformanceSettings Settings { get; private set; } internal CompatibilityGuard Compatibility { get; private set; } internal VfxBudgetController Vfx { get; private set; } internal MetricsSampler Metrics { get; private set; } internal AdaptivePerformanceController Adaptive { get; private set; } private void Awake() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown Compatibility = new CompatibilityGuard(); Settings = new PerformanceSettings(((BaseUnityPlugin)this).Config); Metrics = new MetricsSampler(); Adaptive = new AdaptivePerformanceController(); _overlay = new MetricsOverlay(); _graphics = new GraphicsController(((BaseUnityPlugin)this).Logger); Vfx = new VfxBudgetController(((BaseUnityPlugin)this).Logger); Settings.Changed += ApplySettings; ApplySettings(); _harmony = new Harmony("com.reksikperformance.rounds"); if (Settings.CommonDisconnectGuards.Value) { ReliabilityGuard.Install(_harmony, ((BaseUnityPlugin)this).Logger); } ExperimentalRejoin.Install(_harmony, ((BaseUnityPlugin)this).Logger, new ExperimentalRejoinOptions { Enabled = Settings.ExperimentalRejoinEnabled.Value, PlayerTtlMilliseconds = Settings.RejoinPlayerTtlMilliseconds.Value, EmptyRoomTtlMilliseconds = Settings.RejoinEmptyRoomTtlMilliseconds.Value, ReconnectTimeoutSeconds = Settings.RejoinTimeoutSeconds.Value, RetryDelaySeconds = Settings.RejoinRetryDelaySeconds.Value }); ExperimentalRejoinController.Install(((BaseUnityPlugin)this).Logger); SceneManager.sceneLoaded += OnSceneLoaded; ((BaseUnityPlugin)this).Logger.LogInfo((object)("ReksikPerformance 0.3.1 loaded. " + Compatibility.StatusText)); } private void Start() { PerformanceMenu.Register(this); } private void Update() { Vfx.Tick(); Metrics.Tick(Settings.Profile.Value, Vfx.TrackedParticleSystems, Vfx.TrackedTrails, Compatibility.StatusText, Adaptive.StatusText); Adaptive.Tick(Settings, Metrics, Vfx, Compatibility); PerformanceMenu.Tick(); } private void OnGUI() { if (Settings.ShowOverlay.Value && _overlay != null) { _overlay.Draw(Metrics.OverlayText); } } private void OnDestroy() { Settings.Changed -= ApplySettings; Vfx.RestoreAll(); _graphics?.Restore(); _overlay?.Dispose(); SessionReliabilityApi.Stop(); ExperimentalRejoinController.Shutdown(); SceneManager.sceneLoaded -= OnSceneLoaded; } private void ApplySettings() { _graphics?.Apply(Settings, Compatibility); Vfx.Apply(Settings, Compatibility); ApplyReliabilitySettings(); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { Vfx.NotifySceneChanged(); } private void ApplyReliabilitySettings() { bool value = Settings.ReliabilityEnabled.Value; bool value2 = Settings.WriteReliabilityDiagnostics.Value; if (!_reliabilityStarted || value != _lastReliabilityEnabled || value2 != _lastWriteDiagnostics) { SessionReliabilityApi.Start((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, new SessionReliabilityOptions { Enabled = value, WriteDiagnosticsToDisk = value2 }); _reliabilityStarted = true; _lastReliabilityEnabled = value; _lastWriteDiagnostics = value2; } } } internal sealed class VfxBudgetController { private readonly struct ParticleState { public ParticleSystem System { get; } public int MaxParticles { get; } public float RateOverTimeMultiplier { get; } public float RateOverDistanceMultiplier { get; } public ParticleState(ParticleSystem system, int maxParticles, float rateOverTimeMultiplier, float rateOverDistanceMultiplier) { System = system; MaxParticles = maxParticles; RateOverTimeMultiplier = rateOverTimeMultiplier; RateOverDistanceMultiplier = rateOverDistanceMultiplier; } } private readonly struct TrailState { public TrailRenderer Renderer { get; } public float Lifetime { get; } public TrailState(TrailRenderer renderer, float lifetime) { Renderer = renderer; Lifetime = lifetime; } } private readonly struct LightState { public Light Component { get; } public LightRenderMode RenderMode { get; } public LightShadows Shadows { get; } public LightState(Light component, LightRenderMode renderMode, LightShadows shadows) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) Component = component; RenderMode = renderMode; Shadows = shadows; } } private const int ReducedLightLimit = 24; private const int MinimalLightLimit = 12; private readonly Dictionary<int, ParticleState> _particles = new Dictionary<int, ParticleState>(128); private readonly Dictionary<int, TrailState> _trails = new Dictionary<int, TrailState>(128); private readonly Dictionary<int, LightState> _lights = new Dictionary<int, LightState>(64); private readonly List<int> _deadParticleIds = new List<int>(32); private readonly List<int> _deadTrailIds = new List<int>(32); private readonly List<int> _deadLightIds = new List<int>(16); private readonly ManualLogSource _logger; private bool _active; private bool _staticBudgetEnabled; private bool _adaptiveEnabled; private int _baseParticleLimit; private float _baseTrailMultiplier; private int _baseLightLimit; private int _particleLimit; private float _particleRetention = 1f; private float _emissionRetention = 1f; private float _trailMultiplier = 1f; private int _lightLimit = int.MaxValue; private int _adaptiveTier; private float _scanInterval; private float _nextScanAt; public int TrackedParticleSystems => _particles.Count; public int TrackedTrails => _trails.Count; public int TrackedLights => _lights.Count; public int AdaptiveTier => _adaptiveTier; private bool IsBudgetingNow { get { if (!_staticBudgetEnabled) { if (_adaptiveEnabled) { return _adaptiveTier > 0; } return false; } return true; } } public VfxBudgetController(ManualLogSource logger) { _logger = logger; } public void Apply(PerformanceSettings settings, CompatibilityGuard compatibility) { RestoreAll(); bool flag = settings.Enabled.Value && settings.Profile.Value != PerformanceProfile.Vanilla && !compatibility.PerformanceImprovementsDetected; _staticBudgetEnabled = settings.VfxBudget.Value != LocalVfxBudget.Full; _adaptiveEnabled = settings.AdaptiveEnabled.Value; _active = flag && (_staticBudgetEnabled || _adaptiveEnabled); if (!_adaptiveEnabled || !flag) { _adaptiveTier = 0; } _baseParticleLimit = (_staticBudgetEnabled ? Clamp(settings.ParticleLimit.Value, 16, 10000) : int.MaxValue); _baseTrailMultiplier = (_staticBudgetEnabled ? Clamp(settings.TrailLifetimeMultiplier.Value, 0f, 1.5f) : 1f); _baseLightLimit = ResolveBaseLightLimit(settings.VfxBudget.Value); _scanInterval = Clamp(settings.VfxRescanSeconds.Value, 10f, 30f); _nextScanAt = 0f; UpdateEffectiveLimits(); if (_active && IsBudgetingNow) { ScanAndApply(); } else if (compatibility.PerformanceImprovementsDetected) { _logger.LogInfo((object)"VFX budgeting deferred to Performance Improvements compatibility mode."); } } public void Tick() { if (_active && IsBudgetingNow && !(Time.unscaledTime < _nextScanAt)) { ScanAndApply(); } } public void SetAdaptiveTier(int tier) { int num = Clamp(tier, 0, 3); if (_adaptiveTier == num) { return; } _adaptiveTier = num; UpdateEffectiveLimits(); if (_active) { ApplyToTrackedObjects(); if (IsBudgetingNow) { _nextScanAt = 0f; } else { ClearTracking(); } } } public void NotifySceneChanged() { _nextScanAt = 0f; } private void UpdateEffectiveLimits() { switch (_adaptiveTier) { case 1: _particleRetention = 0.8f; _emissionRetention = 0.9f; _trailMultiplier = _baseTrailMultiplier * 0.85f; _lightLimit = Min(_baseLightLimit, 20); break; case 2: _particleRetention = 0.6f; _emissionRetention = 0.72f; _trailMultiplier = _baseTrailMultiplier * 0.65f; _lightLimit = Min(_baseLightLimit, 10); break; case 3: _particleRetention = 0.4f; _emissionRetention = 0.55f; _trailMultiplier = _baseTrailMultiplier * 0.45f; _lightLimit = Min(_baseLightLimit, 5); break; default: _particleRetention = 1f; _emissionRetention = 1f; _trailMultiplier = _baseTrailMultiplier; _lightLimit = _baseLightLimit; break; } _particleLimit = _baseParticleLimit; } private void ApplyToTrackedObjects() { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Invalid comparison between Unknown and I4 //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Invalid comparison between Unknown and I4 //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) foreach (ParticleState value in _particles.Values) { ApplyParticleState(value); } foreach (TrailState value2 in _trails.Values) { if (!((Object)(object)value2.Renderer == (Object)null)) { float num = value2.Lifetime * _trailMultiplier; if (Mathf.Abs(value2.Renderer.time - num) > 0.0001f) { value2.Renderer.time = num; } } } int num2 = 0; foreach (LightState value3 in _lights.Values) { Light component = value3.Component; if ((Object)(object)component == (Object)null) { continue; } bool flag = (int)value3.RenderMode != 2 || (int)value3.Shadows > 0; if (!((Behaviour)component).enabled || !flag || num2 < _lightLimit) { if (((Behaviour)component).enabled && flag) { num2++; } component.renderMode = value3.RenderMode; component.shadows = value3.Shadows; } else { component.renderMode = (LightRenderMode)2; component.shadows = (LightShadows)0; } } } private void ApplyParticleState(ParticleState state) { //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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)state.System == (Object)null)) { int num = Mathf.CeilToInt((float)state.MaxParticles * _particleRetention); if (state.MaxParticles > 0 && num < 1) { num = 1; } int left = Min(state.MaxParticles, _particleLimit); left = Min(left, num); MainModule main = state.System.main; if (((MainModule)(ref main)).maxParticles != left) { ((MainModule)(ref main)).maxParticles = left; } EmissionModule emission = state.System.emission; float num2 = state.RateOverTimeMultiplier * _emissionRetention; float num3 = state.RateOverDistanceMultiplier * _emissionRetention; if (Mathf.Abs(((EmissionModule)(ref emission)).rateOverTimeMultiplier - num2) > 0.0001f) { ((EmissionModule)(ref emission)).rateOverTimeMultiplier = num2; } if (Mathf.Abs(((EmissionModule)(ref emission)).rateOverDistanceMultiplier - num3) > 0.0001f) { ((EmissionModule)(ref emission)).rateOverDistanceMultiplier = num3; } } } public void RestoreAll() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) foreach (ParticleState value in _particles.Values) { if (!((Object)(object)value.System == (Object)null)) { MainModule main = value.System.main; ((MainModule)(ref main)).maxParticles = value.MaxParticles; EmissionModule emission = value.System.emission; ((EmissionModule)(ref emission)).rateOverTimeMultiplier = value.RateOverTimeMultiplier; ((EmissionModule)(ref emission)).rateOverDistanceMultiplier = value.RateOverDistanceMultiplier; } } foreach (TrailState value2 in _trails.Values) { if ((Object)(object)value2.Renderer != (Object)null) { value2.Renderer.time = value2.Lifetime; } } foreach (LightState value3 in _lights.Values) { if ((Object)(object)value3.Component != (Object)null) { value3.Component.renderMode = value3.RenderMode; value3.Component.shadows = value3.Shadows; } } ClearTracking(); _active = false; } private void ScanAndApply() { //IL_0035: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Invalid comparison between Unknown and I4 //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) _nextScanAt = Time.unscaledTime + _scanInterval; PurgeDestroyed(); ParticleSystem[] array = Resources.FindObjectsOfTypeAll<ParticleSystem>(); Scene scene; foreach (ParticleSystem val in array) { if ((Object)(object)val == (Object)null) { continue; } scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { int instanceID = ((Object)val).GetInstanceID(); if (!_particles.TryGetValue(instanceID, out var value)) { MainModule main = val.main; EmissionModule emission = val.emission; value = new ParticleState(val, ((MainModule)(ref main)).maxParticles, ((EmissionModule)(ref emission)).rateOverTimeMultiplier, ((EmissionModule)(ref emission)).rateOverDistanceMultiplier); _particles.Add(instanceID, value); } } } TrailRenderer[] array2 = Resources.FindObjectsOfTypeAll<TrailRenderer>(); foreach (TrailRenderer val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } scene = ((Component)val2).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { int instanceID2 = ((Object)val2).GetInstanceID(); if (!_trails.TryGetValue(instanceID2, out var value2)) { value2 = new TrailState(val2, val2.time); _trails.Add(instanceID2, value2); } } } Light[] array3 = Resources.FindObjectsOfTypeAll<Light>(); foreach (Light val3 in array3) { if ((Object)(object)val3 == (Object)null || (int)val3.type == 1) { continue; } scene = ((Component)val3).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { int instanceID3 = ((Object)val3).GetInstanceID(); if (!_lights.TryGetValue(instanceID3, out var value3)) { value3 = new LightState(val3, val3.renderMode, val3.shadows); _lights.Add(instanceID3, value3); } } } ApplyToTrackedObjects(); } private void PurgeDestroyed() { _deadParticleIds.Clear(); foreach (KeyValuePair<int, ParticleState> particle in _particles) { if ((Object)(object)particle.Value.System == (Object)null) { _deadParticleIds.Add(particle.Key); } } foreach (int deadParticleId in _deadParticleIds) { _particles.Remove(deadParticleId); } _deadTrailIds.Clear(); foreach (KeyValuePair<int, TrailState> trail in _trails) { if ((Object)(object)trail.Value.Renderer == (Object)null) { _deadTrailIds.Add(trail.Key); } } foreach (int deadTrailId in _deadTrailIds) { _trails.Remove(deadTrailId); } _deadLightIds.Clear(); foreach (KeyValuePair<int, LightState> light in _lights) { if ((Object)(object)light.Value.Component == (Object)null) { _deadLightIds.Add(light.Key); } } foreach (int deadLightId in _deadLightIds) { _lights.Remove(deadLightId); } } private void ClearTracking() { _particles.Clear(); _trails.Clear(); _lights.Clear(); _deadParticleIds.Clear(); _deadTrailIds.Clear(); _deadLightIds.Clear(); } private static int ResolveBaseLightLimit(LocalVfxBudget budget) { return budget switch { LocalVfxBudget.Reduced => 24, LocalVfxBudget.Minimal => 12, _ => int.MaxValue, }; } private static int Min(int left, int right) { if (left >= right) { return right; } return left; } private static int Clamp(int value, int min, int max) { if (value >= min) { if (value <= max) { return value; } return max; } return min; } private static float Clamp(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (!(value < min)) { if (!(value > max)) { return value; } return max; } return min; } } } namespace ReksikPerformance.Rejoin { public static class ExperimentalRejoin { private const string HarmonyIdRwf = "io.olavim.rounds.rwf"; private const string HarmonyIdUnbound = "com.willis.rounds.unbound"; private static readonly object Gate = new object(); private static readonly HashSet<string> RecoverableCauses = new HashSet<string>(StringComparer.Ordinal) { "Exception", "ServerTimeout", "ClientTimeout" }; private static ManualLogSource? logger; private static ExperimentalRejoinOptions options = new ExperimentalRejoinOptions(); private static ExperimentalRejoinBehaviour? behaviour; private static Type? photonNetworkType; private static Type? photonPlayerType; private static Type? roomOptionsType; private static PropertyInfo? photonInRoom; private static PropertyInfo? photonIsConnected; private static PropertyInfo? photonIsConnectedAndReady; private static PropertyInfo? photonIsMasterClient; private static MethodInfo? reconnectAndRejoin; private static MethodInfo? photonDisconnect; private static MethodInfo? networkRestart; private static MemberInfo? playerTtlMember; private static MemberInfo? emptyRoomTtlMember; private static PropertyInfo? playerIsInactive; private static bool installed; private static bool criticalHooksReady; private static ExperimentalRejoinState state = ExperimentalRejoinState.Disabled; private static string detail = "Disabled by default."; private static bool needsWarmStateRestore; private static int roomsPrepared; private static int attemptsStarted; private static int successfulRejoins; private static int failedRejoins; private static int suppressedInactiveLeaveCallbacks; private static int rejectedDisconnects; public static bool IsInstalled => installed; public static bool IsEnabled { get { if (installed && criticalHooksReady) { return options.Enabled; } return false; } } public static ExperimentalRejoinState State => state; public static bool NeedsWarmStateRestore => needsWarmStateRestore; public static string StatusText => state.ToString() + ": " + detail; public static ExperimentalRejoinStatus Status { get { lock (Gate) { return new ExperimentalRejoinStatus(state, detail, installed, IsEnabled, needsWarmStateRestore, roomsPrepared, attemptsStarted, successfulRejoins, failedRejoins, suppressedInactiveLeaveCallbacks, rejectedDisconnects); } } } internal static bool PhotonInRoom => ReadPhotonBool(photonInRoom); internal static bool PhotonIsConnected => ReadPhotonBool(photonIsConnected); internal static bool PhotonIsConnectedAndReady => ReadPhotonBool(photonIsConnectedAndReady); internal static bool PhotonIsMaster => ReadPhotonBool(photonIsMasterClient); internal static ExperimentalRejoinOptions Options => options; public static void Install(Harmony harmony, ManualLogSource suppliedLogger, ExperimentalRejoinOptions suppliedOptions) { //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown if (harmony == null) { throw new ArgumentNullException("harmony"); } if (suppliedLogger == null) { throw new ArgumentNullException("suppliedLogger"); } if (suppliedOptions == null) { throw new ArgumentNullException("suppliedOptions"); } lock (Gate) { if (installed) { return; } logger = suppliedLogger; options = suppliedOptions; options.Normalize(); SetState(ExperimentalRejoinState.Installing, "Resolving exact Photon and ROUNDS hooks."); if (!ResolveCriticalSymbols()) { installed = true; criticalHooksReady = false; SetState(ExperimentalRejoinState.FailedClosed, "Exact Photon/ROUNDS symbols were unavailable; no disconnect flow was intercepted."); logger.LogWarning((object)"Experimental Rejoin failed closed: required runtime symbols were unavailable."); return; } bool flag = PatchRoomCreation(harmony); bool flag2 = PatchExact(harmony, FindExactMethod(FindType("NetworkConnectionHandler"), "OnDisconnected", false, "Photon.Realtime.DisconnectCause"), "RecoverableDisconnectPrefix", 800, critical: true); bool flag3 = PatchExact(harmony, FindExactMethod(FindType("NetworkConnectionHandler"), "OnJoinRoomFailed", false, "System.Int16", "System.String"), "JoinRoomFailedPrefix", 800, critical: true); bool flag4 = PatchInactivePlayerLeaveCallbacks(harmony); criticalHooksReady = flag && flag2 && flag3 && flag4; installed = true; if (!criticalHooksReady) { SetState(ExperimentalRejoinState.FailedClosed, "One or more critical hooks were missing; automatic rejoin remains inactive."); logger.LogWarning((object)"Experimental Rejoin failed closed because its complete critical hook set was not available."); return; } GameObject val = new GameObject("ReksikPerformance.ExperimentalRejoin"); Object.DontDestroyOnLoad((Object)val); behaviour = val.AddComponent<ExperimentalRejoinBehaviour>(); behaviour.Initialize(); if (options.Enabled) { SetState(ExperimentalRejoinState.Armed, "Opt-in active for non-host transport loss; warm state is recovered only at the next point."); logger.LogWarning((object)"EXPERIMENTAL Rejoin enabled. It does not provide seamless host recovery or mid-point deck restoration."); } else { SetState(ExperimentalRejoinState.Disabled, "Installed but disabled by default. Vanilla disconnect behavior is unchanged."); logger.LogInfo((object)"Experimental Rejoin installed in disabled state."); } } } public static void AcknowledgeWarmStateRestoredAtPointBoundary() { lock (Gate) { needsWarmStateRestore = false; if (IsEnabled && state == ExperimentalRejoinState.RejoinedWarmStatePending) { SetState(ExperimentalRejoinState.Armed, "Warm state restore acknowledged at a point boundary."); } } } internal static void RejectWarmStateAtPointBoundary(string reason) { lock (Gate) { needsWarmStateRestore = false; SetState(IsEnabled ? ExperimentalRejoinState.Armed : ExperimentalRejoinState.Disabled, "Warm-state validation rejected at a safe point boundary. Future receipts may arm again. " + reason); } } internal static bool ReadPhotonBool(PropertyInfo? property, bool fallback = false) { if (property == null) { return fallback; } try { return (property.GetValue(null, null) is bool flag) ? flag : fallback; } catch { return fallback; } } internal static bool InvokeReconnectAndRejoin() { if (reconnectAndRejoin == null) { return false; } try { object obj = reconnectAndRejoin.Invoke(null, null); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception exception) { ManualLogSource? obj2 = logger; if (obj2 != null) { obj2.LogWarning((object)("ReconnectAndRejoin invocation failed: " + Unwrap(exception).Message)); } return false; } } internal static void DisconnectForRetry() { if (photonDisconnect == null) { return; } try { photonDisconnect.Invoke(null, null); } catch (Exception exception) { ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)("Could not reset Photon before the bounded retry: " + Unwrap(exception).Message)); } } } internal static void MarkAttemptStarted(int number) { lock (Gate) { attemptsStarted++; SetState((number == 1) ? ExperimentalRejoinState.Reconnecting : ExperimentalRejoinState.Retrying, "Reconnect attempt " + number + " of 2."); } } internal static void MarkWaitingForRoom(int number) { lock (Gate) { SetState(ExperimentalRejoinState.WaitingForRoom, "Photon accepted attempt " + number + "; waiting for the prior room."); } } internal static void MarkSuccess() { lock (Gate) { successfulRejoins++; needsWarmStateRestore = true; SetState(ExperimentalRejoinState.RejoinedWarmStatePending, "Transport rejoined. Mid-point state is unsafe; recover cards/player state at the next point."); } ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)"Photon rejoin succeeded, but warm gameplay state is pending. Continue safely from the next point only."); } ExperimentalRejoinController.NotifyTransportRejoined(); } internal static void MarkFailureAndFallback(object handler, string reason) { lock (Gate) { failedRejoins++; SetState(ExperimentalRejoinState.FallingBackToVanilla, reason); } ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)"Experimental rejoin exhausted its bounded retry. Falling back to vanilla restart."); } ExperimentalRejoinController.NotifyAttemptAborted(reason); try { networkRestart?.Invoke(handler, null); } catch (Exception exception) { SetState(ExperimentalRejoinState.FailedClosed, "Vanilla NetworkRestart invocation failed after rejoin exhaustion."); ManualLogSource? obj2 = logger; if (obj2 != null) { obj2.LogError((object)("Vanilla NetworkRestart fallback failed: " + Unwrap(exception))); } } } internal static void AbortForFatalCause(string cause) { lock (Gate) { rejectedDisconnects++; SetState(ExperimentalRejoinState.FallingBackToVanilla, "Reconnect aborted for non-recoverable cause: " + cause + "."); } behaviour?.AbortCurrentAttempt(); ExperimentalRejoinController.NotifyAttemptAborted("Reconnect aborted for non-recoverable cause: " + cause + "."); } private static bool RoomCreationPrefix(object[] __args) { if (!IsEnabled || roomOptionsType == null) { return true; } object obj = __args.FirstOrDefault((object arg) => arg != null && roomOptionsType.IsInstanceOfType(arg)); if (obj == null) { return true; } try { SetIntMember(playerTtlMember, obj, options.PlayerTtlMilliseconds); SetIntMember(emptyRoomTtlMember, obj, options.EmptyRoomTtlMilliseconds); lock (Gate) { roomsPrepared++; } } catch (Exception ex) { ManualLogSource? obj2 = logger; if (obj2 != null) { obj2.LogWarning((object)("Could not apply Photon room TTLs; vanilla room options were kept: " + ex.Message)); } } return true; } private static bool InactivePlayerLeftPrefix(object[] __args) { if (!IsEnabled || photonPlayerType == null || playerIsInactive == null) { return true; } object obj = __args.FirstOrDefault((object arg) => arg != null && photonPlayerType.IsInstanceOfType(arg)); if (obj == null) { return true; } try { object value = playerIsInactive.GetValue(obj, null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0) { return true; } lock (Gate) { suppressedInactiveLeaveCallbacks++; } return false; } catch { return true; } } private static bool RecoverableDisconnectPrefix(object __instance, object[] __args) { if (!IsEnabled || (Object)(object)behaviour == (Object)null) { return true; } string text = ((__args.Length != 0 && __args[0] != null) ? (__args[0].ToString() ?? "Unknown") : "Unknown"); if (!RecoverableCauses.Contains(text)) { if (string.Equals(text, "DisconnectByClientLogic", StringComparison.Ordinal) && behaviour.IsResettingTransportForRetry) { return false; } if (behaviour.IsAttemptActive) { AbortForFatalCause(text); } else { lock (Gate) { rejectedDisconnects++; } } return true; } bool isAttemptActive = behaviour.IsAttemptActive; if (!isAttemptActive && !ExperimentalRejoinController.TryGetWarmStateRejoinAdmission(out string reasonCode, out string reasonDetail)) { lock (Gate) { rejectedDisconnects++; } ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)("Experimental rejoin declined [warm-receipt=" + reasonCode + "]: " + reasonDetail)); } return true; } if (behaviour.TryBegin(__instance, text)) { if (!isAttemptActive) { ExperimentalRejoinController.NotifyRecoverableDisconnectAccepted(text); } return false; } lock (Gate) { rejectedDisconnects++; } return true; } private static bool JoinRoomFailedPrefix() { if (!((Object)(object)behaviour == (Object)null)) { return !behaviour.IsAttemptActive; } return true; } private static bool ResolveCriticalSymbols() { photonNetworkType = FindType("Photon.Pun.PhotonNetwork"); photonPlayerType = FindType("Photon.Realtime.Player"); roomOptionsType = FindType("Photon.Realtime.RoomOptions"); Type type = FindType("NetworkConnectionHandler"); if (photonNetworkType == null || photonPlayerType == null || roomOptionsType == null || type == null) { return false; } photonInRoom = FindStaticBoolProperty(photonNetworkType, "InRoom"); photonIsConnected = FindStaticBoolProperty(photonNetworkType, "IsConnected"); photonIsConnectedAndReady = FindStaticBoolProperty(photonNetworkType, "IsConnectedAndReady"); photonIsMasterClient = FindStaticBoolProperty(photonNetworkType, "IsMasterClient"); reconnectAndRejoin = FindExactMethod(photonNetworkType, "ReconnectAndRejoin", true); photonDisconnect = FindExactMethod(photonNetworkType, "Disconnect", true); networkRestart = FindExactMethod(type, "NetworkRestart", false); playerTtlMember = FindIntMember(roomOptionsType, "PlayerTtl"); emptyRoomTtlMember = FindIntMember(roomOptionsType, "EmptyRoomTtl"); playerIsInactive = photonPlayerType.GetProperty("IsInactive", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (photonInRoom != null && photonIsConnected != null && photonIsConnectedAndReady != null && photonIsMasterClient != null && reconnectAndRejoin != null && reconnectAndRejoin.ReturnType == typeof(bool) && photonDisconnect != null && photonDisconnect.ReturnType == typeof(void) && networkRestart != null && networkRestart.ReturnType == typeof(void) && playerTtlMember != null && emptyRoomTtlMember != null && playerIsInactive != null) { return playerIsInactive.PropertyType == typeof(bool); } return false; } private static bool PatchRoomCreation(Harmony harmony) { if (photonNetworkType == null || roomOptionsType == null) { return false; } bool flag = false; string[] source = new string[2] { "CreateRoom", "JoinOrCreateRoom" }; MethodInfo[] methods = photonNetworkType.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (source.Contains<string>(methodInfo.Name, StringComparer.Ordinal) && !(methodInfo.ReturnType != typeof(bool)) && methodInfo.GetParameters().Any((ParameterInfo parameter) => parameter.ParameterType == roomOptionsType) && PatchExact(harmony, methodInfo, "RoomCreationPrefix", 800, critical: true)) { flag |= methodInfo.Name == "CreateRoom"; } } return flag; } private static bool PatchInactivePlayerLeaveCallbacks(Harmony harmony) { bool flag = false; bool flag2 = false; string[] array = new string[3] { "NetworkConnectionHandler", "UnboundLib.NetworkEventCallbacks", "RWF.PrivateRoomHandler" }; foreach (string text in array) { Type type = FindType(text); if (!(type == null)) { MethodInfo original = FindExactMethod(type, "OnPlayerLeftRoom", false, "Photon.Realtime.Player"); if (PatchExact(harmony, original, "InactivePlayerLeftPrefix", 800, text != "RWF.PrivateRoomHandler")) { flag |= text == "NetworkConnectionHandler"; flag2 |= text == "UnboundLib.NetworkEventCallbacks"; } } } return flag && flag2; } private static bool PatchExact(Harmony harmony, MethodInfo? original, string prefixName, int priority, bool critical) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown if (original == null) { if (critical) { ManualLogSource? obj = logger; if (obj != null) { obj.LogWarning((object)("Experimental Rejoin exact hook was not found for prefix " + prefixName + ".")); } } return false; } MethodInfo method = typeof(ExperimentalRejoin).GetMethod(prefixName, BindingFlags.Static | BindingFlags.NonPublic); if (method == null) { return false; } try { HarmonyMethod val = new HarmonyMethod(method); val.priority = priority; val.before = new string[2] { "io.olavim.rounds.rwf", "com.willis.rounds.unbound" }; HarmonyMethod val2 = val; harmony.Patch((MethodBase)original, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception exception) { ManualLogSource? obj2 = logger; if (obj2 != null) { obj2.LogWarning((object)("Experimental Rejoin did not patch " + original.DeclaringType?.FullName + "." + original.Name + ": " + Unwrap(exception).Message)); } return false; } } private static MethodInfo? FindExactMethod(Type? type, string name, bool isStatic, params string[] parameterTypeNames) { if (type == null) { return null; } BindingFlags bindingAttr = (BindingFlags)(0x30 | (isStatic ? 8 : 4)); foreach (MethodInfo item in from candidate in type.GetMethods(bindingAttr) where candidate.Name == name select candidate) { ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length != parameterTypeNames.Length) { continue; } bool flag = true; for (int num = 0; num < parameters.Length; num++) { if (!string.Equals(parameters[num].ParameterType.FullName, parameterTypeNames[num], StringComparison.Ordinal)) { flag = false; break; } } if (flag) { return item; } } return null; } private static Type? FindType(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName, throwOnError: false, ignoreCase: false); if (type != null) { return type; } } return null; } private static PropertyInfo? FindStaticBoolProperty(Type type, string name) { PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (!(property != null) || !(property.PropertyType == typeof(bool)) || !(property.GetMethod != null)) { return null; } return property; } private static MemberInfo? FindIntMember(Type type, string name) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(int)) { return field; } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(property != null) || !(property.PropertyType == typeof(int)) || !property.CanWrite) { return null; } return property; } private static void SetIntMember(MemberInfo? member, object instance, int value) { if (member is FieldInfo fieldInfo) { fieldInfo.SetValue(instance, value); return; } if (member is PropertyInfo propertyInfo) { propertyInfo.SetValue(instance, value, null); return; } throw new MissingMemberException("Expected writable Int32 room option member."); } private static Exception Unwrap(Exception exception) { if (!(exception is TargetInvocationException { InnerException: not null } ex)) { return exception; } return ex.InnerException; } private static void SetState(ExperimentalRejoinState newState, string newDetail) { state = newState; detail = newDetail; } } internal sealed class ExperimentalRejoinBehaviour : MonoBehaviour { private object? disconnectHandler; private Coroutine? attemptCoroutine; private bool hasRoomContext; private bool lastRoomWasHost; private bool abortRequested; private bool resettingTransportForRetry; internal bool IsAttemptActive => attemptCoroutine != null; internal bool IsResettingTransportForRetry => resettingTransportForRetry; internal void Initialize() { hasRoomContext = false; lastRoomWasHost = false; abortRequested = false; resettingTransportForRetry = false; } internal bool TryBegin(object handler, string cause) { if (attemptCoroutine != null) { return true; } if (!hasRoomContext || lastRoomWasHost) { return false; } disconnectHandler = handler; abortRequested = false; attemptCoroutine = ((MonoBehaviour)this).StartCoroutine(RejoinWithOneRetry(cause)); return true; } internal void AbortCurrentAttempt() { abortRequested = true; } private void Update() { if (ExperimentalRejoin.PhotonInRoom) { hasRoomContext = true; lastRoomWasHost = ExperimentalRejoin.PhotonIsMaster; } else if (ExperimentalRejoin.PhotonIsConnectedAndReady && attemptCoroutine == null) { hasRoomContext = false; lastRoomWasHost = false; } } private IEnumerator RejoinWithOneRetry(string cause) { for (int attempt = 1; attempt <= 2; attempt++) { if (abortRequested) { FinishWithoutFallback(); yield break; } if (attempt > 1) { ExperimentalRejoin.MarkAttemptStarted(attempt); if (ExperimentalRejoin.PhotonIsConnected) { resettingTransportForRetry = true; ExperimentalRejoin.DisconnectForRetry(); float disconnectDeadline = Time.realtimeSinceStartup + Mathf.Min(3f, ExperimentalRejoin.Options.ReconnectTimeoutSeconds); while (ExperimentalRejoin.PhotonIsConnected && Time.realtimeSinceStartup < disconnectDeadline && !abortRequested) { yield return null; } resettingTransportForRetry = false; } if (abortRequested) { FinishWithoutFallback(); yield break; } yield return (object)new WaitForSecondsRealtime(ExperimentalRejoin.Options.RetryDelaySeconds); } else { ExperimentalRejoin.MarkAttemptStarted(attempt); } if (!ExperimentalRejoin.InvokeReconnectAndRejoin()) { continue; } ExperimentalRejoin.MarkWaitingForRoom(attempt); float deadline = Time.realtimeSinceStartup + ExperimentalRejoin.Options.ReconnectTimeoutSeconds; while (Time.realtimeSinceStartup < deadline && !abortRequested) { if (ExperimentalRejoin.PhotonInRoom && ExperimentalRejoin.PhotonIsConnectedAndReady) { ExperimentalRejoin.MarkSuccess(); FinishWithoutFallback(); yield break; } yield return null; } } object obj = disconnectHandler; FinishWithoutFallback(); if (!abortRequested && obj != null) { ExperimentalRejoin.MarkFailureAndFallback(obj, "Attempts exhausted after recoverable disconnect: " + cause + "."); } } private void FinishWithoutFallback() { disconnectHandler = null; resettingTransportForRetry = false; attemptCoroutine = null; } } public sealed class ExperimentalRejoinOptions { public bool Enabled { get; set; } public int PlayerTtlMilliseconds { get; set; } = 60000; public int EmptyRoomTtlMilliseconds { get; set; } = 30000; public float ReconnectTimeoutSeconds { get; set; } = 12f; public float RetryDelaySeconds { get; set; } = 1f; internal void Normalize() { PlayerTtlMilliseconds = Clamp(PlayerTtlMilliseconds, 5000, 300000); EmptyRoomTtlMilliseconds = Clamp(EmptyRoomTtlMilliseconds, 0, 300000); ReconnectTimeoutSeconds = Clamp(ReconnectTimeoutSeconds, 3f, 30f); RetryDelaySeconds = Clamp(RetryDelaySeconds, 0.25f, 5f); } private static int Clamp(int value, int min, int max) { if (value >= min) { if (value <= max) { return value; } return max; } return min; } private static float Clamp(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value)) { return min; } if (!(value < min)) { if (!(value > max)) { return value; } return max; } return min; } } public enum ExperimentalRejoinState { Disabled, Installing, Armed, Reconnecting, WaitingForRoom, Retrying, RejoinedWarmStatePending, FailedClosed, FallingBackToVanilla } public sealed class ExperimentalRejoinStatus { public ExperimentalRejoinState State { get; } public string Detail { get; } public bool Installed { get; } public bool Enabled { get; } public bool NeedsWarmStateRestore { get; } public int RoomsPrepared { get; } public int AttemptsStarted { get; } public int SuccessfulRejoins { get; } public int FailedRejoins { get; } public int SuppressedInactiveLeaveCallbacks { get; } public int RejectedDisconnects { get; } internal ExperimentalRejoinStatus(ExperimentalRejoinState state, string detail, bool installed, bool enabled, bool needsWarmStateRestore, int roomsPrepared, int attemptsStarted, int successfulRejoins, int failedRejoins, int suppressedInactiveLeaveCallbacks, int rejectedDisconnects) { State = state; Detail = detail; Installed = installed; Enabled = enabled; NeedsWarmStateRestore = needsWarmStateRestore; RoomsPrepared = roomsPrepared; AttemptsStarted = attemptsStarted; SuccessfulRejoins = successfulRejoins; FailedRejoins = failedRejoins; SuppressedInactiveLeaveCallbacks = suppressedInactiveLeaveCallbacks; RejectedDisconnects = rejectedDisconnects; } } } namespace ReksikPerformance.ReliabilityGuards { internal static class PatchContract { internal static Type? TypeInAssembly(string fullName, string assemblyName) { Type type = AccessTools.TypeByName(fullName); if (!(type != null) || !string.Equals(type.Assembly.GetName().Name, assemblyName, StringComparison.Ordinal)) { return null; } return type; } internal static MethodInfo? ExactMethod(Type type, string name, BindingFlags flags, string returnTypeName, params string[] parameterTypeNames) { MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(flags); foreach (MethodInfo methodInfo2 in methods) { if (!string.Equals(methodInfo2.Name, name, StringComparison.Ordinal) || !string.Equals(methodInfo2.ReturnType.FullName, returnTypeName, StringComparison.Ordinal)) { continue; } ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length != parameterTypeNames.Length) { continue; } bool flag = true; for (int j = 0; j < parameters.Length; j++) { if (!string.Equals(parameters[j].ParameterType.FullName, parameterTypeNames[j], StringComparison.Ordinal)) { flag = false; break; } } if (!flag || methodInfo != null) { return null; } methodInfo = methodInfo2; } return methodInfo; } internal static bool IsMissingUnityObject(object? candidate) { if (candidate == null) { return true; } Object val = (Object)((candidate is Object) ? candidate : null); if (val != null) { return val == (Object)null; } return false; } } [HarmonyPatch] internal static class EntityLagFixerRoundEndPatch { private const string PluginGuid = "entity-lag-fixer-upper"; private static MethodInfo? target; internal static bool CanPatch(out string reason) { target = null; if (!Chainloader.PluginInfos.TryGetValue("entity-lag-fixer-upper", out var value)) { reason = "plugin GUID 'entity-lag-fixer-upper' is not loaded"; return false; } Type type = PatchContract.TypeInAssembly("EntityLagFixerUpper", "EntityLagFixerUpper"); if (type == null) { reason = "the exact EntityLagFixerUpper type/assembly contract was not found"; return false; } if ((Object)(object)value.Instance != (Object)null && ((object)value.Instance).GetType().Assembly != type.Assembly) { reason = "the GUID owner does not match the discovered assembly"; return false; } target = PatchContract.ExactMethod(type, "RoundEnd", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, "System.Collections.IEnumerator", "UnboundLib.GameModes.IGameModeHandler"); if (target == null) { reason = "RoundEnd no longer has the audited static IEnumerator signature"; return false; } reason = string.Empty; return true; } [HarmonyPrepare] private static bool Prepare() { string reason; return CanPatch(out reason); } [HarmonyTargetMethod] private static MethodBase? TargetMethod() { MethodInfo? methodInfo = target; if ((object)methodInfo == null) { if (!CanPatch(out string _)) { return null; } methodInfo = target; } return methodInfo; } [HarmonyPrefix] private static bool Prefix(ref IEnumerator __result) { __result = Empty(); ReliabilityGuard.CountEntityLagRoundEnd(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch] internal static class NetworkPhysicsCollisionPatch { private static MethodInfo? target; private static FieldInfo? photonViewField; private static PropertyInfo? contactCountProperty; internal static bool CanPatch(out string reason) { target = null; photonViewField = null; contactCountProperty = null; Type type = PatchContract.TypeInAssembly("NetworkPhysicsObject", "Assembly-CSharp"); if (type == null) { reason = "the vanilla NetworkPhysicsObject type was not found"; return false; } target = PatchContract.ExactMethod(type, "OnCollisionEnter2D", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "System.Void", "UnityEngine.Collision2D"); photonViewField = AccessTools.Field(type, "photonView"); contactCountProperty = AccessTools.TypeByName("UnityEngine.Collision2D")?.GetProperty("contactCount", BindingFlags.Instance | BindingFlags.Public); if (target == null || photonViewField == null || !string.Equals(photonViewField.FieldType.FullName, "Photon.Pun.PhotonView", StringComparison.Ordinal) || contactCountProperty?.PropertyType != typeof(int) || contactCountProperty.GetMethod == null) { reason = "the audited collision, PhotonView, or contact-count contract changed"; return false; } reason = string.Empty; return true; } [HarmonyPrepare] private static bool Prepare() { string reason; return CanPatch(out reason); } [HarmonyTargetMethod] private static MethodBase? TargetMethod() { MethodInfo? methodInfo = target; if ((object)methodInfo == null) { if (!CanPatch(out string _)) { return null; } methodInfo = target; } return methodInfo; } [HarmonyPrefix] private static bool Prefix(object __instance, object? __0) { try { if (PatchContract.IsMissingUnityObject(photonViewField.GetValue(__instance)) || __0 == null || (int)contactCountProperty.GetValue(__0, null) <= 0) { ReliabilityGuard.CountNetworkPhysicsSkip(); return false; } } catch { return true; } return true; } } [HarmonyPatch] internal static class FollowPlayerLateUpdatePatch { private static MethodInfo? target; private static FieldInfo? ownPlayerField; private static FieldInfo? targetField; private static FieldInfo? playerManagerInstanceField; private static MethodInfo? getOtherPlayerMethod; internal static bool CanPatch(out string reason) { target = null; ownPlayerField = null; targetField = null; playerManagerInstanceField = null; getOtherPlayerMethod = null; Type type = PatchContract.TypeInAssembly("FollowPlayer", "Assembly-CSharp"); Type playerType = PatchContract.TypeInAssembly("Player", "Assembly-CSharp"); Type type2 = PatchContract.TypeInAssembly("PlayerManager", "Assembly-CSharp"); if (type == null || playerType == null || type2 == null) { reason = "one or more audited vanilla player-follow types are absent"; return false; } target = PatchContract.ExactMethod(type, "LateUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, "System.Void"); ownPlayerField = AccessTools.Field(type, "ownPlayer"); targetField = AccessTools.Field(type, "target"); playerManagerInstanceField = AccessTools.Field(type2, "instance"); getOtherPlayerMethod = type2.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault(delegate(MethodInfo method) { if (!string.Equals(method.Name, "GetOtherPlayer", StringComparison.Ordinal) || method.ReturnType != playerType) { return false; } ParameterInfo[] parameters = method.GetParameters(); return parameters.Length == 1 && parameters[0].ParameterType == playerType; }); FieldInfo? fieldInfo = targetField; bool flag = (object)fieldInfo != null && fieldInfo.FieldType.IsEnum && Enum.GetNames(targetField.FieldType).Contains("Self") && Enum.GetNames(targetField.FieldType).Contains("Other"); if (target == null || ownPlayerField?.FieldType != playerType || !flag || playerManagerInstanceField?.FieldType != type2 || getOtherPlayerMethod == null || !typeof(Component).IsAssignableFrom(playerType) || !typeof(Behaviour).IsAssignableFrom(type)) { reason = "the audited FollowPlayer ownership/target contrac