Decompiled source of Cyberpunk PINK Companion Mod v1.1.11
BepInEx/plugins/CyberpunkPINKCreatureBridge.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using BepInEx; using BepInEx.Logging; using UnityEngine; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace CyberpunkPINK.CreatureBridge; [BepInPlugin("cyberpunk.pink.creaturebridge", "Cyberpunk PINK Creature Bridge", "1.1.11")] public sealed class CreatureBridgePlugin : BaseUnityPlugin { private sealed class PinkRadialAction { public string Key; public string Label; public string RegistrationKey; public string Group; public string TargetKind; public bool RestrictTargets; public readonly HashSet<string> AllowedTargets = new HashSet<string>(); public bool HasMaxRange; public double MaxRangeMeters; public bool MaxRangeInclusive; } private sealed class PinkRadialEvent { public long Seq; public string Action; public string Label; public string Actor; public string Target; public string TargetCreature; public bool HasDistance; public double DistanceTiles; } private sealed class PinkMovementEvent { public long Seq; public string Creature; public string TurnId; public double DistanceWorld; public double DistanceMeters; } private const int Port = 31748; private const double PinkMetersPerWorldUnit = 2.0; private const string PinkRadialRootKey = "cyberpunk.pink.radial.root.1_1_7"; private HttpListener _listener; private Thread _serverThread; private volatile bool _running; private int _httpRequestCount; private string _lastHttpRequest = "none"; private string _lastHttpOrigin = ""; private string _lastPrivateNetworkRequest = ""; private readonly ConcurrentQueue<Action> _mainThread = new ConcurrentQueue<Action>(); private readonly object _pointPickLock = new object(); private HttpListenerContext _pointPickContext; private DateTime _pointPickDeadlineUtc; private bool _pointPickWaitForRelease; private bool _pointPickResolveNextFrame; private readonly object _movementLock = new object(); private readonly List<PinkMovementEvent> _movementEvents = new List<PinkMovementEvent>(); private readonly HashSet<string> _publishedMovementCreatureIds = new HashSet<string>(StringComparer.Ordinal); private long _movementSeq; private readonly string _movementSession = Guid.NewGuid().ToString("N"); private HttpListenerContext _movementWaitContext; private long _movementWaitAfter; private DateTime _movementWaitDeadlineUtc; private Type _waypointMovementTypeCache; private MethodInfo _waypointSetMovementStateCache; private MethodInfo _waypointClearMovementStateCache; private MethodInfo _waypointPopMovementEventCache; private readonly object _radialLock = new object(); private readonly List<PinkRadialAction> _radialActions = new List<PinkRadialAction>(); private readonly Dictionary<string, string> _radialTargetKinds = new Dictionary<string, string>(); private readonly List<object> _radialSubmenuItems = new List<object>(); private readonly Dictionary<string, object> _radialGroupMenuItems = new Dictionary<string, object>(StringComparer.Ordinal); private readonly Dictionary<string, List<object>> _radialGroupSubmenuItems = new Dictionary<string, List<object>>(StringComparer.Ordinal); private readonly Dictionary<string, string> _radialGroupGuids = new Dictionary<string, string>(StringComparer.Ordinal); private bool _radialRootRegistered; private Sprite _pinkRadialSprite; private Texture2D _pinkRadialTexture; private string _radialActorId = ""; private long _radialSeq; private PinkRadialEvent _lastRadialEvent; private string _lastRadialVisibility = "not checked yet"; private bool _lastRadialVisibilityResult; private int _radialVisibilityChecks; private string _radialCheckerSignature = "not registered yet"; private Type _radialUiTypeCache; private Type _radialSubmenuTypeCache; private readonly Dictionary<string, MethodInfo> _radialUiMethodCache = new Dictionary<string, MethodInfo>(StringComparer.Ordinal); private MethodInfo[] _creatureTryGetAssetMethods = new MethodInfo[0]; private MethodInfo[] _allCreatureAssetViewMethods = new MethodInfo[0]; private readonly Dictionary<string, object> _liveCreatureGuidCache = new Dictionary<string, object>(StringComparer.Ordinal); private int _radialReflectionWarmState; private HttpListenerContext _radialWaitContext; private long _radialWaitAfter; private DateTime _radialWaitDeadlineUtc; private ManualLogSource Logx => ((BaseUnityPlugin)this).Logger; private void Awake() { try { _listener = new HttpListener(); _listener.Prefixes.Add("http://127.0.0.1:" + 31748 + "/"); _listener.Start(); _running = true; _serverThread = new Thread(ServerLoop); _serverThread.IsBackground = true; _serverThread.Name = "PINK Creature Bridge"; _serverThread.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("PINK Creature Bridge listening on 127.0.0.1:" + 31748)); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Native methods visible: " + DescribeNativeMethods())); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Could not start PINK Creature Bridge: " + ex)); } } private void Update() { //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) int num = 0; Action result; while (num++ < 16 && _mainThread.TryDequeue(out result)) { try { result(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } CheckRadialWaiterTimeout(); PollWaypointMovementEvents(); CheckMovementWaiterTimeout(); HttpListenerContext httpListenerContext = null; string s = null; Vector3? val = null; lock (_pointPickLock) { if (_pointPickContext != null) { if (DateTime.UtcNow > _pointPickDeadlineUtc) { httpListenerContext = _pointPickContext; _pointPickContext = null; _pointPickResolveNextFrame = false; s = "Point pick timed out."; } else if (_pointPickWaitForRelease) { if (!Input.GetMouseButton(0)) { _pointPickWaitForRelease = false; } } else if (Input.GetKeyDown((KeyCode)27) || Input.GetMouseButtonDown(1)) { httpListenerContext = _pointPickContext; _pointPickContext = null; _pointPickResolveNextFrame = false; s = "Point pick canceled."; } else if (_pointPickResolveNextFrame) { _pointPickResolveNextFrame = false; if (TryResolveTaleSpireBoardPoint(out var point, out var detail)) { httpListenerContext = _pointPickContext; _pointPickContext = null; val = point; s = detail; } else { httpListenerContext = _pointPickContext; _pointPickContext = null; s = "TaleSpire received the click but did not expose a board position. " + detail; } } else if (Input.GetMouseButtonDown(0)) { _pointPickResolveNextFrame = true; } } } if (httpListenerContext != null) { if (val.HasValue) { Vector3 value = val.Value; Send(httpListenerContext, 200, "{\"ok\":true,\"x\":" + value.x.ToString(CultureInfo.InvariantCulture) + ",\"y\":" + value.y.ToString(CultureInfo.InvariantCulture) + ",\"z\":" + value.z.ToString(CultureInfo.InvariantCulture) + ",\"resolver\":" + Json(s) + "}"); } else { Send(httpListenerContext, 409, "{\"ok\":false,\"error\":" + Json(s) + "}"); } } } private static bool Finite(float v) { if (!float.IsNaN(v)) { return !float.IsInfinity(v); } return false; } private static bool TryVector3FromObject(object value, out Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) point = Vector3.zero; if (value == null) { return false; } if (value is Vector3 val) { if (!Finite(val.x) || !Finite(val.y) || !Finite(val.z)) { return false; } point = val; return true; } value.GetType(); try { object obj = MemberValue(value, "x"); object obj2 = MemberValue(value, "y"); object obj3 = MemberValue(value, "z"); if (obj == null || obj2 == null || obj3 == null) { return false; } float num = Convert.ToSingle(obj, CultureInfo.InvariantCulture); float num2 = Convert.ToSingle(obj2, CultureInfo.InvariantCulture); float num3 = Convert.ToSingle(obj3, CultureInfo.InvariantCulture); if (!Finite(num) || !Finite(num2) || !Finite(num3)) { return false; } point = new Vector3(num, num2, num3); return true; } catch { return false; } } private bool TryResolveTaleSpireBoardPoint(out Vector3 point, out string detail) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_03cf: Unknown result type (might be due to invalid IL or missing references) point = Vector3.zero; List<string> list = new List<string>(); foreach (MethodInfo item in MethodsNamed("TryGetPickedPosition")) { try { ParameterInfo[] parameters = item.GetParameters(); if (!item.IsStatic || parameters.Length != 1 || !parameters[0].ParameterType.IsByRef) { list.Add(Sig(item) + " skipped: expected static one-out-parameter form"); continue; } Type elementType = parameters[0].ParameterType.GetElementType(); object obj = ((elementType != null && elementType.IsValueType) ? Activator.CreateInstance(elementType) : null); object[] array = new object[1] { obj }; object obj2 = item.Invoke(null, array); if (obj2 is int num && num != 0 && TryVector3FromObject(array[0], out var point2)) { point = point2; detail = Sig(item); return true; } list.Add(Sig(item) + " returned " + ((obj2 == null) ? "null" : obj2.ToString()) + ((array[0] == null) ? ", no point" : (", point=" + array[0].ToString()))); } catch (TargetInvocationException ex) { list.Add(Sig(item) + ": " + (ex.InnerException ?? ex).Message); } catch (Exception ex2) { list.Add(Sig(item) + ": " + ex2.Message); } } foreach (MethodInfo item2 in MethodsNamed("GetLastCursorWorldPosition")) { try { if (!item2.IsStatic || item2.GetParameters().Length != 0) { list.Add(Sig(item2) + " skipped: expected static no-argument form"); continue; } object value = item2.Invoke(null, new object[0]); if (TryVector3FromObject(value, out var point3)) { point = point3; detail = Sig(item2); return true; } list.Add(Sig(item2) + " returned no usable Vector3"); } catch (TargetInvocationException ex3) { list.Add(Sig(item2) + ": " + (ex3.InnerException ?? ex3).Message); } catch (Exception ex4) { list.Add(Sig(item2) + ": " + ex4.Message); } } foreach (MethodInfo item3 in MethodsNamed("TryGetPicked")) { try { ParameterInfo[] parameters2 = item3.GetParameters(); if (!item3.IsStatic || parameters2.Length == 0 || !parameters2[0].ParameterType.IsByRef) { continue; } object[] array2 = new object[parameters2.Length]; for (int i = 0; i < parameters2.Length; i++) { Type parameterType = parameters2[i].ParameterType; Type type = (parameterType.IsByRef ? parameterType.GetElementType() : parameterType); array2[i] = ((type != null && type.IsValueType) ? Activator.CreateInstance(type) : null); } object obj3 = item3.Invoke(null, array2); if (TryVector3FromObject(array2[0], out var point4)) { string text = ((obj3 == null) ? "null" : obj3.ToString()); if (!string.Equals(text, "None", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Invalid", StringComparison.OrdinalIgnoreCase)) { point = point4; detail = Sig(item3) + " returned " + text; return true; } } list.Add(Sig(item3) + " returned " + ((obj3 == null) ? "null" : obj3.ToString())); } catch (TargetInvocationException ex5) { list.Add(Sig(item3) + ": " + (ex5.InnerException ?? ex5).Message); } catch (Exception ex6) { list.Add(Sig(item3) + ": " + ex6.Message); } } detail = ((list.Count == 0) ? "No TaleSpire point-picking methods were found." : string.Join(" || ", list.Take(12).ToArray())); return false; } private void OnDestroy() { _running = false; try { ClearRadialActions(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not remove PINK radial actions: " + ex.Message)); } HttpListenerContext httpListenerContext = null; lock (_radialLock) { httpListenerContext = _radialWaitContext; _radialWaitContext = null; } if (httpListenerContext != null) { try { Send(httpListenerContext, 503, "{\"ok\":false,\"error\":\"Creature bridge is shutting down.\"}"); } catch { } } HttpListenerContext httpListenerContext2 = null; lock (_movementLock) { httpListenerContext2 = _movementWaitContext; _movementWaitContext = null; } if (httpListenerContext2 != null) { try { Send(httpListenerContext2, 503, "{\"ok\":false,\"error\":\"Creature bridge is shutting down.\"}"); } catch { } } HttpListenerContext httpListenerContext3 = null; lock (_pointPickLock) { httpListenerContext3 = _pointPickContext; _pointPickContext = null; _pointPickResolveNextFrame = false; } if (httpListenerContext3 != null) { try { Send(httpListenerContext3, 503, "{\"ok\":false,\"error\":\"Creature bridge is shutting down.\"}"); } catch { } } try { if (_listener != null) { _listener.Stop(); } } catch { } try { if (_listener != null) { _listener.Close(); } } catch { } } private void ServerLoop() { while (_running) { try { HttpListenerContext ctx = _listener.GetContext(); ThreadPool.QueueUserWorkItem(delegate { Handle(ctx); }); } catch (HttpListenerException) { if (!_running) { break; } } catch (ObjectDisposedException) { break; } catch (Exception ex3) { Logx.LogError((object)("Bridge server error: " + ex3)); } } } private void Handle(HttpListenerContext ctx) { try { string text = ctx.Request.Url.AbsolutePath ?? "/"; string text2 = ctx.Request.Headers["Origin"] ?? ""; string text3 = ctx.Request.Headers["Access-Control-Request-Private-Network"] ?? ""; Interlocked.Increment(ref _httpRequestCount); if (!string.Equals(text.TrimEnd('/'), "/status", StringComparison.OrdinalIgnoreCase) && text.TrimEnd('/').Length != 0) { _lastHttpRequest = (ctx.Request.HttpMethod ?? "") + " " + text; _lastHttpOrigin = text2; _lastPrivateNetworkRequest = text3; } AddCors(ctx.Response); if (string.Equals(ctx.Request.HttpMethod, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { Logx.LogInfo((object)("PINK bridge preflight: " + _lastHttpRequest + " origin=" + ((text2.Length == 0) ? "<none>" : text2) + " private-network=" + ((text3.Length == 0) ? "<none>" : text3))); Send(ctx, 204, ""); return; } object obj = default(object); switch (text.TrimEnd('/').ToLowerInvariant()) { case "": case "/status": { bool lockTaken3 = false; string radialActorId; string lastRadialVisibility; bool lastRadialVisibilityResult; int radialVisibilityChecks; string radialCheckerSignature; bool radialRootRegistered; try { Monitor.Enter(obj = _radialLock, ref lockTaken3); radialActorId = _radialActorId; lastRadialVisibility = _lastRadialVisibility; lastRadialVisibilityResult = _lastRadialVisibilityResult; radialVisibilityChecks = _radialVisibilityChecks; radialCheckerSignature = _radialCheckerSignature; radialRootRegistered = _radialRootRegistered; } finally { if (lockTaken3) { Monitor.Exit(obj); } } Send(ctx, 200, "{\"ok\":true,\"version\":\"1.1.11\",\"port\":31748,\"radial\":true,\"radialRoot\":true,\"movement\":true,\"movementSession\":" + Json(_movementSession) + ",\"waypointMovement\":" + (WaypointMovementAvailable() ? "true" : "false") + ",\"radialRegistered\":" + (radialRootRegistered ? "true" : "false") + ",\"radialActions\":" + RadialActionCount() + ",\"radialActor\":" + Json(radialActorId) + ",\"radialVisibilityChecks\":" + radialVisibilityChecks + ",\"radialVisibilityResult\":" + (lastRadialVisibilityResult ? "true" : "false") + ",\"radialVisibility\":" + Json(lastRadialVisibility) + ",\"radialChecker\":" + Json(radialCheckerSignature) + ",\"httpRequests\":" + _httpRequestCount + ",\"lastHttp\":" + Json(_lastHttpRequest) + ",\"lastOrigin\":" + Json(_lastHttpOrigin) + ",\"lastPrivateNetworkRequest\":" + Json(_lastPrivateNetworkRequest) + ",\"native\":" + Json(DescribeNativeMethods()) + "}"); break; } case "/pick-point": { bool lockTaken6 = false; try { Monitor.Enter(obj = _pointPickLock, ref lockTaken6); if (_pointPickContext != null) { Send(ctx, 409, "{\"ok\":false,\"error\":\"A board-point pick is already waiting for a click.\"}"); break; } _pointPickContext = ctx; _pointPickDeadlineUtc = DateTime.UtcNow.AddSeconds(35.0); _pointPickWaitForRelease = true; _pointPickResolveNextFrame = false; break; } finally { if (lockTaken6) { Monitor.Exit(obj); } } } case "/movement-state": { if (!string.Equals(ctx.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) { Send(ctx, 405, "{\"ok\":false,\"error\":\"Use POST for movement-state.\"}"); break; } if (!WaypointMovementAvailable()) { Send(ctx, 409, "{\"ok\":false,\"error\":\"Waypoint Movement 0.1.11 or newer is not loaded.\"}"); break; } string body2; using (StreamReader streamReader3 = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8)) { body2 = streamReader3.ReadToEnd(); } Dictionary<string, List<string>> dictionary7 = ParseForm(body2); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); int num10 = 0; if (dictionary7.TryGetValue("state", out var value13)) { foreach (string item in value13) { string[] array3 = (item ?? "").Split(new char[1] { '\t' }); if (array3.Length >= 4) { string text25 = NormalizeGuidText(array3[0]); string text26 = (array3[1] ?? "").Trim(); if (text25.Length != 0 && text26.Length != 0 && float.TryParse((array3[2] ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) && float.TryParse((array3[3] ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) && SetWaypointMovementState(text25, text26, result3, result4)) { hashSet2.Add(text25); num10++; } } } } bool lockTaken4 = false; string[] array4; try { Monitor.Enter(obj = _movementLock, ref lockTaken4); array4 = _publishedMovementCreatureIds.ToArray(); } finally { if (lockTaken4) { Monitor.Exit(obj); } } string[] array = array4; foreach (string text27 in array) { if (!hashSet2.Contains(text27)) { ClearWaypointMovementState(text27); } } bool lockTaken5 = false; try { Monitor.Enter(obj = _movementLock, ref lockTaken5); _publishedMovementCreatureIds.Clear(); foreach (string item2 in hashSet2) { _publishedMovementCreatureIds.Add(item2); } } finally { if (lockTaken5) { Monitor.Exit(obj); } } Send(ctx, 200, "{\"ok\":true,\"count\":" + num10 + ",\"metersPerWorldUnit\":" + 2.0.ToString(CultureInfo.InvariantCulture) + "}"); break; } case "/movement-next": { long after = 0L; long.TryParse(ctx.Request.QueryString["after"] ?? "0", out after); string a = ctx.Request.QueryString["session"] ?? ""; if (!string.Equals(a, _movementSession, StringComparison.Ordinal)) { after = 0L; } PinkMovementEvent pinkMovementEvent = null; HttpListenerContext httpListenerContext2 = null; bool lockTaken2 = false; try { Monitor.Enter(obj = _movementLock, ref lockTaken2); pinkMovementEvent = _movementEvents.FirstOrDefault((PinkMovementEvent e) => e != null && e.Seq > after); if (pinkMovementEvent == null) { httpListenerContext2 = _movementWaitContext; _movementWaitContext = ctx; _movementWaitAfter = after; _movementWaitDeadlineUtc = DateTime.UtcNow.AddSeconds(20.0); } } finally { if (lockTaken2) { Monitor.Exit(obj); } } if (httpListenerContext2 != null) { Send(httpListenerContext2, 409, "{\"ok\":false,\"error\":\"A newer movement listener replaced this one.\"}"); } if (pinkMovementEvent != null) { SendMovementEvent(ctx, pinkMovementEvent); } break; } case "/radial-actions": { if (!string.Equals(ctx.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) { Send(ctx, 405, "{\"ok\":false,\"error\":\"Use POST for radial-actions.\"}"); break; } string body; using (StreamReader streamReader2 = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8)) { body = streamReader2.ReadToEnd(); } Dictionary<string, List<string>> dictionary = ParseForm(body); string actor = FirstForm(dictionary, "actor"); Logx.LogInfo((object)("PINK radial-actions received from origin=" + (((_lastHttpOrigin ?? "").Length == 0) ? "<none>" : _lastHttpOrigin))); Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); if (dictionary.TryGetValue("actionTarget", out var value)) { foreach (string item3 in value) { string text6 = item3 ?? ""; int num = text6.IndexOf('\t'); if (num >= 0) { string text7 = text6.Substring(0, num).Trim(); string value2 = text6.Substring(num + 1).Trim().ToLowerInvariant(); if (text7.Length > 0) { dictionary2[text7] = value2; } } } } Dictionary<string, string> dictionary3 = new Dictionary<string, string>(); if (dictionary.TryGetValue("actionGroup", out var value3)) { foreach (string item4 in value3) { string text8 = item4 ?? ""; int num2 = text8.IndexOf('\t'); if (num2 >= 0) { string text9 = text8.Substring(0, num2).Trim(); string text10 = text8.Substring(num2 + 1).Trim(); if (text9.Length > 0 && text10.Length > 0) { dictionary3[text9] = text10; } } } } Dictionary<string, HashSet<string>> dictionary4 = new Dictionary<string, HashSet<string>>(); if (dictionary.TryGetValue("actionAllow", out var value4)) { foreach (string item5 in value4) { string text11 = item5 ?? ""; int num3 = text11.IndexOf('\t'); if (num3 < 0) { continue; } string text12 = text11.Substring(0, num3).Trim(); if (text12.Length == 0) { continue; } HashSet<string> hashSet = new HashSet<string>(); string text13 = text11.Substring(num3 + 1); string[] array = text13.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string value5 in array) { string text14 = NormalizeGuidText(value5); if (text14.Length > 0) { hashSet.Add(text14); } } dictionary4[text12] = hashSet; } } Dictionary<string, double> dictionary5 = new Dictionary<string, double>(); Dictionary<string, bool> dictionary6 = new Dictionary<string, bool>(); if (dictionary.TryGetValue("actionRange", out var value6)) { foreach (string item6 in value6) { string text15 = item6 ?? ""; string[] array2 = text15.Split(new char[1] { '\t' }); if (array2.Length < 2) { continue; } string text16 = (array2[0] ?? "").Trim(); if (text16.Length == 0 || !double.TryParse((array2[1] ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || double.IsNaN(result) || double.IsInfinity(result) || result < 0.0) { continue; } bool value7 = true; if (array2.Length >= 3 && !string.IsNullOrWhiteSpace(array2[2])) { string text17 = array2[2].Trim().ToLowerInvariant(); int num5; switch (text17) { default: num5 = ((text17 == "<=") ? 1 : 0); break; case "1": case "true": case "yes": case "inclusive": num5 = 1; break; } value7 = (byte)num5 != 0; } dictionary5[text16] = result; dictionary6[text16] = value7; } } Dictionary<string, string> targetKinds = new Dictionary<string, string>(); if (dictionary.TryGetValue("targetKind", out var value8)) { foreach (string item7 in value8) { string text18 = item7 ?? ""; int num6 = text18.IndexOf('\t'); if (num6 >= 0) { string text19 = NormalizeGuidText(text18.Substring(0, num6)); string text20 = text18.Substring(num6 + 1).Trim().ToLowerInvariant(); if (text19.Length > 0 && text20.Length > 0) { targetKinds[text19] = text20; } } } } List<PinkRadialAction> specs = new List<PinkRadialAction>(); if (dictionary.TryGetValue("action", out var value9)) { int num7 = 0; foreach (string item8 in value9) { string text21 = item8 ?? ""; int num8 = text21.IndexOf('\t'); string text22 = ((num8 >= 0) ? text21.Substring(0, num8) : text21); string text23 = ((num8 >= 0) ? text21.Substring(num8 + 1) : text21); text22 = (text22 ?? "").Trim(); text23 = (text23 ?? "").Trim(); if (text22.Length == 0 || text23.Length == 0) { continue; } PinkRadialAction pinkRadialAction = new PinkRadialAction(); pinkRadialAction.Key = text22; pinkRadialAction.Label = text23; int num9 = ++num7; pinkRadialAction.RegistrationKey = "cyberpunk.pink.radial." + num9; pinkRadialAction.Group = (dictionary3.ContainsKey(text22) ? dictionary3[text22] : ""); pinkRadialAction.TargetKind = (dictionary2.ContainsKey(text22) ? dictionary2[text22] : "creature"); PinkRadialAction pinkRadialAction2 = pinkRadialAction; if (dictionary4.TryGetValue(text22, out var value10)) { pinkRadialAction2.RestrictTargets = true; foreach (string item9 in value10) { pinkRadialAction2.AllowedTargets.Add(item9); } } if (dictionary5.TryGetValue(text22, out var value11)) { pinkRadialAction2.HasMaxRange = true; pinkRadialAction2.MaxRangeMeters = value11; pinkRadialAction2.MaxRangeInclusive = !dictionary6.TryGetValue(text22, out var value12) || value12; } else { string text24 = ((text22 ?? "") + " " + (text23 ?? "")).Trim().ToLowerInvariant(); if (text24.Contains("quickhack")) { pinkRadialAction2.HasMaxRange = true; pinkRadialAction2.MaxRangeMeters = 50.0; pinkRadialAction2.MaxRangeInclusive = true; } else if (text24.Contains("analyze em")) { pinkRadialAction2.HasMaxRange = true; pinkRadialAction2.MaxRangeMeters = 20.0; pinkRadialAction2.MaxRangeInclusive = true; } else if (text24.Contains("integrated medscanner") || text24.Contains("scan organic target")) { pinkRadialAction2.HasMaxRange = true; pinkRadialAction2.MaxRangeMeters = 4.0; pinkRadialAction2.MaxRangeInclusive = false; } } specs.Add(pinkRadialAction2); } } OnMainThread(delegate { try { SetRadialActions(actor, specs, targetKinds); Send(ctx, 200, "{\"ok\":true,\"count\":" + specs.Count + "}"); } catch (Exception ex2) { Send(ctx, 500, "{\"ok\":false,\"error\":" + Json(ex2.ToString()) + "}"); } }); break; } case "/radial-next": { long result2 = 0L; long.TryParse(ctx.Request.QueryString["after"] ?? "0", out result2); PinkRadialEvent pinkRadialEvent = null; HttpListenerContext httpListenerContext = null; bool lockTaken = false; try { Monitor.Enter(obj = _radialLock, ref lockTaken); if (_lastRadialEvent != null && _lastRadialEvent.Seq > result2) { pinkRadialEvent = _lastRadialEvent; } else { httpListenerContext = _radialWaitContext; _radialWaitContext = ctx; _radialWaitAfter = result2; _radialWaitDeadlineUtc = DateTime.UtcNow.AddSeconds(20.0); } } finally { if (lockTaken) { Monitor.Exit(obj); } } if (httpListenerContext != null) { Send(httpListenerContext, 409, "{\"ok\":false,\"error\":\"A newer radial listener replaced this one.\"}"); } if (pinkRadialEvent != null) { SendRadialEvent(ctx, pinkRadialEvent); } break; } case "/capture": { string id = ctx.Request.QueryString["id"] ?? ""; if (string.IsNullOrWhiteSpace(id)) { Send(ctx, 400, "{\"ok\":false,\"error\":\"Missing creature id.\"}"); break; } OnMainThread(delegate { try { string detail; string text28 = CaptureBlueprint(id, out detail); if (string.IsNullOrEmpty(text28)) { Send(ctx, 500, "{\"ok\":false,\"error\":" + Json(detail) + "}"); } else { Send(ctx, 200, "{\"ok\":true,\"blueprint\":" + Json(text28) + ",\"source\":\"talespire-native\"}"); } } catch (Exception ex2) { Send(ctx, 500, "{\"ok\":false,\"error\":" + Json(ex2.ToString()) + "}"); } }); break; } case "/spawn": { string text4; using (StreamReader streamReader = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8)) { text4 = streamReader.ReadToEnd(); } string text5 = text4 ?? ""; if (text5.StartsWith("blueprint=", StringComparison.OrdinalIgnoreCase)) { text5 = Uri.UnescapeDataString(text5.Substring("blueprint=".Length).Replace("+", " ")); } if (!text5.StartsWith("talespire://creature-blueprint/", StringComparison.OrdinalIgnoreCase)) { Send(ctx, 400, "{\"ok\":false,\"error\":\"Body is not a TaleSpire creature blueprint.\"}"); break; } string captured = text5; OnMainThread(delegate { try { string detail; bool flag = SpawnBlueprint(captured, out detail); Send(ctx, flag ? 200 : 500, "{\"ok\":" + (flag ? "true" : "false") + (flag ? ",\"mode\":\"native-placement\"" : (",\"error\":" + Json(detail))) + "}"); } catch (Exception ex2) { Send(ctx, 500, "{\"ok\":false,\"error\":" + Json(ex2.ToString()) + "}"); } }); break; } default: Send(ctx, 404, "{\"ok\":false,\"error\":\"Unknown endpoint.\"}"); break; } } catch (Exception ex) { try { Send(ctx, 500, "{\"ok\":false,\"error\":" + Json(ex.ToString()) + "}"); } catch { } } } private static Dictionary<string, List<string>> ParseForm(string body) { Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); string[] array = (body ?? "").Split('&'); foreach (string text in array) { if (!string.IsNullOrEmpty(text)) { int num = text.IndexOf('='); string text2 = ((num >= 0) ? text.Substring(0, num) : text); string text3 = ((num >= 0) ? text.Substring(num + 1) : ""); text2 = Uri.UnescapeDataString((text2 ?? "").Replace("+", " ")); text3 = Uri.UnescapeDataString((text3 ?? "").Replace("+", " ")); if (!dictionary.TryGetValue(text2, out var value)) { value = (dictionary[text2] = new List<string>()); } value.Add(text3); } } return dictionary; } private static string FirstForm(Dictionary<string, List<string>> form, string key) { if (form == null || !form.TryGetValue(key, out var value) || value.Count <= 0) { return ""; } return value[0] ?? ""; } private int RadialActionCount() { lock (_radialLock) { return _radialActions.Count; } } private static string NormalizeGuidText(object value) { string text = ((value == null) ? "" : value.ToString()); StringBuilder stringBuilder = new StringBuilder(); string text2 = text; foreach (char c in text2) { if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { stringBuilder.Append(char.ToLowerInvariant(c)); } } string text3 = stringBuilder.ToString(); if (text3.Length < 32) { return text3; } return text3.Substring(text3.Length - 32); } private Type RadialUiType() { if (_radialUiTypeCache != null) { return _radialUiTypeCache; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = null; try { type = assembly.GetType("RadialUI.RadialUIPlugin", throwOnError: false); } catch { } if (type != null) { _radialUiTypeCache = type; return type; } } return null; } private Type RadialSubmenuType() { if (_radialSubmenuTypeCache != null) { return _radialSubmenuTypeCache; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = null; try { type = assembly.GetType("RadialUI.RadialSubmenu", throwOnError: false); } catch { } if (type != null) { _radialSubmenuTypeCache = type; return type; } } return null; } private MethodInfo RadialUiMethod(string name, int parameterCount) { string key = name + "/" + parameterCount; lock (_radialLock) { if (_radialUiMethodCache.TryGetValue(key, out var value)) { return value; } } Type type = RadialUiType(); if (type == null) { return null; } MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == name && m.GetParameters().Length == parameterCount); if (methodInfo != null) { lock (_radialLock) { _radialUiMethodCache[key] = methodInfo; } } return methodInfo; } private bool RadialActionMatchesTargetLocked(PinkRadialAction spec, string targetId, bool hasDistance, double distanceMeters) { if (spec == null) { return false; } targetId = NormalizeGuidText(targetId); if (targetId.Length == 0) { return false; } if (spec.RestrictTargets && !spec.AllowedTargets.Contains(targetId)) { return false; } string text = (spec.TargetKind ?? "creature").Trim().ToLowerInvariant(); bool flag = true; if (text.Length > 0 && text != "creature") { if (!_radialTargetKinds.TryGetValue(targetId, out var value)) { return false; } value = (value ?? "").Trim().ToLowerInvariant(); flag = ((text == "person") ? (value.Length > 0 && value != "vehicle" && value != "drone" && value != "ice") : ((!(text == "machine")) ? (value == text) : (value == "vehicle" || value == "drone" || value == "ice"))); } if (!flag) { return false; } if (spec.HasMaxRange) { if (!hasDistance || double.IsNaN(distanceMeters) || double.IsInfinity(distanceMeters)) { return false; } if (!spec.MaxRangeInclusive) { return distanceMeters < spec.MaxRangeMeters; } return distanceMeters <= spec.MaxRangeMeters; } return true; } private string CurrentRadialTargetId() { try { MethodInfo methodInfo = RadialUiMethod("GetLastRadialTargetCreature", 0); if (methodInfo == null) { return ""; } object obj = methodInfo.Invoke(null, null); string text = NormalizeGuidText((obj == null) ? "" : obj.ToString()); CacheLiveCreatureGuid(text, obj); return text; } catch { return ""; } } private static string RuntimeValueDescription(object value) { if (value == null) { return "<null>"; } string text; try { text = value.ToString(); } catch (Exception ex) { text = "<ToString failed: " + ex.Message + ">"; } return value.GetType().FullName + " = " + text + " [norm=" + NormalizeGuidText(text) + "]"; } private bool RadialVisibleObjects(object selected, object targeted) { string radialActorId; int count; lock (_radialLock) { radialActorId = _radialActorId; count = _radialActions.Count; } string text = NormalizeGuidText(radialActorId); string text2 = NormalizeGuidText((selected == null) ? "" : selected.ToString()); string text3 = NormalizeGuidText((targeted == null) ? "" : targeted.ToString()); CacheLiveCreatureGuid(text2, selected); CacheLiveCreatureGuid(text3, targeted); bool flag = false; string candidate = ""; bool hasCandidateDistance = false; double candidateDistanceMeters = 0.0; if (!string.IsNullOrWhiteSpace(radialActorId) && count > 0 && text.Length > 0) { bool flag2 = text2 == text; bool flag3 = text3 == text; if (flag2 && text3.Length > 0 && text3 != text2) { candidate = text3; } else if (flag3 && text2.Length > 0 && text2 != text3) { candidate = text2; } if (candidate.Length > 0) { hasCandidateDistance = TryRadialDistanceMeters(radialActorId, candidate, out candidateDistanceMeters); lock (_radialLock) { flag = _radialActions.Any((PinkRadialAction a) => RadialActionMatchesTargetLocked(a, candidate, hasCandidateDistance, candidateDistanceMeters)); } } } string text4 = "actor=" + radialActorId + " [norm=" + text + "] | selected: " + RuntimeValueDescription(selected) + " | targeted: " + RuntimeValueDescription(targeted) + " | target=" + ((candidate.Length == 0) ? "<none>" : candidate) + " | range=" + (hasCandidateDistance ? (candidateDistanceMeters.ToString("0", CultureInfo.InvariantCulture) + "m") : "<unavailable>"); bool flag4 = false; lock (_radialLock) { _radialVisibilityChecks++; flag4 = _radialVisibilityChecks <= 12 || text4 != _lastRadialVisibility || flag != _lastRadialVisibilityResult; _lastRadialVisibility = text4; _lastRadialVisibilityResult = flag; } if (flag4) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("PINK radial visibility => " + flag + " | " + text4)); } return flag; } private static string RadialGroupKey(string group) { return (group ?? "").Trim().ToLowerInvariant(); } private static string RadialGroupGuid(string groupKey) { uint num = 2166136261u; string text = groupKey ?? ""; foreach (char c in text) { num ^= (byte)(c & 0xFF); num *= 16777619; num ^= (byte)(((int)c >> 8) & 0xFF); num *= 16777619; } return "cyberpunk.pink.radial.root.1_1_7.group." + num.ToString("x8"); } private PinkRadialAction RadialDirectActionAtLocked(int slot) { if (slot < 0) { return null; } int num = 0; foreach (PinkRadialAction radialAction in _radialActions) { if (radialAction != null && string.IsNullOrWhiteSpace(radialAction.Group) && num++ == slot) { return radialAction; } } return null; } private PinkRadialAction RadialGroupActionAtLocked(string groupKey, int slot) { if (slot < 0) { return null; } groupKey = RadialGroupKey(groupKey); int num = 0; foreach (PinkRadialAction radialAction in _radialActions) { if (radialAction != null && !(RadialGroupKey(radialAction.Group) != groupKey) && num++ == slot) { return radialAction; } } return null; } private bool RadialSubmenuSlotVisible(int slot) { string targetId = CurrentRadialTargetId(); string radialActorId; lock (_radialLock) { radialActorId = _radialActorId; } double meters; bool hasDistance = TryRadialDistanceMeters(radialActorId, targetId, out meters); lock (_radialLock) { PinkRadialAction pinkRadialAction = RadialDirectActionAtLocked(slot); return pinkRadialAction != null && RadialActionMatchesTargetLocked(pinkRadialAction, targetId, hasDistance, meters); } } private bool RadialGroupVisible(string groupKey) { string target = CurrentRadialTargetId(); string radialActorId; lock (_radialLock) { radialActorId = _radialActorId; } double distanceMeters; bool hasDistance = TryRadialDistanceMeters(radialActorId, target, out distanceMeters); groupKey = RadialGroupKey(groupKey); lock (_radialLock) { return _radialActions.Any((PinkRadialAction action) => action != null && RadialGroupKey(action.Group) == groupKey && RadialActionMatchesTargetLocked(action, target, hasDistance, distanceMeters)); } } private bool RadialGroupSlotVisible(string groupKey, int slot) { string targetId = CurrentRadialTargetId(); string radialActorId; lock (_radialLock) { radialActorId = _radialActorId; } double meters; bool hasDistance = TryRadialDistanceMeters(radialActorId, targetId, out meters); lock (_radialLock) { PinkRadialAction pinkRadialAction = RadialGroupActionAtLocked(groupKey, slot); return pinkRadialAction != null && RadialActionMatchesTargetLocked(pinkRadialAction, targetId, hasDistance, meters); } } private static void SetRuntimeMember(object obj, string name, object value) { if (obj == null) { throw new ArgumentNullException("obj"); } Type type = obj.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { property.SetValue(obj, value, null); return; } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj, value); return; } throw new MissingMemberException(type.FullName, name); } private static Type RuntimeMemberType(Type t, string name) { PropertyInfo property = t.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.PropertyType; } FieldInfo field = t.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.FieldType; } throw new MissingMemberException(t.FullName, name); } private Delegate MakeRadialActionDelegate(Type delegateType, string key, string label) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); ParameterExpression[] parameters2 = parameters.Select((ParameterInfo x, int i) => Expression.Parameter(x.ParameterType, "arg" + i)).ToArray(); MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("EmitRadialAction", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Constant(key ?? ""), Expression.Constant(label ?? "")); return Expression.Lambda(delegateType, body, parameters2).Compile(); } private Delegate MakeRadialSlotActionDelegate(Type delegateType, int slot) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); ParameterExpression[] parameters2 = parameters.Select((ParameterInfo x, int i) => Expression.Parameter(x.ParameterType, "arg" + i)).ToArray(); MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("EmitRadialActionSlot", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Constant(slot)); return Expression.Lambda(delegateType, body, parameters2).Compile(); } private Delegate MakeRadialGroupSlotActionDelegate(Type delegateType, string groupKey, int slot) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); ParameterExpression[] parameters2 = parameters.Select((ParameterInfo x, int i) => Expression.Parameter(x.ParameterType, "arg" + i)).ToArray(); MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("EmitRadialGroupSlot", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Constant(groupKey ?? ""), Expression.Constant(slot)); return Expression.Lambda(delegateType, body, parameters2).Compile(); } private Delegate MakeAlwaysFalseDelegate(Type delegateType) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); if (method.ReturnType != typeof(bool)) { throw new InvalidOperationException("RadialUI checker delegate does not return bool."); } ParameterExpression[] parameters2 = parameters.Select((ParameterInfo x, int i) => Expression.Parameter(x.ParameterType, "arg" + i)).ToArray(); return Expression.Lambda(delegateType, Expression.Constant(false), parameters2).Compile(); } private Delegate MakeRadialVisibleDelegate(Type delegateType) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 2 || method.ReturnType != typeof(bool)) { throw new InvalidOperationException("RadialUI visibility delegate has an unexpected signature."); } ParameterExpression parameterExpression = Expression.Parameter(parameters[0].ParameterType, "selected"); ParameterExpression parameterExpression2 = Expression.Parameter(parameters[1].ParameterType, "targeted"); MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("RadialVisibleObjects", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Convert(parameterExpression, typeof(object)), Expression.Convert(parameterExpression2, typeof(object))); return Expression.Lambda(delegateType, body, parameterExpression, parameterExpression2).Compile(); } private Delegate MakeRadialSlotVisibleDelegate(Type delegateType, int slot) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 0 || method.ReturnType != typeof(bool)) { throw new InvalidOperationException("RadialUI submenu checker has an unexpected signature."); } MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("RadialSubmenuSlotVisible", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Constant(slot)); return Expression.Lambda(delegateType, body).Compile(); } private Delegate MakeRadialGroupVisibleDelegate(Type delegateType, string groupKey) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 0 || method.ReturnType != typeof(bool)) { throw new InvalidOperationException("RadialUI submenu checker has an unexpected signature."); } MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("RadialGroupVisible", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Constant(groupKey ?? "")); return Expression.Lambda(delegateType, body).Compile(); } private Delegate MakeRadialGroupSlotVisibleDelegate(Type delegateType, string groupKey, int slot) { MethodInfo method = delegateType.GetMethod("Invoke"); ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length != 0 || method.ReturnType != typeof(bool)) { throw new InvalidOperationException("RadialUI submenu checker has an unexpected signature."); } MethodInfo method2 = typeof(CreatureBridgePlugin).GetMethod("RadialGroupSlotVisible", BindingFlags.Instance | BindingFlags.NonPublic); Expression body = Expression.Call(Expression.Constant(this), method2, Expression.Constant(groupKey ?? ""), Expression.Constant(slot)); return Expression.Lambda(delegateType, body).Compile(); } private Sprite PinkRadialSprite() { //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_pinkRadialSprite != (Object)null) { return _pinkRadialSprite; } string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ""; string text = Path.Combine(path, "CyberpunkPINKRadial.png"); Texture2D val = new Texture2D(2, 2); bool flag = false; try { if (!File.Exists(text)) { throw new FileNotFoundException("PINK radial icon was not installed beside the bridge DLL.", text); } byte[] array = File.ReadAllBytes(text); Type type = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetType("UnityEngine.ImageConversion", throwOnError: false); } catch { type = null; } if (type != null) { break; } } if (type == null) { try { Assembly assembly2 = Assembly.Load("UnityEngine.ImageConversionModule"); type = assembly2.GetType("UnityEngine.ImageConversion", throwOnError: false); } catch { } } if (type == null) { throw new InvalidOperationException("Unity ImageConversion module is unavailable."); } MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "LoadImage") { return false; } ParameterInfo[] parameters2 = m.GetParameters(); return (parameters2.Length == 2 || parameters2.Length == 3) && parameters2[0].ParameterType.IsAssignableFrom(typeof(Texture2D)) && parameters2[1].ParameterType == typeof(byte[]); }); if (methodInfo == null) { throw new MissingMethodException("UnityEngine.ImageConversion.LoadImage"); } ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Invoke(null, (parameters.Length == 3) ? new object[3] { val, array, false } : new object[2] { val, array }) is int num && num == 0) { throw new InvalidOperationException("Unity refused to decode CyberpunkPINKRadial.png."); } ((Object)val).name = "Cyberpunk PINK Radial"; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not load CyberpunkPINKRadial.png; using fallback PINK tile: " + ex.Message)); try { val.SetPixel(0, 0, new Color(1f, 0f, 0.55f, 1f)); val.SetPixel(1, 0, new Color(0.35f, 0f, 0.2f, 1f)); val.SetPixel(0, 1, new Color(0.35f, 0f, 0.2f, 1f)); val.SetPixel(1, 1, new Color(1f, 0f, 0.55f, 1f)); val.Apply(); } catch { } } _pinkRadialTexture = val; _pinkRadialSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)_pinkRadialSprite).name = "Cyberpunk PINK"; return _pinkRadialSprite; } private MethodInfo RadialCreateSubmenuMethod(Type submenuType) { if (submenuType == null) { return null; } return submenuType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "CreateSubMenuItem") { return false; } ParameterInfo[] parameters = m.GetParameters(); return parameters.Length == 4 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType.Name.IndexOf("ItemArgs", StringComparison.OrdinalIgnoreCase) >= 0; }); } private void EnsurePinkRadialSubmenu(int slots) { Type type = RadialSubmenuType(); if (type == null) { throw new InvalidOperationException("RadialUIPlugin is loaded without RadialSubmenu. Update RadialUIPlugin in this r2modman profile."); } if (!_radialRootRegistered) { MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "EnsureMainMenuItem" && m.GetParameters().Length == 5); if (methodInfo == null) { throw new MissingMethodException("RadialUI.RadialSubmenu.EnsureMainMenuItem(mainGuid, type, title, icon, checker)"); } ParameterInfo[] parameters = methodInfo.GetParameters(); Type parameterType = parameters[1].ParameterType; object obj = Enum.Parse(parameterType, "character", ignoreCase: true); Delegate obj2 = MakeRadialVisibleDelegate(parameters[4].ParameterType); MethodInfo method = parameters[4].ParameterType.GetMethod("Invoke"); string text = parameters[4].ParameterType.FullName; if (method != null) { string text2 = text; text = text2 + "(" + string.Join(", ", (from x in method.GetParameters() select x.ParameterType.FullName).ToArray()) + ") -> " + method.ReturnType.FullName; } methodInfo.Invoke(null, new object[5] { "cyberpunk.pink.radial.root.1_1_7", obj, "PINK", PinkRadialSprite(), obj2 }); lock (_radialLock) { _radialRootRegistered = true; _radialCheckerSignature = text; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("PINK radial root registered; checker " + text)); } MethodInfo methodInfo2 = RadialCreateSubmenuMethod(type); if (methodInfo2 == null) { throw new MissingMethodException("RadialUI.RadialSubmenu.CreateSubMenuItem(mainGuid, ItemArgs, callback, checker)"); } ParameterInfo[] parameters2 = methodInfo2.GetParameters(); Type parameterType2 = parameters2[1].ParameterType; Type delegateType = RuntimeMemberType(parameterType2, "Action"); Type parameterType3 = parameters2[3].ParameterType; while (_radialSubmenuItems.Count < slots) { int count = _radialSubmenuItems.Count; object obj3 = Activator.CreateInstance(parameterType2); SetRuntimeMember(obj3, "Title", "PINK"); SetRuntimeMember(obj3, "Icon", PinkRadialSprite()); SetRuntimeMember(obj3, "Action", MakeRadialSlotActionDelegate(delegateType, count)); SetRuntimeMember(obj3, "CloseMenuOnActivate", true); Delegate obj4 = MakeRadialSlotVisibleDelegate(parameterType3, count); methodInfo2.Invoke(null, new object[4] { "cyberpunk.pink.radial.root.1_1_7", obj3, null, obj4 }); _radialSubmenuItems.Add(obj3); } } private void EnsurePinkRadialGroup(string groupLabel, int slots) { groupLabel = (groupLabel ?? "").Trim(); string text = RadialGroupKey(groupLabel); if (text.Length == 0) { return; } Type type = RadialSubmenuType(); if (type == null) { throw new InvalidOperationException("RadialUIPlugin is loaded without RadialSubmenu. Update RadialUIPlugin in this r2modman profile."); } string text2; List<object> list; if (!_radialGroupMenuItems.TryGetValue(text, out var value)) { MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "EnsureMainMenuItem" && m.GetParameters().Length == 5); if (methodInfo == null) { throw new MissingMethodException("RadialUI.RadialSubmenu.EnsureMainMenuItem(mainGuid, type, title, icon, checker)"); } ParameterInfo[] parameters = methodInfo.GetParameters(); Type parameterType = parameters[1].ParameterType; object obj = Enum.Parse(parameterType, "character", ignoreCase: true); text2 = RadialGroupGuid(text); Delegate obj2 = MakeAlwaysFalseDelegate(parameters[4].ParameterType); methodInfo.Invoke(null, new object[5] { text2, obj, groupLabel, PinkRadialSprite(), obj2 }); MethodInfo methodInfo2 = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "NewMainMenu" && m.GetParameters().Length == 3); if (methodInfo2 == null) { throw new MissingMethodException("RadialUI.RadialSubmenu.NewMainMenu(mainGuid, title, icon)"); } value = methodInfo2.Invoke(null, new object[3] { text2, groupLabel, PinkRadialSprite() }); SetRuntimeMember(value, "Title", groupLabel); SetRuntimeMember(value, "Icon", PinkRadialSprite()); SetRuntimeMember(value, "CloseMenuOnActivate", false); MethodInfo methodInfo3 = RadialCreateSubmenuMethod(type); if (methodInfo3 == null) { throw new MissingMethodException("RadialUI.RadialSubmenu.CreateSubMenuItem(mainGuid, ItemArgs, callback, checker)"); } Type parameterType2 = methodInfo3.GetParameters()[3].ParameterType; Delegate obj3 = MakeRadialGroupVisibleDelegate(parameterType2, text); methodInfo3.Invoke(null, new object[4] { "cyberpunk.pink.radial.root.1_1_7", value, null, obj3 }); _radialGroupMenuItems[text] = value; _radialGroupGuids[text] = text2; list = new List<object>(); _radialGroupSubmenuItems[text] = list; } else { text2 = _radialGroupGuids[text]; list = _radialGroupSubmenuItems[text]; SetRuntimeMember(value, "Title", groupLabel); } MethodInfo methodInfo4 = RadialCreateSubmenuMethod(type); if (methodInfo4 == null) { throw new MissingMethodException("RadialUI.RadialSubmenu.CreateSubMenuItem(mainGuid, ItemArgs, callback, checker)"); } ParameterInfo[] parameters2 = methodInfo4.GetParameters(); Type parameterType3 = parameters2[1].ParameterType; Type delegateType = RuntimeMemberType(parameterType3, "Action"); Type parameterType4 = parameters2[3].ParameterType; while (list.Count < slots) { int count = list.Count; object obj4 = Activator.CreateInstance(parameterType3); SetRuntimeMember(obj4, "Title", groupLabel); SetRuntimeMember(obj4, "Icon", PinkRadialSprite()); SetRuntimeMember(obj4, "Action", MakeRadialGroupSlotActionDelegate(delegateType, text, count)); SetRuntimeMember(obj4, "CloseMenuOnActivate", true); Delegate obj5 = MakeRadialGroupSlotVisibleDelegate(parameterType4, text, count); methodInfo4.Invoke(null, new object[4] { text2, obj4, null, obj5 }); list.Add(obj4); } } private void ClearRadialActions() { lock (_radialLock) { _radialActions.Clear(); _radialTargetKinds.Clear(); _radialActorId = ""; _liveCreatureGuidCache.Clear(); } } private void SetRadialActions(string actor, List<PinkRadialAction> specs, Dictionary<string, string> targetKinds) { specs = specs ?? new List<PinkRadialAction>(); targetKinds = targetKinds ?? new Dictionary<string, string>(); lock (_radialLock) { _radialActorId = actor ?? ""; _radialActions.Clear(); _radialActions.AddRange(specs); _radialTargetKinds.Clear(); foreach (KeyValuePair<string, string> targetKind in targetKinds) { string text = NormalizeGuidText(targetKind.Key); string text2 = (targetKind.Value ?? "").Trim().ToLowerInvariant(); if (text.Length > 0 && text2.Length > 0) { _radialTargetKinds[text] = text2; } } } if (string.IsNullOrWhiteSpace(actor) || specs.Count == 0) { return; } List<PinkRadialAction> list = specs.Where((PinkRadialAction x) => x != null && string.IsNullOrWhiteSpace(x.Group)).ToList(); List<IGrouping<string, PinkRadialAction>> list2 = (from x in specs where x != null && !string.IsNullOrWhiteSpace(x.Group) group x by RadialGroupKey(x.Group) into g where g.Key.Length > 0 select g).ToList(); EnsurePinkRadialSubmenu(list.Count); foreach (IGrouping<string, PinkRadialAction> item in list2) { PinkRadialAction pinkRadialAction = item.FirstOrDefault(); EnsurePinkRadialGroup((pinkRadialAction == null) ? item.Key : pinkRadialAction.Group, item.Count()); } WarmRadialReflectionCachesAsync(); for (int num = 0; num < _radialSubmenuItems.Count; num++) { string value = ((num < list.Count) ? list[num].Label : "PINK"); SetRuntimeMember(_radialSubmenuItems[num], "Title", value); } foreach (IGrouping<string, PinkRadialAction> item2 in list2) { PinkRadialAction pinkRadialAction2 = item2.FirstOrDefault(); string text3 = ((pinkRadialAction2 == null) ? item2.Key : pinkRadialAction2.Group); if (_radialGroupMenuItems.TryGetValue(item2.Key, out var value2)) { SetRuntimeMember(value2, "Title", text3); } if (_radialGroupSubmenuItems.TryGetValue(item2.Key, out var value3)) { List<PinkRadialAction> list3 = item2.ToList(); for (int num2 = 0; num2 < value3.Count; num2++) { SetRuntimeMember(value3[num2], "Title", (num2 < list3.Count) ? list3[num2].Label : text3); } } } ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK radial submenu: " + specs.Count + " action(s) for " + actor + " | " + string.Join(", ", specs.Select((PinkRadialAction x) => (string.IsNullOrWhiteSpace(x.Group) ? "" : ("[" + x.Group + "] ")) + x.Label + (x.HasMaxRange ? (" " + (x.MaxRangeInclusive ? "<= " : "< ") + x.MaxRangeMeters.ToString("0.###", CultureInfo.InvariantCulture) + "m") : "")).ToArray()))); } private void CacheLiveCreatureGuid(string id, object guid) { id = NormalizeGuidText(id); if (id.Length == 0 || guid == null) { return; } Type type = guid.GetType(); bool flag = type == typeof(Guid) || type.Name.IndexOf("CreatureGuid", StringComparison.OrdinalIgnoreCase) >= 0; if (!flag) { MethodInfo[] creatureTryGetAssetMethods; lock (_radialLock) { creatureTryGetAssetMethods = _creatureTryGetAssetMethods; } if (creatureTryGetAssetMethods != null) { flag = creatureTryGetAssetMethods.Any(delegate(MethodInfo m) { ParameterInfo[] parameters = m.GetParameters(); return parameters.Length > 0 && parameters[0].ParameterType.IsInstanceOfType(guid); }); } } if (!flag) { return; } lock (_radialLock) { _liveCreatureGuidCache[id] = guid; } } private object CachedLiveCreatureGuid(string id) { id = NormalizeGuidText(id); if (id.Length == 0) { return null; } lock (_radialLock) { object value; return _liveCreatureGuidCache.TryGetValue(id, out value) ? value : null; } } private void DiscoverRadialReflectionMethods() { lock (_radialLock) { if (_creatureTryGetAssetMethods != null && _creatureTryGetAssetMethods.Length > 0 && _allCreatureAssetViewMethods != null && _allCreatureAssetViewMethods.Length > 0) { return; } } MethodInfo[] source = MethodsNamed("TryGetAsset", "GetTempReadOnlyViewOfAllCreatureAssets").ToArray(); MethodInfo[] array = source.Where(delegate(MethodInfo m) { if (!string.Equals(m.Name, "TryGetAsset", StringComparison.OrdinalIgnoreCase)) { return false; } if (m.DeclaringType == null || m.DeclaringType.Name.IndexOf("CreaturePresenter", StringComparison.OrdinalIgnoreCase) < 0) { return false; } ParameterInfo[] parameters = m.GetParameters(); return parameters.Length == 2 && parameters[1].ParameterType.IsByRef; }).ToArray(); MethodInfo[] array2 = source.Where((MethodInfo m) => string.Equals(m.Name, "GetTempReadOnlyViewOfAllCreatureAssets", StringComparison.OrdinalIgnoreCase) && m.IsStatic && m.GetParameters().Length == 0).ToArray(); lock (_radialLock) { if (array.Length > 0) { _creatureTryGetAssetMethods = array; } if (array2.Length > 0) { _allCreatureAssetViewMethods = array2; } } } private MethodInfo[] CreatureTryGetAssetMethods() { MethodInfo[] creatureTryGetAssetMethods; lock (_radialLock) { creatureTryGetAssetMethods = _creatureTryGetAssetMethods; } if (creatureTryGetAssetMethods != null && creatureTryGetAssetMethods.Length > 0) { return creatureTryGetAssetMethods; } DiscoverRadialReflectionMethods(); lock (_radialLock) { return _creatureTryGetAssetMethods; } } private MethodInfo[] AllCreatureAssetViewMethods() { MethodInfo[] allCreatureAssetViewMethods; lock (_radialLock) { allCreatureAssetViewMethods = _allCreatureAssetViewMethods; } if (allCreatureAssetViewMethods != null && allCreatureAssetViewMethods.Length > 0) { return allCreatureAssetViewMethods; } DiscoverRadialReflectionMethods(); lock (_radialLock) { return _allCreatureAssetViewMethods; } } private void WarmRadialReflectionCachesAsync() { if (Interlocked.CompareExchange(ref _radialReflectionWarmState, 1, 0) != 0) { return; } ThreadPool.QueueUserWorkItem(delegate { try { DiscoverRadialReflectionMethods(); MethodInfo[] creatureTryGetAssetMethods; MethodInfo[] allCreatureAssetViewMethods; lock (_radialLock) { creatureTryGetAssetMethods = _creatureTryGetAssetMethods; allCreatureAssetViewMethods = _allCreatureAssetViewMethods; } ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK radial reflection warm-up: " + ((creatureTryGetAssetMethods != null) ? creatureTryGetAssetMethods.Length : 0) + " TryGetAsset, " + ((allCreatureAssetViewMethods != null) ? allCreatureAssetViewMethods.Length : 0) + " creature-view method(s).")); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK radial reflection warm-up deferred: " + ex.Message)); } finally { Interlocked.Exchange(ref _radialReflectionWarmState, 0); } }); } private static bool TryCoordinates(object value, out double x, out double y, out double z) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) x = (y = (z = 0.0)); if (value == null) { return false; } try { if (value is Vector3 val) { x = val.x; y = val.y; z = val.z; return true; } object obj = MemberValue(value, "x"); object obj2 = MemberValue(value, "y"); object obj3 = MemberValue(value, "z"); if (obj == null || obj2 == null || obj3 == null) { return false; } x = Convert.ToDouble(obj); y = Convert.ToDouble(obj2); z = Convert.ToDouble(obj3); return !double.IsNaN(x) && !double.IsNaN(y) && !double.IsNaN(z) && !double.IsInfinity(x) && !double.IsInfinity(y) && !double.IsInfinity(z); } catch { return false; } } private bool TryCreaturePosition(object liveGuid, string id, out double x, out double y, out double z) { x = (y = (z = 0.0)); MethodInfo[] array = CreatureTryGetAssetMethods(); foreach (MethodInfo methodInfo in array) { ParameterInfo[] parameters = methodInfo.GetParameters(); try { Type parameterType = parameters[0].ParameterType; object obj = liveGuid; if (obj == null || !parameterType.IsInstanceOfType(obj)) { obj = ConstructGuidLike(parameterType, id, 0); } object obj2 = (methodInfo.IsStatic ? null : ResolveInstance(methodInfo.DeclaringType)); if (!methodInfo.IsStatic && obj2 == null) { continue; } object[] array2 = new object[2] { obj, null }; object obj3 = methodInfo.Invoke(obj2, array2); if (obj3 is bool && !(bool)obj3) { continue; } object obj4 = array2[1]; if (obj4 == null) { continue; } object obj5 = MemberValue(obj4, "LastPlacedPosition"); if (obj5 == null) { obj5 = MemberValue(obj4, "Position"); } if (obj5 == null) { object obj6 = MemberValue(obj4, "transform"); if (obj6 != null) { obj5 = MemberValue(obj6, "position"); } } if (!TryCoordinates(obj5, out x, out y, out z)) { continue; } return true; } catch { } } return false; } private bool TryCreatureDistance(object actorGuid, string actorId, object targetGuid, string targetId, out double distance) { distance = 0.0; if (!TryCreaturePosition(actorGuid, actorId, out var x, out var y, out var z)) { return false; } if (!TryCreaturePosition(targetGuid, targetId, out var x2, out var y2, out var z2)) { return false; } double num = x - x2; double num2 = y - y2; double num3 = z - z2; distance = Math.Sqrt(num * num + num2 * num2 + num3 * num3); if (!double.IsNaN(distance)) { return !double.IsInfinity(distance); } return false; } private bool TryRadialDistanceMeters(string actorId, string targetId, out double meters) { meters = 0.0; actorId = NormalizeGuidText(actorId); targetId = NormalizeGuidText(targetId); if (actorId.Length == 0 || targetId.Length == 0) { return false; } string detail; object obj = ResolveLiveCreatureGuid(actorId, out detail); string detail2; object obj2 = ResolveLiveCreatureGuid(targetId, out detail2); if (obj == null || obj2 == null) { return false; } if (!TryCreatureDistance(obj, actorId, obj2, targetId, out var distance)) { return false; } double num = distance * 2.0; meters = Math.Floor(num + 0.5); if (!double.IsNaN(meters)) { return !double.IsInfinity(meters); } return false; } private void EmitRadialActionSlot(int slot) { PinkRadialAction pinkRadialAction = null; string targetId = CurrentRadialTargetId(); string radialActorId; lock (_radialLock) { radialActorId = _radialActorId; } double meters; bool hasDistance = TryRadialDistanceMeters(radialActorId, targetId, out meters); lock (_radialLock) { PinkRadialAction pinkRadialAction2 = RadialDirectActionAtLocked(slot); if (pinkRadialAction2 != null && RadialActionMatchesTargetLocked(pinkRadialAction2, targetId, hasDistance, meters)) { pinkRadialAction = pinkRadialAction2; } } if (pinkRadialAction != null) { EmitRadialAction(pinkRadialAction.Key, pinkRadialAction.Label); } } private void EmitRadialGroupSlot(string groupKey, int slot) { PinkRadialAction pinkRadialAction = null; string targetId = CurrentRadialTargetId(); string radialActorId; lock (_radialLock) { radialActorId = _radialActorId; } double meters; bool hasDistance = TryRadialDistanceMeters(radialActorId, targetId, out meters); lock (_radialLock) { PinkRadialAction pinkRadialAction2 = RadialGroupActionAtLocked(groupKey, slot); if (pinkRadialAction2 != null && RadialActionMatchesTargetLocked(pinkRadialAction2, targetId, hasDistance, meters)) { pinkRadialAction = pinkRadialAction2; } } if (pinkRadialAction != null) { EmitRadialAction(pinkRadialAction.Key, pinkRadialAction.Label); } } private void EmitRadialAction(string action, string label) { try { MethodInfo methodInfo = RadialUiMethod("GetLastRadialTargetCreature", 0); if (methodInfo == null) { return; } object obj = methodInfo.Invoke(null, null); if (obj == null) { return; } string radialActorId; lock (_radialLock) { radialActorId = _radialActorId; } if (string.IsNullOrWhiteSpace(radialActorId)) { return; } string text = obj.ToString(); string detail; object actorGuid = ResolveLiveCreatureGuid(radialActorId, out detail); bool flag = false; double distance = 0.0; flag = TryCreatureDistance(actorGuid, radialActorId, obj, text, out distance); HttpListenerContext httpListenerContext = null; PinkRadialEvent pinkRadialEvent2; lock (_radialLock) { PinkRadialEvent pinkRadialEvent = new PinkRadialEvent(); pinkRadialEvent.Seq = ++_radialSeq; pinkRadialEvent.Action = action ?? ""; pinkRadialEvent.Label = label ?? ""; pinkRadialEvent.Actor = radialActorId; pinkRadialEvent.Target = text; pinkRadialEvent.TargetCreature = text; pinkRadialEvent.HasDistance = flag; pinkRadialEvent.DistanceTiles = distance; pinkRadialEvent2 = (_lastRadialEvent = pinkRadialEvent); if (_radialWaitContext != null && pinkRadialEvent2.Seq > _radialWaitAfter) { httpListenerContext = _radialWaitContext; _radialWaitContext = null; } } if (httpListenerContext != null) { SendRadialEvent(httpListenerContext, pinkRadialEvent2); } ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK radial: " + action + " -> " + text + (flag ? (" @ " + distance.ToString("0.###")) : ""))); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("PINK radial action failed: " + ex)); } } private Type WaypointMovementType() { if (_waypointMovementTypeCache != null) { return _waypointMovementTypeCache; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = null; try { type = assembly.GetType("CyberpunkPink.WaypointMovement.WaypointMovementPlugin", throwOnError: false); } catch { } if (type != null) { _waypointMovementTypeCache = type; return type; } } return null; } private MethodInfo WaypointMovementMethod(ref MethodInfo cache, string name, int parameterCount) { if (cache != null) { return cache; } Type type = WaypointMovementType(); if (type == null) { return null; } try { cache = type.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault((MethodInfo m) => m.Name == name && m.GetParameters().Length == parameterCount); } catch { cache = null; } return cache; } private bool WaypointMovementAvailable() { if (WaypointMovementMethod(ref _waypointSetMovementStateCache, "SetPinkMovementState", 5) != null) { return WaypointMovementMethod(ref _waypointPopMovementEventCache, "PopPinkMovementEvent", 0) != null; } return false; } private bool SetWaypointMovementState(string creature, string turnId, float used, float allowance) { try { MethodInfo methodInfo = WaypointMovementMethod(ref _waypointSetMovementStateCache, "SetPinkMovementState", 5); if (methodInfo == null) { return false; } object obj = methodInfo.Invoke(null, new object[5] { creature, turnId, used, allowance, 2f }); return !(obj is bool) || (bool)obj; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not publish PINK movement state to Waypoint Movement: " + ex.Message)); return false; } } private void ClearWaypointMovementState(string creature) { try { MethodInfo methodInfo = WaypointMovementMethod(ref _waypointClearMovementStateCache, "ClearPinkMovementState", 1); if (methodInfo != null) { methodInfo.Invoke(null, new object[1] { creature }); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not clear PINK movement state from Waypoint Movement: " + ex.Message)); } } private void PollWaypointMovementEvents() { MethodInfo methodInfo = WaypointMovementMethod(ref _waypointPopMovementEventCache, "PopPinkMovementEvent", 0); if (methodInfo == null) { return; } for (int i = 0; i < 8; i++) { string text = null; try { text = methodInfo.Invoke(null, null) as string; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Waypoint movement event read failed: " + ex.Message)); break; } if (string.IsNullOrEmpty(text)) { break; } string[] array = text.Split(new char[1] { '\t' }); if (array.Length >= 3) { string text2 = NormalizeGuidText(array[0]); string text3 = array[1] ?? ""; if (text2.Length != 0 && text3.Length != 0 && double.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !double.IsNaN(result) && !double.IsInfinity(result) && !(result <= 0.0)) { EmitMovementEvent(text2, text3, result); } } } } private void EmitMovementEvent(string creature, string turnId, double worldDistance) { HttpListenerContext httpListenerContext = null; PinkMovementEvent pinkMovementEvent2; lock (_movementLock) { PinkMovementEvent pinkMovementEvent = new PinkMovementEvent(); pinkMovementEvent.Seq = ++_movementSeq; pinkMovementEvent.Creature = creature; pinkMovementEvent.TurnId = turnId; pinkMovementEvent.DistanceWorld = worldDistance; pinkMovementEvent.DistanceMeters = worldDistance * 2.0; pinkMovementEvent2 = pinkMovementEvent; _movementEvents.Add(pinkMovementEvent2); while (_movementEvents.Count > 64) { _movementEvents.RemoveAt(0); } if (_movementWaitContext != null && pinkMovementEvent2.Seq > _movementWaitAfter) { httpListenerContext = _movementWaitContext; _movementWaitContext = null; } } if (httpListenerContext != null) { SendMovementEvent(httpListenerContext, pinkMovementEvent2); } ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK movement: " + creature + " +" + pinkMovementEvent2.DistanceMeters.ToString("0.###", CultureInfo.InvariantCulture) + "m turn=" + turnId)); } private void CheckMovementWaiterTimeout() { HttpListenerContext httpListenerContext = null; long num = 0L; lock (_movementLock) { if (_movementWaitContext != null && DateTime.UtcNow > _movementWaitDeadlineUtc) { httpListenerContext = _movementWaitContext; _movementWaitContext = null; num = _movementSeq; } } if (httpListenerContext != null) { Send(httpListenerContext, 200, "{\"ok\":true,\"idle\":true,\"seq\":" + num + ",\"session\":" + Json(_movementSession) + "}"); } } private void SendMovementEvent(HttpListenerContext ctx, PinkMovementEvent ev) { string s = _movementSession + ":" + ev.Seq; string text = "{\"ok\":true,\"seq\":" + ev.Seq + ",\"session\":" + Json(_movementSession) + ",\"event\":{\"id\":" + Json(s) + ",\"session\":" + Json(_movementSession) + ",\"creature\":" + Json(ev.Creature) + ",\"turn\":" + Json(ev.TurnId) + ",\"distanceWorld\":" + ev.DistanceWorld.ToString(CultureInfo.InvariantCulture) + ",\"meters\":" + ev.DistanceMeters.ToString(CultureInfo.InvariantCulture) + "}}"; Send(ctx, 200, text); } private void CheckRadialWaiterTimeout() { HttpListenerContext httpListenerContext = null; long num = 0L; lock (_radialLock) { if (_radialWaitContext != null && DateTime.UtcNow > _radialWaitDeadlineUtc) { httpListenerContext = _radialWaitContext; _radialWaitContext = null; num = _radialSeq; } } if (httpListenerContext != null) { Send(httpListenerContext, 200, "{\"ok\":true,\"idle\":true,\"seq\":" + num + "}"); } } private static void SendRadialEvent(HttpListenerContext ctx, PinkRadialEvent ev) { string text = "{\"ok\":true,\"seq\":" + ev.Seq + ",\"event\":{\"action\":" + Json(ev.Action) + ",\"label\":" + Json(ev.Label) + ",\"actor\":" + Json(ev.Actor) + ",\"target\":" + Json(ev.Target) + ",\"targetCreature\":" + Json(ev.TargetCreature) + ",\"hasDistance\":" + (ev.HasDistance ? "true" : "false") + ",\"distanceTiles\":" + ev.DistanceTiles.ToString(CultureInfo.InvariantCulture) + "}}"; Send(ctx, 200, text); } private void OnMainThread(Action work) { _mainThread.Enqueue(work); } private static void AddCors(HttpListenerResponse r) { r.Headers["Access-Control-Allow-Origin"] = "*"; r.Headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"; r.Headers["Access-Control-Allow-Headers"] = "Content-Type, Accept, Origin, X-Requested-With"; r.Headers["Access-Control-Allow-Private-Network"] = "true"; r.Headers["Access-Control-Max-Age"] = "600"; r.Headers["Cross-Origin-Resource-Policy"] = "cross-origin"; r.Headers["Cache-Control"] = "no-store"; } private static void Send(HttpListenerContext ctx, int code, string text) { try { byte[] bytes = Encoding.UTF8.GetBytes(text ?? ""); ctx.Response.StatusCode = code; ctx.Response.ContentType = "application/json; charset=utf-8"; ctx.Response.ContentLength64 = bytes.Length; ctx.Response.OutputStream.Write(bytes, 0, bytes.Length); ctx.Response.OutputStream.Close(); } catch { try { ctx.Response.Close(); } catch { } } } private static string Json(string s) { if (s == null) { return "null"; } StringBuilder stringBuilder = new StringBuilder("\""); foreach (char c in s) { switch (c) { case '\\': stringBuilder.Append("\\\\"); continue; case '"': stringBuilder.Append("\\\""); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { int num = c; stringBuilder.Append("\\u" + num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.Append("\"").ToString(); } private static IEnumerable<MethodInfo> MethodsNamed(params string[] names) { HashSet<string> wanted = new HashSet<string>(names, StringComparer.OrdinalIgnoreCase); try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly asm in assemblies) { Type[] types; try { types = asm.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types.Where((Type type) => type != null).ToArray(); } catch { continue; } try { Type[] array = types; foreach (Type t in array) { MethodInfo[] ms; try { ms = t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } try { MethodInfo[] array2 = ms; foreach (MethodInfo m in array2) { if (wanted.Contains(m.Name)) { yield return m; } } } finally { } } } finally { } } } finally { } } private static string Sig(MethodInfo m) { return m.DeclaringType.FullName + "." + m.Name + "(" + string.Join(", ", (from p in m.GetParameters() select (p.IsOut ? "out " : "") + p.ParameterType.Name + " " + p.Name).ToArray()) + ") -> " + m.ReturnType.Name; } private static string DescribeNativeMethods() { string[] array = MethodsNamed("TryGetCreatureBlueprint", "GetCreatureBlueprintV2", "GetCreatureBlueprintV1", "TryProcessCreatureBlueprintString", "ProcessCreatureBlueprint", "TryGetPickedPosition", "GetLastCursorWorldPosition", "TryGetPicked").Select(Sig).Distinct().Take(30) .ToArray(); if (array.Length != 0) { return string.Join(" | ", array); } return "none found"; } private object ResolveInstance(Type t) { string[] array = new string[4] { "Instance", "instance", "Singleton", "singleton" }; foreach (string name in array) { try { PropertyInfo property = t.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value = property.GetValue(null, null); if (value != null) { return value; } } FieldInfo field = t.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value2 = field.GetValue(null); if (value2 != null) { return value2; } } } catch { } } try { if (typeof(Object).IsAssignableFrom(t)) { return Object.FindObjectOfType(t); } } catch { } return null; } private static object MemberValue(object target, string name) { if (target == null) { return null; } Type type = target.GetType(); try { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(target, null); } } catch { } try { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } } catch { } return null; } private static bool SameCreatureId(object creatureGuid, string wanted) { if (creatureGuid == null || string.IsNullOrWhiteSpace(wanted)) { return false; } string text; try { text = creatureGuid.ToString(); } catch { return false; } if (string.Equals(text, wanted, StringComparison.OrdinalIgnoreCase)) { return true; } if (Guid.TryParse(text, out var result) && Guid.TryParse(wanted, out var result2)) { return result == result2; } string a = text.Replace("-", "").Replace("{", "").Replace("}", ""); string b = wanted.Replace("-", "").Replace("{", "").Replace("}", ""); return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } private object ResolveLiveCreatureGuid(string id, out string detail) { object obj = CachedLiveCreatureGuid(id); if (obj != null) { detail = "cached live CreatureId"; return obj; } List<string> list = new List<string>(); MethodInfo[] array = AllCreatureAssetViewMethods(); foreach (MethodInfo methodInfo in array) { if (!methodInfo.IsStatic || methodInfo.GetParameters().Length != 0) { continue; } try { object obj2 = methodInfo.Invoke(null, new object[0]); if (!(obj2 is IEnumerable enumerable)) { list.Add(Sig(methodInfo) + " did not return IEnumerable"); continue; } int num = 0; foreach (object item in enumerable) { num++; object obj3 = MemberValue(item, "CreatureId"); if (SameCreatureId(obj3, id)) { detail = "Matched live CreatureId after " + num + " asset(s) via " + Sig(methodInfo); CacheLiveCreatureGuid(id, obj3); return obj3; } } list.Add(Sig(methodInfo) + " searched " + num + " asset(s), no id match"); } catch (TargetInvocationException ex) { list.Add(Sig(methodInfo) + ": " + (ex.InnerException ?? ex).Message); } catch (Exception ex2) { list.Add(Sig(methodInfo) + ": " + ex2.Message); } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array2; try { array2 = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex3) { array2 = ex3.Types.Where((Type x) => x != null).ToArray(); } catch { continue; } Type[] array3 = array2; foreach (Type type in array3) { if (!string.Equals(type.Name, "LocalClient", StringComparison.Ordinal)) { continue; } try { object obj5 = null; PropertyInfo property = type.GetProperty("SelectedCreatureId", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { obj5 = property.GetValue(null, null); } if (obj5 == null) { FieldInfo field = type.GetField("SelectedCreatureId", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { obj5 = field.GetValue(null); } } if (SameCreatureId(obj5, id)) { detail = "Matched LocalClient.SelectedCreatureId"; return obj5; } } catch (Exception ex4) { list.Add(type.FullName + ".SelectedCreatureId: " + ex4.Message); } } } detail = ((list.Count == 0) ? "TaleSpire exposed no live CreatureGuid source." : string.Join(" || ", list.Take(12).ToArray())); return null; } private static object ConstructGuidLike(Type t, string id, int depth) { if (t == null) { throw new ArgumentNullException("t"); } if (depth > 4) { throw new InvalidOperationException("Guid-wrapper recursion exceeded for " + t.FullName + "."); } Guid result; bool flag = Guid.TryParse(id, out result); MethodInfo[] methods = t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); try { if ((string.Equals(methodInfo.Name, "Parse", StringComparison.OrdinalIgnoreCase) || string.Equals(methodInfo.Name, "FromString", StringComparison.OrdinalIgnoreCase)) && parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { return methodInfo.Invoke(null, new object[1] { id }); } if ((string.Equals(methodInfo.Name, "op_Implicit", StringComparison.Ordinal) || string.Equals(methodInfo.Name, "op_Explicit", StringComparison.Ordinal)) && parameters.Length == 1) { if (parameters[0].ParameterType == typeof(string)) { return methodInfo.Invoke(null, new object[1] { id }); } if (flag && parameters[0].ParameterType == typeof(Guid)) { return methodInfo.Invoke(null, new object[1] { result }); } if (parameters[0].ParameterType != t && parameters[0].ParameterType.Name.IndexOf("Guid", StringComparison.OrdinalIgnoreCase) >= 0) { object obj = ConstructGuidLike(parameters[0].ParameterType, id, depth + 1); return methodInfo.Invoke(null, new object[1] { obj }); } } } catch { } } ConstructorInfo[] constructors = t.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (ConstructorInfo constructorInfo in constructors) { ParameterInfo[] parameters2 = constructorInfo.GetParameters(); if (parameters2.Length != 1) { continue; } try { if (parameters2[0].ParameterType == typeof(string)) { return constructorInfo.Invoke(new object[1] { id }); } if (flag && parameters2[0].ParameterType == typeof(Guid)) { return constructorInfo.Invoke(new object[1] { result }); } Type parameterType = parameters2[0].ParameterType; if (parameterType != t && parameterType.Name.IndexOf("Guid", StringComparison.OrdinalIgnoreCase) >= 0) { object obj3 = ConstructGuidLike(parameterType, id, depth + 1); return constructorInfo.Invoke(new object[1] { obj3 }); } } catch { } } object obj5 = Activator.CreateInstance(t); FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { try { if (flag && fieldInfo.FieldType == typeof(Guid)) { fieldInfo.SetValue(obj5, result); return obj5; } if (fieldInfo.FieldType != t && fieldInfo.FieldType.Name.IndexOf("Guid", StringComparison.OrdinalIgnoreCase) >= 0) { fieldInfo.SetValue(obj5, ConstructGuidLike(fieldInfo.FieldType, id, depth + 1)); return obj5; } } catch { } } PropertyInfo[] properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (!propertyInfo.CanWrite) { continue; } try { if (flag && propertyInfo.PropertyType == typeof(Guid)) { propertyInfo.SetValue(obj5, result, null); return obj5; } if (propertyInfo.PropertyType != t && propertyInfo.PropertyType.Name.IndexOf("Guid", StringComparison.OrdinalIgnoreCase) >= 0) { propertyInfo.SetValue(obj5, ConstructGuidLike(propertyInfo.PropertyType, id, depth + 1), null); return obj5; } } catch { } } throw new InvalidOperationException("Could not construct " + t.FullName + " from creature id. Constructors: " + string.Join(", ", t.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Select(SigCtor).ToArray())); } private static string SigCtor(ConstructorInfo c) { try { return c.DeclaringType.FullName + "(" + string.Join(", ", (from x in c.GetParameters() select x.ParameterType.FullName).ToArray()) + ")"; } catch { return c.ToString(); } } private static object MakeCreatureGuid(Type t, string id) { return ConstructGuidLike(t, id, 0); } private static bool TryBuildArgs(MethodInfo m, string creatureId, string blueprint, object liveCreatureGuid, out object[] args, out string why) { ParameterInfo[] parameters = m.GetParameters(); args = new object[parameters.Length]; why = ""; for (int i = 0; i < parameters.Length; i++) { ParameterInfo parameterInfo = parameters[i]; Type parameterType = parameterInfo.ParameterType; Type type = (parameterType.IsByRef ? parameterType.GetElementType() : parameterType); string text = (parameterInfo.Name ?? "").ToLowerInvariant(); if (parameterType.IsByRef || parameterInfo.IsOut) { args[i] = (type.IsValueType ? Activator.CreateInstance(type) : null); } else if (type == typeof(string)) { if (!string.IsNullOrEmpty(blueprint) && (text.Contains("blueprint") || text.Contains("url") || text.Contains("str"))) { args[i] = blueprint; continue; } if (!string.IsNullOrEmpty(creatureId)) { args[i] = creatureId; continue; } if (string.IsNullOrEmpty(blueprint)) { why = "unmapped string " + parameterInfo.Name; return false; } args[i] = blueprint; } else if (type == typeof(string[])) { if (string.IsNullOrEmpty(blueprint)) { why = "string[] needs blueprint input"; return false; } args[i] = new string[1] { blueprint }; } else if (type == typeof(Guid)) { if (string.IsNullOrEmpty(creatureId)) { why = "Guid needs creature id"; return false; } args[i] = Guid.Parse(creatureId); } else if (type.Name.IndexOf("CreatureGuid", StringComparison.OrdinalIgnoreCase) >= 0) { if (liveCreatureGuid != null && type.IsInstanceOfType(liveCreatureGuid)) { args[i] = liveCreatureGuid; continue; } if (string.IsNullOrEmpty(creatureId)) { why = "CreatureGuid needs creature id"; return false; } args[i] = MakeCreatureGuid(type, creatureId); } else if (type == typeof(bool)) { args[i] = false; } else if (type.IsEnum) { args[i] = Activator.CreateInstance(type); } else if (parameterInfo.HasDefaultValue) { args[i] = parameterInfo.DefaultValue; } else { if (type.IsValueType) { why = "unmapped " + type.FullName + " " + parameterInfo.Name; return false; } args[i] = null; } } return true; } private static string FindBlueprint(object ret, object[] args) { List<string> list = new List<string>(); if (ret is string) { list.Add((string)ret); } foreach (object obj in args) { if (obj is string) { list.Add((string)obj); } } foreach (string item in list) { if (!string.IsNullOrEmpty(item) && item.StartsWith("talespire://creature-blueprint/", StringComparison.OrdinalIgnoreCase)) { return item; } } if (ret != null) { Type type = ret.GetType(); PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType != typeof(string)) { continue; } try { string text = propertyInfo.GetValue(ret, null) as string; if (!string.IsNullOrEmpty(text) && text.StartsWith("talespire://creature-blueprint/", StringComparison.OrdinalIgnoreCase)) { return text; } } catch { } } } return null; } private string CaptureBlueprint(string creatureId, out string detail) { List<MethodInfo> list = (from m in MethodsNamed("TryGetCreatureBlueprint", "GetCreatureBlueprintV2", "GetCreatureBlueprintV1", "GetCreatureBlueprint") orderby (!(m.Name == "TryGetCreatureBlueprint")) ? 1 : 0 select m).ToList(); List<string> list2 = new List<string>(); string detail2; object obj = ResolveLiveCreatureGuid(creatureId, out detail2); if (obj == null) { list2.Add("Live CreatureGuid lookup failed: " + detail2 + " Falling back to reconstructing the TaleSpire CreatureGuid from the saved id."); } else { Logx.LogInfo((object)("Resolved creature " + creatureId + ": " + detail2)); } foreach (MethodInfo item in list) { try { if (!TryBuildArgs(item, creatureId, null, obj, out var args, out var why)) { list2.Add(Sig(item) + " skipped: " + why); continue; } object obj2 = (item.IsStatic ? null : ResolveInstance(item.DeclaringType)); if (!item.IsStatic && obj2 == null) { list2.Add(Sig(item) + " skipped: no instance"); continue; } object ret = item.Invoke(obj2, args); string text = FindBlueprint(ret, args); if (!string.IsNullOrEmpty(text)) { detail = Sig(item); Logx.LogInfo((object)("Captured creature " + creatureId + " via " + Sig(item))); return text; } list2.Add(Sig(item) + " returned no blueprint"); } catch (TargetInvocationException ex) { list2.Add(Sig(item) + ": " + (ex.InnerException ?? ex).Message); } catch (Exception ex2) { list2.Add(Sig(item) + ": " + ex2.Message); } } detail = "TaleSpire native capture did not produce a blueprint. Candidates: " + ((list.Count == 0) ? "none found" : string.Join(" || ", list2.Take(12).ToArray())); return null; } private static bool NativeReturnSucceeded(object ret, out string resultText) { if (ret == null) { resultText = "void/null return"; return true; } if (ret is bool flag) { resultText = (flag ? "true" : "false"); return flag; } Type type = ret.GetType(); if (type.IsEnum) { string text = Enum.GetName(type, ret) ?? ret.ToString(); long num = Convert.ToInt64(ret); resultText = type.Name + "." + text + " (" + num + ")"; return string.Equals(text, "Success", StringComparison.OrdinalIgnoreCase); } resultText = type.FullName + ": " + ret; return false; } private static string OutArgumentDetails(MethodInfo m, object[] args) { ParameterInfo[] parameters = m.GetParameters(); List<string> list = new List<string>(); for (int i = 0; i < parameters.Length && i < args.Length; i++) { if (!parameters[i].IsOut && !parameters[i].ParameterType.IsByRef) { continue; } object obj = args[i]; if (obj != null) { if (obj is Exception) { Exception ex = (Exception)obj; list.Add(parameters[i].Name + "=" + ex.GetType().Name + ": " + ex.Message); } else { list.Add(parameters[i].Name + "=" + obj); } } } if (list.Count != 0) { return " [" + string.Join("; ", list.ToArray()) + "]"; } return ""; } private bool SpawnBlueprint(string blueprint, out string detail) { List<MethodInfo> list = (from m in MethodsNamed("TryProcessCreatureBlueprintString", "ProcessCreatureBlueprint") orderby (!(m.Name == "TryProcessCreatureBlueprintString")) ? 1 : 0 select m).ToList(); List<string> list2 = new List<string>(); string text = blueprint ?? ""; if (text.StartsWith("talespire://creature-blueprint/", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("talespire://creature-blueprint/".Length); } if (string.IsNullOrWhiteSpace(text)) { detail = "Creature blueprint payload was empty after removing the talespire://creature-blueprint/ prefix."; return false; } foreach (MethodInfo item in list) { string[] array = (string.Equals(text, blueprint, StringComparison.Ordinal) ? new string[1] { text } : new string[2] { text, blueprint }); for (int num = 0; num < array.Length; num++) { string blueprint2 = array[num]; string text2 = ((num == 0) ? "payload-only" : "full-url"); try { if (!TryBuildArgs(item, null, blueprint2, null, out var args, out var why)) { list2.Add(Sig(item) + " [" + text2 + "] skipped: " + why); continue; } object obj = (item.IsStatic ? null : ResolveInstance(item.DeclaringType)); if (!item.IsStatic && obj == null) { list2.Add(Sig(item) + " [" + text2 + "] skipped: no instance"); continue; } object ret = item.Invoke(obj, args); string resultText; bool flag = NativeReturnSucceeded(ret, out resultText); string text3 = OutArgumentDetails(item, args); if (!flag) { list2.Add(Sig(item) + " [" + text2 + "] returned " + resultText + text3); continue; } detail = Sig(item) + " [" + text2 + "] returned " + resultText + text3; Logx.LogInfo((object)("Submitted creature blueprint via " + detail)); return true; } catch (TargetInvocationException ex) { list2.Add(Sig(item) + " [" + text2 + "]: " + (ex.InnerException ?? ex).Message); } catch (Exception ex2) { list2.Add(Sig(item) + " [" + text2 + "]: " + ex2.Message); } } } detail = "TaleSpire native placement rejected the creature blueprint. " + ((list
BepInEx/plugins/PINKNativeBridge.dll
Decompiled a week agousing System; using System.Collections.Concurrent; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using BepInEx; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] [BepInPlugin("org.cyberpunkpink.nativebridge", "Cyberpunk PINK Native Bridge", "1.0.0")] public sealed class PINKNativeBridge : BaseUnityPlugin { private sealed class FocusRequest { public string CreatureId; public float X; public float Y; public float Z; public readonly ManualResetEvent Done = new ManualResetEvent(initialState: false); public bool Ok; public string Error; } private const int Port = 31747; private readonly ConcurrentQueue<FocusRequest> _focusQueue = new ConcurrentQueue<FocusRequest>(); private TcpListener _listener; private Thread _serverThread; private volatile bool _stopping; private void Awake() { try { _listener = new TcpListener(IPAddress.Loopback, 31747); _listener.Start(); _serverThread = new Thread(ServerLoop); _serverThread.IsBackground = true; _serverThread.Name = "PINK Native Bridge"; _serverThread.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("PINK Native Bridge listening on 127.0.0.1:" + 31747)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("PINK Native Bridge could not start: " + ex)); } } private void OnDestroy() { _stopping = true; try { if (_listener != null) { _listener.Stop(); } } catch { } try { if (_serverThread != null && _serverThread.IsAlive) { _serverThread.Join(300); } } catch { } } private void Update() { FocusRequest result; while (_focusQueue.TryDequeue(out result)) { try { result.Ok = FocusNative(result.CreatureId, result.X, result.Y, result.Z, out result.Error); } catch (Exception ex) { result.Ok = false; result.Error = ex.GetType().Name + ": " + ex.Message; ((BaseUnityPlugin)this).Logger.LogError((object)("PINK camera focus failed: " + ex)); } finally { result.Done.Set(); } } } private void ServerLoop() { while (!_stopping) { try { if (_listener == null) { break; } if (!_listener.Pending()) { Thread.Sleep(15); continue; } using TcpClient tcpClient = _listener.AcceptTcpClient(); tcpClient.ReceiveTimeout = 3000; tcpClient.SendTimeout = 3000; HandleClient(tcpClient); } catch (SocketException) { if (!_stopping) { Thread.Sleep(100); } } catch (ObjectDisposedException) { break; } catch (Exception ex3) { if (!_stopping) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("PINK bridge request failed: " + ex3.Message)); } } } } private void HandleClient(TcpClient client) { NetworkStream stream = client.GetStream(); StreamReader streamReader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, 4096, leaveOpen: true); string text = streamReader.ReadLine(); if (string.IsNullOrEmpty(text)) { return; } string value; do { value = streamReader.ReadLine(); } while (!string.IsNullOrEmpty(value)); string[] array = text.Split(new char[1] { ' ' }); string text2 = ((array.Length > 0) ? array[0].ToUpperInvariant() : ""); string text3 = ((array.Length > 1) ? array[1] : "/"); if (text2 == "OPTIONS") { WriteResponse(stream, 204, "", "text/plain"); return; } Uri uri; try { uri = new Uri("http://127.0.0.1:" + 31747 + text3); } catch { WriteJson(stream, 400, ok: false, "Bad request URI."); return; } if (uri.AbsolutePath == "/health") { WriteJson(stream, 200, ok: true, null); return; } if (uri.AbsolutePath != "/focus") { WriteJson(stream, 404, ok: false, "Unknown endpoint."); return; } FocusRequest focusRequest = new FocusRequest(); focusRequest.CreatureId = Query(uri, "id"); if (!TryFloat(Query(uri, "x"), out var value2) || !TryFloat(Query(uri, "y"), out var value3) || !TryFloat(Query(uri, "z"), out var value4)) { WriteJson(stream, 400, ok: false, "focus requires numeric x, y and z."); return; } focusRequest.X = value2; focusRequest.Y = value3; focusRequest.Z = value4; _focusQueue.Enqueue(focusRequest); if (!focusRequest.Done.WaitOne(2500)) { WriteJson(stream, 504, ok: false, "TaleSpire did not service the camera request in time."); } else { WriteJson(stream, focusRequest.Ok ? 200 : 500, focusRequest.Ok, focusRequest.Error); } } private static string Query(Uri uri, string key) { string text = uri.Query; if (string.IsNullOrEmpty(text)) { return ""; } if (text[0] == '?') { text = text.Substring(1); } string[] array = text.Split(new char[1] { '&' }); for (int i = 0; i < array.Length; i++) { int num = array[i].IndexOf('='); string stringToUnescape = ((num >= 0) ? array[i].Substring(0, num) : array[i]); if (string.Equals(Uri.UnescapeDataString(stringToUnescape), key, StringComparison.OrdinalIgnoreCase)) { string text2 = ((num >= 0) ? array[i].Substring(num + 1) : ""); return Uri.UnescapeDataString(text2.Replace("+", " ")); } } return ""; } private static bool TryFloat(string text, out float value) { return float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value); } private bool FocusNative(string creatureId, float x, float y, float z, out string error) { //IL_028a: Unknown result type (might be due to invalid IL or missing references) error = null; Type type = FindType("CameraController"); if (type == null) { error = "Current TaleSpire runtime has no CameraController type."; return false; } if (!string.IsNullOrEmpty(creatureId)) { MethodInfo[] array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "TryLookAtCreature" || m.Name == "LookAtCreature" orderby m.GetParameters().Length select m).ToArray(); foreach (MethodInfo methodInfo in array) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length < 1 || !TryBuildId(parameters[0].ParameterType, creatureId, out var value)) { continue; } object[] array2 = new object[parameters.Length]; array2[0] = value; bool flag = true; for (int num2 = 1; num2 < parameters.Length; num2++) { if (parameters[num2].HasDefaultValue) { array2[num2] = parameters[num2].DefaultValue; continue; } if (parameters[num2].ParameterType == typeof(bool)) { array2[num2] = false; continue; } flag = false; break; } if (!flag) { continue; } try { object obj = (methodInfo.IsStatic ? null : CameraInstance(type)); if (methodInfo.IsStatic || obj != null) { object obj2 = methodInfo.Invoke(obj, array2); if (!(methodInfo.ReturnType == typeof(bool)) || !(obj2 is bool) || (bool)obj2) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK focus used " + methodInfo.Name + ".")); return true; } } } catch (TargetInvocationException ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK native creature focus rejected one signature: " + ((ex.InnerException != null) ? ex.InnerException.Message : ex.Message))); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("PINK native creature focus rejected one signature: " + ex2.Message)); } } } MethodInfo method = type.GetMethod("MoveToPosition", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[4] { typeof(Vector3), typeof(bool), typeof(bool), typeof(bool) }, null); if (method == null) { error = "Current TaleSpire runtime has no compatible MoveToPosition method."; return false; } method.Invoke(null, new object[4] { (object)new Vector3(x, y, z), true, false, false }); ((BaseUnityPlugin)this).Logger.LogDebug((object)"PINK focus used CameraController.MoveToPosition fallback."); return true; } private static Type FindType(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } private static object CameraInstance(Type camera) { string[] array = new string[3] { "Instance", "instance", "_instance" }; for (int i = 0; i < array.Length; i++) { PropertyInfo property = camera.GetProperty(array[i], BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { try { return property.GetValue(null, null); } catch { } } FieldInfo field = camera.GetField(array[i], BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(null); } catch { } } } try { return Object.FindObjectOfType(camera); } catch { return null; } } private static bool TryBuildId(Type type, string text, out object value) { value = null; if (type == typeof(string)) { value = text; return true; } Guid result; bool flag = Guid.TryParse(text, out result); if (type == typeof(Guid) && flag) { value = result; return true; } string[] array = new string[4] { "Parse", "FromString", "Create", "FromGuid" }; for (int i = 0; i < array.Length; i++) { MethodInfo method = type.GetMethod(array[i], BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null); if (method != null && type.IsAssignableFrom(method.ReturnType)) { try { value = method.Invoke(null, new object[1] { text }); return value != null; } catch { } } } MethodInfo method2 = type.GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method2 != null) { ParameterInfo[] parameters = method2.GetParameters(); if (parameters.Length == 2 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType.IsByRef) { object obj2 = (type.IsValueType ? Activator.CreateInstance(type) : null); object[] array2 = new object[2] { text, obj2 }; try { object obj3 = method2.Invoke(null, array2); if (obj3 is bool && (bool)obj3) { value = array2[1]; return true; } } catch { } } } ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null); if (constructor != null) { try { value = constructor.Invoke(new object[1] { text }); return true; } catch { } } if (flag) { ConstructorInfo constructor2 = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Guid) }, null); if (constructor2 != null) { try { value = constructor2.Invoke(new object[1] { result }); return true; } catch { } } } try { object obj7 = Activator.CreateInstance(type, nonPublic: true); FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int j = 0; j < fields.Length; j++) { if (flag && fields[j].FieldType == typeof(Guid)) { fields[j].SetValue(obj7, result); value = obj7; return true; } if (fields[j].FieldType == typeof(string)) { fields[j].SetValue(obj7, text); value = obj7; return true; } } } catch { } return false; } private static string JsonEscape(string value) { if (value == null) { return ""; } return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r") .Replace("\n", "\\n"); } private static void WriteJson(NetworkStream stream, int status, bool ok, string error) { string body = "{\"ok\":" + (ok ? "true" : "false") + (string.IsNullOrEmpty(error) ? "" : (",\"error\":\"" + JsonEscape(error) + "\"")) + "}"; WriteResponse(stream, status, body, "application/json; charset=utf-8"); } private static void WriteResponse(NetworkStream stream, int status, string body, string contentType) { byte[] bytes = Encoding.UTF8.GetBytes(body ?? ""); string text = status switch { 504 => "Gateway Timeout", 404 => "Not Found", 400 => "Bad Request", 204 => "No Content", 200 => "OK", _ => "Internal Server Error", }; string s = "HTTP/1.1 " + status + " " + text + "\r\nContent-Type: " + contentType + "\r\nContent-Length: " + bytes.Length + "\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\nConnection: close\r\n\r\n"; byte[] bytes2 = Encoding.ASCII.GetBytes(s); stream.Write(bytes2, 0, bytes2.Length); if (bytes.Length > 0) { stream.Write(bytes, 0, bytes.Length); } stream.Flush(); } }