Decompiled source of ReksikPerformance v0.3.1

ReksikPerformance.dll

Decompiled 3 days ago
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