Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of HostOnlyStart v2.0.0
HostOnlyStart.dll
Decompiled 14 hours agousing 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, }; } } }