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 showDeniedMessage; internal static ConfigEntry allowedSteamIds; internal static ConfigEntry allowedPlayerNames; private static HashSet allowedSteamIdCache = new HashSet(StringComparer.OrdinalIgnoreCase); private static HashSet allowedPlayerNameCache = new HashSet(StringComparer.OrdinalIgnoreCase); private static string allowedSteamIdCacheSource = null; private static string allowedPlayerNameCacheSource = null; private static ConfigEntry 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("Feedback", "ShowDeniedMessage", true, "Show a small UI focus message when a client is blocked from starting."); allowedSteamIds = ((BaseUnityPlugin)this).Config.Bind("Permissions", "AllowedClientSteamIds", string.Empty, "Comma-separated Steam IDs allowed to start the truck when they are not host."); allowedPlayerNames = ((BaseUnityPlugin)this).Config.Bind("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("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 list = new List(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 BuildAllowedSet(string csv) { HashSet hashSet = new HashSet(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() : 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 GetPlayers() { if ((Object)(object)GameDirector.instance == (Object)null || GameDirector.instance.PlayerList == null) { return (IEnumerable)(object)new PlayerAvatar[0]; } List list = new List(); 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 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 list = new List(); 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("", "" + text + " 没有发车权限", 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, }; } } }