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 _cfgEnabled; private ConfigEntry _cfgToggle; private ConfigEntry _cfgMode; private ConfigEntry _cfgSustainSeconds; private ConfigEntry _cfgWindowSeconds; private ConfigEntry _cfgMinMagnitude; private ConfigEntry _cfgMaxRows; private ConfigEntry _cfgScale; private ConfigEntry _cfgCorner; private ConfigEntry _cfgShowPending; private ConfigEntry _cfgHudX; private ConfigEntry _cfgHudY; private FileSystemWatcher _cfgWatcher; private string[] _itemName; private float[] _negSeconds; private readonly List _snaps = new List(); 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("HUD", "Enabled", true, "Show the in-game live-deficit HUD overlay."); _cfgToggle = ((BaseUnityPlugin)this).Config.Bind("HUD", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty()), "Hotkey to show/hide the HUD at runtime."); _cfgMode = ((BaseUnityPlugin)this).Config.Bind("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("Deficit", "SustainSeconds", 5, "Sustained mode: consecutive ~1s samples in deficit before a flag."); _cfgWindowSeconds = ((BaseUnityPlugin)this).Config.Bind("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("Deficit", "MinMagnitude", 1f, "Ignore deficits smaller than this many items/min (noise floor)."); _cfgMaxRows = ((BaseUnityPlugin)this).Config.Bind("HUD", "MaxRows", 12, "Maximum deficit rows drawn in the HUD."); _cfgScale = ((BaseUnityPlugin)this).Config.Bind("HUD", "Scale", 1f, "HUD size multiplier."); _cfgCorner = ((BaseUnityPlugin)this).Config.Bind("HUD", "Corner", Corner.TopRight, "Screen corner the HUD anchors to (used until you drag the HUD)."); _cfgShowPending = ((BaseUnityPlugin)this).Config.Bind("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("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("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 techStates = GameMain.history.techStates; if (techStates != null) { bool flag2 = true; foreach (KeyValuePair 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)(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)(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)(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)(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 dictionary = new Dictionary(snap.ids.Length); Dictionary dictionary2 = new Dictionary(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 list = new List(); 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 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(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(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]; } } } } }