Decompiled source of NeoZancleBridge v0.9.1

BepInEx/plugins/NeoZancleBridge/NeoZancleBridge_v.4.0.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("NeoZancleBridge_v.4.0")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("NeoZancleBridge_v.4.0")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("97140b1c-99af-443e-9648-3f7743b91bcc")]
[assembly: AssemblyFileVersion("0.9.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.9.0.0")]
[BepInPlugin("com.neozancle.bridge", "NeoZancleBridge", "0.9.0")]
public class NeoZancleBridgePlugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	internal static string LatestJson = "{\"type\":\"none\",\"message\":\"NeoZancleBridge online, no roll yet\"}";

	internal static string CityStateJson = "{\"type\":\"falcenet_city_state\",\"status\":\"empty\"}";

	internal static string NetRequestJson = "{\"type\":\"falcenet_net_request\",\"status\":\"empty\"}";

	internal static string EventBusJson = "{\"type\":\"falcenet_event\",\"status\":\"empty\"}";

	internal static string TestJson = "{\"type\":\"falcenet_test\",\"status\":\"empty\"}";

	internal static string CampaignJson = "{\"type\":\"falcenet_campaign\",\"status\":\"empty\"}";

	internal static Dictionary<string, string> SessionClients = new Dictionary<string, string>();

	internal static object BridgeLock = new object();

	internal static bool IsLocalGmMode = false;

	private Harmony harmony;

	private HttpListener listener;

	private Thread listenerThread;

	private volatile bool serverRunning;

	private string falceNetRoot;

	private void Awake()
	{
		Log = ((BaseUnityPlugin)this).Logger;
		((BaseUnityPlugin)this).Logger.LogWarning((object)"### NeoZancleBridge v0.9.0-protocol-core ###");
		ResolveFalceNetRoot();
		StartLocalServer();
		PatchTaleSpireDiceResults();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"NZB: pronto. FalceNet principale = Symbiote, bridge = risultati/log.");
	}

	private void OnDestroy()
	{
		try
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
		catch
		{
		}
		StopLocalServer();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"NeoZancleBridge OFFLINE");
	}

	private void PatchTaleSpireDiceResults()
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Expected O, but got Unknown
		harmony = new Harmony("com.neozancle.bridge");
		Type type = AccessTools.TypeByName("Dice.DiceRollManager");
		if (type == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"NZB: Dice.DiceRollManager non trovato");
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(type, "OnResults", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"NZB: Dice.DiceRollManager.OnResults non trovato");
			return;
		}
		MethodInfo methodInfo2 = AccessTools.Method(typeof(DiceResultsPatch), "Postfix", (Type[])null, (Type[])null);
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"NZB: Dice results postfix attiva v0.9.0-protocol-core");
	}

	private void OnUnityLogMessage(string condition, string stackTrace, LogType type)
	{
		try
		{
			if (!string.IsNullOrEmpty(condition) && condition.Contains("SetPanelOffsetBasedOnGMMode"))
			{
				if (condition.Contains("-115 to -190"))
				{
					IsLocalGmMode = true;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"NZB_TALESPIRE_UI_MODE observed = GM (non-authoritative)");
				}
				else if (condition.Contains("-190 to -115"))
				{
					IsLocalGmMode = false;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"NZB_TALESPIRE_UI_MODE observed = PLAYER (non-authoritative)");
				}
			}
		}
		catch
		{
		}
	}

	private void StartLocalServer()
	{
		try
		{
			listener = new HttpListener();
			listener.Prefixes.Add("http://localhost:8765/");
			listener.Start();
			serverRunning = true;
			listenerThread = new Thread(ServerLoop);
			listenerThread.IsBackground = true;
			listenerThread.Start();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"NZB: HTTP server started on localhost:8765");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("NZB: HTTP server failed: " + ex.Message));
		}
	}

	private void StopLocalServer()
	{
		serverRunning = false;
		try
		{
			if (listener != null)
			{
				listener.Stop();
				listener.Close();
			}
		}
		catch
		{
		}
	}

	private void ServerLoop()
	{
		while (serverRunning)
		{
			try
			{
				HttpListenerContext context = listener.GetContext();
				HandleRequest(context);
			}
			catch
			{
				if (!serverRunning)
				{
					break;
				}
			}
		}
	}

	private void HandleRequest(HttpListenerContext ctx)
	{
		string absolutePath = ctx.Request.Url.AbsolutePath;
		if (ctx.Request.HttpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase))
		{
			WriteOptions(ctx);
		}
		else if (absolutePath.Equals("/health", StringComparison.OrdinalIgnoreCase) || absolutePath.Equals("/status", StringComparison.OrdinalIgnoreCase))
		{
			WriteJson(ctx, "{\"status\":\"online\",\"bridge\":\"NeoZancleBridge\",\"version\":\"0.9.0\",\"localMode\":\"UNKNOWN\",\"roleAuthoritative\":false,\"rollHook\":\"native_talespire_onresults\"}");
		}
		else if (absolutePath.Equals("/latest", StringComparison.OrdinalIgnoreCase))
		{
			WriteJson(ctx, LatestJson);
		}
		else if (absolutePath.Equals("/falcenet-state", StringComparison.OrdinalIgnoreCase))
		{
			HandleSimpleMailbox(ctx, ref CityStateJson, "falcenet_city_state");
		}
		else if (absolutePath.Equals("/falcenet-net-request", StringComparison.OrdinalIgnoreCase))
		{
			HandleSimpleMailbox(ctx, ref NetRequestJson, "falcenet_net_request");
		}
		else if (absolutePath.Equals("/falcenet-event", StringComparison.OrdinalIgnoreCase))
		{
			HandleSimpleMailbox(ctx, ref EventBusJson, "falcenet_event");
		}
		else if (absolutePath.Equals("/falcenet-test", StringComparison.OrdinalIgnoreCase))
		{
			HandleSimpleMailbox(ctx, ref TestJson, "falcenet_test");
		}
		else if (absolutePath.Equals("/falcenet-campaign", StringComparison.OrdinalIgnoreCase))
		{
			HandleSimpleMailbox(ctx, ref CampaignJson, "falcenet_campaign");
		}
		else if (absolutePath.Equals("/falcenet-session", StringComparison.OrdinalIgnoreCase))
		{
			HandleSessionRegistry(ctx);
		}
		else if (absolutePath.Equals("/dice", StringComparison.OrdinalIgnoreCase) || absolutePath.Equals("/falcenet-roll", StringComparison.OrdinalIgnoreCase))
		{
			HandleDiceDebugRequest(ctx);
		}
		else if (absolutePath.StartsWith("/falcenet/", StringComparison.OrdinalIgnoreCase))
		{
			ServeFalceNet(ctx, absolutePath.Substring("/falcenet/".Length));
		}
		else
		{
			WriteJson(ctx, "{\"status\":\"unknown\",\"use\":\"/health, /status, /latest, /falcenet-test, /falcenet-session, /falcenet-state, /falcenet-net-request, /falcenet-event, /falcenet-campaign, /falcenet/\"}");
		}
	}

	private void HandleSimpleMailbox(HttpListenerContext ctx, ref string box, string typeName)
	{
		try
		{
			if (ctx.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
			{
				string text = ReadRequestBody(ctx);
				if (!string.IsNullOrEmpty(text))
				{
					lock (BridgeLock)
					{
						box = text;
					}
					((BaseUnityPlugin)this).Logger.LogInfo((object)("NZB_MAILBOX_RX " + typeName + " " + text.Length + " bytes"));
				}
				WriteJson(ctx, "{\"status\":\"ok\",\"type\":\"" + typeName + "\"}");
				return;
			}
			lock (BridgeLock)
			{
				WriteJson(ctx, box);
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("NZB mailbox error " + typeName + ": " + ex.Message));
			WriteJson(ctx, "{\"status\":\"error\",\"type\":\"" + typeName + "\"}");
		}
	}

	private void HandleSessionRegistry(HttpListenerContext ctx)
	{
		try
		{
			if (ctx.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
			{
				string text = ReadRequestBody(ctx);
				string text2 = ExtractJsonObjectAfterKey(text, "client");
				if (string.IsNullOrEmpty(text2))
				{
					text2 = text;
				}
				string text3 = ExtractJsonString(text2, "characterId");
				if (string.IsNullOrEmpty(text3))
				{
					text3 = ExtractJsonString(text2, "clientId");
				}
				if (string.IsNullOrEmpty(text3))
				{
					text3 = "client_" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
				}
				lock (BridgeLock)
				{
					SessionClients[text3] = text2;
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("NZB_SESSION_RX key=" + text3));
				WriteJson(ctx, "{\"status\":\"ok\",\"type\":\"falcenet_session_presence\",\"key\":\"" + EscapeLocal(text3) + "\"}");
			}
			else
			{
				List<string> list;
				lock (BridgeLock)
				{
					list = SessionClients.Values.ToList();
				}
				string json = "{\"type\":\"falcenet_session_registry\",\"clients\":[" + string.Join(",", list.ToArray()) + "]}";
				WriteJson(ctx, json);
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("NZB session error: " + ex.Message));
			WriteJson(ctx, "{\"status\":\"error\",\"type\":\"falcenet_session_registry\",\"clients\":[]}");
		}
	}

	private string ReadRequestBody(HttpListenerContext ctx)
	{
		using StreamReader streamReader = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding);
		return streamReader.ReadToEnd();
	}

	private string ExtractJsonObjectAfterKey(string json, string key)
	{
		if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(key))
		{
			return "";
		}
		string text = "\"" + key + "\"";
		int num = json.IndexOf(text, StringComparison.OrdinalIgnoreCase);
		if (num < 0)
		{
			return "";
		}
		int num2 = json.IndexOf(':', num + text.Length);
		if (num2 < 0)
		{
			return "";
		}
		int num3 = json.IndexOf('{', num2);
		if (num3 < 0)
		{
			return "";
		}
		int num4 = 0;
		bool flag = false;
		bool flag2 = false;
		for (int i = num3; i < json.Length; i++)
		{
			char c = json[i];
			if (flag2)
			{
				flag2 = false;
				continue;
			}
			switch (c)
			{
			case '\\':
				flag2 = true;
				continue;
			case '"':
				flag = !flag;
				continue;
			}
			if (flag)
			{
				continue;
			}
			switch (c)
			{
			case '{':
				num4++;
				break;
			case '}':
				num4--;
				if (num4 == 0)
				{
					return json.Substring(num3, i - num3 + 1);
				}
				break;
			}
		}
		return "";
	}

	private string ExtractJsonString(string json, string key)
	{
		if (string.IsNullOrEmpty(json) || string.IsNullOrEmpty(key))
		{
			return "";
		}
		string text = "\"" + key + "\"";
		int num = json.IndexOf(text, StringComparison.OrdinalIgnoreCase);
		if (num < 0)
		{
			return "";
		}
		int num2 = json.IndexOf(':', num + text.Length);
		if (num2 < 0)
		{
			return "";
		}
		int num3 = json.IndexOf('"', num2 + 1);
		if (num3 < 0)
		{
			return "";
		}
		int i = num3 + 1;
		bool flag = false;
		for (; i < json.Length; i++)
		{
			char c = json[i];
			if (flag)
			{
				flag = false;
				continue;
			}
			switch (c)
			{
			case '\\':
				flag = true;
				continue;
			default:
				continue;
			case '"':
				break;
			}
			break;
		}
		if (i >= json.Length)
		{
			return "";
		}
		return json.Substring(num3 + 1, i - num3 - 1);
	}

	private string EscapeLocal(string s)
	{
		if (s == null)
		{
			return "";
		}
		return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", " ")
			.Replace("\n", " ");
	}

	private void HandleDiceDebugRequest(HttpListenerContext ctx)
	{
		string text = "";
		try
		{
			using (StreamReader streamReader = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding))
			{
				text = streamReader.ReadToEnd();
			}
			((BaseUnityPlugin)this).Logger.LogWarning((object)"NZB DICE DEBUG ENDPOINT HIT");
			((BaseUnityPlugin)this).Logger.LogWarning((object)("NZB DICE BODY: " + text));
			((BaseUnityPlugin)this).Logger.LogWarning((object)"NZB: /dice è solo debug. Il tray reale deve passare dalla Symbiote JS.");
			WriteJson(ctx, "{\"status\":\"dice_debug_only\",\"message\":\"Use FalceNet as TaleSpire Symbiote for native tray\"}");
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("NZB /dice debug error: " + ex));
			WriteJson(ctx, "{\"status\":\"dice_error\"}");
		}
	}

	private void ResolveFalceNetRoot()
	{
		string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
		falceNetRoot = Path.Combine(directoryName, "FalceNet");
		if (!Directory.Exists(falceNetRoot))
		{
			falceNetRoot = Path.Combine(Paths.PluginPath, "NeoZancleBridge", "FalceNet");
		}
	}

	private void ServeFalceNet(HttpListenerContext ctx, string relativePath)
	{
		try
		{
			if (string.IsNullOrEmpty(relativePath))
			{
				relativePath = "index.html";
			}
			relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar);
			string fullPath = Path.GetFullPath(Path.Combine(falceNetRoot, relativePath));
			string fullPath2 = Path.GetFullPath(falceNetRoot);
			if (!fullPath.StartsWith(fullPath2))
			{
				ctx.Response.StatusCode = 403;
				ctx.Response.Close();
				return;
			}
			if (!File.Exists(fullPath))
			{
				ctx.Response.StatusCode = 404;
				ctx.Response.Close();
				return;
			}
			byte[] array = File.ReadAllBytes(fullPath);
			ctx.Response.ContentType = GuessContentType(fullPath);
			ctx.Response.ContentLength64 = array.Length;
			AddCorsHeaders(ctx);
			ctx.Response.OutputStream.Write(array, 0, array.Length);
			ctx.Response.OutputStream.Close();
		}
		catch (Exception ex)
		{
			Log.LogError((object)("NZB SERVE ERROR: " + ex));
		}
	}

	private void AddCorsHeaders(HttpListenerContext ctx)
	{
		try
		{
			ctx.Response.AddHeader("Access-Control-Allow-Origin", "*");
		}
		catch
		{
		}
		try
		{
			ctx.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
		}
		catch
		{
		}
		try
		{
			ctx.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
		}
		catch
		{
		}
		try
		{
			ctx.Response.AddHeader("Access-Control-Max-Age", "86400");
		}
		catch
		{
		}
	}

	private void WriteOptions(HttpListenerContext ctx)
	{
		AddCorsHeaders(ctx);
		ctx.Response.StatusCode = 204;
		ctx.Response.Close();
	}

	private void WriteJson(HttpListenerContext ctx, string json)
	{
		byte[] bytes = Encoding.UTF8.GetBytes(json);
		ctx.Response.ContentType = "application/json";
		ctx.Response.ContentEncoding = Encoding.UTF8;
		ctx.Response.ContentLength64 = bytes.Length;
		AddCorsHeaders(ctx);
		ctx.Response.OutputStream.Write(bytes, 0, bytes.Length);
		ctx.Response.OutputStream.Close();
	}

	private string GuessContentType(string path)
	{
		switch (Path.GetExtension(path).ToLowerInvariant())
		{
		case ".html":
		case ".htm":
			return "text/html; charset=utf-8";
		case ".js":
			return "application/javascript; charset=utf-8";
		case ".css":
			return "text/css; charset=utf-8";
		case ".json":
			return "application/json; charset=utf-8";
		case ".png":
			return "image/png";
		case ".jpg":
		case ".jpeg":
			return "image/jpeg";
		case ".gif":
			return "image/gif";
		case ".svg":
			return "image/svg+xml";
		case ".webp":
			return "image/webp";
		case ".mp3":
			return "audio/mpeg";
		case ".ogg":
			return "audio/ogg";
		case ".wav":
			return "audio/wav";
		default:
			return "application/octet-stream";
		}
	}
}
public static class DiceResultsPatch
{
	public static void Postfix(object clientId, object rollResults, bool isGmRoll, bool showResult, object resultsOrigin, object optionalSymbioteInteropId)
	{
		try
		{
			if (rollResults == null)
			{
				NeoZancleBridgePlugin.Log.LogInfo((object)"NZB_ROLL: rollResults null");
				return;
			}
			RollExtract rollExtract = RollParser.ExtractRoll(rollResults);
			bool flag = isGmRoll;
			bool flag2 = rollExtract.Values.Contains(10);
			bool flag3 = rollExtract.Values.Contains(1);
			NeoZancleBridgePlugin.LatestJson = "{\"type\":\"dice_roll\",\"source\":\"NeoZancleBridge\",\"version\":\"0.9.0\",\"rollId\":\"" + Escape(rollExtract.RollId) + "\",\"client\":\"" + Escape((clientId == null) ? "" : clientId.ToString()) + "\",\"isGmRoll\":" + (flag ? "true" : "false") + ",\"localGmMode\":" + (NeoZancleBridgePlugin.IsLocalGmMode ? "true" : "false") + ",\"showResult\":" + (showResult ? "true" : "false") + ",\"origin\":\"" + Escape((resultsOrigin == null) ? "" : resultsOrigin.ToString()) + "\",\"total\":" + rollExtract.Total + ",\"values\":[" + string.Join(",", rollExtract.Values.ToArray()) + "],\"crit\":" + (flag2 ? "true" : "false") + ",\"fumble\":" + (flag3 ? "true" : "false") + ",\"raw\":\"" + Escape(rollResults.ToString()) + "\"}";
			NeoZancleBridgePlugin.Log.LogInfo((object)("NZB_ROLL_JSON rollId=" + rollExtract.RollId + " total=" + rollExtract.Total + " values=[" + string.Join(",", rollExtract.Values.ToArray()) + "] gm=" + flag + " crit=" + flag2 + " fumble=" + flag3));
		}
		catch (Exception ex)
		{
			NeoZancleBridgePlugin.Log.LogError((object)("NZB DICE ERROR: " + ex));
		}
	}

	private static string Escape(string s)
	{
		if (s == null)
		{
			return "";
		}
		return s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", " ")
			.Replace("\n", " ");
	}
}
public class RollExtract
{
	public string RollId = "";

	public int Total;

	public List<int> Values = new List<int>();
}
public static class RollParser
{
	public static RollExtract ExtractRoll(object rollResults)
	{
		RollExtract rollExtract = new RollExtract();
		object fieldOrProp = GetFieldOrProp(rollResults, "<RollId>k__BackingField", "RollId");
		if (fieldOrProp != null)
		{
			rollExtract.RollId = fieldOrProp.ToString();
		}
		object fieldOrProp2 = GetFieldOrProp(rollResults, "<ResultsGroups>k__BackingField", "ResultsGroups");
		if (fieldOrProp2 != null)
		{
			foreach (object item in AsEnumerable(fieldOrProp2))
			{
				object fieldOrProp3 = GetFieldOrProp(item, "Result", "Result");
				if (fieldOrProp3 != null)
				{
					int num = EvaluateOperand(fieldOrProp3, rollExtract, 0);
					rollExtract.Total += num;
				}
			}
		}
		return rollExtract;
	}

	private static int EvaluateOperand(object operand, RollExtract extract, int depth)
	{
		if (operand == null || depth > 16)
		{
			return 0;
		}
		string text = SafeText(GetFieldOrProp(operand, "_which", "Which"));
		if (text.Contains("Value"))
		{
			return EvaluateValue(GetFieldOrProp(operand, "_value", "Value"), extract);
		}
		if (text.Contains("Result"))
		{
			return EvaluateRollResult(GetFieldOrProp(operand, "_result", "Result"), extract, depth + 1);
		}
		if (text.Contains("Operation"))
		{
			return EvaluateOperation(GetFieldOrProp(operand, "_operation", "Operation"), extract, depth + 1);
		}
		int num = EvaluateValue(GetFieldOrProp(operand, "_value", "Value"), extract);
		if (num != 0)
		{
			return num;
		}
		int num2 = EvaluateRollResult(GetFieldOrProp(operand, "_result", "Result"), extract, depth + 1);
		if (num2 != 0)
		{
			return num2;
		}
		return EvaluateOperation(GetFieldOrProp(operand, "_operation", "Operation"), extract, depth + 1);
	}

	private static int EvaluateOperation(object operation, RollExtract extract, int depth)
	{
		if (operation == null || depth > 16)
		{
			return 0;
		}
		string text = SafeText(GetFieldOrProp(operation, "Operator", "Operator"));
		object fieldOrProp = GetFieldOrProp(operation, "Operands", "Operands");
		List<int> list = new List<int>();
		if (fieldOrProp != null)
		{
			foreach (object item in AsEnumerable(fieldOrProp))
			{
				list.Add(EvaluateUnknown(item, extract, depth + 1));
			}
		}
		if (list.Count == 0)
		{
			return 0;
		}
		if (text.Contains("Subtract") || text.Contains("Sub"))
		{
			int num = list[0];
			for (int i = 1; i < list.Count; i++)
			{
				num -= list[i];
			}
			return num;
		}
		int num2 = 0;
		foreach (int item2 in list)
		{
			num2 += item2;
		}
		return num2;
	}

	private static int EvaluateRollResult(object rollResult, RollExtract extract, int depth)
	{
		if (rollResult == null || depth > 16)
		{
			return 0;
		}
		object fieldOrProp = GetFieldOrProp(rollResult, "Results", "Results");
		if (fieldOrProp == null)
		{
			return 0;
		}
		int num = 0;
		foreach (object item in AsEnumerable(fieldOrProp))
		{
			num += EvaluateUnknown(item, extract, depth + 1);
		}
		return num;
	}

	private static int EvaluateUnknown(object obj, RollExtract extract, int depth)
	{
		if (obj == null || depth > 16)
		{
			return 0;
		}
		Type type = obj.GetType();
		if (obj is short || obj is int)
		{
			int num = Convert.ToInt32(obj);
			extract.Values.Add(num);
			return num;
		}
		string text = type.FullName ?? "";
		if (text.Contains("RollOperand"))
		{
			return EvaluateOperand(obj, extract, depth + 1);
		}
		if (text.Contains("RollResult"))
		{
			return EvaluateRollResult(obj, extract, depth + 1);
		}
		if (text.Contains("RollOperation"))
		{
			return EvaluateOperation(obj, extract, depth + 1);
		}
		if (text.Contains("RollValue"))
		{
			return EvaluateValue(obj, extract);
		}
		if (obj is IEnumerable && !(obj is string))
		{
			int num2 = 0;
			{
				foreach (object item in AsEnumerable(obj))
				{
					num2 += EvaluateUnknown(item, extract, depth + 1);
				}
				return num2;
			}
		}
		return 0;
	}

	private static int EvaluateValue(object rollValue, RollExtract extract)
	{
		if (rollValue == null)
		{
			return 0;
		}
		object fieldOrProp = GetFieldOrProp(rollValue, "Value", "Value");
		if (fieldOrProp == null)
		{
			return 0;
		}
		try
		{
			int num = Convert.ToInt32(fieldOrProp);
			if (num != 0)
			{
				extract.Values.Add(num);
			}
			return num;
		}
		catch
		{
			return 0;
		}
	}

	private static object GetFieldOrProp(object obj, string fieldName, string propName)
	{
		if (obj == null)
		{
			return null;
		}
		Type type = obj.GetType();
		FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (field != null)
		{
			try
			{
				return field.GetValue(obj);
			}
			catch
			{
			}
		}
		PropertyInfo property = type.GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (property != null && property.CanRead)
		{
			try
			{
				return property.GetValue(obj, null);
			}
			catch
			{
			}
		}
		return null;
	}

	private static IEnumerable<object> AsEnumerable(object obj)
	{
		if (!(obj is IEnumerable enumerable))
		{
			yield break;
		}
		foreach (object item in enumerable)
		{
			yield return item;
		}
	}

	private static string SafeText(object obj)
	{
		if (obj == null)
		{
			return "";
		}
		try
		{
			return obj.ToString();
		}
		catch
		{
			return "";
		}
	}
}