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;
}
}
}