Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of CasinoLedger v0.6.0
Mods/CasinoLedger.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using CasinoLedger; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppFishNet.Object; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Casino; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.UI; using Il2CppSystem; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using ModSettings; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Main), "Casino Ledger", "0.6.0", "holyfurries", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("CasinoLedger")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.6.0.0")] [assembly: AssemblyInformationalVersion("0.6.0+4f4d3c52551c5552fb12b24f1add712ef6e1936b")] [assembly: AssemblyProduct("CasinoLedger")] [assembly: AssemblyTitle("CasinoLedger")] [assembly: AssemblyVersion("0.6.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace CasinoLedger { public readonly record struct RoundReport(Round round, Totals lifetime, Totals day); public static class CasinoStats { internal static readonly Ledger ledger = new Ledger(); internal static Action<Exception>? subscriber_failed; public static event Action<RoundReport>? round_settled; public static string player_key(string player_code) { if (string.IsNullOrEmpty(player_code) || player_code.Length > 128) { throw new ArgumentOutOfRangeException("player_code"); } return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(player_code))); } internal static string world_key(int seed, string? organisation_name) { string text = organisation_name ?? ""; if (text.Length > 128) { throw new ArgumentOutOfRangeException("organisation_name"); } string text2 = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes($"{seed}|{text}"))).Substring(0, 16); if (text2.Length != 16) { throw new InvalidOperationException("World key has the wrong length."); } return text2; } public static Totals lifetime(string player_key, CasinoGame? game = null) { return ledger.lifetime(player_key, game); } public static Totals day(string player_key, CasinoGame? game = null) { return ledger.day(player_key, game); } public static string format_money(double value, bool signed) { return Ledger.format_money(value, signed); } internal static void record(Round round) { ledger.settle(round); RoundReport obj = new RoundReport(round, ledger.lifetime(round.player_key), ledger.day(round.player_key)); if (CasinoStats.round_settled == null) { return; } Delegate[] invocationList = CasinoStats.round_settled.GetInvocationList(); foreach (Delegate obj2 in invocationList) { try { ((Action<RoundReport>)obj2)(obj); } catch (Exception obj3) { subscriber_failed?.Invoke(obj3); } } } } internal static class DaySummary { private const int line_count_max = 8; private const string ui_element_name = "CasinoLedgerDaySummary"; private const float input_delay_seconds = 0.4f; private const float content_width = 1120f; private const float panel_height = 330f; private const float chart_width = 640f; private const float chart_label_width = 210f; private const float chart_padding = 22f; private const float label_gap = 28f; private const float layout_scale = 0.75f; private const float layout_center_y = 468f; private static readonly Color[] line_colors = (Color[])(object)new Color[8] { new Color(0.91f, 0.45f, 0.78f), new Color(0.95f, 0.6f, 0.18f), new Color(0.35f, 0.75f, 0.98f), new Color(0.58f, 0.9f, 0.35f), new Color(0.98f, 0.85f, 0.25f), new Color(0.7f, 0.55f, 0.98f), new Color(0.98f, 0.45f, 0.4f), new Color(0.45f, 0.92f, 0.82f) }; private static readonly string[] game_names = new string[3] { "Blackjack", "Ride the Bus", "Slots" }; private static GameObject? canvas_object; private static float input_open_seconds; public static bool showing => (Object)(object)canvas_object != (Object)null; public static void reset() { if ((Object)(object)canvas_object == (Object)null) { return; } Object.Destroy((Object)(object)canvas_object); canvas_object = null; if (PlayerSingleton<PlayerCamera>.InstanceExists) { PlayerCamera instance = PlayerSingleton<PlayerCamera>.Instance; instance.RemoveActiveUIElement("CasinoLedgerDaySummary"); if (instance.activeUIElements.Count == 0) { instance.LockMouse(true); } } } public static void update(float now_seconds) { if (!((Object)(object)canvas_object == (Object)null)) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; if (!(now_seconds < input_open_seconds) && (GameInput.GetButtonDown((ButtonCode)11) || Input.GetKeyDown((KeyCode)27))) { reset(); } } } public static void show(Ledger ledger, int day_number, float now_seconds) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_010e: 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_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0172: 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_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Unknown result type (might be due to invalid IL or missing references) //IL_0346: 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) if (ledger.day_round_count <= 0) { throw new ArgumentOutOfRangeException("ledger", "No rounds to summarize."); } reset(); string[] array = new string[8]; int num = ledger.day_player_keys(array); Totals totals = default(Totals); for (int i = 0; i < num; i++) { totals = totals.add(ledger.day(array[i])); } canvas_object = Ui.canvas("CasinoLedgerDaySummary", 29500); canvas_object.AddComponent<GraphicRaycaster>(); input_open_seconds = now_seconds + 0.4f; if (PlayerSingleton<PlayerCamera>.InstanceExists) { PlayerSingleton<PlayerCamera>.Instance.AddActiveUIElement("CasinoLedgerDaySummary"); PlayerSingleton<PlayerCamera>.Instance.FreeMouse(true); } GameObject val = new GameObject("Backdrop"); val.transform.SetParent(canvas_object.transform, false); Image obj = val.AddComponent<Image>(); ((Graphic)obj).color = Color.black; ((Graphic)obj).raycastTarget = false; Ui.stretch(((Graphic)obj).rectTransform, 0f); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 1f); GameObject val3 = new GameObject("Layout"); val3.transform.SetParent(canvas_object.transform, false); RectTransform obj2 = val3.AddComponent<RectTransform>(); Ui.place(obj2, new Vector2(0.5f, 0.5f), val2, new Vector2(0f, 351f), new Vector2(1120f, 0f)); ((Transform)obj2).localScale = new Vector3(0.75f, 0.75f, 1f); TextMeshProUGUI obj3 = Ui.text((Transform)(object)obj2, "Title", 36f, (TextAlignmentOptions)514); ((TMP_Text)obj3).text = $"Casino stats for day {day_number}"; Ui.place(((TMP_Text)obj3).rectTransform, val2, val2, new Vector2(0f, -130f), new Vector2(1120f, 80f)); RectTransform obj4 = Ui.outlined_panel((Transform)(object)obj2, "Totals"); Ui.place(obj4, val2, val2, new Vector2(0f, -250f), new Vector2(1120f, 64f)); strip_cell(obj4, 0, "Wagered", Ledger.format_money(totals.wagered, signed: false), Color.white); strip_cell(obj4, 1, "Returned", Ledger.format_money(totals.returned, signed: false), Color.white); strip_cell(obj4, 2, "Profit", Ledger.format_money(totals.net, signed: true), Ui.money_color(totals.net)); RectTransform obj5 = Ui.outlined_panel((Transform)(object)obj2, "Chart"); Ui.place(obj5, val2, new Vector2(0f, 1f), new Vector2(-560f, -340f), new Vector2(640f, 330f)); draw_chart(obj5, ledger, array, num); RectTransform obj6 = Ui.outlined_panel((Transform)(object)obj2, "Breakdown"); float num2 = 454f; Ui.place(obj6, val2, new Vector2(1f, 1f), new Vector2(560f, -340f), new Vector2(num2, 330f)); draw_breakdown(obj6, num2, ledger, array, num, totals); RectTransform obj7 = Ui.panel((Transform)(object)obj2, "Continue", Ui.loss); Ui.place(obj7, val2, val2, new Vector2(0f, -730f), new Vector2(380f, 76f)); TextMeshProUGUI val4 = Ui.text((Transform)(object)obj7, "Label", 28f, (TextAlignmentOptions)514); ((TMP_Text)val4).text = "Continue"; Image component = ((Component)obj7).GetComponent<Image>(); ((Graphic)component).raycastTarget = true; Button obj8 = ((Component)obj7).gameObject.AddComponent<Button>(); ((Selectable)obj8).targetGraphic = (Graphic)(object)component; ((UnityEvent)obj8.onClick).AddListener(UnityAction.op_Implicit((Action)reset)); Ui.stretch(((TMP_Text)val4).rectTransform, 0f); } private static void strip_cell(RectTransform strip, int index, string label, string value, Color value_color) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) float num = 373.33334f; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(0f, 1f); TextMeshProUGUI obj = Ui.text((Transform)(object)strip, label, 26f, (TextAlignmentOptions)513); ((TMP_Text)obj).text = label; Ui.place(((TMP_Text)obj).rectTransform, val, val, new Vector2((float)index * num + 24f, 0f), new Vector2(num - 48f, 64f)); TextMeshProUGUI obj2 = Ui.text((Transform)(object)strip, label + "Value", 26f, (TextAlignmentOptions)516); ((TMP_Text)obj2).text = value; ((Graphic)obj2).color = value_color; Ui.place(((TMP_Text)obj2).rectTransform, val, val, new Vector2((float)index * num + 24f, 0f), new Vector2(num - 48f, 64f)); } private static void draw_breakdown(RectTransform parent, float width, Ledger ledger, string[] keys, int key_count, Totals group) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: 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_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_024e: 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_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) Vector2 top_left = new Vector2(0f, 1f); int row = 0; string text = ""; string text2 = ""; double num = 0.0; double num2 = 0.0; for (int i = 0; i < 3; i++) { Totals totals = default(Totals); for (int j = 0; j < key_count; j++) { totals = totals.add(ledger.day(keys[j], (CasinoGame)i)); } if (totals.round_count > 0) { add_row(game_names[i], Ledger.format_money(totals.net, signed: true), Ui.money_color(totals.net)); } } for (int k = 0; k < key_count; k++) { Totals totals2 = ledger.day(keys[k]); if (totals2.win_largest > num) { num = totals2.win_largest; text = ledger.name(keys[k]); } if (totals2.loss_largest > num2) { num2 = totals2.loss_largest; text2 = ledger.name(keys[k]); } } if (num > 0.0) { add_row("Best round (" + text + ")", Ledger.format_money(num, signed: true), Ui.profit); } if (num2 > 0.0) { add_row("Worst round (" + text2 + ")", Ledger.format_money(0.0 - num2, signed: true), Ui.loss); } Ui.place(Ui.panel((Transform)(object)parent, "Divider", new Color(1f, 1f, 1f, 0.35f)), new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(2f, 70f), new Vector2(width - 4f, 2f)); TextMeshProUGUI obj = Ui.text((Transform)(object)parent, "Rounds", 28f, (TextAlignmentOptions)513); ((TMP_Text)obj).text = "Rounds"; Ui.place(((TMP_Text)obj).rectTransform, Vector2.zero, Vector2.zero, new Vector2(24f, 8f), new Vector2(width - 48f, 56f)); TextMeshProUGUI obj2 = Ui.text((Transform)(object)parent, "RoundCount", 28f, (TextAlignmentOptions)516); ((TMP_Text)obj2).text = $"{group.round_count} ({group.win_count}W {group.loss_count}L)"; Ui.place(((TMP_Text)obj2).rectTransform, Vector2.zero, Vector2.zero, new Vector2(24f, 8f), new Vector2(width - 48f, 56f)); void add_row(string label, string value, Color color) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) TextMeshProUGUI obj3 = Ui.text((Transform)(object)parent, "Label", 22f, (TextAlignmentOptions)513); ((TMP_Text)obj3).text = label; Ui.place(((TMP_Text)obj3).rectTransform, top_left, top_left, new Vector2(24f, 0f - (14f + (float)row * 46f)), new Vector2(width - 48f, 46f)); TextMeshProUGUI obj4 = Ui.text((Transform)(object)parent, "Value", 24f, (TextAlignmentOptions)516); ((TMP_Text)obj4).text = value; ((Graphic)obj4).color = color; Ui.place(((TMP_Text)obj4).rectTransform, top_left, top_left, new Vector2(24f, 0f - (14f + (float)row * 46f)), new Vector2(width - 48f, 46f)); row++; } } private static void draw_chart(RectTransform parent, Ledger ledger, string[] keys, int key_count) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Unknown result type (might be due to invalid IL or missing references) //IL_03ea: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0492: Unknown result type (might be due to invalid IL or missing references) //IL_04a1: Unknown result type (might be due to invalid IL or missing references) //IL_04cb: Unknown result type (might be due to invalid IL or missing references) //IL_04da: Unknown result type (might be due to invalid IL or missing references) float plot_width = 386f; float plot_height = 286f; SeriesPoint[] array = new SeriesPoint[64]; double net_min = 0.0; double num = 0.0; for (int i = 0; i < key_count; i++) { int num2 = ledger.day_series(keys[i], array); for (int j = 0; j < num2; j++) { net_min = Math.Min(net_min, array[j].day_net); num = Math.Max(num, array[j].day_net); } } double net_span = Math.Max(num - net_min, 1.0); Ui.place(Ui.panel((Transform)(object)parent, "Baseline", new Color(1f, 1f, 1f, 0.25f)), Vector2.zero, new Vector2(0f, 0.5f), plot(0, 0.0), new Vector2(plot_width, 2f)); (int, Vector2, float)[] array2 = new(int, Vector2, float)[8]; Vector2 val4 = default(Vector2); for (int k = 0; k < key_count; k++) { int num3 = ledger.day_series(keys[k], array); Vector2 val = plot(0, 0.0); for (int l = 0; l < num3; l++) { Vector2 val2 = plot(array[l].day_round_index, array[l].day_net); Vector2 val3 = val2 - val; RectTransform obj = Ui.panel((Transform)(object)parent, "Segment", line_colors[k]); Ui.place(obj, Vector2.zero, new Vector2(0f, 0.5f), val, new Vector2(((Vector2)(ref val3)).magnitude + 2f, 5f)); ((Transform)obj).localRotation = Quaternion.Euler(0f, 0f, Mathf.Atan2(val3.y, val3.x) * 57.29578f); val = val2; } ((Vector2)(ref val4))..ctor(22f + plot_width, val.y); Ui.place(Ui.panel((Transform)(object)parent, "Tail", line_colors[k]), Vector2.zero, new Vector2(0f, 0.5f), val, new Vector2(val4.x - val.x + 2f, 5f)); array2[k] = (k, val4, val4.y); } Array.Sort(array2, 0, key_count, Comparer<(int, Vector2, float)>.Create(((int key_index, Vector2 end, float label_y) left, (int key_index, Vector2 end, float label_y) right) => right.end.y.CompareTo(left.end.y))); for (int num4 = 0; num4 < key_count; num4++) { if (num4 > 0) { array2[num4].Item3 = Math.Min(array2[num4].Item3, array2[num4 - 1].Item3 - 28f); } } float val5 = 22f; for (int num5 = key_count - 1; num5 >= 0; num5--) { array2[num5].Item3 = Math.Max(array2[num5].Item3, val5); val5 = array2[num5].Item3 + 28f; } for (int num6 = 0; num6 < key_count; num6++) { string player_key = keys[array2[num6].Item1]; double net = ledger.day(player_key).net; Ui.place(Ui.panel((Transform)(object)parent, "Dot", line_colors[array2[num6].Item1]), Vector2.zero, new Vector2(0.5f, 0.5f), array2[num6].Item2, new Vector2(16f, 16f)); TextMeshProUGUI obj2 = Ui.text((Transform)(object)parent, "Label", 19f, (TextAlignmentOptions)513); string value = ColorUtility.ToHtmlStringRGB(Ui.money_color(net)); ((TMP_Text)obj2).text = $"{ledger.name(player_key)} <color=#{value}>{Ledger.format_money(net, signed: true)}</color>"; Ui.place(((TMP_Text)obj2).rectTransform, Vector2.zero, new Vector2(0f, 0.5f), new Vector2(array2[num6].Item2.x + 14f, array2[num6].Item3), new Vector2(202f, 28f)); } Vector2 plot(int day_round_index, double day_net) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) return new Vector2(22f + plot_width * (float)day_round_index / (float)ledger.day_round_count, 22f + plot_height * (float)((day_net - net_min) / net_span)); } } } internal static class Hooks { private sealed class Table { public IntPtr pointer; public bool armed; } private struct Spin { public IntPtr machine; public Round round; public float deadline_seconds; } private readonly record struct Settlement(Table? table, CasinoGameController? game, float stake, float cash_before); private const float spin_settle_seconds_max = 8f; private const float spin_repeat_seconds = 1f; private static readonly Table[] tables = new Table[32]; private static readonly Spin[] spins = new Spin[32]; private static string ledger_path = ""; private static bool failed; private static bool tracking { get { if (Main.ready && !failed) { return ledger_path.Length > 0; } return false; } } public static void install(Harmony harmony) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_0092: 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_00b4: Expected O, but got Unknown //IL_00b4: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown for (int i = 0; i < tables.Length; i++) { tables[i] = new Table(); } Type[] array = new Type[2] { typeof(BlackjackGameController), typeof(RTBGameController) }; foreach (Type type in array) { harmony.Patch((MethodBase)AccessTools.Method(type, "RpcLogic___AddPlayerToCurrentRound_3323014238", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Hooks), "card_round_begin", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(type, "RemoveLocalPlayerFromGame", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Hooks), "card_round_before_settlement", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Hooks), "card_round_after_settlement", (Type[])null), (HarmonyMethod)null); } harmony.Patch((MethodBase)AccessTools.Method(typeof(SlotMachine), "RpcLogic___StartSpin_2659526290", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Hooks), "spin_begin", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(SlotMachine), "DisplayOutcome", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Hooks), "spin_outcome_displayed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(DailySummary), "Close", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Hooks), "day_closed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static void reset() { Sync.reset(); Table[] array = tables; foreach (Table obj in array) { obj.pointer = IntPtr.Zero; obj.armed = false; } Array.Clear(spins, 0, spins.Length); failed = false; ledger_path = ""; CasinoStats.ledger.clear(); } public static void update(float now_seconds) { if (failed) { return; } try { if (ledger_path.Length == 0) { load(); return; } for (int i = 0; i < spins.Length; i++) { if (failed) { break; } if (spins[i].machine != IntPtr.Zero && now_seconds >= spins[i].deadline_seconds) { spin_settle(i); } } if (!failed) { Sync.flush(now_seconds); } } catch (Exception error) { pause(error); } } private static void card_round_begin(CasinoGameController __instance, NetworkObject __0) { if (!tracking || (Object)(object)Player.Local == (Object)null || (Object)(object)__0 == (Object)null || (Object)(object)__0 != (Object)(object)((NetworkBehaviour)Player.Local).NetworkObject) { return; } try { for (int i = 0; i < tables.Length; i++) { if (!(tables[i].pointer != ((Il2CppObjectBase)__instance).Pointer) || !(tables[i].pointer != IntPtr.Zero)) { tables[i].pointer = ((Il2CppObjectBase)__instance).Pointer; tables[i].armed = true; break; } } } catch (Exception error) { pause(error); } } private static void card_round_before_settlement(CasinoGameController __instance, out Settlement __state) { __state = default(Settlement); if (!tracking || !NetworkSingleton<MoneyManager>.InstanceExists || (Object)(object)Player.Local == (Object)null) { return; } try { Table[] array = tables; foreach (Table table in array) { if (!(table.pointer != ((Il2CppObjectBase)__instance).Pointer) && table.armed) { table.armed = false; __state = new Settlement(table, __instance, __instance.LocalPlayerBet, NetworkSingleton<MoneyManager>.Instance.cashBalance); break; } } } catch (Exception error) { pause(error); } } private static Exception? card_round_after_settlement(Exception? __exception, Settlement __state) { if (__exception != null || __state.table == null || (Object)(object)__state.game == (Object)null || (Object)(object)Player.Local == (Object)null) { return __exception; } try { CasinoGame game = ((!((Object)(object)((Il2CppObjectBase)__state.game).TryCast<BlackjackGameController>() != (Object)null)) ? CasinoGame.RideTheBus : CasinoGame.Blackjack); float num = Math.Max(NetworkSingleton<MoneyManager>.Instance.cashBalance - __state.cash_before, 0f); settle_local(new Round(CasinoStats.player_key(Player.Local.PlayerCode), Ledger.safe_name(Player.Local.PlayerName), game, __state.stake, num)); } catch (Exception error) { pause(error); } return __exception; } private static void remote_round(Round round) { if (!tracking || (Object)(object)Player.Local == (Object)null || round.player_key == CasinoStats.player_key(Player.Local.PlayerCode)) { return; } try { settle(round); } catch (ArgumentOutOfRangeException ex) { MelonLogger.Warning("Casino Ledger: Ignored an out of range remote round: " + ex.Message); } catch (Exception error) { pause(error); } } private static void spin_begin(SlotMachine __instance, NetworkConnection __0, Il2CppStructArray<ESymbol> __1, int __2) { //IL_00ec: Unknown result type (might be due to invalid IL or missing references) if (!tracking || __0 == (NetworkConnection)null || __1 == null) { return; } try { Player local = Player.Local; if ((Object)(object)local == (Object)null || ((NetworkBehaviour)local).Owner == (NetworkConnection)null || ((NetworkBehaviour)local).Owner.ClientId != __0.ClientId) { return; } int num = -1; for (int i = 0; i < spins.Length; i++) { if (spins[i].machine == ((Il2CppObjectBase)__instance).Pointer) { if (8f - (spins[i].deadline_seconds - Time.unscaledTime) < 1f) { return; } spin_settle(i); if (failed) { return; } } if (num < 0 && spins[i].machine == IntPtr.Zero) { num = i; } } if (num < 0) { throw new InvalidOperationException("More concurrent slot spins than tracked machines."); } int winAmount = __instance.GetWinAmount(__instance.EvaluateOutcome(__1), __2); spins[num] = new Spin { machine = ((Il2CppObjectBase)__instance).Pointer, round = new Round(CasinoStats.player_key(local.PlayerCode), Ledger.safe_name(local.PlayerName), CasinoGame.Slots, __2, winAmount), deadline_seconds = Time.unscaledTime + 8f }; } catch (Exception error) { pause(error); } } private static void spin_outcome_displayed(SlotMachine __instance) { if (failed) { return; } for (int i = 0; i < spins.Length; i++) { if (spins[i].machine == ((Il2CppObjectBase)__instance).Pointer) { spin_settle(i); } } } private static void spin_settle(int index) { Round round = spins[index].round; spins[index] = default(Spin); try { settle_local(round); } catch (Exception error) { pause(error); } } private static void day_closed() { if (!tracking) { return; } try { for (int i = 0; i < spins.Length; i++) { if (failed) { break; } if (spins[i].machine != IntPtr.Zero) { spin_settle(i); } } if (!failed) { Main.end_day(); Sync.send_totals(); } } catch (Exception error) { pause(error); } } private static void settle_local(Round round) { settle(round); Sync.send_round(round); } private static void settle(Round round) { CasinoStats.record(round); save(); MelonLogger.Msg($"Casino Ledger: {round.player_name} {round.game} stake={round.stake:F2} returned={round.returned:F2} day_net={CasinoStats.ledger.day(round.player_key).net:F2}"); } private static void load() { if (!NetworkSingleton<GameManager>.InstanceExists) { return; } string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CasinoLedger"); Directory.CreateDirectory(text); string text2 = CasinoStats.world_key(NetworkSingleton<GameManager>.Instance.seed, NetworkSingleton<GameManager>.Instance.OrganisationName); string text3 = Path.Combine(text, text2 + ".txt"); if (File.Exists(text3)) { if (new FileInfo(text3).Length > 65536) { throw new InvalidDataException("Casino ledger exceeds size limit."); } CasinoStats.ledger.load(File.ReadAllText(text3)); } else { CasinoStats.ledger.clear(); } ledger_path = text3; Sync.round_received = remote_round; Sync.totals_changed = delegate { if (!tracking) { return; } try { save(); } catch (Exception error) { pause(error); } }; Sync.connect(text2); MelonLogger.Msg("Casino Ledger: Tracking this save in " + Path.GetFileName(text3) + "."); } private static void save() { string text = ledger_path + ".tmp"; File.WriteAllText(text, CasinoStats.ledger.serialize()); File.Move(text, ledger_path, overwrite: true); } internal static void pause(Exception error) { failed = true; Sync.reset(); MelonLogger.Warning("Casino Ledger: Tracking paused for this scene: " + error.Message); } } internal static class Hud { private readonly record struct Standing(string name, double net); private const int row_count_max = 8; private static readonly Comparer<Standing> by_net_descending = Comparer<Standing>.Create((Standing left, Standing right) => right.net.CompareTo(left.net)); private static readonly Standing[] standings = new Standing[8]; private static readonly TextMeshProUGUI?[] amounts = (TextMeshProUGUI?[])(object)new TextMeshProUGUI[8]; private static readonly TextMeshProUGUI?[] names = (TextMeshProUGUI?[])(object)new TextMeshProUGUI[8]; private static GameObject? canvas_object; private static RectTransform? panel; private static float scale = 1f; private static float panel_width => 160f * scale; private static float row_height => 19f * scale; private static float padding => 7f * scale; private static float amount_width => 68f * scale; public static void reset() { if ((Object)(object)canvas_object != (Object)null) { Object.Destroy((Object)(object)canvas_object); } canvas_object = null; panel = null; Array.Clear(amounts, 0, amounts.Length); Array.Clear(names, 0, names.Length); } public static void refresh(bool visible, Vector2 offset, float scale_setting) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_012d: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) offset.x = (float.IsFinite(offset.x) ? Math.Clamp(offset.x, 0f, 1600f) : 13f); offset.y = (float.IsFinite(offset.y) ? Math.Clamp(offset.y, 0f, 900f) : 420f); float num = (float.IsFinite(scale_setting) ? Math.Clamp(scale_setting, 0.5f, 3f) : 1f); if (num != scale) { reset(); scale = num; } bool flag = Singleton<HUD>.InstanceExists && (Object)(object)Singleton<HUD>.Instance.canvas != (Object)null && ((Behaviour)Singleton<HUD>.Instance.canvas).enabled; int num2 = ((visible && flag) ? collect() : 0); if (num2 == 0) { if ((Object)(object)canvas_object != (Object)null) { canvas_object.SetActive(false); } return; } if ((Object)(object)canvas_object == (Object)null) { build(); } if ((Object)(object)panel == (Object)null) { throw new InvalidOperationException("Casino HUD panel missing after build."); } canvas_object.SetActive(true); Ui.place(panel, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(0f - offset.x, 0f - offset.y), new Vector2(panel_width, (float)num2 * row_height + padding * 2f)); for (int i = 0; i < 8; i++) { TextMeshProUGUI val = amounts[i]; TextMeshProUGUI val2 = names[i]; ((Component)val).gameObject.SetActive(i < num2); ((Component)val2).gameObject.SetActive(i < num2); if (i < num2) { ((TMP_Text)val).text = Ledger.format_money(standings[i].net, signed: true); ((Graphic)val).color = Ui.money_color(standings[i].net); ((TMP_Text)val2).text = standings[i].name; } } } private static int collect() { int num = 0; int num2 = Math.Min(Player.PlayerList.Count, 32); for (int i = 0; i < num2; i++) { if (num >= 8) { break; } Player val = Player.PlayerList[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(val.PlayerCode)) { Totals totals = CasinoStats.ledger.lifetime(CasinoStats.player_key(val.PlayerCode)); standings[num++] = new Standing(Ledger.safe_name(val.PlayerName), totals.net); } } Array.Sort(standings, 0, num, by_net_descending); return num; } private static void build() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) canvas_object = Ui.canvas("CasinoLedgerHud", 29000); panel = Ui.outlined_panel(canvas_object.transform, "Standings", 0.75f, 6f * scale); for (int i = 0; i < 8; i++) { float num = 0f - (padding + (float)i * row_height); TextMeshProUGUI val = Ui.text((Transform)(object)panel, "Amount", 12f * scale, (TextAlignmentOptions)513); Ui.place(((TMP_Text)val).rectTransform, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(padding + 3f * scale, num), new Vector2(amount_width, row_height)); TextMeshProUGUI val2 = Ui.text((Transform)(object)panel, "Name", 11f * scale, (TextAlignmentOptions)516); Ui.place(((TMP_Text)val2).rectTransform, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(0f - (padding + 3f * scale), num), new Vector2(panel_width - amount_width - padding * 2f - 10f * scale, row_height)); amounts[i] = val; names[i] = val2; } } } public enum CasinoGame : byte { Blackjack, RideTheBus, Slots } public readonly record struct Round(string player_key, string player_name, CasinoGame game, double stake, double returned) { public double net => Math.Round(returned - stake, 2); } public readonly record struct Totals(int round_count, int win_count, int loss_count, double wagered, double returned, double win_largest, double loss_largest) { public double net => Math.Round(returned - wagered, 2); public int push_count => round_count - win_count - loss_count; public Totals add(Totals other) { return checked(new Totals(round_count + other.round_count, win_count + other.win_count, loss_count + other.loss_count, wagered + other.wagered, returned + other.returned, Math.Max(win_largest, other.win_largest), Math.Max(loss_largest, other.loss_largest))); } } public readonly record struct SeriesPoint(int day_round_index, double day_net); public sealed class Ledger { private sealed class PlayerRecord { public string key = ""; public string name = ""; public readonly Totals[] lifetime = new Totals[3]; public readonly Totals[] day = new Totals[3]; public readonly SeriesPoint[] series = new SeriesPoint[64]; public int series_point_count; } public const int player_count_max = 32; public const int series_point_count_max = 64; public const int game_count = 3; public const int round_count_max = 22369621; public const int row_length_max = 512; public const double stake_max = 1000000.0; public const double returned_max = 100000000.0; public const double total_max = 1000000000000.0; private const string header = "casino-ledger 1"; private readonly PlayerRecord[] players = new PlayerRecord[32]; private int player_count; public int day_round_count { get; private set; } public Ledger() { for (int i = 0; i < players.Length; i++) { players[i] = new PlayerRecord(); } } public void settle(Round round) { if (string.IsNullOrEmpty(round.player_key) || round.player_key.Length > 64) { throw new ArgumentOutOfRangeException("round", "player_key"); } if ((int)round.game >= 3) { throw new ArgumentOutOfRangeException("round", "game"); } if (!double.IsFinite(round.stake) || round.stake <= 0.0 || round.stake > 1000000.0) { throw new ArgumentOutOfRangeException("round", "stake"); } if (!double.IsFinite(round.returned) || round.returned < 0.0 || round.returned > 100000000.0) { throw new ArgumentOutOfRangeException("round", "returned"); } PlayerRecord playerRecord = find(round.player_key) ?? claim(round.player_key); double net = round.net; Totals other = new Totals(1, (net > 0.0) ? 1 : 0, (net < 0.0) ? 1 : 0, round.stake, round.returned, Math.Max(net, 0.0), Math.Max(0.0 - net, 0.0)); Totals totals = playerRecord.lifetime[(uint)round.game].add(other); if (totals.round_count > 22369621) { throw new OverflowException("Casino round count exceeds the tracked maximum."); } if (totals.wagered > 1000000000000.0 || totals.returned > 1000000000000.0) { throw new OverflowException("Casino totals exceed the tracked maximum."); } playerRecord.lifetime[(uint)round.game] = totals; playerRecord.day[(uint)round.game] = playerRecord.day[(uint)round.game].add(other); playerRecord.name = safe_name(round.player_name); day_round_count++; if (playerRecord.series_point_count == 64) { for (int i = 0; i < 32; i++) { playerRecord.series[i] = playerRecord.series[i * 2 + 1]; } playerRecord.series_point_count = 32; } playerRecord.series[playerRecord.series_point_count++] = new SeriesPoint(day_round_count, sum(playerRecord.day).net); assert(playerRecord.series_point_count <= 64, "series bounded"); assert(sum(playerRecord.lifetime).round_count >= sum(playerRecord.day).round_count, "day is a subset of lifetime"); } public void start_day() { for (int i = 0; i < player_count; i++) { Array.Clear(players[i].day, 0, 3); players[i].series_point_count = 0; } day_round_count = 0; } public void clear() { player_count = 0; day_round_count = 0; } public Totals lifetime(string player_key, CasinoGame? game = null) { return pick(find(player_key)?.lifetime, game); } public Totals day(string player_key, CasinoGame? game = null) { return pick(find(player_key)?.day, game); } public string name(string player_key) { return find(player_key)?.name ?? ""; } public int day_series(string player_key, Span<SeriesPoint> target) { PlayerRecord playerRecord = find(player_key); if (playerRecord == null) { return 0; } int num = Math.Min(playerRecord.series_point_count, target.Length); playerRecord.series.AsSpan(0, num).CopyTo(target); return num; } public int day_player_keys(Span<string> target) { int num = 0; for (int i = 0; i < player_count; i++) { if (num >= target.Length) { break; } if (players[i].series_point_count > 0) { target[num++] = players[i].key; } } return num; } public string serialize() { StringBuilder stringBuilder = new StringBuilder("casino-ledger 1").Append('\n'); string[] array = new string[96]; int num = lifetime_rows(array); for (int i = 0; i < num; i++) { stringBuilder.Append(array[i]).Append('\n'); } return stringBuilder.ToString(); } public int lifetime_rows(Span<string> target) { int num = 0; for (int i = 0; i < player_count; i++) { for (int j = 0; j < 3; j++) { if (num >= target.Length) { break; } Totals totals = players[i].lifetime[j]; if (totals.round_count != 0 || totals.wagered != 0.0 || totals.returned != 0.0) { target[num++] = string.Join(' ', players[i].key, j.ToString(CultureInfo.InvariantCulture), totals.round_count.ToString(CultureInfo.InvariantCulture), totals.win_count.ToString(CultureInfo.InvariantCulture), totals.loss_count.ToString(CultureInfo.InvariantCulture), number(totals.wagered), number(totals.returned), number(totals.win_largest), number(totals.loss_largest)); } } } return num; } public void load(string text) { if (text == null || text.Length > 65536) { throw new FormatException("Casino ledger exceeds size limit."); } string[] array = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0 || array[0] != "casino-ledger 1") { throw new FormatException("Unknown casino ledger header."); } if (array.Length > 97) { throw new FormatException("Casino ledger has too many rows."); } Ledger ledger = new Ledger(); for (int i = 1; i < array.Length; i++) { ledger.replace_lifetime_row(array[i]); } Array.Copy(ledger.players, players, players.Length); player_count = ledger.player_count; day_round_count = 0; } public bool replace_lifetime_row(string row) { if (row == null || row.Length > 512) { throw new FormatException("Casino ledger row exceeds size limit."); } string[] array = row.Split(' '); if (array.Length != 9) { throw new FormatException("Casino ledger row has the wrong field count."); } int num = int.Parse(array[1], CultureInfo.InvariantCulture); Totals totals = new Totals(int.Parse(array[2], CultureInfo.InvariantCulture), int.Parse(array[3], CultureInfo.InvariantCulture), int.Parse(array[4], CultureInfo.InvariantCulture), parse(array[5]), parse(array[6]), parse(array[7]), parse(array[8])); if (num < 0 || num >= 3) { throw new FormatException("Casino ledger row names an unknown game."); } if (totals.round_count < 0 || totals.round_count > 22369621) { throw new FormatException("Casino ledger row has an invalid round count."); } if (totals.win_count < 0 || totals.win_count > totals.round_count) { throw new FormatException("Casino ledger row has an invalid win count."); } if (totals.loss_count < 0 || totals.loss_count > totals.round_count - totals.win_count) { throw new FormatException("Casino ledger row has an invalid loss count."); } if (array[0].Length == 0 || array[0].Length > 64) { throw new FormatException("Casino ledger row has a bad player key."); } PlayerRecord playerRecord = find(array[0]) ?? claim(array[0]); if (totals.round_count < playerRecord.day[num].round_count) { return false; } playerRecord.lifetime[num] = totals; return true; } public static string format_money(double value, bool signed) { if (!double.IsFinite(value)) { throw new ArgumentOutOfRangeException("value"); } double num = Math.Abs(value); string text = (signed ? ((value < -0.005) ? "-" : "+") : ((value < 0.0) ? "-" : "")); (double, string)[] array = new(double, string)[4] { (1.0, ""), (1000.0, "K"), (1000000.0, "M"), (1000000000.0, "B") }; for (int i = 0; i < array.Length; i++) { (double, string) tuple = array[i]; double item = tuple.Item1; string item2 = tuple.Item2; double num2 = Math.Round(num / item, (item != 1.0) ? 2 : 0); if (!(num2 >= 1000.0) || !(item2 != "B")) { return text + "$" + num2.ToString("0.##", CultureInfo.InvariantCulture) + item2; } } throw new InvalidOperationException("Unreachable: the last unit accepts every magnitude."); } public static string safe_name(string? name) { StringBuilder stringBuilder = new StringBuilder(24); int num = Math.Min(name?.Length ?? 0, 128); for (int i = 0; i < num; i++) { if (stringBuilder.Length == 24) { break; } char c = name[i]; if (char.IsLetterOrDigit(c) || c == ' ' || c == '_' || c == '-' || c == '.') { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (text.Length != 0) { return text; } return "Player"; } private static Totals pick(Totals[]? totals, CasinoGame? game) { if (totals == null) { return default(Totals); } if (!game.HasValue) { return sum(totals); } if ((int)game.Value >= 3) { throw new ArgumentOutOfRangeException("game"); } return totals[(uint)game.Value]; } private static Totals sum(Totals[] totals) { Totals result = default(Totals); foreach (Totals other in totals) { result = result.add(other); } return result; } private PlayerRecord? find(string player_key) { for (int i = 0; i < player_count; i++) { if (players[i].key == player_key) { return players[i]; } } return null; } private PlayerRecord claim(string player_key) { PlayerRecord playerRecord; if (player_count < 32) { playerRecord = players[player_count++]; } else { playerRecord = players[0]; PlayerRecord[] array = players; foreach (PlayerRecord playerRecord2 in array) { bool flag = playerRecord2.series_point_count == 0; bool flag2 = sum(playerRecord2.lifetime).round_count < sum(playerRecord.lifetime).round_count; if (flag && (flag2 || playerRecord.series_point_count > 0)) { playerRecord = playerRecord2; } } if (playerRecord.series_point_count > 0) { throw new InvalidOperationException("Every tracked casino player is active today."); } } playerRecord.key = player_key; playerRecord.name = ""; Array.Clear(playerRecord.lifetime, 0, 3); Array.Clear(playerRecord.day, 0, 3); playerRecord.series_point_count = 0; return playerRecord; } private static string number(double value) { return value.ToString("R", CultureInfo.InvariantCulture); } private static double parse(string field) { double num = double.Parse(field, NumberStyles.Float, CultureInfo.InvariantCulture); if (!double.IsFinite(num) || num < 0.0 || num > 1000000000000.0) { throw new FormatException("Casino ledger amount out of range."); } return num; } private static void assert(bool condition, string message) { if (!condition) { throw new InvalidOperationException("Casino Ledger assertion failed: " + message); } } } public sealed class Main : MelonMod { private static MelonPreferences_Entry<bool>? hud_enabled; private static MelonPreferences_Entry<bool>? day_summary_enabled; private static MelonPreferences_Entry<float>? hud_margin_right; private static MelonPreferences_Entry<float>? hud_margin_top; private static MelonPreferences_Entry<float>? hud_scale; private static MelonPreferences_Entry<KeyCode>? hud_toggle_key; private static bool running; private static bool ui_failed; private static int day_number; private float next_hud_refresh_seconds; internal static bool hud_visible { get { return hud_enabled.Value; } set { hud_enabled.Value = value; MelonPreferences.Save(); } } internal static bool ready { get { if (running && Singleton<LoadManager>.InstanceExists) { return Singleton<LoadManager>.Instance.IsGameLoaded; } return false; } } public override void OnInitializeMelon() { MelonPreferences_Category obj = MelonPreferences.CreateCategory("CasinoLedger"); hud_enabled = obj.CreateEntry<bool>("hud_enabled", true, "Show each player's total casino profit in this save", (string)null, false, false, (ValueValidator)null, (string)null); day_summary_enabled = obj.CreateEntry<bool>("day_summary_enabled", true, "Show casino stats after the sleep summary", (string)null, false, false, (ValueValidator)null, (string)null); hud_margin_right = obj.CreateEntry<float>("hud_margin_right", 13f, "HUD distance from the right screen edge (1920x1080 units)", (string)null, false, false, (ValueValidator)null, (string)null); hud_margin_top = obj.CreateEntry<float>("hud_margin_top", 420f, "HUD distance from the top screen edge (1920x1080 units)", (string)null, false, false, (ValueValidator)null, (string)null); hud_scale = obj.CreateEntry<float>("hud_scale", 1f, "HUD size multiplier, 0.5 to 3", (string)null, false, false, (ValueValidator)null, (string)null); hud_toggle_key = obj.CreateEntry<KeyCode>("hud_toggle_key", (KeyCode)288, "Key that shows or hides the HUD in game. None turns the key off", (string)null, false, false, (ValueValidator)null, (string)null); CasinoStats.subscriber_failed = delegate(Exception error) { ((MelonBase)this).LoggerInstance.Warning($"A round_settled subscriber threw: {error}"); }; CasinoStats.round_settled += note_day_number; Hooks.install(((MelonBase)this).HarmonyInstance); register_settings(); } private static void register_settings() { Settings.toggle("Casino Ledger", "Show standings", hud_enabled); Settings.slider("Casino Ledger", "Standings size", hud_scale, 0.5f, 3f, false); Settings.slider("Casino Ledger", "Right margin", hud_margin_right, 0f, 1600f, true); Settings.slider("Casino Ledger", "Top margin", hud_margin_top, 0f, 900f, true); Settings.toggle("Casino Ledger", "Day-end stats screen", day_summary_enabled); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (!(sceneName != "Main")) { clear(); running = true; } } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { if (!(sceneName != "Main")) { running = false; clear(); } } public override void OnUpdate() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) if (!ready) { return; } Hooks.update(Time.unscaledTime); if (ui_failed) { return; } try { DaySummary.update(Time.unscaledTime); if ((int)hud_toggle_key.Value != 0 && Input.GetKeyDown(hud_toggle_key.Value)) { hud_visible = !hud_visible; next_hud_refresh_seconds = 0f; } if (!(Time.unscaledTime < next_hud_refresh_seconds)) { next_hud_refresh_seconds = Time.unscaledTime + 0.5f; Hud.refresh(hud_enabled.Value && !DaySummary.showing, new Vector2(hud_margin_right.Value, hud_margin_top.Value), hud_scale.Value); } } catch (Exception error) { disable_ui(error); } } internal static void end_day() { Ledger ledger = CasinoStats.ledger; try { if (ledger.day_round_count > 0 && day_summary_enabled.Value && !ui_failed) { DaySummary.show(ledger, day_number, Time.unscaledTime); } } catch (Exception error) { disable_ui(error); } ledger.start_day(); day_number = 0; } private static void note_day_number(RoundReport report) { if (day_number == 0 && NetworkSingleton<TimeManager>.InstanceExists) { day_number = NetworkSingleton<TimeManager>.Instance.ElapsedDays + 1; } } private void clear() { Hooks.reset(); Hud.reset(); DaySummary.reset(); Ui.reset(); ui_failed = false; day_number = 0; next_hud_refresh_seconds = 0f; } private static void disable_ui(Exception error) { ui_failed = true; Hud.reset(); DaySummary.reset(); MelonLogger.Error($"Casino Ledger: Overlay disabled for this scene, tracking continues: {error}"); } } internal static class Sync { private const float flush_interval_seconds = 1f; private static readonly Round[] outbox = new Round[64]; private static readonly Round[] inbox = new Round[12]; private static int outbox_count; private static float next_flush_seconds; private static IntPtr subscribed_service; private static Action<string>? listener; private static string world_key = ""; public static Action<Round>? round_received; public static Action? totals_changed; public static void reset() { world_key = ""; outbox_count = 0; next_flush_seconds = 0f; Array.Clear(outbox, 0, outbox.Length); Array.Clear(inbox, 0, inbox.Length); round_received = null; totals_changed = null; } public static void connect(string key) { world_key = key; SteamLobbyService val = service_in_lobby(); if (val == null) { return; } if (((Il2CppObjectBase)val).Pointer != subscribed_service) { if (listener == null) { listener = Action<string>.op_Implicit((Action<string>)receive); } val.OnLobbyMessage += listener; subscribed_service = ((Il2CppObjectBase)val).Pointer; } if (!InstanceFinder.IsServer) { Singleton<Lobby>.Instance.SendLobbyMessage(Wire.hello(world_key)); } } public static void send_round(Round round) { if (world_key.Length != 0 && service_in_lobby() != null) { if (outbox_count == outbox.Length) { flush_batch(); } outbox[outbox_count++] = round; } } public static void flush(float now_seconds) { if (outbox_count == 0 || now_seconds < next_flush_seconds) { return; } next_flush_seconds = now_seconds + 1f; for (int i = 0; i < outbox.Length; i += 12) { if (outbox_count <= 0) { break; } flush_batch(); } } private static void flush_batch() { int num = Math.Min(outbox_count, 12); string text = Wire.rounds(world_key, outbox.AsSpan(0, num)); Array.Copy(outbox, num, outbox, 0, outbox_count - num); outbox_count -= num; if (service_in_lobby() != null) { Singleton<Lobby>.Instance.SendLobbyMessage(text); } } public static void send_totals() { if (world_key.Length != 0 && InstanceFinder.IsServer && service_in_lobby() != null) { string[] array = new string[96]; int num = CasinoStats.ledger.lifetime_rows(array); for (int i = 0; i < num; i++) { Singleton<Lobby>.Instance.SendLobbyMessage(Wire.total(world_key, array[i])); } } } private static SteamLobbyService? service_in_lobby() { if (!Singleton<Lobby>.InstanceExists || !Singleton<Lobby>.Instance.IsInLobby || Singleton<Lobby>.Instance._lobbyService == null) { return null; } return ((Il2CppObjectBase)Singleton<Lobby>.Instance._lobbyService).TryCast<SteamLobbyService>(); } private static void receive(string message) { try { if (world_key.Length == 0 || !Main.ready || service_in_lobby() == null) { return; } switch (Wire.kind(message, world_key)) { case WireKind.Round: { int num = Wire.parse_rounds(message, world_key, inbox); for (int i = 0; i < num; i++) { round_received?.Invoke(inbox[i]); } break; } case WireKind.Hello: send_totals(); break; case WireKind.Total: if (!InstanceFinder.IsServer && CasinoStats.ledger.replace_lifetime_row(Wire.parse_total(message, world_key))) { totals_changed?.Invoke(); } break; } } catch (Exception ex) when (((ex is FormatException || ex is ArgumentOutOfRangeException || ex is OverflowException) ? 1 : 0) != 0) { MelonLogger.Warning("Casino Ledger: Ignored a malformed lobby message: " + ex.Message); } catch (Exception error) { Hooks.pause(error); } } } internal static class Ui { public static readonly Color profit = new Color(0.24f, 0.93f, 0.38f, 1f); public static readonly Color loss = new Color(1f, 0.23f, 0.36f, 1f); public static readonly Color surface = new Color(0.13f, 0.13f, 0.13f, 0.9f); public static readonly Color outline = new Color(1f, 1f, 1f, 0.8f); private const int corner_radius_pixels = 10; private const float outline_pixels = 2f; private static Sprite? rounded_sprite; private const int panel_style_count_max = 2; private static readonly Dictionary<float, Sprite> outlined_sprites = new Dictionary<float, Sprite>(2); public static void reset() { if ((Object)(object)rounded_sprite != (Object)null) { Object.Destroy((Object)(object)rounded_sprite.texture); Object.Destroy((Object)(object)rounded_sprite); } rounded_sprite = null; foreach (Sprite value in outlined_sprites.Values) { if (!((Object)(object)value == (Object)null)) { Object.Destroy((Object)(object)value.texture); Object.Destroy((Object)(object)value); } } outlined_sprites.Clear(); } public static Color money_color(double value) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!(value < -0.005)) { return profit; } return loss; } public static GameObject canvas(string name, int sorting_order) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown GameObject val = new GameObject(name); Canvas obj = val.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = sorting_order; CanvasScaler obj2 = val.AddComponent<CanvasScaler>(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 1f; return val; } public static RectTransform panel(Transform parent, string name, Color color) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); RectTransform result = val.AddComponent<RectTransform>(); Image obj = val.AddComponent<Image>(); obj.sprite = rounded(); obj.type = (Type)1; ((Graphic)obj).color = color; ((Graphic)obj).raycastTarget = false; return result; } public static RectTransform outlined_panel(Transform parent, string name, float fill_alpha = 1f, float corner_radius = 10f) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!(fill_alpha >= 0f) || !(fill_alpha <= 1f)) { throw new ArgumentOutOfRangeException("fill_alpha"); } if (!(corner_radius >= 1f) || !(corner_radius <= 64f)) { throw new ArgumentOutOfRangeException("corner_radius"); } RectTransform obj = panel(parent, name, Color.white); Image component = ((Component)obj).GetComponent<Image>(); component.sprite = outlined(fill_alpha); component.pixelsPerUnitMultiplier = 10f / corner_radius; return obj; } public static TextMeshProUGUI text(Transform parent, string name, float size, TextAlignmentOptions alignment) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>(); if (Singleton<NotificationsManager>.InstanceExists && (Object)(object)Singleton<NotificationsManager>.Instance.NotificationPrefab != (Object)null) { TextMeshProUGUI componentInChildren = Singleton<NotificationsManager>.Instance.NotificationPrefab.GetComponentInChildren<TextMeshProUGUI>(true); TMP_FontAsset val3 = ((componentInChildren != null) ? ((TMP_Text)componentInChildren).font : null); if ((Object)(object)val3 != (Object)null) { ((TMP_Text)val2).font = val3; } } ((TMP_Text)val2).fontSize = size; ((TMP_Text)val2).fontStyle = (FontStyles)17; ((Graphic)val2).color = Color.white; ((TMP_Text)val2).alignment = alignment; ((TMP_Text)val2).enableWordWrapping = false; ((TMP_Text)val2).overflowMode = (TextOverflowModes)1; ((Graphic)val2).raycastTarget = false; return val2; } public static void place(RectTransform rect, Vector2 anchor, Vector2 pivot, Vector2 position, Vector2 size) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = anchor; rect.anchorMax = anchor; rect.pivot = pivot; rect.anchoredPosition = position; rect.sizeDelta = size; } public static void stretch(RectTransform rect, float inset) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2(inset, inset); rect.offsetMax = new Vector2(0f - inset, 0f - inset); } private static Sprite rounded() { if (rounded_sprite == null) { rounded_sprite = sliced(((float outer, float inner) coverage) => new Color(1f, 1f, 1f, coverage.outer)); } return rounded_sprite; } private static Sprite outlined(float fill_alpha) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (outlined_sprites.TryGetValue(fill_alpha, out Sprite value) && (Object)(object)value != (Object)null) { return value; } if (outlined_sprites.Count >= 2 && !outlined_sprites.ContainsKey(fill_alpha)) { throw new InvalidOperationException("Casino panel sprite cache is full."); } Color fill = new Color(surface.r, surface.g, surface.b, fill_alpha); Sprite val = sliced(delegate((float outer, float inner) coverage) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) Color result = Color.Lerp(outline, fill, coverage.inner); result.a *= coverage.outer; return result; }); outlined_sprites[fill_alpha] = val; return val; } private static Sprite sliced(Func<(float outer, float inner), Color> shade) { //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) //IL_0014: Expected O, but got Unknown //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(22, 22, (TextureFormat)4, false) { hideFlags = (HideFlags)61 }; for (int i = 0; i < 22; i++) { for (int j = 0; j < 22; j++) { float num = Mathf.Clamp((float)j + 0.5f, 10f, 12f); float num2 = Mathf.Clamp((float)i + 0.5f, 10f, 12f); float num3 = 10f - Vector2.Distance(new Vector2((float)j + 0.5f, (float)i + 0.5f), new Vector2(num, num2)); val.SetPixel(j, i, shade((Mathf.Clamp01(num3 + 0.5f), Mathf.Clamp01(num3 - 2f + 0.5f)))); } } val.Apply(); float num4 = 10f; Sprite obj = Sprite.Create(val, new Rect(0f, 0f, 22f, 22f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(num4, num4, num4, num4)); ((Object)obj).hideFlags = (HideFlags)61; return obj; } } internal enum WireKind { None, Round, Hello, Total } internal static class Wire { public const string prefix = "CasinoLedger|3|"; public const int length_max = 2048; public const int rounds_per_message_max = 12; private const int world_key_length = 16; public static string hello(string world_key) { return message_prefix(world_key) + "hello"; } public static string rounds(string world_key, ReadOnlySpan<Round> batch) { if (batch.Length == 0 || batch.Length > 12) { throw new ArgumentOutOfRangeException("batch"); } string[] array = new string[batch.Length]; for (int i = 0; i < batch.Length; i++) { Round round = batch[i]; array[i] = string.Join('|', round.player_key, ((int)round.game).ToString(CultureInfo.InvariantCulture), round.stake.ToString("R", CultureInfo.InvariantCulture), round.returned.ToString("R", CultureInfo.InvariantCulture), Ledger.safe_name(round.player_name)); } string text = message_prefix(world_key) + "round|" + string.Join(';', array); if (text.Length > 2048) { throw new InvalidOperationException("Round batch exceeds the message limit."); } return text; } public static string total(string world_key, string ledger_row) { if (ledger_row == null || ledger_row.Length > 512) { throw new ArgumentOutOfRangeException("ledger_row"); } return message_prefix(world_key) + "total|" + ledger_row; } public static WireKind kind(string? message, string world_key) { int num = "CasinoLedger|3|".Length + 16 + 1; if (world_key.Length != 16 || message == null || message.Length < num || message.Length > 2048 || !message.StartsWith("CasinoLedger|3|", StringComparison.Ordinal)) { return WireKind.None; } if (!message.AsSpan("CasinoLedger|3|".Length, 16).SequenceEqual(world_key) || message[num - 1] != '|') { return WireKind.None; } ReadOnlySpan<char> span = message.AsSpan(num); if (span.SequenceEqual("hello")) { return WireKind.Hello; } if (span.StartsWith("round|", StringComparison.Ordinal)) { return WireKind.Round; } if (span.StartsWith("total|", StringComparison.Ordinal)) { return WireKind.Total; } return WireKind.None; } public static int parse_rounds(string message, string world_key, Span<Round> target) { if (kind(message, world_key) != WireKind.Round) { throw new FormatException("Round message does not match the loaded save."); } int num = "CasinoLedger|3|".Length + 16 + 1 + "round|".Length; string[] array = message.Substring(num, message.Length - num).Split(';'); if (array.Length > 12 || array.Length > target.Length) { throw new FormatException("Round message has too many rounds."); } for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('|'); if (array2.Length != 5) { throw new FormatException("Round message has the wrong shape."); } if (array2[0].Length != 64) { throw new FormatException("Round message has a bad player key."); } int num2 = int.Parse(array2[1], CultureInfo.InvariantCulture); if (num2 < 0 || num2 >= 3) { throw new FormatException("Round message names an unknown game."); } double num3 = double.Parse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture); double num4 = double.Parse(array2[3], NumberStyles.Float, CultureInfo.InvariantCulture); if (!double.IsFinite(num3) || num3 <= 0.0 || num3 > 1000000.0) { throw new FormatException("Round stake is out of range."); } if (!double.IsFinite(num4) || num4 < 0.0 || num4 > 100000000.0) { throw new FormatException("Round return is out of range."); } target[i] = new Round(array2[0], Ledger.safe_name(array2[4]), (CasinoGame)num2, num3, num4); } return array.Length; } public static string parse_total(string message, string world_key) { if (kind(message, world_key) != WireKind.Total) { throw new FormatException("Total message does not match the loaded save."); } int num = "CasinoLedger|3|".Length + 16 + 1 + "total|".Length; string text = message.Substring(num, message.Length - num); if (text.Length == 0 || text.Length > 512 || text.Contains('\n')) { throw new FormatException("Total message has the wrong shape."); } return text; } private static string message_prefix(string world_key) { if (world_key.Length != 16) { throw new ArgumentOutOfRangeException("world_key"); } for (int i = 0; i < world_key.Length; i++) { bool flag; switch (world_key[i]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': flag = true; break; default: flag = false; break; } if (!flag) { throw new ArgumentOutOfRangeException("world_key"); } } return "CasinoLedger|3|" + world_key + "|"; } } }