Decompiled source of HostOnlyStart v2.0.0

HostOnlyStart.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 HostOnlyStart
{
	[HarmonyPatch(typeof(CameraAim), "Update")]
	internal static class CameraAimUpdatePatch
	{
		private static bool Prefix()
		{
			return !Plugin.IsMenuMouseCaptured();
		}
	}
	[HarmonyPatch(typeof(MenuPageLobby), "ButtonStart")]
	internal static class MenuPageLobbyButtonStartPatch
	{
		private static bool Prefix()
		{
			return Plugin.CanLocalPlayerStart();
		}
	}
	[BepInPlugin("local.repo.hostonlystart", "Host Only Start", "2.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal enum TruckStartDecision
		{
			AllowVanilla,
			Blocked,
			DirectSwitchHandled
		}

		private static Plugin Instance;

		internal static ConfigEntry<bool> showDeniedMessage;

		internal static ConfigEntry<string> allowedSteamIds;

		internal static ConfigEntry<string> allowedPlayerNames;

		private static HashSet<string> allowedSteamIdCache = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static HashSet<string> allowedPlayerNameCache = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static string allowedSteamIdCacheSource = null;

		private static string allowedPlayerNameCacheSource = null;

		private static ConfigEntry<KeyCode> menuKey;

		private const float TruckScreenStarterTtl = 30f;

		private const float AuthorizedTruckStartTtl = 30f;

		private static PlayerAvatar lastTruckScreenStarter;

		private static string lastTruckScreenStarterSteamId = string.Empty;

		private static string lastTruckScreenStarterName = string.Empty;

		private static float lastTruckScreenStarterTime = -1000f;

		private static float lastAuthorizedTruckStartTime = -1000f;

		private Harmony harmony;

		private Rect menuRect = new Rect(80f, 60f, 1100f, 720f);

		private bool menuVisible;

		private int listeningHotkey;

		private float lastDeniedScreenMessageTime;

		private void Awake()
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			Instance = this;
			showDeniedMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("Feedback", "ShowDeniedMessage", true, "Show a small UI focus message when a client is blocked from starting.");
			allowedSteamIds = ((BaseUnityPlugin)this).Config.Bind<string>("Permissions", "AllowedClientSteamIds", string.Empty, "Comma-separated Steam IDs allowed to start the truck when they are not host.");
			allowedPlayerNames = ((BaseUnityPlugin)this).Config.Bind<string>("Permissions", "AllowedClientPlayerNames", string.Empty, "Comma-separated player names allowed to start the truck when they are not host. Steam IDs are preferred.");
			menuKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("UI", "MenuKey", (KeyCode)278, "Host-only key to open the truck start permission menu.");
			harmony = new Harmony("local.repo.hostonlystart");
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Host Only Start 2.0.0 loaded.");
		}

		private void OnDestroy()
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (Input.GetKeyDown(menuKey.Value))
			{
				SetMenuVisible(!menuVisible);
			}
		}

		private void OnGUI()
		{
			if (menuVisible)
			{
				DrawMenuPanel();
			}
		}

		internal static bool IsMenuMouseCaptured()
		{
			if ((Object)(object)Instance != (Object)null)
			{
				return Instance.menuVisible;
			}
			return false;
		}

		private void SetMenuVisible(bool visible)
		{
			menuVisible = visible;
			Cursor.visible = visible;
			Cursor.lockState = (CursorLockMode)(!visible);
		}

		private bool ManualButton(Rect rect, string text, bool enabled)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			bool enabled2 = GUI.enabled;
			GUI.enabled = enabled;
			bool result = GUI.Button(rect, text);
			GUI.enabled = enabled2;
			return result;
		}

		private void DrawMenuPanel()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_041c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0445: Unknown result type (might be due to invalid IL or missing references)
			//IL_046e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0325: Unknown result type (might be due to invalid IL or missing references)
			GUI.Box(menuRect, "Host Only Start - 发车权限");
			float num = ((Rect)(ref menuRect)).x + 16f;
			float num2 = ((Rect)(ref menuRect)).y + 34f;
			float num3 = ((Rect)(ref menuRect)).width - 32f;
			bool flag = !GameManager.Multiplayer() || PhotonNetwork.IsMasterClient;
			GUI.Label(new Rect(num, num2, num3, 22f), "");
			num2 += 0f;
			if (!flag)
			{
				GUI.Label(new Rect(num, num2, num3, 22f), "只有房主可以修改发车权限。");
				num2 += 28f;
			}
			List<PlayerAvatar> list = new List<PlayerAvatar>(GetPlayers());
			GUI.Label(new Rect(num, num2, num3, 22f), "玩家列表(" + list.Count + "/20)");
			num2 += 28f;
			if (ManualButton(new Rect(num, num2, 150f, 26f), "增加全部客机", flag))
			{
				SetAllClientsAllowed(list, allowed: true);
			}
			if (ManualButton(new Rect(num + 160f, num2, 150f, 26f), "撤销全部", flag))
			{
				SetAllClientsAllowed(list, allowed: false);
			}
			if (ManualButton(new Rect(((Rect)(ref menuRect)).x + ((Rect)(ref menuRect)).width - 216f, num2, 90f, 26f), "重载配置", enabled: true))
			{
				((BaseUnityPlugin)this).Config.Reload();
			}
			if (ManualButton(new Rect(((Rect)(ref menuRect)).x + ((Rect)(ref menuRect)).width - 116f, num2, 90f, 26f), "关闭", enabled: true))
			{
				SetMenuVisible(visible: false);
			}
			num2 += 36f;
			float num4 = (num3 - 12f) / 2f;
			Rect val = default(Rect);
			for (int i = 0; i < Math.Min(list.Count, 20); i++)
			{
				PlayerAvatar target = list[i];
				string fieldValue = GetFieldValue(target, "steamID");
				string fieldValue2 = GetFieldValue(target, "playerName");
				bool flag2 = IsTrue(GetFieldValue(target, "isLocal"));
				bool flag3 = flag2 || IsAllowed(fieldValue, fieldValue2);
				int num5 = i / 10;
				int num6 = i % 10;
				float num7 = num + (float)num5 * (num4 + 12f);
				float num8 = num2 + (float)num6 * 36f;
				((Rect)(ref val))..ctor(num7, num8, num4, 32f);
				GUI.Box(val, GUIContent.none);
				GUI.Label(new Rect(((Rect)(ref val)).x + 8f, ((Rect)(ref val)).y + 5f, 190f, 20f), ShortText(fieldValue2, 18));
				GUI.Label(new Rect(((Rect)(ref val)).x + 198f, ((Rect)(ref val)).y + 5f, 72f, 20f), flag2 ? "房主/本机" : (flag3 ? "已允许" : "已禁止"));
				string text = (flag3 ? "撤销" : "允许");
				if (ManualButton(new Rect(((Rect)(ref val)).x + ((Rect)(ref val)).width - 76f, ((Rect)(ref val)).y + 3f, 68f, 26f), text, flag && !flag2))
				{
					SetAllowed(fieldValue, fieldValue2, !flag3);
				}
			}
			float num9 = ((Rect)(ref menuRect)).y + ((Rect)(ref menuRect)).height - 100f;
			GUI.Label(new Rect(num, num9, num3, 22f), "已授权 SteamID:");
			GUI.TextArea(new Rect(num, num9 + 22f, num3, 44f), allowedSteamIds.Value);
			GUI.Label(new Rect(num, num9 + 70f, 130f, 22f), "菜单热键:" + ((object)menuKey.Value/*cast due to .constrained prefix*/).ToString());
			DrawHotkeyButton(new Rect(num + 140f, num9 + 68f, 80f, 26f), (KeyCode)278);
			DrawHotkeyButton(new Rect(num + 226f, num9 + 68f, 80f, 26f), (KeyCode)277);
			DrawHotkeyButton(new Rect(num + 312f, num9 + 68f, 80f, 26f), (KeyCode)291);
		}

		private unsafe void DrawHotkeyButton(Rect rect, KeyCode key)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if (ManualButton(rect, ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(), enabled: true))
			{
				menuKey.Value = key;
				((BaseUnityPlugin)this).Config.Save();
			}
		}

		private static string ShortText(string value, int maxLength)
		{
			if (string.IsNullOrEmpty(value) || value.Length <= maxLength)
			{
				return value;
			}
			return value.Substring(0, maxLength - 1) + "...";
		}

		private static bool IsTrue(string value)
		{
			return string.Equals(value, "True", StringComparison.OrdinalIgnoreCase);
		}

		private static bool IsAllowed(string steamId, string playerName)
		{
			RefreshAllowedCache();
			if (string.IsNullOrWhiteSpace(steamId) || !allowedSteamIdCache.Contains(steamId.Trim()))
			{
				if (!string.IsNullOrWhiteSpace(playerName))
				{
					return allowedPlayerNameCache.Contains(playerName.Trim());
				}
				return false;
			}
			return true;
		}

		private static void RefreshAllowedCache()
		{
			string text = ((allowedSteamIds != null) ? allowedSteamIds.Value : string.Empty);
			string text2 = ((allowedPlayerNames != null) ? allowedPlayerNames.Value : string.Empty);
			if (!string.Equals(allowedSteamIdCacheSource, text, StringComparison.Ordinal) || !string.Equals(allowedPlayerNameCacheSource, text2, StringComparison.Ordinal))
			{
				allowedSteamIdCacheSource = text;
				allowedPlayerNameCacheSource = text2;
				allowedSteamIdCache = BuildAllowedSet(text);
				allowedPlayerNameCache = BuildAllowedSet(text2);
			}
		}

		private static HashSet<string> BuildAllowedSet(string csv)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (!string.IsNullOrWhiteSpace(csv))
			{
				string[] array = csv.Split(new char[5] { ',', ';', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length > 0)
					{
						hashSet.Add(text);
					}
				}
			}
			return hashSet;
		}

		private static void GetPlayerIdentity(PlayerAvatar player, out string steamId, out string playerName, out bool isLocal)
		{
			steamId = GetFieldValue(player, "steamID");
			playerName = GetFieldValue(player, "playerName");
			isLocal = IsTrue(GetFieldValue(player, "isLocal"));
		}

		private static bool IsPlayerAuthorized(PlayerAvatar player, out string steamId, out string playerName, out bool isLocal)
		{
			GetPlayerIdentity(player, out steamId, out playerName, out isLocal);
			if (!isLocal)
			{
				return IsAllowed(steamId, playerName);
			}
			return true;
		}

		internal static bool AllowChatMessageSend(TruckScreenText screen, string playerName, string source)
		{
			if (!IsTruckScreenWhereStartNeedsPermission(screen))
			{
				return true;
			}
			if (!GameManager.Multiplayer())
			{
				return true;
			}
			if (!PhotonNetwork.IsMasterClient)
			{
				return CanLocalPlayerStart();
			}
			PlayerAvatar val = GetPlayerByName(playerName);
			if ((Object)(object)val == (Object)null)
			{
				val = GetFreshTruckScreenStarter(screen);
			}
			if ((Object)(object)val == (Object)null)
			{
				DenyStart(playerName, string.Empty);
				ResetTruckScreenChatInput(screen, source + " denied unknown player");
				return false;
			}
			if (IsPlayerAuthorized(val, out var steamId, out var playerName2, out var _))
			{
				RememberTruckScreenStarter(val, source);
				return true;
			}
			DenyStart(playerName2, steamId);
			ResetTruckScreenChatInput(screen, source + " denied " + playerName2);
			ReleaseUnauthorizedTruckScreenGrabber(screen, val, source + " denied " + playerName2);
			return false;
		}

		internal static bool AllowTruckScreenStartChat(TruckScreenText screen, string source)
		{
			if (!IsTruckScreenWhereStartNeedsPermission(screen))
			{
				return true;
			}
			if (!GameManager.Multiplayer())
			{
				return true;
			}
			if (!PhotonNetwork.IsMasterClient)
			{
				bool num = CanLocalPlayerStart();
				if (!num)
				{
					ResetTruckScreenChatInput(screen, source + " local denied");
				}
				return num;
			}
			PlayerAvatar freshTruckScreenStarter = GetFreshTruckScreenStarter(screen);
			if ((Object)(object)freshTruckScreenStarter == (Object)null)
			{
				DenyStart("unknown", string.Empty);
				ResetTruckScreenChatInput(screen, source + " denied unknown starter");
				ReleaseUnauthorizedTruckScreenGrabber(screen, null, source + " unknown starter");
				ClearTruckScreenStarter(source + " unknown starter");
				return false;
			}
			if (IsPlayerAuthorized(freshTruckScreenStarter, out var steamId, out var playerName, out var _))
			{
				RememberTruckScreenStarter(freshTruckScreenStarter, source);
				return true;
			}
			DenyStart(playerName, steamId);
			ResetTruckScreenChatInput(screen, source + " denied " + playerName);
			ReleaseUnauthorizedTruckScreenGrabber(screen, freshTruckScreenStarter, source + " denied " + playerName);
			ClearTruckScreenStarter(source + " denied start chat");
			return false;
		}

		internal static bool CanLocalPlayerStart()
		{
			if (!GameManager.Multiplayer() || PhotonNetwork.IsMasterClient)
			{
				return true;
			}
			if (IsPlayerAuthorized(GetLocalPlayer(), out var steamId, out var playerName, out var _))
			{
				return true;
			}
			DenyStart(playerName, steamId);
			return false;
		}

		internal static bool CanTruckScreenStart(TruckScreenText screen, bool allowHostFallback)
		{
			return CanTruckScreenStart(screen, allowHostFallback, "TruckScreenStart");
		}

		internal static bool CanTruckScreenStart(TruckScreenText screen, bool allowHostFallback, string source)
		{
			if (!GameManager.Multiplayer())
			{
				return true;
			}
			if (!PhotonNetwork.IsMasterClient)
			{
				return CanLocalPlayerStart();
			}
			if (allowHostFallback && HasRecentAuthorizedTruckStart() && IsLateTruckScreenSource(source))
			{
				if (AreVanillaTruckDepartureConditionsMet(out var reason))
				{
					LogInfo("Allowing " + source + " from recent authorized truck start after condition recheck.");
					return true;
				}
				LogInfo("Blocking " + source + " recent-authorized shortcut because conditions are not ready: " + reason + ".");
				return false;
			}
			PlayerAvatar val = GetFreshTruckScreenStarter(screen);
			if ((Object)(object)val == (Object)null && allowHostFallback)
			{
				val = GetLocalPlayer();
				LogInfo("Truck start using host fallback from " + source + ".");
			}
			if ((Object)(object)val == (Object)null)
			{
				if (HasRecentAuthorizedTruckStart())
				{
					if (AreVanillaTruckDepartureConditionsMet(out var reason2))
					{
						LogInfo("Allowing " + source + " with no fresh starter because a truck start was recently authorized and conditions are ready.");
						return true;
					}
					LogInfo("Blocking " + source + " with no fresh starter because conditions are not ready: " + reason2 + ".");
					return false;
				}
				DenyStart("unknown", string.Empty);
				ResetLocalTruckScreenState(screen, source + " denied unknown starter");
				ClearTruckScreenStarter(source + " unknown starter");
				return false;
			}
			return AuthorizeTruckScreenStarter(screen, val, source);
		}

		internal static void RememberTruckScreenStarter(StaticGrabObject grabObject, int grabberPhotonViewId)
		{
			TryGetTruckScreenGrabber(grabObject, grabberPhotonViewId, out var _);
		}

		internal static bool AllowTruckScreenGrab(StaticGrabObject grabObject, int grabberPhotonViewId)
		{
			if (!TryGetTruckScreenGrabber(grabObject, grabberPhotonViewId, out var player))
			{
				return true;
			}
			IsPlayerAuthorized(player, out var steamId, out var playerName, out var isLocal);
			LogInfo("Truck screen grab by '" + playerName + "' (" + steamId + "), isLocal=" + isLocal + ".");
			if (!isLocal)
			{
				return IsAllowed(steamId, playerName);
			}
			return true;
		}

		private static bool TryGetTruckScreenGrabber(StaticGrabObject grabObject, int grabberPhotonViewId, out PlayerAvatar player)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			player = null;
			if (!GameManager.Multiplayer() || !PhotonNetwork.IsMasterClient || !IsTruckScreenGrabObject(grabObject))
			{
				return false;
			}
			try
			{
				PhotonView val = PhotonView.Find(grabberPhotonViewId);
				object objectFieldValue = GetObjectFieldValue(((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<PhysGrabber>() : null, "playerAvatar");
				player = (PlayerAvatar)((objectFieldValue is PlayerAvatar) ? objectFieldValue : null);
				if ((Object)(object)player == (Object)null)
				{
					return false;
				}
				RememberTruckScreenStarter(player, "GrabStartedRPC");
				return true;
			}
			catch (Exception ex)
			{
				LogWarning("Failed to remember truck screen starter: " + ex.Message);
				return false;
			}
		}

		private static void RememberTruckScreenStarter(PlayerAvatar player, string source)
		{
			if (!((Object)(object)player == (Object)null))
			{
				lastTruckScreenStarter = player;
				lastTruckScreenStarterSteamId = GetFieldValue(player, "steamID");
				lastTruckScreenStarterName = GetFieldValue(player, "playerName");
				lastTruckScreenStarterTime = Time.time;
				LogInfo("Remembered truck screen starter '" + lastTruckScreenStarterName + "' (" + lastTruckScreenStarterSteamId + ") from " + source + ".");
			}
		}

		private static PlayerAvatar GetFreshTruckScreenStarter(TruckScreenText screen)
		{
			PlayerAvatar truckScreenStarter = GetTruckScreenStarter(screen);
			if ((Object)(object)truckScreenStarter != (Object)null)
			{
				RememberTruckScreenStarter(truckScreenStarter, "current grabber");
				return truckScreenStarter;
			}
			if ((Object)(object)lastTruckScreenStarter != (Object)null && Time.time - lastTruckScreenStarterTime <= 30f)
			{
				return lastTruckScreenStarter;
			}
			if ((Object)(object)lastTruckScreenStarter != (Object)null)
			{
				ClearTruckScreenStarter("starter expired");
			}
			return null;
		}

		private static bool AuthorizeTruckScreenStarter(TruckScreenText screen, PlayerAvatar player, string source)
		{
			return AuthorizeTruckScreenStarter(screen, player, source, showMessage: true);
		}

		private static bool AuthorizeTruckScreenStarter(TruckScreenText screen, PlayerAvatar player, string source, bool showMessage)
		{
			return AuthorizeTruckScreenStarter(screen, player, source, showMessage, resetOnDeny: true);
		}

		private static bool AuthorizeTruckScreenStarter(TruckScreenText screen, PlayerAvatar player, string source, bool showMessage, bool resetOnDeny)
		{
			string steamId;
			string playerName;
			bool isLocal;
			bool flag = IsPlayerAuthorized(player, out steamId, out playerName, out isLocal);
			LogInfo("Truck start requested by '" + playerName + "' (" + steamId + "), isLocal=" + isLocal + ", source=" + source + ".");
			if (flag)
			{
				MarkAuthorizedTruckStart(player, source);
				if (showMessage && (Object)(object)screen != (Object)null)
				{
					screen.MessageSendCustom("", string.Concat("发车: " + playerName, "\n"), 1);
				}
				return true;
			}
			DenyStart(playerName, steamId);
			if (resetOnDeny)
			{
				ResetLocalTruckScreenState(screen, source + " denied " + playerName);
				ClearTruckScreenStarter(source + " denied");
			}
			else
			{
				LogInfo("Ignored unauthorized " + source + " by '" + playerName + "' without resetting UI because departure conditions are ready.");
			}
			return false;
		}

		private static void MarkAuthorizedTruckStart(PlayerAvatar player, string source)
		{
			lastAuthorizedTruckStartTime = Time.time;
			LogInfo("Authorized truck start from " + source + " by '" + GetFieldValue(player, "playerName") + "' (" + GetFieldValue(player, "steamID") + ").");
		}

		private static bool HasRecentAuthorizedTruckStart()
		{
			return Time.time - lastAuthorizedTruckStartTime <= 30f;
		}

		private static bool IsLateTruckScreenSource(string source)
		{
			if (!string.Equals(source, "GotoNextLevel", StringComparison.OrdinalIgnoreCase) && !string.Equals(source, "ShopGotoNextLevel", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(source, "DelayedLevelSwitch", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		internal static void ClearTruckScreenStarter(string reason)
		{
			if ((Object)(object)lastTruckScreenStarter != (Object)null || !string.IsNullOrEmpty(lastTruckScreenStarterName) || !string.IsNullOrEmpty(lastTruckScreenStarterSteamId))
			{
				LogInfo("Cleared truck screen starter because " + reason + ". Last starter was '" + lastTruckScreenStarterName + "' (" + lastTruckScreenStarterSteamId + ").");
			}
			lastTruckScreenStarter = null;
			lastTruckScreenStarterSteamId = string.Empty;
			lastTruckScreenStarterName = string.Empty;
			lastTruckScreenStarterTime = -1000f;
		}

		internal static TruckStartDecision GetTruckStartDecision(TruckScreenText screen, string source)
		{
			string reason;
			bool flag = AreVanillaTruckDepartureConditionsMet(out reason);
			if (!CanTruckScreenStartForDecision(screen, source, flag))
			{
				if (!flag)
				{
					ResetLocalTruckScreenState(screen, source + " denied");
				}
				else
				{
					LogInfo("Ignored unauthorized " + source + " without resetting UI because departure conditions are ready.");
				}
				return TruckStartDecision.Blocked;
			}
			if (!flag)
			{
				LogInfo("Allowing vanilla truck UI from " + source + ": " + reason + ".");
				return TruckStartDecision.AllowVanilla;
			}
			if (TryDirectChangeToNextStage(screen, source))
			{
				return TruckStartDecision.DirectSwitchHandled;
			}
			return TruckStartDecision.AllowVanilla;
		}

		private static bool CanTruckScreenStartForDecision(TruckScreenText screen, string source, bool conditionsMet)
		{
			if (!GameManager.Multiplayer())
			{
				return true;
			}
			if (!PhotonNetwork.IsMasterClient)
			{
				return CanLocalPlayerStart();
			}
			PlayerAvatar freshTruckScreenStarter = GetFreshTruckScreenStarter(screen);
			if ((Object)(object)freshTruckScreenStarter == (Object)null)
			{
				if (HasRecentAuthorizedTruckStart())
				{
					LogInfo("Allowing " + source + " with no fresh starter because a truck start was recently authorized.");
					return true;
				}
				DenyStart("unknown", string.Empty);
				if (!conditionsMet)
				{
					ResetLocalTruckScreenState(screen, source + " denied unknown starter");
				}
				else
				{
					LogInfo("Ignored unknown " + source + " without resetting UI because departure conditions are ready.");
				}
				ClearTruckScreenStarter(source + " unknown starter");
				return false;
			}
			return AuthorizeTruckScreenStarter(screen, freshTruckScreenStarter, source, showMessage: false, !conditionsMet);
		}

		private static bool AreVanillaTruckDepartureConditionsMet(out string reason)
		{
			reason = string.Empty;
			if (GameManager.Multiplayer() && !PhotonNetwork.IsMasterClient)
			{
				reason = "local client is not master";
				return false;
			}
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				reason = "RunManager is missing";
				return false;
			}
			try
			{
				if (SemiFunc.RunIsLevel() && (Object)(object)RoundDirector.instance != (Object)null)
				{
					int intFieldValue = GetIntFieldValue(RoundDirector.instance, "extractionPoints");
					int intFieldValue2 = GetIntFieldValue(RoundDirector.instance, "extractionPointsCompleted");
					bool boolFieldValue = GetBoolFieldValue(RoundDirector.instance, "allExtractionPointsCompleted");
					if (intFieldValue > 0 && intFieldValue2 < intFieldValue && !boolFieldValue)
					{
						reason = "extraction points incomplete " + intFieldValue2 + "/" + intFieldValue;
						return false;
					}
				}
			}
			catch (Exception ex)
			{
				reason = "extraction condition check failed: " + ex.Message;
				return false;
			}
			reason = "ready";
			return true;
		}

		private static bool TryDirectChangeToNextStage(TruckScreenText screen, string source)
		{
			if ((Object)(object)RunManager.instance == (Object)null)
			{
				LogWarning("Direct next stage skipped from " + source + ": RunManager is missing.");
				return false;
			}
			try
			{
				lastAuthorizedTruckStartTime = Time.time;
				LogInfo("Direct next stage from " + source + ".");
				ClearTruckScreenStarter("direct next stage");
				RunManager.instance.ChangeLevel(true, false, (ChangeLevelType)0);
				return true;
			}
			catch (Exception ex)
			{
				LogWarning("Direct next stage failed from " + source + ": " + ex.Message);
				return false;
			}
		}

		internal static bool CanHostChangeLevel(ChangeLevelType changeLevelType)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected I4, but got Unknown
			if (!GameManager.Multiplayer() || !PhotonNetwork.IsMasterClient)
			{
				return true;
			}
			int num = (int)changeLevelType;
			if (num == 2 || num == 3 || num == 4 || num == 6)
			{
				return true;
			}
			if (!IsDepartLevel() || (Object)(object)TruckScreenText.instance == (Object)null)
			{
				return true;
			}
			if (HasRecentAuthorizedTruckStart())
			{
				LogInfo("Allowing ChangeLevel " + num + " from recent authorized truck start.");
				ClearTruckScreenStarter("ChangeLevel authorized");
				return true;
			}
			PlayerAvatar freshTruckScreenStarter = GetFreshTruckScreenStarter(TruckScreenText.instance);
			if ((Object)(object)freshTruckScreenStarter != (Object)null)
			{
				return AuthorizeTruckScreenStarter(TruckScreenText.instance, freshTruckScreenStarter, "RunManager.ChangeLevel");
			}
			LogWarning("Allowing ChangeLevel " + num + " with no fresh truck screen starter to avoid stuck depart state.");
			ResetLocalTruckScreenState(TruckScreenText.instance, "ChangeLevel safe fallback");
			ClearTruckScreenStarter("ChangeLevel safe fallback");
			return true;
		}

		internal static bool ShouldSuppressClientLocalLevelSwitch()
		{
			if (GameManager.Multiplayer())
			{
				return !PhotonNetwork.IsMasterClient;
			}
			return false;
		}

		internal static void ResetLocalTruckScreenState(TruckScreenText screen)
		{
			ResetLocalTruckScreenState(screen, "unspecified");
		}

		internal static void ResetLocalTruckScreenState(TruckScreenText screen, string reason)
		{
			if (!((Object)(object)screen == (Object)null))
			{
				SetField(screen, "started", false);
				SetField(screen, "lobbyStarted", false);
				SetField(screen, "startWaitTimer", 0f);
				LogInfo("Reset truck screen state: " + reason + ".");
			}
		}

		internal static IEnumerator EmptyCoroutine()
		{
			yield break;
		}

		private static PlayerAvatar GetLocalPlayer()
		{
			foreach (PlayerAvatar player in GetPlayers())
			{
				if (IsTrue(GetFieldValue(player, "isLocal")))
				{
					return player;
				}
			}
			return null;
		}

		private static IEnumerable<PlayerAvatar> GetPlayers()
		{
			if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null)
			{
				return (IEnumerable<PlayerAvatar>)(object)new PlayerAvatar[0];
			}
			List<PlayerAvatar> list = new List<PlayerAvatar>();
			foreach (PlayerAvatar player in GameDirector.instance.PlayerList)
			{
				if ((Object)(object)player != (Object)null)
				{
					list.Add(player);
				}
			}
			return list;
		}

		private static PlayerAvatar GetPlayerByName(string playerName)
		{
			foreach (PlayerAvatar player in GetPlayers())
			{
				if (string.Equals(GetFieldValue(player, "playerName"), playerName, StringComparison.OrdinalIgnoreCase))
				{
					return player;
				}
			}
			return null;
		}

		private static bool IsTruckScreenWhereStartNeedsPermission(TruckScreenText screen)
		{
			return (Object)(object)screen != (Object)null;
		}

		private static void ReleaseUnauthorizedTruckScreenGrabber(TruckScreenText screen, PlayerAvatar deniedPlayer, string reason)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			try
			{
				object objectFieldValue = GetObjectFieldValue(screen, "staticGrabObject");
				StaticGrabObject val = (StaticGrabObject)((objectFieldValue is StaticGrabObject) ? objectFieldValue : null);
				if ((Object)(object)val == (Object)null || !(GetObjectFieldValue(val, "playerGrabbing") is IList list))
				{
					return;
				}
				for (int num = list.Count - 1; num >= 0; num--)
				{
					object? obj = list[num];
					PhysGrabber val2 = (PhysGrabber)((obj is PhysGrabber) ? obj : null);
					if (!((Object)(object)val2 == (Object)null))
					{
						object objectFieldValue2 = GetObjectFieldValue(val2, "playerAvatar");
						PlayerAvatar val3 = (PlayerAvatar)((objectFieldValue2 is PlayerAvatar) ? objectFieldValue2 : null);
						if (!((Object)(object)val3 == (Object)null))
						{
							string steamId;
							string playerName;
							bool isLocal;
							bool num2 = IsPlayerAuthorized(val3, out steamId, out playerName, out isLocal);
							bool flag = (Object)(object)deniedPlayer == (Object)null || (Object)(object)deniedPlayer == (Object)(object)val3;
							if (!num2 && flag)
							{
								val2.OverrideGrabRelease(-1, 0.5f);
								val.GrabEnded(val2);
								LogInfo("Released unauthorized truck screen grabber '" + playerName + "' (" + steamId + ") because " + reason + ".");
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				LogWarning("Failed to release unauthorized truck screen grabber: " + ex.Message);
			}
		}

		private static void ResetTruckScreenChatInput(TruckScreenText screen, string reason)
		{
			if (!((Object)(object)screen == (Object)null))
			{
				SetField(screen, "chatActive", false);
				SetField(screen, "chatActiveTimer", 0f);
				SetField(screen, "chatMessageTimer", 0f);
				SetField(screen, "chatCharacterIndex", 0);
				SetField(screen, "chatDeactivatedTimer", 0f);
				LogInfo("Reset truck screen chat input: " + reason + ".");
			}
		}

		private static void SetAllClientsAllowed(List<PlayerAvatar> players, bool allowed)
		{
			if (!allowed)
			{
				allowedSteamIds.Value = "";
				allowedPlayerNames.Value = "";
			}
			foreach (PlayerAvatar player in players)
			{
				if (!((Object)(object)player == (Object)null) && !IsTrue(GetFieldValue(player, "isLocal")))
				{
					SetAllowed(GetFieldValue(player, "steamID"), GetFieldValue(player, "playerName"), allowed);
				}
			}
			((BaseUnityPlugin)Instance).Config.Save();
		}

		private static void SetAllowed(string steamId, string playerName, bool allowed)
		{
			if (!string.IsNullOrWhiteSpace(steamId))
			{
				allowedSteamIds.Value = UpdateCsv(allowedSteamIds.Value, steamId, allowed);
			}
			else if (!string.IsNullOrWhiteSpace(playerName))
			{
				allowedPlayerNames.Value = UpdateCsv(allowedPlayerNames.Value, playerName, allowed);
			}
			((BaseUnityPlugin)Instance).Config.Save();
			LogInfo((allowed ? "Allowed" : "Blocked") + " truck start for '" + playerName + "' (" + steamId + ").");
		}

		private static string UpdateCsv(string current, string value, bool add)
		{
			List<string> list = new List<string>();
			string[] array = (current ?? string.Empty).Split(new char[5] { ',', ';', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0 && !string.Equals(text, value, StringComparison.OrdinalIgnoreCase))
				{
					list.Add(text);
				}
			}
			if (add && !string.IsNullOrWhiteSpace(value))
			{
				list.Add(value.Trim());
			}
			return string.Join(", ", list.ToArray());
		}

		private static bool ContainsConfiguredValue(string csv, string value)
		{
			if (string.IsNullOrWhiteSpace(csv) || string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			string[] array = csv.Split(new char[5] { ',', ';', '\n', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				if (string.Equals(array[i].Trim(), value.Trim(), StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static string GetFieldValue(object target, string fieldName)
		{
			object objectFieldValue = GetObjectFieldValue(target, fieldName);
			if (objectFieldValue == null)
			{
				return string.Empty;
			}
			return objectFieldValue.ToString();
		}

		private static object GetObjectFieldValue(object target, string fieldName)
		{
			if (target == null)
			{
				return null;
			}
			FieldInfo field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (!(field != null))
			{
				return null;
			}
			return field.GetValue(target);
		}

		private static int GetIntFieldValue(object target, string fieldName)
		{
			object objectFieldValue = GetObjectFieldValue(target, fieldName);
			if (objectFieldValue is int)
			{
				return (int)objectFieldValue;
			}
			return 0;
		}

		private static bool GetBoolFieldValue(object target, string fieldName)
		{
			object objectFieldValue = GetObjectFieldValue(target, fieldName);
			if (objectFieldValue is bool)
			{
				return (bool)objectFieldValue;
			}
			return false;
		}

		private static PlayerAvatar GetTruckScreenStarter(TruckScreenText screen)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			if ((Object)(object)screen == (Object)null)
			{
				return null;
			}
			object objectFieldValue = GetObjectFieldValue(screen, "staticGrabObject");
			if (!(GetObjectFieldValue((object)(StaticGrabObject)((objectFieldValue is StaticGrabObject) ? objectFieldValue : null), "playerGrabbing") is IEnumerable enumerable))
			{
				return null;
			}
			PlayerAvatar val = null;
			foreach (object item in enumerable)
			{
				object objectFieldValue2 = GetObjectFieldValue(item, "playerAvatar");
				PlayerAvatar val2 = (PlayerAvatar)((objectFieldValue2 is PlayerAvatar) ? objectFieldValue2 : null);
				if (!((Object)(object)val2 == (Object)null))
				{
					if ((Object)(object)val == (Object)null)
					{
						val = val2;
					}
					if (IsPlayerAuthorized(val2, out var steamId, out var playerName, out var _))
					{
						LogInfo("Selected authorized truck screen grabber '" + playerName + "' (" + steamId + ") from multiple grabbers.");
						return val2;
					}
				}
			}
			return val;
		}

		private static bool IsDepartLevel()
		{
			if ((Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent != (Object)null)
			{
				return (Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelShop[0];
			}
			return false;
		}

		private static bool IsTruckScreenGrabObject(StaticGrabObject grabObject)
		{
			if ((Object)(object)grabObject == (Object)null || (Object)(object)TruckScreenText.instance == (Object)null)
			{
				return false;
			}
			return GetObjectFieldValue(TruckScreenText.instance, "staticGrabObject") == grabObject;
		}

		private static void LogInfo(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogInfo((object)message);
			}
		}

		private static void LogWarning(string message)
		{
			if ((Object)(object)Instance != (Object)null)
			{
				((BaseUnityPlugin)Instance).Logger.LogWarning((object)message);
			}
		}

		private static void DenyStart(string playerName, string steamId)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Instance != (Object)null)
			{
				LogInfo("Blocked truck start by client '" + playerName + "' (" + steamId + ").");
				ShowDeniedScreenMessage(playerName);
			}
			if (showDeniedMessage != null && showDeniedMessage.Value)
			{
				try
				{
					SemiFunc.UIFocusText("Only host or host-approved clients can start the truck.", Color.white, Color.red, 3f);
				}
				catch
				{
				}
			}
		}

		private static void SetField(object target, string fieldName, object value)
		{
			if (target != null)
			{
				FieldInfo field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					field.SetValue(target, value);
				}
			}
		}

		private static void ShowDeniedScreenMessage(string P_0)
		{
			if ((Object)(object)Instance == (Object)null || (Object)(object)TruckScreenText.instance == (Object)null || Time.time - Instance.lastDeniedScreenMessageTime < 1.5f)
			{
				return;
			}
			Instance.lastDeniedScreenMessageTime = Time.time;
			string text = (string.IsNullOrWhiteSpace(P_0) ? "该玩家" : P_0);
			try
			{
				TruckScreenText.instance.MessageSendCustom("", "<color=#FF5555>" + text + " 没有发车权限</color>", 2);
			}
			catch (Exception ex)
			{
				LogWarning("Failed to show denied message on truck screen: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
	internal static class RunManagerChangeLevelPatch
	{
		private static bool Prefix(ChangeLevelType _changeLevelType)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Plugin.CanHostChangeLevel(_changeLevelType);
		}
	}
	[HarmonyPatch(typeof(StaticGrabObject), "GrabStartedRPC")]
	internal static class StaticGrabObjectGrabStartedRpcPatch
	{
		private static bool Prefix(StaticGrabObject __instance, int playerPhotonID)
		{
			Plugin.RememberTruckScreenStarter(__instance, playerPhotonID);
			return true;
		}
	}
	[HarmonyPatch(typeof(TruckScreenOpen), "DelayedLevelSwitch")]
	internal static class TruckScreenOpenDelayedLevelSwitchPatch
	{
		private static bool Prefix(ref IEnumerator __result)
		{
			if (Plugin.ShouldSuppressClientLocalLevelSwitch())
			{
				Plugin.ResetLocalTruckScreenState(TruckScreenText.instance, "client local DelayedLevelSwitch suppressed");
				__result = Plugin.EmptyCoroutine();
				return false;
			}
			if (Plugin.CanTruckScreenStart(TruckScreenText.instance, allowHostFallback: true, "DelayedLevelSwitch"))
			{
				return true;
			}
			Plugin.ResetLocalTruckScreenState(TruckScreenText.instance, "DelayedLevelSwitch denied");
			__result = Plugin.EmptyCoroutine();
			return false;
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "ChatMessageLevel")]
	internal static class TruckScreenTextChatMessageLevelPatch
	{
		private static bool Prefix(TruckScreenText __instance)
		{
			return Plugin.GetTruckStartDecision(__instance, "ChatMessageLevel") switch
			{
				Plugin.TruckStartDecision.DirectSwitchHandled => false, 
				Plugin.TruckStartDecision.Blocked => false, 
				_ => true, 
			};
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "ChatMessageSend")]
	internal static class TruckScreenTextChatMessageSendPatch
	{
		private static bool Prefix(TruckScreenText __instance, string playerName)
		{
			return Plugin.AllowChatMessageSend(__instance, playerName, "ChatMessageSend");
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "ChatMessageSendRPC")]
	internal static class TruckScreenTextChatMessageSendRpcPatch
	{
		private static bool Prefix(TruckScreenText __instance, string playerName)
		{
			return Plugin.AllowChatMessageSend(__instance, playerName, "ChatMessageSendRPC");
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "ChatMessageShop")]
	internal static class TruckScreenTextChatMessageShopPatch
	{
		private static bool Prefix(TruckScreenText __instance)
		{
			return Plugin.GetTruckStartDecision(__instance, "ChatMessageShop") switch
			{
				Plugin.TruckStartDecision.DirectSwitchHandled => false, 
				Plugin.TruckStartDecision.Blocked => false, 
				_ => true, 
			};
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "GotoNextLevel")]
	internal static class TruckScreenTextGotoNextLevelPatch
	{
		private static bool Prefix(TruckScreenText __instance)
		{
			if (Plugin.ShouldSuppressClientLocalLevelSwitch())
			{
				Plugin.ResetLocalTruckScreenState(__instance, "client local GotoNextLevel suppressed");
				return false;
			}
			if (Plugin.CanTruckScreenStart(__instance, allowHostFallback: true, "GotoNextLevel"))
			{
				return true;
			}
			Plugin.ResetLocalTruckScreenState(__instance, "GotoNextLevel denied");
			return false;
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "ShopGotoNextLevel")]
	internal static class TruckScreenTextShopGotoNextLevelPatch
	{
		private static bool Prefix(TruckScreenText __instance, ref IEnumerator __result)
		{
			if (Plugin.ShouldSuppressClientLocalLevelSwitch())
			{
				Plugin.ResetLocalTruckScreenState(__instance, "client local ShopGotoNextLevel suppressed");
				__result = Plugin.EmptyCoroutine();
				return false;
			}
			if (Plugin.CanTruckScreenStart(__instance, allowHostFallback: true, "ShopGotoNextLevel"))
			{
				return true;
			}
			Plugin.ResetLocalTruckScreenState(__instance, "ShopGotoNextLevel denied");
			__result = Plugin.EmptyCoroutine();
			return false;
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "StartChat")]
	internal static class TruckScreenTextStartChatPatch
	{
		private static bool Prefix(TruckScreenText __instance)
		{
			return Plugin.AllowTruckScreenStartChat(__instance, "StartChat");
		}
	}
	[HarmonyPatch(typeof(TruckScreenText), "PlayerChatBoxStateUpdate")]
	internal static class TruckScreenTextStateUpdatePatch
	{
		private static bool Prefix(TruckScreenText __instance, PlayerChatBoxState _state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			if ((int)_state != 3)
			{
				return true;
			}
			return Plugin.GetTruckStartDecision(__instance, "PlayerChatBoxStateUpdate") switch
			{
				Plugin.TruckStartDecision.DirectSwitchHandled => false, 
				Plugin.TruckStartDecision.Blocked => false, 
				_ => true, 
			};
		}
	}
}