Decompiled source of ProperSave CommandQueueExtended v1.0.0

ProperSave_CommandQueueExtended.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using ProperSave;
using RoR2;
using SimpleJSON;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (e.g., Code Suggestions, Refactoring, Documentation Generation).")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ProperSaveCommandQueueExtended;

internal static class Log
{
	private static ManualLogSource _source;

	internal static void Init(ManualLogSource source)
	{
		_source = source;
	}

	internal static void Info(object message)
	{
		ManualLogSource source = _source;
		if (source != null)
		{
			source.LogInfo(message);
		}
	}

	internal static void Warning(object message)
	{
		ManualLogSource source = _source;
		if (source != null)
		{
			source.LogWarning(message);
		}
	}

	internal static void Error(object message)
	{
		ManualLogSource source = _source;
		if (source != null)
		{
			source.LogError(message);
		}
	}
}
[BepInPlugin("ProperSave_CommandQueueExtended", "ProperSave_CommandQueueExtended", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class ProperSaveCommandQueueExtendedPlugin : BaseUnityPlugin
{
	public const string Guid = "ProperSave_CommandQueueExtended";

	public const string Name = "ProperSave_CommandQueueExtended";

	public const string Version = "1.0.0";

	private const string ProperSaveGuid = "com.KingEnderBrine.ProperSave";

	internal const string SaveKey = "local.ProperSave_CommandQueueExtended.queue";

	private const string ChatPrefix = "<color=#9ad3ff>[CommandQueue]</color> ";

	private const float DelayAfterSpawn = 0.75f;

	private const float GiveUpAfter = 30f;

	private QueueBridge _bridge;

	private string _bridgeFailure;

	private bool _bridgeLogged;

	private List<QueuedPickup> _pending;

	private float _pendingSince;

	private float _applyAt = -1f;

	private QueueBridge Bridge
	{
		get
		{
			if (_bridge == null && _bridgeFailure == null)
			{
				_bridge = QueueBridge.Discover(out _bridgeFailure);
				if (!_bridgeLogged)
				{
					_bridgeLogged = true;
					if (_bridge != null)
					{
						Log.Info("Using " + _bridge.Description);
					}
					else
					{
						Log.Error("CommandQueueExtended's queue could not be found: " + _bridgeFailure);
					}
				}
			}
			return _bridge;
		}
	}

	private void Awake()
	{
		Log.Init(((BaseUnityPlugin)this).Logger);
		SaveFile.OnGatherSaveData += OnGatherSaveData;
		Loading.OnLoadingEnded += OnLoadingEnded;
		CharacterBody.onBodyStartGlobal += OnBodyStart;
		Run.onRunDestroyGlobal += OnRunDestroy;
		Log.Info("ProperSave_CommandQueueExtended 1.0.0 ready.");
	}

	private void OnDestroy()
	{
		SaveFile.OnGatherSaveData -= OnGatherSaveData;
		Loading.OnLoadingEnded -= OnLoadingEnded;
		CharacterBody.onBodyStartGlobal -= OnBodyStart;
		Run.onRunDestroyGlobal -= OnRunDestroy;
	}

	private void OnGatherSaveData(Dictionary<string, object> data)
	{
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		QueueBridge bridge = Bridge;
		if (bridge == null || bridge.Queues == null)
		{
			return;
		}
		List<QueuedPickup> queue = new List<QueuedPickup>();
		foreach (KeyValuePair<PickupIndex, int> item in bridge.Snapshot())
		{
			PickupDef pickupDef = PickupCatalog.GetPickupDef(item.Key);
			if (pickupDef != null && !string.IsNullOrEmpty(pickupDef.internalName))
			{
				QueueCodec.Append(queue, pickupDef.internalName, item.Value);
			}
		}
		data["local.ProperSave_CommandQueueExtended.queue"] = QueueCodec.Encode(queue);
		Log.Info($"Saved {QueueCodec.Total(queue)} queued item(s) with the run.");
	}

	private void OnLoadingEnded(SaveFile save)
	{
		try
		{
			string text = ((save != null) ? save.TryGetModdedData<string>("local.ProperSave_CommandQueueExtended.queue") : null);
			if (string.IsNullOrEmpty(text))
			{
				Log.Info("This save has no stored queue (it was made before this mod was installed).");
				return;
			}
			_pending = QueueCodec.Decode(text);
			_pendingSince = Time.unscaledTime;
			_applyAt = -1f;
			Log.Info($"Loaded save holds {QueueCodec.Total(_pending)} queued item(s); restoring once you spawn.");
		}
		catch (Exception arg)
		{
			_pending = null;
			Log.Error($"Could not read the saved queue: {arg}");
		}
	}

	private void OnBodyStart(CharacterBody body)
	{
		if (_pending != null && !((Object)(object)body == (Object)null) && !(_applyAt >= 0f))
		{
			LocalUser firstLocalUser = LocalUserManager.GetFirstLocalUser();
			CharacterMaster master = body.master;
			if ((Object)(object)master != (Object)null && firstLocalUser != null && ((Object)(object)firstLocalUser.cachedMaster == (Object)(object)master || ((Object)(object)master.playerCharacterMasterController != (Object)null && (Object)(object)master.playerCharacterMasterController.networkUser != (Object)null && master.playerCharacterMasterController.networkUser.localUser == firstLocalUser)))
			{
				_applyAt = Time.unscaledTime + 0.75f;
			}
		}
	}

	private void OnRunDestroy(Run run)
	{
		_pending = null;
		_applyAt = -1f;
	}

	private void Update()
	{
		if (_pending == null)
		{
			return;
		}
		if ((Object)(object)Run.instance == (Object)null)
		{
			if (Time.unscaledTime - _pendingSince > 30f)
			{
				_pending = null;
			}
			return;
		}
		bool num = _applyAt >= 0f && Time.unscaledTime >= _applyAt;
		bool flag = Time.unscaledTime - _pendingSince > 30f;
		if (num || flag)
		{
			Restore();
		}
	}

	private void Restore()
	{
		//IL_0061: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		//IL_0072: Unknown result type (might be due to invalid IL or missing references)
		List<QueuedPickup> pending = _pending;
		_pending = null;
		_applyAt = -1f;
		QueueBridge bridge = Bridge;
		if (bridge == null)
		{
			Chat.AddMessage("<color=#9ad3ff>[CommandQueue]</color> Could not restore the queue: " + _bridgeFailure + ".");
			return;
		}
		try
		{
			List<KeyValuePair<PickupIndex, int>> list = new List<KeyValuePair<PickupIndex, int>>();
			int num = 0;
			foreach (QueuedPickup item in pending)
			{
				PickupIndex key = PickupCatalog.FindPickupIndex(item.Pickup);
				if (((PickupIndex)(ref key)).isValid)
				{
					list.Add(new KeyValuePair<PickupIndex, int>(key, item.Count));
				}
				else
				{
					num += item.Count;
				}
			}
			int num2 = bridge.Replace(list, TierOf);
			string text = ((num > 0) ? $" ({num} skipped: item no longer installed)" : string.Empty);
			Chat.AddMessage("<color=#9ad3ff>[CommandQueue]</color> " + string.Format("Restored {0} queued item{1} from your save{2}.", num2, (num2 == 1) ? string.Empty : "s", text));
			Log.Info($"Restored {num2} queued item(s){text}.");
		}
		catch (Exception arg)
		{
			Log.Error($"Restoring the queue failed: {arg}");
			Chat.AddMessage("<color=#9ad3ff>[CommandQueue]</color> Restoring the queue failed; see the BepInEx log.");
		}
	}

	private static object TierOf(PickupIndex pickup)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		PickupDef pickupDef = PickupCatalog.GetPickupDef(pickup);
		if (pickupDef == null)
		{
			return null;
		}
		return pickupDef.itemTier;
	}
}
internal sealed class QueueBridge
{
	private const BindingFlags Static = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private const BindingFlags Instance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private FieldInfo _queuesField;

	private PropertyInfo _queuesProperty;

	private Type _entryType;

	private MemberInfo _entryPickup;

	private MemberInfo _entryCount;

	private MethodInfo _enqueue;

	private MethodInfo _remove;

	private bool _removeTakesCount;

	internal string Description { get; private set; }

	internal IDictionary Queues => ((_queuesField != null) ? _queuesField.GetValue(null) : _queuesProperty.GetValue(null)) as IDictionary;

	private QueueBridge()
	{
	}

	internal static QueueBridge Discover(out string failure)
	{
		failure = null;
		List<Assembly> list = CandidateAssemblies();
		if (list.Count == 0)
		{
			failure = "no CommandQueue plugin is loaded (is CommandQueueExtended installed and enabled?)";
			return null;
		}
		foreach (Assembly item in list)
		{
			foreach (Type item2 in SafeTypes(item))
			{
				QueueBridge queueBridge = TryType(item2);
				if (queueBridge != null)
				{
					return queueBridge;
				}
			}
		}
		failure = "found " + string.Join(", ", list.Select((Assembly a) => a.GetName().Name)) + " but no queue storage inside it (a dictionary of ItemTier to queue entries)";
		return null;
	}

	private static List<Assembly> CandidateAssemblies()
	{
		List<Assembly> list = new List<Assembly>();
		Assembly assembly = typeof(QueueBridge).Assembly;
		try
		{
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				if ((value.Metadata.GUID + " " + value.Metadata.Name).ToLowerInvariant().Contains("commandqueue") && !((Object)(object)value.Instance == (Object)null))
				{
					Assembly assembly2 = ((object)value.Instance).GetType().Assembly;
					if (assembly2 != assembly && !list.Contains(assembly2))
					{
						list.Add(assembly2);
					}
				}
			}
		}
		catch (Exception)
		{
		}
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly3 in assemblies)
		{
			string text = assembly3.GetName().Name ?? string.Empty;
			if (assembly3 != assembly && text.IndexOf("CommandQueue", StringComparison.OrdinalIgnoreCase) >= 0 && !list.Contains(assembly3))
			{
				list.Add(assembly3);
			}
		}
		return list.OrderByDescending((Assembly a) => (a.GetName().Name ?? string.Empty).IndexOf("Extended", StringComparison.OrdinalIgnoreCase) >= 0).ToList();
	}

	private static IEnumerable<Type> SafeTypes(Assembly assembly)
	{
		try
		{
			return assembly.GetTypes();
		}
		catch (ReflectionTypeLoadException ex)
		{
			return ex.Types.Where((Type t) => t != null);
		}
		catch (Exception)
		{
			return Enumerable.Empty<Type>();
		}
	}

	private static QueueBridge TryType(Type type)
	{
		foreach (MemberInfo item in from m in type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast<MemberInfo>().Concat(from p in type.GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where p.GetIndexParameters().Length == 0
				select p)
			orderby string.Equals(m.Name, "mainQueues", StringComparison.OrdinalIgnoreCase) descending
			select m)
		{
			if (!TryQueueShape((item is FieldInfo fieldInfo) ? fieldInfo.FieldType : ((PropertyInfo)item).PropertyType, out var entryType, out var pickup, out var count))
			{
				continue;
			}
			QueueBridge queueBridge = new QueueBridge
			{
				_queuesField = (item as FieldInfo),
				_queuesProperty = (item as PropertyInfo),
				_entryType = entryType,
				_entryPickup = pickup,
				_entryCount = count
			};
			queueBridge._enqueue = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name.IndexOf("Enqueue", StringComparison.OrdinalIgnoreCase) >= 0 && Matches(m, typeof(PickupIndex))
				orderby (!(m.Name == "Enqueue")) ? 1 : 0, m.GetParameters().Length
				select m).FirstOrDefault();
			queueBridge._remove = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == "Remove" && Matches(m, typeof(ItemTier), typeof(int), typeof(int))
				orderby m.GetParameters().Length
				select m).FirstOrDefault();
			queueBridge._removeTakesCount = queueBridge._remove != null;
			if (queueBridge._remove == null)
			{
				queueBridge._remove = (from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
					where m.Name == "Remove" && Matches(m, typeof(ItemTier), typeof(int))
					orderby m.GetParameters().Length
					select m).FirstOrDefault();
			}
			queueBridge.Description = type.FullName + "." + item.Name + " in " + type.Assembly.GetName().Name + " (entries " + entryType.Name + ": " + pickup.Name + ", " + count.Name + "; enqueue: " + ((queueBridge._enqueue != null) ? queueBridge._enqueue.Name : "direct add") + "; remove: " + ((queueBridge._remove != null) ? queueBridge._remove.Name : "direct clear") + ")";
			return queueBridge;
		}
		return null;
	}

	private static bool TryQueueShape(Type type, out Type entryType, out MemberInfo pickup, out MemberInfo count)
	{
		entryType = null;
		pickup = null;
		count = null;
		if (type == null || !typeof(IDictionary).IsAssignableFrom(type) || !type.IsGenericType)
		{
			return false;
		}
		Type[] genericArguments = type.GetGenericArguments();
		if (genericArguments.Length != 2 || genericArguments[0] != typeof(ItemTier) || !typeof(IList).IsAssignableFrom(genericArguments[1]) || !genericArguments[1].IsGenericType)
		{
			return false;
		}
		entryType = genericArguments[1].GetGenericArguments().FirstOrDefault();
		if (entryType == null)
		{
			return false;
		}
		List<MemberInfo> source = entryType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Cast<MemberInfo>().Concat(from p in entryType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
			where p.GetIndexParameters().Length == 0 && p.CanRead
			select p)
			.ToList();
		pickup = source.FirstOrDefault((MemberInfo m) => TypeOf(m) == typeof(PickupIndex) && !m.Name.Contains("k__BackingField"));
		count = (from m in source
			where TypeOf(m) == typeof(int) && !m.Name.Contains("k__BackingField")
			orderby string.Equals(m.Name, "count", StringComparison.OrdinalIgnoreCase) descending
			select m).FirstOrDefault();
		if (pickup != null)
		{
			return count != null;
		}
		return false;
	}

	private static Type TypeOf(MemberInfo member)
	{
		if (!(member is FieldInfo fieldInfo))
		{
			return (member as PropertyInfo)?.PropertyType;
		}
		return fieldInfo.FieldType;
	}

	private static bool Matches(MethodInfo method, params Type[] leading)
	{
		ParameterInfo[] parameters = method.GetParameters();
		if (parameters.Length < leading.Length)
		{
			return false;
		}
		for (int i = 0; i < leading.Length; i++)
		{
			if (parameters[i].ParameterType != leading[i])
			{
				return false;
			}
		}
		return parameters.Skip(leading.Length).All((ParameterInfo p) => p.IsOptional);
	}

	private static object[] Arguments(MethodInfo method, params object[] leading)
	{
		ParameterInfo[] parameters = method.GetParameters();
		object[] array = new object[parameters.Length];
		for (int i = 0; i < parameters.Length; i++)
		{
			array[i] = ((i < leading.Length) ? leading[i] : (parameters[i].HasDefaultValue ? parameters[i].DefaultValue : Type.Missing));
		}
		return array;
	}

	internal List<KeyValuePair<PickupIndex, int>> Snapshot()
	{
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		List<KeyValuePair<PickupIndex, int>> list = new List<KeyValuePair<PickupIndex, int>>();
		IDictionary queues = Queues;
		if (queues == null)
		{
			return list;
		}
		foreach (DictionaryEntry item in queues)
		{
			if (!(item.Value is IList list2))
			{
				continue;
			}
			foreach (object item2 in list2)
			{
				if (item2 != null && Read(item2, _entryPickup) is PickupIndex key && Read(item2, _entryCount) is int num && num > 0)
				{
					list.Add(new KeyValuePair<PickupIndex, int>(key, num));
				}
			}
		}
		return list;
	}

	internal int Replace(IEnumerable<KeyValuePair<PickupIndex, int>> pickups, Func<PickupIndex, object> tierOf)
	{
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_005b: 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_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_0106: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		IDictionary queues = Queues;
		if (queues == null)
		{
			throw new InvalidOperationException("CommandQueueExtended's queues are not set up yet");
		}
		Clear(queues);
		int num = 0;
		foreach (KeyValuePair<PickupIndex, int> pickup in pickups)
		{
			if (_enqueue != null)
			{
				for (int i = 0; i < pickup.Value; i++)
				{
					_enqueue.Invoke(null, Arguments(_enqueue, pickup.Key));
					num++;
				}
				continue;
			}
			object obj = tierOf(pickup.Key);
			if (obj != null && queues.Contains(obj) && queues[obj] is IList list)
			{
				object obj2 = ((list.Count > 0) ? list[list.Count - 1] : null);
				if (obj2 != null && Read(obj2, _entryPickup) is PickupIndex val && ((PickupIndex)(ref val)).Equals(pickup.Key) && Read(obj2, _entryCount) is int num2 && !_entryType.IsValueType)
				{
					Write(obj2, _entryCount, num2 + pickup.Value);
				}
				else
				{
					object obj3 = Activator.CreateInstance(_entryType);
					Write(obj3, _entryPickup, pickup.Key);
					Write(obj3, _entryCount, pickup.Value);
					list.Add(obj3);
				}
				num += pickup.Value;
			}
		}
		return num;
	}

	private void Clear(IDictionary queues)
	{
		object[] array = queues.Keys.Cast<object>().ToArray();
		foreach (object obj in array)
		{
			if (!(queues[obj] is IList list))
			{
				continue;
			}
			if (_remove == null)
			{
				list.Clear();
				continue;
			}
			int num = 10000;
			while (list.Count > 0 && num-- > 0)
			{
				int count = list.Count;
				int num2 = ((!(Read(list[0], _entryCount) is int num3)) ? 1 : num3);
				_remove.Invoke(null, _removeTakesCount ? Arguments(_remove, obj, 0, num2) : Arguments(_remove, obj, 0));
				int num4 = ((list.Count > 0 && Read(list[0], _entryCount) is int num5) ? num5 : 0);
				if (list.Count == count && num4 == num2)
				{
					list.Clear();
					break;
				}
			}
		}
	}

	private static object Read(object instance, MemberInfo member)
	{
		if (!(member is FieldInfo fieldInfo))
		{
			return ((PropertyInfo)member).GetValue(instance);
		}
		return fieldInfo.GetValue(instance);
	}

	private static void Write(object instance, MemberInfo member, object value)
	{
		if (member is FieldInfo fieldInfo)
		{
			fieldInfo.SetValue(instance, value);
		}
		else if (member is PropertyInfo { CanWrite: not false } propertyInfo)
		{
			propertyInfo.SetValue(instance, value);
		}
	}
}
public sealed class QueuedPickup
{
	public string Pickup { get; }

	public int Count { get; set; }

	public QueuedPickup(string pickup, int count)
	{
		Pickup = pickup;
		Count = count;
	}

	public override string ToString()
	{
		return Pickup + "*" + Count;
	}
}
public static class QueueCodec
{
	public const int Version = 1;

	public const int MaxCount = 999;

	public static void Append(List<QueuedPickup> queue, string pickup, int count)
	{
		if (!string.IsNullOrEmpty(pickup) && count > 0)
		{
			if (queue.Count > 0 && string.Equals(queue[queue.Count - 1].Pickup, pickup, StringComparison.Ordinal))
			{
				queue[queue.Count - 1].Count = Math.Min(999, queue[queue.Count - 1].Count + count);
			}
			else
			{
				queue.Add(new QueuedPickup(pickup, Math.Min(999, count)));
			}
		}
	}

	public static string Encode(List<QueuedPickup> queue)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0090: Expected O, but got Unknown
		JSONArray val = new JSONArray();
		foreach (QueuedPickup item in queue)
		{
			JSONObject val2 = new JSONObject();
			((JSONNode)val2)["pickup"] = JSONNode.op_Implicit(item.Pickup);
			((JSONNode)val2)["count"] = JSONNode.op_Implicit(item.Count);
			((JSONNode)val).Add((JSONNode)(object)val2);
		}
		return ((object)new JSONObject
		{
			["version"] = JSONNode.op_Implicit(1),
			["queue"] = (JSONNode)(object)val
		}).ToString();
	}

	public static List<QueuedPickup> Decode(string text)
	{
		List<QueuedPickup> list = new List<QueuedPickup>();
		if (string.IsNullOrEmpty(text))
		{
			return list;
		}
		JSONNode val = JSON.Parse(text);
		JSONNode val2 = ((val != (object)null && val.IsObject) ? val["queue"] : null);
		if (val2 == (object)null || !val2.IsArray)
		{
			throw new FormatException("saved queue has no queue array");
		}
		foreach (JSONNode child in val2.Children)
		{
			if (!(child == (object)null) && child.IsObject)
			{
				JSONNode val3 = child["pickup"];
				JSONNode val4 = child["count"];
				if (val3 != (object)null && val3.IsString)
				{
					Append(list, val3.Value, (val4 != (object)null) ? val4.AsInt : 0);
				}
			}
		}
		return list;
	}

	public static int Total(List<QueuedPickup> queue)
	{
		int num = 0;
		foreach (QueuedPickup item in queue)
		{
			num += item.Count;
		}
		return num;
	}
}