Decompiled source of Cyberpunk PINK Companion Mod v1.1.11

BepInEx/plugins/CyberpunkPINKCreatureBridge.dll

Decompiled a week ago
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 ago
using 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();
	}
}