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 ItemIntegrity ServerOnly v0.2.0
ItemIntegrity.ServerOnly.dll
Decompiled 2 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using ItemIntegrity.Core; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ItemIntegrity.ServerOnly")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0-preview.1")] [assembly: AssemblyProduct("ItemIntegrity.ServerOnly")] [assembly: AssemblyTitle("ItemIntegrity.ServerOnly")] [assembly: AssemblyVersion("0.2.0.0")] namespace ItemIntegrity { internal sealed class AuditStore { private readonly string _root = Path.Combine(Paths.ConfigPath, "ItemIntegrity.ServerOnly", "quarantine"); private long _used; internal string Fault { get; private set; } internal AuditStore() { checked { try { Directory.CreateDirectory(_root); foreach (string item in Directory.EnumerateFiles(_root, "*", SearchOption.AllDirectories)) { _used += new FileInfo(item).Length; } } catch (Exception ex) { Fault = ex.Message; } } } internal bool Snapshot(string kind, string context, byte[] bytes, int capMiB, out string path) { path = null; if (Fault != null) { return false; } try { if (bytes == null || bytes.Length > 2097152) { throw new IOException("Invalid backup payload size"); } string path2 = DateTime.UtcNow.ToString("yyyyMMdd"); string text = Path.Combine(_root, path2); string text2 = DateTime.UtcNow.ToString("HHmmssfff") + "-" + Guid.NewGuid().ToString("N"); string text3 = Path.Combine(text, text2 + ".bin"); string s = "{\"schema\":1,\"utc\":" + Quote(DateTime.UtcNow.ToString("O")) + ",\"kind\":" + Quote(kind) + ",\"context\":" + Quote(context) + ",\"sha256\":" + Quote(Hash(bytes)) + ",\"payload\":" + Quote(text2 + ".bin") + "}\n"; byte[] bytes2 = Encoding.UTF8.GetBytes(s); if (_used + bytes.Length + bytes2.Length > (long)capMiB * 1024L * 1024) { throw new IOException("Quarantine size limit reached. Archive it and restart before further deletion."); } Directory.CreateDirectory(text); WriteNew(text3, bytes); WriteNew(Path.Combine(text, text2 + ".json"), bytes2); _used += bytes.Length + bytes2.Length; path = text3; return true; } catch (Exception ex) { Fault = ex.Message; return false; } } private static void WriteNew(string path, byte[] bytes) { using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } internal static string Hash(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant(); } internal static string Quote(string text) { StringBuilder stringBuilder = new StringBuilder("\""); string text2 = text ?? ""; foreach (char c in text2) { 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.Append('"').ToString(); } } internal static class GameAccess { private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static FieldInfo _objects; private static FieldInfo _containerRevision; private static FieldInfo _containerLoading; private static FieldInfo _containerView; private static FieldInfo _standView; private static FieldInfo _armorView; internal static void VerifyApi() { _objects = RequireField(typeof(ZDOMan), "m_objectsByID", typeof(Dictionary<ZDOID, ZDO>)); _containerRevision = RequireField(typeof(Container), "m_lastRevision", typeof(uint)); _containerLoading = RequireField(typeof(Container), "m_loading", typeof(bool)); _containerView = RequireField(typeof(Container), "m_nview", typeof(ZNetView)); _standView = RequireField(typeof(ItemStand), "m_nview", typeof(ZNetView)); _armorView = RequireField(typeof(ArmorStand), "m_nview", typeof(ZNetView)); int num = 0; foreach (object value in Enum.GetValues(typeof(Item))) { num = Math.Max(num, Convert.ToInt32(value)); } if (num != 109) { throw new NotSupportedException("Game item format " + num + " is not the reviewed version 109"); } } private static FieldInfo RequireField(Type type, string name, Type expected) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null || field.FieldType != expected) { throw new MissingFieldException(type.Name + "." + name); } return field; } internal static Dictionary<ZDOID, ZDO> Objects(ZDOMan man) { return (Dictionary<ZDOID, ZDO>)_objects.GetValue(man); } internal static ZNetView View(Container c) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown return (ZNetView)_containerView.GetValue(c); } internal static ZNetView View(ItemStand s) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown return (ZNetView)_standView.GetValue(s); } internal static ZNetView View(ArmorStand s) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown return (ZNetView)_armorView.GetValue(s); } internal static bool ContainerReady(Container c, ZDO z) { if ((bool)_containerLoading.GetValue(c)) { return false; } if (!c.IsInUse()) { return (uint)_containerRevision.GetValue(c) == z.DataRevision; } return true; } internal static int AttachedKey(int slot) { if (slot >= 0) { return StringExtensionMethods.GetStableHashCode(slot + "_item"); } return ZDOVars.s_item; } internal static int ItemDataKey(int slot) { if (slot >= 0) { return StringExtensionMethods.GetStableHashCode(slot + "_itemData"); } return ZDOVars.s_itemData; } internal static string PrefabName(int hash) { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(hash) : null); if ((Object)(object)val == (Object)null && (Object)(object)ZNetScene.instance != (Object)null) { val = ZNetScene.instance.GetPrefab(hash); } if (!((Object)(object)val != (Object)null)) { return "#" + hash; } return ((Object)val).name; } internal static byte[] SaveItem(ItemData item) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)109); item.Save(val); return val.GetArray(); } internal static byte[] SaveInventory(Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); inventory.Save(val); return val.GetArray(); } internal static void NativeCodecSelfTest() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Wood"); if ((Object)(object)itemPrefab == (Object)null) { throw new InvalidOperationException("ObjectDB Wood prefab not available"); } Inventory val = new Inventory("ItemIntegrity codec self-test", (Sprite)null, 8, 4); for (int i = 0; i < 4; i++) { ItemData val2 = new ItemData { m_dropPrefab = itemPrefab, m_cheated = ((i & 1) != 0), m_stack = ((i == 0) ? 1 : 50), m_quality = ((i < 2) ? 1 : 3), m_variant = ((i >= 2) ? 7 : 0), m_durability = 123.45f, m_worldLevel = 2, m_gridPos = new Vector2i(i, 1), m_pickedUp = true, m_equipped = (i == 2), m_crafterID = ((i >= 2) ? 123456789 : 0), m_crafterName = "codec-é-木" }; if (i == 3) { for (int j = 0; j < 130; j++) { val2.m_customData["key" + j] = "value-木-" + new string('x', j); } } ItemCodec.Record record = ItemCodec.ReadDrop(SaveItem(val2)); if (record.Cheated != val2.m_cheated || record.Stack != val2.m_stack || record.Quality != val2.m_quality || record.PrefabHash != StringExtensionMethods.GetStableHashCode(((Object)itemPrefab).name)) { throw new InvalidDataException("Native item codec self-test mismatch"); } val.GetAllItems().Add(val2); } ItemCodec.InventoryData inventoryData = ItemCodec.ReadInventory(SaveInventory(val)); if (inventoryData.Records.Count != 4) { throw new InvalidDataException("Native inventory count mismatch"); } ItemCodec.InventoryData inventoryData2 = ItemCodec.ReadInventory(inventoryData.Without(new HashSet<int> { 1, 3 })); if (inventoryData2.Records.Count != 2 || inventoryData2.Records.Any((ItemCodec.Record r) => r.Cheated)) { throw new InvalidDataException("Native inventory filter mismatch"); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class ConsolePatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; internal void <Postfix>b__0_0(ConsoleEventArgs a) { a.Context.AddString(((Object)(object)Plugin.Instance != (Object)null) ? Plugin.Instance.Status() : "Not initialized"); } internal void <Postfix>b__0_1(ConsoleEventArgs a) { Plugin.Instance?.CommandScan(a.Context); } internal void <Postfix>b__0_2(ConsoleEventArgs a) { Plugin.Instance?.CommandReload(a.Context); } } private static void Postfix() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_00a5: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs a) { a.Context.AddString(((Object)(object)Plugin.Instance != (Object)null) ? Plugin.Instance.Status() : "Not initialized"); }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("itemintegrity_status", "Show server-only world cleaner state", (ConsoleEvent)obj, false, false, true, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs a) { Plugin.Instance?.CommandScan(a.Context); }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("itemintegrity_scan", "Request a server-only world scan", (ConsoleEvent)obj2, false, false, true, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs a) { Plugin.Instance?.CommandReload(a.Context); }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("itemintegrity_reload", "Reload server-only cleaner settings", (ConsoleEvent)obj3, false, false, true, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } [BepInPlugin("local.valheim.itemintegrity.serveronly", "ItemIntegrity.ServerOnly", "0.2.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "local.valheim.itemintegrity.serveronly"; public const string BuildVersion = "0.2.0"; public const string Release = "0.2.0-preview.1 (source preview; staging required)"; private Harmony _harmony; private AuditStore _audit; private WorldCleaner _world; private ServerLocalCleaner _local; private ZNet _net; private ZDOMan _man; private bool _apiOk; private bool _nativeOk; private bool _nativeAttempted; private bool _oldConflict; private double _nextLocal; private double _nextSummary; private double _nextChecks; private double _lastPeerSeen; private readonly Dictionary<string, double> _rate = new Dictionary<string, double>(); internal static Plugin Instance { get; private set; } internal static double Now => Time.realtimeSinceStartup; internal static bool IsDedicatedServer { get { if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return ZNet.instance.IsDedicated(); } return false; } } internal static int ConnectedPeers { get { if (!((Object)(object)ZNet.instance != (Object)null)) { return 0; } return ZNet.instance.GetPeers().Count; } } internal Settings Options { get; private set; } internal bool Operational { get { if (IsDedicatedServer && _apiOk && _nativeOk) { return !_oldConflict; } return false; } } internal Policy ServerPolicy { get; private set; } = new Policy { Enabled = false }; internal double EmptySeconds { get { if (!IsDedicatedServer || ConnectedPeers != 0) { return 0.0; } return Math.Max(0.0, Now - _lastPeerSeen); } } internal bool StorageWritesAllowed { get { if (Operational && ConnectedPeers == 0) { return EmptySeconds >= 15.0; } return false; } } private void Awake() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown Instance = this; Options = new Settings(((BaseUnityPlugin)this).Config); _audit = new AuditStore(); _local = new ServerLocalCleaner(this); _world = new WorldCleaner(this); try { GameAccess.VerifyApi(); _harmony = new Harmony("local.valheim.itemintegrity.serveronly"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); _apiOk = true; } catch (Exception ex) { _apiOk = false; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } ((BaseUnityPlugin)this).Logger.LogError((object)("[paused] API/patch validation failed. Nothing will be cleaned: " + ex)); } Info("[startup] 0.2.0-preview.1 (source preview; staging required); dedicated-server only; no client component or custom client protocol; dryRun=" + Options.DryRun.Value); Info("[scope] Ground drops: online ZDO cleanup. Containers/stands: empty-server maintenance. Carried/equipped player inventories: NOT scanned or changed."); if (_audit.Fault != null) { ((BaseUnityPlugin)this).Logger.LogError((object)("[backup-unavailable] deletion will be refused: " + _audit.Fault)); } } private void Update() { try { Tick(); } catch (Exception ex) { Warn("update", "[update-error] " + ex); } } private void Tick() { ZNet instance = ZNet.instance; ZDOMan instance2 = ZDOMan.instance; if (instance != _net || instance2 != _man) { ResetSession(instance, instance2); } if (!IsDedicatedServer) { return; } double now = Now; if (ConnectedPeers > 0) { _lastPeerSeen = now; } if (now >= _nextChecks) { _nextChecks = now + 1.0; _oldConflict = Chainloader.PluginInfos.ContainsKey("ageous27.nocheatitems") || Chainloader.PluginInfos.ContainsKey("local.valheim.itemintegrity"); if (_oldConflict) { Warn("old-plugin", "[paused] Disable NoCheatItems and the earlier client-required ItemIntegrity before using this replacement."); } if (_apiOk && !_oldConflict && !_nativeAttempted && instance2 != null && (Object)(object)ZNetScene.instance != (Object)null && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)ObjectDB.instance.GetItemPrefab("Wood") != (Object)null) { _nativeAttempted = true; try { GameAccess.NativeCodecSelfTest(); _nativeOk = true; Info("[native-codec PASS] version=109; real native item/inventory writer matched the checked decoder"); } catch (Exception ex) { _nativeOk = false; ((BaseUnityPlugin)this).Logger.LogError((object)("[paused] Native serialization check failed; deletion disabled: " + ex)); } } ServerPolicy = Options.MakePolicy(Operational); } if (Operational && ServerPolicy.Enabled) { _world.Tick(ServerPolicy); if (now >= _nextLocal) { _nextLocal = now + (double)Options.SweepSeconds.Value; ServerLocalCleaner.Counts counts = _local.Sweep(ServerPolicy); if (counts.Inspected > 0) { Info("[server-local-world-sweep] inspected=" + counts.Inspected + " matched=" + counts.Matched + " removed=" + counts.Removed + " storageWritesAllowed=" + StorageWritesAllowed + " dryRun=" + ServerPolicy.DryRun); } } } if (now >= _nextSummary) { _nextSummary = now + 30.0; Info("[status] " + Status()); } } private void ResetSession(ZNet net, ZDOMan man) { _world = new WorldCleaner(this); _net = net; _man = man; _nativeOk = false; _nativeAttempted = false; _nextLocal = (_nextSummary = (_nextChecks = 0.0)); _lastPeerSeen = Now; ServerPolicy = new Policy { Enabled = false }; _rate.Clear(); } internal bool Backup(string kind, string context, byte[] bytes) { string path; bool num = _audit.Snapshot(kind, context, bytes, Options.QuarantineMiB.Value, out path); if (!num) { Warn("audit", "[deletion-blocked] backup failed: " + _audit.Fault); } return num; } internal void Info(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } internal void Warn(string key, string message) { Throttled(key, message, warning: true); } internal void Note(string key, string message) { Throttled(key, message, warning: false); } private void Throttled(string key, string message, bool warning) { if (!_rate.TryGetValue(key, out var value) || !(Now < value)) { if (_rate.Count >= 4096) { _rate.Clear(); } _rate[key] = Now + 30.0; if (warning) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } } } internal static string Label(string value) { string text = (value ?? "").Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' '); if (text.Length <= 180) { return text; } return text.Substring(0, 180); } internal string Status() { string text = (((Object)(object)ZNet.instance == (Object)null) ? "menu" : (IsDedicatedServer ? "dedicated-server" : "inactive-non-dedicated-process")); return "release=0.2.0-preview.1 (source preview; staging required) role=" + text + " api=" + _apiOk + " nativeCodec=" + _nativeOk + " conflictingCleaner=" + _oldConflict + " enabled=" + ServerPolicy.Enabled + " dryRun=" + ServerPolicy.DryRun + " peers=" + ConnectedPeers + " emptySeconds=" + EmptySeconds.ToString("F1") + " storageWritesAllowed=" + StorageWritesAllowed + " playerBags=not-covered auditFault=" + Label(_audit?.Fault ?? "none") + " " + _world.Status; } internal void CommandScan(Terminal terminal) { if (!IsDedicatedServer) { terminal.AddString("This command only acts inside the dedicated-server process."); return; } _world.Request(); _nextLocal = 0.0; terminal.AddString("World scan requested. It does not bypass dry-run, backups, or the empty-server storage gate. " + Status()); } internal void CommandReload(Terminal terminal) { if (!IsDedicatedServer) { terminal.AddString("Run reload in the dedicated-server console."); return; } ((BaseUnityPlugin)this).Config.Reload(); ServerPolicy = Options.MakePolicy(Operational); _world = new WorldCleaner(this); _lastPeerSeen = Now; terminal.AddString("Server settings reloaded; stored-item grace reset. No client action required. " + Status()); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if (Instance == this) { Instance = null; } } } internal sealed class ServerLocalCleaner { internal struct Counts { internal int Inspected; internal int Matched; internal int Removed; } private readonly Plugin _p; private bool _busy; internal ServerLocalCleaner(Plugin plugin) { _p = plugin; } internal unsafe Counts Sweep(Policy policy) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) Counts counts = default(Counts); if (_busy || !Plugin.IsDedicatedServer || policy == null || !policy.Enabled || !_p.Operational) { return counts; } _busy = true; try { HashSet<Inventory> hashSet = new HashSet<Inventory>(); if (policy.ScanContainers) { Container[] array = Object.FindObjectsByType<Container>((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (Container val in array) { if ((Object)(object)val == (Object)null) { continue; } try { ZNetView val2 = GameAccess.View(val); if (Owned(val2) && GameAccess.ContainerReady(val, val2.GetZDO())) { Inventory inventory = val.GetInventory(); if (inventory != null && hashSet.Add(inventory)) { string text = Plugin.Label(((Object)val).name); ZDOID uid = val2.GetZDO().m_uid; CleanInventory(inventory, val2, "container=" + text + " zdo=" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(), policy, ref counts); } } } catch (Exception ex) { _p.Warn("local-container", "[local-scan-error] " + Plugin.Label(ex.Message)); } } } if (policy.ScanStands) { ItemStand[] array2 = Object.FindObjectsByType<ItemStand>((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (ItemStand s in array2) { if ((Object)(object)s == (Object)null) { continue; } ZNetView val3 = GameAccess.View(s); if (Owned(val3)) { CleanAttachment(val3.GetZDO(), -1, delegate { s.RPC_DestroyAttachment(ZNet.GetUID()); }, policy, ref counts); } } ArmorStand[] array3 = Object.FindObjectsByType<ArmorStand>((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (ArmorStand s2 in array3) { if ((Object)(object)s2 == (Object)null || s2.m_slots == null) { continue; } ZNetView val4 = GameAccess.View(s2); if (!Owned(val4)) { continue; } for (int num = 0; num < Math.Min(s2.m_slots.Count, 64); num++) { int slot = num; CleanAttachment(val4.GetZDO(), slot, delegate { s2.RPC_DestroyAttachment(ZNet.GetUID(), slot); }, policy, ref counts); } } } } finally { _busy = false; } return counts; } private static bool Owned(ZNetView view) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)view == (Object)null || !view.IsValid() || !view.IsOwner() || ZDOMan.instance == null) { return false; } ZDO zDO = view.GetZDO(); if (zDO != null) { return ZDOMan.instance.GetZDO(zDO.m_uid) == zDO; } return false; } private void CleanInventory(Inventory inventory, ZNetView view, string context, Policy policy, ref Counts counts) { ItemData[] array = inventory.GetAllItems().ToArray(); foreach (ItemData val in array) { if (!Owned(view)) { break; } if (val == null || !inventory.ContainsItem(val)) { continue; } counts.Inspected++; string text = (((Object)(object)val.m_dropPrefab != (Object)null) ? ((Object)val.m_dropPrefab).name : "<missing prefab>"); string text2 = policy.Reason(val.m_cheated, text); if (text2 == null) { continue; } counts.Matched++; string text3 = context + " item=" + Plugin.Label(text) + " stack=" + val.m_stack + " reason=" + text2; if (policy.DryRun) { _p.Note("dry:" + context + ":" + text, "[dry-run server-local-container] " + text3); continue; } if (!_p.StorageWritesAllowed || counts.Removed >= _p.Options.ActionsPerSweep.Value) { _p.Note("defer-local:" + context, "[deferred stored-items] " + context + " waiting for an empty server and edit budget"); continue; } try { byte[] array2 = GameAccess.SaveItem(val); ItemCodec.Record record = ItemCodec.ReadDrop(array2); if (record.Stack != val.m_stack || record.Quality != val.m_quality) { throw new InvalidDataException("Native serialization changed stack/quality; refusing lossy backup"); } if (_p.Backup("inventory-item", text3, array2) && _p.StorageWritesAllowed && Owned(view) && inventory.ContainsItem(val) && policy.Reason(val.m_cheated, text) != null) { bool flag = inventory.RemoveItem(val); bool flag2 = !inventory.ContainsItem(val); if (flag && flag2) { counts.Removed++; _p.Info("[removed server-local-container] " + text3); continue; } _p.Warn("remove:" + context, "[remove-unconfirmed] " + text3 + " nativeResult=" + flag + " absent=" + flag2); } } catch (Exception ex) { _p.Warn("remove-error:" + context, "[remove-error] " + text3 + "; an observer may have run after mutation: " + Plugin.Label(ex.Message)); } } } private unsafe void CleanAttachment(ZDO z, int slot, Action remove, Policy policy, ref Counts counts) { //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) int num = GameAccess.AttachedKey(slot); int num2 = z.GetInt(num, 0); if (num2 == 0) { return; } counts.Inspected++; try { byte[] byteArray = z.GetByteArray(GameAccess.ItemDataKey(slot), (byte[])null); if (byteArray == null || byteArray.Length == 0) { return; } ItemCodec.Record record = ItemCodec.ReadDrop(byteArray); if (record.PrefabHash != 0 && record.PrefabHash != num2) { throw new InvalidDataException("Attachment hash mismatch"); } string text = GameAccess.PrefabName(num2); string text2 = policy.Reason(record.Cheated, text); if (text2 == null) { return; } counts.Matched++; string[] obj = new string[8] { "stand=", null, null, null, null, null, null, null }; ZDOID uid = z.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " slot="; obj[3] = slot.ToString(); obj[4] = " item="; obj[5] = Plugin.Label(text); obj[6] = " reason="; obj[7] = text2; string text3 = string.Concat(obj); if (policy.DryRun) { _p.Note("dry:" + text3, "[dry-run attachment] " + text3); } else if (_p.StorageWritesAllowed && counts.Removed < _p.Options.ActionsPerSweep.Value && _p.Backup("attachment", text3, byteArray) && _p.StorageWritesAllowed && z.IsOwner() && ZDOMan.instance.GetZDO(z.m_uid) == z && z.GetInt(num, 0) == num2 && byteArray.SequenceEqual(z.GetByteArray(GameAccess.ItemDataKey(slot), (byte[])null) ?? Array.Empty<byte>())) { remove(); if (z.GetInt(num, 0) == 0) { counts.Removed++; _p.Info("[removed attachment] " + text3); } else { _p.Warn("stand:" + text3, "[remove-unconfirmed attachment] " + text3); } } } catch (Exception ex) { Plugin p = _p; ZDOID uid = z.m_uid; p.Warn("stand:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString() + ":" + slot, "[attachment-skip] " + Plugin.Label(ex.Message)); } } } internal sealed class Settings { internal readonly ConfigEntry<bool> Enabled; internal readonly ConfigEntry<bool> DryRun; internal readonly ConfigEntry<bool> RemoveFlagged; internal readonly ConfigEntry<bool> RemoveKnown; internal readonly ConfigEntry<bool> ScanDrops; internal readonly ConfigEntry<bool> ScanContainers; internal readonly ConfigEntry<bool> ScanStands; internal readonly ConfigEntry<string> ExtraPrefabs; internal readonly ConfigEntry<float> SweepSeconds; internal readonly ConfigEntry<float> FrameMilliseconds; internal readonly ConfigEntry<int> RecordsPerFrame; internal readonly ConfigEntry<int> ActionsPerSweep; internal readonly ConfigEntry<int> QuarantineMiB; internal Settings(ConfigFile c) { //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Expected O, but got Unknown //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Expected O, but got Unknown Enabled = c.Bind<bool>("General", "Enabled", true, "Dedicated-server-only master switch. No client plugin is used."); DryRun = c.Bind<bool>("General", "DryRun", true, "SAFE DEFAULT: log matches without altering world data. Set false only after backups and staging validation."); RemoveFlagged = c.Bind<bool>("Detection", "RemoveFlaggedItems", true, "Remove records/items carrying the native cheated flag."); RemoveKnown = c.Bind<bool>("Detection", "RemoveKnownCheatPrefabs", true, "Remove SwordCheat and SledgeCheat."); ExtraPrefabs = c.Bind<string>("Detection", "ExtraPrefabNames", "", "Comma-separated exact prefab names. Deletes legitimate copies too when enabled. Maximum 4096 characters."); ScanDrops = c.Bind<bool>("Scan", "WorldDrops", true, "Dedicated-server ZDO scan of serialized ground items."); ScanContainers = c.Bind<bool>("Scan", "Containers", true, "Scan native container inventories. Removal waits until no peers are connected for at least 15 seconds; carried player inventory is not covered."); ScanStands = c.Bind<bool>("Scan", "ItemAndArmorStands", true, "Scan attached item flags. Removal waits until the server is empty for at least 15 seconds; the stand is retained."); SweepSeconds = c.Bind<float>("Server", "WorldSweepSeconds", 8f, new ConfigDescription("Delay between complete ZDO passes. Large worlds take longer than this to complete a pass.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 120f), Array.Empty<object>())); RecordsPerFrame = c.Bind<int>("Server", "RecordsPerFrame", 128, new ConfigDescription("ZDO scan batch limit. Snapshotting IDs is a separate main-thread operation.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16, 4096), Array.Empty<object>())); FrameMilliseconds = c.Bind<float>("Server", "FrameBudgetMilliseconds", 2f, new ConfigDescription("Soft per-frame scan budget; one parse or disk write can exceed it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), Array.Empty<object>())); ActionsPerSweep = c.Bind<int>("Safety", "MaximumEditsPerWorldSweep", 100, new ConfigDescription("Maximum world-object edits per pass; excess matches wait for later passes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>())); QuarantineMiB = c.Bind<int>("Safety", "MaximumQuarantineMiB", 256, new ConfigDescription("Local backup cap. Reaching it blocks deletion; nothing is automatically pruned.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16, 8192), Array.Empty<object>())); } internal Policy MakePolicy(bool ready) { string text = ExtraPrefabs.Value ?? ""; if (text.Length > 4096) { throw new InvalidOperationException("ExtraPrefabNames is too long"); } return new Policy { Enabled = (ready && Enabled.Value), DryRun = DryRun.Value, RemoveFlagged = RemoveFlagged.Value, RemoveKnown = RemoveKnown.Value, ScanDrops = ScanDrops.Value, ScanContainers = ScanContainers.Value, ScanStands = ScanStands.Value, ExtraPrefabs = text }; } } internal sealed class WorldCleaner { private sealed class Candidate { internal string Hash; internal long Owner; internal uint Revision; internal double Since; internal double Seen; } private sealed class Shape { internal bool Drop; internal bool Container; internal bool Stand; internal int ArmorSlots; internal string Name; } private readonly Plugin _p; private ZDOID[] _ids; private int _cursor; private int _visited; private int _drops; private int _inventories; private int _stands; private int _matches; private int _edits; private int _deferred; private int _skipped; private double _nextSweep; private readonly Dictionary<ZDOID, double> _pendingDrops = new Dictionary<ZDOID, double>(); private readonly Dictionary<string, Candidate> _stable = new Dictionary<string, Candidate>(); private readonly Dictionary<int, Shape> _shapes = new Dictionary<int, Shape>(); internal string Status { get { string[] obj = new string[8] { "inPass=", (_ids != null).ToString(), " cursor=", _cursor.ToString(), "/", null, null, null }; ZDOID[] ids = _ids; obj[5] = ((ids != null) ? ids.Length : 0).ToString(); obj[6] = " pendingDropDeletes="; obj[7] = _pendingDrops.Count.ToString(); return string.Concat(obj); } } internal WorldCleaner(Plugin p) { _p = p; } internal void Request() { if (_ids == null) { _nextSweep = 0.0; } } internal unsafe void Tick(Policy policy) { //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: 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) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) if (policy == null || !policy.Enabled || !Plugin.IsDedicatedServer || !_p.Operational || ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { return; } ConfirmDeletes(); double now = Plugin.Now; if (_ids == null) { if (now < _nextSweep) { return; } Dictionary<ZDOID, ZDO> dictionary = GameAccess.Objects(ZDOMan.instance); if (dictionary.Count > 2000000) { _p.Warn("snapshot-cap", "[world-paused] more than two million ZDOs; snapshot safety cap reached"); _nextSweep = now + 30.0; return; } _ids = (ZDOID[])(object)new ZDOID[dictionary.Count]; dictionary.Keys.CopyTo(_ids, 0); _cursor = (_visited = (_drops = (_inventories = (_stands = (_matches = (_edits = (_deferred = (_skipped = 0)))))))); string[] array = (from kv in _stable where now - kv.Value.Seen > (double)Math.Max(120f, _p.Options.SweepSeconds.Value * 4f) select kv.Key).ToArray(); foreach (string key in array) { _stable.Remove(key); } } Stopwatch stopwatch = Stopwatch.StartNew(); int num2 = 0; while (_cursor < _ids.Length && num2++ < _p.Options.RecordsPerFrame.Value && stopwatch.Elapsed.TotalMilliseconds < (double)_p.Options.FrameMilliseconds.Value) { ZDOID val = _ids[_cursor++]; ZDO zDO = ZDOMan.instance.GetZDO(val); if (zDO != null) { _visited++; try { Inspect(zDO, policy); } catch (Exception ex) { _skipped++; Plugin p = _p; ZDOID val2 = val; string key2 = "world:" + ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString(); val2 = val; p.Warn(key2, "[world-skip] zdo=" + ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString() + " reason=" + Plugin.Label(ex.Message)); } } } if (_cursor == _ids.Length) { _p.Info("[world-sweep] zdos=" + _visited + " dropRecords=" + _drops + " inventories=" + _inventories + " standAttachments=" + _stands + " matchingStacks=" + _matches + " objectEdits=" + _edits + " deferred=" + _deferred + " skipped=" + _skipped + " storageWritesAllowed=" + _p.StorageWritesAllowed + " dryRun=" + policy.DryRun); _ids = null; _nextSweep = now + (double)_p.Options.SweepSeconds.Value; } } private Shape Classify(int hash) { if (_shapes.TryGetValue(hash, out var value)) { return value; } GameObject prefab = ZNetScene.instance.GetPrefab(hash); if ((Object)(object)prefab == (Object)null) { return null; } ZNetView root = prefab.GetComponent<ZNetView>(); if ((Object)(object)root == (Object)null) { return null; } bool container = prefab.GetComponentsInChildren<Container>(true).Any((Container c) => (Object)(object)(((Object)(object)c.m_rootObjectOverride != (Object)null) ? c.m_rootObjectOverride : ((Component)c).GetComponent<ZNetView>()) == (Object)(object)root); bool stand = prefab.GetComponentsInChildren<ItemStand>(true).Any((ItemStand s) => (Object)(object)(((Object)(object)s.m_netViewOverride != (Object)null) ? s.m_netViewOverride : ((Component)s).GetComponent<ZNetView>()) == (Object)(object)root); ArmorStand val = ((IEnumerable<ArmorStand>)prefab.GetComponentsInChildren<ArmorStand>(true)).FirstOrDefault((Func<ArmorStand, bool>)((ArmorStand s) => (Object)(object)(((Object)(object)s.m_netViewOverride != (Object)null) ? s.m_netViewOverride : ((Component)s).GetComponent<ZNetView>()) == (Object)(object)root)); Shape shape = new Shape { Name = ((Object)prefab).name, Drop = ((Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null), Container = container, Stand = stand, ArmorSlots = (((Object)(object)val != (Object)null && val.m_slots != null) ? Math.Min(val.m_slots.Count, 64) : 0) }; if (_shapes.Count < 10000) { _shapes[hash] = shape; } return shape; } private void Inspect(ZDO z, Policy policy) { Shape shape = Classify(z.GetPrefab()); if (shape == null) { _skipped++; return; } if (shape.Drop && policy.ScanDrops) { InspectDrop(z, shape, policy); return; } if (shape.Container && policy.ScanContainers) { InspectContainer(z, shape, policy); } if (policy.ScanStands) { if (shape.Stand) { InspectAttachment(z, -1, policy); } for (int i = 0; i < shape.ArmorSlots; i++) { InspectAttachment(z, i, policy); } } } private unsafe void InspectDrop(ZDO z, Shape shape, Policy policy) { //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_0119: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0186: 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_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) byte[] byteArray = z.GetByteArray(ZDOVars.s_itemData, (byte[])null); if (byteArray == null || byteArray.Length == 0) { return; } _drops++; ItemCodec.Record record = ItemCodec.ReadDrop(byteArray); if (record.PrefabHash != 0 && record.PrefabHash != z.GetPrefab()) { throw new InvalidDataException("Drop prefab/record mismatch"); } string text = policy.Reason(record.Cheated, shape.Name); if (text == null) { return; } _matches++; string[] obj = new string[8] { "zdo=", null, null, null, null, null, null, null }; ZDOID uid = z.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " prefab="; obj[3] = Plugin.Label(shape.Name); obj[4] = " stack="; obj[5] = record.Stack.ToString(); obj[6] = " reason="; obj[7] = text; string text2 = string.Concat(obj); if (policy.DryRun) { Plugin p = _p; uid = z.m_uid; p.Note("dry-drop:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(), "[dry-run world-drop] " + text2); return; } if (_pendingDrops.ContainsKey(z.m_uid) || !HasBudget()) { _deferred++; return; } uint dataRevision = z.DataRevision; long owner = z.GetOwner(); if (!_p.Backup("world-drop", text2 + " owner=" + z.GetOwner() + " position=" + ((object)z.GetPosition()/*cast due to .constrained prefix*/).ToString(), byteArray)) { _deferred++; return; } ZDOID uid2 = z.m_uid; if (ZDOMan.instance.GetZDO(uid2) != z || z.DataRevision != dataRevision || z.GetOwner() != owner || !byteArray.SequenceEqual(z.GetByteArray(ZDOVars.s_itemData, (byte[])null) ?? Array.Empty<byte>())) { _deferred++; return; } z.SetOwner(ZNet.GetUID()); if (!z.IsOwner()) { throw new InvalidOperationException("Server could not claim drop ownership"); } ZDOMan.instance.DestroyZDO(z); _pendingDrops[uid2] = Plugin.Now; _edits++; _p.Info("[delete-requested world-drop] " + text2); } private unsafe void InspectContainer(ZDO z, Shape shape, Policy policy) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0109: 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_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) byte[] byteArray = z.GetByteArray(ZDOVars.s_items, (byte[])null); if (byteArray == null || byteArray.Length == 0) { return; } _inventories++; ItemCodec.InventoryData inventoryData = ItemCodec.ReadInventory(byteArray); HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < inventoryData.Records.Count; i++) { ItemCodec.Record record = inventoryData.Records[i]; if (policy.Reason(record.Cheated, GameAccess.PrefabName(record.PrefabHash)) != null) { hashSet.Add(i); } } if (hashSet.Count == 0) { return; } _matches += hashSet.Count; string[] obj = new string[6] { "zdo=", null, null, null, null, null }; ZDOID uid = z.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " container="; obj[3] = Plugin.Label(shape.Name); obj[4] = " matchingStacks="; obj[5] = hashSet.Count.ToString(); string text = string.Concat(obj); if (policy.DryRun) { Plugin p = _p; uid = z.m_uid; p.Note("dry-container:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(), "[dry-run container] " + text); return; } if (!HasBudget() || !MayEditRecord(z, "inventory", byteArray)) { _deferred++; return; } byte[] array = inventoryData.Without(hashSet); if (ItemCodec.ReadInventory(array).Records.Count != inventoryData.Records.Count - hashSet.Count) { throw new InvalidDataException("Filtered count mismatch"); } uint dataRevision = z.DataRevision; long owner = z.GetOwner(); if (!_p.Backup("container-inventory", text + " owner=" + owner + " position=" + ((object)z.GetPosition()/*cast due to .constrained prefix*/).ToString(), byteArray)) { _deferred++; return; } if (!_p.StorageWritesAllowed || ZDOMan.instance.GetZDO(z.m_uid) != z || z.DataRevision != dataRevision || z.GetOwner() != owner || !byteArray.SequenceEqual(z.GetByteArray(ZDOVars.s_items, (byte[])null) ?? Array.Empty<byte>())) { _deferred++; return; } z.SetOwner(ZNet.GetUID()); if (!z.IsOwner()) { throw new InvalidOperationException("Cannot claim stable inventory record"); } z.Set(ZDOVars.s_items, array); ZDOMan.instance.ForceSendZDO(z.m_uid); _edits++; Dictionary<string, Candidate> stable = _stable; uid = z.m_uid; stable.Remove(((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString() + ":inventory"); _p.Info("[written container-record] " + text + " retainedStacks=" + (inventoryData.Records.Count - hashSet.Count) + " (server data updated; client/restart persistence requires integration verification)"); } private unsafe void InspectAttachment(ZDO z, int slot, Policy policy) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) int num = GameAccess.AttachedKey(slot); int num2 = z.GetInt(num, 0); if (num2 == 0) { return; } byte[] byteArray = z.GetByteArray(GameAccess.ItemDataKey(slot), (byte[])null); if (byteArray == null || byteArray.Length == 0) { return; } _stands++; ItemCodec.Record record = ItemCodec.ReadDrop(byteArray); if (record.PrefabHash != 0 && record.PrefabHash != num2) { throw new InvalidDataException("Attachment record mismatch"); } string text = GameAccess.PrefabName(num2); string text2 = policy.Reason(record.Cheated, text); if (text2 == null) { return; } _matches++; string[] obj = new string[8] { "zdo=", null, null, null, null, null, null, null }; ZDOID uid = z.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " slot="; obj[3] = slot.ToString(); obj[4] = " item="; obj[5] = Plugin.Label(text); obj[6] = " reason="; obj[7] = text2; string text3 = string.Concat(obj); if (policy.DryRun) { _p.Note("dry-stand:" + text3, "[dry-run attachment-record] " + text3); return; } if (!HasBudget() || !MayEditRecord(z, "slot" + slot, byteArray)) { _deferred++; return; } uint dataRevision = z.DataRevision; long owner = z.GetOwner(); if (!_p.Backup("stand-item", text3 + " owner=" + owner, byteArray)) { _deferred++; return; } if (!_p.StorageWritesAllowed || ZDOMan.instance.GetZDO(z.m_uid) != z || z.DataRevision != dataRevision || z.GetOwner() != owner || z.GetInt(num, 0) != num2 || !byteArray.SequenceEqual(z.GetByteArray(GameAccess.ItemDataKey(slot), (byte[])null) ?? Array.Empty<byte>())) { _deferred++; return; } z.SetOwner(ZNet.GetUID()); if (!z.IsOwner()) { throw new InvalidOperationException("Cannot claim stable attachment"); } z.Set(num, 0, false); z.Set(GameAccess.ItemDataKey(slot), Array.Empty<byte>()); ZDOMan.instance.ForceSendZDO(z.m_uid); _edits++; Dictionary<string, Candidate> stable = _stable; uid = z.m_uid; stable.Remove(((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString() + ":slot" + slot); _p.Info("[written attachment-record] " + text3 + " (attachment cleared; stand retained)"); } private unsafe bool MayEditRecord(ZDO z, string kind, byte[] bytes) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) long owner = z.GetOwner(); uint dataRevision = z.DataRevision; bool flag = (Object)(object)ZNetScene.instance.FindInstance(z) != (Object)null; ZDOID uid; if (!_p.StorageWritesAllowed || flag) { Dictionary<string, Candidate> stable = _stable; uid = z.m_uid; stable.Remove(((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString() + ":" + kind); Plugin p = _p; uid = z.m_uid; string key = "defer:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); string[] obj = new string[9] { "[deferred stored-items] zdo=", null, null, null, null, null, null, null, null }; uid = z.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = " peers="; obj[3] = Plugin.ConnectedPeers.ToString(); obj[4] = " emptySeconds="; obj[5] = _p.EmptySeconds.ToString("F1"); obj[6] = " localInstance="; obj[7] = flag.ToString(); obj[8] = "; serialized maintenance waits for an empty server; live SERVER-owned components use their native inventory path"; p.Note(key, string.Concat(obj)); return false; } uid = z.m_uid; string key2 = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString() + ":" + kind; string text = AuditStore.Hash(bytes); double now = Plugin.Now; if (!_stable.TryGetValue(key2, out var value) || value.Hash != text || value.Owner != owner || value.Revision != dataRevision) { if (_stable.Count >= 20000 && !_stable.ContainsKey(key2)) { return false; } _stable[key2] = new Candidate { Hash = text, Owner = owner, Revision = dataRevision, Since = now, Seen = now }; return false; } value.Seen = now; return WorldWriteRules.MayRewrite(Plugin.ConnectedPeers, _p.EmptySeconds, flag, value.Revision, z.DataRevision, now - value.Since); } private bool HasBudget() { return _edits < _p.Options.ActionsPerSweep.Value; } private void ConfirmDeletes() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) KeyValuePair<ZDOID, double>[] array = _pendingDrops.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<ZDOID, double> keyValuePair = array[i]; if (ZDOMan.instance.GetZDO(keyValuePair.Key) == null) { _p.Info("[delete-confirmed server-record] zdo=" + ((object)keyValuePair.Key/*cast due to .constrained prefix*/).ToString() + " absent from ZDOMan"); _pendingDrops.Remove(keyValuePair.Key); } else if (Plugin.Now - keyValuePair.Value > 15.0) { _p.Warn("pending:" + ((object)keyValuePair.Key/*cast due to .constrained prefix*/).ToString(), "[delete-unconfirmed] zdo=" + ((object)keyValuePair.Key/*cast due to .constrained prefix*/).ToString() + " still present after 15s"); _pendingDrops.Remove(keyValuePair.Key); } } } } } namespace ItemIntegrity.Core { public static class ItemCodec { public sealed class Record { public int Offset; public int Length; public int PrefabHash; public int Stack; public int Quality; public bool Cheated; } public sealed class InventoryData { public readonly byte[] Original; public readonly List<Record> Records; internal InventoryData(byte[] original, List<Record> records) { Original = original; Records = records; } public byte[] Without(ISet<int> removedIndices) { if (removedIndices == null) { throw new ArgumentNullException("removedIndices"); } foreach (int removedIndex in removedIndices) { if (removedIndex < 0 || removedIndex >= Records.Count) { throw new ArgumentOutOfRangeException("removedIndices"); } } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(109); binaryWriter.Write(checked((ushort)(Records.Count - removedIndices.Count))); for (int i = 0; i < Records.Count; i++) { if (!removedIndices.Contains(i)) { Record record = Records[i]; binaryWriter.Write(Original, record.Offset, record.Length); } } binaryWriter.Flush(); return memoryStream.ToArray(); } } private sealed class Reader { private readonly byte[] _bytes; internal int Position { get; private set; } internal Reader(byte[] bytes) { _bytes = bytes ?? throw new ArgumentNullException("bytes"); if (bytes.Length > 2097152) { throw new InvalidDataException("Serialized data exceeds safety limit"); } } private void Need(int n) { if (n < 0 || n > _bytes.Length - Position) { throw new EndOfStreamException("Truncated item data"); } } internal byte Byte() { Need(1); return _bytes[Position++]; } internal void Skip(int n) { Need(n); Position += n; } internal int UInt16() { Need(2); int result = _bytes[Position] | (_bytes[Position + 1] << 8); Position += 2; return result; } internal int Int32() { Need(4); int position = Position; Position += 4; return _bytes[position] | (_bytes[position + 1] << 8) | (_bytes[position + 2] << 16) | (_bytes[position + 3] << 24); } internal int NumItems() { int num = Byte(); if ((num & 0x80) != 0) { return ((num & 0x7F) << 8) | Byte(); } return num; } internal void String() { uint num = 0u; bool flag = false; for (int i = 0; i <= 28; i += 7) { byte b = Byte(); if (i == 28 && (b & 0xF8) != 0) { throw new InvalidDataException("Invalid string length"); } num |= (uint)((b & 0x7F) << i); if ((b & 0x80) == 0) { flag = true; break; } } if (!flag || num > 2097152) { throw new InvalidDataException("Oversized string"); } Need((int)num); StrictUtf8.GetCharCount(_bytes, Position, (int)num); Position += (int)num; } internal void RequireEnd() { if (Position != _bytes.Length) { throw new InvalidDataException("Unknown trailing data; refusing to rewrite"); } } } public const int SupportedVersion = 109; public const int MaxBytes = 2097152; public const int MaxItems = 4096; private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); public static InventoryData ReadInventory(byte[] data) { Reader reader = new Reader(data); int num = reader.Int32(); if (num != 109) { throw new InvalidDataException("Unsupported inventory format " + num); } int num2 = reader.UInt16(); if (num2 > 4096) { throw new InvalidDataException("Inventory item count exceeds safety limit"); } List<Record> list = new List<Record>(num2); for (int i = 0; i < num2; i++) { list.Add(ReadRecord(reader)); } reader.RequireEnd(); return new InventoryData(data, list); } public static Record ReadDrop(byte[] data) { Reader reader = new Reader(data); int num = reader.Byte(); if (num != 109) { throw new InvalidDataException("Unsupported item format " + num); } Record result = ReadRecord(reader); reader.RequireEnd(); return result; } private static Record ReadRecord(Reader r) { int position = r.Position; r.Skip(4); r.Skip(3); byte num = r.Byte(); int num2 = (((num & 4) == 0) ? 1 : r.UInt16()); int num3 = (((num & 8) == 0) ? 1 : r.UInt16()); if ((num & 0x10) != 0) { r.Skip(4); } if ((num & 0x20) != 0) { r.Skip(8); r.String(); } int prefabHash = (((num & 0x40) != 0) ? r.Int32() : 0); int num4 = (((num & 0x80) != 0) ? r.NumItems() : 0); for (int i = 0; i < num4; i++) { r.String(); r.String(); } byte b = r.Byte(); if (num3 <= 0 || num2 <= 0) { throw new InvalidDataException("Invalid zero stack/quality; refusing to edit"); } return new Record { Offset = position, Length = r.Position - position, PrefabHash = prefabHash, Stack = num3, Quality = num2, Cheated = ((b & 1) != 0) }; } } public sealed class Policy { public bool Enabled = true; public bool DryRun = true; public bool RemoveFlagged = true; public bool RemoveKnown = true; public bool ScanDrops = true; public bool ScanContainers = true; public bool ScanStands = true; public string ExtraPrefabs = ""; private string _lastExtras; private HashSet<string> _extraSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public string Reason(bool cheated, string prefab) { if (!Enabled) { return null; } if (RemoveFlagged && cheated) { return "cheated flag"; } if (RemoveKnown && (string.Equals(prefab, "SwordCheat", StringComparison.OrdinalIgnoreCase) || string.Equals(prefab, "SledgeCheat", StringComparison.OrdinalIgnoreCase))) { return "debug prefab"; } string text = ExtraPrefabs ?? ""; if (!string.Equals(_lastExtras, text, StringComparison.Ordinal)) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(','); foreach (string text2 in array) { if (text2.Trim().Length > 0) { hashSet.Add(text2.Trim()); } } _extraSet = hashSet; _lastExtras = text; } if (prefab == null || !_extraSet.Contains(prefab)) { return null; } return "configured prefab"; } } public static class WorldWriteRules { public const double EmptyServerGraceSeconds = 15.0; public const double StableRecordSeconds = 2.0; public static bool MayRewrite(int connectedPeers, double emptyServerSeconds, bool instantiatedLocally, uint expectedRevision, uint currentRevision, double unchangedSeconds) { if (connectedPeers == 0 && !double.IsNaN(emptyServerSeconds) && !double.IsInfinity(emptyServerSeconds) && emptyServerSeconds >= 15.0 && !instantiatedLocally && expectedRevision == currentRevision && !double.IsNaN(unchangedSeconds) && !double.IsInfinity(unchangedSeconds)) { return unchangedSeconds >= 2.0; } return false; } } }