Decompiled source of DSP Planner Export v1.8.0

BepInEx/plugins/DSPPlannerExport.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("DSPPlannerExport")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0+b263694d51951580018488a24aea05e21e5b5781")]
[assembly: AssemblyProduct("DSPPlannerExport")]
[assembly: AssemblyTitle("DSPPlannerExport")]
[assembly: AssemblyVersion("1.4.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DSPPlannerExport
{
	[BepInPlugin("dev.ski.dspplannerexport", "DSP Planner Export", "1.8.0")]
	public class Plugin : BaseUnityPlugin
	{
		private enum DeficitMode
		{
			Sustained,
			Instant,
			Window
		}

		private enum Corner
		{
			TopLeft,
			TopRight,
			BottomLeft,
			BottomRight
		}

		private struct Snap
		{
			public long tick;

			public int[] ids;

			public long[] p;

			public long[] c;
		}

		private struct DefRow
		{
			public int id;

			public string name;

			public float prod;

			public float cons;

			public float net;

			public int streak;

			public bool flagged;
		}

		public const string GUID = "dev.ski.dspplannerexport";

		public const string NAME = "DSP Planner Export";

		public const string VERSION = "1.8.0";

		private const int Port = 8765;

		private HttpListener _listener;

		private Thread _thread;

		private Harmony _harmony;

		private volatile bool _running;

		private volatile string _snapshot = "{\"version\":1,\"running\":false,\"states\":[]}";

		private volatile string _rates = "{\"version\":1,\"running\":false,\"gameTick\":0,\"items\":[]}";

		private volatile string _protos;

		private volatile string _techs;

		private volatile string _deficits = "{\"version\":1,\"running\":false,\"items\":[]}";

		private int _frame;

		private ConfigEntry<bool> _cfgEnabled;

		private ConfigEntry<KeyboardShortcut> _cfgToggle;

		private ConfigEntry<DeficitMode> _cfgMode;

		private ConfigEntry<int> _cfgSustainSeconds;

		private ConfigEntry<int> _cfgWindowSeconds;

		private ConfigEntry<float> _cfgMinMagnitude;

		private ConfigEntry<int> _cfgMaxRows;

		private ConfigEntry<float> _cfgScale;

		private ConfigEntry<Corner> _cfgCorner;

		private ConfigEntry<bool> _cfgShowPending;

		private ConfigEntry<float> _cfgHudX;

		private ConfigEntry<float> _cfgHudY;

		private FileSystemWatcher _cfgWatcher;

		private string[] _itemName;

		private float[] _negSeconds;

		private readonly List<Snap> _snaps = new List<Snap>();

		private volatile DefRow[] _hudRows = new DefRow[0];

		private volatile bool _inGame;

		private bool _hudVisible = true;

		private bool _dragging;

		private float _hudX;

		private float _hudY;

		private Vector2 _dragOffset;

		private GUIStyle _titleStyle;

		private GUIStyle _rowStyle;

		private Texture2D _px;

		private void Awake()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Expected O, but got Unknown
			_cfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "Enabled", true, "Show the in-game live-deficit HUD overlay.");
			_cfgToggle = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("HUD", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Hotkey to show/hide the HUD at runtime.");
			_cfgMode = ((BaseUnityPlugin)this).Config.Bind<DeficitMode>("Deficit", "Mode", DeficitMode.Window, "How a deficit is flagged. Window = net averaged over the last WindowSeconds (recommended — buffer churn averages out, only real shortages show). Sustained = net-negative held for SustainSeconds. Instant = net-negative in the latest 1s sample (most detail, spammy).");
			_cfgSustainSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Deficit", "SustainSeconds", 5, "Sustained mode: consecutive ~1s samples in deficit before a flag.");
			_cfgWindowSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Deficit", "WindowSeconds", 60, "Window mode: average net production over this many seconds (e.g. 30, 60, 120, 300, 600, 900, 1800, 3600). Longer = calmer, only structural deficits show. Capped at 3600 (1h).");
			_cfgMinMagnitude = ((BaseUnityPlugin)this).Config.Bind<float>("Deficit", "MinMagnitude", 1f, "Ignore deficits smaller than this many items/min (noise floor).");
			_cfgMaxRows = ((BaseUnityPlugin)this).Config.Bind<int>("HUD", "MaxRows", 12, "Maximum deficit rows drawn in the HUD.");
			_cfgScale = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "Scale", 1f, "HUD size multiplier.");
			_cfgCorner = ((BaseUnityPlugin)this).Config.Bind<Corner>("HUD", "Corner", Corner.TopRight, "Screen corner the HUD anchors to (used until you drag the HUD).");
			_cfgShowPending = ((BaseUnityPlugin)this).Config.Bind<bool>("HUD", "ShowPending", true, "Also list items dipping but not yet sustained (dimmed, with their streak) so the HUD matches the planner card. Off = show only flagged deficits.");
			_cfgHudX = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "HudX", -1f, "HUD pixel X of the top-left corner. -1 = anchor to Corner. Set by dragging the HUD in-game.");
			_cfgHudY = ((BaseUnityPlugin)this).Config.Bind<float>("HUD", "HudY", -1f, "HUD pixel Y of the top-left corner. -1 = anchor to Corner. Set by dragging the HUD in-game.");
			_hudVisible = _cfgEnabled.Value;
			try
			{
				string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath);
				string fileName = Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath);
				_cfgWatcher = new FileSystemWatcher(directoryName, fileName)
				{
					NotifyFilter = (NotifyFilters.Size | NotifyFilters.LastWrite),
					EnableRaisingEvents = true
				};
				_cfgWatcher.Changed += delegate
				{
					try
					{
						((BaseUnityPlugin)this).Config.Reload();
					}
					catch
					{
					}
				};
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: config watcher failed (edit the cfg + restart to apply). " + ex.Message));
			}
			try
			{
				_harmony = new Harmony("dev.ski.dspplannerexport");
				_harmony.PatchAll(typeof(ProductionTally));
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: stats patch failed (live /rates disabled). " + ex2.Message));
			}
			try
			{
				_listener = new HttpListener();
				_listener.Prefixes.Add($"http://localhost:{8765}/");
				_listener.Prefixes.Add($"http://127.0.0.1:{8765}/");
				_listener.Start();
				_running = true;
				_thread = new Thread(ServeLoop)
				{
					IsBackground = true,
					Name = "DSPPlannerExport"
				};
				_thread.Start();
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"DSP Planner Export: serving http://localhost:{8765}/state, /protos, /techs, /rates, /deficits, /config");
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("DSP Planner Export: could not start HTTP listener. On Windows you may need to run the game as admin once, or pick another port. " + ex3));
			}
		}

		private void Update()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = _cfgToggle.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				_hudVisible = !_hudVisible;
			}
			if (++_frame % 60 != 0)
			{
				return;
			}
			try
			{
				_snapshot = BuildJson();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: snapshot error " + ex.Message));
			}
			try
			{
				_rates = BuildRates();
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: rates error " + ex2.Message));
			}
			try
			{
				ComputeDeficits();
			}
			catch (Exception ex3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: deficit error " + ex3.Message));
			}
		}

		private void ServeLoop()
		{
			while (_running)
			{
				HttpListenerContext ctx;
				try
				{
					ctx = _listener.GetContext();
				}
				catch
				{
					break;
				}
				try
				{
					HttpListenerResponse response = ctx.Response;
					response.AddHeader("Access-Control-Allow-Origin", "*");
					response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
					response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
					response.AddHeader("Cache-Control", "no-store");
					if (ctx.Request.HttpMethod == "OPTIONS")
					{
						response.StatusCode = 204;
						response.OutputStream.Close();
						continue;
					}
					string absolutePath = ctx.Request.Url.AbsolutePath;
					if (absolutePath.EndsWith("/events"))
					{
						Thread thread = new Thread((ThreadStart)delegate
						{
							ServeEvents(ctx);
						});
						thread.IsBackground = true;
						thread.Name = "DSPPlannerExportSSE";
						thread.Start();
						continue;
					}
					response.ContentType = "application/json";
					string s;
					if (absolutePath.EndsWith("/protos"))
					{
						if (_protos == null)
						{
							try
							{
								_protos = BuildProtos();
							}
							catch (Exception ex)
							{
								_protos = "{\"items\":[]}";
								((BaseUnityPlugin)this).Logger.LogWarning((object)("protos error " + ex.Message));
							}
						}
						s = _protos;
					}
					else if (absolutePath.EndsWith("/techs"))
					{
						if (_techs == null)
						{
							try
							{
								_techs = BuildTechs();
							}
							catch (Exception ex2)
							{
								_techs = "{\"version\":1,\"techs\":[]}";
								((BaseUnityPlugin)this).Logger.LogWarning((object)("techs error " + ex2.Message));
							}
						}
						s = _techs;
					}
					else if (absolutePath.EndsWith("/rates"))
					{
						s = _rates;
					}
					else if (absolutePath.EndsWith("/deficits"))
					{
						s = _deficits;
					}
					else if (absolutePath.EndsWith("/config"))
					{
						if (ctx.Request.HttpMethod == "POST")
						{
							string form;
							using (StreamReader streamReader = new StreamReader(ctx.Request.InputStream, Encoding.UTF8))
							{
								form = streamReader.ReadToEnd();
							}
							try
							{
								ApplyConfig(form);
							}
							catch (Exception ex3)
							{
								((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: config apply error " + ex3.Message));
							}
						}
						s = BuildConfig();
					}
					else
					{
						s = _snapshot;
					}
					byte[] bytes = Encoding.UTF8.GetBytes(s);
					response.ContentLength64 = bytes.Length;
					response.OutputStream.Write(bytes, 0, bytes.Length);
					response.OutputStream.Close();
				}
				catch (Exception ex4)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("DSP Planner Export: request error " + ex4.Message));
				}
			}
		}

		private void ServeEvents(HttpListenerContext ctx)
		{
			HttpListenerResponse response = ctx.Response;
			Stream outputStream = response.OutputStream;
			try
			{
				response.ContentType = "text/event-stream";
				response.SendChunked = true;
				string text = null;
				string text2 = null;
				while (_running)
				{
					string snapshot = _snapshot;
					string rates = _rates;
					StringBuilder stringBuilder = new StringBuilder();
					if (snapshot != text)
					{
						stringBuilder.Append("event: state\ndata: ").Append(snapshot).Append("\n\n");
						text = snapshot;
					}
					if (rates != text2)
					{
						stringBuilder.Append("event: rates\ndata: ").Append(rates).Append("\n\n");
						text2 = rates;
					}
					if (stringBuilder.Length == 0)
					{
						stringBuilder.Append(": keepalive\n\n");
					}
					byte[] bytes = Encoding.UTF8.GetBytes(stringBuilder.ToString());
					outputStream.Write(bytes, 0, bytes.Length);
					outputStream.Flush();
					Thread.Sleep(1000);
				}
			}
			catch
			{
			}
			try
			{
				outputStream.Close();
			}
			catch
			{
			}
		}

		private static string BuildJson()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder(4096);
			bool flag = (Object)(object)GameMain.instance != (Object)null && GameMain.history != null && !DSPGame.IsMenuDemo;
			stringBuilder.Append("{\"version\":1,\"running\":").Append(flag ? "true" : "false").Append(",\"states\":[");
			if (flag)
			{
				Dictionary<int, TechState> techStates = GameMain.history.techStates;
				if (techStates != null)
				{
					bool flag2 = true;
					foreach (KeyValuePair<int, TechState> item in techStates)
					{
						TechState value = item.Value;
						if (value.unlocked || value.curLevel > 0)
						{
							if (!flag2)
							{
								stringBuilder.Append(',');
							}
							flag2 = false;
							stringBuilder.Append("{\"id\":").Append(item.Key).Append(",\"level\":")
								.Append(value.curLevel)
								.Append(",\"max\":")
								.Append(value.maxLevel)
								.Append(",\"unlocked\":")
								.Append(value.unlocked ? "true" : "false")
								.Append('}');
						}
					}
				}
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private static string BuildProtos()
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder(65536);
			stringBuilder.Append("{\"version\":2,\"items\":[");
			bool flag = true;
			ItemProto[] dataArray = ((ProtoSet<ItemProto>)(object)LDB.items).dataArray;
			foreach (ItemProto val in dataArray)
			{
				if (val == null || ((Proto)val).ID <= 0)
				{
					continue;
				}
				if (!flag)
				{
					stringBuilder.Append(',');
				}
				flag = false;
				stringBuilder.Append("{\"id\":").Append(((Proto)val).ID).Append(",\"name\":\"")
					.Append(JsonEscape(((Proto)val).name))
					.Append('"')
					.Append(",\"grid\":")
					.Append(val.GridIndex);
				if (val.ModelIndex <= 0)
				{
					stringBuilder.Append('}');
					continue;
				}
				stringBuilder.Append(",\"model\":").Append(val.ModelIndex);
				try
				{
					PrefabDesc val2 = ((ProtoSet<ModelProto>)(object)LDB.models).Select(val.ModelIndex)?.prefabDesc;
					if (val2 != null)
					{
						Vector2 blueprintBoxSize = val2.blueprintBoxSize;
						if (blueprintBoxSize.x > 0f && blueprintBoxSize.y > 0f)
						{
							stringBuilder.Append(",\"w\":").Append((int)blueprintBoxSize.x).Append(",\"h\":")
								.Append((int)blueprintBoxSize.y);
						}
						float y = val2.lapJoint.y;
						if (y > 0f)
						{
							stringBuilder.Append(",\"zstep\":").Append(y.ToString("0.###", CultureInfo.InvariantCulture));
						}
						Pose[] slotPoses = val2.slotPoses;
						if (slotPoses != null && slotPoses.Length != 0)
						{
							int[] array = new int[4];
							Pose[] array2 = slotPoses;
							for (int j = 0; j < array2.Length; j++)
							{
								Vector3 position = array2[j].position;
								int num = ((Math.Abs(position.x) >= Math.Abs(position.z)) ? ((position.x >= 0f) ? 1 : 3) : ((!(position.z >= 0f)) ? 2 : 0));
								array[num]++;
							}
							stringBuilder.Append(",\"slots\":[").Append(array[0]).Append(',')
								.Append(array[1])
								.Append(',')
								.Append(array[2])
								.Append(',')
								.Append(array[3])
								.Append(']');
						}
					}
				}
				catch
				{
				}
				stringBuilder.Append('}');
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private static string BuildTechs()
		{
			StringBuilder stringBuilder = new StringBuilder(65536);
			stringBuilder.Append("{\"version\":1,\"techs\":[");
			bool flag = true;
			TechProto[] dataArray = ((ProtoSet<TechProto>)(object)LDB.techs).dataArray;
			foreach (TechProto val in dataArray)
			{
				if (val == null || ((Proto)val).ID <= 0)
				{
					continue;
				}
				if (!flag)
				{
					stringBuilder.Append(',');
				}
				flag = false;
				long hashNeeded;
				try
				{
					hashNeeded = val.GetHashNeeded(val.Level);
				}
				catch
				{
					hashNeeded = val.HashNeeded;
				}
				stringBuilder.Append("{\"id\":").Append(((Proto)val).ID).Append(",\"name\":\"")
					.Append(JsonEscape(((Proto)val).name))
					.Append('"')
					.Append(",\"level\":")
					.Append(val.Level)
					.Append(",\"max\":")
					.Append(val.MaxLevel)
					.Append(",\"hash\":")
					.Append(hashNeeded);
				if (val.MaxLevel > val.Level)
				{
					stringBuilder.Append(",\"hashByLevel\":[");
					int num = Math.Min(val.MaxLevel, val.Level + 49);
					for (int j = val.Level; j <= num; j++)
					{
						if (j > val.Level)
						{
							stringBuilder.Append(',');
						}
						long hashNeeded2;
						try
						{
							hashNeeded2 = val.GetHashNeeded(j);
						}
						catch
						{
							hashNeeded2 = val.HashNeeded;
						}
						stringBuilder.Append(hashNeeded2);
					}
					stringBuilder.Append(']');
				}
				stringBuilder.Append(",\"pre\":");
				AppendIntArray(stringBuilder, val.PreTechs);
				stringBuilder.Append(",\"preImplicit\":");
				AppendIntArray(stringBuilder, val.PreTechsImplicit);
				stringBuilder.Append(",\"items\":");
				AppendIntArray(stringBuilder, val.Items);
				stringBuilder.Append(",\"points\":");
				AppendIntArray(stringBuilder, val.ItemPoints);
				stringBuilder.Append(",\"preItem\":");
				AppendIntArray(stringBuilder, val.PreItem);
				stringBuilder.Append('}');
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private static void AppendIntArray(StringBuilder sb, int[] a)
		{
			sb.Append('[');
			if (a != null)
			{
				for (int i = 0; i < a.Length; i++)
				{
					if (i > 0)
					{
						sb.Append(',');
					}
					sb.Append(a[i]);
				}
			}
			sb.Append(']');
		}

		private static string BuildRates()
		{
			bool flag = (Object)(object)GameMain.instance != (Object)null && GameMain.data != null && !DSPGame.IsMenuDemo;
			StringBuilder stringBuilder = new StringBuilder(16384);
			stringBuilder.Append("{\"version\":1,\"running\":").Append(flag ? "true" : "false").Append(",\"gameTick\":")
				.Append(flag ? GameMain.gameTick : 0)
				.Append(",\"items\":[");
			if (flag)
			{
				bool flag2 = true;
				for (int i = 0; i < ProductionTally.Produced.Length; i++)
				{
					long num = ProductionTally.Produced[i];
					long num2 = ProductionTally.Consumed[i];
					if (num != 0L || num2 != 0L)
					{
						if (!flag2)
						{
							stringBuilder.Append(',');
						}
						flag2 = false;
						stringBuilder.Append("{\"id\":").Append(i).Append(",\"p\":")
							.Append(num)
							.Append(",\"c\":")
							.Append(num2)
							.Append('}');
					}
				}
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private void ComputeDeficits()
		{
			if (!(_inGame = (Object)(object)GameMain.instance != (Object)null && GameMain.data != null && !DSPGame.IsMenuDemo))
			{
				_deficits = "{\"version\":2,\"running\":false,\"items\":[]}";
				_hudRows = new DefRow[0];
				_snaps.Clear();
				return;
			}
			int num = ProductionTally.Produced.Length;
			if (_itemName == null)
			{
				_itemName = new string[num];
				ItemProto[] dataArray = ((ProtoSet<ItemProto>)(object)LDB.items).dataArray;
				foreach (ItemProto val in dataArray)
				{
					if (val != null && ((Proto)val).ID > 0 && ((Proto)val).ID < num)
					{
						_itemName[((Proto)val).ID] = ((Proto)val).name;
					}
				}
			}
			if (_negSeconds == null)
			{
				_negSeconds = new float[num];
			}
			long[] produced = ProductionTally.Produced;
			long[] consumed = ProductionTally.Consumed;
			long gameTick = GameMain.gameTick;
			if (_snaps.Count > 0 && gameTick <= _snaps[_snaps.Count - 1].tick)
			{
				_snaps.Clear();
			}
			DeficitMode value = _cfgMode.Value;
			int num2 = Math.Max(5, Math.Min(3600, _cfgWindowSeconds.Value));
			int num3 = -1;
			if (_snaps.Count > 0)
			{
				if (value == DeficitMode.Window)
				{
					long num4 = gameTick - (long)num2 * 60L;
					num3 = 0;
					for (int j = 0; j < _snaps.Count && _snaps[j].tick <= num4; j++)
					{
						num3 = j;
					}
				}
				else
				{
					num3 = _snaps.Count - 1;
				}
			}
			if (num3 < 0)
			{
				PushSnap(gameTick, produced, consumed, num);
				return;
			}
			Snap snap = _snaps[num3];
			long num5 = gameTick - snap.tick;
			if (num5 <= 0)
			{
				PushSnap(gameTick, produced, consumed, num);
				return;
			}
			float num6 = (float)num5 / 60f;
			Dictionary<int, long> dictionary = new Dictionary<int, long>(snap.ids.Length);
			Dictionary<int, long> dictionary2 = new Dictionary<int, long>(snap.ids.Length);
			for (int k = 0; k < snap.ids.Length; k++)
			{
				dictionary[snap.ids[k]] = snap.p[k];
				dictionary2[snap.ids[k]] = snap.c[k];
			}
			float value2 = _cfgMinMagnitude.Value;
			int num7 = Math.Max(1, _cfgSustainSeconds.Value);
			List<DefRow> list = new List<DefRow>();
			for (int l = 0; l < num; l++)
			{
				long num8 = produced[l];
				long num9 = consumed[l];
				long value3;
				long num10 = num8 - (dictionary.TryGetValue(l, out value3) ? value3 : 0);
				long value4;
				long num11 = num9 - (dictionary2.TryGetValue(l, out value4) ? value4 : 0);
				if (num10 == 0L && num11 == 0L)
				{
					_negSeconds[l] = 0f;
					continue;
				}
				float num12 = (float)((double)num10 / (double)num5 * 3600.0);
				float num13 = (float)((double)num11 / (double)num5 * 3600.0);
				float num14 = num12 - num13;
				if (num14 >= 0f - value2)
				{
					_negSeconds[l] = 0f;
					continue;
				}
				bool flagged;
				int streak;
				if (value == DeficitMode.Sustained)
				{
					_negSeconds[l] += 1f;
					flagged = _negSeconds[l] >= (float)num7;
					streak = (int)Math.Round(_negSeconds[l]);
				}
				else
				{
					flagged = true;
					streak = (int)Math.Round(num6);
				}
				list.Add(new DefRow
				{
					id = l,
					name = (_itemName[l] ?? ("#" + l)),
					prod = num12,
					cons = num13,
					net = num14,
					streak = streak,
					flagged = flagged
				});
			}
			PushSnap(gameTick, produced, consumed, num);
			long num15 = ((value == DeficitMode.Window) ? num2 : 2) + 120;
			long num16 = gameTick - num15 * 60;
			while (_snaps.Count > 1 && _snaps[0].tick < num16)
			{
				_snaps.RemoveAt(0);
			}
			list.Sort((DefRow x, DefRow y) => x.net.CompareTo(y.net));
			_hudRows = list.ToArray();
			_deficits = BuildDeficitsJson(gameTick, num5, list);
		}

		private void PushSnap(long tick, long[] P, long[] C, int len)
		{
			int num = 0;
			for (int i = 0; i < len; i++)
			{
				if (P[i] != 0L || C[i] != 0L)
				{
					num++;
				}
			}
			int[] array = new int[num];
			long[] array2 = new long[num];
			long[] array3 = new long[num];
			int num2 = 0;
			for (int j = 0; j < len; j++)
			{
				if (P[j] != 0L || C[j] != 0L)
				{
					array[num2] = j;
					array2[num2] = P[j];
					array3[num2] = C[j];
					num2++;
				}
			}
			_snaps.Add(new Snap
			{
				tick = tick,
				ids = array,
				p = array2,
				c = array3
			});
		}

		private string BuildDeficitsJson(long tick, long dt, List<DefRow> rows)
		{
			StringBuilder stringBuilder = new StringBuilder(2048);
			stringBuilder.Append("{\"version\":2,\"running\":true,\"gameTick\":").Append(tick).Append(",\"windowTicks\":")
				.Append(dt)
				.Append(",\"windowSec\":")
				.Append(Fmt((float)dt / 60f))
				.Append(",\"mode\":\"")
				.Append(_cfgMode.Value.ToString().ToLowerInvariant())
				.Append('"')
				.Append(",\"sustainSeconds\":")
				.Append(_cfgSustainSeconds.Value)
				.Append(",\"requestedWindowSec\":")
				.Append(Math.Max(5, Math.Min(3600, _cfgWindowSeconds.Value)))
				.Append(",\"minMagnitude\":")
				.Append(Fmt(_cfgMinMagnitude.Value))
				.Append(",\"items\":[");
			int num = Math.Min(rows.Count, 50);
			for (int i = 0; i < num; i++)
			{
				DefRow defRow = rows[i];
				if (i > 0)
				{
					stringBuilder.Append(',');
				}
				stringBuilder.Append("{\"id\":").Append(defRow.id).Append(",\"name\":\"")
					.Append(JsonEscape(defRow.name))
					.Append('"')
					.Append(",\"produce\":")
					.Append(Fmt(defRow.prod))
					.Append(",\"consume\":")
					.Append(Fmt(defRow.cons))
					.Append(",\"net\":")
					.Append(Fmt(defRow.net))
					.Append(",\"streak\":")
					.Append(defRow.streak)
					.Append(",\"flagged\":")
					.Append(defRow.flagged ? "true" : "false")
					.Append('}');
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private static string Fmt(float v)
		{
			return v.ToString("0.#", CultureInfo.InvariantCulture);
		}

		private string BuildConfig()
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder(256);
			stringBuilder.Append("{\"version\":2").Append(",\"mode\":\"").Append(_cfgMode.Value.ToString())
				.Append('"')
				.Append(",\"sustainSeconds\":")
				.Append(_cfgSustainSeconds.Value)
				.Append(",\"windowSeconds\":")
				.Append(_cfgWindowSeconds.Value)
				.Append(",\"minMagnitude\":")
				.Append(Fmt(_cfgMinMagnitude.Value))
				.Append(",\"hudEnabled\":")
				.Append(_cfgEnabled.Value ? "true" : "false")
				.Append(",\"toggleKey\":\"")
				.Append(JsonEscape(((object)_cfgToggle.Value/*cast due to .constrained prefix*/).ToString()))
				.Append('"')
				.Append(",\"showPending\":")
				.Append(_cfgShowPending.Value ? "true" : "false")
				.Append(",\"corner\":\"")
				.Append(_cfgCorner.Value.ToString())
				.Append('"')
				.Append(",\"scale\":")
				.Append(Fmt(_cfgScale.Value))
				.Append(",\"maxRows\":")
				.Append(_cfgMaxRows.Value)
				.Append(",\"hudX\":")
				.Append(Fmt(_cfgHudX.Value))
				.Append(",\"hudY\":")
				.Append(Fmt(_cfgHudY.Value))
				.Append(",\"dragged\":")
				.Append((_cfgHudX.Value >= 0f && _cfgHudY.Value >= 0f) ? "true" : "false")
				.Append('}');
			return stringBuilder.ToString();
		}

		private void ApplyConfig(string form)
		{
			if (string.IsNullOrEmpty(form))
			{
				return;
			}
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			string[] array = form.Split(new char[1] { '&' });
			foreach (string text in array)
			{
				int num = text.IndexOf('=');
				if (num <= 0)
				{
					continue;
				}
				string text2 = text.Substring(0, num);
				string text3 = Uri.UnescapeDataString(text.Substring(num + 1).Replace('+', ' ')).Trim();
				try
				{
					if (text2 == null)
					{
						continue;
					}
					switch (text2.Length)
					{
					case 4:
						switch (text2[3])
						{
						case 'e':
						{
							if (text2 == "mode" && Enum.TryParse<DeficitMode>(text3, ignoreCase: true, out var result4))
							{
								_cfgMode.Value = result4;
							}
							break;
						}
						case 'X':
						{
							if (text2 == "hudX" && float.TryParse(text3, NumberStyles.Float, invariantCulture, out var result3))
							{
								_cfgHudX.Value = result3;
							}
							break;
						}
						case 'Y':
						{
							if (text2 == "hudY" && float.TryParse(text3, NumberStyles.Float, invariantCulture, out var result2))
							{
								_cfgHudY.Value = result2;
							}
							break;
						}
						}
						break;
					case 14:
					{
						if (text2 == "sustainSeconds" && int.TryParse(text3, out var result8))
						{
							_cfgSustainSeconds.Value = Math.Max(1, result8);
						}
						break;
					}
					case 13:
					{
						if (text2 == "windowSeconds" && int.TryParse(text3, out var result5))
						{
							_cfgWindowSeconds.Value = Math.Max(5, Math.Min(3600, result5));
						}
						break;
					}
					case 12:
					{
						if (text2 == "minMagnitude" && float.TryParse(text3, NumberStyles.Float, invariantCulture, out var result))
						{
							_cfgMinMagnitude.Value = Math.Max(0f, result);
						}
						break;
					}
					case 7:
					{
						if (text2 == "maxRows" && int.TryParse(text3, out var result9))
						{
							_cfgMaxRows.Value = Math.Max(1, result9);
						}
						break;
					}
					case 5:
					{
						if (text2 == "scale" && float.TryParse(text3, NumberStyles.Float, invariantCulture, out var result7))
						{
							_cfgScale.Value = Mathf.Clamp(result7, 0.5f, 3f);
						}
						break;
					}
					case 6:
					{
						if (text2 == "corner" && Enum.TryParse<Corner>(text3, ignoreCase: true, out var result6))
						{
							_cfgCorner.Value = result6;
						}
						break;
					}
					case 10:
						if (text2 == "hudEnabled")
						{
							_cfgEnabled.Value = text3 == "true" || text3 == "1";
						}
						break;
					case 11:
						if (text2 == "showPending")
						{
							_cfgShowPending.Value = text3 == "true" || text3 == "1";
						}
						break;
					case 8:
					case 9:
						break;
					}
				}
				catch
				{
				}
			}
		}

		private void OnGUI()
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0439: Unknown result type (might be due to invalid IL or missing references)
			//IL_043e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0454: Unknown result type (might be due to invalid IL or missing references)
			//IL_0466: Unknown result type (might be due to invalid IL or missing references)
			//IL_02db: Unknown result type (might be due to invalid IL or missing references)
			//IL_04aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_048f: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_035b: Invalid comparison between Unknown and I4
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0517: Unknown result type (might be due to invalid IL or missing references)
			//IL_0529: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fd: Invalid comparison between Unknown and I4
			//IL_0363: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0324: Unknown result type (might be due to invalid IL or missing references)
			//IL_032d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0332: Unknown result type (might be due to invalid IL or missing references)
			//IL_0337: Unknown result type (might be due to invalid IL or missing references)
			//IL_0401: Unknown result type (might be due to invalid IL or missing references)
			//IL_0407: Invalid comparison between Unknown and I4
			//IL_057f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0569: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_066b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0667: Unknown result type (might be due to invalid IL or missing references)
			if (!_cfgEnabled.Value || !_hudVisible || !_inGame)
			{
				return;
			}
			DefRow[] hudRows = _hudRows;
			bool value = _cfgShowPending.Value;
			int num = 0;
			int num2 = 0;
			for (int i = 0; i < hudRows.Length; i++)
			{
				if (hudRows[i].flagged)
				{
					num2++;
					num++;
				}
				else if (value)
				{
					num++;
				}
			}
			if (_titleStyle == null)
			{
				_titleStyle = new GUIStyle(GUI.skin.label)
				{
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)3
				};
				_rowStyle = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)3
				};
			}
			if ((Object)(object)_px == (Object)null)
			{
				_px = new Texture2D(1, 1);
				_px.SetPixel(0, 0, Color.white);
				_px.Apply();
				((Object)_px).hideFlags = (HideFlags)61;
			}
			float num3 = Mathf.Clamp(_cfgScale.Value, 0.5f, 3f);
			int num4 = Mathf.RoundToInt(14f * num3);
			_titleStyle.fontSize = num4;
			_rowStyle.fontSize = num4;
			float num5 = 10f * num3;
			float num6 = 4f * num3;
			float rowH = num4 + 8;
			float num7 = 300f * num3;
			float num8 = 14f * num3;
			int num9 = Math.Max(1, _cfgMaxRows.Value);
			int num10 = ((num <= 0) ? 1 : Math.Min(num, num9));
			float num11 = num5 * 2f + rowH * (float)(num10 + 1);
			Corner value2 = _cfgCorner.Value;
			bool num12 = value2 == Corner.TopLeft || value2 == Corner.BottomLeft;
			bool flag = value2 == Corner.TopLeft || value2 == Corner.TopRight;
			float num13 = (num12 ? num8 : ((float)Screen.width - num7 - num8));
			float num14 = (flag ? num8 : ((float)Screen.height - num11 - num8));
			float num15 = (_dragging ? _hudX : ((_cfgHudX.Value >= 0f && _cfgHudY.Value >= 0f) ? _cfgHudX.Value : num13));
			float num16 = (_dragging ? _hudY : ((_cfgHudX.Value >= 0f && _cfgHudY.Value >= 0f) ? _cfgHudY.Value : num14));
			num15 = Mathf.Clamp(num15, 0f, Math.Max(0f, (float)Screen.width - num7));
			num16 = Mathf.Clamp(num16, 0f, Math.Max(0f, (float)Screen.height - num11));
			Event current = Event.current;
			if (current != null)
			{
				if ((int)current.type == 0 && current.button == 0)
				{
					Rect val = new Rect(num15, num16, num7, num11);
					if (((Rect)(ref val)).Contains(current.mousePosition))
					{
						_dragging = true;
						_hudX = num15;
						_hudY = num16;
						_dragOffset = current.mousePosition - new Vector2(num15, num16);
						current.Use();
						goto IL_0439;
					}
				}
				if (_dragging && (int)current.type == 3)
				{
					num15 = (_hudX = Mathf.Clamp(current.mousePosition.x - _dragOffset.x, 0f, Math.Max(0f, (float)Screen.width - num7)));
					num16 = (_hudY = Mathf.Clamp(current.mousePosition.y - _dragOffset.y, 0f, Math.Max(0f, (float)Screen.height - num11)));
					current.Use();
				}
				else if (_dragging && ((int)current.type == 1 || (int)current.rawType == 1))
				{
					_dragging = false;
					_cfgHudX.Value = _hudX;
					_cfgHudY.Value = _hudY;
					current.Use();
				}
			}
			goto IL_0439;
			IL_0439:
			Color color = GUI.color;
			GUI.color = new Color(0.04f, 0.05f, 0.07f, 0.93f);
			GUI.DrawTexture(new Rect(num15, num16, num7, num11), (Texture)(object)_px);
			GUI.color = ((num2 > 0) ? new Color(1f, 0.45f, 0.35f, 0.95f) : new Color(0.55f, 0.6f, 0.7f, 0.9f));
			GUI.DrawTexture(new Rect(num15, num16, num6, num11), (Texture)(object)_px);
			GUI.color = color;
			float tx = num15 + num5 + num6;
			float tw = num7 - num5 * 2f - num6;
			float num17 = num16 + num5;
			if (num == 0)
			{
				L(num17, "✓ No deficits", new Color(0.55f, 0.95f, 0.6f), _titleStyle);
				GUI.color = color;
				return;
			}
			L(num17, (num2 > 0) ? ("▲ Live deficits (" + num2 + ")") : "Dipping — none sustained", (num2 > 0) ? new Color(1f, 0.78f, 0.32f) : new Color(0.92f, 0.94f, 1f), _titleStyle);
			num17 += rowH;
			Color val2 = default(Color);
			((Color)(ref val2))..ctor(1f, 0.5f, 0.42f);
			Color val3 = default(Color);
			((Color)(ref val3))..ctor(0.9f, 0.92f, 0.97f);
			int num18 = 0;
			for (int j = 0; j < hudRows.Length; j++)
			{
				if (num18 >= num9)
				{
					break;
				}
				DefRow defRow = hudRows[j];
				if (defRow.flagged || value)
				{
					string text = defRow.name + "   " + defRow.net.ToString("0.0", CultureInfo.InvariantCulture) + "/min" + (defRow.flagged ? "" : ("  (" + defRow.streak + "s)"));
					L(num17, text, defRow.flagged ? val2 : val3, _rowStyle);
					num17 += rowH;
					num18++;
				}
			}
			GUI.color = color;
			void L(float y, string text2, Color col, GUIStyle st)
			{
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0074: Unknown result type (might be due to invalid IL or missing references)
				st.normal.textColor = new Color(0f, 0f, 0f, 0.9f);
				GUI.Label(new Rect(tx + 1.5f, y + 1.5f, tw, rowH), text2, st);
				st.normal.textColor = col;
				GUI.Label(new Rect(tx, y, tw, rowH), text2, st);
			}
		}

		private static string JsonEscape(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length + 8);
			foreach (char c in s)
			{
				if (c == '"' || c == '\\')
				{
					stringBuilder.Append('\\').Append(c);
				}
				else if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4"));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}

		private void OnDestroy()
		{
			_running = false;
			try
			{
				_cfgWatcher?.Dispose();
			}
			catch
			{
			}
			try
			{
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
			try
			{
				_listener?.Stop();
			}
			catch
			{
			}
			try
			{
				_listener?.Close();
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(FactoryProductionStat), "GameTick")]
	internal static class ProductionTally
	{
		internal static readonly long[] Produced = new long[12000];

		internal static readonly long[] Consumed = new long[12000];

		private static void Prefix(FactoryProductionStat __instance)
		{
			int[] productRegister = __instance.productRegister;
			int[] consumeRegister = __instance.consumeRegister;
			if (productRegister == null || consumeRegister == null)
			{
				return;
			}
			int num = Math.Min(Math.Min(productRegister.Length, consumeRegister.Length), Produced.Length);
			for (int i = 0; i < num; i++)
			{
				if (productRegister[i] != 0)
				{
					Produced[i] += productRegister[i];
				}
				if (consumeRegister[i] != 0)
				{
					Consumed[i] += consumeRegister[i];
				}
			}
		}
	}
}