Decompiled source of LwfFpsBoost v2.1.1

BepInEx\plugins\LwfFpsBoost.dll

Decompiled 21 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Spine;
using Spine.Unity;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;

[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace LwfFpsBoost;

internal sealed class Hotkey
{
	private readonly bool _ctrl;

	private readonly bool _alt;

	private readonly bool _shift;

	private readonly Key _key;

	private readonly string _text;

	private Hotkey(bool ctrl, bool alt, bool shift, Key key, string text)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		_ctrl = ctrl;
		_alt = alt;
		_shift = shift;
		_key = key;
		_text = text;
	}

	public override string ToString()
	{
		return _text;
	}

	internal static Hotkey Parse(string spec)
	{
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		if (string.IsNullOrEmpty(spec))
		{
			return null;
		}
		bool ctrl = false;
		bool alt = false;
		bool shift = false;
		string text = null;
		string[] array = spec.Split('+');
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim();
			if (text2.Length != 0)
			{
				switch (text2.ToLowerInvariant())
				{
				case "ctrl":
				case "control":
					ctrl = true;
					break;
				case "alt":
					alt = true;
					break;
				case "shift":
					shift = true;
					break;
				default:
					text = text2;
					break;
				}
			}
		}
		if (text == null)
		{
			return null;
		}
		try
		{
			Key key = (Key)Enum.Parse(typeof(Key), text, ignoreCase: true);
			return new Hotkey(ctrl, alt, shift, key, spec.Trim());
		}
		catch (Exception)
		{
			return null;
		}
	}

	internal bool WasPressedThisFrame(Keyboard kb)
	{
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Invalid comparison between Unknown and I4
		if (kb == null)
		{
			return false;
		}
		bool flag = ((ButtonControl)kb.leftCtrlKey).isPressed || ((ButtonControl)kb.rightCtrlKey).isPressed;
		bool flag2 = ((ButtonControl)kb.leftAltKey).isPressed || ((ButtonControl)kb.rightAltKey).isPressed;
		bool flag3 = ((ButtonControl)kb.leftShiftKey).isPressed || ((ButtonControl)kb.rightShiftKey).isPressed;
		if (flag != _ctrl || flag2 != _alt || flag3 != _shift)
		{
			return false;
		}
		if (((ButtonControl)kb[_key]).wasPressedThisFrame)
		{
			return true;
		}
		if ((int)_key == 2 && ((ButtonControl)kb[(Key)77]).wasPressedThisFrame)
		{
			return true;
		}
		return false;
	}
}
[BepInPlugin("kiyonakanata.lwffpsboost", "LWF FPS Boost", "2.1.1")]
public class SpineThreadingMod : BaseUnityPlugin
{
	public const string PluginGuid = "kiyonakanata.lwffpsboost";

	public const string PluginName = "LWF FPS Boost";

	public const string PluginVersion = "2.1.1";

	private const int HudFontSize = 16;

	private const int WindowSize = 3600;

	private const float HudInterval = 0.25f;

	private const int AbIdle = 0;

	private const int AbRunA = 1;

	private const int AbFinaleA = 2;

	private const int AbSettleA = 3;

	private const int AbRest = 4;

	private const int AbRunB = 5;

	private const int AbFinaleB = 6;

	private const int AbSettleB = 7;

	private const int AbDone = 8;

	private readonly string _harmonyID = "kiyonakanata.lwffpsboost." + Guid.NewGuid().ToString("N").Substring(0, 8);

	private ConfigEntry<bool> _enabled;

	private ConfigEntry<bool> _threadedAnimation;

	private ConfigEntry<bool> _threadedMeshGeneration;

	private StressTools _stress;

	private Hotkey _keyStressChurn;

	private Hotkey _keyStressStall;

	private Hotkey _keyStressHog;

	private Hotkey _keyAutoAB;

	private float _abPhaseSeconds = 30f;

	private int _abPhase;

	private float _abPhaseEnd;

	private string _abSummary = "";

	private int _abBaseTimeout;

	private int _abBaseSpineErr;

	private int _abBaseLogic;

	private int _abBaseIndex;

	private int _abBaseWorkerExc;

	private int _abBaseStall;

	private int _abBaseNull;

	private string _abResultA = "";

	private string _abVerdict = "";

	private string _abVerdictA = "";

	private bool _abFullAB;

	private Hotkey _keyPerfAB;

	private float _perfPhaseSeconds = 20f;

	private int _perfPhase;

	private float _perfPhaseEnd;

	private bool _perfStartedChurn;

	private double _perfOn1;

	private double _perfOff;

	private double _perfOn2;

	private string _perfSummary = "";

	private int _perfChurnAlive = 1500;

	private int _perfSavedChurnAlive;

	private int _perfSavedVSync;

	private int _perfSavedTargetFps;

	private int _logicErrorCount;

	private int _indexErrorCount;

	private int _nullErrorCount;

	private volatile string _lastSpineError = "";

	private Harmony _harmony;

	private bool _waitPathPatched;

	private string _waitPathReport = "";

	private int _errorCount;

	private int _spineErrorCount;

	private volatile string _lastError = "";

	private bool _logHooked;

	private ConfigEntry<bool> _hudOnStart;

	private Hotkey _keyToggleThreading;

	private Hotkey _keyToggleHud;

	private Hotkey _keyToggleDetail;

	private Hotkey _keyResetStats;

	private bool _detail;

	private bool _inGame;

	private string _abortNotice = "";

	private bool _locked;

	private bool _upstreamFixed;

	private bool _subscribed;

	private bool _threadingOn;

	private int _skeletonCount = -1;

	private readonly List<float> _frameMs = new List<float>(3600);

	private int _cursor;

	private double _sum;

	private int _count;

	private readonly List<float> _sortScratch = new List<float>(3600);

	private bool _hudVisible;

	private float _nextHudRebuild;

	private readonly GUIContent _hudContent = new GUIContent("");

	private GUIStyle _style;

	private GUIStyle _shadowStyle;

	private string _message = "";

	private float _messageUntil;

	private int _abFinaleFired0;

	private bool _perfSkipDone;

	private void Awake()
	{
		//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
		//IL_0269: Unknown result type (might be due to invalid IL or missing references)
		//IL_0273: Expected O, but got Unknown
		//IL_028a: Unknown result type (might be due to invalid IL or missing references)
		//IL_028f: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
		_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1. General", "Enabled", true, "");
		_keyAutoAB = Hotkey.Parse("F9");
		_keyPerfAB = Hotkey.Parse("F10");
		_keyToggleHud = null;
		_threadedAnimation = ((BaseUnityPlugin)this).Config.Bind<bool>("9. Developer", "ThreadedAnimation", true, "");
		_threadedMeshGeneration = ((BaseUnityPlugin)this).Config.Bind<bool>("9. Developer", "ThreadedMeshGeneration", true, "");
		_hudOnStart = ((BaseUnityPlugin)this).Config.Bind<bool>("9. Developer", "ShowInGame", false, "");
		_abFullAB = ((BaseUnityPlugin)this).Config.Bind<bool>("9. Developer", "FullAB", false, "true = also run with guard off (may crash the game)").Value;
		_stress = new StressTools(((BaseUnityPlugin)this).Logger);
		_stress.ChurnAlive = 600;
		_stress.ChurnPerFrame = 15;
		_stress.ChurnSpread = 12f;
		_abPhaseSeconds = 45f;
		_perfChurnAlive = 1500;
		_perfPhaseSeconds = 20f;
		StressTools.StallMs = 1200;
		_stress.StallEveryFrames = 60;
		_stress.StallBackoff = 12;
		StressTools.StallSlowMs = 3;
		StressTools.StallSlowCalls = 16;
		_stress.StallSeconds = 60f;
		_stress.HogThreads = 0;
		_stress.HogSeconds = 15f;
		_keyToggleDetail = Hotkey.Parse("Shift+F11");
		_keyToggleThreading = Hotkey.Parse("Shift+F10");
		_keyResetStats = null;
		_keyStressChurn = null;
		_keyStressStall = null;
		_keyStressHog = null;
		Scene activeScene = SceneManager.GetActiveScene();
		_inGame = IsInGameScene(((Scene)(ref activeScene)).name);
		_hudVisible = !_inGame || _hudOnStart.Value;
		if (!_enabled.Value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[boot] Enabled=false, doing nothing");
			return;
		}
		if (GameHasThreading())
		{
			_upstreamFixed = true;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[boot] the game already runs Spine threaded; this mod is not needed");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"[boot] delete BepInEx/plugins/LwfFpsBoost.dll");
			SceneManager.sceneLoaded += OnSceneLoaded;
			_subscribed = true;
			return;
		}
		Application.logMessageReceivedThreaded += new LogCallback(OnUnityLog);
		_logHooked = true;
		IncidentLog.Init(((BaseUnityPlugin)this).Logger, "2.1.1");
		Scene activeScene2 = SceneManager.GetActiveScene();
		IncidentLog.SceneName = ((Scene)(ref activeScene2)).name;
		ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
		Scene activeScene3 = SceneManager.GetActiveScene();
		GameReport.Init(logger, "2.1.1", ((Scene)(ref activeScene3)).name);
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[game] run detection: " + (GameReport.ProbeOk ? GameReport.ProbeReport : ("fallback to InGame scene (" + GameReport.ProbeReport + ")"))));
		StressTools.MainThreadId = Thread.CurrentThread.ManagedThreadId;
		InstallWaitPath();
		InstallStallPatch();
		ApplyGlobals(_threadedAnimation.Value, _threadedMeshGeneration.Value, "boot");
		_threadingOn = _threadedAnimation.Value || _threadedMeshGeneration.Value;
		IncidentLog.ThreadingOn = _threadingOn;
		SceneManager.sceneLoaded += OnSceneLoaded;
		_subscribed = true;
		((BaseUnityPlugin)this).Logger.LogInfo((object)string.Concat("[boot] LWF FPS Boost 2.1.1  guard=", _waitPathPatched ? "on" : "off", "  keys: ", _keyAutoAB, "=load test, ", _keyPerfAB, "=perf test (title screen only)"));
	}

	private void OnApplicationQuit()
	{
		GameReport.Finish();
	}

	private void OnDestroy()
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		if (_subscribed)
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			_subscribed = false;
		}
		if (_logHooked)
		{
			Application.logMessageReceivedThreaded -= new LogCallback(OnUnityLog);
			_logHooked = false;
		}
		if (_abPhase != 0 && _abPhase != 8)
		{
			AbFinish("destroyed");
		}
		if (_perfPhase != 0 && _perfPhase != 4)
		{
			PerfFinish("destroyed");
		}
		if (_stress != null)
		{
			_stress.StopAll();
		}
		LateUpdateGuard.Active = false;
		if (_harmony != null)
		{
			try
			{
				_harmony.UnpatchSelf();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[boot] unpatch failed: " + ex.Message));
			}
			_harmony = null;
		}
	}

	private void InstallWaitPath()
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		if (!LateUpdateGuard.Prepare(out var report))
		{
			_waitPathReport = report;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("[guard] " + report));
			return;
		}
		try
		{
			_harmony = new Harmony(_harmonyID);
			_harmony.PatchAll(typeof(LateUpdateGuard));
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[guard] patched SkeletonUpdateSystem.LateUpdateAsync (Postfix). " + report));
			if (UpdateGuard.Prepare(out var report2))
			{
				_harmony.PatchAll(typeof(UpdateGuard));
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[guard] patched SkeletonUpdateSystem.WaitForThreadUpdateTasks (Postfix). " + report2));
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[guard] " + report2));
			}
			LateUpdateGuard.Active = true;
			IncidentLog.GuardOn = true;
			_waitPathPatched = true;
			_waitPathReport = report;
		}
		catch (Exception ex)
		{
			_waitPathReport = "Harmony patch failed: " + ex.Message;
			((BaseUnityPlugin)this).Logger.LogError((object)("[guard] " + _waitPathReport));
		}
	}

	private void AbStart()
	{
		if (!_waitPathPatched)
		{
			Say(Lang.T("高負荷テスト: 実行不可   エラー回避処理: 無効(", "Load test: cannot run   guard: off (") + _waitPathReport + Lang.T(")", ")"));
			return;
		}
		_abSummary = "";
		_abResultA = "";
		_abVerdict = "";
		if (AbBeginRun(waitPath: true))
		{
			_abPhase = 1;
			if (!_hudVisible)
			{
				_hudVisible = true;
			}
			if (_skeletonCount < 0)
			{
				RefreshSkeletonCount();
			}
			Say(Lang.T("高負荷テスト: 開始", "Load test: started"));
		}
	}

	private bool AbBeginRun(bool waitPath)
	{
		LateUpdateGuard.Active = waitPath;
		IncidentLog.GuardOn = waitPath;
		IncidentLog.TestPhase = (waitPath ? "load test A (guard on)" : "load test B (guard off)");
		IncidentLog.Note(waitPath ? "test: A start (guard on)" : "test: B start (guard off)");
		if (!_stress.ChurnOn)
		{
			string msg = _stress.ToggleChurn();
			if (!_stress.ChurnOn)
			{
				Say(msg);
				AbFinish("cancelled");
				return false;
			}
		}
		AbSnapshot();
		_stress.StallPeriodic = true;
		if (!StressTools.StallOn)
		{
			_stress.ToggleStall();
		}
		_abPhaseEnd = Time.unscaledTime + _abPhaseSeconds;
		ResetStats();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] " + (waitPath ? "A start: guard on" : "B start: guard off") + ", churn + stall, " + _abPhaseSeconds + " s"));
		return true;
	}

	private void AbBeginFinale()
	{
		_stress.StallPeriodic = false;
		if (!StressTools.StallOn)
		{
			_stress.ToggleStall();
		}
		_abFinaleFired0 = StressTools.StallFired;
		_stress.FireMeshStallOnce();
		_abPhaseEnd = Time.unscaledTime + 3f;
	}

	private void AbSnapshot()
	{
		_abBaseTimeout = LateUpdateGuard.TimeoutCount + UpdateGuard.TimeoutCount;
		_abBaseWorkerExc = LateUpdateGuard.GiveUpCount + UpdateGuard.GiveUpCount;
		_abBaseNull = _nullErrorCount;
		_abBaseSpineErr = _spineErrorCount;
		_abBaseLogic = _logicErrorCount;
		_abBaseIndex = _indexErrorCount;
		_abBaseStall = StressTools.StallFired;
	}

	private string AbDelta(string label)
	{
		return label + " stalls " + (StressTools.StallFired - _abBaseStall) + " / guard waits " + (LateUpdateGuard.TimeoutCount + UpdateGuard.TimeoutCount - _abBaseTimeout) + " / upstream timeouts " + (_logicErrorCount - _abBaseLogic) + " / out of range " + (_indexErrorCount - _abBaseIndex) + " / null (GetMix) " + (_nullErrorCount - _abBaseNull) + " / spine errors " + (_spineErrorCount - _abBaseSpineErr) + " / give-ups " + (LateUpdateGuard.GiveUpCount + UpdateGuard.GiveUpCount - _abBaseWorkerExc) + "  " + F(AvgMs()) + " ms";
	}

	private void AbTick()
	{
		if (_abPhase == 0 || _abPhase == 8)
		{
			return;
		}
		if (_abPhase == 2 || _abPhase == 6)
		{
			bool flag = StressTools.StallFired > _abFinaleFired0;
			if (flag || Time.unscaledTime >= _abPhaseEnd)
			{
				if (!flag)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"[test] finale: stall did not fire within 3 s, continuing");
				}
				_stress.StopStall();
				if (_stress.ChurnOn)
				{
					_stress.ToggleChurn();
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] finale: mass unregister (fired=" + flag + "), settling 5 s"));
				IncidentLog.Note("test: finale, mass unregister (fired=" + flag + ")");
				_abPhase = ((_abPhase == 2) ? 3 : 7);
				_abPhaseEnd = Time.unscaledTime + 5f;
			}
		}
		else
		{
			if (Time.unscaledTime < _abPhaseEnd)
			{
				return;
			}
			switch (_abPhase)
			{
			case 1:
				_abPhase = 2;
				AbBeginFinale();
				break;
			case 3:
				_abResultA = AbDelta(_abFullAB ? "A[guard on]" : "[guard on]");
				_abVerdictA = AbVerdict();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] " + _abResultA));
				if (!_abFullAB)
				{
					AbFinish("done");
					_abVerdict = _abVerdictA;
					_abSummary = _abVerdictA + "\n                 " + _abResultA;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] load test: " + _abVerdictA));
					GameReport.TestBlock("load test", _abVerdictA, _abResultA);
					Say(Lang.T("高負荷テスト: ", "Load test: ") + _abVerdictA);
				}
				else
				{
					_abPhase = 4;
					_abPhaseEnd = Time.unscaledTime + 5f;
				}
				break;
			case 4:
				if (AbBeginRun(waitPath: false))
				{
					_abPhase = 5;
				}
				break;
			case 5:
				_abPhase = 6;
				AbBeginFinale();
				break;
			case 7:
			{
				string text = AbDelta("B[guard off]");
				int num = _indexErrorCount - _abBaseIndex;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] " + text));
				AbFinish("done");
				int num2 = _nullErrorCount - _abBaseNull;
				_abVerdict = Lang.T("あり: ", "guard on: ") + _abVerdictA + Lang.T("   なし: out of range ", "   guard off: out of range ") + num + "   null " + num2;
				_abSummary = _abResultA + "\n                 " + text + ((_lastSpineError.Length > 0) ? ("\n                 last Spine error: " + Truncate(_lastSpineError, 100)) : "");
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] A/B done. last Spine error: " + _lastSpineError));
				GameReport.TestBlock("A/B", _abVerdict, _abResultA + "  |  " + text);
				Say(Lang.T("A/B: 完了", "A/B: done"));
				break;
			}
			case 2:
			case 6:
				break;
			}
		}
	}

	private string AbVerdict()
	{
		int num = StressTools.StallFired - _abBaseStall;
		int num2 = LateUpdateGuard.TimeoutCount + UpdateGuard.TimeoutCount - _abBaseTimeout;
		int num3 = _indexErrorCount - _abBaseIndex;
		int num4 = _nullErrorCount - _abBaseNull;
		int num5 = _spineErrorCount - _abBaseSpineErr;
		int num6 = _logicErrorCount - _abBaseLogic;
		int num7 = LateUpdateGuard.GiveUpCount + UpdateGuard.GiveUpCount - _abBaseWorkerExc;
		int num8 = num5 - num6;
		if (num3 > 0 || num4 > 0 || num8 > 0 || num7 > 0)
		{
			return Lang.T("不合格   out of range ", "FAIL   out of range ") + num3 + "   null " + num4 + Lang.T("   Spine 例外 ", "   Spine exceptions ") + num8 + Lang.T("   未完了 ", "   give-ups ") + num7;
		}
		if (num == 0 || num2 == 0)
		{
			return Lang.T("判定不能   ワーカー遅延 ", "INCONCLUSIVE   worker stalls ") + num + Lang.T("   対応 ", "   handled ") + num2 + Lang.T("   → もう一度 ", "   → run again with ") + _keyAutoAB;
		}
		return Lang.T("合格   例外 0   ワーカー遅延 ", "PASS   exceptions 0   worker stalls ") + num + Lang.T("   対応 ", "   handled ") + num2;
	}

	private void AbFinish(string how)
	{
		_stress.StopStall();
		_stress.StallPeriodic = true;
		if (_stress.ChurnOn)
		{
			_stress.ToggleChurn();
		}
		LateUpdateGuard.Active = _waitPathPatched;
		IncidentLog.GuardOn = _waitPathPatched;
		IncidentLog.TestPhase = "";
		IncidentLog.Note("test: " + how);
		_abPhase = ((how == "done") ? 8 : 0);
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] " + how + ". guard restored (active=" + LateUpdateGuard.Active + ")"));
	}

	private string AbStatus()
	{
		string text = Mathf.CeilToInt(Math.Max(0f, _abPhaseEnd - Time.unscaledTime)) + "s";
		return _abPhase switch
		{
			1 => (_abFullAB ? Lang.T("A[回避処理あり] ", "A[guard on] ") : Lang.T("負荷 ", "load ")) + text, 
			2 => (_abFullAB ? "A " : "") + Lang.T("一斉解除", "mass unregister"), 
			3 => (_abFullAB ? "A " : "") + Lang.T("集計 ", "settle ") + text, 
			4 => Lang.T("休止 ", "rest ") + text, 
			5 => Lang.T("B[回避処理なし] ", "B[guard off] ") + text, 
			6 => Lang.T("B 一斉解除", "B mass unregister"), 
			7 => Lang.T("B 集計 ", "B settle ") + text, 
			8 => Lang.T("完了", "done"), 
			_ => Lang.T("停止", "idle"), 
		};
	}

	private void PerfStart()
	{
		if (_abPhase != 0 && _abPhase != 8)
		{
			Say(Lang.T("マルチスレッド効果検証: 実行不可   高負荷テスト中", "Speed test: cannot run   the load test is running"));
			return;
		}
		_perfSummary = "";
		if (StressTools.StallOn)
		{
			_stress.StopStall();
		}
		_perfStartedChurn = false;
		_perfSavedChurnAlive = _stress.ChurnAlive;
		_stress.ChurnAlive = Math.Max(_stress.ChurnAlive, _perfChurnAlive);
		if (!_stress.ChurnOn)
		{
			string msg = _stress.ToggleChurn();
			if (!_stress.ChurnOn)
			{
				_stress.ChurnAlive = _perfSavedChurnAlive;
				Say(msg);
				return;
			}
			_perfStartedChurn = true;
		}
		_perfSavedVSync = QualitySettings.vSyncCount;
		_perfSavedTargetFps = Application.targetFrameRate;
		QualitySettings.vSyncCount = 0;
		Application.targetFrameRate = -1;
		if (!_threadingOn)
		{
			ToggleThreading();
		}
		_perfPhase = 1;
		IncidentLog.TestPhase = "perf ON#1";
		IncidentLog.Note("perf: start");
		_perfPhaseEnd = Time.unscaledTime + _perfPhaseSeconds + 2f;
		_perfSkipDone = false;
		ResetStats();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[perf] start: " + _stress.ChurnAlive + " skeletons, ON->OFF->ON, " + _perfPhaseSeconds + " s each, vsync " + _perfSavedVSync + "->0, targetFps " + _perfSavedTargetFps + "->-1"));
		Say(Lang.T("マルチスレッド効果検証: 開始", "Speed test: started"));
	}

	private void PerfTick()
	{
		if (_perfPhase == 0 || _perfPhase == 4)
		{
			return;
		}
		if (!_perfSkipDone && Time.unscaledTime >= _perfPhaseEnd - _perfPhaseSeconds)
		{
			ResetStats();
			_perfSkipDone = true;
		}
		if (!(Time.unscaledTime < _perfPhaseEnd))
		{
			double num = AvgMs();
			switch (_perfPhase)
			{
			case 1:
				_perfOn1 = num;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[perf] ON#1  " + F(num) + " ms  " + F(1000.0 / Math.Max(0.001, num)) + " fps  (" + _frameMs.Count + " frames)"));
				ToggleThreading();
				PerfNextPhase(2);
				break;
			case 2:
				_perfOff = num;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[perf] OFF   " + F(num) + " ms  " + F(1000.0 / Math.Max(0.001, num)) + " fps  (" + _frameMs.Count + " frames)"));
				ToggleThreading();
				PerfNextPhase(3);
				break;
			case 3:
			{
				_perfOn2 = num;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[perf] ON#2  " + F(num) + " ms  " + F(1000.0 / Math.Max(0.001, num)) + " fps  (" + _frameMs.Count + " frames)"));
				double num2 = (_perfOn1 + _perfOn2) / 2.0;
				double v = ((num2 > 0.0) ? (_perfOff / num2) : 0.0);
				double v2 = _perfOff - num2;
				_perfSummary = Lang.T("1 フレーム -", "per frame -") + F(v2) + " ms   OFF " + F(_perfOff) + " ms → ON " + F(num2) + " ms   fps " + F(1000.0 / Math.Max(0.001, _perfOff)) + " → " + F(1000.0 / Math.Max(0.001, num2)) + Lang.T("(", " (x") + F(v) + Lang.T(" 倍)", ")");
				((BaseUnityPlugin)this).Logger.LogInfo((object)("[perf] ON " + F(_perfOn1) + " / OFF " + F(_perfOff) + " / ON " + F(_perfOn2) + " ms; saved " + F(v2) + " ms/frame, x" + F(v)));
				GameReport.TestBlock("perf", "saved " + F(v2) + " ms/frame, x" + F(v), "ON " + F(_perfOn1) + " / OFF " + F(_perfOff) + " / ON " + F(_perfOn2) + " ms" + (_stress.UsingBuiltin ? (" (title, builtin skeletons x" + _stress.ChurnAlive + ")") : " (in game)"));
				PerfFinish("done");
				Say(Lang.T("マルチスレッド効果検証: 完了", "Speed test: done"));
				break;
			}
			}
		}
	}

	private void PerfNextPhase(int phase)
	{
		_perfPhase = phase;
		IncidentLog.TestPhase = ((phase == 2) ? "perf OFF" : "perf ON#2");
		IncidentLog.Note("perf: phase " + phase);
		_perfPhaseEnd = Time.unscaledTime + _perfPhaseSeconds + 2f;
		_perfSkipDone = false;
		ResetStats();
	}

	private void PerfFinish(string how)
	{
		if (!_threadingOn)
		{
			ToggleThreading();
		}
		if (_perfStartedChurn && _stress.ChurnOn)
		{
			_stress.ToggleChurn();
		}
		_stress.ChurnAlive = _perfSavedChurnAlive;
		QualitySettings.vSyncCount = _perfSavedVSync;
		Application.targetFrameRate = _perfSavedTargetFps;
		_perfPhase = ((how == "done") ? 4 : 0);
		IncidentLog.TestPhase = "";
		IncidentLog.Note("perf: " + how);
		_perfSkipDone = false;
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[perf] " + how));
	}

	private string PerfStatus()
	{
		string text = Mathf.CeilToInt(Math.Max(0f, _perfPhaseEnd - Time.unscaledTime)) + "s";
		return _perfPhase switch
		{
			1 => "ON#1 " + text, 
			2 => "OFF " + text, 
			3 => "ON#2 " + text, 
			4 => Lang.T("完了", "done"), 
			_ => Lang.T("停止", "idle"), 
		};
	}

	private void InstallStallPatch()
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Expected O, but got Unknown
		try
		{
			if (_harmony == null)
			{
				_harmony = new Harmony(_harmonyID);
			}
			_harmony.PatchAll(typeof(WorkerStallPatch));
			_harmony.PatchAll(typeof(AnimStallPatch));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("[stress] stall patch failed: " + ex.Message));
		}
	}

	private void OnUnityLog(string condition, string stackTrace, LogType type)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Invalid comparison between Unknown and I4
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Invalid comparison between Unknown and I4
		if ((int)type == 0 || (int)type == 4 || (int)type == 1)
		{
			Interlocked.Increment(ref _errorCount);
			IncidentLog.OnUnityLog(condition, stackTrace, type);
			string text = condition ?? "";
			string text2 = stackTrace ?? "";
			int num = text.IndexOf('\n');
			string text3 = ((num >= 0) ? text.Substring(0, num) : text);
			if (text.IndexOf("Spine", StringComparison.Ordinal) >= 0 || text2.IndexOf("Spine.", StringComparison.Ordinal) >= 0 || text.IndexOf("Internal threading logic error", StringComparison.Ordinal) >= 0 || text.IndexOf("updateDone", StringComparison.Ordinal) >= 0 || text.IndexOf("lateUpdateDone", StringComparison.Ordinal) >= 0)
			{
				Interlocked.Increment(ref _spineErrorCount);
				_lastSpineError = text3;
			}
			if (text.IndexOf("Internal threading logic error", StringComparison.Ordinal) >= 0 || text.IndexOf("ran into a timeout", StringComparison.Ordinal) >= 0)
			{
				Interlocked.Increment(ref _logicErrorCount);
			}
			if (text.IndexOf("cannot be null", StringComparison.Ordinal) >= 0)
			{
				Interlocked.Increment(ref _nullErrorCount);
			}
			if (text.IndexOf("out of range", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				Interlocked.Increment(ref _indexErrorCount);
			}
			_lastError = text3;
		}
	}

	private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
	{
		Lang.Refresh();
		if (_upstreamFixed)
		{
			_inGame = IsInGameScene(((Scene)(ref scene)).name);
			_hudVisible = !_inGame || _hudOnStart.Value;
			return;
		}
		bool flag = false;
		if (_abPhase != 0 && _abPhase != 8)
		{
			AbFinish("cancelled");
			flag = true;
		}
		if (_perfPhase != 0 && _perfPhase != 4)
		{
			PerfFinish("cancelled");
			flag = true;
		}
		_inGame = IsInGameScene(((Scene)(ref scene)).name);
		IncidentLog.SceneName = ((Scene)(ref scene)).name;
		IncidentLog.Note("scene: " + ((Scene)(ref scene)).name);
		GameReport.Scene(((Scene)(ref scene)).name);
		if (flag)
		{
			if (_threadingOn)
			{
				ToggleThreading();
			}
			_locked = true;
			GameReport.Event("test cancelled by scene change; threading disabled until restart");
			_abortNotice = "■ LWF FPS Boost" + Lang.T(": テスト中止(画面切替)   マルチスレッド: 無効   → ゲームを再起動", ": test stopped (scene change)   threading: off   → restart the game") + ((_keyToggleHud != null) ? string.Concat("   ", _keyToggleHud, Lang.T(": 消す", ": hide")) : "");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[test] cancelled by scene change (" + ((Scene)(ref scene)).name + "). threading disabled, tests locked until restart"));
		}
		_hudVisible = !_inGame || _hudOnStart.Value;
		if (_hudVisible && _skeletonCount < 0)
		{
			RefreshSkeletonCount();
		}
		ApplyGlobals(_threadingOn && _threadedAnimation.Value, _threadingOn && _threadedMeshGeneration.Value, "scene:" + ((Scene)(ref scene)).name);
		_skeletonCount = -1;
		if (_stress != null)
		{
			_stress.StopChurn();
		}
	}

	private static bool GameHasThreading()
	{
		try
		{
			return RuntimeSettings.UseThreadedAnimation && RuntimeSettings.UseThreadedMeshGeneration;
		}
		catch (Exception)
		{
			return false;
		}
	}

	private void ApplyGlobals(bool anim, bool mesh, string reason)
	{
		try
		{
			RuntimeSettings.UseThreadedAnimation = anim;
			RuntimeSettings.UseThreadedMeshGeneration = mesh;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("[" + reason + "] UseThreadedAnimation=" + RuntimeSettings.UseThreadedAnimation + " UseThreadedMeshGeneration=" + RuntimeSettings.UseThreadedMeshGeneration));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("[settings] apply failed: " + ex));
		}
	}

	private void ToggleThreading()
	{
		//IL_0054: 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_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_008d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		bool flag = !_threadingOn;
		bool flag2 = flag && _threadedAnimation.Value;
		bool flag3 = flag && _threadedMeshGeneration.Value;
		ApplyGlobals(flag2, flag3, flag ? "toggle:ON" : "toggle:OFF");
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		try
		{
			SettingsTriState val = (SettingsTriState)(flag2 ? 1 : 0);
			SettingsTriState val2 = (SettingsTriState)(flag3 ? 1 : 0);
			SkeletonAnimationBase[] array = Object.FindObjectsByType<SkeletonAnimationBase>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			for (int i = 0; i < array.Length; i++)
			{
				if ((Object)(object)array[i] != (Object)null && array[i].ThreadedAnimation != val)
				{
					array[i].ThreadedAnimation = val;
					num++;
				}
			}
			SkeletonRenderer[] array2 = Object.FindObjectsByType<SkeletonRenderer>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			num3 = array2.Length;
			for (int j = 0; j < array2.Length; j++)
			{
				if ((Object)(object)array2[j] != (Object)null && array2[j].ThreadedMeshGeneration != val2)
				{
					array2[j].ThreadedMeshGeneration = val2;
					num2++;
				}
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("[threading] toggle failed: " + ex));
			Say(Lang.T("マルチスレッド切替: 失敗   ", "Threading toggle failed   ") + ex.Message);
			return;
		}
		_threadingOn = flag;
		IncidentLog.ThreadingOn = flag;
		IncidentLog.Note("threading: " + (flag ? "on" : "off"));
		_skeletonCount = num3;
		ResetStats();
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[threading] " + (flag ? "on" : "off") + " (anim " + num + " / mesh " + num2 + " changed)"));
		Say(Lang.T("マルチスレッド: ", "Threading: ") + (flag ? Lang.T("有効", "on") : Lang.T("無効", "off")) + "   anim " + num + " / mesh " + num2);
	}

	private void Update()
	{
		if (!_enabled.Value)
		{
			return;
		}
		if (_upstreamFixed)
		{
			if (Time.unscaledTime >= _nextHudRebuild)
			{
				_nextHudRebuild = Time.unscaledTime + 0.25f;
				RebuildHud();
			}
			return;
		}
		RecordFrame();
		HandleInput();
		_stress.Tick();
		AbTick();
		PerfTick();
		if (Time.unscaledTime >= _nextHudRebuild)
		{
			_nextHudRebuild = Time.unscaledTime + 0.25f;
			RebuildHud();
		}
	}

	private void RecordFrame()
	{
		float num = Time.unscaledDeltaTime * 1000f;
		GameReport.Counters(_logicErrorCount, _indexErrorCount, _nullErrorCount, _errorCount, _spineErrorCount);
		GameReport.Frame(num, Time.unscaledDeltaTime, _threadingOn, LateUpdateGuard.RegisteredRenderers, LateUpdateGuard.RegisteredAnimations);
		if (_frameMs.Count < 3600)
		{
			_frameMs.Add(num);
			_sum += num;
		}
		else
		{
			_sum -= _frameMs[_cursor];
			_sum += num;
			_frameMs[_cursor] = num;
			_cursor = (_cursor + 1) % 3600;
		}
		_count++;
	}

	private void HandleInput()
	{
		Keyboard current = Keyboard.current;
		if (current == null || (_inGame && !_hudOnStart.Value))
		{
			return;
		}
		if (_locked)
		{
			if (_keyToggleHud != null && _keyToggleHud.WasPressedThisFrame(current))
			{
				_hudVisible = !_hudVisible;
			}
			if ((_keyAutoAB != null && _keyAutoAB.WasPressedThisFrame(current)) || (_keyPerfAB != null && _keyPerfAB.WasPressedThisFrame(current)))
			{
				_hudVisible = true;
				Say(Lang.T("テスト: 実行不可(画面切替で中止済み)   → ゲームを再起動", "Tests are stopped until restart   → restart the game"));
			}
			return;
		}
		if (_keyToggleThreading != null && _keyToggleThreading.WasPressedThisFrame(current))
		{
			ToggleThreading();
		}
		if (_keyToggleHud != null && _keyToggleHud.WasPressedThisFrame(current))
		{
			_hudVisible = !_hudVisible;
			if (_hudVisible && _skeletonCount < 0)
			{
				RefreshSkeletonCount();
			}
		}
		if (_keyToggleDetail != null && _keyToggleDetail.WasPressedThisFrame(current))
		{
			_detail = !_detail;
			if (_detail)
			{
				_hudVisible = true;
				RefreshSkeletonCount();
			}
		}
		if (_keyResetStats != null && _keyResetStats.WasPressedThisFrame(current))
		{
			ResetStats();
			RefreshSkeletonCount();
			Say(Lang.T("集計: リセット", "Counters reset"));
		}
		if (_keyPerfAB != null && _keyPerfAB.WasPressedThisFrame(current))
		{
			if (_perfPhase != 0 && _perfPhase != 4)
			{
				PerfFinish("cancelled");
				Say(Lang.T("マルチスレッド効果検証: 中止", "Speed test: stopped"));
			}
			else
			{
				PerfStart();
			}
		}
		if (_keyAutoAB != null && _keyAutoAB.WasPressedThisFrame(current))
		{
			if (_abPhase != 0 && _abPhase != 8)
			{
				AbFinish("cancelled");
				Say(Lang.T("高負荷テスト: 中止", "Load test: stopped"));
			}
			else
			{
				AbStart();
			}
		}
		if (_keyStressStall != null && _keyStressStall.WasPressedThisFrame(current))
		{
			Say(_stress.ToggleStall());
		}
		else if (_keyStressHog != null && _keyStressHog.WasPressedThisFrame(current))
		{
			Say(_stress.ToggleHog());
		}
		else if (_keyStressChurn != null && _keyStressChurn.WasPressedThisFrame(current))
		{
			Say(_stress.ToggleChurn());
		}
	}

	private void ResetStats()
	{
		_frameMs.Clear();
		_cursor = 0;
		_sum = 0.0;
		_count = 0;
	}

	private void RefreshSkeletonCount()
	{
		try
		{
			_skeletonCount = Object.FindObjectsByType<SkeletonRenderer>((FindObjectsInactive)0, (FindObjectsSortMode)0).Length;
		}
		catch (Exception)
		{
			_skeletonCount = -1;
		}
	}

	private void Say(string msg)
	{
		_message = msg;
		_messageUntil = Time.unscaledTime + 5f;
	}

	private static string F(double v)
	{
		return v.ToString("0.##", CultureInfo.InvariantCulture);
	}

	private static string Truncate(string s, int max)
	{
		if (s.Length > max)
		{
			return s.Substring(0, max) + "…";
		}
		return s;
	}

	private static bool IsInGameScene(string sceneName)
	{
		return sceneName != "Title";
	}

	private float AbRemaining()
	{
		float num = Math.Max(0f, _abPhaseEnd - Time.unscaledTime);
		float num2 = (_abFullAB ? (5f + _abPhaseSeconds + 3f + 5f) : 0f);
		return _abPhase switch
		{
			1 => num + 3f + 5f + num2, 
			2 => num + 5f + num2, 
			3 => num + num2, 
			4 => num + _abPhaseSeconds + 3f + 5f, 
			5 => num + 3f + 5f, 
			6 => num + 5f, 
			7 => num, 
			_ => 0f, 
		};
	}

	private float AbTotalSeconds()
	{
		return _abPhaseSeconds + 3f + 5f + (_abFullAB ? (5f + _abPhaseSeconds + 3f + 5f) : 0f);
	}

	private float PerfRemaining()
	{
		float num = Math.Max(0f, _perfPhaseEnd - Time.unscaledTime);
		float num2 = _perfPhaseSeconds + 2f;
		return _perfPhase switch
		{
			1 => num + num2 * 2f, 
			2 => num + num2, 
			3 => num, 
			_ => 0f, 
		};
	}

	private float PerfTotalSeconds()
	{
		return (_perfPhaseSeconds + 2f) * 3f;
	}

	private double AvgMs()
	{
		if (_frameMs.Count <= 0)
		{
			return 0.0;
		}
		return _sum / (double)_frameMs.Count;
	}

	private double LowMs()
	{
		int count = _frameMs.Count;
		if (count == 0)
		{
			return 0.0;
		}
		_sortScratch.Clear();
		for (int i = 0; i < count; i++)
		{
			_sortScratch.Add(_frameMs[i]);
		}
		_sortScratch.Sort();
		int num = (int)((double)count * 0.99);
		if (num >= count)
		{
			num = count - 1;
		}
		return _sortScratch[num];
	}

	private void RebuildHud()
	{
		//IL_030a: Unknown result type (might be due to invalid IL or missing references)
		//IL_030f: Unknown result type (might be due to invalid IL or missing references)
		if (_inGame && !_hudOnStart.Value)
		{
			_hudContent.text = "";
			return;
		}
		if (!_hudVisible)
		{
			_hudContent.text = "";
			return;
		}
		if (_upstreamFixed)
		{
			_hudContent.text = "[LWF FPS Boost 2.1.1]   " + Lang.T("本体が対応済み。この MOD は不要   → BepInEx/plugins/LwfFpsBoost.dll を削除", "The game runs Spine threaded   → delete BepInEx/plugins/LwfFpsBoost.dll");
			return;
		}
		StringBuilder stringBuilder = new StringBuilder(768);
		bool flag = _abPhase != 0 && _abPhase != 8;
		bool flag2 = _perfPhase != 0 && _perfPhase != 4;
		string text = Lang.T("有効", "on");
		string text2 = Lang.T("無効", "off");
		stringBuilder.AppendLine("[LWF FPS Boost 2.1.1]" + Lang.T("   マルチスレッド: ", "   threading: ") + (_threadingOn ? text : text2) + Lang.T("   マルチスレッドエラー回避処理: ", "   guard: ") + ((_waitPathPatched && LateUpdateGuard.Active) ? text : text2));
		if (_locked)
		{
			stringBuilder.AppendLine(Lang.T("■ テスト中止(画面切替)   マルチスレッド: 無効   テスト: 再起動まで停止", "■ test stopped (scene change)   threading: off   tests: stopped until restart"));
			stringBuilder.AppendLine(Lang.T("   → ゲームを再起動", "   → restart the game") + ((_keyToggleHud != null) ? string.Concat("   ", _keyToggleHud, Lang.T(": 表示 OFF", ": hide")) : ""));
			if (Time.unscaledTime < _messageUntil && _message.Length > 0)
			{
				stringBuilder.AppendLine(">> " + _message);
			}
			_hudContent.text = stringBuilder.ToString().TrimEnd('\r', '\n');
			return;
		}
		if (IncidentLog.Count > 0)
		{
			stringBuilder.AppendLine(Lang.T("■ エラー記録: ", "■ incidents: ") + IncidentLog.Count + Lang.T(" 件   BepInEx/", "   BepInEx/") + "LwfFpsBoost-incidents.log" + Lang.T("   最初: ", "   first: ") + Truncate(IncidentLog.First, 80));
		}
		stringBuilder.AppendLine(Lang.T("記録: BepInEx/", "log: BepInEx/") + "LwfFpsBoost-games.log" + Lang.T("(1 回の工場ごとに追記)", " (one line per factory run)") + ((IncidentLog.Count > 0) ? "   BepInEx/LwfFpsBoost-incidents.log" : ""));
		if (flag || flag2)
		{
			string text3 = (flag ? Lang.T("高負荷テスト", "load test") : Lang.T("マルチスレッド効果検証", "speed test"));
			int num = Mathf.CeilToInt(flag ? AbRemaining() : PerfRemaining());
			Scene activeScene = SceneManager.GetActiveScene();
			bool flag3 = !IsInGameScene(((Scene)(ref activeScene)).name);
			stringBuilder.AppendLine("■ " + text3 + Lang.T(": 実行中   残り約 ", ": running   ~") + num + Lang.T(" 秒   PC: 高負荷", " s left   CPU: heavy"));
			stringBuilder.AppendLine(string.Concat("   ", flag3 ? Lang.T("タイトル画面を移動すると中止", "leaving the title screen stops it") : Lang.T("画面を切り替えると中止", "changing scene stops it"), Lang.T("   段階: ", "   phase: "), flag ? AbStatus() : PerfStatus(), "   ", flag ? _keyAutoAB : _keyPerfAB, Lang.T(": 中止", ": stop")));
		}
		else
		{
			string text4 = "";
			if (_keyAutoAB != null)
			{
				object obj = text4;
				text4 = string.Concat(obj, _keyAutoAB, Lang.T(": 高負荷テスト(約 ", ": load test (~"), Mathf.CeilToInt(AbTotalSeconds()), Lang.T(" 秒)   ", " s)   "));
			}
			if (_keyPerfAB != null)
			{
				object obj2 = text4;
				text4 = string.Concat(obj2, _keyPerfAB, Lang.T(": マルチスレッド効果検証(約 ", ": speed test (~"), Mathf.CeilToInt(PerfTotalSeconds()), Lang.T(" 秒)   ", " s)   "));
			}
			if (_keyToggleHud != null)
			{
				text4 = string.Concat(text4, _keyToggleHud, Lang.T(": 表示 OFF", ": hide"));
			}
			stringBuilder.AppendLine(text4);
		}
		if (_abPhase == 8 && _abVerdict.Length > 0)
		{
			stringBuilder.AppendLine(Lang.T("高負荷テスト: ", "load test: ") + _abVerdict);
		}
		if (_perfPhase == 4 && _perfSummary.Length > 0)
		{
			stringBuilder.AppendLine(Lang.T("マルチスレッド効果検証: ", "speed test: ") + _perfSummary);
		}
		if (Time.unscaledTime < _messageUntil && _message.Length > 0)
		{
			stringBuilder.AppendLine(">> " + _message);
		}
		if (_detail)
		{
			double num2 = AvgMs();
			double v = LowMs();
			stringBuilder.AppendLine(string.Concat(Lang.T("---- 詳細   ", "---- detail   "), _keyToggleDetail, Lang.T(": 閉じる ----", ": close ----")));
			stringBuilder.AppendLine(Lang.T("マルチスレッド : ", "threading   : ") + (_threadingOn ? "ON" : "OFF") + "   (anim=" + RuntimeSettings.UseThreadedAnimation + " mesh=" + RuntimeSettings.UseThreadedMeshGeneration + ")");
			stringBuilder.AppendLine(Lang.T("フレーム時間   : ", "frame time  : ") + F(num2) + " ms   1%Low " + F(v) + " ms   " + F((num2 > 0.0) ? (1000.0 / num2) : 0.0) + " fps   (" + _frameMs.Count + " frames)");
			int registeredRenderers = LateUpdateGuard.RegisteredRenderers;
			int registeredAnimations = LateUpdateGuard.RegisteredAnimations;
			stringBuilder.AppendLine(Lang.T("スケルトン数   : 登録 mesh ", "skeletons   : registered mesh ") + ((registeredRenderers >= 0) ? registeredRenderers.ToString() : "?") + " / anim " + ((registeredAnimations >= 0) ? registeredAnimations.ToString() : "?") + Lang.T("   シーン全体 ", "   in scene ") + ((_skeletonCount >= 0) ? _skeletonCount.ToString() : "?"));
			stringBuilder.AppendLine(Lang.T("LateUpdate     : ", "LateUpdate  : ") + ((_waitPathPatched && LateUpdateGuard.Active) ? (Lang.T("エラー回避処理あり  checked ", "guard on   checked ") + LateUpdateGuard.FramesChecked + "   caught timeout mesh " + LateUpdateGuard.TimeoutCount + " / anim " + UpdateGuard.TimeoutCount + "   giveup " + (LateUpdateGuard.GiveUpCount + UpdateGuard.GiveUpCount)) : (Lang.T("エラー回避処理なし  (", "guard off   (") + _waitPathReport + ")")));
			stringBuilder.AppendLine(Lang.T("Unity エラー   : ", "unity errors: ") + _errorCount + Lang.T("   (Spine 由来 ", "   (Spine ") + _spineErrorCount + ")" + ((_lastError.Length > 0) ? (Lang.T("   最後: ", "   last: ") + Truncate(_lastError, 90)) : ""));
			stringBuilder.AppendLine(Lang.T("負荷           : ", "stress      : ") + _stress.Status());
			if (_abSummary.Length > 0)
			{
				stringBuilder.AppendLine(Lang.T("テスト集計     : ", "load test   : ") + _abSummary);
			}
			if (_perfSummary.Length > 0)
			{
				stringBuilder.AppendLine(Lang.T("効果検証       : ", "speed test  : ") + _perfSummary);
			}
		}
		_hudContent.text = stringBuilder.ToString().TrimEnd('\r', '\n');
	}

	private void OnGUI()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Invalid comparison between Unknown and I4
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_01be: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_022e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0244: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Expected O, but got Unknown
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Expected O, but got Unknown
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		if (_enabled.Value && (int)Event.current.type == 7 && _hudContent.text.Length != 0)
		{
			if (_style == null)
			{
				_style = new GUIStyle(GUI.skin.label);
				_style.fontSize = 16;
				_style.normal.textColor = Color.white;
				_style.alignment = (TextAnchor)0;
				_style.richText = false;
				_style.wordWrap = false;
				_shadowStyle = new GUIStyle(_style);
				_shadowStyle.normal.textColor = Color.black;
			}
			Vector2 val = _style.CalcSize(_hudContent);
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor(10f, 10f, val.x + 4f, val.y + 4f);
			Color color = GUI.color;
			GUI.color = new Color(0f, 0f, 0f, 0.65f);
			GUI.DrawTexture(new Rect(((Rect)(ref val2)).x - 8f, ((Rect)(ref val2)).y - 6f, ((Rect)(ref val2)).width + 16f, ((Rect)(ref val2)).height + 12f), (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
			GUI.Label(new Rect(((Rect)(ref val2)).x - 1f, ((Rect)(ref val2)).y, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), _hudContent, _shadowStyle);
			GUI.Label(new Rect(((Rect)(ref val2)).x + 1f, ((Rect)(ref val2)).y, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), _hudContent, _shadowStyle);
			GUI.Label(new Rect(((Rect)(ref val2)).x, ((Rect)(ref val2)).y - 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), _hudContent, _shadowStyle);
			GUI.Label(new Rect(((Rect)(ref val2)).x, ((Rect)(ref val2)).y + 1f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), _hudContent, _shadowStyle);
			GUI.Label(val2, _hudContent, _style);
		}
	}
}
[HarmonyPatch(typeof(SkeletonUpdateSystem), "LateUpdateAsync")]
internal static class LateUpdateGuard
{
	private const int WaitSliceMs = 50;

	private const int WaitMaxMs = 10000;

	internal static volatile bool Active;

	internal static int FramesChecked;

	internal static int TimeoutCount;

	internal static int GiveUpCount;

	internal static long TotalWaitMs;

	internal static int MaxWaitMs;

	private static FieldInfo _fUpdatedAtTask;

	private static FieldInfo _fProcessedAtTask;

	private static FieldInfo _fPartitions;

	private static bool _ready;

	private static SkeletonUpdateSystem _owner;

	internal static List<ISkeletonRenderer> RegisteredRendererList
	{
		get
		{
			SkeletonUpdateSystem owner = _owner;
			if (!((Object)(object)owner == (Object)null))
			{
				return owner.skeletonRenderers;
			}
			return null;
		}
	}

	internal static List<SkeletonAnimationBase> RegisteredAnimationList
	{
		get
		{
			SkeletonUpdateSystem owner = _owner;
			if (!((Object)(object)owner == (Object)null))
			{
				return owner.skeletonAnimationsUpdate;
			}
			return null;
		}
	}

	internal static int RegisteredRenderers
	{
		get
		{
			SkeletonUpdateSystem owner = _owner;
			if (!((Object)(object)owner == (Object)null))
			{
				return owner.skeletonRenderers.Count;
			}
			return -1;
		}
	}

	internal static int RegisteredAnimations
	{
		get
		{
			SkeletonUpdateSystem owner = _owner;
			if ((Object)(object)owner == (Object)null)
			{
				return -1;
			}
			return owner.skeletonAnimationsUpdate.Count + owner.skeletonAnimationsFixedUpdate.Count + owner.skeletonAnimationsLateUpdate.Count;
		}
	}

	internal static bool Prepare(out string report)
	{
		Type typeFromHandle = typeof(SkeletonUpdateSystem);
		BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
		_fUpdatedAtTask = typeFromHandle.GetField("skeletonsLateUpdatedAtTask", bindingAttr);
		_fProcessedAtTask = typeFromHandle.GetField("mainThreadProcessedAtTask", bindingAttr);
		_fPartitions = typeFromHandle.GetField("taskPartitionsLateUpdate", bindingAttr);
		MethodInfo method = typeFromHandle.GetMethod("WaitForThreadLateUpdateTasks", bindingAttr);
		if (_fUpdatedAtTask == null || method != null)
		{
			report = "spine-unity.dll is not built with DONT_WAIT_FOR_ALL_LATEUPDATE_TASKS; upstream seems fixed, guard not installed";
			_ready = false;
			return false;
		}
		if (_fProcessedAtTask == null || _fPartitions == null || _fPartitions.FieldType != typeof(ExposedList<SkeletonPartitionRange>))
		{
			report = "SkeletonUpdateSystem private members not found (mainThreadProcessedAtTask=" + (_fProcessedAtTask != null) + ", taskPartitionsLateUpdate=" + (_fPartitions != null) + ")";
			_ready = false;
			return false;
		}
		_ready = true;
		report = "guard ready (threads=" + Environment.ProcessorCount + ")";
		return true;
	}

	private static void Postfix(SkeletonUpdateSystem __instance)
	{
		if (!Active || !_ready)
		{
			return;
		}
		try
		{
			Run(__instance);
		}
		catch (Exception ex)
		{
			Active = false;
			Debug.LogError((object)("[LWF FPS Boost] guard threw, disabling it for this session: " + ex));
		}
	}

	private static void Run(SkeletonUpdateSystem inst)
	{
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		_owner = inst;
		List<ISkeletonRenderer> skeletonRenderers = inst.skeletonRenderers;
		if (skeletonRenderers.Count == 0)
		{
			return;
		}
		int[] array = _fUpdatedAtTask.GetValue(inst) as int[];
		int[] array2 = _fProcessedAtTask.GetValue(inst) as int[];
		ExposedList<SkeletonPartitionRange> val = _fPartitions.GetValue(inst) as ExposedList<SkeletonPartitionRange>;
		if (array == null || array2 == null || val == null)
		{
			return;
		}
		FramesChecked++;
		int num = Math.Min(val.Count, Math.Min(array.Length, array2.Length));
		SkeletonPartitionRange[] items = val.Items;
		if (!AnyUnfinished(items, array, num))
		{
			return;
		}
		TimeoutCount++;
		AutoResetEvent lateUpdateWorkAvailable = inst.lateUpdateWorkAvailable;
		int num2 = 0;
		while (AnyUnfinished(items, array, num) && num2 < 10000)
		{
			if (lateUpdateWorkAvailable != null)
			{
				lateUpdateWorkAvailable.WaitOne(50);
			}
			else
			{
				Thread.Sleep(50);
			}
			num2 += 50;
		}
		if (AnyUnfinished(items, array, num))
		{
			GiveUpCount++;
			IncidentLog.Note("guard: LateUpdate workers still busy after 10 s, gave up");
			Debug.LogError((object)("[LWF FPS Boost] LateUpdate workers still busy after " + 10 + " s, giving up"));
			return;
		}
		TotalWaitMs += num2;
		if (num2 > MaxWaitMs)
		{
			MaxWaitMs = num2;
		}
		IncidentLog.Note("guard: LateUpdate abandoned, waited " + num2 + " ms");
		Debug.LogWarning((object)("[LWF FPS Boost] LateUpdate abandoned its workers; waited ~" + num2 + " ms for them"));
		int count = skeletonRenderers.Count;
		for (int i = 0; i < num; i++)
		{
			SkeletonPartitionRange val2 = items[i];
			int num3 = val2.rangeStart + Math.Max(0, array2[i]);
			for (int j = num3; j < val2.rangeEndExclusive && j < count; j++)
			{
				ISkeletonRenderer val3 = skeletonRenderers[j];
				if (val3 != null && ((IThreadedRenderer)val3).RequiresMeshBufferAssignmentMainThread)
				{
					((IThreadedRenderer)val3).UpdateMeshAndMaterialsToBuffers();
				}
			}
			array2[i] = val2.rangeEndExclusive - val2.rangeStart;
		}
	}

	private static bool AnyUnfinished(SkeletonPartitionRange[] items, int[] updated, int numTasks)
	{
		for (int i = 0; i < numTasks; i++)
		{
			int num = items[i].rangeEndExclusive - items[i].rangeStart;
			if (num > 0 && Volatile.Read(in updated[i]) < num)
			{
				return true;
			}
		}
		return false;
	}
}
[HarmonyPatch(typeof(SkeletonUpdateSystem), "WaitForThreadUpdateTasks")]
internal static class UpdateGuard
{
	private const int WaitSliceMs = 50;

	private const int WaitMaxMs = 10000;

	internal static int TimeoutCount;

	internal static int GiveUpCount;

	internal static long TotalWaitMs;

	internal static int MaxWaitMs;

	private static bool _ready;

	internal static bool Prepare(out string report)
	{
		Type typeFromHandle = typeof(SkeletonUpdateSystem);
		BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
		MethodInfo method = typeFromHandle.GetMethod("WaitForThreadUpdateTasks", bindingAttr);
		FieldInfo field = typeFromHandle.GetField("updateDone", bindingAttr);
		if (method == null || field == null || field.FieldType != typeof(List<ManualResetEventSlim>))
		{
			report = "SkeletonUpdateSystem.WaitForThreadUpdateTasks / updateDone not found";
			_ready = false;
			return false;
		}
		_ready = true;
		report = "update guard ready";
		return true;
	}

	private static void Postfix(SkeletonUpdateSystem __instance, int numAsyncTasks)
	{
		if (!LateUpdateGuard.Active || !_ready)
		{
			return;
		}
		try
		{
			List<ManualResetEventSlim> updateDone = __instance.updateDone;
			int num = Math.Min(numAsyncTasks, updateDone.Count);
			bool flag = false;
			for (int i = 0; i < num; i++)
			{
				if (!updateDone[i].IsSet)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return;
			}
			TimeoutCount++;
			int num2 = 0;
			for (int j = 0; j < num; j++)
			{
				while (!updateDone[j].IsSet && num2 < 10000)
				{
					updateDone[j].Wait(50);
					num2 += 50;
				}
			}
			bool flag2 = false;
			for (int k = 0; k < num; k++)
			{
				if (!updateDone[k].IsSet)
				{
					flag2 = true;
					break;
				}
			}
			if (flag2)
			{
				GiveUpCount++;
				IncidentLog.Note("guard: Update workers still busy after 10 s, gave up");
				Debug.LogError((object)("[LWF FPS Boost] Update workers still busy after " + 10 + " s, giving up"));
				return;
			}
			TotalWaitMs += num2;
			if (num2 > MaxWaitMs)
			{
				MaxWaitMs = num2;
			}
			IncidentLog.Note("guard: Update abandoned, waited " + num2 + " ms");
			Debug.LogWarning((object)("[LWF FPS Boost] Update abandoned its workers; waited ~" + num2 + " ms for them"));
		}
		catch (Exception ex)
		{
			LateUpdateGuard.Active = false;
			Debug.LogError((object)("[LWF FPS Boost] update guard threw, disabling guards for this session: " + ex));
		}
	}
}
internal static class IncidentLog
{
	internal const string FileName = "LwfFpsBoost-incidents.log";

	private const int ContextLines = 40;

	private const long RotateBytes = 2097152L;

	private const int SameMessageReports = 3;

	private const int SessionReports = 200;

	internal static int Count;

	internal static int Suppressed;

	internal static volatile string First = "";

	internal static volatile string SceneName = "";

	internal static volatile bool ThreadingOn;

	internal static volatile bool GuardOn;

	internal static volatile string TestPhase = "";

	private static readonly object Lock = new object();

	private static readonly Queue<string> Context = new Queue<string>(41);

	private static readonly Dictionary<string, int> Written = new Dictionary<string, int>();

	private static readonly Dictionary<string, int> NotedUpstream = new Dictionary<string, int>();

	private static int _writtenTotal;

	private static ManualLogSource _log;

	private static string _path = "";

	private static string _gameVersion = "";

	private static string _modVersion = "";

	internal static string Path => _path;

	internal static void Init(ManualLogSource log, string modVersion)
	{
		_log = log;
		_modVersion = modVersion;
		try
		{
			_gameVersion = Application.version;
		}
		catch (Exception)
		{
			_gameVersion = "?";
		}
		try
		{
			_path = System.IO.Path.Combine(Paths.BepInExRootPath, "LwfFpsBoost-incidents.log");
		}
		catch (Exception)
		{
			_path = "LwfFpsBoost-incidents.log";
		}
		try
		{
			if (File.Exists(_path))
			{
				Count = 0;
			}
		}
		catch (Exception)
		{
		}
	}

	internal static void Note(string line)
	{
		string item = Stamp() + " " + line;
		lock (Lock)
		{
			Context.Enqueue(item);
			while (Context.Count > 40)
			{
				Context.Dequeue();
			}
		}
	}

	internal static void OnUnityLog(string condition, string stackTrace, LogType type)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: Invalid comparison between Unknown and I4
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Invalid comparison between Unknown and I4
		//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
		if ((int)type != 0 && (int)type != 4 && (int)type != 1)
		{
			return;
		}
		string text = condition ?? "";
		string text2 = stackTrace ?? "";
		if (text.IndexOf("Internal threading logic error", StringComparison.Ordinal) >= 0 || text.IndexOf("ran into a timeout", StringComparison.Ordinal) >= 0)
		{
			string text3 = FirstLine(text);
			int value;
			lock (Lock)
			{
				NotedUpstream.TryGetValue(text3, out value);
				NotedUpstream[text3] = value + 1;
			}
			if (value < 3)
			{
				Note("upstream: " + text3);
			}
		}
		else if (text.IndexOf("Spine", StringComparison.Ordinal) >= 0 || text2.IndexOf("Spine.", StringComparison.Ordinal) >= 0 || text.IndexOf("updateDone", StringComparison.Ordinal) >= 0 || text.IndexOf("lateUpdateDone", StringComparison.Ordinal) >= 0)
		{
			Record(((object)type).ToString(), text, text2);
		}
	}

	private static void Record(string kind, string condition, string stackTrace)
	{
		string text = FirstLine(condition);
		bool flag;
		bool flag2;
		lock (Lock)
		{
			Count++;
			if (First.Length == 0)
			{
				First = text;
			}
			Written.TryGetValue(text, out var value);
			flag = value < 3 && _writtenTotal < 200;
			flag2 = flag && value == 2;
			if (flag)
			{
				Written[text] = value + 1;
				_writtenTotal++;
			}
			else
			{
				Suppressed++;
			}
		}
		if (!flag)
		{
			return;
		}
		StringBuilder stringBuilder = new StringBuilder(2048);
		stringBuilder.AppendLine("==== " + Stamp() + "  " + kind + "  " + text);
		stringBuilder.AppendLine("game=" + _gameVersion + "  mod=" + _modVersion + "  threads=" + Environment.ProcessorCount + "  scene=" + SceneName + "  threading=" + (ThreadingOn ? "on" : "off") + "  guard=" + (GuardOn ? "on" : "off") + "  test=" + ((TestPhase.Length > 0) ? TestPhase : "none"));
		stringBuilder.AppendLine("guard: lateUpdate waits=" + LateUpdateGuard.TimeoutCount + " giveups=" + LateUpdateGuard.GiveUpCount + "  update waits=" + UpdateGuard.TimeoutCount + " giveups=" + UpdateGuard.GiveUpCount + "  registered mesh=" + LateUpdateGuard.RegisteredRenderers + " anim=" + LateUpdateGuard.RegisteredAnimations);
		stringBuilder.AppendLine("-- context (oldest first)");
		lock (Lock)
		{
			foreach (string item in Context)
			{
				stringBuilder.AppendLine(item);
			}
		}
		stringBuilder.AppendLine("-- message");
		stringBuilder.AppendLine(condition);
		if (!string.IsNullOrEmpty(stackTrace))
		{
			stringBuilder.AppendLine("-- stack");
			stringBuilder.AppendLine(stackTrace.TrimEnd());
		}
		stringBuilder.AppendLine();
		if (flag2)
		{
			stringBuilder.AppendLine("-- note");
			stringBuilder.AppendLine("last full report for this message; further ones are counted only (see the totals at the end of the game)");
			stringBuilder.AppendLine();
		}
		lock (Lock)
		{
			try
			{
				RotateIfLarge();
				File.AppendAllText(_path, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			}
			catch (Exception ex)
			{
				if (_log != null)
				{
					_log.LogWarning((object)("[incident] write failed: " + ex.Message));
				}
			}
		}
		if (_log != null)
		{
			_log.LogError((object)("[incident] " + kind + ": " + text + "  -> LwfFpsBoost-incidents.log" + (flag2 ? "  (last full report for this message)" : "")));
		}
		Note("incident: " + text);
	}

	private static void RotateIfLarge()
	{
		FileInfo fileInfo = new FileInfo(_path);
		if (fileInfo.Exists && fileInfo.Length >= 2097152)
		{
			string text = _path + ".1";
			if (File.Exists(text))
			{
				File.Delete(text);
			}
			File.Move(_path, text);
		}
	}

	private static string Stamp()
	{
		return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
	}

	private static string FirstLine(string s)
	{
		int num = s.IndexOf('\n');
		string text = ((num >= 0) ? s.Substring(0, num) : s);
		if (text.Length <= 160)
		{
			return text;
		}
		return text.Substring(0, 160) + "…";
	}
}
internal static class GameReport
{
	private sealed class Run
	{
		public DateTime start;

		public float startedAt;

		public string mode = "";

		public string runId = "";

		public long frames;

		public double sumMs;

		public float maxMs;

		public long over33;

		public long over100;

		public int maxMesh;

		public int maxAnim;

		public double threadingOffSeconds;

		public int base_lateWaits;

		public int base_updWaits;

		public int base_giveups;

		public int base_incidents;

		public long base_lateWaitMs;

		public long base_updWaitMs;

		public int base_logic;

		public int base_index;

		public int base_null;

		public int base_unity;

		public int base_spine;

		public List<string> events = new List<string>();
	}

	internal const string FileName = "LwfFpsBoost-games.log";

	internal const string CurrentFileName = "LwfFpsBoost-game-current.txt";

	private const float SnapshotIntervalSec = 60f;

	private const float PollIntervalSec = 0.5f;

	private const long RotateBytes = 2097152L;

	private static ManualLogSource _log;

	private static string _path = "";

	private static string _currentPath = "";

	private static string _system = "";

	private static string _versions = "";

	private static bool _ready;

	private static PropertyInfo _pHasActiveRun;

	private static FieldInfo _fAcceptedEnd;

	private static PropertyInfo _pEndReason;

	private static FieldInfo _fHost;

	private static PropertyInfo _pGameMode;

	private static bool _probeOk;

	private static string _probeReport = "";

	private static string _scene = "";

	private static Run _run;

	private static bool _active;

	private static float _nextPoll;

	private static float _nextSnapshot;

	private static int _logic;

	private static int _index;

	private static int _null;

	private static int _unity;

	private static int _spine;

	internal static string Path => _path;

	internal static bool ProbeOk => _probeOk;

	internal static string ProbeReport => _probeReport;

	internal static void Init(ManualLogSource log, string modVersion, string sceneName)
	{
		_log = log;
		try
		{
			_path = System.IO.Path.Combine(Paths.BepInExRootPath, "LwfFpsBoost-games.log");
			_currentPath = System.IO.Path.Combine(Paths.BepInExRootPath, "LwfFpsBoost-game-current.txt");
		}
		catch (Exception)
		{
			_path = "LwfFpsBoost-games.log";
			_currentPath = "LwfFpsBoost-game-current.txt";
		}
		try
		{
			_versions = "game=" + Application.version + "  mod=" + modVersion + "  unity=" + Application.unityVersion;
			_system = "os=\"" + SystemInfo.operatingSystem + "\"  cpu=\"" + SystemInfo.processorType.Trim() + "\"  threads=" + SystemInfo.processorCount + "  ram=" + SystemInfo.systemMemorySize / 1024 + " GB  gpu=\"" + SystemInfo.graphicsDeviceName + "\"";
		}
		catch (Exception ex2)
		{
			_system = "system=unavailable (" + ex2.Message + ")";
		}
		_scene = sceneName ?? "";
		Probe();
		_ready = true;
		_nextPoll = 0f;
	}

	private static void Probe()
	{
		try
		{
			Assembly assembly = null;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				if (assemblies[i].GetName().Name == "Assembly-CSharp")
				{
					assembly = assemblies[i];
					break;
				}
			}
			if (assembly == null)
			{
				_probeReport = "Assembly-CSharp not loaded";
				return;
			}
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
			Type type = assembly.GetType("RunHistory.RunRecordingRuntime");
			Type type2 = assembly.GetType("RunHistory.GameEndContext");
			if (type == null || type2 == null)
			{
				_probeReport = "RunHistory types not found";
				return;
			}
			_pHasActiveRun = type.GetProperty("HasActiveRun", bindingAttr);
			_fAcceptedEnd = type.GetField("_acceptedEndContext", bindingAttr);
			_fHost = type.GetField("_host", bindingAttr);
			_pEndReason = type2.GetProperty("Reason", bindingAttr);
			Type[] types = assembly.GetTypes();
			for (int j = 0; j < types.Length; j++)
			{
				if (types[j].Name == "CurrentGameMode")
				{
					_pGameMode = types[j].GetProperty("Get", bindingAttr);
					break;
				}
			}
			if (_pHasActiveRun == null || _pHasActiveRun.PropertyType != typeof(bool))
			{
				_probeReport = "RunRecordingRuntime.HasActiveRun not found";
				return;
			}
			_probeOk = true;
			_probeReport = "RunRecordingRuntime.HasActiveRun" + ((_fAcceptedEnd != null && _pEndReason != null) ? " + end reason" : "") + ((_fHost != null) ? " + run id" : "") + ((_pGameMode != null) ? " + game mode" : "");
		}
		catch (Exception ex)
		{
			_probeOk = false;
			_probeReport = "probe failed: " + ex.Message;
		}
	}

	private static bool ReadActive()
	{
		if (_probeOk)
		{
			try
			{
				return (bool)_pHasActiveRun.GetValue(null, null);
			}
			catch (Exception)
			{
				_probeOk = false;
			}
		}
		return _scene == "InGame";
	}

	private static string ReadEndReason()
	{
		if (!_probeOk || _fAcceptedEnd == null || _pEndReason == null)
		{
			if (!_probeOk)
			{
				return "scene left";
			}
			return "unknown";
		}
		try
		{
			object value = _fAcceptedEnd.GetValue(null);
			if (value == null)
			{
				return "unknown";
			}
			object value2 = _pEndReason.GetValue(value, null);
			return (value2 == null) ? "unknown" : value2.ToString();
		}
		catch (Exception)
		{
			return "unknown";
		}
	}

	private static string ReadRunId()
	{
		if (!_probeOk || _fHost == null)
		{
			return "";
		}
		try
		{
			object value = _fHost.GetValue(null);
			if (value == null)
			{
				return "";
			}
			FieldInfo field = value.GetType().GetField("_service", BindingFlags.Instance | BindingFlags.NonPublic);
			object obj = ((field == null) ? null : field.GetValue(value));
			if (obj == null)
			{
				return "";
			}
			FieldInfo field2 = obj.GetType().GetField("_activeRunId", BindingFlags.Instance | BindingFlags.NonPublic);
			object obj2 = ((field2 == null) ? null : field2.GetValue(obj));
			return (obj2 == null) ? "" : obj2.ToString();
		}
		catch (Exception)
		{
			return "";
		}
	}

	private static string ReadGameMode()
	{
		if (_pGameMode == null)
		{
			return "";
		}
		try
		{
			object value = _pGameMode.GetValue(null, null);
			return (value == null) ? "" : value.ToString();
		}
		catch (Exception)
		{
			return "";
		}
	}

	internal static void Scene(string name)
	{
		_scene = name ?? "";
	}

	internal static void Counters(int logic, int index, int nul, int unity, int spine)
	{
		_logic = logic;
		_index = index;
		_null = nul;
		_unity = unity;
		_spine = spine;
	}

	internal static void Frame(float ms, float dt, bool threadingOn, int registeredMesh, int registeredAnim)
	{
		if (!_ready)
		{
			return;
		}
		Run run = _run;
		if (run != null)
		{
			run.frames++;
			run.sumMs += ms;
			if (ms > run.maxMs)
			{
				run.maxMs = ms;
			}
			if (ms > 33f)
			{
				run.over33++;
			}
			if (ms > 100f)
			{
				run.over100++;
			}
			if (!threadingOn)
			{
				run.threadingOffSeconds += dt;
			}
			if (registeredMesh > run.maxMesh)
			{
				run.maxMesh = registeredMesh;
			}
			if (registeredAnim > run.maxAnim)
			{
				run.maxAnim = registeredAnim;
			}
		}
		if (Time.unscaledTime >= _nextPoll)
		{
			_nextPoll = Time.unscaledTime + 0.5f;
			bool flag = ReadActive();
			if (flag && !_active)
			{
				Begin();
			}
			else if (!flag && _active)
			{
				End(ReadEndReason(), "ended");
			}
			_active = flag;
		}
		if (_run != null && Time.unscaledTime >= _nextSnapshot)
		{
			_nextSnapshot = Time.unscaledTime + 60f;
			WriteCurrent(BuildGame(_run, "running", "running"));
		}
	}

	internal static void Event(string line)
	{
		Run run = _run;
		if (run != null)
		{
			run.events.Add(DateTime.Now.ToString("HH:mm:ss", CultureInfo.InvariantCulture) + "  " + line);
			if (run.events.Count > 10)
			{
				run.events.RemoveAt(0);
			}
		}
	}

	internal static void TestBlock(string name, string verdict, string detail)
	{
		if (_ready)
		{
			StringBuilder stringBuilder = new StringBuilder(512);
			stringBuilder.AppendLine("==== test " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + "  " + name + "  " + verdict);
			stringBuilder.AppendLine(_versions);
			stringBuilder.AppendLine(_system);
			stringBuilder.AppendLine("threading=" + (IncidentLog.ThreadingOn ? "on" : "off") + "  guard=" + (IncidentLog.GuardOn ? "on" : "off") + "  scene=" + _scene);
			if (!string.IsNullOrEmpty(detail))
			{
				stringBuilder.AppendLine("detail: " + detail);
			}
			stringBuilder.AppendLine();
			Append(stringBuilder.ToString());
		}
	}

	internal static void Finish()
	{
		if (!_ready)
		{
			return;
		}
		if (_run != null)
		{
			End(_probeOk ? ReadEndReason() : "ApplicationQuit", "quit");
		}
		try
		{
			if (File.Exists(_currentPath))
			{
				File.Delete(_currentPath);
			}
		}
		catch (Exception)
		{
		}
		_ready = false;
	}

	private static void Begin()
	{
		Run run = new Run();
		run.start = DateTime.Now;
		run.startedAt = Time.unscaledTime;
		run.mode = ReadGameMode();
		run.runId = ReadRunId();
		run.base_lateWaits = LateUpdateGuard.TimeoutCount;
		run.base_lateWaitMs = LateUpdateGuard.TotalWaitMs;
		run.base_updWaits = UpdateGuard.TimeoutCount;
		run.base_updWaitMs = UpdateGuard.TotalWaitMs;
		run.base_giveups = LateUpdateGuard.GiveUpCount + UpdateGuard.GiveUpCount;
		run.base_incidents = IncidentLog.Count;
		run.base_logic = _logic;
		run.base_index = _index;
		run.base_null = _null;
		run.base_unity = _unity;
		run.base_spine = _spine;
		_run = run;
		_nextSnapshot = Time.unscaledTime + 60f;
		IncidentLog.Note("game: start" + ((run.runId.Length > 0) ? (" run=" + run.runId) : "") + ((run.mode.Length > 0) ? (" mode=" + run.mode) : ""));
		if (_log != null)
		{
			_log.LogInfo((object)("[game] start" + ((run.runId.Length > 0) ? (" run=" + run.runId) : "") + ((run.mode.Length > 0) ? (" mode=" + run.mode) : "")));
		}
	}

	private static void End(string reason, string status)
	{
		Run run = _run;
		if (run == null)
		{
			return;
		}
		if (run.runId.Length == 0)
		{
			run.runId = ReadRunId();
		}
		_run = null;
		Append(BuildGame(run, reason, status));
		try
		{
			if (File.Exists(_currentPath))
			{
				File.Delete(_currentPath);
			}
		}
		catch (Exception)
		{
		}
		IncidentLog.Note("game: end (" + reason + ")");
		if (_log != null)
		{
			_log.LogInfo((object)("[game] end (" + reason + "), " + run.frames + " frames -> LwfFpsBoost-games.log"));
		}
	}

	private static string BuildGame(Run r, string reason, string status)
	{
		DateTime now = DateTime.Now;
		double v = (double)(Time.unscaledTime - r.startedAt) / 60.0;
		double v2 = ((r.frames > 0) ? (r.sumMs / (double)r.frames) : 0.0);
		int num = LateUpdateGuard.TimeoutCount - r.base_lateWaits;
		int num2 = UpdateGuard.TimeoutCount - r.base_updWaits;
		long num3 = LateUpdateGuard.TotalWaitMs - r.base_lateWaitMs;
		long num4 = UpdateGuard.TotalWaitMs - r.base_updWaitMs;
		int num5 = LateUpdateGuard.GiveUpCount + UpdateGuard.GiveUpCount - r.base_giveups;
		StringBuilder stringBuilder = new StringBuilder(1024);
		stringBuilder.AppendLine("==== game " + r.start.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + " -> " + now.ToString("HH:mm:ss", CultureInfo.InvariantCulture) + "  (" + F(v) + " min)  end=" + reason + ((r.mode.Length > 0) ? ("  mode=" + r.mode) : "") + ((r.runId.Length > 0) ? ("  run=" + r.runId) : "") + ((status != "ended") ? ("  status=" + status) : ""));
		stringBuilder.AppendLine(_versions);
		stringBuilder.AppendLine(_system);
		stringBuilder.AppendLine("threading=" + (IncidentLog.ThreadingOn ? "on" : "off") + "  guard=" + (IncidentLog.GuardOn ? "on" : "off") + "  threading_off_time=" + F(r.threadingOffSeconds) + " s");
		stringBuilder.AppendLine("frames=" + r.frames + "  avg=" + F(v2) + " ms  max=" + F(r.maxMs) + " ms  over33ms=" + r.over33 + " (" + F((r.frames > 0) ? (100.0 * (double)r.over33 / (double)r.frames) : 0.0) + "%)  over100ms=" + r.over100);
		stringBuilder.AppendLine("skeletons_max: mesh=" + r.maxMesh + " anim=" + r.maxAnim);
		stringBuilder.AppendLine("guard: lateUpdate waits=" + num + " (total " + num3 + " ms)  update waits=" + num2 + " (total " + num4 + " ms)  giveups=" + num5);
		stringBuilder.AppendLine("errors: upstream_timeouts=" + (_logic - r.base_logic) + "  incidents=" + (IncidentLog.Count - r.base_incidents) + " (out_of_range=" + (_index - r.base_index) + ", null=" + (_null - r.base_null) + ")  unity_errors=" + (_unity - r.base_unity) + "  spine_errors=" + (_spine - r.base_spine) + ((IncidentLog.Suppressed > 0) ? ("  repeated=" + IncidentLog.Suppressed + " (counted only)") : ""));
		if (r.events.Count > 0)
		{
			stringBuilder.AppendLine("events:");
			for (int i = 0; i < r.events.Count; i++)
			{
				stringBuilder.AppendLine("  " + r.events[i]);
			}
		}
		stringBuilder.AppendLine();
		return stringBuilder.ToString();
	}

	private static void Append(string block)
	{
		try
		{
			FileInfo fileInfo = new FileInfo(_path);
			if (fileInfo.Exists && fileInfo.Length >= 2097152)
			{
				string text = _path + ".1";
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				File.Move(_path, text);
			}
			File.AppendAllText(_path, block, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		}
		catch (Exception ex)
		{
			if (_log != null)
			{
				_log.LogWarning((object)("[game] write failed: " + ex.Message));
			}
		}
	}

	private static void WriteCurrent(string block)
	{
		try
		{
			File.WriteAllText(_currentPath, block, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		}
		catch (Exception)
		{
		}
	}

	private static string F(double v)
	{
		return v.ToString("0.#", CultureInfo.InvariantCulture);
	}
}
[HarmonyPatch(typeof(SkeletonRenderer), "LateUpdateImplementation")]
internal static class WorkerStallPatch
{
	[ThreadStatic]
	private static int _slowCallsLeft;

	[HarmonyPrefix]
	private static void Prefix(SkeletonRenderer __instance, bool calledFromMainThread)
	{
		if (!StressTools.StallOn || calledFromMainThread)
		{
			return;
		}
		if (_slowCallsLeft > 0)
		{
			_slowCallsLeft--;
			Thread.Sleep(StressTools.StallSlowMs);
		}
		else if (StressTools.StallArmed == 1)
		{
			SkeletonRenderer stallTarget = StressTools.StallTarget;
			if ((!((Object)(object)stallTarget != (Object)null) || object.ReferenceEquals(__instance, stallTarget)) && Interlocked.CompareExchange(ref StressTools.StallArmed, 0, 1) == 1)
			{
				Interlocked.Increment(ref StressTools.StallFired);
				Thread.Sleep(StressTools.StallMs);
				_slowCallsLeft = StressTools.StallSlowCalls;
			}
		}
	}
}
[HarmonyPatch(typeof(SkeletonAnimationBase), "UpdateInternalSplit")]
internal static class AnimStallPatch
{
	[ThreadStatic]
	private static int _slowCallsLeft;

	[HarmonyPrefix]
	private static void Prefix(SkeletonAnimationBase __instance)
	{
		if (!StressTools.StallOn || Thread.CurrentThread.ManagedThreadId == StressTools.MainThreadId)
		{
			return;
		}
		if (_slowCallsLeft > 0)
		{
			_slowCallsLeft--;
			Thread.Sleep(StressTools.AnimSlowMs);
		}
		else if (StressTools.AnimStallArmed == 1)
		{
			SkeletonAnimationBase animStallTarget = StressTools.AnimStallTarget;
			if ((!((Object)(object)animStallTarget != (Object)null) || object.ReferenceEquals(__instance, animStallTarget)) && Interlocked.CompareExchange(ref StressTools.AnimStallArmed, 0, 1) == 1)
			{
				Interlocked.Increment(ref StressTools.StallFired);
				Interlocked.Increment(ref StressTools.AnimStallFired);
				Thread.Sleep(StressTools.StallMs);
				_slowCallsLeft = StressTools.AnimSlowCalls;
			}
		}
	}
}
internal sealed class StressTools
{
	internal static int MainThreadId;

	internal static int AnimStallArmed;

	internal static int AnimStallFired;

	internal static volatile SkeletonAnimationBase AnimStallTarget;

	internal static int AnimSlowMs = 10;

	internal static int AnimSlowCalls = 30;

	internal int HammerExceptions;

	private bool _nextIsAnim;

	private float _hammerUntil;

	private readonly List<SkeletonAnimationBase> _hammer = new List<SkeletonAnimationBase>(32);

	internal static volatile bool StallOn;

	internal static int StallArmed;

	internal static int StallFired;

	internal static int StallMs = 1200;

	internal static volatile SkeletonRenderer StallTarget;

	internal static int StallSlowMs = 3;

	internal static int StallSlowCalls = 16;

	internal bool StallPeriodic = true;

	internal int StallEveryFrames = 60;

	internal int StallBackoff = 12;

	internal float StallSeconds = 60f;

	private float _stallUntil;

	private int _stallFrame;

	private readonly ManualLogSource _log;

	private readonly Random _rng = new Random(12345);

	internal bool ChurnOn;

	internal int ChurnAlive = 300;

	internal int ChurnPerFrame = 15;

	internal float ChurnSpread = 12f;

	internal int Created;

	internal int Destroyed;

	private GameObject _root;

	private SkeletonDataAsset _asset;

	private MeshRenderer _templateRenderer;

	private Vector3 _templateScale = Vector3.one;

	private float _templateZ;

	private bool _builtin;

	private Camera _cam;

	private readonly List<string> _animNames = new List<string>();

	private readonly List<GameObject> _alive = new List<GameObject>(512);

	private readonly List<GameObject> _reactivate = new List<GameObject>(32);

	internal volatile bool HogOn;

	internal int HogThreads;

	internal float HogSeconds = 15f;

	private float _hogUntil;

	private int _hogCount;

	private static long _hogSink;

	internal bool UsingBuiltin => _builtin;

	internal StressTools(ManualLogSource log)
	{
		//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)
		_log = log;
	}

	internal string ToggleChurn()
	{
		//IL_0114: Unknown result type (might be due to invalid IL or missing references)
		//IL_0119: Unknown result type (might be due to invalid IL or missing references)
		//IL_0151: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_019c: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0207: Unknown result type (might be due to invalid IL or missing references)
		//IL_0211: Expected O, but got Unknown
		if (ChurnOn)
		{
			StopChurn();
			return "churn: OFF   created " + Created + " / destroyed " + Destroyed;
		}
		SkeletonAnimation val = null;
		int num = -1;
		SkeletonAnimation[] array = Object.FindObjectsByType<SkeletonAnimation>((FindObjectsInactive)0, (FindObjectsSortMode)0);
		foreach (SkeletonAnimation val2 in array)
		{
			if (!((Object)(object)val2 == (Object)null) && !((Object)(object)((SkeletonAnimationBase)val2).SkeletonDataAsset == (Object)null) && ((SkeletonAnimationBase)val2).Skeleton != null)
			{
				SkeletonData data = ((SkeletonAnimationBase)val2).Skeleton.Data;
				int num2 = ((data != null) ? (data.Animations.Count * 1000 + data.Bones.Count) : 0);
				if (num2 > num)
				{
					num = num2;
					val = val2;
				}
			}
		}
		if ((Object)(object)val == (Object)null || (Object)(object)((SkeletonAnimationBase)val).SkeletonDataAsset == (Object)null)
		{
			_asset = BuiltinSkeleton.Get(_log);
			if ((Object)(object)_asset == (Object)null)
			{
				return "churn: cannot run   no skeleton available";
			}
			_templateRenderer = null;
			_templateScale = Vector3.one;
			_templateZ = 0f;
			_builtin = true;
		}
		else
		{
			_asset = ((SkeletonAnimationBase)val).SkeletonDataAsset;
			_templateRenderer = ((Component)val).GetComponent<MeshRenderer>();
			_templateScale = ((Component)val).transform.lossyScale;
			_templateZ = ((Component)val).transform.position.z;
			_builtin = false;
		}
		_animNames.Clear();
		SkeletonData skeletonData = _asset.GetSkeletonData(true);
		if (skeletonData != null)
		{
			Enumerator<Animation> enumerator = skeletonData.Animations.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					Animation current = enumerator.Current;
					_animNames.Add(current.Name);
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}
		if (_animNames.Count == 0)
		{
			return "churn: cannot run   " + ((Object)_asset).name + " has no animation";
		}
		_root = new GameObject("LwfSpineStress");
		ChurnOn = true;
		string text = (_builtin ? "built-in" : ((Object)_asset).name);
		_log.LogInfo((object)("[stress] churn on: template=" + (_builtin ? "builtin" : ((Object)_asset).name) + ", anims=" + _animNames.Count + ", alive=" + ChurnAlive + ", perFrame=" + ChurnPerFrame));
		return "churn: ON   " + text + "   alive " + ChurnAlive + "   per frame " + ChurnPerFrame;
	}

	internal void StopChurn()
	{
		ChurnOn = false;
		_alive.Clear();
		_reactivate.Clear();
		if ((Object)(object)_root != (Object)null)
		{
			Object.Destroy((Object)(object)_root);
			_root = null;
		}
	}

	internal string ToggleStall()
	{
		if (StallOn)
		{
			StopStall();
			return "stall: OFF   fired " + StallFired;
		}
		StallOn = true;
		_stallUntil = Time.unscaledTime + StallSeconds;
		_stallFrame = 0;
		StallArmed = 0;
		_log.LogInfo((object)("[stress] stall on: every " + StallEveryFrames + " frames sleep one worker task " + StallMs + " ms, auto-off in " + StallSeconds + " s"));
		return "stall: ON   every " + StallEveryFrames + " frames " + StallMs + " ms   stops in " + StallSeconds + " s";
	}

	internal void StopStall()
	{
		StallOn = false;
		StallArmed = 0;
		StallTarget = null;
		AnimStallArmed = 0;
		AnimStallTarget = null;
		_hammer.Clear();
		_nextIsAnim = false;
	}

	internal void FireStallOnce()
	{
		if (_nextIsAnim && FireAnimStallOnce())
		{
			_nextIsAnim = false;
			return;
		}
		_nextIsAnim = true;
		FireMeshStallOnce();
	}

	internal void FireMeshStallOnce()
	{
		StallTarget = PickStallTarget();
		StallArmed = 1;
		IncidentLog.Note("stress: mesh stall armed");
	}

	internal bool FireAnimStallOnce()
	{
		List<SkeletonAnimationBase> registeredAnimationList = LateUpdateGuard.RegisteredAnimationList;
		if (registeredAnimationList == null || registeredAnimationList.Count == 0)
		{
			return false;
		}
		int num = registeredAnimationList.Count - 1 - Math.Max(0, StallBackoff);
		if (num < 0)
		{
			num = 0;
		}
		AnimStallTarget = registeredAnimationList[num];
		_hammer.Clear();
		for (int i = num; i < registeredAnimationList.Count; i++)
		{
			_hammer.Add(registeredAnimationList[i]);
		}
		_hammerUntil = Time.unscaledTime + (float)StallMs / 1000f + 0.8f;
		AnimStallArmed = 1;
		IncidentLog.Note("stress: anim stall armed (" + _hammer.Count + " targets)");
		return true;
	}

	private void HammerTick()
	{
		if (_hammer.Count == 0 || Time.unscaledTime >= _hammerUntil)
		{
			_hammer.Clear();
			return;
		}
		for (int i = 0; i < _hammer.Count; i++)
		{
			SkeletonAnimationBase obj = _hammer[i];
			SkeletonAnimation val = (SkeletonAnimation)(object)((obj is SkeletonAnimation) ? obj : null);
			if (!((Object)(object)val == (Object)null) && val.AnimationState != null)
			{
				try
				{
					val.AnimationState.SetAnimation(0, _animNames[_rng.Next(_animNames.Count)], true);
					val.AnimationState.AddEmptyAnimation(0, 0.1f, 0f);
					val.AnimationState.SetEmptyAnimation(1, 0.05f);
					val.AnimationState.ClearTrack(1);
				}
				catch (Exception ex)
				{
					HammerExceptions++;
					Debug.LogError((object)("[LWF FPS Boost] Spine AnimationState threw on main thread: " + ex.GetType().Name + ": " + ex.Message));
				}
			}
		}
	}

	private SkeletonRenderer PickStallTarget()
	{
		List<ISkeletonRenderer> registeredRendererList = LateUpdateGuard.RegisteredRendererList;
		if (registeredRendererList == null || registeredRendererList.Count == 0)
		{
			return null;
		}
		int count = registeredRendererList.Count;
		int num = count - 1 - Math.Max(0, StallBackoff);
		if (num < 0)
		{
			num = 0;
		}
		for (int num2 = num; num2 >= 0; num2--)
		{
			ISkeletonRenderer obj = registeredRendererList[num2];
			SkeletonRenderer val = (SkeletonRenderer)(object)((obj is SkeletonRenderer) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
		}
		return null;
	}

	internal void Tick()
	{
		//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0210: Unknown result type (might be due to invalid IL or missing references)
		if (StallOn)
		{
			if (Time.unscaledTime >= _stallUntil)
			{
				StopStall();
				_log.LogInfo((object)("[stress] stall auto-off after " + StallSeconds + " s, fired " + StallFired));
			}
			else if (StallPeriodic && ++_stallFrame % Math.Max(1, StallEveryFrames) == 0)
			{
				FireStallOnce();
			}
			HammerTick();
		}
		if (HogOn && Time.unscaledTime >= _hogUntil)
		{
			StopHog();
			_log.LogInfo((object)("[stress] hog auto-off after " + HogSeconds + " s"));
		}
		if (!ChurnOn)
		{
			return;
		}
		if ((Object)(object)_root == (Object)null)
		{
			StopChurn();
			return;
		}
		for (int i = 0; i < _reactivate.Count; i++)
		{
			GameObject val = _reactivate[i];
			if ((Object)(object)val != (Object)null)
			{
				val.SetActive(true);
			}
		}
		_reactivate.Clear();
		int num = ((_alive.Count >= ChurnAlive) ? ChurnPerFrame : 0);
		for (int j = 0; j < num; j++)
		{
			if (_alive.Count <= 0)
			{
				break;
			}
			GameObject val2 = _alive[0];
			_alive.RemoveAt(0);
			if ((Object)(object)val2 != (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				Destroyed++;
			}
		}
		int num2 = Math.Min(ChurnPerFrame * 4, ChurnAlive - _alive.Count);
		Camera main = Camera.main;
		Vector3 center = (((Object)(object)main != (Object)null) ? ((Component)main).transform.position : Vector3.zero);
		center.z = _templateZ;
		_cam = main;
		for (int k = 0; k < num2; k++)
		{
			GameObject val3 = CreateOne(center);
			if ((Object)(object)val3 == (Object)null)
			{
				break;
			}
			_alive.Add(val3);
			Created++;
		}
		int num3 = Math.Min(_alive.Count, Math.Max(1, ChurnPerFrame / 2));
		for (int l = 0; l < num3; l++)
		{
			GameObject val4 = _alive[_rng.Next(_alive.Count)];
			if ((Object)(object)val4 == (Object)null || !val4.activeSelf)
			{
				continue;
			}
			SkeletonAnimation component = val4.GetComponent<SkeletonAnimation>();
			if ((Object)(object)component == (Object)null || component.AnimationState == null)
			{
				continue;
			}
			try
			{
				component.AnimationState.SetAnimation(0, _animNames[_rng.Next(_animNames.Count)], true);
				if (_rng.Next(4) == 0)
				{
					component.AnimationState.AddEmptyAnimation(0, 0.1f, 0.2f);
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("[stress] SetAnimation threw: " + ex.Message));
			}
		}
	}

	private GameObject CreateOne(Vector3 center)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_0178: Unknown result type (might be due to invalid IL or missing references)
		//IL_0189: Unknown result type (might be due to invalid IL or missing references)
		//IL_019b: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a0: 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_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0224: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b8: Expected O, but got Unknown
		//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cb: Expected O, but got Unknown
		//IL_0290: Unknown result type (might be due to invalid IL or missing references)
		//IL_0295: Unknown result type (might be due to invalid IL or missing references)
		//IL_029e: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			GameObject val = new GameObject("stress");
			val.transform.SetParent(_root.transform, false);
			if (_builtin && (Object)(object)_cam != (Object)null)
			{
				float num = (_cam.orthographic ? Mathf.Max(1f, _cam.nearClipPlane + 1f) : Mathf.Max(2f, _cam.nearClipPlane + 8f));
				Vector3 val2 = default(Vector3);
				((Vector3)(ref val2))..ctor(0.05f + (float)_rng.NextDouble() * 0.9f, 0.08f + (float)_rng.NextDouble() * 0.84f, num);
				val.transform.position = _cam.ViewportToWorldPoint(val2);
				val.transform.rotation = ((Component)_cam).transform.rotation;
				val.transform.localScale = Vector3.one * 0.35f;
			}
			else
			{
				val.transform.position = new Vector3(center.x + (float)(_rng.NextDouble() * 2.0 - 1.0) * ChurnSpread, center.y + (float)(_rng.NextDouble() * 2.0 - 1.0) * ChurnSpread, center.z);
				val.transform.localScale = _templateScale;
			}
			SkeletonAnimation skeletonAnimation = SkeletonAnimation.AddToGameObject(val, _asset, true).skeletonAnimation;
			if ((Object)(object)skeletonAnimation == (Object)null || ((SkeletonAnimationBase)skeletonAnimation).Skeleton == null)
			{
				Object.Destroy((Object)(object)val);
				return null;
			}
			MeshRenderer component = val.GetComponent<MeshRenderer>();
			if ((Object)(object)component != (Object)null && (Object)(object)_templateRenderer != (Object)null)
			{
				((Renderer)component).sortingLayerID = ((Renderer)_templateRenderer).sortingLayerID;
				((Renderer)component).sortingOrder = ((Renderer)_templateRenderer).sortingOrder;
			}
			SkeletonRenderer component2 = val.GetComponent<SkeletonRenderer>();
			if ((Object)(object)component2 != (Object)null)
			{
				component2.updateWhenInvisible = (UpdateMode)3;
			}
			skeletonAnimation.timeScale = 0.5f + (float)_rng.NextDouble();
			skeletonAnimation.AnimationState.SetAnimation(0, _animNames[_rng.Next(_animNames.Count)], true);
			if (_builtin)
			{
				Color color = Color.HSVToRGB((float)_rng.NextDouble(), 0.6f, 1f);
				((SkeletonAnimationBase)skeletonAnimation).Skeleton.SetColor(color);
			}
			((SkeletonAnimationBase)skeletonAnimation).UpdateComplete += new SkeletonRendererDelegate(OnUpdateComplete);
			((SkeletonAnimationBase)skeletonAnimation).OnMeshAndMaterialsUpdated += new SkeletonRendererDelegate(OnUpdateComplete);
			return val;
		}
		catch (Exception ex)
		{
			_log.LogWarning((object)("[stress] spawn threw: " + ex.Message));
			return null;
		}
	}

	private void OnUpdateComplete(ISkeletonRenderer renderer)
	{
		if (ChurnOn && _rng.Next(200) == 0)
		{
			MonoBehaviour component = ((ISkeletonComponent)renderer).Component;
			if (!((Object)(object)component == (Object)null) && !((Object)(object)((Component)component).gameObject == (Object)null) && ((Component)component).gameObject.activeSelf)
			{
				((Component)component).gameObject.SetActive(false);
				_reactivate.Add(((Component)component).gameObject);
			}
		}
	}

	internal string ToggleHog()
	{
		if (HogOn)
		{
			StopHog();
			return "hog: OFF";
		}
		int num = ((HogThreads > 0) ? HogThreads : (Environment.ProcessorCount * 2));
		HogOn = true;
		_hogCount = num;
		_hogUntil = Time.unscaledTime + HogSeconds;
		for (int i = 0; i < num; i++)
		{
			Thread thread = new Thread(HogLoop);
			thread.IsBackground = true;
			thread.Priority = ThreadPriority.AboveNormal;
			thread.Name = "LwfSpineStressHog" + i;
			thread.Start();
		}
		_log.LogInfo((object)("[stress] hog on: threads=" + num + ", auto-off in " + HogSeconds + " s"));
		return "hog: ON   " + num + " threads   stops in " + HogSeconds + " s";
	}

	internal void StopHog()
	{
		HogOn = false;
		_hogCount = 0;
	}

	private void HogLoop()
	{
		long num = 0L;
		while (HogOn)
		{
			for (int i = 0; i < 100000; i++)
			{
				num += i ^ (num >> 3);
			}
		}
		Interlocked.Exchange(ref _hogSink, num);
	}

	internal void StopAll()
	{
		StopStall();
		StopHog();
		StopChurn();
	}

	internal string Status()
	{
		string text = (ChurnOn ? ("churn ON alive " + _alive.Count + " (created " + Created + " / destroyed " + Destroyed + ")") : ("churn OFF" + ((Created > 0) ? (" (created " + Created + " / destroyed " + Destroyed + ")") : "")));
		string text2 = (StallOn ? "stall ON " : "stall OFF ") + "fired " + StallFired + " (anim " + AnimStallFired + ")  hammer exceptions " + HammerExceptions + (StallOn ? ("  " + Math.Max(0f, _stallUntil - Time.unscaledTime).ToString("0") + "s left") : "");
		string text3 = (HogOn ? ("hog ON " + _hogCount + " threads " + Math.Max(0f, _hogUntil - Time.unscaledTime).ToString("0") + "s left") : "");
		return text + "   " + text2 + ((text3.Length > 0) ? ("   " + text3) : "");
	}
}
internal static class BuiltinSkeleton
{
	private const string PageName = "lwfstress";

	internal const int BoneCount = 80;

	private const string Atlas = "lwfstress.png\nsize: 4, 4\nformat: RGBA8888\nfilter: Linear, Linear\nrepeat: none\na\n  rotate: false\n  xy: 0, 0\n  size: 4, 4\n  orig: 4, 4\n  offset: 0, 0\n  index: -1\n";

	private static readonly string[] ShaderCandidates = new string[6] { "Spine/Skeleton", "Universal Render Pipeline/2D/Sprite-Unlit-Default", "Universal Render Pipeline/Unlit", "Sprites/Default", "Unlit/Texture", "Hidden/InternalErrorShader" };

	private static SkeletonDataAsset _cached;

	private static string _shaderUsed = "";

	internal static string ShaderUsed => _shaderUsed;

	private static string BuildJson(int bones)
	{
		StringBuilder stringBuilder = new StringBuilder(bones * 400 + 1024);
		stringBuilder.Append("{\n\"skeleton\": { \"hash\": \"lwfstress\", \"spine\": \"4.3.0\", \"x\": -200, \"y\": -200, \"width\": 400, \"height\": 400 },\n");
		stringBuilder.Append("\"bones\": [ { \"name\": \"root\" }");
		for (int i = 1; i <= bones; i++)
		{
			stringBuilder.Append(",\n  { \"name\": \"b").Append(i).Append("\", \"parent\": \"")
				.Append((i == 1) ? "root" : ("b" + (i - 1)))
				.Append("\", \"length\": 5, \"x\": 5 }");
		}
		stringBuilder.Append(" ],\n");
		stringBuilder.Append("\"slots\": [");
		for (int j = 1; j <= bones; j++)
		{
			if (j > 1)
			{
				stringBuilder.Append(",");
			}
			stringBuilder.Append("\n  { \"name\": \"s").Append(j).Append("\", \"bone\": \"b")
				.Append(j)
				.Append("\", \"attachment\": \"a\" }");
		}
		stringBuilder.Append(" ],\n");
		stringBuilder.Append("\"skins\": [ { \"name\": \"default\", \"attachments\": {");
		for (int k = 1; k <= bones; k++)
		{
			if (k > 1)
			{
				stringBuilder.Append(",");
			}
			stringBuilder.Append("\n  \"s").Append(k).Append("\": { \"a\": { \"width\": 4, \"height\": 4 } }");
		}
		stringBuilder.Append("\n} } ],\n");
		stringBuilder.Append("\"animations\": {\n");
		stringBuilder.Append("  \"spin\": { \"bones\": {");
		for (int l = 1; l <= bones; l++)
		{
			if (l > 1)
			{
				stringBuilder.Append(",");
			}
			int num = l * 7 % 90;
			stringBuilder.Append("\n    \"b").Append(l).Append("\": { \"rotate\": [ { \"value\": ")
				.Append(num)
				.Append(" }, { \"time\": 0.5, \"value\": ")
				.Append(num + 180)
				.Append(" }, { \"time\": 1, \"value\": ")
				.Append(num + 360)
				.Append(" } ] }");
		}
		stringBuilder.Append("\n  } },\n");
		stringBuilder.Append("  \"pulse\": { \"bones\": {");
		for (int m = 1; m <= bones; m++)
		{
			if (m > 1)
			{
				stringBuilder.Append(",");
			}
			stringBuilder.Append("\n    \"b").Append(m).Append("\": { \"scale\": [ { \"x\": 1, \"y\": 1 }, { \"time\": 0.5, \"x\": 1.8, \"y\": 0.6 }, { \"time\": 1, \"x\": 1, \"y\": 1 } ] }");
		}
		stringBuilder.Append("\n  } },\n");
		stringBuilder.Append("  \"wave\": { \"bones\": {");
		for (int n = 1; n <= bones; n++)
		{
			if (n > 1)
			{
				stringBuilder.Append(",");
			}
			int num2 = 2 + n % 5;
			stringBuilder.Append("\n    \"b").Append(n).Append("\": { \"translate\": [ { \"x\": 0, \"y\": 0 }, { \"time\": 0.5, \"x\": ")
				.Append(num2)
				.Append(", \"y\": ")
				.Append(-num2)
				.Append(" }, { \"time\": 1, \"x\": 0, \"y\": 0 } ] }");
		}
		stringBuilder.Append("\n  } }\n");
		stringBuilder.Append("}\n}\n");
		return stringBuilder.ToString();
	}

	internal static SkeletonDataAsset Get(ManualLogSource log)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Expected O, but got Unknown
		//IL_0134: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Expected O, but got Unknown
		if ((Object)(object)_cached != (Object)null)
		{
			return _cached;
		}
		try
		{
			Texture2D val = new Texture2D(4, 4, (TextureFormat)4, false);
			Color32[] array = (Color32[])(object)new Color32[16];
			for (int i = 0; i < array.Length; i++)
			{
				ref Color32 reference = ref array[i];
				reference = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
			}
			val.SetPixels32(array);
			val.Apply(false, false);
			((Object)val).name = "lwfstress";
			Shader val2 = null;
			for (int j = 0; j < ShaderCandidates.Length; j++)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					break;
				}
				val2 = Shader.Find(ShaderCandidates[j]);
				if ((Object)(object)val2 != (Object)null)
				{
					_shaderUsed = ShaderCandidates[j];
				}
			}
			if ((Object)(object)val2 == (Object)null)
			{
				log.LogWarning((object)"[builtin] no shader found, cannot build the test skeleton");
				return null;
			}
			TextAsset val3 = new TextAsset("lwfstress.png\nsize: 4, 4\nformat: RGBA8888\nfilter: Linear, Linear\nrepeat: none\na\n  rotate: false\n  xy: 0, 0\n  size: 4, 4\n  orig: 4, 4\n  offset: 0, 0\n  index: -1\n");
			((Object)val3).name = "lwfstress.atlas";
			SpineAtlasAsset val4 = SpineAtlasAsset.CreateRuntimeInstance(val3, (Texture2D[])(object)new Texture2D[1] { val }, val2, true, (Func<SpineAtlasAsset, TextureLoader>)null);
			if ((Object)(object)val4 == (Object)null || ((AtlasAssetBase)val4).GetAtlas(false) == null)
			{
				log.LogWarning((object)"[builtin] atlas creation failed");
				return null;
			}
			TextAsset val5 = new TextAsset(BuildJson(80));
			((Object)val5).name = "lwfstress";
			SkeletonDataAsset val6 = SkeletonDataAsset.CreateRuntimeInstance(val5, (AtlasAssetBase)(object)val4, true, 0.01f);
			if ((Object)(object)val6 == (Object)null || val6.GetSkeletonData(true) == null)
			{
				log.LogWarning((object)"[builtin] skeleton data creation failed");
				return null;
			}
			((Object)val6).name = "lwfstress";
			_cached = val6;
			SkeletonData skeletonData = val6.GetSkeletonData(true);
			log.LogInfo((object)("[builtin] test skeleton ready: shader=" + _shaderUsed + ", bones=" + skeletonData.Bones.Count + ", slots=" + skeletonData.Slots.Count + ", anims=" + skeletonData.Animations.Count));
			return _cached;
		}
		catch (Exception ex)
		{
			log.LogWarning((object)("[builtin] building the test skeleton threw: " + ex));
			return null;
		}
	}
}
internal static class Lang
{
	private static bool _known;

	private static bool _ja;

	internal static bool Ja
	{
		get
		{
			if (!_known)
			{
				Refresh();
			}
			return _ja;
		}
	}

	internal static void Refresh()
	{
		_known = true;
		_ja = Detect();
	}

	internal static string T(string ja, string en)
	{
		if (!Ja)
		{
			return en;
		}
		return ja;
	}

	private static bool Detect()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Invalid comparison between Unknown and I4
		string text = LocaleCode();
		if (!string.IsNullOrEmpty(text))
		{
			return text.StartsWith("ja", StringComparison.OrdinalIgnoreCase);
		}
		try
		{
			return (int)Application.systemLanguage == 22;
		}
		catch (Exception)
		{
			return false;
		}
	}

	private static string LocaleCode()
	{
		try
		{
			Type type = AccessTools.TypeByName("UnityEngine.Localization.Settings.LocalizationSettings");
			if (type == null)
			{
				return null;
			}
			PropertyInfo property = type.GetProperty("SelectedLocale", BindingFlags.Static | BindingFlags.Public);
			object obj = ((property != null) ? property.GetValue(null, null) : null);
			if (obj == null)
			{
				return null;
			}
			PropertyInfo property2 = obj.GetType().GetProperty("Identifier");
			object obj2 = ((property2 != null) ? property2.GetValue(obj, null) : null);
			if (obj2 == null)
			{
				return null;
			}
			PropertyInfo property3 = obj2.GetType().GetProperty("Code");
			return (property3 != null) ? (property3.GetValue(obj2, null) as string) : null;
		}
		catch (Exception)
		{
			return null;
		}
	}
}