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 "";
}
}
}