Decompiled source of CorpseRunHelper v1.0.1

plugins/CorpseRunHelper.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CorpseRunHelper")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+583b781092ca24d11342d85f694475f0e38738d3")]
[assembly: AssemblyProduct("CorpseRunHelper")]
[assembly: AssemblyTitle("CorpseRunHelper")]
[assembly: AssemblyVersion("1.0.1.0")]
[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;
		}
	}
}
namespace PlayToDate.CorpseRunHelper
{
	[BepInPlugin("it.playtodate.corpserunhelper", "Corpse Run Helper", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		private sealed class Entry
		{
			public string Slot;

			public string Name;

			public int Quality;

			public int Variant;

			public long Crafter;

			public float Durability;
		}

		public const string Guid = "it.playtodate.corpserunhelper";

		public const string Name = "Corpse Run Helper";

		public const string Version = "1.0.1";

		private const string DataKey = "PlayToDate.CorpseRun";

		private static readonly string[] Slots = new string[9] { "m_helmetItem", "m_chestItem", "m_legItem", "m_shoulderItem", "m_utilityItem", "m_trinketItem", "m_rightItem", "m_leftItem", "m_ammoItem" };

		private static ConfigEntry<bool> _enabled;

		private static ConfigEntry<bool> _message;

		private static Plugin _self;

		private static Type _tPlayer;

		private static Type _tHumanoid;

		private static FieldInfo _localPlayer;

		private static FieldInfo _customData;

		private static FieldInfo _inventory;

		private static readonly Dictionary<string, FieldInfo> _slotFields = new Dictionary<string, FieldInfo>();

		private static MethodInfo _equip;

		private static MethodInfo _getAllItems;

		private static MethodInfo _message0;

		private static float _watchUntil;

		private static float _nextCheck;

		private static float _nextSync;

		private static MethodInfo _getLanguage;

		private static List<Entry> _memory = new List<Entry>();

		private void Awake()
		{
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Expected O, but got Unknown
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Expected O, but got Unknown
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Expected O, but got Unknown
			_self = this;
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Re-equip the gear you were wearing when you died, as soon as you loot your tombstone. / Rimette addosso l'equipaggiamento quando recuperi la tomba.");
			_message = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Show message", true, "Show a short message when your gear is re-equipped. / Mostra un breve messaggio quando l'equipaggiamento torna addosso.");
			_tPlayer = AccessTools.TypeByName("Player");
			_tHumanoid = AccessTools.TypeByName("Humanoid");
			Type type = AccessTools.TypeByName("TombStone");
			if (_tPlayer == null || _tHumanoid == null || type == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Corpse Run Helper: game types not found, mod disabled");
				return;
			}
			_localPlayer = AccessTools.Field(_tPlayer, "m_localPlayer");
			_customData = AccessTools.Field(_tPlayer, "m_customData");
			_inventory = AccessTools.Field(_tHumanoid, "m_inventory");
			string[] slots = Slots;
			foreach (string text in slots)
			{
				FieldInfo fieldInfo = AccessTools.Field(_tHumanoid, text);
				if (fieldInfo != null)
				{
					_slotFields[text] = fieldInfo;
				}
			}
			MethodInfo[] methods = _tHumanoid.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "EquipItem" && methodInfo.GetParameters().Length >= 1 && (_equip == null || methodInfo.GetParameters().Length > _equip.GetParameters().Length))
				{
					_equip = methodInfo;
				}
			}
			if (_localPlayer == null || _inventory == null || _equip == null || !_slotFields.ContainsKey("m_rightItem"))
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"Corpse Run Helper: the game changed (missing m_localPlayer, m_inventory, EquipItem or equipment slots), mod disabled");
				return;
			}
			if (_customData == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Corpse Run Helper: no m_customData on Player: the gear list is kept only until you quit the game");
			}
			Harmony val = new Harmony("it.playtodate.corpserunhelper");
			int num = 0;
			HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin).GetMethod("DeathPrefix", BindingFlags.Static | BindingFlags.NonPublic));
			HarmonyMethod val3 = new HarmonyMethod(typeof(Plugin).GetMethod("TombPostfix", BindingFlags.Static | BindingFlags.NonPublic));
			methods = _tPlayer.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo2 in methods)
			{
				if (methodInfo2.Name == "OnDeath")
				{
					try
					{
						val.Patch((MethodBase)methodInfo2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Corpse Run Helper: could not patch Player.OnDeath (" + ex.Message + ")"));
					}
				}
			}
			methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo3 in methods)
			{
				if (methodInfo3.Name == "Interact" || methodInfo3.Name == "GiveBoost")
				{
					try
					{
						val.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex2)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Corpse Run Helper: could not patch TombStone." + methodInfo3.Name + " (" + ex2.Message + ")"));
					}
				}
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Corpse Run Helper 1.0.1 ready (" + num + " patches)"));
		}

		private static object LocalPlayer()
		{
			try
			{
				return _localPlayer.GetValue(null);
			}
			catch
			{
				return null;
			}
		}

		private static void DeathPrefix(object __instance)
		{
			try
			{
				if (!_enabled.Value || __instance == null || __instance != LocalPlayer())
				{
					return;
				}
				List<Entry> list = new List<Entry>();
				foreach (KeyValuePair<string, FieldInfo> slotField in _slotFields)
				{
					object value = slotField.Value.GetValue(__instance);
					Entry e = Fingerprint(value);
					if (e != null)
					{
						e.Slot = slotField.Key;
						if (!list.Exists((Entry x) => x.Name == e.Name && x.Crafter == e.Crafter && Math.Abs(x.Durability - e.Durability) < 0.05f && x.Slot != "m_ammoItem"))
						{
							list.Add(e);
						}
					}
				}
				if (list.Count != 0)
				{
					Save(__instance, list);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)_self).Logger.LogWarning((object)("Corpse Run Helper: could not read your gear at death (" + ex.Message + ")"));
			}
		}

		private static void TombPostfix()
		{
			if (_enabled.Value)
			{
				_watchUntil = Time.time + 6f;
				_nextCheck = 0f;
			}
		}

		private void Update()
		{
			if (_memory.Count > 0 && Time.time > _nextSync)
			{
				_nextSync = Time.time + 5f;
				try
				{
					object obj = LocalPlayer();
					if (obj != null && _customData?.GetValue(obj) is IDictionary<string, string> dictionary && !dictionary.ContainsKey("PlayToDate.CorpseRun"))
					{
						Save(obj, _memory);
					}
				}
				catch
				{
				}
			}
			if (_watchUntil <= 0f)
			{
				return;
			}
			if (Time.time > _watchUntil)
			{
				_watchUntil = 0f;
			}
			else if (!(Time.time < _nextCheck))
			{
				_nextCheck = Time.time + 0.25f;
				try
				{
					Restore();
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Corpse Run Helper: could not re-equip (" + ex.Message + ")"));
					_watchUntil = 0f;
				}
			}
		}

		private static void Restore()
		{
			object obj = LocalPlayer();
			if (obj == null)
			{
				return;
			}
			List<Entry> list = Load(obj);
			if (list.Count == 0)
			{
				_watchUntil = 0f;
				return;
			}
			List<object> list2 = Items(obj);
			if (list2 == null)
			{
				return;
			}
			int num = 0;
			List<Entry> list3 = new List<Entry>();
			foreach (Entry item in list)
			{
				if (_slotFields.TryGetValue(item.Slot, out var value) && value.GetValue(obj) != null)
				{
					continue;
				}
				object obj2 = null;
				foreach (object item2 in list2)
				{
					if (!IsEquipped(item2))
					{
						Entry entry = Fingerprint(item2);
						if (entry != null && !(entry.Name != item.Name) && (item.Slot == "m_ammoItem" || (entry.Quality == item.Quality && entry.Variant == item.Variant && entry.Crafter == item.Crafter && Math.Abs(entry.Durability - item.Durability) < 0.05f)))
						{
							obj2 = item2;
							break;
						}
					}
				}
				if (obj2 == null)
				{
					list3.Add(item);
					continue;
				}
				ParameterInfo[] parameters = _equip.GetParameters();
				object[] array = new object[parameters.Length];
				array[0] = obj2;
				for (int i = 1; i < parameters.Length; i++)
				{
					array[i] = ((parameters[i].ParameterType == typeof(bool)) ? ((object)false) : (parameters[i].HasDefaultValue ? parameters[i].DefaultValue : null));
				}
				object obj3 = _equip.Invoke(obj, array);
				if (obj3 is bool && !(bool)obj3)
				{
					list3.Add(item);
				}
				else
				{
					num++;
				}
			}
			Save(obj, list3);
			if (num > 0)
			{
				if (_message.Value)
				{
					Say(obj, (!Italian()) ? ((num == 1) ? "Gear re-equipped" : ("Gear re-equipped (" + num + ")")) : ((num == 1) ? "Equipaggiamento rimesso" : ("Equipaggiamento rimesso (" + num + ")")));
				}
				if (list3.Count == 0)
				{
					_watchUntil = 0f;
				}
			}
		}

		private static Entry Fingerprint(object item)
		{
			if (item == null)
			{
				return null;
			}
			Type type = item.GetType();
			object obj = AccessTools.Field(type, "m_shared")?.GetValue(item);
			string text = ((obj != null) ? (AccessTools.Field(obj.GetType(), "m_name")?.GetValue(obj) as string) : null);
			if (string.IsNullOrEmpty(text))
			{
				return null;
			}
			return new Entry
			{
				Name = text,
				Quality = ToInt(AccessTools.Field(type, "m_quality")?.GetValue(item)),
				Variant = ToInt(AccessTools.Field(type, "m_variant")?.GetValue(item)),
				Crafter = ((AccessTools.Field(type, "m_crafterID")?.GetValue(item) is long num) ? num : 0),
				Durability = ((AccessTools.Field(type, "m_durability")?.GetValue(item) is float num2) ? num2 : 0f)
			};
		}

		private static bool IsEquipped(object item)
		{
			object obj = AccessTools.Field(item.GetType(), "m_equipped")?.GetValue(item);
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static int ToInt(object v)
		{
			try
			{
				return (v != null) ? Convert.ToInt32(v) : 0;
			}
			catch
			{
				return 0;
			}
		}

		private static List<object> Items(object player)
		{
			object value = _inventory.GetValue(player);
			if (value == null)
			{
				return null;
			}
			if (_getAllItems == null)
			{
				_getAllItems = AccessTools.Method(value.GetType(), "GetAllItems", Type.EmptyTypes, (Type[])null);
			}
			List<object> list = new List<object>();
			if (_getAllItems?.Invoke(value, null) is IEnumerable enumerable)
			{
				foreach (object item in enumerable)
				{
					list.Add(item);
				}
			}
			return list;
		}

		private static bool Italian()
		{
			try
			{
				Type type = AccessTools.TypeByName("Localization");
				object obj = type?.GetProperty("instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				if (_getLanguage == null)
				{
					_getLanguage = AccessTools.Method(type, "GetSelectedLanguage", (Type[])null, (Type[])null);
				}
				return string.Equals(_getLanguage?.Invoke(obj, null) as string, "Italian", StringComparison.OrdinalIgnoreCase);
			}
			catch
			{
				return false;
			}
		}

		private static void Say(object player, string text)
		{
			try
			{
				if (_message0 == null)
				{
					MethodInfo[] methods = AccessTools.TypeByName("Character").GetMethods(BindingFlags.Instance | BindingFlags.Public);
					foreach (MethodInfo methodInfo in methods)
					{
						if (methodInfo.Name == "Message" && methodInfo.GetParameters().Length >= 2)
						{
							_message0 = methodInfo;
							break;
						}
					}
				}
				if (!(_message0 == null))
				{
					ParameterInfo[] parameters = _message0.GetParameters();
					object[] array = new object[parameters.Length];
					array[0] = Enum.ToObject(parameters[0].ParameterType, 1);
					array[1] = text;
					for (int j = 2; j < parameters.Length; j++)
					{
						array[j] = (parameters[j].HasDefaultValue ? parameters[j].DefaultValue : (parameters[j].ParameterType.IsValueType ? Activator.CreateInstance(parameters[j].ParameterType) : null));
					}
					_message0.Invoke(player, array);
				}
			}
			catch
			{
			}
		}

		private static void Save(object player, List<Entry> list)
		{
			_memory = list;
			if (!(_customData?.GetValue(player) is IDictionary<string, string> dictionary))
			{
				return;
			}
			if (list.Count == 0)
			{
				dictionary.Remove("PlayToDate.CorpseRun");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (Entry item in list)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append(';');
				}
				stringBuilder.Append(item.Slot).Append('|').Append(Uri.EscapeDataString(item.Name))
					.Append('|')
					.Append(item.Quality)
					.Append('|')
					.Append(item.Variant)
					.Append('|')
					.Append(item.Crafter)
					.Append('|')
					.Append(item.Durability.ToString("R", CultureInfo.InvariantCulture));
			}
			dictionary["PlayToDate.CorpseRun"] = stringBuilder.ToString();
		}

		private static List<Entry> Load(object player)
		{
			if (!(_customData?.GetValue(player) is IDictionary<string, string> dictionary))
			{
				return _memory;
			}
			List<Entry> list = new List<Entry>();
			if (!dictionary.TryGetValue("PlayToDate.CorpseRun", out var value) || string.IsNullOrEmpty(value))
			{
				return new List<Entry>(_memory);
			}
			string[] array = value.Split(';');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('|');
				if (array2.Length >= 6)
				{
					long.TryParse(array2[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result);
					float.TryParse(array2[5], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2);
					list.Add(new Entry
					{
						Slot = array2[0],
						Name = Uri.UnescapeDataString(array2[1]),
						Quality = ToInt(array2[2]),
						Variant = ToInt(array2[3]),
						Crafter = result,
						Durability = result2
					});
				}
			}
			return list;
		}
	}
}