Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Chipmunks Shared Storage v0.1.0
BepInEx/plugins/ChipmunksSharedStorage/SharedStorageLedger.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] [BepInPlugin("local.chipmunks.storage.ledger", "Chipmunks Shared Storage", "0.1.0")] public sealed class SharedStorageLedger : BaseUnityPlugin { private sealed class Scope { public Container container; public string id; public Dictionary<string, int> before; } [HarmonyPatch] private static class Transfers { private static IEnumerable<MethodBase> TargetMethods() { try { string[] array = new string[2] { "OnSelectedItem", "OnDropOutside" }; foreach (string method in array) { yield return AccessTools.Method(typeof(InventoryGui), method, (Type[])null, (Type[])null); } } finally { } try { string[] array2 = new string[2] { "RPC_TakeAllResponse", "RPC_StackResponse" }; foreach (string method2 in array2) { yield return AccessTools.Method(typeof(Container), method2, (Type[])null, (Type[])null); } } finally { } } [HarmonyPrefix] private static void Prefix(object __instance, out Scope __state) { __state = null; try { Container val = (Container)((__instance is Container) ? __instance : null); if ((Object)(object)val == (Object)null) { object? value = AccessTools.Field(typeof(InventoryGui), "m_currentContainer").GetValue(__instance); val = (Container)((value is Container) ? value : null); } __state = Begin(val); } catch (Exception ex) { if ((Object)(object)self != (Object)null) { self.Fail(ex); } } } [HarmonyFinalizer] private static void Finalizer(Scope __state) { if (__state == null) { return; } try { self.Record(__state); } catch (Exception ex) { if ((Object)(object)self != (Object)null) { self.Fail(ex); } } finally { depth--; } } } private const string Hello = "ChipmunksLedgerHello1"; private const string Welcome = "ChipmunksLedgerWelcome1"; private const string Event = "ChipmunksLedgerEvent1"; private const string Ack = "ChipmunksLedgerAck1"; private static SharedStorageLedger self; private Harmony patches; private ZRoutedRpc registered; private float nextTick; private float nextHello; private float nextWarning; private long serverPeer; private string world = ""; private string pendingPath = ""; private string journalPath = ""; private bool ready; private bool serverReady; private bool dirty; private readonly List<string> pending = new List<string>(); private LedgerJournal journal; private readonly Dictionary<long, float> helloTimes = new Dictionary<long, float>(); private readonly Dictionary<long, float> rateStart = new Dictionary<long, float>(); private readonly Dictionary<long, int> rateCount = new Dictionary<long, int>(); private static int depth; private readonly string directory = Path.Combine(Paths.ConfigPath, "ChipmunksSharedStorage"); private bool IsServer => (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); private void Awake() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown self = this; Directory.CreateDirectory(directory); patches = new Harmony("local.chipmunks.storage.ledger"); patches.PatchAll(typeof(SharedStorageLedger).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Shared storage hooks installed; waiting for server connection"); } private void OnDestroy() { try { if (dirty && pendingPath != "") { SavePending(); } } catch (Exception ex) { Fail(ex); } if (patches != null) { patches.UnpatchSelf(); } self = null; } private void Fail(Exception ex) { if (!(Time.unscaledTime < nextWarning)) { nextWarning = Time.unscaledTime + 30f; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Shared storage telemetry: " + ex.GetType().Name + ": " + ex.Message)); } } private void Update() { if (Time.unscaledTime < nextTick) { return; } nextTick = Time.unscaledTime + 2f; try { if (dirty && pendingPath != "") { SavePending(); } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null || (Object)(object)ZNet.instance == (Object)null) { ready = false; return; } if (!object.ReferenceEquals(registered, instance)) { registered = instance; ready = false; serverReady = false; serverPeer = 0L; world = ""; nextHello = 0f; journal = null; helloTimes.Clear(); rateStart.Clear(); rateCount.Clear(); instance.Register<string>("ChipmunksLedgerHello1", (Action<long, string>)OnHello); instance.Register<string>("ChipmunksLedgerWelcome1", (Action<long, string>)OnWelcome); instance.Register<string>("ChipmunksLedgerEvent1", (Action<long, string>)OnEvent); instance.Register<string>("ChipmunksLedgerAck1", (Action<long, string>)OnAck); } if (IsServer) { if (!serverReady) { OpenJournal(); } return; } ZNetPeer val = ZNet.instance.GetServerPeer(); if (val == null || val.m_uid == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { ready = false; return; } if (val.m_uid != serverPeer) { serverPeer = val.m_uid; ready = false; world = ""; nextHello = 0f; } if (Time.unscaledTime >= nextHello) { nextHello = Time.unscaledTime + 60f; instance.InvokeRoutedRPC(serverPeer, "ChipmunksLedgerHello1", new object[1] { "0.1.0" }); } if (ready) { for (int i = 0; i < Math.Min(20, pending.Count); i++) { instance.InvokeRoutedRPC(serverPeer, "ChipmunksLedgerEvent1", new object[1] { pending[i] }); } } } catch (Exception ex) { if (IsServer) { nextTick = Time.unscaledTime + 60f; } Fail(ex); } } private void OpenJournal() { journalPath = Path.Combine(directory, "events.tsv"); journal = new LedgerJournal(journalPath); serverReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("CHIPMUNKS_LEDGER_READY\t0.1.0\t" + ZNet.instance.GetWorldUID())); } private string Steam(long sender) { ZNetPeer peer = ZNet.instance.GetPeer(sender); return (peer == null || peer.m_socket == null) ? null : SteamIdentity.Extract(peer.m_socket.GetHostName()); } private void OnHello(long sender, string version) { try { if (IsServer && serverReady && !(version != "0.1.0")) { string text = Steam(sender); if (text != null && (!helloTimes.TryGetValue(sender, out var value) || !(Time.unscaledTime - value < 20f))) { helloTimes[sender] = Time.unscaledTime; ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ChipmunksLedgerWelcome1", new object[1] { ZNet.instance.GetWorldUID().ToString() }); ((BaseUnityPlugin)this).Logger.LogInfo((object)("CHIPMUNKS_LEDGER_CLIENT\t" + text + "\t0.1.0")); } } } catch (Exception ex) { Fail(ex); } } private void OnWelcome(long sender, string worldId) { try { if (IsServer || sender != serverPeer || !long.TryParse(worldId, out var _)) { return; } if (world != worldId) { string text = Path.Combine(directory, "pending-" + worldId + ".tsv"); List<string> list = new List<string>(); if (File.Exists(text)) { if (new FileInfo(text).Length > 4194304) { throw new IOException("Pending ledger too large"); } string[] array = File.ReadAllLines(text); foreach (string text2 in array) { if (!LedgerProtocol.Valid(text2)) { throw new IOException("Invalid pending ledger; preserve for diagnosis"); } if (list.Count >= 10000) { throw new IOException("Pending queue exceeds limit"); } list.Add(text2); } } pending.Clear(); pending.AddRange(list); pendingPath = text; world = worldId; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Shared storage connected; pending events=" + pending.Count)); } ready = true; } catch (Exception ex) { ready = false; Fail(ex); } } private void OnEvent(long sender, string text) { try { if (!IsServer || !serverReady || !LedgerProtocol.Valid(text) || !helloTimes.ContainsKey(sender)) { return; } string text2 = Steam(sender); if (text2 == null) { return; } if (!rateStart.TryGetValue(sender, out var value) || Time.unscaledTime - value >= 60f) { rateStart[sender] = Time.unscaledTime; rateCount[sender] = 0; } if (++rateCount[sender] <= 1200) { string[] array = text.Split(new char[1] { '\t' }); long num = long.Parse(array[1]); if (num <= LedgerProtocol.Now() + 300) { journal.Append(ZNet.instance.GetWorldUID(), text2, text); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "ChipmunksLedgerAck1", new object[1] { array[0] }); } } } catch (Exception ex) { Fail(ex); } } private void SavePending() { string text = pendingPath + ".tmp"; File.WriteAllLines(text, pending.ToArray()); if (File.Exists(pendingPath)) { File.Replace(text, pendingPath, null); } else { File.Move(text, pendingPath); } dirty = false; } private void OnAck(long sender, string id) { if (!IsServer && ready && sender == serverPeer && LedgerProtocol.Id.IsMatch(id ?? "")) { int num = pending.FindIndex((string row) => row.StartsWith(id + "\t", StringComparison.Ordinal)); if (num >= 0) { pending.RemoveAt(num); dirty = true; } } } private void Record(Scope scope) { int count = pending.Count; foreach (KeyValuePair<string, int> item in LedgerProtocol.Difference(scope.before, Count(scope.container.GetInventory()))) { if (item.Value != 0 && LedgerProtocol.Item.IsMatch(item.Key)) { if (pending.Count >= 10000) { throw new IOException("Pending queue full; collection incomplete"); } pending.Add(LedgerProtocol.Encode(Guid.NewGuid().ToString("N"), LedgerProtocol.Now(), scope.id, item.Key, item.Value)); } } if (pending.Count != count) { SavePending(); } } private static Dictionary<string, int> Count(Inventory inventory) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (ItemData allItem in inventory.GetAllItems()) { if (!((Object)(object)allItem.m_dropPrefab == (Object)null) && allItem.m_stack > 0) { string key = ((Object)allItem.m_dropPrefab).name.Replace("(Clone)", ""); dictionary.TryGetValue(key, out var value); dictionary[key] = value + allItem.m_stack; } } return dictionary; } private static Scope Begin(Container container) { if ((Object)(object)self == (Object)null || self.IsServer || !self.ready || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)container == (Object)null || depth != 0) { return null; } if ((Object)(object)((Component)container).GetComponent<TombStone>() != (Object)null || (Object)(object)((Component)container).GetComponentInParent<TombStone>() != (Object)null) { return null; } ZNetView component = ((Component)container).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || component.GetZDO() == null) { return null; } Scope scope = new Scope(); scope.container = container; scope.id = ((object)Unsafe.As<ZDOID, ZDOID>(ref component.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString(); scope.before = Count(container.GetInventory()); Scope scope2 = scope; if (!LedgerProtocol.ContainerId.IsMatch(scope2.id)) { return null; } depth++; return scope2; } } public static class LedgerProtocol { public const int MaxPending = 10000; public static readonly Regex Item = new Regex("\\A[A-Za-z0-9_]{1,100}\\z"); public static readonly Regex Id = new Regex("\\A[a-f0-9]{32}\\z"); public static readonly Regex ContainerId = new Regex("\\A-?[0-9]{1,20}:[0-9]{1,20}\\z"); public static long Now() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; } public static string Encode(string id, long stamp, string container, string item, int delta) { return id + "\t" + stamp.ToString(CultureInfo.InvariantCulture) + "\t" + container + "\t" + item + "\t" + delta.ToString(CultureInfo.InvariantCulture); } public static bool Valid(string text) { if (text == null || text.Length > 256) { return false; } string[] array = text.Split(new char[1] { '\t' }); long result; int result2; return array.Length == 5 && Id.IsMatch(array[0]) && long.TryParse(array[1], out result) && result > 0 && ContainerId.IsMatch(array[2]) && Item.IsMatch(array[3]) && int.TryParse(array[4], out result2) && result2 != 0 && result2 >= -10000000 && result2 <= 10000000; } public static Dictionary<string, int> Difference(Dictionary<string, int> before, Dictionary<string, int> after) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (string key in before.Keys) { dictionary[key] = -before[key]; } foreach (string key2 in after.Keys) { dictionary.TryGetValue(key2, out var value); dictionary[key2] = value + after[key2]; } return dictionary; } } public sealed class LedgerJournal { private const int MaxBytes = 67108864; private readonly string path; private readonly HashSet<string> seen = new HashSet<string>(); private static bool Sid(string value) { return Regex.IsMatch(value, "\\A[0-9]{17}\\z"); } public LedgerJournal(string path) { this.path = path; if (!File.Exists(path)) { File.WriteAllText(path, ""); } if (new FileInfo(path).Length > 67108864) { throw new IOException("Ledger exceeds 64 MiB; archive before continuing"); } using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) { long num = fileStream.Length; if (num > 0) { fileStream.Position = num - 1; if (fileStream.ReadByte() != 10) { File.Copy(path, path + ".partial-" + DateTime.UtcNow.Ticks); while (num > 0) { num = (fileStream.Position = num - 1); if (fileStream.ReadByte() == 10) { num++; break; } } fileStream.SetLength(num); fileStream.Flush(flushToDisk: true); } } } foreach (string item in File.ReadLines(path)) { string[] array = item.Split(new char[1] { '\t' }); if (array.Length != 7 || !long.TryParse(array[0], out var _) || !Sid(array[1]) || !LedgerProtocol.Valid(string.Join("\t", array, 2, 5))) { throw new IOException("Invalid journal row; preserve for diagnosis"); } seen.Add(array[0] + ":" + array[1] + ":" + array[2]); } } public bool Append(long world, string sid, string text) { if (!Sid(sid) || !LedgerProtocol.Valid(text)) { throw new ArgumentException("Invalid ledger event"); } string item = world + ":" + sid + ":" + text.Split(new char[1] { '\t' })[0]; if (seen.Contains(item)) { return false; } byte[] bytes = Encoding.UTF8.GetBytes(world + "\t" + sid + "\t" + text + "\n"); using (FileStream fileStream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read)) { if (fileStream.Length + bytes.Length > 67108864) { throw new IOException("Ledger full; events retained on client"); } fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } seen.Add(item); return true; } } internal static class SteamIdentity { private static readonly Regex HostId = new Regex("^(?:Steam_)?(\\d{17})$", RegexOptions.CultureInvariant); internal static string Extract(string host) { Match match = HostId.Match(host ?? ""); return match.Success ? match.Groups[1].Value : null; } }