Decompiled source of TFWR Oracle Runner v0.2.0

plugins/TFWROracleRunner/TFWROracleRunner.dll

Decompiled 7 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("TFWROracleRunner")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+55514a27d14a3819e28577bb4e1b027beb4d7887")]
[assembly: AssemblyProduct("TFWROracleRunner")]
[assembly: AssemblyTitle("TFWROracleRunner")]
[assembly: AssemblyVersion("0.2.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TFWROracleRunner
{
	public static class LeaderboardLogFormatter
	{
		public static string FormatStartLine(string scriptName)
		{
			return string.Format(CultureInfo.InvariantCulture, "[{0}] start", RunnerStateProtocol.NormalizeTargetScriptName(scriptName));
		}

		public static string FormatRunLine(string scriptName, int runIndex, TimeSpan timeSpan)
		{
			return string.Format(CultureInfo.InvariantCulture, "[{0}] run={1} time={2}", RunnerStateProtocol.NormalizeTargetScriptName(scriptName), runIndex, FormatClock(timeSpan));
		}

		public static string FormatSummaryLine(string scriptName, bool finished, int runCount, TimeSpan averageTime)
		{
			return string.Format(CultureInfo.InvariantCulture, "[{0}] finished={1} runs={2} average={3}", RunnerStateProtocol.NormalizeTargetScriptName(scriptName), finished ? "true" : "false", runCount, FormatClock(averageTime));
		}

		public static string FormatClock(TimeSpan timeSpan)
		{
			int num = (int)Math.Floor(timeSpan.TotalMinutes);
			return string.Format(CultureInfo.InvariantCulture, "{0}:{1:00}.{2:000}", num, timeSpan.Seconds, timeSpan.Milliseconds);
		}
	}
	public sealed class LeaderboardResultAutoCloseGate
	{
		private bool externalResultPendingClose;

		public bool ExternalResultPendingClose => externalResultPendingClose;

		public void MarkExternalLeaderboardObserved()
		{
			externalResultPendingClose = true;
		}

		public void Clear()
		{
			externalResultPendingClose = false;
		}
	}
	internal sealed class OracleRunnerConfig
	{
		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<string> TargetScriptName { get; }

		public ConfigEntry<bool> AutoPlayMainMenu { get; }

		public ConfigEntry<int> StartupPollIntervalMs { get; }

		public ConfigEntry<float> ScriptReadyTimeoutSeconds { get; }

		public ConfigEntry<float> DefaultRequestTimeoutSeconds { get; }

		public ConfigEntry<float> RequestStopGracePeriodSeconds { get; }

		public OracleRunnerConfig(ConfigFile config)
		{
			Enabled = config.Bind<bool>("General", "Enabled", true, "是否启用自动权威执行器。");
			TargetScriptName = config.Bind<string>("General", "TargetScriptName", "lb_start", "目标脚本窗口名,不带 .py。");
			AutoPlayMainMenu = config.Bind<bool>("General", "AutoPlayMainMenu", false, "是否自动进入游戏。无人值守运行可开启;关闭时请手动进入工作区后再请求脚本。");
			StartupPollIntervalMs = config.Bind<int>("Timing", "StartupPollIntervalMs", 250, "轮询主菜单、工作区和 state.json 的时间间隔(毫秒)。");
			ScriptReadyTimeoutSeconds = config.Bind<float>("Timing", "ScriptReadyTimeoutSeconds", 30f, "等待脚本窗口出现的最长时间(秒)。仅用于单次 requested 请求启动期。");
			DefaultRequestTimeoutSeconds = config.Bind<float>("Timing", "DefaultRequestTimeoutSeconds", 20f, "state.json 未显式提供 timeout_seconds 时,单次请求的默认执行超时(秒)。");
			RequestStopGracePeriodSeconds = config.Bind<float>("Timing", "RequestStopGracePeriodSeconds", 3f, "收到覆盖请求或超时停止后,等待 StopMainExecution() 生效的提示窗口(秒)。");
		}
	}
	[DataContract]
	public sealed class OracleRunnerStateFile
	{
		[DataMember(Name = "request_id", IsRequired = true)]
		public int RequestId { get; set; }

		public RunnerRequestStatus Status { get; set; }

		[DataMember(Name = "status", IsRequired = true)]
		private string StatusText
		{
			get
			{
				return RunnerStateProtocol.ToStatusText(Status);
			}
			set
			{
				Status = RunnerStateProtocol.ParseStatusText(value);
			}
		}

		[DataMember(Name = "target_script")]
		public string? TargetScript { get; set; }

		[DataMember(Name = "timeout_seconds")]
		public double? TimeoutSeconds { get; set; }

		[DataMember(Name = "started_at")]
		public string? StartedAt { get; set; }

		[DataMember(Name = "finished_at")]
		public string? FinishedAt { get; set; }

		[DataMember(Name = "last_error")]
		public string? LastError { get; set; }
	}
	[BepInPlugin("mlj.tfwr.oracle-runner", "TFWR Oracle Runner", "0.2.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private enum StopReason
		{
			None,
			Timeout,
			Superseded,
			StopRequested
		}

		[HarmonyPatch(typeof(Menu), "Play")]
		private static class MenuPlayPatch
		{
			private static void Postfix()
			{
				Instance?.MarkMenuPlayed();
			}
		}

		[HarmonyPatch(typeof(LeaderboardManager), "StopLeaderboardRun")]
		private static class LeaderboardStopPatch
		{
			private static void Prefix(LeaderboardManager __instance, bool finished, string leaderboardName, TimeSpan timeSpan)
			{
				MainSim inst = MainSim.Inst;
				if (!((Object)(object)inst == (Object)null))
				{
					Instance?.ObserveLeaderboardStopped(inst, finished, leaderboardName, timeSpan, inst.numLeaderboardRuns);
				}
			}
		}

		internal const string PluginGuid = "mlj.tfwr.oracle-runner";

		internal const string PluginName = "TFWR Oracle Runner";

		internal const string PluginVersion = "0.2.0";

		private static Plugin? instance;

		private static readonly FieldRef<MainSim, Simulation> SimRef = AccessTools.FieldRefAccess<MainSim, Simulation>("sim");

		private static readonly FieldRef<MainSim, LeaderboardStartArgs> PrevLeaderboardStartRef = AccessTools.FieldRefAccess<MainSim, LeaderboardStartArgs>("prevLeaderboardStart");

		private static readonly FieldRef<LeaderboardManager, GameObject> RunCancelledRef = AccessTools.FieldRefAccess<LeaderboardManager, GameObject>("runCancelled");

		private static readonly FieldRef<LeaderboardManager, TextMeshProUGUI> FinishTimeTextRef = AccessTools.FieldRefAccess<LeaderboardManager, TextMeshProUGUI>("finishTimeText");

		private static readonly FieldRef<Simulation, bool> SimulationHasErrorRef = AccessTools.FieldRefAccess<Simulation, bool>("hasError");

		private static readonly FieldRef<CodeWindow, string> CodeWindowErrorStringRef = AccessTools.FieldRefAccess<CodeWindow, string>("errorString");

		private static readonly FieldRef<CodeWindow, int> CodeWindowErrorStartIndexRef = AccessTools.FieldRefAccess<CodeWindow, int>("errorStartIndex");

		private static readonly FieldRef<CodeWindow, int> CodeWindowErrorEndIndexRef = AccessTools.FieldRefAccess<CodeWindow, int>("errorEndIndex");

		private OracleRunnerConfig runnerConfig;

		private RunnerStateStore stateStore;

		private Harmony? harmony;

		private float pluginStartedAt;

		private float nextStatePollAt;

		private float nextStateWarningAt;

		private float lastProgressLogAt;

		private float lastItemSnapshotLogAt;

		private float activeRequestStartedAt;

		private float stopRequestedAt;

		private readonly LeaderboardResultAutoCloseGate leaderboardResultAutoCloseGate = new LeaderboardResultAutoCloseGate();

		private bool playInvoked;

		private StopReason stopReason;

		private OracleRunnerStateFile? activeRequest;

		private OracleRunnerStateFile? pendingRequest;

		private int lastHandledRequestId;

		private bool wasLeaderboardRunning;

		private int observedLeaderboardRuns;

		private TimeSpan observedLeaderboardTotalTime = TimeSpan.Zero;

		private string observedLeaderboardScriptName = string.Empty;

		private ManualLogSource Log => ((BaseUnityPlugin)this).Logger;

		private static Plugin? Instance => instance;

		private void Awake()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			instance = this;
			runnerConfig = new OracleRunnerConfig(((BaseUnityPlugin)this).Config);
			stateStore = new RunnerStateStore(Path.Combine(Paths.ConfigPath, "mlj.tfwr.oracle-runner.state.json"));
			pluginStartedAt = Time.realtimeSinceStartup;
			lastProgressLogAt = pluginStartedAt;
			harmony = new Harmony("mlj.tfwr.oracle-runner");
			harmony.PatchAll();
			Log.LogInfo((object)($"enabled={runnerConfig.Enabled.Value} state_file={stateStore.StateFilePath} " + $"poll_ms={runnerConfig.StartupPollIntervalMs.Value} default_timeout={runnerConfig.DefaultRequestTimeoutSeconds.Value}s"));
		}

		private void OnDestroy()
		{
			Harmony? obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}

		private void Update()
		{
			if (!runnerConfig.Enabled.Value)
			{
				return;
			}
			MainSim inst = MainSim.Inst;
			if ((Object)(object)inst == (Object)null)
			{
				MaybeLogProgress("等待 MainSim 初始化");
				return;
			}
			if (!playInvoked && runnerConfig.AutoPlayMainMenu.Value && (Object)(object)inst.menu != (Object)null && ((Component)inst.menu).gameObject.activeInHierarchy)
			{
				inst.menu.Play();
				playInvoked = true;
				Log.LogInfo((object)"已调用 Menu.Play() 进入游戏工作区");
			}
			ObserveLeaderboardTiming(inst);
			PollState(inst);
			MonitorExecution(inst);
		}

		private void PollState(MainSim mainSim)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < nextStatePollAt)
			{
				return;
			}
			nextStatePollAt = realtimeSinceStartup + Mathf.Max(0.05f, (float)runnerConfig.StartupPollIntervalMs.Value / 1000f);
			try
			{
				OracleRunnerStateFile oracleRunnerStateFile = stateStore.Read();
				EnsureStateFileReady(mainSim, oracleRunnerStateFile);
				if (oracleRunnerStateFile != null)
				{
					ObserveStopRequestedState(mainSim, oracleRunnerStateFile);
					ObserveRequestedState(mainSim, oracleRunnerStateFile);
				}
			}
			catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is SerializationException)
			{
				if (realtimeSinceStartup >= nextStateWarningAt)
				{
					nextStateWarningAt = realtimeSinceStartup + 5f;
					Log.LogWarning((object)("状态文件同步失败,将在下次轮询重试:" + stateStore.StateFilePath + ";" + ex.Message));
				}
			}
		}

		private void EnsureStateFileReady(MainSim mainSim, OracleRunnerStateFile? current)
		{
			if (current == null)
			{
				stateStore.Write(RunnerStateProtocol.CreateIdle(lastHandledRequestId));
			}
			else if (activeRequest == null && !mainSim.IsExecuting() && current.Status == RunnerRequestStatus.Running)
			{
				stateStore.Write(RunnerStateProtocol.CreateIdle(current.RequestId));
			}
		}

		private void ObserveRequestedState(MainSim mainSim, OracleRunnerStateFile observedState)
		{
			if (observedState.Status != RunnerRequestStatus.Requested)
			{
				return;
			}
			if (activeRequest == null)
			{
				if (observedState.RequestId > lastHandledRequestId)
				{
					TryStartRequest(mainSim, observedState);
				}
			}
			else if (observedState.RequestId > activeRequest.RequestId)
			{
				pendingRequest = observedState;
				if (stopReason == StopReason.None)
				{
					BeginStop(mainSim, StopReason.Superseded);
				}
			}
		}

		private void ObserveStopRequestedState(MainSim mainSim, OracleRunnerStateFile observedState)
		{
			if (activeRequest != null && observedState.Status == RunnerRequestStatus.StopRequested && observedState.RequestId == activeRequest.RequestId)
			{
				activeRequest.LastError = (string.IsNullOrWhiteSpace(observedState.LastError) ? "stop requested" : observedState.LastError);
				if (stopReason == StopReason.None)
				{
					BeginStop(mainSim, StopReason.StopRequested);
				}
			}
		}

		private void TryStartRequest(MainSim mainSim, OracleRunnerStateFile requestedState)
		{
			CloseLeaderboardResultScreen(mainSim, "request_start");
			if ((Object)(object)mainSim.workspace == (Object)null || !((Component)mainSim.workspace).gameObject.activeInHierarchy)
			{
				MaybeLogProgress("等待工作区激活");
				return;
			}
			string text = RunnerStateProtocol.NormalizeTargetScriptName(requestedState.TargetScript ?? runnerConfig.TargetScriptName.Value);
			CodeWindow val = FindTargetWindow(mainSim.workspace, text);
			if ((Object)(object)val == (Object)null)
			{
				if (Time.realtimeSinceStartup - pluginStartedAt > runnerConfig.ScriptReadyTimeoutSeconds.Value)
				{
					MarkFailedWithOutput(requestedState, "script window not found: " + text, DateTimeOffset.UtcNow);
					stateStore.Write(requestedState);
					lastHandledRequestId = Math.Max(lastHandledRequestId, requestedState.RequestId);
				}
				else
				{
					MaybeLogProgress("等待脚本窗口 " + text);
				}
				return;
			}
			if (mainSim.IsExecuting())
			{
				MaybeLogProgress("当前已有执行,等待停止后再启动新请求");
				return;
			}
			Node val2 = val.Parse();
			if (val2 == null)
			{
				MarkFailedWithOutput(requestedState, BuildParseFailureMessage(val, text), DateTimeOffset.UtcNow);
				stateStore.Write(requestedState);
				lastHandledRequestId = Math.Max(lastHandledRequestId, requestedState.RequestId);
				return;
			}
			RunnerStateProtocol.MarkRunning(requestedState, DateTimeOffset.UtcNow);
			stateStore.Write(requestedState);
			mainSim.StartMainExecution(val, val2);
			activeRequest = requestedState;
			activeRequestStartedAt = Time.realtimeSinceStartup;
			lastHandledRequestId = Math.Max(lastHandledRequestId, requestedState.RequestId);
			stopReason = StopReason.None;
			Log.LogInfo((object)$"已启动脚本 {val.fileName} request_id={requestedState.RequestId}");
		}

		private void MonitorExecution(MainSim mainSim)
		{
			if (activeRequest == null)
			{
				return;
			}
			if (stopReason != StopReason.None)
			{
				CloseLeaderboardResultScreen(mainSim, "request_stop");
				if (!mainSim.IsExecuting())
				{
					FinishStopping(mainSim);
					return;
				}
				float num = Time.realtimeSinceStartup - stopRequestedAt;
				if (num >= runnerConfig.RequestStopGracePeriodSeconds.Value)
				{
					MaybeLogItemSnapshot(mainSim, "stopping", "stop_elapsed=" + num.ToString("0.0", CultureInfo.InvariantCulture));
				}
			}
			else if (HasSimulationError(mainSim))
			{
				MarkFailedWithOutput(activeRequest, "script runtime error; see output.txt Error block", DateTimeOffset.UtcNow);
				stateStore.Write(activeRequest);
				mainSim.StopMainExecution();
				activeRequest = null;
				stopReason = StopReason.None;
			}
			else if (!mainSim.IsExecuting())
			{
				FinishDone();
			}
			else
			{
				double num2 = activeRequest.TimeoutSeconds ?? ((double)runnerConfig.DefaultRequestTimeoutSeconds.Value);
				if (num2 > 0.0 && (double)(Time.realtimeSinceStartup - activeRequestStartedAt) >= num2)
				{
					BeginStop(mainSim, StopReason.Timeout);
				}
				else
				{
					MaybeLogItemSnapshot(mainSim, "running", "timeout=" + num2.ToString("0.0", CultureInfo.InvariantCulture));
				}
			}
		}

		private void MaybeLogProgress(string message)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			float num = Mathf.Max(0.05f, (float)runnerConfig.StartupPollIntervalMs.Value / 1000f);
			if (!(realtimeSinceStartup - lastProgressLogAt < num))
			{
				lastProgressLogAt = realtimeSinceStartup;
				Log.LogInfo((object)message);
			}
		}

		private void MaybeLogItemSnapshot(MainSim mainSim, string status, string detail)
		{
			if (activeRequest != null)
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (!(realtimeSinceStartup - lastItemSnapshotLogAt < 1f))
				{
					lastItemSnapshotLogAt = realtimeSinceStartup;
					Log.LogInfo((object)BuildItemSnapshotLine(mainSim, status, detail));
				}
			}
		}

		private string BuildItemSnapshotLine(MainSim mainSim, string status, string detail)
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("item_snapshot request_id=");
			stringBuilder.Append(activeRequest?.RequestId ?? 0);
			stringBuilder.Append(" status=");
			stringBuilder.Append(status);
			if (!string.IsNullOrWhiteSpace(detail))
			{
				stringBuilder.Append(' ');
				stringBuilder.Append(detail);
			}
			stringBuilder.Append(" real_elapsed=");
			stringBuilder.Append((Time.realtimeSinceStartup - activeRequestStartedAt).ToString("0.0", CultureInfo.InvariantCulture));
			Simulation val = SimRef.Invoke(mainSim);
			if (val != null)
			{
				stringBuilder.Append(" game_time=");
				Duration currentTime = val.CurrentTime;
				stringBuilder.Append(((Duration)(ref currentTime)).Seconds.ToString("0.###", CultureInfo.InvariantCulture));
				if (val.Execution != null)
				{
					stringBuilder.Append(" game_tick=");
					stringBuilder.Append(val.Execution.GlobalOpCount.ToString("0.###", CultureInfo.InvariantCulture));
				}
			}
			stringBuilder.Append(" leaderboard_runs=");
			stringBuilder.Append(mainSim.numLeaderboardRuns.ToString(CultureInfo.InvariantCulture));
			stringBuilder.Append(" leaderboard_total_time=");
			stringBuilder.Append(((Duration)(ref mainSim.totalLeaderboardTime)).Seconds.ToString("0.###", CultureInfo.InvariantCulture));
			if (!string.IsNullOrWhiteSpace(observedLeaderboardScriptName))
			{
				stringBuilder.Append(" leaderboard_script=");
				stringBuilder.Append(observedLeaderboardScriptName);
			}
			ItemBlock inventory = mainSim.GetInventory();
			foreach (ItemSO allItem in ResourceManager.GetAllItems())
			{
				stringBuilder.Append(' ');
				stringBuilder.Append(allItem.itemName);
				stringBuilder.Append('=');
				stringBuilder.Append(inventory.GetNumber(allItem.itemId).ToString("0.###", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		private void ObserveLeaderboardTiming(MainSim mainSim)
		{
			if ((Object)(object)mainSim.leaderboardManager == (Object)null)
			{
				return;
			}
			bool isRunning = mainSim.leaderboardManager.IsRunning;
			Simulation sim = SimRef.Invoke(mainSim);
			if (isRunning)
			{
				if (activeRequest != null)
				{
					leaderboardResultAutoCloseGate.MarkExternalLeaderboardObserved();
				}
				if (!wasLeaderboardRunning)
				{
					wasLeaderboardRunning = true;
					observedLeaderboardRuns = 0;
					observedLeaderboardTotalTime = TimeSpan.Zero;
					observedLeaderboardScriptName = ResolveLeaderboardScriptName(mainSim, sim);
					LogLeaderboardLine(LeaderboardLogFormatter.FormatStartLine(observedLeaderboardScriptName));
				}
				if (mainSim.numLeaderboardRuns > observedLeaderboardRuns)
				{
					TimeSpan timeSpan = ((Duration)(ref mainSim.totalLeaderboardTime)).ToTimeSpan();
					TimeSpan timeSpan2 = timeSpan - observedLeaderboardTotalTime;
					string line = LeaderboardLogFormatter.FormatRunLine(observedLeaderboardScriptName, mainSim.numLeaderboardRuns, timeSpan2);
					LogLeaderboardLine(line);
					observedLeaderboardRuns = mainSim.numLeaderboardRuns;
					observedLeaderboardTotalTime = timeSpan;
				}
				return;
			}
			if (!wasLeaderboardRunning)
			{
				TryCloseExternalLeaderboardResultScreen(mainSim, "external_result_idle");
				return;
			}
			if (observedLeaderboardRuns > 0)
			{
				bool flag = !RunCancelledRef.Invoke(mainSim.leaderboardManager).activeSelf;
				TimeSpan averageTime = TimeSpan.FromTicks(observedLeaderboardTotalTime.Ticks / observedLeaderboardRuns);
				string line2 = LeaderboardLogFormatter.FormatSummaryLine(observedLeaderboardScriptName, flag, observedLeaderboardRuns, averageTime);
				LogLeaderboardLine(line2);
				if (activeRequest != null)
				{
					leaderboardResultAutoCloseGate.MarkExternalLeaderboardObserved();
					if (flag)
					{
						RunnerStateProtocol.MarkDone(activeRequest, DateTimeOffset.UtcNow);
					}
					else
					{
						MarkFailedWithOutput(activeRequest, "leaderboard cancelled", DateTimeOffset.UtcNow);
					}
					stateStore.Write(activeRequest);
					activeRequest = null;
				}
				TryCloseExternalLeaderboardResultScreen(mainSim, "external_result_complete");
			}
			else if (mainSim.leaderboardManager.IsLeaderBoardScreenOpen)
			{
				if (!RunCancelledRef.Invoke(mainSim.leaderboardManager).activeSelf)
				{
					string scriptName = (string.IsNullOrWhiteSpace(observedLeaderboardScriptName) ? ResolveLeaderboardScriptName(mainSim, sim) : observedLeaderboardScriptName);
					TimeSpan averageTime2 = ParseLeaderboardFinishTime(mainSim.leaderboardManager);
					string line3 = LeaderboardLogFormatter.FormatSummaryLine(scriptName, finished: true, 1, averageTime2);
					LogLeaderboardLine(line3);
					if (activeRequest != null)
					{
						leaderboardResultAutoCloseGate.MarkExternalLeaderboardObserved();
						RunnerStateProtocol.MarkDone(activeRequest, DateTimeOffset.UtcNow);
						stateStore.Write(activeRequest);
						activeRequest = null;
					}
					TryCloseExternalLeaderboardResultScreen(mainSim, "external_result_complete");
				}
				else
				{
					if (activeRequest != null)
					{
						leaderboardResultAutoCloseGate.MarkExternalLeaderboardObserved();
						MarkFailedWithOutput(activeRequest, "leaderboard finished without completed runs", DateTimeOffset.UtcNow);
						stateStore.Write(activeRequest);
						activeRequest = null;
					}
					TryCloseExternalLeaderboardResultScreen(mainSim, "external_result_cancelled");
				}
			}
			wasLeaderboardRunning = false;
			observedLeaderboardRuns = 0;
			observedLeaderboardTotalTime = TimeSpan.Zero;
			observedLeaderboardScriptName = string.Empty;
		}

		private void TryCloseExternalLeaderboardResultScreen(MainSim mainSim, string reason)
		{
			if (leaderboardResultAutoCloseGate.ExternalResultPendingClose)
			{
				CloseLeaderboardResultScreen(mainSim, reason);
			}
		}

		private bool CloseLeaderboardResultScreen(MainSim mainSim, string reason)
		{
			if ((Object)(object)mainSim.leaderboardManager == (Object)null || !mainSim.leaderboardManager.IsLeaderBoardScreenOpen)
			{
				return false;
			}
			mainSim.leaderboardManager.OkPressed();
			leaderboardResultAutoCloseGate.Clear();
			Log.LogInfo((object)("已自动确认 leaderboard 结果页面 reason=" + reason));
			return true;
		}

		private static TimeSpan ParseLeaderboardFinishTime(LeaderboardManager leaderboardManager)
		{
			string[] array = ((TMP_Text)FinishTimeTextRef.Invoke(leaderboardManager)).text.Trim().Split(':', 2);
			if (array.Length != 2 || !int.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !double.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
			{
				return TimeSpan.Zero;
			}
			return TimeSpan.FromMinutes(result) + TimeSpan.FromSeconds(result2);
		}

		private void ObserveLeaderboardStopped(MainSim mainSim, bool finished, string leaderboardName, TimeSpan averageTime, int runCount)
		{
			if (activeRequest != null)
			{
				leaderboardResultAutoCloseGate.MarkExternalLeaderboardObserved();
				string scriptName = (string.IsNullOrWhiteSpace(observedLeaderboardScriptName) ? ResolveLeaderboardScriptName(mainSim, SimRef.Invoke(mainSim)) : observedLeaderboardScriptName);
				int runCount2 = Math.Max(1, runCount);
				string line = LeaderboardLogFormatter.FormatSummaryLine(scriptName, finished, runCount2, averageTime);
				LogLeaderboardLine(line);
				if (finished)
				{
					RunnerStateProtocol.MarkDone(activeRequest, DateTimeOffset.UtcNow);
				}
				else
				{
					MarkFailedWithOutput(activeRequest, "leaderboard cancelled: " + leaderboardName, DateTimeOffset.UtcNow);
				}
				stateStore.Write(activeRequest);
				activeRequest = null;
				wasLeaderboardRunning = false;
				observedLeaderboardRuns = 0;
				observedLeaderboardTotalTime = TimeSpan.Zero;
				observedLeaderboardScriptName = string.Empty;
			}
		}

		private void BeginStop(MainSim mainSim, StopReason reason)
		{
			if (activeRequest != null)
			{
				stopReason = reason;
				stopRequestedAt = Time.realtimeSinceStartup;
				mainSim.StopMainExecution();
				Log.LogInfo((object)($"请求停止当前执行 request_id={activeRequest.RequestId} reason={reason} " + $"pending={((pendingRequest != null) ? pendingRequest.RequestId : (-1))}"));
			}
		}

		private void FinishStopping(MainSim mainSim)
		{
			if (activeRequest == null)
			{
				stopReason = StopReason.None;
				return;
			}
			if (stopReason == StopReason.Timeout || stopReason == StopReason.StopRequested)
			{
				MarkFailedWithOutput(activeRequest, (stopReason == StopReason.Timeout) ? $"request timed out after {activeRequest.TimeoutSeconds ?? ((double)runnerConfig.DefaultRequestTimeoutSeconds.Value):0.###}s" : (activeRequest.LastError ?? "stop requested"), DateTimeOffset.UtcNow);
				stateStore.Write(activeRequest);
			}
			OracleRunnerStateFile oracleRunnerStateFile = pendingRequest;
			activeRequest = null;
			pendingRequest = null;
			stopReason = StopReason.None;
			if (oracleRunnerStateFile != null)
			{
				TryStartRequest(mainSim, oracleRunnerStateFile);
			}
		}

		private void FinishDone()
		{
			if (activeRequest != null)
			{
				RunnerStateProtocol.MarkDone(activeRequest, DateTimeOffset.UtcNow);
				stateStore.Write(activeRequest);
				Log.LogInfo((object)$"脚本执行完成 request_id={activeRequest.RequestId}");
				activeRequest = null;
			}
		}

		private static bool HasSimulationError(MainSim mainSim)
		{
			Simulation val = SimRef.Invoke(mainSim);
			if (val != null)
			{
				return SimulationHasErrorRef.Invoke(val);
			}
			return false;
		}

		private static string BuildParseFailureMessage(CodeWindow targetWindow, string targetScriptName)
		{
			string text = CodeWindowErrorStringRef.Invoke(targetWindow);
			int num = CodeWindowErrorStartIndexRef.Invoke(targetWindow);
			int num2 = CodeWindowErrorEndIndexRef.Invoke(targetWindow);
			if (string.IsNullOrWhiteSpace(text))
			{
				return "parse failed: " + targetScriptName;
			}
			return $"parse failed: {targetScriptName} error={text} start={num} end={num2}";
		}

		private static void MarkFailedWithOutput(OracleRunnerStateFile state, string message, DateTimeOffset finishedAt)
		{
			RunnerStateProtocol.MarkFailed(state, message, finishedAt);
			WriteOracleErrorToOutput(state.RequestId, message);
		}

		private static void WriteOracleErrorToOutput(int requestId, string message)
		{
			string arg = message.Replace('\r', ' ').Replace('\n', ' ');
			Logger.Log($"oracle_error request_id={requestId} {arg}");
		}

		private void LogLeaderboardLine(string line)
		{
			Log.LogInfo((object)line);
			Logger.Log(line);
		}

		private CodeWindow? FindTargetWindow(Workspace workspace, string targetScriptName)
		{
			if (workspace.codeWindows.TryGetValue(targetScriptName, out var value))
			{
				return value;
			}
			foreach (KeyValuePair<string, CodeWindow> codeWindow in workspace.codeWindows)
			{
				if (string.Equals(codeWindow.Key, targetScriptName, StringComparison.OrdinalIgnoreCase))
				{
					return codeWindow.Value;
				}
			}
			return null;
		}

		private void MarkMenuPlayed()
		{
			playInvoked = true;
		}

		private static string ResolveLeaderboardScriptName(MainSim mainSim, Simulation? sim)
		{
			LeaderboardStartArgs val = PrevLeaderboardStartRef.Invoke(mainSim);
			if (!string.IsNullOrWhiteSpace(val?.fileName))
			{
				return val.fileName;
			}
			if (!string.IsNullOrWhiteSpace(sim?.leaderboardName))
			{
				return sim.leaderboardName;
			}
			return "leaderboard";
		}
	}
	public enum RunnerRequestStatus
	{
		Idle,
		Requested,
		Running,
		StopRequested,
		Done,
		Failed,
		Superseded
	}
	public static class RunnerStateProtocol
	{
		public const string StateFileName = "mlj.tfwr.oracle-runner.state.json";

		public static string NormalizeTargetScriptName(string? configuredName)
		{
			string text = (configuredName ?? string.Empty).Trim();
			if (text.EndsWith(".py", StringComparison.OrdinalIgnoreCase))
			{
				string text2 = text;
				text = text2.Substring(0, text2.Length - 3);
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				return text;
			}
			return "lb_start";
		}

		public static OracleRunnerStateFile CreateIdle(int requestId)
		{
			return new OracleRunnerStateFile
			{
				RequestId = requestId,
				Status = RunnerRequestStatus.Idle,
				TargetScript = null,
				TimeoutSeconds = null,
				StartedAt = null,
				FinishedAt = null,
				LastError = null
			};
		}

		public static OracleRunnerStateFile CreateRequested(int requestId, string targetScript, double timeoutSeconds)
		{
			return new OracleRunnerStateFile
			{
				RequestId = requestId,
				Status = RunnerRequestStatus.Requested,
				TargetScript = NormalizeTargetScriptName(targetScript),
				TimeoutSeconds = timeoutSeconds,
				StartedAt = null,
				FinishedAt = null,
				LastError = null
			};
		}

		public static void MarkSuperseded(OracleRunnerStateFile state, int supersededByRequestId, DateTimeOffset finishedAt)
		{
			state.Status = RunnerRequestStatus.Superseded;
			state.FinishedAt = ToIso8601(finishedAt);
			state.LastError = $"superseded by request_id={supersededByRequestId}";
		}

		public static void MarkRunning(OracleRunnerStateFile state, DateTimeOffset startedAt)
		{
			state.Status = RunnerRequestStatus.Running;
			state.StartedAt = ToIso8601(startedAt);
			state.FinishedAt = null;
			state.LastError = null;
		}

		public static void MarkStopRequested(OracleRunnerStateFile state, string message)
		{
			state.Status = RunnerRequestStatus.StopRequested;
			state.LastError = message;
		}

		public static void MarkDone(OracleRunnerStateFile state, DateTimeOffset finishedAt)
		{
			state.Status = RunnerRequestStatus.Done;
			state.FinishedAt = ToIso8601(finishedAt);
			state.LastError = null;
		}

		public static void MarkFailed(OracleRunnerStateFile state, string message, DateTimeOffset finishedAt)
		{
			state.Status = RunnerRequestStatus.Failed;
			state.FinishedAt = ToIso8601(finishedAt);
			state.LastError = message;
		}

		public static string ToIso8601(DateTimeOffset value)
		{
			return value.ToString("O");
		}

		public static string ToStatusText(RunnerRequestStatus status)
		{
			if (status == RunnerRequestStatus.StopRequested)
			{
				return "stop_requested";
			}
			return status.ToString().ToLowerInvariant();
		}

		public static RunnerRequestStatus ParseStatusText(string? text)
		{
			return (text ?? string.Empty).Trim().ToLowerInvariant() switch
			{
				"idle" => RunnerRequestStatus.Idle, 
				"requested" => RunnerRequestStatus.Requested, 
				"running" => RunnerRequestStatus.Running, 
				"stop_requested" => RunnerRequestStatus.StopRequested, 
				"done" => RunnerRequestStatus.Done, 
				"failed" => RunnerRequestStatus.Failed, 
				"superseded" => RunnerRequestStatus.Superseded, 
				_ => throw new SerializationException("未知请求状态:" + text), 
			};
		}
	}
	public sealed class RunnerStateStore
	{
		private readonly string stateFilePath;

		private readonly object gate = new object();

		private OracleRunnerStateFile? pendingState;

		public string StateFilePath => stateFilePath;

		public RunnerStateStore(string stateFilePath)
		{
			this.stateFilePath = stateFilePath;
		}

		public OracleRunnerStateFile? Read()
		{
			lock (gate)
			{
				string directoryName = Path.GetDirectoryName(stateFilePath);
				if (!string.IsNullOrWhiteSpace(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				string path = stateFilePath + ".lock";
				using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
				try
				{
					OracleRunnerStateFile oracleRunnerStateFile = ReadCurrent();
					if (pendingState == null)
					{
						return oracleRunnerStateFile;
					}
					if (oracleRunnerStateFile == null || (oracleRunnerStateFile.RequestId == pendingState.RequestId && (pendingState.Status != RunnerRequestStatus.Running || oracleRunnerStateFile.Status == RunnerRequestStatus.Requested || oracleRunnerStateFile.Status == RunnerRequestStatus.Running)))
					{
						WriteCurrent(pendingState);
						oracleRunnerStateFile = pendingState;
					}
					pendingState = null;
					return oracleRunnerStateFile;
				}
				finally
				{
					fileStream.Dispose();
					File.Delete(path);
				}
			}
		}

		public void Write(OracleRunnerStateFile state)
		{
			lock (gate)
			{
				pendingState = new OracleRunnerStateFile
				{
					RequestId = state.RequestId,
					Status = state.Status,
					TargetScript = state.TargetScript,
					TimeoutSeconds = state.TimeoutSeconds,
					StartedAt = state.StartedAt,
					FinishedAt = state.FinishedAt,
					LastError = state.LastError
				};
			}
		}

		private OracleRunnerStateFile? ReadCurrent()
		{
			try
			{
				using FileStream stream = new FileStream(stateFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
				OracleRunnerStateFile oracleRunnerStateFile = (new DataContractJsonSerializer(typeof(OracleRunnerStateFile)).ReadObject(stream) as OracleRunnerStateFile) ?? throw new SerializationException("状态文件必须是 JSON 对象。");
				if (oracleRunnerStateFile.RequestId < 0 || (oracleRunnerStateFile.TimeoutSeconds.HasValue && (double.IsNaN(oracleRunnerStateFile.TimeoutSeconds.Value) || double.IsInfinity(oracleRunnerStateFile.TimeoutSeconds.Value) || oracleRunnerStateFile.TimeoutSeconds.Value < 0.0)))
				{
					throw new SerializationException("request_id 和 timeout_seconds 必须是非负有限数值。");
				}
				return oracleRunnerStateFile;
			}
			catch (FileNotFoundException)
			{
				return null;
			}
		}

		private void WriteCurrent(OracleRunnerStateFile state)
		{
			string text = $"{stateFilePath}.{Guid.NewGuid():N}.tmp";
			try
			{
				using (FileStream stream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None))
				{
					new DataContractJsonSerializer(typeof(OracleRunnerStateFile)).WriteObject(stream, state);
				}
				if (File.Exists(stateFilePath))
				{
					File.Replace(text, stateFilePath, null, ignoreMetadataErrors: true);
				}
				else
				{
					File.Move(text, stateFilePath);
				}
			}
			finally
			{
				File.Delete(text);
			}
		}
	}
}