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 _enabled; private ConfigEntry _threadedAnimation; private ConfigEntry _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 _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 _frameMs = new List(3600); private int _cursor; private double _sum; private int _count; private readonly List _sortScratch = new List(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("1. General", "Enabled", true, ""); _keyAutoAB = Hotkey.Parse("F9"); _keyPerfAB = Hotkey.Parse("F10"); _keyToggleHud = null; _threadedAnimation = ((BaseUnityPlugin)this).Config.Bind("9. Developer", "ThreadedAnimation", true, ""); _threadedMeshGeneration = ((BaseUnityPlugin)this).Config.Bind("9. Developer", "ThreadedMeshGeneration", true, ""); _hudOnStart = ((BaseUnityPlugin)this).Config.Bind("9. Developer", "ShowInGame", false, ""); _abFullAB = ((BaseUnityPlugin)this).Config.Bind("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((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((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((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 RegisteredRendererList { get { SkeletonUpdateSystem owner = _owner; if (!((Object)(object)owner == (Object)null)) { return owner.skeletonRenderers; } return null; } } internal static List 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)) { 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 skeletonRenderers = inst.skeletonRenderers; if (skeletonRenderers.Count == 0) { return; } int[] array = _fUpdatedAtTask.GetValue(inst) as int[]; int[] array2 = _fProcessedAtTask.GetValue(inst) as int[]; ExposedList val = _fPartitions.GetValue(inst) as ExposedList; 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)) { 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 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 Context = new Queue(41); private static readonly Dictionary Written = new Dictionary(); private static readonly Dictionary NotedUpstream = new Dictionary(); 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 events = new List(); } 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 _hammer = new List(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 _animNames = new List(); private readonly List _alive = new List(512); private readonly List _reactivate = new List(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((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(); _templateScale = ((Component)val).transform.lossyScale; _templateZ = ((Component)val).transform.position.z; _builtin = false; } _animNames.Clear(); SkeletonData skeletonData = _asset.GetSkeletonData(true); if (skeletonData != null) { Enumerator 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 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 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(); 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(); 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(); 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)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; } } }