Decompiled source of Multitool v1.0.1

plugins/Marioalexsan.Multitool.dll

Decompiled a month ago
using System;
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.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Marioalexsan.Multitool")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+73def33ce42faeb1228b16d483cce8ca8c476ae4")]
[assembly: AssemblyProduct("Multitool")]
[assembly: AssemblyTitle("Marioalexsan.Multitool")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 Marioalexsan.Multitool
{
	internal static class Logging
	{
		private static ManualLogSource InternalLogger => MultitoolPlugin.Plugin.Logger;

		public static void LogFatal(object data, ConfigEntry<bool>? toggle = null)
		{
			Log(data, (LogLevel)1, toggle);
		}

		public static void LogError(object data, ConfigEntry<bool>? toggle = null)
		{
			Log(data, (LogLevel)2, toggle);
		}

		public static void LogWarning(object data, ConfigEntry<bool>? toggle = null)
		{
			Log(data, (LogLevel)4, toggle);
		}

		public static void LogMessage(object data, ConfigEntry<bool>? toggle = null)
		{
			Log(data, (LogLevel)8, toggle);
		}

		public static void LogInfo(object data, ConfigEntry<bool>? toggle = null)
		{
			Log(data, (LogLevel)16, toggle);
		}

		public static void LogDebug(object data, ConfigEntry<bool>? toggle = null)
		{
			Log(data, (LogLevel)32, toggle);
		}

		private static void Log(object data, LogLevel level = 16, ConfigEntry<bool>? toggle = null)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (toggle == null || toggle.Value)
			{
				ManualLogSource internalLogger = InternalLogger;
				if (internalLogger != null)
				{
					internalLogger.Log(level, data);
				}
			}
		}
	}
	[BepInPlugin("Marioalexsan.Multitool", "Multitool", "1.0.1")]
	public class MultitoolPlugin : BaseUnityPlugin
	{
		private static MultitoolPlugin? _plugin;

		private readonly Harmony _harmony;

		public static MultitoolPlugin Plugin => _plugin ?? throw new InvalidOperationException("MultitoolPlugin hasn't been initialized yet. Either wait until initialization, or check via ChainLoader instead.");

		internal ManualLogSource Logger { get; private set; }

		public MultitoolPlugin()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			_plugin = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("Marioalexsan.Multitool");
		}

		public void Awake()
		{
			_harmony.PatchAll();
		}
	}
	internal static class PluginUtils
	{
		public static bool ValidatePlugin(BaseUnityPlugin plugin, out string guid)
		{
			BaseUnityPlugin plugin2 = plugin;
			KeyValuePair<string, PluginInfo> keyValuePair = Chainloader.PluginInfos.FirstOrDefault((KeyValuePair<string, PluginInfo> x) => (Object)(object)x.Value.Instance == (Object)(object)plugin2);
			if (keyValuePair.Value == null)
			{
				Logging.LogWarning("Couldn't validate that calling mod exists in the Chainloader.");
				guid = "";
				return false;
			}
			guid = keyValuePair.Key;
			return true;
		}
	}
	internal static class ModInfo
	{
		public const string GUID = "Marioalexsan.Multitool";

		public const string NAME = "Multitool";

		public const string VERSION = "1.0.1";
	}
}
namespace Marioalexsan.Multitool.Utils
{
	public static class HierarchyTraverse
	{
		public static GameObject ForChild(this GameObject obj, string path, Action<GameObject> action)
		{
			action(obj.GoToChild(path));
			return obj;
		}

		public static GameObject GoToChild(this GameObject obj, string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				throw new ArgumentException("Path is empty", "path");
			}
			string[] array = path.Split("/");
			if (array.Length == 0)
			{
				throw new ArgumentException("Path is empty", "path");
			}
			GameObject val = obj;
			for (int i = 0; i < array.Length; i++)
			{
				Transform val2 = val.transform.Find(array[i]);
				if (!Object.op_Implicit((Object)(object)val2))
				{
					throw new InvalidOperationException("Child " + array[i] + " in path " + path + " does not exist for object " + ((Object)obj).name + ".");
				}
				val = ((Component)val2).gameObject;
			}
			return val;
		}

		public static GameObject ForEachChild(this GameObject obj, Action<GameObject, int> action)
		{
			Transform transform = obj.transform;
			for (int i = 0; i < transform.childCount; i++)
			{
				action(((Component)transform.GetChild(i)).gameObject, i);
			}
			return obj;
		}

		public static GameObject ForParent(this GameObject obj, Action<GameObject> action)
		{
			action(obj.GoToParent());
			return obj;
		}

		public static GameObject ForParent(this GameObject obj, int levels, Action<GameObject> action)
		{
			action(obj.GoToParent(levels));
			return obj;
		}

		public static GameObject GoToParent(this GameObject obj)
		{
			return obj.GoToParent(1);
		}

		public static GameObject GoToParent(this GameObject obj, int levels)
		{
			if (levels <= 0)
			{
				throw new ArgumentException("Levels must be a positive number", "levels");
			}
			int num = 0;
			while (++num <= levels)
			{
				Transform parent = obj.transform.parent;
				if (!Object.op_Implicit((Object)(object)parent))
				{
					throw new InvalidOperationException($"Parent at level {num} does not exist for object {((Object)obj).name}.");
				}
				obj = ((Component)parent).gameObject;
			}
			return obj;
		}

		public static GameObject ForComponent<T>(this GameObject obj, Action<T> action) where T : Component
		{
			T component = obj.GetComponent<T>();
			if (!Object.op_Implicit((Object)(object)component))
			{
				throw new InvalidOperationException("Component " + typeof(T).Name + " does not exist on object " + ((Object)obj).name + ".");
			}
			action(component);
			return obj;
		}

		public static GameObject ForComponentIfExists<T>(this GameObject obj, Action<T> action) where T : Component
		{
			T component = obj.GetComponent<T>();
			if (Object.op_Implicit((Object)(object)component))
			{
				action(component);
			}
			return obj;
		}

		public static GameObject ForChildComponent<T>(this GameObject obj, Action<T> action) where T : Component
		{
			T componentInChildren = obj.GetComponentInChildren<T>();
			if (!Object.op_Implicit((Object)(object)componentInChildren))
			{
				throw new InvalidOperationException("Component " + typeof(T).Name + " does not exist on object " + ((Object)obj).name + ".");
			}
			action(componentInChildren);
			return obj;
		}

		public static GameObject ForChildComponentIfExists<T>(this GameObject obj, Action<T> action) where T : Component
		{
			T componentInChildren = obj.GetComponentInChildren<T>();
			if (Object.op_Implicit((Object)(object)componentInChildren))
			{
				action(componentInChildren);
			}
			return obj;
		}

		public static GameObject ForEachComponent<T>(this GameObject obj, Action<T, int> action) where T : Component
		{
			T[] components = obj.GetComponents<T>();
			for (int i = 0; i < components.Length; i++)
			{
				action(components[i], i);
			}
			return obj;
		}

		public static GameObject SaveComponentRef<T>(this GameObject obj, ref T? field) where T : Component
		{
			T component = obj.GetComponent<T>();
			if (!Object.op_Implicit((Object)(object)component))
			{
				throw new InvalidOperationException("Component " + typeof(T).Name + " does not exist on object " + ((Object)obj).name + ".");
			}
			field = component;
			return obj;
		}

		public static GameObject Rename(this GameObject obj, string name)
		{
			((Object)obj).name = name;
			return obj;
		}
	}
}
namespace Marioalexsan.Multitool.SaveUtils
{
	public delegate void SaveProfileData<T>(CharacterFile file, int slotIndex, out T? data) where T : class;
	public delegate void LoadProfileData<T>(CharacterFile file, int slotIndex, T? data) where T : class;
	public delegate void DeleteProfileData(CharacterFile file, int slotIndex);
	internal delegate void SaveRawProfileData(CharacterFile file, int slotIndex, out JToken? data);
	internal delegate void LoadRawProfileData(CharacterFile file, int slotIndex, JToken? data);
	internal class PluginSaveConfig
	{
		public SaveRawProfileData OnSave { get; set; }

		public LoadRawProfileData OnLoad { get; set; }

		public DeleteProfileData OnDelete { get; set; }

		public PluginSaveConfig(SaveRawProfileData onSave, LoadRawProfileData onLoad, DeleteProfileData onDelete)
		{
			OnSave = onSave;
			OnLoad = onLoad;
			OnDelete = onDelete;
			base..ctor();
		}
	}
	internal class ProfileDataStore
	{
		public CharacterFile AssociatedFile { get; set; }

		public Dictionary<string, JToken> StoredData { get; set; }

		public ProfileDataStore(CharacterFile associatedFile, Dictionary<string, JToken> storedData)
		{
			AssociatedFile = associatedFile;
			StoredData = storedData;
			base..ctor();
		}
	}
	public static class SaveUtilsAPI
	{
		internal static readonly Dictionary<string, PluginSaveConfig> PluginConfiguration = new Dictionary<string, PluginSaveConfig>();

		internal static Dictionary<int, ProfileDataStore> ProfileDataStores { get; } = new Dictionary<int, ProfileDataStore>();


		public static string SaveDirectory => Path.Combine(Path.GetDirectoryName(Paths.ExecutablePath), "ATLYSS_Data", "profileCollections", "Marioalexsan_Multitool");

		public static string GetModdedCharacterProfilePath(int index)
		{
			return Path.Combine(SaveDirectory, $"atl_characterProfile_{index}_mods");
		}

		public static void RegisterProfileData<T>(string guid, SaveProfileData<T> onSave, LoadProfileData<T> onLoad, DeleteProfileData onDelete) where T : class
		{
			SaveProfileData<T> onSave2 = onSave;
			string guid2 = guid;
			LoadProfileData<T> onLoad2 = onLoad;
			if (string.IsNullOrWhiteSpace(guid2))
			{
				Logging.LogWarning("GUID " + guid2 + " is invalid!");
			}
			else if (PluginConfiguration.ContainsKey(guid2))
			{
				Logging.LogWarning("Mod " + guid2 + " already registered profile save data!");
			}
			else
			{
				PluginConfiguration[guid2] = new PluginSaveConfig(WrappedOnSave, WrappedOnLoad, onDelete);
			}
			void WrappedOnLoad(CharacterFile file, int slotIndex, JToken? data)
			{
				T data2;
				try
				{
					data2 = ((data != null) ? data.ToObject<T>() : null);
				}
				catch (Exception data3)
				{
					Logging.LogWarning("Failed to deserialize modded data of type " + typeof(T).Name + " for " + guid2 + "! This may cause issues with saved mod data.");
					Logging.LogWarning("Will try to load without any modded save data available.");
					Logging.LogWarning(data3);
					data2 = null;
				}
				onLoad2(file, slotIndex, data2);
			}
			void WrappedOnSave(CharacterFile file, int slotIndex, out JToken? data)
			{
				onSave2(file, slotIndex, out var data4);
				data = ((data4 != null) ? JToken.FromObject((object)data4) : null);
			}
		}
	}
}
namespace Marioalexsan.Multitool.SaveUtils.Patches
{
	[HarmonyPatch(typeof(ProfileDataManager), "Delete_ProfileData")]
	internal static class DeleteProfileData
	{
		private static void Postfix(ProfileDataManager __instance, int _index)
		{
			CharacterFile file = __instance._characterFiles[_index];
			try
			{
				ExecuteDeleteCallbacks(file, _index);
				DeleteModdedData(_index);
			}
			catch (Exception data)
			{
				Logging.LogError("Failed to execute DeleteProfileData logic for mooded saves! Please report this error to the mod author!");
				Logging.LogError(data);
			}
		}

		private static void DeleteModdedData(int index)
		{
			SaveUtilsAPI.ProfileDataStores.Remove(index);
			if (Directory.Exists(SaveUtilsAPI.SaveDirectory))
			{
				string moddedCharacterProfilePath = SaveUtilsAPI.GetModdedCharacterProfilePath(index);
				if (File.Exists(moddedCharacterProfilePath))
				{
					File.Delete(moddedCharacterProfilePath);
				}
			}
		}

		private static void ExecuteDeleteCallbacks(CharacterFile file, int index)
		{
			foreach (KeyValuePair<string, PluginSaveConfig> item in SaveUtilsAPI.PluginConfiguration)
			{
				try
				{
					item.Value.OnDelete(file, index);
				}
				catch (Exception data)
				{
					Logging.LogWarning("Failed to execute DeleteProfileData callback for " + item.Key + "! This may cause issues with saved mod data.");
					Logging.LogWarning(data);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ProfileDataManager), "Load_ProfileData")]
	[HarmonyPriority(800)]
	internal static class LoadProfileData
	{
		private static readonly object CallbackLock = new object();

		private static void Postfix(ProfileDataManager __instance, int _index)
		{
			CharacterFile val = __instance._characterFiles[_index];
			if (val._isEmptySlot)
			{
				return;
			}
			try
			{
				lock (CallbackLock)
				{
					LoadModdedData(val, _index);
					ExecuteLoadCallbacks(val, _index);
				}
			}
			catch (Exception data)
			{
				Logging.LogError("Failed to execute LoadProfileData logic for mooded saves! Please report this error to the mod author!");
				Logging.LogError(data);
			}
		}

		private static void LoadModdedData(CharacterFile file, int index)
		{
			if (!Directory.Exists(SaveUtilsAPI.SaveDirectory))
			{
				Directory.CreateDirectory(SaveUtilsAPI.SaveDirectory);
			}
			string moddedCharacterProfilePath = SaveUtilsAPI.GetModdedCharacterProfilePath(index);
			Dictionary<string, JToken> dictionary = new Dictionary<string, JToken>();
			SaveUtilsAPI.ProfileDataStores[index] = new ProfileDataStore(file, dictionary);
			if (!File.Exists(moddedCharacterProfilePath))
			{
				return;
			}
			JToken val = JToken.Parse(File.ReadAllText(moddedCharacterProfilePath));
			JObject val2 = (JObject)(object)((val is JObject) ? val : null);
			if (val2 == null)
			{
				Logging.LogWarning("Couldn't parse character file plugin profile data.");
				return;
			}
			JToken obj = val2["mods"];
			JObject val3 = (JObject)(object)((obj is JObject) ? obj : null);
			if (val3 == null)
			{
				Logging.LogWarning("Couldn't parse character file plugin profile data.");
				return;
			}
			foreach (KeyValuePair<string, JToken> item in val3)
			{
				JToken value = item.Value;
				JObject val4 = (JObject)(object)((value is JObject) ? value : null);
				if (val4 != null)
				{
					dictionary[item.Key] = (JToken)(object)val4;
				}
				else
				{
					Logging.LogWarning("Profile data key " + item.Key + " is not an object. This may cause issues with saved mod data.");
				}
			}
		}

		private static void ExecuteLoadCallbacks(CharacterFile file, int index)
		{
			ProfileDataStore profileDataStore = SaveUtilsAPI.ProfileDataStores[index];
			foreach (KeyValuePair<string, PluginSaveConfig> item in SaveUtilsAPI.PluginConfiguration)
			{
				JToken valueOrDefault = profileDataStore.StoredData.GetValueOrDefault(item.Key);
				try
				{
					item.Value.OnLoad(file, index, valueOrDefault);
				}
				catch (Exception data)
				{
					Logging.LogWarning("Failed to execute LoadProfileData callback for " + item.Key + "! This may cause issues with saved mod data.");
					Logging.LogWarning(data);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ProfileDataManager), "Save_ProfileData")]
	[HarmonyPriority(800)]
	internal static class SaveProfileData
	{
		private static void Postfix(ProfileDataManager __instance)
		{
			int selectedFileIndex = __instance._selectedFileIndex;
			CharacterFile characterFile = __instance._characterFile;
			try
			{
				ExecuteSaveCallbacks(characterFile, selectedFileIndex);
				SaveModdedData(characterFile, selectedFileIndex);
			}
			catch (Exception data)
			{
				Logging.LogError("Failed to execute SaveProfileData logic for mooded saves! Please report this error to the mod author!");
				Logging.LogError(data);
			}
		}

		private static void SaveModdedData(CharacterFile file, int index)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Expected O, but got Unknown
			if (!Directory.Exists(SaveUtilsAPI.SaveDirectory))
			{
				Directory.CreateDirectory(SaveUtilsAPI.SaveDirectory);
			}
			ProfileDataStore value;
			Dictionary<string, JToken> dictionary = (SaveUtilsAPI.ProfileDataStores.TryGetValue(index, out value) ? value.StoredData : new Dictionary<string, JToken>());
			JToken val = JToken.FromObject((object)dictionary);
			JObject val2 = new JObject
			{
				["multitoolVersion"] = JToken.op_Implicit("1.0.1"),
				["mods"] = val
			};
			string moddedCharacterProfilePath = SaveUtilsAPI.GetModdedCharacterProfilePath(index);
			File.WriteAllText(moddedCharacterProfilePath, ((JToken)val2).ToString((Formatting)1, Array.Empty<JsonConverter>()));
		}

		private static void ExecuteSaveCallbacks(CharacterFile file, int index)
		{
			if (!SaveUtilsAPI.ProfileDataStores.TryGetValue(index, out ProfileDataStore value))
			{
				value = (SaveUtilsAPI.ProfileDataStores[index] = new ProfileDataStore(file, new Dictionary<string, JToken>()));
			}
			foreach (KeyValuePair<string, PluginSaveConfig> item in SaveUtilsAPI.PluginConfiguration)
			{
				try
				{
					item.Value.OnSave(file, index, out var data);
					if (data == null)
					{
						value.StoredData.Remove(item.Key);
					}
					else
					{
						value.StoredData[item.Key] = data;
					}
				}
				catch (Exception data2)
				{
					Logging.LogWarning("Failed to execute SaveProfileData callback for " + item.Key + "! This may cause issues with saved mod data.");
					Logging.LogWarning(data2);
				}
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}