Decompiled source of StackProMax v1.0.1

BepInEx/plugins/StackProMax/StackProMax.dll

Decompiled 2 days ago
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<bool> ResourcePacksEnabled;

	internal static ConfigEntry<int> ResourcePackStackLimit;

	internal static ConfigEntry<bool> ConsumablesEnabled;

	internal static ConfigEntry<int> 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<bool>("ResourcePacks", "Enabled", true, "Allow the host to merge pickups into the matching resource-pack stack.");
		ResourcePackStackLimit = ((BasePlugin)this).Config.Bind<int>("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<int>(1, 999), Array.Empty<object>()));
		ConsumablesEnabled = ((BasePlugin)this).Config.Bind<bool>("Consumables", "Enabled", true, "Allow the host to merge pickups into the matching consumable stack.");
		ConsumableStackLimit = ((BasePlugin)this).Config.Bind<int>("Consumables", "MaxStackCount", 6, new ConfigDescription("Maximum consumable ammo units in one matching stack.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 999), Array.Empty<object>()));
		((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<string>("StackProMax");
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" ");
			((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("1.0.1");
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" loaded; host-authoritative pickup stacking active; ");
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("resource packs=");
			((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<bool>(ResourcePacksEnabled.Value);
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", limit=");
			((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(ResourcePackStackLimit.Value);
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("; ");
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("consumables=");
			((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<bool>(ConsumablesEnabled.Value);
			((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", limit=");
			((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(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<bool>(ResourcePacksEnabled.Value);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(ResourcePackStackLimit.Value);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("consumables=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(ConsumablesEnabled.Value);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(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;
	}
}