Decompiled source of BatchScrapping v2.1.3

BepInEx/plugins/BatchScrapping.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
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;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Pigeon;
using Sparroh.UI;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.UI;

[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("Sparroh")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("2.1.3.0")]
[assembly: AssemblyInformationalVersion("2.1.3")]
[assembly: AssemblyProduct("BatchScrapping")]
[assembly: AssemblyTitle("BatchScrapping")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.1.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
public static class ConfigManager
{
	private const float DebounceSeconds = 0.25f;

	private static ConfigFile config;

	private static ManualLogSource logger;

	private static FileSystemWatcher configWatcher;

	private static volatile bool reloadPending;

	private static float lastReloadTime;

	public static ConfigEntry<Key> TrashMarkKey { get; private set; }

	public static ConfigEntry<bool> EnableInstantScrapping { get; private set; }

	public static ConfigEntry<bool> EnableFixedTimer { get; private set; }

	public static ConfigEntry<float> FixedTimerDuration { get; private set; }

	public static void Initialize(ConfigFile configFile, ManualLogSource log)
	{
		//IL_0092: 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)
		config = configFile;
		logger = log;
		TrashMarkKey = config.Bind<Key>("Keybinds", "Trash Mark Keybind", (Key)34, "Key to toggle trash mark on upgrades");
		EnableInstantScrapping = config.Bind<bool>("General", "Instant Scrap", false, "Enable instant scrapping without hold timer");
		EnableFixedTimer = config.Bind<bool>("General", "Fixed Scrap Time", false, "Use fixed scrap timer instead of default (ignored when Instant Scrap is on)");
		FixedTimerDuration = config.Bind<float>("General", "Scrap Duration", 1f, "Duration in seconds for fixed scrap timer");
		ScrapHandlingMod.currentTrashKey = TrashMarkKey.Value;
		TrashMarkKey.SettingChanged += OnTrashMarkKeyChanged;
		try
		{
			SetupFileWatcher();
		}
		catch (Exception ex)
		{
			logger.LogError((object)("Error setting up config file watcher: " + ex.Message));
		}
	}

	public static void Tick()
	{
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		if (!reloadPending || Time.unscaledTime - lastReloadTime < 0.25f)
		{
			return;
		}
		reloadPending = false;
		lastReloadTime = Time.unscaledTime;
		try
		{
			config.Reload();
			ScrapHandlingMod.currentTrashKey = TrashMarkKey.Value;
			logger.LogInfo((object)"Config reloaded from disk.");
		}
		catch (Exception ex)
		{
			logger.LogError((object)("Error reloading config: " + ex.Message));
		}
	}

	public static void Dispose()
	{
		if (TrashMarkKey != null)
		{
			TrashMarkKey.SettingChanged -= OnTrashMarkKeyChanged;
		}
		if (configWatcher != null)
		{
			configWatcher.EnableRaisingEvents = false;
			configWatcher.Changed -= OnConfigFileChanged;
			configWatcher.Created -= OnConfigFileChanged;
			configWatcher.Renamed -= OnConfigFileChanged;
			configWatcher.Dispose();
			configWatcher = null;
		}
	}

	private static void SetupFileWatcher()
	{
		configWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.batchscrapping.cfg");
		configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
		configWatcher.Changed += OnConfigFileChanged;
		configWatcher.Created += OnConfigFileChanged;
		configWatcher.Renamed += OnConfigFileChanged;
		configWatcher.EnableRaisingEvents = true;
	}

	private static void OnConfigFileChanged(object sender, FileSystemEventArgs e)
	{
		reloadPending = true;
	}

	private static void OnTrashMarkKeyChanged(object sender, EventArgs e)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		ScrapHandlingMod.currentTrashKey = TrashMarkKey.Value;
	}
}
public static class InstantPatches
{
	private const float InstantDuration = 0.05f;

	private static Harmony _harmony;

	public static bool ShouldModifyDuration
	{
		get
		{
			if (ConfigManager.EnableInstantScrapping == null || !ConfigManager.EnableInstantScrapping.Value)
			{
				if (ConfigManager.EnableFixedTimer != null)
				{
					return ConfigManager.EnableFixedTimer.Value;
				}
				return false;
			}
			return true;
		}
	}

	public static void Initialize()
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Expected O, but got Unknown
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Expected O, but got Unknown
		try
		{
			_harmony = new Harmony("sparroh.batchscrapping.instant");
			MethodInfo methodInfo = AccessTools.Method(typeof(GearUpgradeUI), "HasUnlockAction", new Type[1] { typeof(UnlockActionParams).MakeByRefType() }, (Type[])null);
			if (methodInfo != null)
			{
				try
				{
					_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(InstantScrapPatches), "HasUnlockActionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					SparrohPlugin.Logger.LogInfo((object)"Instant Scrap: patched GearUpgradeUI.HasUnlockAction");
					return;
				}
				catch (Exception ex)
				{
					SparrohPlugin.Logger.LogError((object)("Failed to patch HasUnlockAction: " + ex.Message));
					return;
				}
			}
			SparrohPlugin.Logger.LogError((object)"Instant Scrap: could not find GearUpgradeUI.HasUnlockAction(out UnlockActionParams). Instant scrap disabled.");
		}
		catch (Exception ex2)
		{
			SparrohPlugin.Logger.LogError((object)("Critical error during Instant Scrap initialization: " + ex2.Message));
		}
	}

	public static void Destroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		_harmony = null;
	}

	public static float GetModifiedDuration(float original)
	{
		if (ConfigManager.EnableInstantScrapping != null && ConfigManager.EnableInstantScrapping.Value)
		{
			return 0.05f;
		}
		if (ConfigManager.EnableFixedTimer != null && ConfigManager.EnableFixedTimer.Value)
		{
			return Mathf.Max(0.05f, ConfigManager.FixedTimerDuration.Value);
		}
		return original;
	}
}
public static class InstantScrapPatches
{
	public static void HasUnlockActionPostfix(ref UnlockActionParams data)
	{
		try
		{
			if (data.OnSecondaryComplete != null && InstantPatches.ShouldModifyDuration)
			{
				data.SecondaryDuration = InstantPatches.GetModifiedDuration(data.SecondaryDuration);
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("Error in HasUnlockActionPostfix: " + ex.Message));
		}
	}
}
[BepInPlugin("sparroh.batchscrapping", "BatchScrapping", "2.1.3")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[MycoMod(/*Could not decode attribute arguments.*/)]
public class SparrohPlugin : BaseUnityPlugin
{
	public const string PluginGUID = "sparroh.batchscrapping";

	public const string PluginName = "BatchScrapping";

	public const string PluginVersion = "2.1.3";

	internal static ManualLogSource Logger;

	public static SparrohPlugin Instance;

	private bool _barRegistered;

	private bool _lastUndoCan;

	private int _lastUndoCount = -1;

	private void Awake()
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Expected O, but got Unknown
		//IL_0138: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Expected O, but got Unknown
		//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Expected O, but got Unknown
		//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0203: Expected O, but got Unknown
		//IL_038c: Unknown result type (might be due to invalid IL or missing references)
		//IL_039a: Expected O, but got Unknown
		//IL_0239: Unknown result type (might be due to invalid IL or missing references)
		//IL_024e: Unknown result type (might be due to invalid IL or missing references)
		//IL_025b: Expected O, but got Unknown
		//IL_025b: Expected O, but got Unknown
		//IL_028c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0293: Expected O, but got Unknown
		try
		{
			Logger = ((BaseUnityPlugin)this).Logger;
			Instance = this;
			Harmony val = new Harmony("sparroh.batchscrapping");
			try
			{
				ConfigManager.Initialize(((BaseUnityPlugin)this).Config, Logger);
				ScrapHandlingMod.ScrapMarkedAction = delegate
				{
					ScrapHandlingMod.TryScrapMarkedUpgrades((MonoBehaviour)(object)this);
				};
				ScrapHandlingMod.ScrapNonFavoriteAction = delegate
				{
					ScrapHandlingMod.TryScrapNonFavoriteUpgrades((MonoBehaviour)(object)this);
				};
				ScrapHandlingMod.LoadTrashSprite();
			}
			catch (Exception ex)
			{
				Logger.LogError((object)("Failed to setup configuration bindings: " + ex.Message));
			}
			try
			{
				InstantPatches.Initialize();
			}
			catch (Exception ex2)
			{
				Logger.LogError((object)("Failed to initialize Instant Scrap: " + ex2.Message));
			}
			try
			{
				val.PatchAll();
			}
			catch (Exception ex3)
			{
				Logger.LogError((object)("Failed to apply Harmony patches: " + ex3.Message));
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(GearDetailsWindow), "Update", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					val.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ScrapUIPatches), "UpdatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				MethodInfo methodInfo2 = AccessTools.Method(typeof(GearUpgradeUI), "UpdateFavoriteIcon", (Type[])null, (Type[])null);
				if (methodInfo2 != null)
				{
					val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(ScrapUIPatches), "UpdateFavoriteIconPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				MethodInfo methodInfo3 = AccessTools.Method(typeof(GearUpgradeUI), "OnAdditionalAction", new Type[2]
				{
					typeof(int),
					typeof(bool).MakeByRefType()
				}, (Type[])null);
				if (methodInfo3 != null)
				{
					val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(ScrapUIPatches), "OnAdditionalActionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				MethodInfo methodInfo4 = AccessTools.Method(typeof(GearUpgradeUI), "EnableGridView", new Type[1] { typeof(bool) }, (Type[])null);
				if (methodInfo4 != null)
				{
					val.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(ScrapUIPatches), "EnableGridViewPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				MethodInfo methodInfo5 = AccessTools.Method(typeof(GearUpgradeUI), "Dismantle", (Type[])null, (Type[])null);
				if (methodInfo5 != null)
				{
					val.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(ScrapUndoPatches), "DismantlePrefix", (Type[])null), new HarmonyMethod(typeof(ScrapUndoPatches), "DismantlePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Logger.LogInfo((object)"Patched GearUpgradeUI.Dismantle for scrap undo.");
				}
				else
				{
					Logger.LogError((object)"Could not find GearUpgradeUI.Dismantle — single-scrap undo unavailable.");
				}
				HarmonyMethod val2 = new HarmonyMethod(typeof(BatchPickupFeedbackPatches), "ShowPickupInfoPrefix", (Type[])null);
				int num = 0;
				foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(GameManager)))
				{
					if (!(declaredMethod == null) && !(declaredMethod.Name != "ShowPickupInfo"))
					{
						try
						{
							val.Patch((MethodBase)declaredMethod, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							num++;
						}
						catch (Exception ex4)
						{
							Logger.LogWarning((object)$"Failed to patch ShowPickupInfo ({declaredMethod}): {ex4.Message}");
						}
					}
				}
				if (num > 0)
				{
					Logger.LogInfo((object)$"Patched {num} GameManager.ShowPickupInfo overload(s) for batch toast consolidation.");
				}
				else
				{
					Logger.LogWarning((object)"Could not find GameManager.ShowPickupInfo — batch pickup toasts may spam.");
				}
				MethodInfo methodInfo6 = AccessTools.Method(typeof(PlayerResource), "PlayPickupSound", (Type[])null, (Type[])null);
				if (methodInfo6 != null)
				{
					val.Patch((MethodBase)methodInfo6, new HarmonyMethod(typeof(BatchPickupFeedbackPatches), "PlayPickupSoundPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Logger.LogInfo((object)"Patched PlayerResource.PlayPickupSound for batch scrap.");
				}
			}
			catch (Exception ex5)
			{
				Logger.LogError((object)("Failed to apply scrap patches: " + ex5.Message));
			}
		}
		catch (Exception ex6)
		{
			Logger.LogError((object)("Critical error during mod initialization: " + ex6.Message + "\n" + ex6.StackTrace));
		}
		Logger.LogInfo((object)"BatchScrapping v2.1.3 loaded successfully.");
	}

	private void Update()
	{
		ConfigManager.Tick();
		GearActionBar.Tick();
		if (!GearActionBar.IsGearMenuOpen())
		{
			return;
		}
		if (!_barRegistered)
		{
			GearActionBar.Register("scrap_marked", "Scrap Marked", 120, (Action)delegate
			{
				UIDialog.Confirm("Scrap Marked", "Scrap all trash-marked upgrades? This can be undone.", (Action)delegate
				{
					ScrapHandlingMod.ScrapMarkedAction?.Invoke();
				}, (Action)null, "Confirm", "Cancel");
			}, (UIButtonStyle)0);
			GearActionBar.Register("scrap_nonfav", "Scrap No-Fav", 130, (Action)delegate
			{
				UIDialog.Confirm("Scrap Non-Favorite", "Scrap ALL non-favorite upgrades? This can be undone.", (Action)delegate
				{
					ScrapHandlingMod.ScrapNonFavoriteAction?.Invoke();
				}, (Action)null, "Confirm", "Cancel");
			}, (UIButtonStyle)2);
			GearActionBar.Register("undo_scrap", "Undo Scrap", 140, (Action)delegate
			{
				UndoPatches.TryUndo();
			}, (UIButtonStyle)1);
			_barRegistered = true;
			_lastUndoCan = !UndoPatches.CanUndo;
			_lastUndoCount = -1;
		}
		bool canUndo = UndoPatches.CanUndo;
		int undoCount = UndoPatches.UndoCount;
		if (canUndo != _lastUndoCan || undoCount != _lastUndoCount)
		{
			_lastUndoCan = canUndo;
			_lastUndoCount = undoCount;
			GearActionBar.SetInteractable("undo_scrap", canUndo);
			GearActionBar.SetText("undo_scrap", canUndo ? $"Undo ({undoCount})" : "Undo");
		}
	}

	private void OnDestroy()
	{
		try
		{
			ConfigManager.Dispose();
			InstantPatches.Destroy();
			UndoPatches.Clear();
			GearActionBar.Unregister("scrap_marked");
			GearActionBar.Unregister("scrap_nonfav");
			GearActionBar.Unregister("undo_scrap");
			_barRegistered = false;
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Failed to destroy BatchScrapping UI: " + ex.Message));
		}
	}
}
public static class ScrapHandlingMod
{
	private const float HOLD_DURATION = 1f;

	private const int BATCH_SIZE = 1000000;

	private const float BATCH_INTERVAL = 0f;

	private const byte FavoriteFlag = 1;

	private const byte TrashMarkFlag = 32;

	private static bool wasScrappingSkins;

	public static Key currentTrashKey;

	public static Action ScrapMarkedAction;

	public static Action ScrapNonFavoriteAction;

	public static Sprite starSprite;

	public static Sprite trashSprite;

	public static bool IsScrapping { get; private set; }

	public static bool SuppressPickupFeedback { get; private set; }

	public static void LoadTrashSprite()
	{
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Expected O, but got Unknown
		//IL_0122: 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)
		if ((Object)(object)trashSprite != (Object)null)
		{
			return;
		}
		try
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			string text = null;
			string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
			foreach (string text2 in manifestResourceNames)
			{
				if (text2.EndsWith("trashcan.png", StringComparison.OrdinalIgnoreCase))
				{
					text = text2;
					break;
				}
			}
			if (text == null)
			{
				SparrohPlugin.Logger.LogWarning((object)"trashcan.png embedded resource not found; trash marks will use red star.");
				return;
			}
			using Stream stream = executingAssembly.GetManifestResourceStream(text);
			if (stream == null)
			{
				SparrohPlugin.Logger.LogWarning((object)"Failed to open trashcan.png resource stream.");
				return;
			}
			byte[] array = new byte[stream.Length];
			int num;
			for (int j = 0; j < array.Length; j += num)
			{
				if ((num = stream.Read(array, j, array.Length - j)) <= 0)
				{
					break;
				}
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			if (!ImageConversion.LoadImage(val, array, false))
			{
				SparrohPlugin.Logger.LogWarning((object)"Failed to decode trashcan.png.");
				Object.Destroy((Object)(object)val);
				return;
			}
			((Texture)val).filterMode = (FilterMode)1;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			((Object)val).name = "BatchScrapping_Trashcan";
			trashSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
			((Object)trashSprite).name = "BatchScrapping_Trashcan";
			SparrohPlugin.Logger.LogInfo((object)$"Loaded trashcan sprite ({((Texture)val).width}x{((Texture)val).height}).");
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("Failed to load trashcan sprite: " + ex.Message));
		}
	}

	public static Sprite GetTrashIconSprite()
	{
		Sprite obj;
		if (!((Object)(object)trashSprite != (Object)null))
		{
			obj = starSprite;
			if (obj == null)
			{
				return Resources.Load<Sprite>("favorite star");
			}
		}
		else
		{
			obj = trashSprite;
		}
		return obj;
	}

	public static void ApplyMarkIcon(Image favoriteIcon, UpgradeInstance upgrade)
	{
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)favoriteIcon == (Object)null || upgrade == null)
		{
			return;
		}
		if (IsFavorite(upgrade))
		{
			if ((Object)(object)starSprite == (Object)null && (Object)(object)favoriteIcon.sprite != (Object)null && (Object)(object)favoriteIcon.sprite != (Object)(object)trashSprite)
			{
				starSprite = favoriteIcon.sprite;
			}
			favoriteIcon.sprite = starSprite ?? favoriteIcon.sprite;
			((Component)favoriteIcon).gameObject.SetActive(true);
			((Graphic)favoriteIcon).color = Color.white;
		}
		else if (IsTrashMarked(upgrade))
		{
			if ((Object)(object)starSprite == (Object)null && (Object)(object)favoriteIcon.sprite != (Object)null && (Object)(object)favoriteIcon.sprite != (Object)(object)trashSprite)
			{
				starSprite = favoriteIcon.sprite;
			}
			favoriteIcon.sprite = GetTrashIconSprite();
			((Component)favoriteIcon).gameObject.SetActive(true);
			((Graphic)favoriteIcon).color = Color.red;
		}
		else
		{
			((Component)favoriteIcon).gameObject.SetActive(false);
		}
	}

	private static bool TryScrapInstance(UpgradeInstance inst)
	{
		if (inst == null)
		{
			return false;
		}
		Upgrade upgrade;
		try
		{
			upgrade = inst.Upgrade;
		}
		catch
		{
			return false;
		}
		if ((Object)(object)upgrade == (Object)null)
		{
			return false;
		}
		try
		{
			PlayerData.UnequipFromAll(inst);
		}
		catch
		{
		}
		try
		{
			if (!inst.Destroy())
			{
				return false;
			}
			upgrade.GiveDismantleResources(inst);
			return true;
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("TryScrapInstance failed: " + ex.Message));
			return false;
		}
	}

	private static IEnumerator FinishBatchScrapCoroutine(bool success, bool isSkinsMode)
	{
		try
		{
			if (success)
			{
				UndoPatches.EndBatch();
			}
			else
			{
				UndoPatches.CancelBatch();
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("FinishBatchScrap EndBatch failed: " + ex.Message));
			UndoPatches.CancelBatch();
			success = false;
		}
		SuppressPickupFeedback = false;
		if (success)
		{
			try
			{
				wasScrappingSkins = isSkinsMode;
				RefreshOpenWindows();
			}
			catch (Exception ex2)
			{
				SparrohPlugin.Logger.LogError((object)("FinishBatchScrap refresh failed: " + ex2.Message));
			}
			yield return null;
			try
			{
				BatchPickupFeedbackPatches.FlushAccumulatedPickups();
			}
			catch (Exception ex3)
			{
				SparrohPlugin.Logger.LogWarning((object)("FinishBatchScrap popup flush failed: " + ex3.Message));
			}
		}
		else
		{
			BatchPickupFeedbackPatches.ClearAccumulated();
		}
		IsScrapping = false;
	}

	public static IEnumerator ScrapMarkedUpgrades()
	{
		int num = 0;
		bool flag;
		List<UpgradeInstance> list;
		bool flag2;
		try
		{
			if (IsScrapping)
			{
				SparrohPlugin.Logger.LogWarning((object)"ScrapMarkedUpgrades: Already scrapping, aborting.");
				yield break;
			}
			IsScrapping = true;
			SuppressPickupFeedback = true;
			BatchPickupFeedbackPatches.ClearAccumulated();
			SparrohPlugin.Logger.LogInfo((object)"Starting ScrapMarkedUpgrades operation.");
			GearDetailsWindow val = Object.FindObjectOfType<GearDetailsWindow>();
			if ((Object)(object)val == (Object)null)
			{
				SparrohPlugin.Logger.LogError((object)"ScrapMarkedUpgrades: GearDetailsWindow not found.");
				SuppressPickupFeedback = false;
				IsScrapping = false;
				yield break;
			}
			IUpgradable upgradablePrefab = val.UpgradablePrefab;
			if (upgradablePrefab == null)
			{
				SparrohPlugin.Logger.LogError((object)"ScrapMarkedUpgrades: UpgradablePrefab is null.");
				SuppressPickupFeedback = false;
				IsScrapping = false;
				yield break;
			}
			flag = (bool)AccessTools.Field(typeof(GearDetailsWindow), "inSkinMode").GetValue(val);
			IEnumerable<UpgradeInfo> enumerable = (flag ? PlayerData.GetAllSkins(upgradablePrefab, true) : PlayerData.GetAllUpgrades(upgradablePrefab, true));
			int num2 = 0;
			foreach (UpgradeInfo item in enumerable)
			{
				if (item?.Instances != null)
				{
					num2 += item.Instances.Count;
				}
			}
			list = new List<UpgradeInstance>(num2);
			foreach (UpgradeInfo item2 in enumerable)
			{
				if (item2?.Instances == null)
				{
					continue;
				}
				foreach (UpgradeInstance instance in item2.Instances)
				{
					if (instance != null && IsTrashMarked(instance))
					{
						list.Add(instance);
					}
				}
			}
			if (list.Count == 0)
			{
				SparrohPlugin.Logger.LogInfo((object)"ScrapMarkedUpgrades: No marked upgrades found.");
				SuppressPickupFeedback = false;
				IsScrapping = false;
				yield break;
			}
			UndoPatches.BeginBatch($"Scrap Marked ({list.Count})");
			foreach (UpgradeInstance item3 in list)
			{
				UndoPatches.AddToBatch(item3);
			}
			SparrohPlugin.Logger.LogInfo((object)$"ScrapMarkedUpgrades: Processing {list.Count} upgrades.");
			flag2 = true;
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapMarkedUpgrades: Setup failed: " + ex.Message));
			UndoPatches.CancelBatch();
			SuppressPickupFeedback = false;
			IsScrapping = false;
			yield break;
		}
		if (flag2 && list != null)
		{
			for (int i = 0; i < list.Count; i += 1000000)
			{
				int num3 = Mathf.Min(i + 1000000, list.Count);
				for (int j = i; j < num3; j++)
				{
					if (TryScrapInstance(list[j]))
					{
						num++;
					}
				}
			}
		}
		yield return FinishBatchScrapCoroutine(num > 0, flag);
	}

	public static bool IsFavorite(UpgradeInstance instance)
	{
		if (instance == null)
		{
			return false;
		}
		return ((byte)AccessTools.Field(typeof(UpgradeInstance), "flags").GetValue(instance) & 1) != 0;
	}

	public static bool IsTrashMarked(UpgradeInstance instance)
	{
		if (instance == null)
		{
			return false;
		}
		return ((byte)AccessTools.Field(typeof(UpgradeInstance), "flags").GetValue(instance) & 0x20) != 0;
	}

	public static void SetTrashMark(UpgradeInstance instance, bool marked)
	{
		if (instance != null)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(UpgradeInstance), "flags");
			byte b = (byte)fieldInfo.GetValue(instance);
			if (marked)
			{
				b |= 0x20;
				b &= 0xFE;
			}
			else
			{
				b &= 0xDF;
			}
			fieldInfo.SetValue(instance, b);
		}
	}

	public static void SetFavorite(UpgradeInstance instance, bool favorite)
	{
		if (instance != null)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(UpgradeInstance), "flags");
			byte b = (byte)fieldInfo.GetValue(instance);
			if (favorite)
			{
				b |= 1;
				b &= 0xDF;
			}
			else
			{
				b &= 0xFE;
			}
			fieldInfo.SetValue(instance, b);
		}
	}

	public static void TryScrapMarkedUpgrades(MonoBehaviour owner)
	{
		GearDetailsWindow val = Object.FindObjectOfType<GearDetailsWindow>();
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		IUpgradable upgradablePrefab = val.UpgradablePrefab;
		if (upgradablePrefab == null)
		{
			return;
		}
		List<UpgradeInfo> obj = (((bool)AccessTools.Field(typeof(GearDetailsWindow), "inSkinMode").GetValue(val)) ? PlayerData.GetAllSkins(upgradablePrefab, true) : PlayerData.GetAllUpgrades(upgradablePrefab, true));
		bool flag = false;
		foreach (UpgradeInfo item in obj)
		{
			if (item?.Instances != null && item.Instances.Any((UpgradeInstance inst) => inst != null && IsTrashMarked(inst)))
			{
				flag = true;
				break;
			}
		}
		if (flag)
		{
			owner.StartCoroutine(ScrapMarkedUpgrades());
		}
	}

	public static IEnumerator ScrapNonFavoriteUpgrades()
	{
		int num = 0;
		bool flag;
		List<UpgradeInstance> list;
		bool flag2;
		try
		{
			if (IsScrapping)
			{
				SparrohPlugin.Logger.LogWarning((object)"ScrapNonFavoriteUpgrades: Already scrapping, aborting.");
				yield break;
			}
			IsScrapping = true;
			SuppressPickupFeedback = true;
			BatchPickupFeedbackPatches.ClearAccumulated();
			SparrohPlugin.Logger.LogInfo((object)"Starting ScrapNonFavoriteUpgrades operation.");
			GearDetailsWindow val = Object.FindObjectOfType<GearDetailsWindow>();
			if ((Object)(object)val == (Object)null)
			{
				SparrohPlugin.Logger.LogError((object)"ScrapNonFavoriteUpgrades: GearDetailsWindow not found.");
				SuppressPickupFeedback = false;
				IsScrapping = false;
				yield break;
			}
			IUpgradable upgradablePrefab = val.UpgradablePrefab;
			if (upgradablePrefab == null)
			{
				SparrohPlugin.Logger.LogError((object)"ScrapNonFavoriteUpgrades: UpgradablePrefab is null.");
				SuppressPickupFeedback = false;
				IsScrapping = false;
				yield break;
			}
			flag = (bool)AccessTools.Field(typeof(GearDetailsWindow), "inSkinMode").GetValue(val);
			IEnumerable<UpgradeInfo> enumerable = (flag ? PlayerData.GetAllSkins(upgradablePrefab, true) : PlayerData.GetAllUpgrades(upgradablePrefab, true));
			int num2 = 0;
			foreach (UpgradeInfo item in enumerable)
			{
				if (item?.Instances != null)
				{
					num2 += item.Instances.Count;
				}
			}
			list = new List<UpgradeInstance>(num2);
			foreach (UpgradeInfo item2 in enumerable)
			{
				if (item2?.Instances == null)
				{
					continue;
				}
				foreach (UpgradeInstance instance in item2.Instances)
				{
					if (instance != null && !IsFavorite(instance))
					{
						list.Add(instance);
					}
				}
			}
			if (list.Count == 0)
			{
				SparrohPlugin.Logger.LogInfo((object)"ScrapNonFavoriteUpgrades: No non-favorite upgrades found.");
				SuppressPickupFeedback = false;
				IsScrapping = false;
				yield break;
			}
			UndoPatches.BeginBatch($"Scrap Non-Favorite ({list.Count})");
			foreach (UpgradeInstance item3 in list)
			{
				UndoPatches.AddToBatch(item3);
			}
			SparrohPlugin.Logger.LogInfo((object)$"ScrapNonFavoriteUpgrades: Processing {list.Count} upgrades.");
			flag2 = true;
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapNonFavoriteUpgrades: Setup failed: " + ex.Message));
			UndoPatches.CancelBatch();
			SuppressPickupFeedback = false;
			IsScrapping = false;
			yield break;
		}
		if (flag2 && list != null)
		{
			for (int i = 0; i < list.Count; i += 1000000)
			{
				int num3 = Mathf.Min(i + 1000000, list.Count);
				for (int j = i; j < num3; j++)
				{
					if (TryScrapInstance(list[j]))
					{
						num++;
					}
				}
			}
		}
		yield return FinishBatchScrapCoroutine(num > 0, flag);
	}

	public static void TryScrapNonFavoriteUpgrades(MonoBehaviour owner)
	{
		GearDetailsWindow val = Object.FindObjectOfType<GearDetailsWindow>();
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		IUpgradable upgradablePrefab = val.UpgradablePrefab;
		if (upgradablePrefab == null)
		{
			return;
		}
		List<UpgradeInfo> obj = (((bool)AccessTools.Field(typeof(GearDetailsWindow), "inSkinMode").GetValue(val)) ? PlayerData.GetAllSkins(upgradablePrefab, true) : PlayerData.GetAllUpgrades(upgradablePrefab, true));
		bool flag = false;
		foreach (UpgradeInfo item in obj)
		{
			if (item?.Instances != null && item.Instances.Any((UpgradeInstance inst) => inst != null && !IsFavorite(inst)))
			{
				flag = true;
				break;
			}
		}
		if (flag)
		{
			owner.StartCoroutine(ScrapNonFavoriteUpgrades());
		}
	}

	private static void RefreshOpenWindows()
	{
		if (!((Object)(object)Menu.Instance != (Object)null) || !Menu.Instance.IsOpen)
		{
			return;
		}
		Window top = Menu.Instance.WindowSystem.GetTop();
		if (!((Object)(object)top != (Object)null))
		{
			return;
		}
		top.OnOpen(Menu.Instance.WindowSystem);
		if (wasScrappingSkins)
		{
			GearDetailsWindow val = (GearDetailsWindow)(object)((top is GearDetailsWindow) ? top : null);
			if ((Object)(object)val != (Object)null)
			{
				AccessTools.Field(typeof(GearDetailsWindow), "inSkinMode").SetValue(val, true);
			}
			wasScrappingSkins = false;
		}
	}
}
public class ScrapUIPatches
{
	private static readonly HashSet<UpgradeInstance> toggledThisSession = new HashSet<UpgradeInstance>();

	private static void AddScrapButton(GearDetailsWindow window)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Expected O, but got Unknown
		//IL_0087: 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)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0124: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_0134: Unknown result type (might be due to invalid IL or missing references)
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0156: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: Unknown result type (might be due to invalid IL or missing references)
		//IL_016d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		//IL_019b: 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_01cb: Expected O, but got Unknown
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_0209: Unknown result type (might be due to invalid IL or missing references)
		//IL_021f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0235: Unknown result type (might be due to invalid IL or missing references)
		//IL_0243: Unknown result type (might be due to invalid IL or missing references)
		//IL_0258: Unknown result type (might be due to invalid IL or missing references)
		//IL_026a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0289: Unknown result type (might be due to invalid IL or missing references)
		//IL_028e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0292: Unknown result type (might be due to invalid IL or missing references)
		//IL_029e: Unknown result type (might be due to invalid IL or missing references)
		//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0306: Unknown result type (might be due to invalid IL or missing references)
		Transform val = ((Component)window).transform.Find("ModScrapButtonMarked");
		if ((Object)(object)val != (Object)null)
		{
			Object.DestroyImmediate((Object)(object)((Component)val).gameObject);
		}
		Transform val2 = ((Component)window).transform.Find("ModScrapButtonNonFavorite");
		if ((Object)(object)val2 != (Object)null)
		{
			Object.DestroyImmediate((Object)(object)((Component)val2).gameObject);
		}
		RectTransform component = ((Component)((Component)window).transform).GetComponent<RectTransform>();
		GameObject val3 = new GameObject("ModScrapButtonMarked");
		val3.transform.SetParent(((Component)window).transform, false);
		RectTransform val4 = val3.AddComponent<RectTransform>();
		val4.sizeDelta = new Vector2(200f, 50f);
		val4.anchorMin = new Vector2(1f, 0f);
		val4.anchorMax = new Vector2(1f, 0f);
		val4.pivot = new Vector2(1f, 0f);
		Rect rect = component.rect;
		val4.anchoredPosition = new Vector2((0f - ((Rect)(ref rect)).width) * 0.25f, 10f);
		Image obj = val3.AddComponent<Image>();
		((Graphic)obj).color = Color.gray;
		((Graphic)obj).raycastTarget = true;
		Button obj2 = val3.AddComponent<Button>();
		((Selectable)obj2).transition = (Transition)1;
		ColorBlock colors = ((Selectable)obj2).colors;
		((ColorBlock)(ref colors)).normalColor = Color.gray;
		((ColorBlock)(ref colors)).highlightedColor = Color.white;
		((ColorBlock)(ref colors)).pressedColor = Color.blue;
		((Selectable)obj2).colors = colors;
		GameObject val5 = new GameObject("Text");
		val5.transform.SetParent(val3.transform, false);
		val5.AddComponent<RectTransform>().sizeDelta = val4.sizeDelta;
		TextMeshProUGUI obj3 = val5.AddComponent<TextMeshProUGUI>();
		((TMP_Text)obj3).text = "Scrap Marked";
		((TMP_Text)obj3).alignment = (TextAlignmentOptions)514;
		((Graphic)obj3).color = Color.white;
		((TMP_Text)obj3).fontSize = 24f;
		val3.AddComponent<HoldButtonHandler>().onHoldComplete = ScrapHandlingMod.ScrapMarkedAction;
		GameObject val6 = new GameObject("ModScrapButtonNonFavorite");
		val6.transform.SetParent(((Component)window).transform, false);
		RectTransform val7 = val6.AddComponent<RectTransform>();
		val7.sizeDelta = new Vector2(250f, 50f);
		val7.anchorMin = new Vector2(1f, 0f);
		val7.anchorMax = new Vector2(1f, 0f);
		val7.pivot = new Vector2(1f, 0f);
		val7.anchoredPosition = new Vector2(val4.anchoredPosition.x + 210f, 50f);
		Image obj4 = val6.AddComponent<Image>();
		((Graphic)obj4).color = Color.red;
		((Graphic)obj4).raycastTarget = true;
		Button obj5 = val6.AddComponent<Button>();
		((Selectable)obj5).transition = (Transition)1;
		ColorBlock colors2 = ((Selectable)obj5).colors;
		((ColorBlock)(ref colors2)).normalColor = Color.red;
		((ColorBlock)(ref colors2)).highlightedColor = Color.white;
		((ColorBlock)(ref colors2)).pressedColor = Color.blue;
		((Selectable)obj5).colors = colors2;
		GameObject val8 = new GameObject("Text");
		val8.transform.SetParent(val6.transform, false);
		val8.AddComponent<RectTransform>().sizeDelta = val7.sizeDelta;
		TextMeshProUGUI obj6 = val8.AddComponent<TextMeshProUGUI>();
		((TMP_Text)obj6).text = "Scrap All Non-Favorite";
		((TMP_Text)obj6).alignment = (TextAlignmentOptions)514;
		((Graphic)obj6).color = Color.white;
		((TMP_Text)obj6).fontSize = 20f;
		val6.AddComponent<HoldButtonHandler>().onHoldComplete = ScrapHandlingMod.ScrapNonFavoriteAction;
	}

	public static void UpdateFavoriteIconPostfix(GearUpgradeUI __instance)
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Expected O, but got Unknown
		if (ScrapHandlingMod.IsScrapping)
		{
			return;
		}
		Image val = (Image)AccessTools.Field(typeof(GearUpgradeUI), "favoriteIcon").GetValue(__instance);
		if ((Object)(object)val != (Object)null && ((HoverInfoUpgrade)__instance).Upgrade != null)
		{
			ScrapHandlingMod.ApplyMarkIcon(val, ((HoverInfoUpgrade)__instance).Upgrade);
			if (ScrapHandlingMod.IsTrashMarked(((HoverInfoUpgrade)__instance).Upgrade))
			{
				Canvas.ForceUpdateCanvases();
			}
		}
	}

	public static void OnAdditionalActionPostfix(GearUpgradeUI __instance, int index, ref bool refreshUI)
	{
		if (index == 0 && ((HoverInfoUpgrade)__instance).Upgrade != null)
		{
			ScrapHandlingMod.SetFavorite(((HoverInfoUpgrade)__instance).Upgrade, ((HoverInfoUpgrade)__instance).Upgrade.Favorite);
		}
	}

	public static void EnableGridViewPostfix(GearUpgradeUI __instance, bool grid)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Expected O, but got Unknown
		if (!ScrapHandlingMod.IsScrapping && ((HoverInfoUpgrade)__instance).Upgrade != null)
		{
			Image val = (Image)AccessTools.Field(typeof(GearUpgradeUI), "favoriteIcon").GetValue(__instance);
			if ((Object)(object)val != (Object)null)
			{
				ScrapHandlingMod.ApplyMarkIcon(val, ((HoverInfoUpgrade)__instance).Upgrade);
			}
		}
	}

	public static void UpdatePrefix(GearDetailsWindow __instance)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ae: Expected O, but got Unknown
		if (ScrapHandlingMod.IsScrapping || Keyboard.current == null)
		{
			return;
		}
		if (!((ButtonControl)Keyboard.current[ScrapHandlingMod.currentTrashKey]).isPressed)
		{
			if (toggledThisSession.Count > 0)
			{
				toggledThisSession.Clear();
			}
			return;
		}
		GearUpgradeUI val = null;
		if (!UIRaycaster.RaycastForComponent<GearUpgradeUI>(ref val))
		{
			return;
		}
		UpgradeInstance upgrade = ((HoverInfoUpgrade)val).Upgrade;
		if (upgrade != null && !ScrapHandlingMod.IsFavorite(upgrade) && !toggledThisSession.Contains(upgrade))
		{
			bool flag = ScrapHandlingMod.IsTrashMarked(upgrade);
			ScrapHandlingMod.SetTrashMark(upgrade, !flag);
			toggledThisSession.Add(upgrade);
			Image val2 = (Image)(AccessTools.Field(typeof(GearUpgradeUI), "favoriteIcon")?.GetValue(val));
			if ((Object)(object)val2 != (Object)null)
			{
				ScrapHandlingMod.ApplyMarkIcon(val2, upgrade);
				Canvas.ForceUpdateCanvases();
			}
		}
	}
}
public class HoldButtonHandler : MonoBehaviour, IPointerDownHandler, IEventSystemHandler, IPointerUpHandler
{
	private const float HOLD_DURATION = 1f;

	private float holdTimer;

	private bool isHolding;

	public Action onHoldComplete;

	private void Update()
	{
		if (isHolding)
		{
			holdTimer += Time.deltaTime;
			if (holdTimer >= 1f)
			{
				onHoldComplete?.Invoke();
				isHolding = false;
			}
		}
	}

	public void OnPointerDown(PointerEventData eventData)
	{
		isHolding = true;
		holdTimer = 0f;
	}

	public void OnPointerUp(PointerEventData eventData)
	{
		isHolding = false;
	}
}
public static class BatchPickupFeedbackPatches
{
	private struct AccumEntry
	{
		public object Item;

		public int Amount;

		public MethodBase Method;

		public object Instance;

		public object[] TemplateArgs;

		public int AmountArgIndex;
	}

	private static readonly Dictionary<object, AccumEntry> accumulated = new Dictionary<object, AccumEntry>();

	private static int interceptedCount;

	public static void ClearAccumulated()
	{
		accumulated.Clear();
		interceptedCount = 0;
	}

	public static bool ShowPickupInfoPrefix(object __instance, object[] __args, MethodBase __originalMethod)
	{
		if (!ScrapHandlingMod.SuppressPickupFeedback)
		{
			return true;
		}
		try
		{
			if (__args == null || __args.Length < 2 || __originalMethod == null)
			{
				return false;
			}
			object obj = null;
			int num = 0;
			int num2 = -1;
			for (int i = 0; i < __args.Length; i++)
			{
				object obj2 = __args[i];
				if (obj2 != null)
				{
					if (obj == null && (obj2 is PlayerResource || obj2 is IInventoryItem))
					{
						obj = obj2;
					}
					else if (obj2 is int num3 && num2 < 0)
					{
						num = num3;
						num2 = i;
					}
				}
			}
			if (obj == null || num2 < 0 || num <= 0)
			{
				return false;
			}
			interceptedCount++;
			object key = obj;
			PlayerResource val = (PlayerResource)((obj is PlayerResource) ? obj : null);
			if (val != null && !string.IsNullOrEmpty(val.ID))
			{
				key = val.ID;
			}
			else
			{
				IInventoryItem val2 = (IInventoryItem)((obj is IInventoryItem) ? obj : null);
				if (val2 != null && !string.IsNullOrEmpty(val2.ID))
				{
					key = val2.ID;
				}
			}
			if (accumulated.TryGetValue(key, out var value))
			{
				value.Amount += num;
				accumulated[key] = value;
			}
			else
			{
				object[] array = new object[__args.Length];
				Array.Copy(__args, array, __args.Length);
				accumulated[key] = new AccumEntry
				{
					Item = obj,
					Amount = num,
					Method = __originalMethod,
					Instance = __instance,
					TemplateArgs = array,
					AmountArgIndex = num2
				};
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogWarning((object)("ShowPickupInfoPrefix accumulate failed: " + ex.Message));
		}
		return false;
	}

	public static bool PlayPickupSoundPrefix()
	{
		return !ScrapHandlingMod.SuppressPickupFeedback;
	}

	public static void FlushAccumulatedPickups()
	{
		try
		{
			SparrohPlugin.Logger.LogInfo((object)$"FlushAccumulatedPickups: intercepted={interceptedCount}, unique={accumulated.Count}");
			if (accumulated.Count == 0)
			{
				SparrohPlugin.Logger.LogWarning((object)"FlushAccumulatedPickups: nothing accumulated — no resource toasts to show (grants may still have applied).");
				return;
			}
			int num = 0;
			foreach (KeyValuePair<object, AccumEntry> item in accumulated)
			{
				AccumEntry value = item.Value;
				if (value.Method == null || value.TemplateArgs == null || value.Amount <= 0)
				{
					continue;
				}
				try
				{
					object[] array = new object[value.TemplateArgs.Length];
					Array.Copy(value.TemplateArgs, array, value.TemplateArgs.Length);
					if (value.AmountArgIndex >= 0 && value.AmountArgIndex < array.Length)
					{
						array[value.AmountArgIndex] = value.Amount;
					}
					object obj = value.Instance ?? GameManager.Instance;
					if (obj != null)
					{
						goto IL_0125;
					}
					MethodBase method = value.Method;
					if (!(method is MethodInfo) || method.IsStatic)
					{
						goto IL_0125;
					}
					SparrohPlugin.Logger.LogWarning((object)"FlushAccumulatedPickups: no GameManager instance.");
					goto end_IL_0098;
					IL_0125:
					value.Method.Invoke(obj, array);
					num++;
					end_IL_0098:;
				}
				catch (Exception ex)
				{
					Exception ex2 = ((ex is TargetInvocationException { InnerException: not null } ex3) ? ex3.InnerException : ex);
					SparrohPlugin.Logger.LogWarning((object)$"FlushAccumulatedPickups: failed for key={item.Key} x{value.Amount}: {ex2.Message}");
				}
			}
			SparrohPlugin.Logger.LogInfo((object)$"FlushAccumulatedPickups: showed {num} consolidated toast(s).");
		}
		finally
		{
			ClearAccumulated();
		}
	}
}
public static class UndoPatches
{
	public sealed class UpgradeSnapshot
	{
		public IUpgradable EquippedOnGear;

		public byte EquipRotation;

		public sbyte EquipX;

		public sbyte EquipY;

		public byte Flags;

		public IUpgradable Gear;

		public bool HasBeenSeen;

		public bool IsUnlocked;

		public UpgradeID OverriddenPattern;

		public bool RemoveOnQuit;

		public int Seed;

		public Upgrade Upgrade;

		public bool WasEquipped;
	}

	public sealed class ScrapAction
	{
		public string Description;

		public Dictionary<PlayerResource, int> ResourcesGranted = new Dictionary<PlayerResource, int>();

		public List<UpgradeSnapshot> Upgrades = new List<UpgradeSnapshot>();
	}

	public const int MaxDepth = 5;

	private static readonly List<ScrapAction> undoList = new List<ScrapAction>(5);

	private static readonly FieldInfo flagsField = AccessTools.Field(typeof(UpgradeInstance), "flags");

	private static readonly PropertyInfo hasBeenSeenProp = AccessTools.Property(typeof(UpgradeInstance), "HasBeenSeen");

	private static readonly PropertyInfo seedProp = AccessTools.Property(typeof(UpgradeInstance), "Seed");

	private static readonly PropertyInfo removeOnQuitProp = AccessTools.Property(typeof(UpgradeInstance), "RemoveOnQuit");

	private static ScrapAction pendingBatch;

	private static Dictionary<PlayerResource, int> resourceBaseline;

	private static readonly Queue<ScrapAction> deferredActions = new Queue<ScrapAction>();

	private static bool flushScheduled;

	public static int UndoCount => undoList.Count;

	public static bool CanUndo => undoList.Count > 0;

	public static void Clear()
	{
		undoList.Clear();
		deferredActions.Clear();
		flushScheduled = false;
		pendingBatch = null;
		resourceBaseline = null;
	}

	public static UpgradeSnapshot CaptureUpgrade(UpgradeInstance instance)
	{
		//IL_0092: 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)
		if (instance == null)
		{
			return null;
		}
		Upgrade upgrade;
		try
		{
			upgrade = instance.Upgrade;
		}
		catch
		{
			return null;
		}
		if ((Object)(object)upgrade == (Object)null)
		{
			return null;
		}
		UpgradeSnapshot upgradeSnapshot = new UpgradeSnapshot
		{
			Gear = instance.Gear,
			Upgrade = upgrade,
			Seed = instance.Seed,
			Flags = (byte)((flagsField != null) ? ((byte)flagsField.GetValue(instance)) : 0),
			IsUnlocked = instance.IsUnlocked,
			HasBeenSeen = instance.HasBeenSeen,
			RemoveOnQuit = instance.RemoveOnQuit,
			OverriddenPattern = instance.OverriddenPattern,
			WasEquipped = false
		};
		try
		{
			IUpgradable gear = instance.Gear;
			sbyte equipX = default(sbyte);
			sbyte equipY = default(sbyte);
			byte equipRotation = default(byte);
			if (gear != null && instance.IsEquipped(gear) && instance.GetPosition(gear, ref equipX, ref equipY, ref equipRotation))
			{
				upgradeSnapshot.WasEquipped = true;
				upgradeSnapshot.EquippedOnGear = gear;
				upgradeSnapshot.EquipX = equipX;
				upgradeSnapshot.EquipY = equipY;
				upgradeSnapshot.EquipRotation = equipRotation;
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogWarning((object)("ScrapUndo: failed to capture equip state: " + ex.Message));
		}
		return upgradeSnapshot;
	}

	public static Dictionary<PlayerResource, int> SnapshotResources()
	{
		Dictionary<PlayerResource, int> dictionary = new Dictionary<PlayerResource, int>();
		try
		{
			if (PlayerData.Instance == null)
			{
				return dictionary;
			}
			if (Global.Instance?.PlayerResources != null)
			{
				PlayerResource[] playerResources = Global.Instance.PlayerResources;
				foreach (PlayerResource val in playerResources)
				{
					if (!((Object)(object)val == (Object)null))
					{
						dictionary[val] = PlayerData.Instance.GetResource(val);
					}
				}
			}
			TryTrack(dictionary, "strange_comp");
			TryTrack(dictionary, "oyster");
			TryTrack(dictionary, "ouroscrap");
			TryTrack(dictionary, "ourosample");
			Global instance = Global.Instance;
			if ((Object)(object)((instance != null) ? instance.ScripResource : null) != (Object)null)
			{
				dictionary[Global.Instance.ScripResource] = PlayerData.Instance.GetResource(Global.Instance.ScripResource);
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogWarning((object)("ScrapUndo: resource snapshot failed: " + ex.Message));
		}
		return dictionary;
	}

	private static void TryTrack(Dictionary<PlayerResource, int> dict, string name)
	{
		PlayerResource val = default(PlayerResource);
		if (PlayerResource.TryGetResource(name, ref val) && (Object)(object)val != (Object)null)
		{
			dict[val] = PlayerData.Instance.GetResource(val);
		}
	}

	public static Dictionary<PlayerResource, int> DiffResources(Dictionary<PlayerResource, int> before, Dictionary<PlayerResource, int> after)
	{
		Dictionary<PlayerResource, int> dictionary = new Dictionary<PlayerResource, int>();
		if (before == null || after == null)
		{
			return dictionary;
		}
		foreach (KeyValuePair<PlayerResource, int> item in after)
		{
			before.TryGetValue(item.Key, out var value);
			int num = item.Value - value;
			if (num > 0)
			{
				dictionary[item.Key] = num;
			}
		}
		return dictionary;
	}

	public static void PushAction(ScrapAction action)
	{
		if (action == null || action.Upgrades == null || action.Upgrades.Count == 0)
		{
			return;
		}
		try
		{
			while (undoList.Count >= 5)
			{
				undoList.RemoveAt(0);
			}
			undoList.Add(action);
			SparrohPlugin.Logger.LogInfo((object)$"ScrapUndo: recorded action ({action.Upgrades.Count} upgrade(s)). Stack depth: {undoList.Count}/{5}");
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo PushAction failed: " + ex.Message));
		}
	}

	public static void PushSingle(UpgradeSnapshot snap, Dictionary<PlayerResource, int> resourcesGranted)
	{
		if (snap != null)
		{
			PushAction(new ScrapAction
			{
				Description = (((Object)(object)snap.Upgrade != (Object)null) ? snap.Upgrade.Name : "upgrade"),
				ResourcesGranted = (resourcesGranted ?? new Dictionary<PlayerResource, int>()),
				Upgrades = { snap }
			});
		}
	}

	public static void QueueSingleDeferred(UpgradeSnapshot snap, Dictionary<PlayerResource, int> resourcesBefore)
	{
		if (snap == null)
		{
			return;
		}
		try
		{
			Dictionary<PlayerResource, int> after = SnapshotResources();
			Dictionary<PlayerResource, int> resourcesGranted = DiffResources(resourcesBefore, after);
			ScrapAction scrapAction = new ScrapAction
			{
				Description = (((Object)(object)snap.Upgrade != (Object)null) ? snap.Upgrade.Name : "upgrade"),
				ResourcesGranted = resourcesGranted
			};
			scrapAction.Upgrades.Add(snap);
			deferredActions.Enqueue(scrapAction);
			ScheduleFlush();
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo QueueSingleDeferred failed: " + ex.Message));
		}
	}

	private static void ScheduleFlush()
	{
		if (!flushScheduled && !((Object)(object)SparrohPlugin.Instance == (Object)null))
		{
			flushScheduled = true;
			((MonoBehaviour)SparrohPlugin.Instance).StartCoroutine(FlushDeferredNextFrame());
		}
	}

	private static IEnumerator FlushDeferredNextFrame()
	{
		yield return null;
		flushScheduled = false;
		try
		{
			while (deferredActions.Count > 0)
			{
				PushAction(deferredActions.Dequeue());
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo flush failed: " + ex.Message));
			deferredActions.Clear();
		}
	}

	public static void BeginBatch(string description)
	{
		pendingBatch = new ScrapAction
		{
			Description = description
		};
		resourceBaseline = SnapshotResources();
	}

	public static void AddToBatch(UpgradeInstance instance)
	{
		if (pendingBatch != null)
		{
			UpgradeSnapshot upgradeSnapshot = CaptureUpgrade(instance);
			if (upgradeSnapshot != null)
			{
				pendingBatch.Upgrades.Add(upgradeSnapshot);
			}
		}
	}

	public static Dictionary<PlayerResource, int> EndBatch()
	{
		Dictionary<PlayerResource, int> dictionary = null;
		if (pendingBatch == null)
		{
			return new Dictionary<PlayerResource, int>();
		}
		try
		{
			Dictionary<PlayerResource, int> after = SnapshotResources();
			pendingBatch.ResourcesGranted = DiffResources(resourceBaseline, after);
			dictionary = pendingBatch.ResourcesGranted;
			if (pendingBatch.Upgrades.Count > 0)
			{
				PushAction(pendingBatch);
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo EndBatch failed: " + ex.Message));
		}
		pendingBatch = null;
		resourceBaseline = null;
		return dictionary ?? new Dictionary<PlayerResource, int>();
	}

	public static void CancelBatch()
	{
		pendingBatch = null;
		resourceBaseline = null;
	}

	public static bool TryUndo()
	{
		if (undoList.Count == 0)
		{
			SparrohPlugin.Logger.LogInfo((object)"ScrapUndo: nothing to undo.");
			return false;
		}
		int index = undoList.Count - 1;
		ScrapAction scrapAction = undoList[index];
		undoList.RemoveAt(index);
		int num = 0;
		try
		{
			if (scrapAction?.Upgrades != null)
			{
				foreach (UpgradeSnapshot upgrade in scrapAction.Upgrades)
				{
					if (RestoreUpgrade(upgrade))
					{
						num++;
					}
				}
			}
			if (scrapAction?.ResourcesGranted != null)
			{
				foreach (KeyValuePair<PlayerResource, int> item in scrapAction.ResourcesGranted)
				{
					if (!((Object)(object)item.Key == (Object)null) && item.Value > 0)
					{
						int num2 = Mathf.Min(PlayerData.Instance.GetResource(item.Key), item.Value);
						if (num2 > 0)
						{
							PlayerData.Instance.TryRemoveResource(item.Key, num2);
						}
					}
				}
			}
			RefreshOpenWindows();
			SparrohPlugin.Logger.LogInfo((object)$"ScrapUndo: restored {num}/{(scrapAction?.Upgrades?.Count).GetValueOrDefault()} upgrade(s). Remaining stack: {undoList.Count}");
			return num > 0;
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo: undo failed: " + ex.Message + "\n" + ex.StackTrace));
			return false;
		}
	}

	private static bool RestoreUpgrade(UpgradeSnapshot snap)
	{
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		if (snap == null || snap.Gear == null || (Object)(object)snap.Upgrade == (Object)null)
		{
			return false;
		}
		try
		{
			UpgradeInstance val = PlayerData.CreateUpgradeInstance(snap.Gear, snap.Upgrade, false);
			if (val == null)
			{
				return false;
			}
			if (seedProp != null)
			{
				seedProp.SetValue(val, snap.Seed);
			}
			if (flagsField != null)
			{
				flagsField.SetValue(val, snap.Flags);
			}
			if (removeOnQuitProp != null)
			{
				removeOnQuitProp.SetValue(val, snap.RemoveOnQuit);
			}
			val.OverriddenPattern = snap.OverriddenPattern;
			PlayerData.CollectInstance(val, (UnlockFlags)1);
			if (snap.IsUnlocked)
			{
				val.Unlock(true);
			}
			else
			{
				val.Lock();
			}
			if (hasBeenSeenProp != null)
			{
				hasBeenSeenProp.SetValue(val, snap.HasBeenSeen);
			}
			if (flagsField != null)
			{
				flagsField.SetValue(val, snap.Flags);
			}
			if (snap.WasEquipped && snap.EquippedOnGear != null)
			{
				try
				{
					val.Equip(snap.EquippedOnGear, snap.EquipX, snap.EquipY, snap.EquipRotation, true, false);
				}
				catch (Exception ex)
				{
					SparrohPlugin.Logger.LogWarning((object)("ScrapUndo: re-equip failed: " + ex.Message));
				}
			}
			return true;
		}
		catch (Exception ex2)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo: restore upgrade failed: " + ex2.Message));
			return false;
		}
	}

	private static void RefreshOpenWindows()
	{
		try
		{
			if ((Object)(object)Menu.Instance != (Object)null && Menu.Instance.IsOpen)
			{
				Window top = Menu.Instance.WindowSystem.GetTop();
				if ((Object)(object)top != (Object)null)
				{
					top.OnOpen(Menu.Instance.WindowSystem);
				}
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogWarning((object)("ScrapUndo: window refresh failed: " + ex.Message));
		}
	}
}
public static class ScrapUndoPatches
{
	private struct Pending
	{
		public UpgradeInstance Instance;

		public UndoPatches.UpgradeSnapshot Snap;

		public Dictionary<PlayerResource, int> ResourcesBefore;
	}

	private static readonly Stack<Pending> pendingStack = new Stack<Pending>();

	public static void DismantlePrefix(GearUpgradeUI __instance)
	{
		try
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return;
			}
			UpgradeInstance upgrade = ((HoverInfoUpgrade)__instance).Upgrade;
			if (upgrade != null && !upgrade.Favorite)
			{
				UndoPatches.UpgradeSnapshot upgradeSnapshot = UndoPatches.CaptureUpgrade(upgrade);
				if (upgradeSnapshot != null)
				{
					pendingStack.Push(new Pending
					{
						Instance = upgrade,
						Snap = upgradeSnapshot,
						ResourcesBefore = UndoPatches.SnapshotResources()
					});
				}
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo DismantlePrefix: " + ex.Message));
		}
	}

	public static void DismantlePostfix(GearUpgradeUI __instance)
	{
		try
		{
			if (pendingStack.Count == 0)
			{
				return;
			}
			Pending pending = pendingStack.Pop();
			if (pending.Instance != null && pending.Snap != null)
			{
				bool flag;
				try
				{
					flag = pending.Instance.IsDestroyed();
				}
				catch
				{
					flag = true;
				}
				if (flag)
				{
					UndoPatches.QueueSingleDeferred(pending.Snap, pending.ResourcesBefore);
				}
			}
		}
		catch (Exception ex)
		{
			SparrohPlugin.Logger.LogError((object)("ScrapUndo DismantlePostfix: " + ex.Message));
			if (pendingStack.Count > 0)
			{
				try
				{
					pendingStack.Pop();
					return;
				}
				catch
				{
					return;
				}
			}
		}
	}
}
namespace BatchScrapping
{
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "BatchScrapping";

		public const string PLUGIN_NAME = "BatchScrapping";

		public const string PLUGIN_VERSION = "2.1.3";
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}