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 ValheimCli v0.5.0
BepInEx/plugins/ValheimCliBridge/ValheimCliBridge.dll
Decompiled 15 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Xml; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ValheimCliBridge")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.0.0")] [assembly: AssemblyInformationalVersion("0.5.0+ae92f4b334210d49cb3829d3ab3d3a9af2a6fccc")] [assembly: AssemblyProduct("ValheimCliBridge")] [assembly: AssemblyTitle("ValheimCliBridge")] [assembly: AssemblyVersion("0.5.0.0")] [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 ValheimCliBridge { [HarmonyPatch] public static class BackgroundCursor { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo methodInfo = AccessTools.Method(typeof(ZCursor), "set_LockState", (Type[])null, (Type[])null) ?? throw new MissingMethodException("Native cursor lock policy unavailable"); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZCursor), "SetVisible", (Type[])null, (Type[])null) ?? throw new MissingMethodException("Native cursor visibility policy unavailable"); return new MethodBase[2] { methodInfo, methodInfo2 }; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { MethodInfo lockSetter = AccessTools.Method(typeof(Cursor), "set_lockState", (Type[])null, (Type[])null) ?? throw new MissingMethodException("Unity cursor lock API unavailable"); MethodInfo visibilitySetter = AccessTools.Method(typeof(Cursor), "set_visible", (Type[])null, (Type[])null) ?? throw new MissingMethodException("Unity cursor visibility API unavailable"); int replaced = 0; foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Call && object.Equals(instruction.operand, lockSetter)) { instruction.operand = AccessTools.Method(typeof(BackgroundCursor), "SetLockState", (Type[])null, (Type[])null); replaced++; } else if (instruction.opcode == OpCodes.Call && object.Equals(instruction.operand, visibilitySetter)) { instruction.operand = AccessTools.Method(typeof(BackgroundCursor), "SetVisible", (Type[])null, (Type[])null); replaced++; } yield return instruction; } if (replaced != 1) { throw new MissingMethodException("Native cursor write path unavailable"); } } public static void SetLockState(CursorLockMode value) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.Instance != (Object)null && (Plugin.Instance.AgentMode || !Application.isFocused)) { value = (CursorLockMode)0; if (Cursor.lockState == value) { return; } } Cursor.lockState = value; } public static void SetVisible(bool value) { if ((Object)(object)Plugin.Instance != (Object)null && (Plugin.Instance.AgentMode || !Application.isFocused)) { value = true; if (Cursor.visible == value) { return; } } Cursor.visible = value; } } public sealed class ActionRefusedException : InvalidOperationException { public ActionRefusedException(string message) : base(message) { } } public sealed class Dispatcher { private sealed class Job { public Request Request; public long Expires; public readonly object Gate = new object(); public bool Started; public bool Cancelled; public Response Result; } private readonly Queue<Job> queue = new Queue<Job>(); private readonly object gate = new object(); private bool stopped; private readonly Func<Request, Response> execute; public Dispatcher(Func<Request, Response> execute) { this.execute = execute; } public Response Run(Request request, int timeoutMs = 2000) { Job job = new Job { Request = request, Expires = Stopwatch.GetTimestamp() + timeoutMs * Stopwatch.Frequency / 1000 }; lock (gate) { if (stopped) { return Response.Error(request.id, "Bridge stopped", "cancelled"); } if (queue.Count >= 8) { return Response.Error(request.id, "Bridge busy", "cancelled"); } queue.Enqueue(job); } lock (job.Gate) { while (job.Result == null) { long num = (job.Expires - Stopwatch.GetTimestamp()) * 1000 / Stopwatch.Frequency; if (num <= 0) { break; } Monitor.Wait(job.Gate, (int)Math.Min(num, 2147483647L)); } if (job.Result != null) { return job.Result; } if (job.Started) { return Response.Error(request.id, "Execution started; outcome unknown. Do not retry automatically."); } job.Cancelled = true; return Response.Error(request.id, "Expired before execution; action cancelled", "cancelled"); } } public void Pump() { Job job; lock (gate) { if (stopped || queue.Count == 0) { return; } job = queue.Dequeue(); } lock (job.Gate) { if (job.Cancelled || Stopwatch.GetTimestamp() >= job.Expires) { job.Result = Response.Error(job.Request.id, "Expired before execution; action cancelled", "cancelled"); Monitor.PulseAll(job.Gate); return; } job.Started = true; } Response result; try { result = execute(job.Request); } catch (ActionRefusedException ex) { result = Response.Error(job.Request.id, ex.Message, "cancelled"); } catch (Exception ex2) { result = Response.Error(job.Request.id, "Game operation failed: " + ex2.GetType().Name); } lock (job.Gate) { job.Result = result; Monitor.PulseAll(job.Gate); } } public void Stop() { lock (gate) { stopped = true; while (queue.Count > 0) { Job job = queue.Dequeue(); lock (job.Gate) { job.Cancelled = true; job.Result = Response.Error(job.Request.id, "Bridge stopped", "cancelled"); Monitor.PulseAll(job.Gate); } } } } } [HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(string) })] public static class GameConsole { [ThreadStatic] private static Terminal context; [ThreadStatic] private static List<string> output; [ThreadStatic] private static bool truncated; internal static Response Execute(Request request, Response response) { context = (Terminal)(object)Console.instance; output = new List<string>(); truncated = false; try { context.TryRunCommand(request.command, false, false); response.state = "submitted"; response.consoleOutput = output.ToArray(); response.consoleOutputTruncated = truncated; return response; } finally { context = null; output = null; } } public static void Postfix(Terminal __instance, string text) { if (output == null || (Object)(object)__instance != (Object)(object)context || text == null) { return; } if (output.Count == 32) { truncated = true; return; } if (text.Length > 256) { text = text.Substring(0, char.IsHighSurrogate(text[255]) ? 255 : 256); truncated = true; } output.Add(text); } } [HarmonyPatch(typeof(Player), "SetControls")] public static class GameInput { private static readonly FieldInfo Blocking = AccessTools.Field(typeof(Player), "m_blocking"); private static readonly MethodInfo TakeInput = AccessTools.Method(typeof(PlayerController), "TakeInput", new Type[1] { typeof(bool) }, (Type[])null); internal static void Validate() { if (Blocking == null || TakeInput == null || TakeInput.ReturnType != typeof(bool)) { throw new InvalidOperationException("Game control policy unavailable"); } GameActions.Validate(); } internal static bool CanTakeInput(Player local) { PlayerController component = ((Component)local).GetComponent<PlayerController>(); if ((Object)(object)component != (Object)null && TakeInput != null) { return (bool)TakeInput.Invoke(component, new object[1] { false }); } return false; } public static void Prefix(Player __instance, ref Vector3 movedir, ref bool attack, ref bool attackHold, ref bool secondaryAttack, ref bool secondaryAttackHold, ref bool block, ref bool blockHold, ref bool jump, ref bool crouch, ref bool run, ref bool autoRun, ref bool dodge) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } ControlFrame controlFrame = instance.ControlFrame(__instance); if (controlFrame != null) { if (PlayerController.HasInputDelay) { controlFrame.Attack = (controlFrame.AttackHold = (controlFrame.Secondary = (controlFrame.SecondaryHold = false))); controlFrame.Block = (controlFrame.BlockHold = (controlFrame.Jump = (controlFrame.Crouch = (controlFrame.Dodge = false)))); } movedir = new Vector3(controlFrame.X, 0f, controlFrame.Z); attack = controlFrame.Attack; attackHold = controlFrame.AttackHold; secondaryAttack = controlFrame.Secondary; secondaryAttackHold = controlFrame.SecondaryHold; __instance.m_autoRun = false; block = (__instance.ToggleBlock ? ((bool)Blocking.GetValue(__instance) != controlFrame.BlockHold) : controlFrame.Block); blockHold = controlFrame.BlockHold; jump = controlFrame.Jump; crouch = controlFrame.Crouch; run = controlFrame.Run; autoRun = false; dodge = controlFrame.Dodge; } } } public static class GameActions { private static readonly MethodInfo TakeInput = AccessTools.Method(typeof(Player), "TakeInput", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo Interact = AccessTools.Method(typeof(Player), "Interact", new Type[3] { typeof(GameObject), typeof(bool), typeof(bool) }, (Type[])null); private static readonly MethodInfo Placement = AccessTools.Method(typeof(Player), "UpdatePlacement", new Type[2] { typeof(bool), typeof(float) }, (Type[])null); private static readonly FieldInfo PlacePressed = AccessTools.Field(typeof(Player), "m_placePressedTime"); private static readonly FieldInfo Rotation = AccessTools.Field(typeof(Player), "m_placeRotation"); internal static void Validate() { if (TakeInput == null || TakeInput.ReturnType != typeof(bool)) { throw new InvalidOperationException("Game action policy unavailable"); } } internal static bool CanTakeInput(Player local) { if (TakeInput != null) { return (bool)TakeInput.Invoke(local, null); } return false; } public static void Execute(Player local, Request r) { switch (r.action) { case "interact": { GameObject hoverObject = ((Humanoid)local).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null || Interact == null) { throw new ActionRefusedException("No interactable object under the game's crosshair"); } Interact.Invoke(local, new object[3] { hoverObject, false, false }); break; } case "slot": if (((Humanoid)local).GetInventory().GetItemAt(r.slot.Value - 1, 0) == null) { throw new ActionRefusedException("Hotbar slot is empty"); } local.UseHotbarItem(r.slot.Value); break; case "menu": if ((Object)(object)Menu.instance == (Object)null) { throw new ActionRefusedException("Pause menu unavailable"); } Menu.instance.Show(); break; case "inventory": if ((Object)(object)InventoryGui.instance == (Object)null) { throw new ActionRefusedException("Inventory UI unavailable"); } if (InventoryGui.IsVisible()) { InventoryGui.instance.Hide(); } else { InventoryGui.instance.Show((Container)null, 1); } break; case "build-menu": if (!((Character)local).InPlaceMode() || (Object)(object)Hud.instance == (Object)null) { throw new ActionRefusedException("Equip a building tool first"); } if (!Hud.IsPieceSelectionVisible() && (PlayerController.HasInputDelay || Hud.InRadial())) { throw new ActionRefusedException("Game currently blocks opening the build menu"); } Hud.instance.TogglePieceSelection(); break; case "hide": if (((Character)local).InAttack() || ((Character)local).InDodge()) { throw new ActionRefusedException("Cannot hide equipment during an attack or dodge"); } ((Humanoid)local).HideHandItems(false, true); break; case "guardian": if (!local.StartGuardianPower()) { throw new InvalidOperationException("Game refused guardian power"); } break; case "place": if (!((Character)local).InPlaceMode() || Placement == null || PlacePressed == null) { throw new ActionRefusedException("Building tool or placement API unavailable"); } PlacePressed.SetValue(local, Time.time); Placement.Invoke(local, new object[2] { true, Time.deltaTime }); break; case "rotate": if (!((Character)local).InPlaceMode() || Rotation == null) { throw new ActionRefusedException("Building tool or rotation API unavailable"); } Rotation.SetValue(local, (int)Rotation.GetValue(local) + r.scroll.Value); break; default: throw new ActionRefusedException("Unknown game action"); } } } public static class GameUi { private sealed class Target { public GameObject Object; public UiControl Description; } private static readonly Dictionary<string, Target> targets = new Dictionary<string, Target>(); private static float listedAt; public static void Invalidate() { targets.Clear(); } public static void List(Response response, int offset) { Invalidate(); GameObject[] array = (from c in (from canvas in Object.FindObjectsOfType<Canvas>() where (Object)(object)canvas != (Object)null && ((Behaviour)canvas).isActiveAndEnabled select canvas).SelectMany((Canvas canvas) => ((Component)canvas).GetComponentsInChildren<MonoBehaviour>()) where (Object)(object)c != (Object)null && ((Behaviour)c).isActiveAndEnabled && Supported(c) select ((Component)c).gameObject).Distinct().OrderBy<GameObject, string>((GameObject o) => HierarchyPath(o), StringComparer.Ordinal).ToArray(); response.uiTotal = array.Length; response.uiNextOffset = ((offset + 64 < array.Length) ? new int?(offset + 64) : ((int?)null)); response.ui = array.Skip(offset).Take(64).Select(delegate(GameObject o) { Vector2 point; RaycastResult hit; UiControl uiControl = Describe(o, out point, out hit); uiControl.id = Guid.NewGuid().ToString(); targets.Add(uiControl.id, new Target { Object = o, Description = uiControl }); return uiControl; }) .ToArray(); listedAt = Time.realtimeSinceStartup; } private static bool Supported(MonoBehaviour c) { if (!(c is Button) && !(c is Toggle)) { if (!(c is Selectable)) { if (!(c is IPointerClickHandler) && !(c is ISubmitHandler)) { return c is IScrollHandler; } return true; } return false; } return true; } private static UiControl Describe(GameObject o, out Vector2 point, out RaycastResult hit) { MonoBehaviour[] source = (from c in o.GetComponents<MonoBehaviour>() where (Object)(object)c != (Object)null && ((Behaviour)c).isActiveAndEnabled select c).ToArray(); Selectable component = o.GetComponent<Selectable>(); List<string> list = new List<string>(); if (source.Any((MonoBehaviour c) => Supported(c) && (c is IPointerClickHandler || c is ISubmitHandler))) { list.Add("click"); } if (source.Any((MonoBehaviour c) => Supported(c) && c is IScrollHandler)) { list.Add("scroll"); } string value = HierarchyPath(o); string value2 = string.Join(" ", (from s in (from c in o.GetComponentsInChildren<MonoBehaviour>() where (Object)(object)c != (Object)null && ((Behaviour)c).isActiveAndEnabled && (c is Text || ((object)c).GetType().FullName == "TMPro.TextMeshProUGUI") select c).Select(delegate(MonoBehaviour c) { Text val = (Text)(object)((c is Text) ? c : null); return (val == null) ? (((object)c).GetType().GetProperty("text")?.GetValue(c, null) as string) : val.text; }) where !string.IsNullOrWhiteSpace(s) select s).Take(8)); bool flag = AvailablePoint(o, out point, out hit); bool flag2 = (Object)(object)component == (Object)null || component.IsInteractable(); return new UiControl { path = Limit(value, 240), label = Limit(value2, 160), type = Limit(string.Join(",", from c in source where Supported(c) select ((object)c).GetType().FullName), 160), actions = list.ToArray(), interactable = (flag && flag2), reason = ((!flag2) ? "disabled" : ((!flag) ? "hidden_or_obstructed" : null)) }; } private static string HierarchyPath(GameObject o) { string text = ((Object)o).name + "[" + o.transform.GetSiblingIndex() + "]"; Transform parent = o.transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "[" + parent.GetSiblingIndex() + "]/" + text; parent = parent.parent; } return text; } private static string Limit(string value, int length) { if (value.Length <= length) { return value; } return value.Substring(0, length); } private static bool AvailablePoint(GameObject target, out Vector2 point, out RaycastResult raycast) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Invalid comparison between Unknown and I4 //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Invalid comparison between Unknown and I4 //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Expected O, but got Unknown //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) point = default(Vector2); raycast = default(RaycastResult); EventSystem current = EventSystem.current; Transform transform = target.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); Canvas componentInParent = target.GetComponentInParent<Canvas>(); if ((Object)(object)current == (Object)null || (Object)(object)val == (Object)null || (Object)(object)componentInParent == (Object)null || !((Behaviour)componentInParent).isActiveAndEnabled || !target.activeInHierarchy || target.GetComponentsInParent<CanvasGroup>().Any((CanvasGroup g) => g.alpha <= 0.01f)) { return false; } Vector3[] array = (Vector3[])(object)new Vector3[4]; val.GetWorldCorners(array); Camera val2 = (((int)componentInParent.renderMode == 0 || ((int)componentInParent.renderMode == 1 && (Object)(object)componentInParent.worldCamera == (Object)null)) ? null : (((Object)(object)componentInParent.worldCamera != (Object)null) ? componentInParent.worldCamera : Camera.main)); if ((int)componentInParent.renderMode == 2 && (Object)(object)val2 == (Object)null) { return false; } Vector2 val3 = RectTransformUtility.WorldToScreenPoint(val2, array[0]); Vector2 val4 = RectTransformUtility.WorldToScreenPoint(val2, array[2]); Vector2[] array2 = (Vector2[])(object)new Vector2[5] { new Vector2(0.5f, 0.5f), new Vector2(0.1f, 0.1f), new Vector2(0.9f, 0.1f), new Vector2(0.1f, 0.9f), new Vector2(0.9f, 0.9f) }; Vector2 val6 = default(Vector2); foreach (Vector2 val5 in array2) { ((Vector2)(ref val6))..ctor(val3.x + (val4.x - val3.x) * val5.x, val3.y + (val4.y - val3.y) * val5.y); if (!(val6.x < 0f) && !(val6.y < 0f) && !(val6.x >= (float)Screen.width) && !(val6.y >= (float)Screen.height)) { PointerEventData val7 = new PointerEventData(current) { position = val6 }; List<RaycastResult> list = new List<RaycastResult>(); current.RaycastAll(val7, list); RaycastResult val8 = ((IEnumerable<RaycastResult>)list).FirstOrDefault((Func<RaycastResult, bool>)((RaycastResult h) => (Object)(object)((RaycastResult)(ref h)).gameObject != (Object)null && (Object)(object)((RaycastResult)(ref h)).gameObject.GetComponentInParent<Canvas>() != (Object)null)); GameObject gameObject = ((RaycastResult)(ref val8)).gameObject; if (!((Object)(object)gameObject == (Object)null) && ((Object)(object)gameObject == (Object)(object)target || gameObject.transform.IsChildOf(target.transform))) { point = val6; raycast = val8; return true; } } } return false; } public static void Execute(Request r) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Expected O, but got Unknown //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Expected O, but got Unknown if (!targets.TryGetValue(r.target, out var value) || (Object)(object)value.Object == (Object)null || Time.realtimeSinceStartup - listedAt > 30f) { throw new ActionRefusedException("Unknown or expired UI target; run ui-list again"); } if (r.uiAction == "scroll" && !r.scroll.HasValue) { throw new ActionRefusedException("Scroll requires a delta"); } Vector2 point; RaycastResult hit; UiControl uiControl = Describe(value.Object, out point, out hit); if (uiControl.path != value.Description.path || uiControl.label != value.Description.label || uiControl.type != value.Description.type || !uiControl.interactable || !uiControl.actions.Contains(r.uiAction)) { throw new ActionRefusedException("UI target changed, disabled or obstructed; run ui-list again"); } EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null) { throw new ActionRefusedException("Game UI event system unavailable"); } PointerEventData val = new PointerEventData(current) { position = point, pressPosition = point, pointerCurrentRaycast = hit, pointerPressRaycast = hit, eligibleForClick = true, clickCount = 1, button = (InputButton)((r.button == "right") ? 1 : ((r.button == "middle") ? 2 : 0)) }; GameObject val2 = value.Object; Button component = val2.GetComponent<Button>(); bool flag = (Object)(object)component != (Object)null && ((Behaviour)component).isActiveAndEnabled && !val2.GetComponents<MonoBehaviour>().Any((MonoBehaviour c) => (Object)(object)c != (Object)null && ((Behaviour)c).isActiveAndEnabled && c is IPointerClickHandler && !(c is Button)); bool flag2 = (Object)(object)ExecuteEvents.GetEventHandler<IPointerClickHandler>(val2) == (Object)(object)val2; if (r.uiAction == "click" && (int)val.button != 0 && (flag || !flag2)) { throw new ActionRefusedException("This UI target only accepts left activation"); } Invalidate(); bool flag3; if (r.uiAction == "scroll") { val.scrollDelta = new Vector2(0f, (float)r.scroll.Value); flag3 = ExecuteEvents.Execute<IScrollHandler>(val2, (BaseEventData)(object)val, ExecuteEvents.scrollHandler); } else if ((int)val.button == 0 && (flag || !flag2)) { flag3 = ExecuteEvents.Execute<ISubmitHandler>(val2, new BaseEventData(current), ExecuteEvents.submitHandler); } else { val.pointerPress = val2; ExecuteEvents.Execute<IPointerDownHandler>(val2, (BaseEventData)(object)val, ExecuteEvents.pointerDownHandler); try { ExecuteEvents.Execute<IPointerUpHandler>(val2, (BaseEventData)(object)val, ExecuteEvents.pointerUpHandler); flag3 = ExecuteEvents.Execute<IPointerClickHandler>(val2, (BaseEventData)(object)val, ExecuteEvents.pointerClickHandler); } finally { val.eligibleForClick = false; } } if (!flag3) { throw new InvalidOperationException("UI handler disappeared during activation; inspect UI before another action"); } } } internal static class HttpTransport { internal static byte[] Read(Stream stream, byte[] prefix, int port) { List<byte> list = new List<byte>(prefix); while (list.Count < 8192) { int num = stream.ReadByte(); if (num < 0) { throw new EndOfStreamException(); } list.Add((byte)num); int count = list.Count; if (count >= 4 && list[count - 4] == 13 && list[count - 3] == 10 && list[count - 2] == 13 && num == 10) { break; } } if (list.Count >= 8192) { throw new InvalidDataException("HTTP headers too large"); } using (List<byte>.Enumerator enumerator = list.GetEnumerator()) { while (enumerator.MoveNext()) { switch (enumerator.Current) { case 9: case 10: case 13: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64: case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: case 98: case 99: case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: case 108: case 109: case 110: case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: continue; } throw new InvalidDataException("Invalid HTTP header encoding"); } } string[] array = Encoding.ASCII.GetString(list.ToArray()).Split(new string[1] { "\r\n" }, StringSplitOptions.None); if (array[0] != "POST /v1/command HTTP/1.1") { throw new InvalidDataException("Expected POST /v1/command HTTP/1.1"); } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); for (int i = 1; i < array.Length - 2; i++) { int num2 = array[i].IndexOf(':'); if (num2 <= 0) { throw new InvalidDataException("Invalid HTTP header"); } string text = array[i].Substring(0, num2); string text2 = text; foreach (char c in text2) { if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && "!#$%&'*+-.^_`|~".IndexOf(c) < 0) { throw new InvalidDataException("Invalid HTTP field name"); } } if (dictionary.ContainsKey(text)) { throw new InvalidDataException("Duplicate HTTP header"); } string text3 = array[i].Substring(num2 + 1).Trim(' ', '\t'); if (text3.IndexOf('\r') >= 0 || text3.IndexOf('\n') >= 0) { throw new InvalidDataException("Invalid HTTP field value"); } dictionary.Add(text, text3); } if (!dictionary.TryGetValue("Host", out var value) || value != "127.0.0.1:" + port.ToString(CultureInfo.InvariantCulture) || dictionary.ContainsKey("Origin") || dictionary.ContainsKey("Transfer-Encoding")) { throw new InvalidDataException("Only local non-browser requests with Content-Length are supported"); } if (!dictionary.TryGetValue("Content-Type", out var value2)) { throw new InvalidDataException("Expected application/json"); } string[] array2 = value2.Split(new char[1] { ';' }); if (!array2[0].Trim().Equals("application/json", StringComparison.OrdinalIgnoreCase) || array2.Length > 2 || (array2.Length == 2 && !array2[1].Trim().Equals("charset=utf-8", StringComparison.OrdinalIgnoreCase))) { throw new InvalidDataException("Expected UTF-8 application/json"); } if (!dictionary.TryGetValue("Content-Length", out var value3) || !int.TryParse(value3, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0 || result > 65536) { throw new InvalidDataException("Invalid HTTP body length"); } if (dictionary.TryGetValue("Expect", out var value4)) { if (!value4.Equals("100-continue", StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("Unsupported HTTP expectation"); } byte[] bytes = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); stream.Write(bytes, 0, bytes.Length); stream.Flush(); } return Protocol.ReadExact(stream, result); } internal static void Write(Stream stream, Response response, int status) { byte[] array = Protocol.EncodeResponse(response); string text = status switch { 401 => "Unauthorized", 200 => "OK", _ => "Bad Request", }; byte[] bytes = Encoding.ASCII.GetBytes("HTTP/1.1 " + status + " " + text + "\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: " + array.Length.ToString(CultureInfo.InvariantCulture) + "\r\nConnection: close\r\nCache-Control: no-store\r\nX-Content-Type-Options: nosniff\r\n\r\n"); stream.Write(bytes, 0, bytes.Length); stream.Write(array, 0, array.Length); } } public static class InputSpec { private static readonly HashSet<string> Actions = new HashSet<string> { "attack", "secondary", "block", "jump", "crouch", "run", "dodge" }; public static string[] Split(string value) { if (value != null) { return value.Split(new char[1] { ',' }); } return new string[0]; } public static void Validate(Request r) { string[] array = Split(r.actions); if (array.Any((string a) => !Actions.Contains(a)) || array.Distinct().Count() != array.Length) { throw new InvalidDataException("Unsupported or repeated game action"); } if (!r.durationMs.HasValue || r.durationMs < 50 || r.durationMs > 5000) { throw new InvalidDataException("Input requires duration 50..5000 ms"); } if (!Bounded(r.moveX.GetValueOrDefault(), 1.0) || !Bounded(r.moveZ.GetValueOrDefault(), 1.0)) { throw new InvalidDataException("Movement must be finite and in -1..1"); } if (r.moveX.GetValueOrDefault() == 0.0 && r.moveZ.GetValueOrDefault() == 0.0 && array.Length == 0) { throw new InvalidDataException("Empty input"); } } public static bool Bounded(double value, double limit) { if (!double.IsNaN(value) && !double.IsInfinity(value)) { return Math.Abs(value) <= limit; } return false; } } public sealed class ControlFrame { public float X; public float Z; public bool Attack; public bool AttackHold; public bool Secondary; public bool SecondaryHold; public bool Block; public bool BlockHold; public bool Jump; public bool Crouch; public bool Run; public bool Dodge; } public sealed class InputController : IDisposable { private readonly object gate = new object(); private readonly Timer watchdog; private ControlFrame held; private bool permitted; private bool disposed; private bool first; private long expires; private long epoch; private string reason = "idle"; private string id; public bool Active { get { lock (gate) { Expire(); return held != null; } } } public string Reason { get { lock (gate) { return reason; } } } public string Id { get { lock (gate) { return id; } } } public long Epoch { get { lock (gate) { return epoch; } } } public InputController() { watchdog = new Timer(delegate { Tick(); }, null, 20, 20); } public void Permit(bool value) { lock (gate) { if (permitted && !value) { epoch++; StopLocked("permission_revoked"); } permitted = value; } } public void CheckEpoch(Request r) { lock (gate) { if (disposed || !permitted) { throw new InvalidOperationException("AllowControl=true is required"); } if (r.controlEpoch.HasValue && r.controlEpoch != epoch) { throw new InvalidOperationException("Action cancelled by stop or loss of control"); } } } public void Start(Request r) { InputSpec.Validate(r); lock (gate) { CheckEpoch(r); Expire(); if (held != null) { throw new InvalidOperationException("Input busy; observe or stop before another action"); } string[] source = InputSpec.Split(r.actions); float num = (float)r.moveX.GetValueOrDefault(); float num2 = (float)r.moveZ.GetValueOrDefault(); double num3 = Math.Sqrt(num * num + num2 * num2); if (num3 > 1.0) { num /= (float)num3; num2 /= (float)num3; } held = new ControlFrame { X = num, Z = num2, AttackHold = source.Contains("attack"), SecondaryHold = source.Contains("secondary"), BlockHold = source.Contains("block"), Jump = source.Contains("jump"), Crouch = source.Contains("crouch"), Run = source.Contains("run"), Dodge = source.Contains("dodge") }; first = true; id = r.id; reason = "running"; expires = Stopwatch.GetTimestamp() + r.durationMs.Value * Stopwatch.Frequency / 1000; } } public ControlFrame Sample() { lock (gate) { Expire(); if (held == null) { return null; } ControlFrame result = new ControlFrame { X = held.X, Z = held.Z, AttackHold = held.AttackHold, SecondaryHold = held.SecondaryHold, BlockHold = held.BlockHold, Run = held.Run, Attack = (first && held.AttackHold), Secondary = (first && held.SecondaryHold), Block = (first && held.BlockHold), Jump = (first && held.Jump), Crouch = (first && held.Crouch), Dodge = (first && held.Dodge) }; first = false; return result; } } public void Tick() { lock (gate) { if (!disposed) { Expire(); } } } internal void ExhaustRun() { lock (gate) { if (held != null) { held.Run = false; } } } private void Expire() { if (held != null && Stopwatch.GetTimestamp() >= expires) { StopLocked("completed"); } } public void Stop(string why = "stopped") { lock (gate) { epoch++; StopLocked(why); } } private void StopLocked(string why) { held = null; reason = why; } public void Dispose() { lock (gate) { disposed = true; permitted = false; epoch++; StopLocked("unloaded"); } watchdog.Dispose(); } } [BepInPlugin("swear01.ValheimCliBridge", "Valheim CLI Bridge", "0.5.0")] public sealed class Plugin : BaseUnityPlugin { private Dispatcher dispatcher; private Server server; private ConfigEntry<bool> allowTeleport; private ConfigEntry<bool> allowControl; private ConfigEntry<bool> allowConsole; internal static Plugin Instance; private Harmony harmony; private bool controlReady; private bool appliedControl; private bool? previousBackground; private float? previousVolume; private bool? previousAudioPause; private InputController input; private Player controlledPlayer; private bool capturing; private Player imagePlayer; private string image; private string capturedAt; private string captureError; private int imageWidth; private int imageHeight; private float capturedTime = -1f; internal bool AgentMode { get; private set; } private bool CanConsole { get { if (controlReady && allowConsole != null && allowConsole.Value && (Object)(object)Console.instance != (Object)null) { return Console.instance.IsConsoleEnabled(); } return false; } } private void Awake() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Expected O, but got Unknown ConfigEntry<bool> obj = ((BaseUnityPlugin)this).Config.Bind<bool>("Bridge", "Enabled", true, "Enable local CLI bridge. Restart after changes."); ConfigEntry<int> val = ((BaseUnityPlugin)this).Config.Bind<int>("Bridge", "Port", 28761, new ConfigDescription("Local loopback port. Restart after changes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1024, 65535), Array.Empty<object>())); allowTeleport = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowTeleport", false, "Allow the local host to teleport their own character. Client writes are refused."); allowControl = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowControl", false, "Allow bounded game-internal actions for your local character, including joining clients, and menu UI. Works while the game is unfocused. F12 in the game revokes permission. Never enable while someone else is playing this character."); allowConsole = ((BaseUnityPlugin)this).Config.Bind<bool>("Permissions", "AllowConsole", false, "Allow direct native console commands, including mod commands. Works while unfocused; requires an enabled game console. Native command permissions still apply. F12 in the game revokes this permission."); if (!obj.Value) { return; } AgentMode = Environment.GetCommandLineArgs().Contains("--valheim-cli-agent"); try { string path = Path.Combine(Paths.ConfigPath, "swear01.ValheimCliBridge.token"); if (!File.Exists(path)) { using FileStream stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); using StreamWriter streamWriter = new StreamWriter(stream); streamWriter.Write(Protocol.CreateToken()); } string text = File.ReadAllText(path).Trim(); if (text.Length != 64 || text.Any((char c) => !Enumerable.Contains("0123456789abcdef", c))) { throw new InvalidDataException("Invalid local token file"); } dispatcher = new Dispatcher(Execute); input = new InputController(); Instance = this; GameInput.Validate(); harmony = new Harmony("swear01.ValheimCliBridge"); harmony.PatchAll(typeof(Plugin).Assembly); controlReady = true; previousBackground = Application.runInBackground; Application.runInBackground = true; if (AgentMode) { previousVolume = AudioListener.volume; previousAudioPause = AudioListener.pause; AudioListener.pause = true; AudioListener.volume = 0f; } server = new Server(val.Value, text, dispatcher, delegate(Exception error) { ((BaseUnityPlugin)this).Logger.LogError((object)("CLI bridge connection failed: " + error.GetType().Name)); }, StopInput, () => input.Epoch); ((BaseUnityPlugin)this).Logger.LogInfo((object)("CLI bridge listening on 127.0.0.1:" + val.Value + "; token stored in local config folder. Never share it.")); } catch (Exception ex) { controlReady = false; if (previousBackground.HasValue) { Application.runInBackground = previousBackground.Value; } if (previousVolume.HasValue) { AudioListener.volume = previousVolume.Value; } if (previousAudioPause.HasValue) { AudioListener.pause = previousAudioPause.Value; } Instance = null; Harmony obj2 = harmony; if (obj2 != null) { obj2.UnpatchSelf(); } input?.Dispose(); ((BaseUnityPlugin)this).Logger.LogError((object)("CLI bridge did not start: " + ex.GetType().Name + ((ex is InvalidDataException) ? "; stop the game, delete swear01.ValheimCliBridge.token from the config folder, and restart." : "; check the config folder permissions and whether the configured port is already in use."))); } } private bool CanControl(Player local) { if (controlReady && allowControl != null && allowControl.Value && (Object)(object)local != (Object)null && (Object)(object)ZNet.instance != (Object)null && !((Character)local).IsDead() && !((Character)local).IsTeleporting() && !((Character)local).InCutscene() && (Object)(object)((Component)local).GetComponent<ZNetView>() != (Object)null && ((Component)local).GetComponent<ZNetView>().IsOwner() && !Console.IsVisible() && ((Object)(object)Chat.instance == (Object)null || !Chat.instance.HasFocus()) && !Menu.IsVisible() && !TextInput.IsVisible()) { return !Minimap.InTextInput(); } return false; } private bool CanUseUi(Player local) { if (controlReady && allowControl != null && allowControl.Value && !Console.IsVisible() && ((Object)(object)Chat.instance == (Object)null || !Chat.instance.HasFocus())) { if (!((Object)(object)local == (Object)null)) { if ((Object)(object)ZNet.instance != (Object)null && !((Character)local).IsTeleporting() && !TextInput.IsVisible() && !Minimap.InTextInput() && (Object)(object)((Component)local).GetComponent<ZNetView>() != (Object)null) { return ((Component)local).GetComponent<ZNetView>().IsOwner(); } return false; } return (Object)(object)FejdStartup.instance != (Object)null; } return false; } private static bool CanMove(Player local) { if (!InventoryGui.IsVisible() && !StoreGui.IsVisible() && !Minimap.IsOpen() && !Hud.IsPieceSelectionVisible() && !GameCamera.InFreeFly() && !PlayerCustomizaton.IsBarberGuiVisible() && !Hud.InRadial()) { return GameInput.CanTakeInput(local); } return false; } internal ControlFrame ControlFrame(Player local) { if (input == null || (Object)(object)controlledPlayer != (Object)(object)local) { return null; } if (input.Active && (!CanControl(local) || !CanMove(local))) { input.Stop("game_input_blocked"); } if (local.GetStamina() <= 0f) { input.ExhaustRun(); } ControlFrame controlFrame = input.Sample(); if (controlFrame != null) { appliedControl = true; return controlFrame; } if (appliedControl) { appliedControl = false; return new ControlFrame(); } return null; } private void Update() { if (Application.isFocused && Input.GetKey((KeyCode)293)) { if (allowControl != null) { allowControl.Value = false; } if (allowConsole != null) { allowConsole.Value = false; } input?.Stop("panic"); } if (input != null && (Object)(object)controlledPlayer != (Object)(object)Player.m_localPlayer) { input.Stop("player_changed"); controlledPlayer = Player.m_localPlayer; appliedControl = false; GameUi.Invalidate(); } if (input != null && input.Active && (!CanControl(Player.m_localPlayer) || !CanMove(Player.m_localPlayer))) { input.Stop("game_input_blocked"); } input?.Permit(CanControl(Player.m_localPlayer) || CanUseUi(Player.m_localPlayer)); dispatcher?.Pump(); } private void LateUpdate() { if (!((Object)(object)Instance != (Object)(object)this) && AgentMode) { if (!Application.runInBackground) { Application.runInBackground = true; } if (!AudioListener.pause) { AudioListener.pause = true; } if (AudioListener.volume != 0f) { AudioListener.volume = 0f; } } } private void OnApplicationFocus(bool focused) { if (!focused && !((Object)(object)Instance != (Object)(object)this)) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } private void OnDestroy() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) controlReady = false; if (previousBackground.HasValue) { Application.runInBackground = previousBackground.Value; } if (previousVolume.HasValue) { AudioListener.volume = previousVolume.Value; } if (previousAudioPause.HasValue) { AudioListener.pause = previousAudioPause.Value; } input?.Dispose(); server?.Dispose(); if (appliedControl && (Object)(object)controlledPlayer != (Object)null && (Object)(object)controlledPlayer == (Object)(object)Player.m_localPlayer) { controlledPlayer.SetControls(Vector3.zero, false, false, false, false, false, false, false, false, false, false, false); } Instance = null; Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private Response StopInput(string id) { input?.Stop(); return new Response { id = id, ok = true, state = "stopped", inputActive = false, inputState = input?.Reason, inputId = input?.Id }; } private Response Execute(Request request) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0865: Unknown result type (might be due to invalid IL or missing references) //IL_086d: Unknown result type (might be due to invalid IL or missing references) //IL_089c: Unknown result type (might be due to invalid IL or missing references) //IL_0509: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; ZNet instance = ZNet.instance; Response response = new Response { id = request.id, ok = true, state = "observed", version = "0.5.0", inWorld = ((Object)(object)localPlayer != (Object)null && (Object)(object)instance != (Object)null), host = ((Object)(object)instance != (Object)null && instance.IsServer()), teleportAllowed = ((Object)(object)instance != (Object)null && instance.IsServer() && allowTeleport.Value), world = (((Object)(object)localPlayer != (Object)null && (Object)(object)instance != (Object)null) ? instance.GetWorldName() : null), player = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetPlayerName() : null), position = (((Object)(object)localPlayer != (Object)null) ? PositionOf(((Component)localPlayer).transform.position) : null), teleporting = ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsTeleporting()), consoleAllowed = CanConsole, controlAllowed = CanControl(localPlayer), uiAllowed = CanUseUi(localPlayer), foreground = Application.isFocused, agentMode = AgentMode, paused = ((Object)(object)localPlayer != (Object)null && (Object)(object)ZNet.instance != (Object)null && Game.IsPaused()), timeScale = Time.timeScale, inputActive = (input != null && input.Active), inputState = input?.Reason, inputId = input?.Id, dead = ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsDead()), health = (((Object)(object)localPlayer != (Object)null) ? ((Character)localPlayer).GetHealth() : 0f), maxHealth = (((Object)(object)localPlayer != (Object)null) ? ((Character)localPlayer).GetMaxHealth() : 0f), stamina = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetStamina() : 0f), maxStamina = (((Object)(object)localPlayer != (Object)null) ? ((Character)localPlayer).GetMaxStamina() : 0f), view = (((Object)(object)Camera.main != (Object)null) ? PositionOf(((Component)Camera.main).transform.eulerAngles) : null) }; if (request.operation == "stop") { return StopInput(request.id); } if (request.operation == "console") { if (!CanConsole) { return Response.Error(request.id, "Console requires AllowConsole=true and an enabled game console", "cancelled"); } input?.Stop("console"); image = null; return GameConsole.Execute(request, response); } if (request.operation == "ui-list") { GameUi.List(response, request.offset.GetValueOrDefault()); } if (request.IsControl) { if (!((request.operation == "ui") ? CanUseUi(localPlayer) : CanControl(localPlayer)) || input == null) { return Response.Error(request.id, "Requires AllowControl=true, no console/chat, and owned player or startup UI", "cancelled"); } if ((request.operation == "input" || request.operation == "look" || (request.operation == "action" && request.action != "inventory" && request.action != "build-menu")) && !CanMove(localPlayer)) { return Response.Error(request.id, "Game controller input is blocked; close blocking UI first", "cancelled"); } if (request.operation == "action" && request.action != "inventory" && (!(request.action == "build-menu") || !Hud.IsPieceSelectionVisible()) && !GameActions.CanTakeInput(localPlayer)) { return Response.Error(request.id, "Game player input is blocked", "cancelled"); } if ((request.operation == "action" || request.operation == "ui") && input.Active) { return Response.Error(request.id, "Stop movement/combat before a UI or discrete action", "cancelled"); } input.Permit(value: true); try { input.CheckEpoch(request); if (request.operation == "input") { input.Start(request); } } catch (InvalidOperationException ex) { return Response.Error(request.id, ex.Message, "cancelled"); } if (request.operation == "input") { controlledPlayer = localPlayer; } else if (request.operation == "look") { localPlayer.SetMouseLook(new Vector2((float)request.yaw.GetValueOrDefault(), 0f - (float)request.pitch.GetValueOrDefault())); } else if (request.operation == "action") { GameActions.Execute(localPlayer, request); } else { GameUi.Execute(request); } image = null; response.state = ((request.operation == "input" || (request.operation == "action" && request.action == "place")) ? "started" : "applied"); response.inputActive = input.Active; response.inputState = input.Reason; response.inputId = input.Id; } if (request.operation == "observe") { response.inventory = (((Object)(object)localPlayer == (Object)null) ? new InventoryItem[0] : (from item in (from item in ((Humanoid)localPlayer).GetInventory().GetAllItems() where item != null select item).Take(512) select new InventoryItem { name = item.m_shared.m_name, count = item.m_stack, quality = item.m_quality, column = item.m_gridPos.x, row = item.m_gridPos.y, equipped = item.m_equipped }).ToArray()); if (captureError != null) { string text = captureError; captureError = null; if ((Object)(object)imagePlayer == (Object)(object)localPlayer) { response.captureError = text; response.state = "capture_failed"; return response; } } if (image != null && (Object)(object)imagePlayer == (Object)(object)localPlayer && Time.realtimeSinceStartup - capturedTime < 0.5f) { response.image = image; response.imageWidth = imageWidth; response.imageHeight = imageHeight; response.capturedAt = capturedAt; } else { response.state = "capture_pending"; if (!capturing) { capturing = true; ((MonoBehaviour)this).StartCoroutine(Capture(localPlayer)); } } } if (request.operation == "players") { IEnumerable<Player> allPlayers = Player.GetAllPlayers(); response.players = (from p in allPlayers ?? Enumerable.Empty<Player>() where (Object)(object)p != (Object)null select p.GetPlayerName()).ToArray(); } if (request.operation == "teleport") { if (!response.inWorld) { return Response.Error(request.id, "No local player in world", "cancelled"); } if (!response.teleportAllowed) { return Response.Error(request.id, "Teleport requires local host and AllowTeleport=true", "cancelled"); } if (((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting()) { return Response.Error(request.id, "Player dead or already teleporting", "cancelled"); } input?.Stop("teleport"); if (input != null && input.Active) { return Response.Error(request.id, "Cannot release active input", "cancelled"); } image = null; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor((float)request.x.Value, (float)request.y.Value, (float)request.z.Value); if (!((Character)localPlayer).TeleportTo(val, ((Component)localPlayer).transform.rotation, true)) { return Response.Error(request.id, "Game refused teleport", "cancelled"); } response.state = "started"; response.destination = PositionOf(val); response.teleporting = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("CLI teleport started; request " + request.id)); } return response; } private IEnumerator Capture(Player expectedPlayer) { yield return (object)new WaitForEndOfFrame(); Texture2D val = null; Texture2D val2 = null; RenderTexture val3 = null; RenderTexture active = RenderTexture.active; try { if (!((Object)(object)Player.m_localPlayer != (Object)(object)expectedPlayer)) { val = ScreenCapture.CaptureScreenshotAsTexture(); float num = Math.Min(1f, Math.Min(1280f / (float)((Texture)val).width, 720f / (float)((Texture)val).height)); imageWidth = Math.Max(1, (int)((float)((Texture)val).width * num)); imageHeight = Math.Max(1, (int)((float)((Texture)val).height * num)); val3 = RenderTexture.GetTemporary(imageWidth, imageHeight, 0); Graphics.Blit((Texture)(object)val, val3); RenderTexture.active = val3; val2 = new Texture2D(imageWidth, imageHeight, (TextureFormat)3, false); val2.ReadPixels(new Rect(0f, 0f, (float)imageWidth, (float)imageHeight), 0, 0); val2.Apply(); image = Convert.ToBase64String(ImageConversion.EncodeToPNG(val2)); imagePlayer = expectedPlayer; capturedTime = Time.realtimeSinceStartup; capturedAt = DateTime.UtcNow.ToString("o"); captureError = null; } } catch (Exception ex) { image = null; imagePlayer = expectedPlayer; captureError = "Screenshot failed: " + ex.GetType().Name; } finally { RenderTexture.active = active; if ((Object)(object)val3 != (Object)null) { RenderTexture.ReleaseTemporary(val3); } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } capturing = false; } } private static Position PositionOf(Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) return new Position { x = position.x, y = position.y, z = position.z }; } } [DataContract] public sealed class Request { [DataMember(IsRequired = true)] public string id; [DataMember(IsRequired = true)] public string token; [DataMember(IsRequired = true)] public string operation; [DataMember(EmitDefaultValue = false)] public double? x; [DataMember(EmitDefaultValue = false)] public double? y; [DataMember(EmitDefaultValue = false)] public double? z; [DataMember(EmitDefaultValue = false)] public string command; [DataMember(EmitDefaultValue = false)] public string actions; [DataMember(EmitDefaultValue = false)] public double? moveX; [DataMember(EmitDefaultValue = false)] public double? moveZ; [DataMember(EmitDefaultValue = false)] public int? durationMs; [DataMember(EmitDefaultValue = false)] public double? yaw; [DataMember(EmitDefaultValue = false)] public double? pitch; [DataMember(EmitDefaultValue = false)] public string action; [DataMember(EmitDefaultValue = false)] public int? slot; [DataMember(EmitDefaultValue = false)] public string uiAction; [DataMember(EmitDefaultValue = false)] public string button; [DataMember(EmitDefaultValue = false)] public int? scroll; [DataMember(EmitDefaultValue = false)] public string target; [DataMember(EmitDefaultValue = false)] public int? offset; internal long? controlEpoch; public bool IsControl { get { if (!(operation == "input") && !(operation == "look") && !(operation == "action")) { return operation == "ui"; } return true; } } public bool IsWrite { get { if (!(operation == "teleport") && !(operation == "console")) { return IsControl; } return true; } } public void Validate() { if (!Guid.TryParseExact(id, "D", out var result) || token == null || token.Length != 64) { throw new InvalidDataException("Invalid request schema"); } if (operation != "status" && operation != "players" && operation != "teleport" && operation != "observe" && operation != "ui-list" && operation != "console" && !IsControl && operation != "stop") { throw new InvalidDataException("Unknown operation"); } if (operation == "console") { if (string.IsNullOrWhiteSpace(command) || command.Length > 512 || command != command.Trim() || command.Any(char.IsControl)) { throw new InvalidDataException("Console requires one trimmed command line of 1..512 characters without control characters"); } } else if (command != null) { throw new InvalidDataException("Unexpected console command"); } if (operation == "teleport") { if (!Finite(x) || !Finite(y) || !Finite(z) || Math.Abs(x.Value) > 10500.0 || Math.Abs(z.Value) > 10500.0 || y < -1000.0 || y > 5000.0) { throw new InvalidDataException("Invalid coordinates"); } } else if (x.HasValue || y.HasValue || z.HasValue) { throw new InvalidDataException("Unexpected coordinates"); } if (operation == "input") { InputSpec.Validate(this); } else if (actions != null || moveX.HasValue || moveZ.HasValue || durationMs.HasValue) { throw new InvalidDataException("Unexpected input fields"); } if (operation == "look") { if ((!yaw.HasValue && !pitch.HasValue) || !InputSpec.Bounded(yaw.GetValueOrDefault(), 180.0) || !InputSpec.Bounded(pitch.GetValueOrDefault(), 180.0)) { throw new InvalidDataException("Look requires finite yaw/pitch deltas in -180..180 degrees"); } } else if (yaw.HasValue || pitch.HasValue) { throw new InvalidDataException("Unexpected look fields"); } if (operation == "action") { if (action != "menu" && action != "interact" && action != "slot" && action != "inventory" && action != "build-menu" && action != "hide" && action != "guardian" && action != "place" && action != "rotate") { throw new InvalidDataException("Unknown game action"); } bool num; if (!(action == "slot")) { num = slot.HasValue; } else { if (!slot.HasValue || slot < 1) { goto IL_046a; } num = slot > 8; } if (num) { goto IL_046a; } bool num2; if (!(action == "rotate")) { num2 = scroll.HasValue; } else { if (!scroll.HasValue || scroll == 0) { goto IL_04db; } num2 = Math.Abs((long)scroll.Value) > 10; } if (num2) { goto IL_04db; } } else if (action != null || slot.HasValue) { throw new InvalidDataException("Unexpected game action fields"); } if (operation == "ui") { if (uiAction != "click" && uiAction != "scroll") { throw new InvalidDataException("Unknown UI action"); } if (!Guid.TryParseExact(target, "D", out result)) { throw new InvalidDataException("UI requires a target ID from ui-list"); } if (button != null && button != "left" && button != "right" && button != "middle") { throw new InvalidDataException("Unknown UI button"); } bool num3; if (!(uiAction == "scroll")) { num3 = scroll.HasValue; } else { if (!scroll.HasValue || scroll == 0) { goto IL_0618; } num3 = Math.Abs((long)scroll.Value) > 10; } if (num3) { goto IL_0618; } } bool num4; if (!(operation == "ui-list")) { num4 = offset.HasValue; } else { if (!offset.HasValue) { goto IL_06a2; } if (offset < 0) { goto IL_0697; } num4 = offset > 100000; } if (num4) { goto IL_0697; } goto IL_06a2; IL_04db: throw new InvalidDataException("Rotate requires nonzero scroll in -10..10"); IL_06a2: if (operation != "ui" && (uiAction != null || button != null || target != null || (operation != "action" && scroll.HasValue))) { throw new InvalidDataException("Unexpected UI fields"); } return; IL_046a: throw new InvalidDataException("Slot requires 1..8 and only applies to the slot action"); IL_0618: throw new InvalidDataException("Scroll requires nonzero -10..10"); IL_0697: throw new InvalidDataException("Offset only applies to ui-list and must be 0..100000"); } private static bool Finite(double? value) { if (value.HasValue && !double.IsNaN(value.Value)) { return !double.IsInfinity(value.Value); } return false; } } [DataContract] public sealed class UiControl { [DataMember] public string id; [DataMember] public string path; [DataMember] public string label; [DataMember] public string type; [DataMember] public string[] actions; [DataMember] public bool interactable; [DataMember(EmitDefaultValue = false)] public string reason; } [DataContract] public sealed class InventoryItem { [DataMember] public string name; [DataMember] public int count; [DataMember] public int quality; [DataMember] public int column; [DataMember] public int row; [DataMember] public bool equipped; } [DataContract] public sealed class Position { [DataMember] public float x; [DataMember] public float y; [DataMember] public float z; } [DataContract] public sealed class Response { [DataMember] public string id; [DataMember] public bool ok; [DataMember(EmitDefaultValue = false)] public string error; [DataMember(EmitDefaultValue = false)] public string state; [DataMember(EmitDefaultValue = false)] public string version; [DataMember] public bool inWorld; [DataMember] public bool host; [DataMember] public bool teleportAllowed; [DataMember] public bool teleporting; [DataMember] public bool consoleAllowed; [DataMember(EmitDefaultValue = false)] public string[] consoleOutput; [DataMember] public bool consoleOutputTruncated; [DataMember] public bool controlAllowed; [DataMember] public bool uiAllowed; [DataMember(EmitDefaultValue = false)] public UiControl[] ui; [DataMember] public int uiTotal; [DataMember(EmitDefaultValue = false)] public int? uiNextOffset; [DataMember] public bool inputActive; [DataMember(EmitDefaultValue = false)] public string inputState; [DataMember(EmitDefaultValue = false)] public string inputId; [DataMember] public bool foreground; [DataMember] public bool agentMode; [DataMember] public bool paused; [DataMember] public float timeScale; [DataMember] public bool dead; [DataMember] public float health; [DataMember] public float maxHealth; [DataMember] public float stamina; [DataMember] public float maxStamina; [DataMember(EmitDefaultValue = false)] public Position view; [DataMember(EmitDefaultValue = false)] public InventoryItem[] inventory; [DataMember(EmitDefaultValue = false)] public string image; [DataMember] public int imageWidth; [DataMember] public int imageHeight; [DataMember(EmitDefaultValue = false)] public string capturedAt; [DataMember(EmitDefaultValue = false)] public string captureError; [DataMember(EmitDefaultValue = false)] public string world; [DataMember(EmitDefaultValue = false)] public string player; [DataMember(EmitDefaultValue = false)] public string[] players; [DataMember(EmitDefaultValue = false)] public Position position; [DataMember(EmitDefaultValue = false)] public Position destination; public static Response Error(string id, string message, string state = null) { return new Response { id = id, ok = false, error = message, state = state }; } } public static class Protocol { public const int MaxFrame = 65536; public const int MaxObservationFrame = 8388608; public static byte[] Encode<T>(T value) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(); ((XmlObjectSerializer)new DataContractJsonSerializer(typeof(T))).WriteObject((Stream)memoryStream, (object)value); return memoryStream.ToArray(); } public static Request Decode(byte[] bytes) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Invalid comparison between Unknown and I4 //IL_0270: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(); XmlDictionaryReader val = JsonReaderWriterFactory.CreateJsonReader(bytes, new XmlDictionaryReaderQuotas { MaxDepth = 4, MaxStringContentLength = 512 }); try { ((XmlReader)val).MoveToContent(); if (((XmlReader)val).Name != "root" || ((XmlReader)val).GetAttribute("type") != "object" || ((XmlReader)val).GetAttribute("__type") != null) { throw new InvalidDataException("Expected plain request object"); } while (((XmlReader)val).Read()) { if ((int)((XmlReader)val).NodeType == 1) { bool flag = ((XmlReader)val).Name == "id" || ((XmlReader)val).Name == "token" || ((XmlReader)val).Name == "operation" || ((XmlReader)val).Name == "actions" || ((XmlReader)val).Name == "action" || ((XmlReader)val).Name == "uiAction" || ((XmlReader)val).Name == "button" || ((XmlReader)val).Name == "target" || ((XmlReader)val).Name == "command"; bool flag2 = ((XmlReader)val).Name == "x" || ((XmlReader)val).Name == "y" || ((XmlReader)val).Name == "z" || ((XmlReader)val).Name == "durationMs" || ((XmlReader)val).Name == "moveX" || ((XmlReader)val).Name == "moveZ" || ((XmlReader)val).Name == "yaw" || ((XmlReader)val).Name == "pitch" || ((XmlReader)val).Name == "slot" || ((XmlReader)val).Name == "scroll" || ((XmlReader)val).Name == "offset"; if (((XmlReader)val).Depth != 1 || !hashSet.Add(((XmlReader)val).Name) || (!flag && !flag2)) { throw new InvalidDataException("Invalid request fields"); } if (((XmlReader)val).GetAttribute("type") != (flag2 ? "number" : "string")) { throw new InvalidDataException("Invalid request field type"); } } } } finally { ((IDisposable)val)?.Dispose(); } using MemoryStream memoryStream = new MemoryStream(bytes); Request obj = (Request)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(Request))).ReadObject((Stream)memoryStream); if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing data"); } obj.Validate(); return obj; } public static byte[] ReadFrame(Stream stream, byte[] header = null) { header = header ?? ReadExact(stream, 4); uint num = (uint)((header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3]); if (num == 0 || num > 65536) { throw new InvalidDataException("Invalid frame length"); } return ReadExact(stream, (int)num); } internal static byte[] ReadExact(Stream stream, int length) { byte[] array = new byte[length]; int num; for (int i = 0; i < length; i += num) { num = stream.Read(array, i, length - i); if (num == 0) { throw new EndOfStreamException(); } } return array; } internal static byte[] EncodeResponse(Response response) { byte[] array = Encode(response); if (array.Length > ((response.image == null && response.ui == null) ? 65536 : 8388608)) { throw new InvalidDataException("Response too large"); } return array; } public static void WriteFrame(Stream stream, Response response) { byte[] array = EncodeResponse(response); stream.Write(new byte[4] { (byte)(array.Length >> 24), (byte)(array.Length >> 16), (byte)(array.Length >> 8), (byte)array.Length }, 0, 4); stream.Write(array, 0, array.Length); } public static bool Authenticated(string expected, string actual) { if (expected == null || actual == null || expected.Length != 64 || actual.Length != 64) { return false; } int num = 0; for (int i = 0; i < 64; i++) { num |= expected[i] ^ actual[i]; } return num == 0; } public static string CreateToken() { byte[] array = new byte[32]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } return BitConverter.ToString(array).Replace("-", "").ToLowerInvariant(); } } public sealed class Server : IDisposable { private readonly TcpListener listener; private readonly Dispatcher dispatcher; private readonly string token; private readonly Action<Exception> logError; private readonly Func<string, Response> stopInput; private readonly Func<long> inputEpoch; private readonly Thread thread; private volatile bool stopped; private TcpClient active; private readonly object gate = new object(); private readonly HashSet<string> writes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public int Port => ((IPEndPoint)listener.LocalEndpoint).Port; public Server(int port, string token, Dispatcher dispatcher, Action<Exception> logError, Func<string, Response> stopInput = null, Func<long> inputEpoch = null) { if (token == null || token.Length != 64) { throw new ArgumentException("Invalid token"); } this.token = token; this.dispatcher = dispatcher; this.logError = logError; this.stopInput = stopInput; this.inputEpoch = inputEpoch; listener = new TcpListener(IPAddress.Loopback, port); listener.Start(8); thread = new Thread(Listen) { IsBackground = true, Name = "ValheimCliBridge" }; thread.Start(); } private void Listen() { while (!stopped) { try { TcpClient client = listener.AcceptTcpClient(); try { using (new Timer(delegate { client.Close(); }, null, 4000, -1)) { lock (gate) { if (stopped) { break; } active = client; } client.ReceiveTimeout = 1000; client.SendTimeout = 1000; using (NetworkStream stream = client.GetStream()) { byte[] array = Protocol.ReadExact(stream, 4); bool flag = array[0] != 0; int status = 200; Response response; try { Request request = Protocol.Decode(flag ? HttpTransport.Read(stream, array, Port) : Protocol.ReadFrame(stream, array)); if (!Protocol.Authenticated(token, request.token)) { response = Response.Error(request.id, "Unauthorized"); status = 401; } else if (request.operation == "stop" && stopInput != null) { response = stopInput(request.id); } else if (request.IsWrite && writes.Contains(request.id)) { response = Response.Error(request.id, "Write ID already used; inspect status, do not retry"); } else if (request.IsWrite && writes.Count >= 65536) { response = Response.Error(request.id, "Write limit reached; restart the bridge before a new session"); } else { if (request.IsControl && inputEpoch != null) { request.controlEpoch = inputEpoch(); } if (request.IsWrite) { writes.Add(request.id); } response = dispatcher.Run(request); if (request.IsWrite && response.state == "cancelled") { writes.Remove(request.id); } } } catch { response = Response.Error(null, "Invalid request"); status = 400; } if (flag) { HttpTransport.Write(stream, response, status); } else { Protocol.WriteFrame(stream, response); } } lock (gate) { active = null; } } } finally { if (client != null) { ((IDisposable)client).Dispose(); } } } catch (IOException) { } catch (InvalidDataException) { } catch (SerializationException) { } catch (SocketException) { if (stopped) { break; } } catch (ObjectDisposedException) { if (stopped) { break; } } catch (Exception obj2) { if (stopped) { break; } logError(obj2); } } } public void Dispose() { stopped = true; dispatcher.Stop(); listener.Stop(); lock (gate) { active?.Close(); } thread.Join(3000); } } }