using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; using LevelGeneration; using Player; using SNetwork; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("StackProMax")] [assembly: AssemblyProduct("StackProMax")] [assembly: AssemblyCompany("HawChow")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] namespace HawChow.StackProMax; internal static class ConfigReloadPolicy { internal const int DebounceMilliseconds = 400; internal static bool ShouldReload(string appliedHash, string diskHash, long lastWriteUtcTicks, long nowUtcTicks, int debounceMilliseconds = 400) { if (string.IsNullOrEmpty(diskHash) || string.Equals(appliedHash, diskHash, StringComparison.Ordinal)) { return false; } if (lastWriteUtcTicks <= 0 || nowUtcTicks < lastWriteUtcTicks || debounceMilliseconds < 0) { return false; } long ticks = TimeSpan.FromMilliseconds(debounceMilliseconds).Ticks; return nowUtcTicks - lastWriteUtcTicks >= ticks; } } [HarmonyPatch(typeof(LG_PickupItem_Sync), "AttemptInteract")] internal static class PickupStackPatch { [HarmonyPrefix] private static bool Prefix(LG_PickupItem_Sync __instance, pPickupItemInteraction interaction) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_004d: Invalid comparison between Unknown and I4 //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Invalid comparison between Unknown and I4 //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected I4, but got Unknown //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: 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_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: 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) if (!Plugin.IsHost()) { return true; } Plugin.TryReloadConfig(); try { if ((Object)(object)__instance == (Object)null || (int)interaction.type != 0) { return true; } Item item = __instance.item; if ((Object)(object)item == (Object)null) { return true; } InventorySlot slot = item.pItemData.slot; StackKind stackKind = (((int)slot == 4) ? StackKind.ResourcePack : (((int)slot == 5) ? StackKind.Consumable : StackKind.None)); bool value; switch (stackKind) { case StackKind.None: return true; default: value = Plugin.ConsumablesEnabled.Value; break; case StackKind.ResourcePack: value = Plugin.ResourcePacksEnabled.Value; break; } bool flag = value; if (!flag) { return true; } int stackLimit = ((stackKind == StackKind.ResourcePack) ? Plugin.ResourcePackStackLimit.Value : Plugin.ConsumableStackLimit.Value); SNet_Player val = null; if (!((pPlayer)(ref interaction.pPlayer)).TryGetPlayer(ref val) || (Object)(object)val == (Object)null || !val.HasCharacterSlot) { return true; } PlayerBackpack val2 = default(PlayerBackpack); if (!PlayerBackpackManager.TryGetBackpack(val, ref val2) || val2 == null) { return true; } BackpackItem val3 = default(BackpackItem); bool flag2 = val2.TryGetBackpackItem(slot, ref val3) && val3 != null && (Object)(object)val3.Instance != (Object)null; if (!flag2) { return true; } Item instance = val3.Instance; bool flag3 = instance.pItemData.itemID_gearCRC == item.pItemData.itemID_gearCRC; if (!flag3) { return true; } PlayerAmmoStorage ammoStorage = val2.AmmoStorage; if (ammoStorage == null) { return true; } AmmoType val4 = (AmmoType)((stackKind == StackKind.ResourcePack) ? 3 : 5); float ammoInPack = ammoStorage.GetAmmoInPack(val4); float ammo = item.pItemData.custom.ammo; bool isPickupInteraction = (int)interaction.type == 0; bool featureEnabled = flag; if (!StackPolicy.TryPlan((int)slot, isHost: true, isPickupInteraction, featureEnabled, flag2, flag3, stackLimit, ammoInPack, ammo, out var plan)) { return true; } pItemData_Custom customData = item.GetCustomData(); return TryApplyPlan(__instance, val2, instance, val, ammoStorage, slot, item.pItemData.itemID_gearCRC, val4, ammoInPack, customData, plan); } catch (Exception ex) { Plugin.LogWarningOnce("Stack evaluation failed before mutation; vanilla pickup continues: " + ex.GetType().Name + ": " + ex.Message); return true; } } private static bool TryApplyPlan(LG_PickupItem_Sync sync, PlayerBackpack backpack, Item existingItem, SNet_Player player, PlayerAmmoStorage ammoStorage, InventorySlot inventorySlot, uint itemIdGearCrc, AmmoType ammoType, float originalStoredAmmo, pItemData_Custom originalCustomData, StackPlan plan) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) pItemData_Custom val = originalCustomData; val.ammo = plan.PickupAmmo; bool flag = false; bool flag2 = false; bool flag3 = false; try { flag = true; sync.SetCustomData(val, true); if (plan.RemoveExistingItem) { flag2 = true; PlayerBackpackManager.MasterRemoveItem(existingItem, player); } else if (plan.OverflowAmmo > 0f) { flag3 = true; ammoStorage.SetAmmo(ammoType, plan.OverflowAmmo); } return true; } catch (Exception ex) { bool flag4 = true; if (flag3) { try { ammoStorage.SetAmmo(ammoType, originalStoredAmmo); } catch { flag4 = false; } } if (flag2) { try { BackpackItem val2 = default(BackpackItem); if (!backpack.TryGetBackpackItem(inventorySlot, itemIdGearCrc, ref val2) || val2 == null || !((Object)(object)val2.Instance != (Object)null)) { PlayerBackpackManager.MasterAddItem(existingItem, player); } } catch { flag4 = false; } } if (flag) { try { sync.SetCustomData(originalCustomData, true); } catch { flag4 = false; } } Plugin.LogWarningOnce("Stack transaction failed; vanilla pickup cancelled and rollbackComplete=" + flag4 + " (" + ex.GetType().Name + ": " + ex.Message + ")."); return false; } } } [BepInPlugin("HawChow.StackProMax", "StackProMax", "1.0.1")] public sealed class Plugin : BasePlugin { internal static ManualLogSource PluginLog; internal static ConfigEntry ResourcePacksEnabled; internal static ConfigEntry ResourcePackStackLimit; internal static ConfigEntry ConsumablesEnabled; internal static ConfigEntry ConsumableStackLimit; private static ConfigFile _configFile; private static string _appliedConfigHash; private static bool _masterReadWarningLogged; private static bool _stackErrorLogged; public override void Load() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown PluginLog = ((BasePlugin)this).Log; _configFile = ((BasePlugin)this).Config; ResourcePacksEnabled = ((BasePlugin)this).Config.Bind("ResourcePacks", "Enabled", true, "Allow the host to merge pickups into the matching resource-pack stack."); ResourcePackStackLimit = ((BasePlugin)this).Config.Bind("ResourcePacks", "MaxStackCount", 6, new ConfigDescription("Maximum full resource-pack units in one stack. Six units correspond to 120 normalized resource-pack ammo units.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 999), Array.Empty())); ConsumablesEnabled = ((BasePlugin)this).Config.Bind("Consumables", "Enabled", true, "Allow the host to merge pickups into the matching consumable stack."); ConsumableStackLimit = ((BasePlugin)this).Config.Bind("Consumables", "MaxStackCount", 6, new ConfigDescription("Maximum consumable ammo units in one matching stack.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 999), Array.Empty())); ((BasePlugin)this).Config.Save(); _appliedConfigHash = (TryHashConfig(out var _, out var _) ? HashConfigFile() : string.Empty); Harmony val = new Harmony("HawChow.StackProMax"); val.PatchAll(typeof(Plugin).Assembly); MethodBase methodBase = AccessTools.Method(typeof(LG_PickupItem_Sync), "AttemptInteract", (Type[])null, (Type[])null); if (methodBase == null || !HasOwnPatch(val, methodBase)) { throw new InvalidOperationException("Required host pickup stacking patch was not installed."); } ManualLogSource log = ((BasePlugin)this).Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(176, 6, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted("StackProMax"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted("1.0.1"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" loaded; host-authoritative pickup stacking active; "); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("resource packs="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ResourcePacksEnabled.Value); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", limit="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ResourcePackStackLimit.Value); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("; "); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("consumables="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ConsumablesEnabled.Value); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", limit="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted(ConsumableStackLimit.Value); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("; "); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("config hot reload checks the stable cfg before each host pickup interaction."); } log.LogInfo(val2); } internal static bool IsHost() { try { return SNet.IsMaster; } catch (Exception ex) { if (!_masterReadWarningLogged) { _masterReadWarningLogged = true; ManualLogSource pluginLog = PluginLog; if (pluginLog != null) { pluginLog.LogWarning((object)("SNet.IsMaster could not be read; stacking remains disabled on this peer: " + ex.Message)); } } return false; } } internal static void TryReloadConfig() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown if (_configFile == null) { return; } try { if (!TryHashConfig(out var hash, out var lastWriteUtcTicks)) { return; } long ticks = DateTime.UtcNow.Ticks; if (!ConfigReloadPolicy.ShouldReload(_appliedConfigHash, hash, lastWriteUtcTicks, ticks)) { return; } _configFile.Reload(); _appliedConfigHash = HashConfigFile(); ManualLogSource pluginLog = PluginLog; if (pluginLog != null) { bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(53, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Config hot-reloaded: resource packs="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ResourcePacksEnabled.Value); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ResourcePackStackLimit.Value); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; "); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("consumables="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ConsumablesEnabled.Value); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ConsumableStackLimit.Value); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } pluginLog.LogInfo(val); } } catch (Exception ex) { ManualLogSource pluginLog2 = PluginLog; if (pluginLog2 != null) { pluginLog2.LogWarning((object)("Config hot reload failed; previous values remain in use: " + ex.Message)); } } } internal static void LogWarningOnce(string message) { if (!_stackErrorLogged) { _stackErrorLogged = true; ManualLogSource pluginLog = PluginLog; if (pluginLog != null) { pluginLog.LogWarning((object)message); } } } private static bool HasOwnPatch(Harmony harmony, MethodBase target) { foreach (MethodBase patchedMethod in harmony.GetPatchedMethods()) { if (patchedMethod == target) { return true; } } return false; } private static bool TryHashConfig(out string hash, out long lastWriteUtcTicks) { hash = string.Empty; lastWriteUtcTicks = 0L; string text = ((_configFile == null) ? string.Empty : _configFile.ConfigFilePath); if (string.IsNullOrWhiteSpace(text) || !File.Exists(text)) { return false; } FileInfo fileInfo = new FileInfo(text); fileInfo.Refresh(); byte[] array = File.ReadAllBytes(text); FileInfo fileInfo2 = new FileInfo(text); fileInfo2.Refresh(); if (!fileInfo2.Exists || fileInfo.Length != fileInfo2.Length || fileInfo.LastWriteTimeUtc.Ticks != fileInfo2.LastWriteTimeUtc.Ticks || array.Length != fileInfo2.Length) { return false; } hash = Convert.ToHexString(SHA256.HashData(array)); lastWriteUtcTicks = fileInfo2.LastWriteTimeUtc.Ticks; return true; } private static string HashConfigFile() { if (!TryHashConfig(out var hash, out var _)) { return string.Empty; } return hash; } } internal static class PluginInfo { internal const string Guid = "HawChow.StackProMax"; internal const string Name = "StackProMax"; internal const string Version = "1.0.1"; } internal enum StackKind { None, ResourcePack, Consumable } internal readonly struct StackPlan { internal StackKind Kind { get; } internal float Capacity { get; } internal float PickupAmmo { get; } internal float OverflowAmmo { get; } internal bool RemoveExistingItem { get; } internal StackPlan(StackKind kind, float capacity, float pickupAmmo, float overflowAmmo, bool removeExistingItem) { Kind = kind; Capacity = capacity; PickupAmmo = pickupAmmo; OverflowAmmo = overflowAmmo; RemoveExistingItem = removeExistingItem; } } internal static class StackPolicy { internal const int ResourcePackSlot = 4; internal const int ConsumableSlot = 5; internal const int DefaultStackLimit = 6; internal const int MaximumStackLimit = 999; internal const float ResourcePackAmmoPerStack = 20f; internal static StackKind KindForSlot(int slot) { return slot switch { 4 => StackKind.ResourcePack, 5 => StackKind.Consumable, _ => StackKind.None, }; } internal static float CapacityFor(StackKind kind, int stackLimit) { if (stackLimit < 1 || stackLimit > 999) { return 0f; } return kind switch { StackKind.ResourcePack => (float)stackLimit * 20f, StackKind.Consumable => stackLimit, _ => 0f, }; } internal static bool TryPlan(int slot, bool isHost, bool isPickupInteraction, bool featureEnabled, bool hasBackpackItem, bool itemIdsMatch, int stackLimit, float storedAmmo, float incomingAmmo, out StackPlan plan) { plan = default(StackPlan); StackKind stackKind = KindForSlot(slot); if (stackKind == StackKind.None || !isHost || !isPickupInteraction || !featureEnabled || !hasBackpackItem || !itemIdsMatch || !float.IsFinite(storedAmmo) || !float.IsFinite(incomingAmmo) || storedAmmo < 0f || incomingAmmo <= 0f) { return false; } float num = CapacityFor(stackKind, stackLimit); if (num <= 0f || storedAmmo >= num) { return false; } float num2 = storedAmmo + incomingAmmo; if (!float.IsFinite(num2) || num2 <= storedAmmo) { return false; } if (num2 <= num) { plan = new StackPlan(stackKind, num, num2, 0f, removeExistingItem: true); return true; } plan = new StackPlan(stackKind, num, num, num2 - num, removeExistingItem: false); return true; } }