using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("PowerTerminalOS")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PowerTerminalOS")] [assembly: AssemblyTitle("PowerTerminalOS")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PowerTerminalOS { [BepInPlugin("anton.powerterminalos", "PowerTerminalOS", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "anton.powerterminalos"; public const string Name = "PowerTerminalOS"; public const string Version = "1.0.0"; internal static Plugin Instance; private Harmony harmony; private ConfigEntry accent; private ConfigEntry hostOnly; internal ManualLogSource Log => ((BaseUnityPlugin)this).Logger; private void Awake() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown Instance = this; accent = ((BaseUnityPlugin)this).Config.Bind("Appearance", "AccentColor", "00D7FF", "Terminal accent color as an RGB hex value."); hostOnly = ((BaseUnityPlugin)this).Config.Bind("Security", "HostOnlyControlCommands", true, "Only the host may run commands that alter the world."); harmony = new Harmony("anton.powerterminalos"); harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"PowerTerminalOS 1.0.0 loaded. Type pwsh or ps-help in the terminal."); } internal void Style(Terminal terminal) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) try { Color val = default(Color); if (!ColorUtility.TryParseHtmlString("#" + accent.Value.TrimStart('#'), ref val)) { ((Color)(ref val))..ctor(0f, 0.84f, 1f); } terminal.screenText.textComponent.color = val; terminal.screenText.textComponent.fontStyle = (FontStyles)0; terminal.screenText.caretColor = Color.white; terminal.screenText.selectionColor = new Color(val.r, val.g, val.b, 0.35f); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)ex); } } internal bool TryExecute(Terminal terminal, string raw, out TerminalNode node) { node = null; string text = Normalize(raw); if (text.Length == 0) { return false; } string[] array = text.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string text2 = array[0].ToLowerInvariant(); string[] args = array.Skip(1).ToArray(); if (new Dictionary(StringComparer.OrdinalIgnoreCase) { { "pwsh", "ps-help" }, { "help-ps", "ps-help" }, { "status", "get-shipstatus" }, { "moon", "get-moon" }, { "crew", "get-crew" }, { "scan", "scan-map" }, { "lights", "set-lights" }, { "turrets", "set-turrets" }, { "doors", "set-doors" }, { "teleport", "invoke-teleporter" }, { "launch", "start-ship" }, { "clear", "clear-host" } }.TryGetValue(text2, out var value)) { text2 = value; } bool clearPreviousText = false; string text3; switch (text2) { case "ps-help": text3 = Help(); break; case "get-shipstatus": text3 = ShipStatus(); break; case "get-moon": text3 = MoonStatus(); break; case "get-crew": text3 = CrewStatus(); break; case "scan-map": text3 = MapScan(); break; case "get-weather": text3 = MoonStatus(); break; case "get-scrap": text3 = ScrapScan(); break; case "set-lights": text3 = Control(args, "lights"); break; case "set-turrets": text3 = Control(args, "turrets"); break; case "set-doors": text3 = Control(args, "doors"); break; case "invoke-teleporter": text3 = Control(args, "teleporter"); break; case "start-ship": text3 = Control(args, "ship"); break; case "clear-host": text3 = Banner("SESSION CLEARED"); clearPreviousText = true; break; default: return false; } node = ScriptableObject.CreateInstance(); node.clearPreviousText = clearPreviousText; node.acceptAnything = false; node.displayText = text3 + "\n\nPS LC:\\Ship> "; node.maxCharactersToType = 16000; return true; } private string Control(string[] args, string system) { if (hostOnly.Value && (Object)(object)NetworkManager.Singleton != (Object)null && !NetworkManager.Singleton.IsHost) { return Error("ACCESS DENIED", "Control commands are host-only."); } string text = ((args.Length == 0) ? "toggle" : args[0].ToLowerInvariant()); try { switch (system) { case "lights": { object obj = AccessTools.Field(typeof(StartOfRound), "shipRoomLights")?.GetValue(StartOfRound.Instance); if (obj == null) { return Error("NOT AVAILABLE", "Ship light controller was not found."); } bool flag3 = text == "on" || (text == "toggle" && !ReadBool(obj, "areLightsOn", "lightsOn")); bool flag4 = InvokeBest(obj, new string[2] { "SetShipLightsBoolean", "SetShipLights" }, flag3); if (!flag4) { flag4 = InvokeBest(obj, (!flag3) ? new string[2] { "SetShipLightsOff", "TurnOffLights" } : new string[2] { "SetShipLightsOn", "TurnOnLights" }); } if (!flag4) { InvokeBest(obj, new string[1] { "ToggleShipLights" }); } return Success("SHIP LIGHTS", flag3 ? "ONLINE" : "OFFLINE"); } case "turrets": { bool flag = text == "on" || text == "enable"; int num = 0; foreach (Object item in FindByTypeName("Turret")) { SetMember(item, flag, "turretActive", "turretEnabled"); InvokeBest(item, new string[2] { "ToggleTurretEnabled", "SetTurretMode" }, flag); num++; } return Success("TURRET NETWORK", string.Format("{0} turret(s) {1}", num, flag ? "ENABLED" : "DISABLED")); } case "doors": { bool flag2 = text == "open" || text == "on"; int num2 = 0; foreach (Object item2 in FindByTypeName("TerminalAccessibleObject")) { if (item2.name.ToLowerInvariant().Contains("door")) { if (!InvokeBest(item2, new string[2] { "SetDoorOpen", "SetDoorOpenServerRpc" }, flag2) && ReadBool(item2, "isDoorOpen") != flag2) { InvokeBest(item2, new string[1] { "CallFunctionFromTerminal" }); } num2++; } } return Success("FACILITY DOORS", string.Format("{0} door(s) commanded {1}", num2, flag2 ? "OPEN" : "CLOSED")); } case "teleporter": { Object val = FindByTypeName("ShipTeleporter").FirstOrDefault(); if (val == (Object)null) { return Error("NOT INSTALLED", "No ship teleporter was found."); } InvokeBest(val, new string[2] { "PressTeleportButtonOnLocalClient", "beamUpPlayer" }); return Success("TELEPORTER", "Beam sequence requested"); } case "ship": if ((Object)(object)StartOfRound.Instance == (Object)null) { return Error("NOT READY", "Round controller was not found."); } InvokeBest(StartOfRound.Instance, new string[2] { "EndGameServerRpc", "StartGameServerRpc" }); return Success("AUTOPILOT", "Ship sequence requested"); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); return Error("COMMAND FAILED", ex.GetType().Name); } return Error("INVALID ACTION", "Use on/off, enable/disable, or open/close."); } private string ShipStatus() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return Error("OFFLINE", "No active ship controller."); } bool flag = ReadBool(instance, "shipHasLanded"); bool flag2 = ReadBool(instance, "inShipPhase"); return Panel("SHIP STATUS", new string[5] { Row("State", flag ? "LANDED" : (flag2 ? "IN ORBIT" : "IN TRANSIT")), Row("Crew", ActivePlayers().Count.ToString()), Row("Host", IsHost() ? "LOCAL" : "REMOTE"), Row("Moon", CurrentMoon()), Row("Time", TimeText()) }); } private string MoonStatus() { string value = GetMember(((Object)(object)StartOfRound.Instance == (Object)null) ? null : GetMember(StartOfRound.Instance, "currentLevel"), "currentWeather")?.ToString() ?? "Unknown"; return Panel("MOON TELEMETRY", new string[5] { Row("Moon", CurrentMoon()), Row("Weather", value), Row("Time", TimeText()), Row("Enemies", FindByBaseType("EnemyAI").Count.ToString()), Row("Scrap", ScrapObjects().Count.ToString()) }); } private string CrewStatus() { List list = new List(); foreach (object item in ActivePlayers()) { string key = GetMember(item, "playerUsername")?.ToString() ?? "Unknown"; bool flag = ReadBool(item, "isPlayerDead"); bool flag2 = ReadBool(item, "isInsideFactory"); list.Add(Row(key, flag ? "DECEASED" : (flag2 ? "FACILITY" : "SURFACE/SHIP"))); } return Panel("CREW MANIFEST", list); } private string MapScan() { return Panel("LOCAL SENSOR ARRAY", new string[5] { Row("Hostiles", FindByBaseType("EnemyAI").Count.ToString()), Row("Turrets", FindByTypeName("Turret").Count.ToString()), Row("Landmines", FindByTypeName("Landmine").Count.ToString()), Row("Powered doors", FindByTypeName("TerminalAccessibleObject").Count.ToString()), Row("Scrap objects", ScrapObjects().Count.ToString()) }); } private string ScrapScan() { return Panel("SCRAP MANIFEST", new string[2] { Row("Detected", ScrapObjects().Count.ToString()), Row("Scanner", "LOCAL RANGE") }); } private static string Help() { return Banner("POWERSHELL SHIP OS 1.0") + "\n INFORMATION\n Get-ShipStatus Ship, crew, moon and time\n Get-Moon Weather and moon telemetry\n Get-Crew Player status and location\n Scan-Map Enemies, hazards and scrap\n Get-Scrap Local scrap scan\n\n CONTROL (HOST)\n Set-Lights on|off|toggle\n Set-Turrets enable|disable\n Set-Doors open|close\n Invoke-Teleporter\n Start-Ship\n\n Aliases: status, moon, crew, scan, lights,\n turrets, doors, teleport, launch, clear"; } private static string Normalize(string value) { value = value.Replace("\r", "").Trim(); int num = value.LastIndexOf('>'); if (num >= 0) { value = value.Substring(num + 1).Trim(); } return value; } private static string CurrentMoon() { object target = (((Object)(object)StartOfRound.Instance == (Object)null) ? null : GetMember(StartOfRound.Instance, "currentLevel")); return GetMember(target, "PlanetName")?.ToString() ?? GetMember(target, "name")?.ToString() ?? "Unknown"; } private static string TimeText() { if (!(GetMember(Type.GetType("TimeOfDay, Assembly-CSharp")?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null), "normalizedTimeOfDay") is float num)) { return "Unknown"; } return $"{(int)(6f + num * 12f):00}:{(int)(num * 12f % 1f * 60f):00}"; } private static List ActivePlayers() { if (!(GetMember(StartOfRound.Instance, "allPlayerScripts") is IEnumerable source)) { return new List(); } return (from object p in source where p != null && ReadBool(p, "isPlayerControlled") select p).ToList(); } private static List ScrapObjects() { return (from o in FindByBaseType("GrabbableObject") where ReadBool(o, "itemProperties.isScrap", "isScrap") select o).ToList(); } private static List FindByTypeName(string name) { Type type = Type.GetType(name + ", Assembly-CSharp"); if (!(type == null)) { return Object.FindObjectsOfType(type).Cast().ToList(); } return new List(); } private static List FindByBaseType(string name) { return FindByTypeName(name); } private static bool IsHost() { if (!((Object)(object)NetworkManager.Singleton == (Object)null)) { return NetworkManager.Singleton.IsHost; } return true; } private static object? GetMember(object? target, string name) { if (target == null) { return null; } Type type = target.GetType(); object obj = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target); if (obj == null) { PropertyInfo? property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)property == null) { return null; } obj = property.GetValue(target); } return obj; } private static bool ReadBool(object? target, params string[] names) { foreach (string text in names) { object obj = (text.Contains(".") ? text.Split('.').Aggregate(target, GetMember) : GetMember(target, text)); if (obj is bool) { return (bool)obj; } } return false; } private static void SetMember(object target, object value, params string[] names) { foreach (string name in names) { FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType.IsInstanceOfType(value)) { field.SetValue(target, value); break; } } } private static bool InvokeBest(object target, string[] names, params object[] args) { foreach (string name in names) { MethodInfo methodInfo = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == name && m.GetParameters().Length == args.Length); if (!(methodInfo == null)) { try { methodInfo.Invoke(target, args); return true; } catch { } } } return false; } private static string Banner(string title) { return "\n+------------------------------------------------+\n| " + title.PadRight(44).Substring(0, 44) + " |\n+------------------------------------------------+"; } private static string Panel(string title, IEnumerable rows) { return Banner(title) + "\n" + string.Join("\n", rows); } private static string Row(string key, string value) { return " " + key.PadRight(18) + " " + value; } private static string Success(string title, string text) { return Panel(title, new string[2] { Row("Result", text), Row("Status", "OK") }); } private static string Error(string title, string text) { return Panel(title, new string[2] { Row("Error", text), Row("Status", "FAILED") }); } } [HarmonyPatch(typeof(Terminal), "BeginUsingTerminal")] internal static class BeginUsingTerminalPatch { private static void Postfix(Terminal __instance) { Plugin.Instance.Style(__instance); } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal static class ParsePlayerSentencePatch { private static void Postfix(Terminal __instance, ref TerminalNode __result) { try { int textAdded = __instance.textAdded; string text = __instance.screenText.text; string raw = ((textAdded > 0 && textAdded <= text.Length) ? text.Substring(text.Length - textAdded) : text); if (Plugin.Instance.TryExecute(__instance, raw, out TerminalNode node)) { __result = node; } } catch (Exception ex) { Plugin.Instance.Log.LogError((object)ex); } } } }