Decompiled source of Clover and Gloom Menus v1.0.0

RESTORE.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[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("RESTORE")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RESTORE")]
[assembly: AssemblyTitle("RESTORE")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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;
		}
	}
}
namespace RestoreMod
{
	[BepInPlugin("com.asher.restore", "RESTORE", "0.4.4")]
	public sealed class RestorePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.asher.restore";

		public const string PluginName = "RESTORE";

		public const string PluginVersion = "0.4.4";

		private Harmony harmony;

		private string modRoot;

		internal static RestorePlugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal RestoreBundleManager Bundles { get; private set; }

		private void Awake()
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			modRoot = ResolveModRoot();
			Directory.CreateDirectory(modRoot);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"RESTORE 0.4.4 starting.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin DLL: " + ResolvePluginDllPath()));
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Mod root: " + modRoot));
			RestorePlayerData.Initialize(((BaseUnityPlugin)this).Logger);
			harmony = new Harmony("com.asher.restore");
			RestoreQuestUiFix.Initialize(harmony, ((BaseUnityPlugin)this).Logger);
			Bundles = new RestoreBundleManager(modRoot, ((BaseUnityPlugin)this).Logger);
			Bundles.Load();
			RestoreLocalization.Initialize(modRoot, harmony, ((BaseUnityPlugin)this).Logger);
			RestoreRoomLoader.Initialize(this, Bundles, harmony, ((BaseUnityPlugin)this).Logger);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"RESTORE startup complete.");
		}

		private string ResolveModRoot()
		{
			if (((BaseUnityPlugin)this).Info != null && !string.IsNullOrWhiteSpace(((BaseUnityPlugin)this).Info.Location))
			{
				string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
				if (!string.IsNullOrWhiteSpace(directoryName))
				{
					return Path.GetFullPath(directoryName);
				}
			}
			string location = Assembly.GetExecutingAssembly().Location;
			if (!string.IsNullOrWhiteSpace(location))
			{
				string directoryName2 = Path.GetDirectoryName(location);
				if (!string.IsNullOrWhiteSpace(directoryName2))
				{
					return Path.GetFullPath(directoryName2);
				}
			}
			return Path.GetFullPath(Paths.PluginPath);
		}

		private string ResolvePluginDllPath()
		{
			if (((BaseUnityPlugin)this).Info != null && !string.IsNullOrWhiteSpace(((BaseUnityPlugin)this).Info.Location))
			{
				return ((BaseUnityPlugin)this).Info.Location;
			}
			return Assembly.GetExecutingAssembly().Location;
		}

		private void OnDestroy()
		{
			RestoreRoomLoader.Shutdown();
			RestoreQuestUiFix.Shutdown();
			RestoreLocalization.Shutdown();
			RestorePlayerData.Shutdown();
			if (harmony != null)
			{
				harmony.UnpatchSelf();
				harmony = null;
			}
			if (Bundles != null)
			{
				Bundles.Unload(unloadLoadedObjects: false);
				Bundles = null;
			}
			Instance = null;
			Log = null;
		}

		public static T GetDataAsset<T>(string assetName) where T : Object
		{
			if ((Object)(object)Instance == (Object)null || Instance.Bundles == null)
			{
				return default(T);
			}
			return Instance.Bundles.GetDataAsset<T>(assetName);
		}

		public static T[] GetAllDataAssets<T>() where T : Object
		{
			if ((Object)(object)Instance == (Object)null || Instance.Bundles == null)
			{
				return Array.Empty<T>();
			}
			return Instance.Bundles.GetAllDataAssets<T>();
		}

		public static void ReloadLocalization()
		{
			RestoreLocalization.Reload();
		}
	}
	public sealed class RestoreBundleManager
	{
		private readonly string modRoot;

		private readonly ManualLogSource logger;

		private readonly Dictionary<string, string> dataAssets = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

		private readonly List<string> overlayAssets = new List<string>();

		public AssetBundle DataBundle { get; private set; }

		public AssetBundle OverlayBundle { get; private set; }

		public RestoreBundleManager(string root, ManualLogSource log)
		{
			modRoot = root;
			logger = log;
		}

		public void Load()
		{
			Directory.CreateDirectory(modRoot);
			RestoreAudioResolver.Initialize(logger);
			DataBundle = LoadBundle("000_restore_data");
			OverlayBundle = LoadBundle("restore_room_overlays");
			RestoreAudioResolver.RegisterBundle(DataBundle, "000_restore_data");
			RestoreAudioResolver.RegisterBundle(OverlayBundle, "restore_room_overlays");
			RestoreAudioResolver.RefreshLoadedGameClips();
			IndexDataAssets();
			IndexOverlayAssets();
		}

		public T GetDataAsset<T>(string assetName) where T : Object
		{
			if ((Object)(object)DataBundle == (Object)null || string.IsNullOrWhiteSpace(assetName))
			{
				return default(T);
			}
			if (!dataAssets.TryGetValue(NormalizeAssetName(assetName), out var value))
			{
				logger.LogWarning((object)("Data asset not found: " + assetName));
				return default(T);
			}
			T val = DataBundle.LoadAsset<T>(value);
			if ((Object)(object)val == (Object)null)
			{
				logger.LogWarning((object)("Data asset exists but could not be loaded as " + typeof(T).FullName + ": " + value));
				return default(T);
			}
			RestoreAudioResolver.RegisterBundledAsset((Object)(object)val);
			return RestoreAudioResolver.RepairAsset(val);
		}

		public T[] GetAllDataAssets<T>() where T : Object
		{
			if ((Object)(object)DataBundle == (Object)null)
			{
				return Array.Empty<T>();
			}
			T[] array = DataBundle.LoadAllAssets<T>();
			for (int i = 0; i < array.Length; i++)
			{
				RestoreAudioResolver.RegisterBundledAsset((Object)(object)array[i]);
				array[i] = RestoreAudioResolver.RepairAsset(array[i]);
			}
			return array;
		}

		public List<GameObject> LoadOverlayPrefabs(string sceneName)
		{
			List<GameObject> list = new List<GameObject>();
			if ((Object)(object)OverlayBundle == (Object)null || string.IsNullOrWhiteSpace(sceneName))
			{
				return list;
			}
			string sceneName2 = NormalizeAssetName(sceneName);
			for (int i = 0; i < overlayAssets.Count; i++)
			{
				string text = overlayAssets[i];
				if (MatchesScene(NormalizeAssetName(text), sceneName2))
				{
					GameObject val = OverlayBundle.LoadAsset<GameObject>(text);
					if ((Object)(object)val == (Object)null)
					{
						logger.LogWarning((object)("Could not load overlay prefab: " + text));
						continue;
					}
					RestoreAudioResolver.RegisterBundledAsset((Object)(object)val);
					RestoreAudioResolver.RepairGameObject(val);
					list.Add(val);
					logger.LogInfo((object)("Matched overlay prefab '" + ((Object)val).name + "' to scene '" + sceneName + "'."));
				}
			}
			return list;
		}

		public void Unload(bool unloadLoadedObjects)
		{
			if ((Object)(object)OverlayBundle != (Object)null)
			{
				OverlayBundle.Unload(unloadLoadedObjects);
				OverlayBundle = null;
			}
			if ((Object)(object)DataBundle != (Object)null)
			{
				DataBundle.Unload(unloadLoadedObjects);
				DataBundle = null;
			}
			dataAssets.Clear();
			overlayAssets.Clear();
		}

		private AssetBundle LoadBundle(string fileName)
		{
			string text = Path.Combine(modRoot, fileName);
			string text2 = Path.Combine(modRoot, "Bundles", fileName);
			string text3 = null;
			if (File.Exists(text))
			{
				text3 = text;
			}
			else if (File.Exists(text2))
			{
				text3 = text2;
			}
			if (text3 == null)
			{
				logger.LogError((object)("Bundle not found. Checked:\n" + text + "\n" + text2));
				return null;
			}
			logger.LogInfo((object)("Loading bundle from: " + text3));
			AssetBundle val = AssetBundle.LoadFromFile(text3);
			if ((Object)(object)val == (Object)null)
			{
				logger.LogError((object)("Unity failed to load bundle: " + text3));
				return null;
			}
			logger.LogInfo((object)("Loaded bundle: " + Path.GetFileName(text3)));
			return val;
		}

		private void IndexDataAssets()
		{
			dataAssets.Clear();
			if ((Object)(object)DataBundle == (Object)null)
			{
				return;
			}
			string[] allAssetNames = DataBundle.GetAllAssetNames();
			for (int i = 0; i < allAssetNames.Length; i++)
			{
				string key = NormalizeAssetName(allAssetNames[i]);
				if (!dataAssets.ContainsKey(key))
				{
					dataAssets.Add(key, allAssetNames[i]);
				}
			}
			logger.LogInfo((object)("Indexed " + dataAssets.Count + " data asset(s)."));
		}

		private void IndexOverlayAssets()
		{
			overlayAssets.Clear();
			if ((Object)(object)OverlayBundle == (Object)null)
			{
				return;
			}
			string[] allAssetNames = OverlayBundle.GetAllAssetNames();
			for (int i = 0; i < allAssetNames.Length; i++)
			{
				if (allAssetNames[i].EndsWith(".prefab", StringComparison.OrdinalIgnoreCase))
				{
					overlayAssets.Add(allAssetNames[i]);
				}
			}
			overlayAssets.Sort(StringComparer.OrdinalIgnoreCase);
			logger.LogInfo((object)("Indexed " + overlayAssets.Count + " overlay prefab(s)."));
		}

		private static bool MatchesScene(string assetName, string sceneName)
		{
			if (assetName.Equals(sceneName, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (assetName.Equals(sceneName + "r", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (assetName.StartsWith(sceneName + "__", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (assetName.StartsWith(sceneName + "+", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return false;
		}

		private static string NormalizeAssetName(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			value = value.Replace('\\', '/');
			return Path.GetFileNameWithoutExtension(Path.GetFileName(value)).Trim();
		}
	}
	public static class RestoreRoomLoader
	{
		private sealed class UnpackedObjectState
		{
			public Transform Transform;

			public bool WasActive;
		}

		private static RestorePlugin plugin;

		private static RestoreBundleManager bundles;

		private static ManualLogSource logger;

		private static readonly HashSet<int> spawnedSceneHandles = new HashSet<int>();

		private static bool initialized;

		public static void Initialize(RestorePlugin owner, RestoreBundleManager bundleManager, Harmony harmony, ManualLogSource log)
		{
			if (!initialized)
			{
				plugin = owner;
				bundles = bundleManager;
				logger = log;
				initialized = true;
				SceneManager.sceneLoaded += OnSceneLoaded;
				SceneManager.sceneUnloaded += OnSceneUnloaded;
				PatchSetupSceneRefs(harmony);
			}
		}

		public static void Shutdown()
		{
			if (initialized)
			{
				SceneManager.sceneLoaded -= OnSceneLoaded;
				SceneManager.sceneUnloaded -= OnSceneUnloaded;
				spawnedSceneHandles.Clear();
				plugin = null;
				bundles = null;
				logger = null;
				initialized = false;
			}
		}

		private static void PatchSetupSceneRefs(Harmony harmony)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("GameManager");
			if (type == null)
			{
				logger.LogWarning((object)"GameManager type not found. Room overlays will use the sceneLoaded fallback.");
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "SetupSceneRefs", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				logger.LogWarning((object)"GameManager.SetupSceneRefs was not found. Room overlays will use the sceneLoaded fallback.");
				return;
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(RestoreRoomLoader), "BeforeSetupSceneRefs", (Type[])null, (Type[])null);
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			logger.LogInfo((object)"Patched GameManager.SetupSceneRefs for pre-setup overlays.");
		}

		private static void BeforeSetupSceneRefs()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			SpawnForScene(SceneManager.GetActiveScene());
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)plugin != (Object)null)
			{
				((MonoBehaviour)plugin).StartCoroutine(SpawnFallback(scene));
			}
		}

		private static IEnumerator SpawnFallback(Scene scene)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			yield return null;
			SpawnForScene(scene);
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			spawnedSceneHandles.Remove(((Scene)(ref scene)).handle);
		}

		private static void SpawnForScene(Scene scene)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: 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_0164: Unknown result type (might be due to invalid IL or missing references)
			if (!initialized || bundles == null || !((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded || spawnedSceneHandles.Contains(((Scene)(ref scene)).handle))
			{
				return;
			}
			List<GameObject> list = bundles.LoadOverlayPrefabs(((Scene)(ref scene)).name);
			if (list == null || list.Count == 0)
			{
				return;
			}
			spawnedSceneHandles.Add(((Scene)(ref scene)).handle);
			List<GameObject> list2 = new List<GameObject>();
			for (int i = 0; i < list.Count; i++)
			{
				GameObject val = list[i];
				if (!((Object)(object)val == (Object)null))
				{
					GameObject val2 = InstantiateInactive(val);
					if ((Object)(object)val2 == (Object)null)
					{
						logger.LogWarning((object)("Failed to instantiate overlay prefab at index " + i + " for scene " + ((Scene)(ref scene)).name + "."));
					}
					else
					{
						((Object)val2).name = ((Object)val).name;
						SceneManager.MoveGameObjectToScene(val2, scene);
						list2.Add(val2);
					}
				}
			}
			for (int j = 0; j < list2.Count; j++)
			{
				GameObject val3 = list2[j];
				if (!((Object)(object)val3 == (Object)null))
				{
					ApplyDeleteMarkers(val3, scene);
				}
			}
			HashSet<string> usedRootNames = CollectExistingRootNames(scene, list2);
			int num = 0;
			for (int k = 0; k < list2.Count; k++)
			{
				GameObject val4 = list2[k];
				if (!((Object)(object)val4 == (Object)null))
				{
					num += UnpackOverlayContents(val4, scene, usedRootNames);
				}
			}
			logger.LogInfo((object)("Loaded " + list2.Count + " overlay prefab container(s) and unpacked " + num + " root object(s) in " + ((Scene)(ref scene)).name + "."));
		}

		private static GameObject InstantiateInactive(GameObject prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return null;
			}
			bool activeSelf = prefab.activeSelf;
			try
			{
				prefab.SetActive(false);
				return Object.Instantiate<GameObject>(prefab);
			}
			finally
			{
				prefab.SetActive(activeSelf);
			}
		}

		private static void ApplyDeleteMarkers(GameObject overlayInstance, Scene scene)
		{
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)overlayInstance == (Object)null)
			{
				return;
			}
			Transform[] componentsInChildren = overlayInstance.GetComponentsInChildren<Transform>(true);
			List<Transform> list = new List<Transform>();
			foreach (Transform val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && ((Object)val).name.Equals("DELETE", StringComparison.OrdinalIgnoreCase))
				{
					list.Add(val);
				}
			}
			for (int j = 0; j < list.Count; j++)
			{
				Transform val2 = list[j];
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				List<string> list2 = new List<string>();
				for (int k = 0; k < val2.childCount; k++)
				{
					Transform child = val2.GetChild(k);
					if (!((Object)(object)child == (Object)null))
					{
						list2.Add(((Object)child).name);
					}
				}
				for (int l = 0; l < list2.Count; l++)
				{
					DeleteSceneTargets(scene, overlayInstance.transform, list2[l]);
				}
			}
			list.Sort((Transform left, Transform right) => GetTransformDepth(right).CompareTo(GetTransformDepth(left)));
			for (int num = 0; num < list.Count; num++)
			{
				Transform val3 = list[num];
				if (!((Object)(object)val3 == (Object)null))
				{
					Object.DestroyImmediate((Object)(object)((Component)val3).gameObject);
				}
			}
		}

		private static int GetTransformDepth(Transform transform)
		{
			int num = 0;
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				num++;
				val = val.parent;
			}
			return num;
		}

		private static void DeleteSceneTargets(Scene scene, Transform overlayRoot, string targetNameOrPath)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrWhiteSpace(targetNameOrPath))
			{
				return;
			}
			List<GameObject> list = FindSceneObjects(scene, targetNameOrPath);
			int num = 0;
			for (int i = 0; i < list.Count; i++)
			{
				GameObject val = list[i];
				if (!((Object)(object)val == (Object)null) && (!((Object)(object)overlayRoot != (Object)null) || (!((Object)(object)val.transform == (Object)(object)overlayRoot) && !val.transform.IsChildOf(overlayRoot))))
				{
					val.SetActive(false);
					Object.DestroyImmediate((Object)(object)val);
					num++;
				}
			}
			if (num == 0)
			{
				logger.LogWarning((object)("DELETE marker found no target in " + ((Scene)(ref scene)).name + ": " + targetNameOrPath));
			}
			else
			{
				logger.LogInfo((object)("DELETE removed " + num + " object(s): " + targetNameOrPath));
			}
		}

		private static int UnpackOverlayContents(GameObject overlayContainer, Scene scene, HashSet<string> usedRootNames)
		{
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)overlayContainer == (Object)null)
			{
				return 0;
			}
			Transform transform = overlayContainer.transform;
			int childCount = transform.childCount;
			if (childCount == 0)
			{
				logger.LogWarning((object)("Overlay prefab container '" + ((Object)overlayContainer).name + "' contained no root children to unpack."));
				Object.DestroyImmediate((Object)(object)overlayContainer);
				return 0;
			}
			List<UnpackedObjectState> list = new List<UnpackedObjectState>(childCount);
			for (int i = 0; i < childCount; i++)
			{
				Transform child = transform.GetChild(i);
				if (!((Object)(object)child == (Object)null))
				{
					UnpackedObjectState unpackedObjectState = new UnpackedObjectState();
					unpackedObjectState.Transform = child;
					unpackedObjectState.WasActive = ((Component)child).gameObject.activeSelf;
					list.Add(unpackedObjectState);
				}
			}
			for (int j = 0; j < list.Count; j++)
			{
				Transform transform2 = list[j].Transform;
				if (!((Object)(object)transform2 == (Object)null))
				{
					((Component)transform2).gameObject.SetActive(false);
				}
			}
			int num = 0;
			for (int k = 0; k < list.Count; k++)
			{
				Transform transform3 = list[k].Transform;
				if (!((Object)(object)transform3 == (Object)null))
				{
					string name = ((Object)((Component)transform3).gameObject).name;
					string uniqueRootName = GetUniqueRootName(name, usedRootNames);
					((Object)((Component)transform3).gameObject).name = uniqueRootName;
					transform3.SetParent((Transform)null, true);
					if (((Component)transform3).gameObject.scene != scene)
					{
						SceneManager.MoveGameObjectToScene(((Component)transform3).gameObject, scene);
					}
					usedRootNames.Add(uniqueRootName);
					num++;
					if (!uniqueRootName.Equals(name, StringComparison.Ordinal))
					{
						logger.LogInfo((object)("Renamed unpacked overlay object '" + name + "' to '" + uniqueRootName + "' in " + ((Scene)(ref scene)).name + "."));
					}
				}
			}
			Object.DestroyImmediate((Object)(object)overlayContainer);
			for (int l = 0; l < list.Count; l++)
			{
				Transform transform4 = list[l].Transform;
				if (!((Object)(object)transform4 == (Object)null))
				{
					((Component)transform4).gameObject.SetActive(list[l].WasActive);
				}
			}
			return num;
		}

		private static HashSet<string> CollectExistingRootNames(Scene scene, List<GameObject> overlayContainers)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			HashSet<GameObject> hashSet2 = new HashSet<GameObject>();
			if (overlayContainers != null)
			{
				for (int i = 0; i < overlayContainers.Count; i++)
				{
					GameObject val = overlayContainers[i];
					if ((Object)(object)val != (Object)null)
					{
						hashSet2.Add(val);
					}
				}
			}
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			foreach (GameObject val2 in rootGameObjects)
			{
				if (!((Object)(object)val2 == (Object)null) && !hashSet2.Contains(val2))
				{
					hashSet.Add(((Object)val2).name);
				}
			}
			return hashSet;
		}

		private static string GetUniqueRootName(string requestedName, HashSet<string> usedNames)
		{
			string text = (string.IsNullOrWhiteSpace(requestedName) ? "Overlay Object" : requestedName.Trim());
			if (usedNames == null || !usedNames.Contains(text))
			{
				return text;
			}
			int num = 1;
			string text2;
			while (true)
			{
				text2 = text + " " + num;
				if (!usedNames.Contains(text2))
				{
					break;
				}
				num++;
			}
			return text2;
		}

		private static List<GameObject> FindSceneObjects(Scene scene, string nameOrPath)
		{
			List<GameObject> list = new List<GameObject>();
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			if (nameOrPath.IndexOf('/') >= 0)
			{
				string[] array = nameOrPath.Split('/');
				foreach (GameObject val in rootGameObjects)
				{
					if ((Object)(object)val == (Object)null || !((Object)val).name.Equals(array[0], StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					Transform val2 = val.transform;
					for (int j = 1; j < array.Length; j++)
					{
						val2 = FindDirectChild(val2, array[j]);
						if ((Object)(object)val2 == (Object)null)
						{
							break;
						}
					}
					if ((Object)(object)val2 != (Object)null)
					{
						list.Add(((Component)val2).gameObject);
					}
				}
				return list;
			}
			foreach (GameObject val3 in rootGameObjects)
			{
				if ((Object)(object)val3 == (Object)null)
				{
					continue;
				}
				Transform[] componentsInChildren = val3.GetComponentsInChildren<Transform>(true);
				foreach (Transform val4 in componentsInChildren)
				{
					if (!((Object)(object)val4 == (Object)null) && ((Object)val4).name.Equals(nameOrPath, StringComparison.OrdinalIgnoreCase))
					{
						list.Add(((Component)val4).gameObject);
					}
				}
			}
			return list;
		}

		private static Transform FindDirectChild(Transform parent, string name)
		{
			if ((Object)(object)parent == (Object)null)
			{
				return null;
			}
			for (int i = 0; i < parent.childCount; i++)
			{
				Transform child = parent.GetChild(i);
				if (!((Object)(object)child == (Object)null) && ((Object)child).name.Equals(name, StringComparison.OrdinalIgnoreCase))
				{
					return child;
				}
			}
			return null;
		}
	}
	public static class RestoreLocalization
	{
		private static readonly Dictionary<string, Dictionary<string, Dictionary<string, string>>> localization = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>(StringComparer.OrdinalIgnoreCase);

		private static Type languageType;

		private static Type localisedTextCollectionType;

		private static Type localisedTextCollectionDataType;

		private static FieldInfo currentEntrySheetsField;

		private static MethodInfo currentLanguageMethod;

		private static MethodInfo sendMonoMessageMethod;

		private static FieldInfo collectionDataField;

		private static FieldInfo collectionIsInitialisedField;

		private static FieldInfo collectionCurrentTextsField;

		private static FieldInfo collectionCurrentProbabilitiesField;

		private static FieldInfo collectionPreviousIndexField;

		private static string localizationRoot;

		private static ManualLogSource logger;

		private static Harmony harmony;

		private static bool initialized;

		public static void Initialize(string modRoot, Harmony harmonyInstance, ManualLogSource log)
		{
			localizationRoot = Path.Combine(modRoot, "Localization");
			harmony = harmonyInstance;
			logger = log;
			initialized = true;
			Directory.CreateDirectory(localizationRoot);
			ResolveRuntimeTypes();
			if (languageType == null)
			{
				logger.LogError((object)"RESTORE could not find runtime type TeamCherry.Localization.Language.");
				ReloadFilesOnly();
				return;
			}
			InstallLanguageSwitchPatch();
			Reload();
			logger.LogInfo((object)("RESTORE localization initialized using runtime type " + languageType.FullName + " from assembly " + languageType.Assembly.GetName().Name + "."));
		}

		public static void Shutdown()
		{
			localization.Clear();
			languageType = null;
			localisedTextCollectionType = null;
			localisedTextCollectionDataType = null;
			currentEntrySheetsField = null;
			currentLanguageMethod = null;
			sendMonoMessageMethod = null;
			collectionDataField = null;
			collectionIsInitialisedField = null;
			collectionCurrentTextsField = null;
			collectionCurrentProbabilitiesField = null;
			collectionPreviousIndexField = null;
			localizationRoot = null;
			logger = null;
			harmony = null;
			initialized = false;
		}

		public static void Reload()
		{
			ReloadFilesOnly();
			if (!(languageType == null))
			{
				InjectLocalizationForCurrentLanguage();
				ResetLoadedLocalisedTextCollections();
			}
		}

		private static void ResolveRuntimeTypes()
		{
			languageType = FindLoadedType("TeamCherry.Localization.Language");
			localisedTextCollectionType = FindLoadedType("TeamCherry.Localization.LocalisedTextCollection");
			localisedTextCollectionDataType = FindLoadedType("TeamCherry.Localization.LocalisedTextCollectionData");
			if (languageType != null)
			{
				currentEntrySheetsField = AccessTools.Field(languageType, "_currentEntrySheets");
				currentLanguageMethod = AccessTools.Method(languageType, "CurrentLanguage", Type.EmptyTypes, (Type[])null);
				sendMonoMessageMethod = AccessTools.Method(languageType, "SendMonoMessage", (Type[])null, (Type[])null);
			}
			if (localisedTextCollectionType != null)
			{
				collectionDataField = AccessTools.Field(localisedTextCollectionType, "data");
			}
			if (localisedTextCollectionDataType != null)
			{
				collectionIsInitialisedField = AccessTools.Field(localisedTextCollectionDataType, "isInitialised");
				collectionCurrentTextsField = AccessTools.Field(localisedTextCollectionDataType, "currentTexts");
				collectionCurrentProbabilitiesField = AccessTools.Field(localisedTextCollectionDataType, "currentProbabilities");
				collectionPreviousIndexField = AccessTools.Field(localisedTextCollectionDataType, "previousIndex");
			}
		}

		private static Type FindLoadedType(string fullName)
		{
			Type type = AccessTools.TypeByName(fullName);
			if (type != null)
			{
				return type;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (!(assembly == null))
				{
					try
					{
						type = assembly.GetType(fullName, throwOnError: false);
					}
					catch
					{
						type = null;
					}
					if (type != null)
					{
						return type;
					}
				}
			}
			return null;
		}

		private static void ReloadFilesOnly()
		{
			localization.Clear();
			if (string.IsNullOrEmpty(localizationRoot))
			{
				return;
			}
			Directory.CreateDirectory(localizationRoot);
			string[] directories = Directory.GetDirectories(localizationRoot, "*", SearchOption.TopDirectoryOnly);
			Array.Sort(directories, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
			int num = 0;
			int num2 = 0;
			foreach (string path in directories)
			{
				string fileName = Path.GetFileName(path);
				if (string.IsNullOrWhiteSpace(fileName))
				{
					continue;
				}
				List<string> list = new List<string>();
				list.AddRange(Directory.GetFiles(path, "*.yml", SearchOption.TopDirectoryOnly));
				list.AddRange(Directory.GetFiles(path, "*.yaml", SearchOption.TopDirectoryOnly));
				list.Sort(StringComparer.OrdinalIgnoreCase);
				for (int j = 0; j < list.Count; j++)
				{
					string text = list[j];
					string text2 = NormalizeLanguageCode(Path.GetFileNameWithoutExtension(text));
					try
					{
						Dictionary<string, string> dictionary = ParseSimpleYaml(text);
						RegisterLocalization(text2, fileName, dictionary);
						num++;
						num2 += dictionary.Count;
						logger.LogInfo((object)("RESTORE localization loaded: " + fileName + "/" + text2 + Path.GetExtension(text) + " (" + dictionary.Count + " entries)."));
					}
					catch (Exception ex)
					{
						logger.LogError((object)("RESTORE failed to load localization file " + text + ":\n" + ex));
					}
				}
			}
			logger.LogInfo((object)("RESTORE parsed " + num2 + " localization entries from " + num + " file(s)."));
		}

		private static void RegisterLocalization(string languageCode, string sheetName, Dictionary<string, string> entries)
		{
			if (!localization.TryGetValue(languageCode, out var value))
			{
				value = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
				localization.Add(languageCode, value);
			}
			if (!value.TryGetValue(sheetName, out var value2))
			{
				value2 = new Dictionary<string, string>(StringComparer.Ordinal);
				value.Add(sheetName, value2);
			}
			foreach (KeyValuePair<string, string> entry in entries)
			{
				value2[entry.Key] = entry.Value;
			}
		}

		private static void InjectLocalizationForCurrentLanguage()
		{
			if (!initialized || languageType == null)
			{
				return;
			}
			if (currentEntrySheetsField == null)
			{
				logger.LogError((object)"RESTORE could not find runtime field Language._currentEntrySheets.");
				return;
			}
			if (currentLanguageMethod == null)
			{
				logger.LogError((object)"RESTORE could not find runtime method Language.CurrentLanguage().");
				return;
			}
			string text;
			try
			{
				object obj = currentLanguageMethod.Invoke(null, null);
				text = NormalizeLanguageCode((obj != null) ? obj.ToString() : "EN");
			}
			catch (Exception ex)
			{
				logger.LogError((object)("RESTORE could not read the current language:\n" + ex));
				return;
			}
			Dictionary<string, Dictionary<string, string>> dictionary = currentEntrySheetsField.GetValue(null) as Dictionary<string, Dictionary<string, string>>;
			if (dictionary == null)
			{
				dictionary = new Dictionary<string, Dictionary<string, string>>();
				currentEntrySheetsField.SetValue(null, dictionary);
			}
			int createdSheets = 0;
			int appendedEntries = 0;
			int overriddenEntries = 0;
			if (!text.Equals("EN", StringComparison.OrdinalIgnoreCase))
			{
				MergeLanguageIntoGameSheets("EN", dictionary, ref createdSheets, ref appendedEntries, ref overriddenEntries);
			}
			MergeLanguageIntoGameSheets(text, dictionary, ref createdSheets, ref appendedEntries, ref overriddenEntries);
			logger.LogInfo((object)("RESTORE localization injected for " + text + ": created " + createdSheets + " sheet(s), appended " + appendedEntries + " key(s), overrode " + overriddenEntries + " key(s)."));
		}

		private static void MergeLanguageIntoGameSheets(string languageCode, Dictionary<string, Dictionary<string, string>> gameSheets, ref int createdSheets, ref int appendedEntries, ref int overriddenEntries)
		{
			if (!localization.TryGetValue(languageCode, out var value))
			{
				return;
			}
			foreach (KeyValuePair<string, Dictionary<string, string>> item in value)
			{
				if (!gameSheets.TryGetValue(item.Key, out var value2))
				{
					value2 = new Dictionary<string, string>();
					gameSheets.Add(item.Key, value2);
					createdSheets++;
					logger.LogInfo((object)("RESTORE created localization sheet: " + item.Key + "."));
				}
				foreach (KeyValuePair<string, string> item2 in item.Value)
				{
					if (value2.ContainsKey(item2.Key))
					{
						overriddenEntries++;
					}
					else
					{
						appendedEntries++;
					}
					value2[item2.Key] = item2.Value;
				}
			}
		}

		private static void InstallLanguageSwitchPatch()
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			if (harmony == null || languageType == null)
			{
				logger.LogError((object)"RESTORE localization could not install language patches.");
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(RestoreLocalization), "LanguageDoSwitchPostfix", (Type[])null, (Type[])null);
			MethodInfo[] methods = languageType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			int num = 0;
			foreach (MethodInfo methodInfo2 in methods)
			{
				if (methodInfo2.Name.Equals("DoSwitch", StringComparison.Ordinal))
				{
					try
					{
						harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
						logger.LogInfo((object)("RESTORE patched localization language switch: " + FormatMethod(methodInfo2) + "."));
					}
					catch (Exception ex)
					{
						logger.LogError((object)("RESTORE failed to patch " + FormatMethod(methodInfo2) + ":\n" + ex));
					}
				}
			}
			if (num == 0)
			{
				logger.LogError((object)"RESTORE could not find runtime Language.DoSwitch.");
			}
		}

		private static void LanguageDoSwitchPostfix()
		{
			InjectLocalizationForCurrentLanguage();
			ResetLoadedLocalisedTextCollections();
			BroadcastChangedLanguageAgain();
		}

		private static void BroadcastChangedLanguageAgain()
		{
			if (sendMonoMessageMethod == null || currentLanguageMethod == null)
			{
				return;
			}
			try
			{
				object obj = currentLanguageMethod.Invoke(null, null);
				sendMonoMessageMethod.Invoke(null, new object[2]
				{
					"ChangedLanguage",
					new object[1] { obj }
				});
			}
			catch (Exception ex)
			{
				logger.LogWarning((object)("RESTORE could not rebroadcast ChangedLanguage:\n" + ex));
			}
		}

		private static void ResetLoadedLocalisedTextCollections()
		{
			if (localisedTextCollectionType == null || collectionDataField == null)
			{
				return;
			}
			Object[] array;
			try
			{
				array = Resources.FindObjectsOfTypeAll(localisedTextCollectionType);
			}
			catch (Exception ex)
			{
				logger.LogWarning((object)("RESTORE could not enumerate localization collections:\n" + ex));
				return;
			}
			int num = 0;
			foreach (Object val in array)
			{
				if (val == (Object)null)
				{
					continue;
				}
				object value;
				try
				{
					value = collectionDataField.GetValue(val);
				}
				catch
				{
					continue;
				}
				if (value == null)
				{
					continue;
				}
				try
				{
					if (collectionIsInitialisedField != null)
					{
						collectionIsInitialisedField.SetValue(value, false);
					}
					if (collectionCurrentTextsField != null)
					{
						collectionCurrentTextsField.SetValue(value, null);
					}
					if (collectionCurrentProbabilitiesField != null)
					{
						collectionCurrentProbabilitiesField.SetValue(value, null);
					}
					if (collectionPreviousIndexField != null)
					{
						collectionPreviousIndexField.SetValue(value, -1);
					}
					num++;
				}
				catch (Exception ex2)
				{
					logger.LogWarning((object)("RESTORE could not reset localization collection '" + val.name + "': " + ex2.Message));
				}
			}
			logger.LogInfo((object)("RESTORE reset " + num + " LocalisedTextCollection cache(s)."));
		}

		private static Dictionary<string, string> ParseSimpleYaml(string filePath)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			string[] array = File.ReadAllLines(filePath, Encoding.UTF8);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i];
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				string text2 = text.TrimStart();
				if (text2.StartsWith("#", StringComparison.Ordinal) || text2.StartsWith("---", StringComparison.Ordinal) || text2.StartsWith("...", StringComparison.Ordinal))
				{
					continue;
				}
				int num = FindYamlSeparator(text2);
				if (num <= 0)
				{
					logger.LogWarning((object)("RESTORE localization ignored malformed line " + (i + 1) + " in " + filePath + ": " + text));
					continue;
				}
				string text3 = text2.Substring(0, num).Trim();
				string text4 = text2.Substring(num + 1).Trim();
				if (text3.Length != 0)
				{
					string value;
					switch (text4)
					{
					case "|":
					case "|-":
					case "|+":
					case ">":
					case ">-":
					case ">+":
						value = ReadBlockValue(array, ref i, CountIndent(text), text4[0] == '>');
						break;
					default:
						value = DecodeScalar(text4);
						break;
					}
					dictionary[text3] = value;
				}
			}
			return dictionary;
		}

		private static int FindYamlSeparator(string value)
		{
			bool flag = false;
			bool flag2 = false;
			bool flag3 = false;
			for (int i = 0; i < value.Length; i++)
			{
				char c = value[i];
				if (flag2 && c == '\\' && !flag3)
				{
					flag3 = true;
					continue;
				}
				if (c == '\'' && !flag2)
				{
					flag = !flag;
				}
				else if (c == '"' && !flag && !flag3)
				{
					flag2 = !flag2;
				}
				else if (c == ':' && !flag && !flag2)
				{
					return i;
				}
				flag3 = false;
			}
			return -1;
		}

		private static string ReadBlockValue(string[] lines, ref int lineIndex, int parentIndent, bool foldLines)
		{
			List<string> list = new List<string>();
			int num = -1;
			while (lineIndex + 1 < lines.Length)
			{
				string text = lines[lineIndex + 1];
				if (string.IsNullOrWhiteSpace(text))
				{
					lineIndex++;
					list.Add(string.Empty);
					continue;
				}
				int num2 = CountIndent(text);
				if (num2 <= parentIndent)
				{
					break;
				}
				if (num < 0)
				{
					num = num2;
				}
				lineIndex++;
				string item = ((text.Length >= num) ? text.Substring(num) : string.Empty);
				list.Add(item);
			}
			if (!foldLines)
			{
				return string.Join("\n", list);
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < list.Count; i++)
			{
				string text2 = list[i];
				if (i > 0)
				{
					if (text2.Length == 0 || list[i - 1].Length == 0)
					{
						stringBuilder.Append('\n');
					}
					else
					{
						stringBuilder.Append(' ');
					}
				}
				stringBuilder.Append(text2);
			}
			return stringBuilder.ToString();
		}

		private static string DecodeScalar(string value)
		{
			if (value == null)
			{
				return string.Empty;
			}
			if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"')
			{
				value = value.Substring(1, value.Length - 2);
				return value.Replace("\\\\", "\u0001").Replace("\\n", "\n").Replace("\\r", "\r")
					.Replace("\\t", "\t")
					.Replace("\\\"", "\"")
					.Replace("\u0001", "\\");
			}
			if (value.Length >= 2 && value[0] == '\'' && value[value.Length - 1] == '\'')
			{
				return value.Substring(1, value.Length - 2).Replace("''", "'");
			}
			return value;
		}

		private static int CountIndent(string value)
		{
			int i;
			for (i = 0; i < value.Length && (value[i] == ' ' || value[i] == '\t'); i++)
			{
			}
			return i;
		}

		private static string NormalizeLanguageCode(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "EN";
			}
			string text = value.Trim().Replace('-', '_').ToUpperInvariant();
			return text switch
			{
				"ENGLISH" => "EN", 
				"SPANISH" => "ES", 
				"FRENCH" => "FR", 
				"GERMAN" => "DE", 
				"ITALIAN" => "IT", 
				"PORTUGUESE" => "PT", 
				"RUSSIAN" => "RU", 
				"JAPANESE" => "JA", 
				"KOREAN" => "KO", 
				"CHINESE" => "ZH", 
				_ => text, 
			};
		}

		private static string FormatMethod(MethodInfo method)
		{
			ParameterInfo[] parameters = method.GetParameters();
			string[] array = new string[parameters.Length];
			for (int i = 0; i < parameters.Length; i++)
			{
				array[i] = parameters[i].ParameterType.Name;
			}
			return method.DeclaringType.FullName + "." + method.Name + "(" + string.Join(", ", array) + ")";
		}
	}
	public static class RestoreAudioResolver
	{
		private sealed class ReferenceComparer : IEqualityComparer<object>
		{
			public static readonly ReferenceComparer Instance = new ReferenceComparer();

			public new bool Equals(object left, object right)
			{
				return left == right;
			}

			public int GetHashCode(object value)
			{
				return RuntimeHelpers.GetHashCode(value);
			}
		}

		private static readonly Dictionary<string, List<AudioClip>> gameClips = new Dictionary<string, List<AudioClip>>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<int> bundledClipIds = new HashSet<int>();

		private static readonly HashSet<int> repairedObjectIds = new HashSet<int>();

		private static readonly HashSet<string> loggedResolutions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> loggedMisses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static ManualLogSource logger;

		private static float lastRefreshTime = -1000f;

		public static void Initialize(ManualLogSource log)
		{
			logger = log;
			gameClips.Clear();
			bundledClipIds.Clear();
			repairedObjectIds.Clear();
			loggedResolutions.Clear();
			loggedMisses.Clear();
			lastRefreshTime = -1000f;
			if (logger != null)
			{
				logger.LogInfo((object)"Audio resolver initialized. Waiting for RESTORE bundles to be registered before scanning game audio.");
			}
		}

		public static bool IsBundledClip(AudioClip clip)
		{
			if ((Object)(object)clip != (Object)null)
			{
				return bundledClipIds.Contains(((Object)clip).GetInstanceID());
			}
			return false;
		}

		public static void RegisterBundle(AssetBundle bundle, string bundleLabel)
		{
			if ((Object)(object)bundle == (Object)null)
			{
				return;
			}
			int count = bundledClipIds.Count;
			try
			{
				AudioClip[] array = bundle.LoadAllAssets<AudioClip>();
				foreach (AudioClip val in array)
				{
					if ((Object)(object)val != (Object)null)
					{
						bundledClipIds.Add(((Object)val).GetInstanceID());
					}
				}
			}
			catch (Exception ex)
			{
				if (logger != null)
				{
					logger.LogWarning((object)("Could not enumerate AudioClips directly from bundle '" + bundleLabel + "': " + ex));
				}
			}
			try
			{
				Object[] array2 = bundle.LoadAllAssets<Object>();
				for (int j = 0; j < array2.Length; j++)
				{
					RegisterBundledAsset(array2[j]);
				}
			}
			catch (Exception ex2)
			{
				if (logger != null)
				{
					logger.LogWarning((object)("Could not inspect all assets in bundle '" + bundleLabel + "': " + ex2));
				}
			}
			if (logger != null)
			{
				logger.LogInfo((object)("Registered " + (bundledClipIds.Count - count) + " RESTORE AudioClip object(s) from bundle '" + bundleLabel + "'."));
			}
		}

		public static void RegisterBundledAsset(Object asset)
		{
			if (!(asset == (Object)null))
			{
				HashSet<object> visited = new HashSet<object>(ReferenceComparer.Instance);
				CollectBundledAudioClips(asset, visited, 0);
			}
		}

		public static void RefreshLoadedGameClips()
		{
			gameClips.Clear();
			lastRefreshTime = Time.realtimeSinceStartup;
			AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
			int num = 0;
			int num2 = 0;
			foreach (AudioClip val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				if (bundledClipIds.Contains(((Object)val).GetInstanceID()))
				{
					num2++;
					continue;
				}
				string text = NormalizeName(((Object)val).name);
				if (text.Length != 0)
				{
					if (!gameClips.TryGetValue(text, out var value))
					{
						value = new List<AudioClip>();
						gameClips.Add(text, value);
					}
					value.Add(val);
					num++;
				}
			}
			if (logger != null)
			{
				logger.LogDebug((object)("Indexed " + num + " loaded original-game AudioClip object(s); excluded " + num2 + " registered RESTORE clip object(s)."));
			}
		}

		public static T RepairAsset<T>(T asset) where T : Object
		{
			if ((Object)(object)asset == (Object)null)
			{
				return default(T);
			}
			RegisterBundledAsset((Object)(object)asset);
			int instanceID = ((Object)asset).GetInstanceID();
			if (!repairedObjectIds.Add(instanceID))
			{
				return asset;
			}
			object obj = asset;
			AudioClip val = (AudioClip)((obj is AudioClip) ? obj : null);
			if ((Object)(object)val != (Object)null)
			{
				AudioClip obj2 = ResolveClip(val);
				return (T)(object)((obj2 is T) ? obj2 : null);
			}
			RepairObjectFields(asset, new HashSet<object>(ReferenceComparer.Instance), 0);
			return asset;
		}

		public static void RepairGameObject(GameObject root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return;
			}
			RegisterBundledAsset((Object)(object)root);
			int instanceID = ((Object)root).GetInstanceID();
			if (!repairedObjectIds.Add(instanceID))
			{
				return;
			}
			Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					AudioSource val2 = (AudioSource)(object)((val is AudioSource) ? val : null);
					if ((Object)(object)val2 != (Object)null && (Object)(object)val2.clip != (Object)null)
					{
						val2.clip = ResolveClip(val2.clip);
					}
					RepairObjectFields(val, new HashSet<object>(ReferenceComparer.Instance), 0);
				}
			}
		}

		public static AudioClip ResolveClip(AudioClip fallback)
		{
			if ((Object)(object)fallback == (Object)null)
			{
				return null;
			}
			if (!bundledClipIds.Contains(((Object)fallback).GetInstanceID()))
			{
				return fallback;
			}
			string text = NormalizeName(((Object)fallback).name);
			if (text.Length == 0)
			{
				return fallback;
			}
			AudioClip val = FindBestMatch(text, fallback);
			if ((Object)(object)val == (Object)null && Time.realtimeSinceStartup - lastRefreshTime >= 0.5f)
			{
				RefreshLoadedGameClips();
				val = FindBestMatch(text, fallback);
			}
			if ((Object)(object)val == (Object)null)
			{
				if (loggedMisses.Add(text) && logger != null)
				{
					logger.LogDebug((object)("No loaded original-game match for RESTORE AudioClip '" + ((Object)fallback).name + "'. Using bundled fallback."));
				}
				return fallback;
			}
			string item = ((Object)fallback).GetInstanceID() + "->" + ((Object)val).GetInstanceID();
			if (loggedResolutions.Add(item) && logger != null)
			{
				logger.LogInfo((object)("Using original game AudioClip '" + ((Object)val).name + "' instead of bundled fallback '" + ((Object)fallback).name + "'. " + DescribeClip(val)));
			}
			return val;
		}

		private static AudioClip FindBestMatch(string key, AudioClip fallback)
		{
			if (!gameClips.TryGetValue(key, out var value))
			{
				return null;
			}
			AudioClip result = null;
			int num = int.MinValue;
			for (int i = 0; i < value.Count; i++)
			{
				AudioClip val = value[i];
				if (!((Object)(object)val == (Object)null) && ((Object)val).GetInstanceID() != ((Object)fallback).GetInstanceID() && !bundledClipIds.Contains(((Object)val).GetInstanceID()))
				{
					int num2 = ScoreCandidate(val, fallback);
					if (num2 > num)
					{
						num = num2;
						result = val;
					}
				}
			}
			if (num < 20)
			{
				return null;
			}
			return result;
		}

		private static int ScoreCandidate(AudioClip candidate, AudioClip fallback)
		{
			int num = 0;
			if (candidate.channels == fallback.channels)
			{
				num += 8;
			}
			if (candidate.frequency == fallback.frequency)
			{
				num += 8;
			}
			if (candidate.samples == fallback.samples)
			{
				num += 12;
			}
			else
			{
				int num2 = Math.Abs(candidate.samples - fallback.samples);
				int num3 = Math.Max(64, fallback.frequency / 20);
				if (num2 <= num3)
				{
					num += 6;
				}
			}
			float num4 = Mathf.Abs(candidate.length - fallback.length);
			if (num4 <= 0.01f)
			{
				num += 8;
			}
			else if (num4 <= 0.05f)
			{
				num += 5;
			}
			else if (num4 <= 0.2f)
			{
				num += 2;
			}
			return num;
		}

		private static void CollectBundledAudioClips(object value, HashSet<object> visited, int depth)
		{
			if (value == null || depth > 8 || !visited.Add(value))
			{
				return;
			}
			AudioClip val = (AudioClip)((value is AudioClip) ? value : null);
			if ((Object)(object)val != (Object)null)
			{
				bundledClipIds.Add(((Object)val).GetInstanceID());
				return;
			}
			GameObject val2 = (GameObject)((value is GameObject) ? value : null);
			if ((Object)(object)val2 != (Object)null)
			{
				Component[] componentsInChildren = val2.GetComponentsInChildren<Component>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					if ((Object)(object)componentsInChildren[i] != (Object)null)
					{
						CollectBundledAudioClips(componentsInChildren[i], visited, depth + 1);
					}
				}
				return;
			}
			Type type = value.GetType();
			if (IsTerminalType(type))
			{
				return;
			}
			if (value is IList list)
			{
				for (int j = 0; j < list.Count; j++)
				{
					CollectBundledAudioClips(list[j], visited, depth + 1);
				}
			}
			else
			{
				if (value is Object && !(value is ScriptableObject) && !(value is Component))
				{
					return;
				}
				foreach (FieldInfo relevantField in GetRelevantFields(type))
				{
					try
					{
						CollectBundledAudioClips(relevantField.GetValue(value), visited, depth + 1);
					}
					catch
					{
					}
				}
			}
		}

		private static void RepairObjectFields(object value, HashSet<object> visited, int depth)
		{
			if (value == null || depth > 8 || !visited.Add(value))
			{
				return;
			}
			Type type = value.GetType();
			if (IsTerminalType(type))
			{
				return;
			}
			if (value is IList list)
			{
				RepairList(list, visited, depth);
				return;
			}
			foreach (FieldInfo relevantField in GetRelevantFields(type))
			{
				object value2;
				try
				{
					value2 = relevantField.GetValue(value);
				}
				catch
				{
					continue;
				}
				if (value2 == null)
				{
					continue;
				}
				if (relevantField.FieldType == typeof(AudioClip))
				{
					AudioClip val = (AudioClip)((value2 is AudioClip) ? value2 : null);
					AudioClip val2 = ResolveClip(val);
					if ((Object)(object)val2 != (Object)(object)val && !relevantField.IsInitOnly)
					{
						try
						{
							relevantField.SetValue(value, val2);
						}
						catch
						{
						}
					}
				}
				else if (value2 is AudioClip[] array)
				{
					for (int i = 0; i < array.Length; i++)
					{
						array[i] = ResolveClip(array[i]);
					}
				}
				else if (value2 is IList list2)
				{
					RepairList(list2, visited, depth + 1);
				}
				else if (!(value2 is Object) || value2 is ScriptableObject || value2 is Component)
				{
					RepairObjectFields(value2, visited, depth + 1);
				}
			}
		}

		private static void RepairList(IList list, HashSet<object> visited, int depth)
		{
			for (int i = 0; i < list.Count; i++)
			{
				object obj = list[i];
				if (obj == null)
				{
					continue;
				}
				AudioClip val = (AudioClip)((obj is AudioClip) ? obj : null);
				if ((Object)(object)val != (Object)null)
				{
					AudioClip val2 = ResolveClip(val);
					if ((Object)(object)val2 != (Object)(object)val && !list.IsReadOnly)
					{
						try
						{
							list[i] = val2;
						}
						catch
						{
						}
					}
				}
				else
				{
					RepairObjectFields(obj, visited, depth + 1);
				}
			}
		}

		private static IEnumerable<FieldInfo> GetRelevantFields(Type type)
		{
			Type current = type;
			while (current != null && current != typeof(object) && current != typeof(Object) && current != typeof(ScriptableObject) && current != typeof(Component) && current != typeof(Behaviour) && current != typeof(MonoBehaviour))
			{
				FieldInfo[] fields = current.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (!fieldInfo.IsStatic && !fieldInfo.IsLiteral && !fieldInfo.IsNotSerialized)
					{
						yield return fieldInfo;
					}
				}
				current = current.BaseType;
			}
		}

		private static bool IsTerminalType(Type type)
		{
			if (!type.IsPrimitive && !type.IsEnum && !(type == typeof(string)) && !(type == typeof(decimal)) && !(type == typeof(Type)) && !typeof(Delegate).IsAssignableFrom(type))
			{
				return typeof(MemberInfo).IsAssignableFrom(type);
			}
			return true;
		}

		private static string NormalizeName(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return string.Empty;
			}
			value = value.Replace('\\', '/');
			string text = Path.GetFileNameWithoutExtension(Path.GetFileName(value));
			if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase))
			{
				text = text.Substring(0, text.Length - "(Clone)".Length);
			}
			return text.Trim();
		}

		private static string DescribeClip(AudioClip clip)
		{
			if ((Object)(object)clip == (Object)null)
			{
				return string.Empty;
			}
			return "[" + clip.channels + "ch, " + clip.frequency + "Hz, " + clip.samples + " samples, " + clip.length.ToString("0.000") + "s]";
		}
	}
	public static class RestorePlayerData
	{
		private static ManualLogSource logger;

		public static void Initialize(ManualLogSource log)
		{
			logger = log;
			PlayerData.ClearOptimisers();
			int num = 0;
			FieldInfo[] fields = typeof(PlayerData).GetFields(BindingFlags.Instance | BindingFlags.Public);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!(fieldInfo == null) && IsSupportedType(fieldInfo.FieldType) && fieldInfo.Name.StartsWith("restore", StringComparison.OrdinalIgnoreCase))
				{
					num++;
					logger.LogInfo((object)("RESTORE found real PlayerData field: " + fieldInfo.FieldType.Name + " " + fieldInfo.Name + "."));
				}
			}
			logger.LogInfo((object)("RESTORE real PlayerData initialized with " + num + " injected restore field(s)."));
		}

		public static void Shutdown()
		{
			logger = null;
		}

		public static void RegisterBool(string variableName, bool defaultValue = false)
		{
			RequireField(variableName, typeof(bool), defaultValue);
		}

		public static void RegisterInt(string variableName, int defaultValue = 0)
		{
			RequireField(variableName, typeof(int), defaultValue);
		}

		public static void RegisterFloat(string variableName, float defaultValue = 0f)
		{
			RequireField(variableName, typeof(float), defaultValue);
		}

		public static void RegisterString(string variableName, string defaultValue = "")
		{
			RequireField(variableName, typeof(string), defaultValue ?? string.Empty);
		}

		public static void Register<T>(string variableName, T defaultValue = default(T))
		{
			RequireField(variableName, typeof(T), defaultValue);
		}

		public static bool IsRegistered(string variableName)
		{
			return GetField(variableName) != null;
		}

		public static Type GetRegisteredType(string variableName)
		{
			FieldInfo field = GetField(variableName);
			if (!(field != null))
			{
				return null;
			}
			return field.FieldType;
		}

		public static bool GetBool(string variableName)
		{
			RequireField(variableName, typeof(bool), false);
			return PlayerData.instance.GetBool(variableName);
		}

		public static void SetBool(string variableName, bool value)
		{
			RequireField(variableName, typeof(bool), false);
			PlayerData.instance.SetBool(variableName, value);
		}

		public static int GetInt(string variableName)
		{
			RequireField(variableName, typeof(int), 0);
			return PlayerData.instance.GetInt(variableName);
		}

		public static void SetInt(string variableName, int value)
		{
			RequireField(variableName, typeof(int), 0);
			PlayerData.instance.SetInt(variableName, value);
		}

		public static float GetFloat(string variableName)
		{
			RequireField(variableName, typeof(float), 0f);
			return PlayerData.instance.GetFloat(variableName);
		}

		public static void SetFloat(string variableName, float value)
		{
			RequireField(variableName, typeof(float), 0f);
			PlayerData.instance.SetFloat(variableName, value);
		}

		public static string GetString(string variableName)
		{
			RequireField(variableName, typeof(string), string.Empty);
			return PlayerData.instance.GetString(variableName);
		}

		public static void SetString(string variableName, string value)
		{
			RequireField(variableName, typeof(string), string.Empty);
			PlayerData.instance.SetString(variableName, value ?? string.Empty);
		}

		public static T Get<T>(string variableName)
		{
			Type typeFromHandle = typeof(T);
			if (typeFromHandle == typeof(bool))
			{
				return (T)(object)GetBool(variableName);
			}
			if (typeFromHandle == typeof(int))
			{
				return (T)(object)GetInt(variableName);
			}
			if (typeFromHandle == typeof(float))
			{
				return (T)(object)GetFloat(variableName);
			}
			if (typeFromHandle == typeof(string))
			{
				return (T)(object)GetString(variableName);
			}
			throw new NotSupportedException("RESTORE real PlayerData supports bool, int, float, and string.");
		}

		public static void Set<T>(string variableName, T value)
		{
			Type typeFromHandle = typeof(T);
			if (typeFromHandle == typeof(bool))
			{
				SetBool(variableName, (bool)(object)value);
				return;
			}
			if (typeFromHandle == typeof(int))
			{
				SetInt(variableName, (int)(object)value);
				return;
			}
			if (typeFromHandle == typeof(float))
			{
				SetFloat(variableName, (float)(object)value);
				return;
			}
			if (typeFromHandle == typeof(string))
			{
				SetString(variableName, (string)(object)value);
				return;
			}
			throw new NotSupportedException("RESTORE real PlayerData supports bool, int, float, and string.");
		}

		private static FieldInfo RequireField(string variableName, Type expectedType, object expectedDefault)
		{
			if (string.IsNullOrWhiteSpace(variableName))
			{
				throw new ArgumentException("PlayerData field name cannot be empty.", "variableName");
			}
			FieldInfo field = GetField(variableName);
			if (field == null)
			{
				throw new MissingFieldException("PlayerData", variableName + ". Add it to restore_playerdata_fields.txt, place RESTORE.PlayerDataPatcher.dll in BepInEx/patchers, and restart the game.");
			}
			if (field.FieldType != expectedType)
			{
				throw new InvalidOperationException("PlayerData field '" + variableName + "' exists as " + field.FieldType.FullName + ", not " + expectedType.FullName + ".");
			}
			return field;
		}

		private static FieldInfo GetField(string variableName)
		{
			if (string.IsNullOrWhiteSpace(variableName))
			{
				return null;
			}
			return typeof(PlayerData).GetField(variableName, BindingFlags.Instance | BindingFlags.Public);
		}

		private static bool IsSupportedType(Type type)
		{
			if (!(type == typeof(bool)) && !(type == typeof(int)) && !(type == typeof(float)))
			{
				return type == typeof(string);
			}
			return true;
		}
	}
	internal static class RestoreQuestUiFix
	{
		private static ManualLogSource logger;

		private static Type inventoryPaneStandaloneType;

		private static MethodInfo paneStartMethod;

		private static MethodInfo paneEndMethod;

		private static bool initialized;

		internal static void Initialize(Harmony harmony, ManualLogSource log)
		{
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			logger = log;
			if (initialized)
			{
				return;
			}
			initialized = true;
			inventoryPaneStandaloneType = AccessTools.TypeByName("InventoryPaneStandalone");
			if (inventoryPaneStandaloneType == null)
			{
				logger.LogError((object)"RESTORE quest UI fix could not find InventoryPaneStandalone.");
				return;
			}
			paneStartMethod = AccessTools.Method(inventoryPaneStandaloneType, "PaneStart", Type.EmptyTypes, (Type[])null);
			paneEndMethod = AccessTools.Method(inventoryPaneStandaloneType, "PaneEnd", Type.EmptyTypes, (Type[])null);
			MethodInfo methodInfo = AccessTools.Method(typeof(RestoreQuestUiFix), "PaneAnimationPrefix", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				logger.LogError((object)"RESTORE quest UI fix could not resolve its pane-animation prefix.");
				return;
			}
			int num = 0;
			if (paneStartMethod != null)
			{
				harmony.Patch((MethodBase)paneStartMethod, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				num++;
			}
			else
			{
				logger.LogError((object)"RESTORE quest UI fix could not find InventoryPaneStandalone.PaneStart().");
			}
			if (paneEndMethod != null)
			{
				harmony.Patch((MethodBase)paneEndMethod, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				num++;
			}
			else
			{
				logger.LogError((object)"RESTORE quest UI fix could not find InventoryPaneStandalone.PaneEnd().");
			}
			logger.LogInfo((object)("RESTORE patched " + num + " InventoryPaneStandalone animation method(s) to reactivate inactive pane hierarchies."));
		}

		internal static void Shutdown()
		{
			logger = null;
			inventoryPaneStandaloneType = null;
			paneStartMethod = null;
			paneEndMethod = null;
			initialized = false;
		}

		private static void PaneAnimationPrefix(object __instance, MethodBase __originalMethod)
		{
			if (__instance == null)
			{
				return;
			}
			Component val = (Component)((__instance is Component) ? __instance : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			GameObject gameObject = val.gameObject;
			if ((Object)(object)gameObject == (Object)null || gameObject.activeInHierarchy)
			{
				return;
			}
			string text = ((__originalMethod != null) ? __originalMethod.Name : "pane animation");
			try
			{
				int num = ActivateRequiredHierarchy(val.transform);
				if (gameObject.activeInHierarchy)
				{
					logger.LogWarning((object)("RESTORE activated " + num + " inactive GameObject(s) before " + ((object)val).GetType().FullName + "." + text + "() on '" + ((Object)gameObject).name + "'."));
				}
				else
				{
					logger.LogError((object)("RESTORE tried to reactivate pane '" + ((Object)gameObject).name + "' before " + text + "(), but it is still inactive in hierarchy."));
				}
			}
			catch (Exception ex)
			{
				logger.LogError((object)("RESTORE failed to reactivate pane '" + ((Object)gameObject).name + "' before " + text + "():\n" + ex));
			}
		}

		private static int ActivateRequiredHierarchy(Transform paneTransform)
		{
			if ((Object)(object)paneTransform == (Object)null)
			{
				return 0;
			}
			List<GameObject> list = new List<GameObject>();
			Transform val = paneTransform;
			while ((Object)(object)val != (Object)null)
			{
				GameObject gameObject = ((Component)val).gameObject;
				if ((Object)(object)gameObject != (Object)null && !gameObject.activeSelf)
				{
					list.Add(gameObject);
				}
				val = val.parent;
			}
			int num = 0;
			for (int num2 = list.Count - 1; num2 >= 0; num2--)
			{
				GameObject val2 = list[num2];
				if (!((Object)(object)val2 == (Object)null) && !val2.activeSelf)
				{
					val2.SetActive(true);
					num++;
				}
			}
			return num;
		}
	}
}

RESTORE.PlayerDataPatcher.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using Mono.Cecil.Cil;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("RESTORE.PlayerDataPatcher")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RESTORE.PlayerDataPatcher")]
[assembly: AssemblyTitle("RESTORE.PlayerDataPatcher")]
[assembly: AssemblyVersion("1.0.0.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 RestorePlayerDataPatcher
{
	public static class Patcher
	{
		private enum FieldKind
		{
			Bool,
			Int,
			Float,
			String
		}

		private sealed class FieldSpec
		{
			public FieldKind Type;

			public string Name;

			public object DefaultValue;

			public string Source;
		}

		private sealed class InjectedField
		{
			public FieldSpec Spec;

			public FieldDefinition Field;
		}

		private const string ConfigFileName = "restore_playerdata_fields.txt";

		private static readonly ManualLogSource Log = Logger.CreateLogSource("RESTORE.PlayerDataPatcher");

		private static readonly List<FieldSpec> Specs = new List<FieldSpec>();

		private static bool specsLoaded;

		public static IEnumerable<string> TargetDLLs
		{
			get
			{
				EnsureSpecsLoaded();
				yield return "Assembly-CSharp.dll";
			}
		}

		public static void Patch(AssemblyDefinition assembly)
		{
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Expected O, but got Unknown
			EnsureSpecsLoaded();
			if (assembly == null || assembly.MainModule == null)
			{
				Log.LogError((object)"Assembly-CSharp was null.");
				return;
			}
			TypeDefinition val = FindPlayerData(assembly.MainModule);
			if (val == null)
			{
				Log.LogError((object)"Could not find PlayerData in Assembly-CSharp.");
				return;
			}
			List<InjectedField> list = new List<InjectedField>();
			for (int i = 0; i < Specs.Count; i++)
			{
				FieldSpec fieldSpec = Specs[i];
				FieldDefinition val2 = FindField(val, fieldSpec.Name);
				if (val2 != null)
				{
					TypeReference typeReference = GetTypeReference(assembly.MainModule, fieldSpec.Type);
					if (((MemberReference)((FieldReference)val2).FieldType).FullName != ((MemberReference)typeReference).FullName)
					{
						Log.LogError((object)("PlayerData field '" + fieldSpec.Name + "' already exists as " + ((MemberReference)((FieldReference)val2).FieldType).FullName + ", not " + ((MemberReference)typeReference).FullName + "."));
					}
					else
					{
						Log.LogInfo((object)("PlayerData field already exists: " + ((MemberReference)((FieldReference)val2).FieldType).Name + " " + ((MemberReference)val2).Name + "."));
					}
				}
				else
				{
					TypeReference typeReference2 = GetTypeReference(assembly.MainModule, fieldSpec.Type);
					FieldDefinition val3 = new FieldDefinition(fieldSpec.Name, (FieldAttributes)6, typeReference2);
					val.Fields.Add(val3);
					list.Add(new InjectedField
					{
						Spec = fieldSpec,
						Field = val3
					});
					Log.LogInfo((object)("Injected real PlayerData field: " + ((MemberReference)typeReference2).Name + " " + fieldSpec.Name + "."));
				}
			}
			if (list.Count > 0)
			{
				InjectDefaults(val, list);
			}
			Log.LogInfo((object)("RESTORE PlayerData patch complete. " + list.Count + " field(s) injected."));
		}

		private static void EnsureSpecsLoaded()
		{
			if (!specsLoaded)
			{
				specsLoaded = true;
				Specs.Clear();
				List<string> list = FindConfigFiles();
				for (int i = 0; i < list.Count; i++)
				{
					LoadConfigFile(list[i]);
				}
				if (Specs.Count == 0)
				{
					Log.LogWarning((object)"No restore_playerdata_fields.txt was found. Using the three built-in example fields.");
					AddOrValidate(new FieldSpec
					{
						Type = FieldKind.Bool,
						Name = "restoreStartedAct4",
						DefaultValue = false,
						Source = "<built-in>"
					});
					AddOrValidate(new FieldSpec
					{
						Type = FieldKind.Int,
						Name = "restoreOldHeartCount",
						DefaultValue = 0,
						Source = "<built-in>"
					});
					AddOrValidate(new FieldSpec
					{
						Type = FieldKind.String,
						Name = "restoreCurrentObjective",
						DefaultValue = string.Empty,
						Source = "<built-in>"
					});
				}
				Log.LogInfo((object)("Loaded " + Specs.Count + " RESTORE PlayerData field definition(s)."));
			}
		}

		private static List<string> FindConfigFiles()
		{
			List<string> list = new List<string>();
			AddConfigFilesFromDirectory(Paths.PluginPath, list);
			AddConfigFilesFromDirectory(Paths.PatcherPluginPath, list);
			return list;
		}

		private static void AddConfigFilesFromDirectory(string directory, List<string> results)
		{
			if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
			{
				return;
			}
			string[] files;
			try
			{
				files = Directory.GetFiles(directory, "restore_playerdata_fields.txt", SearchOption.AllDirectories);
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Could not search for restore_playerdata_fields.txt under " + directory + ":\n" + ex));
				return;
			}
			for (int i = 0; i < files.Length; i++)
			{
				string fullPath = Path.GetFullPath(files[i]);
				bool flag = false;
				for (int j = 0; j < results.Count; j++)
				{
					if (string.Equals(results[j], fullPath, StringComparison.OrdinalIgnoreCase))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					results.Add(fullPath);
				}
			}
		}

		private static void LoadConfigFile(string path)
		{
			Log.LogInfo((object)("Loading PlayerData fields from: " + path));
			string[] array;
			try
			{
				array = File.ReadAllLines(path);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Could not read " + path + ":\n" + ex));
				return;
			}
			for (int i = 0; i < array.Length; i++)
			{
				string text = ((array[i] != null) ? array[i].Trim() : string.Empty);
				if (text.Length != 0 && !text.StartsWith("#") && !text.StartsWith("//"))
				{
					FieldSpec spec;
					try
					{
						spec = ParseLine(text, path, i + 1);
					}
					catch (Exception ex2)
					{
						Log.LogError((object)(path + ":" + (i + 1) + ": " + ex2.Message));
						continue;
					}
					AddOrValidate(spec);
				}
			}
		}

		private static FieldSpec ParseLine(string line, string path, int lineNumber)
		{
			SplitDefinitionLine(line, out var typeText, out var name, out var defaultText);
			FieldKind fieldKind = ParseKind(typeText);
			if (!IsValidIdentifier(name))
			{
				throw new FormatException("'" + name + "' is not a valid C# field name.");
			}
			object defaultValue = ParseDefault(fieldKind, defaultText);
			return new FieldSpec
			{
				Type = fieldKind,
				Name = name,
				DefaultValue = defaultValue,
				Source = path + ":" + lineNumber
			};
		}

		private static void SplitDefinitionLine(string line, out string typeText, out string name, out string defaultText)
		{
			int num = SkipWhitespace(line, 0);
			int num2 = FindWhitespace(line, num);
			if (num >= line.Length || num2 <= num)
			{
				throw new FormatException("Expected: <type> <name> [default]");
			}
			typeText = line.Substring(num, num2 - num);
			int num3 = SkipWhitespace(line, num2);
			int num4 = FindWhitespace(line, num3);
			if (num3 >= line.Length)
			{
				throw new FormatException("Expected a field name after the type.");
			}
			if (num4 <= num3)
			{
				num4 = line.Length;
			}
			name = line.Substring(num3, num4 - num3);
			int num5 = SkipWhitespace(line, num4);
			defaultText = ((num5 < line.Length) ? line.Substring(num5) : string.Empty);
		}

		private static int SkipWhitespace(string text, int index)
		{
			while (index < text.Length && char.IsWhiteSpace(text[index]))
			{
				index++;
			}
			return index;
		}

		private static int FindWhitespace(string text, int index)
		{
			while (index < text.Length && !char.IsWhiteSpace(text[index]))
			{
				index++;
			}
			return index;
		}

		private static FieldKind ParseKind(string value)
		{
			if (string.Equals(value, "bool", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "boolean", StringComparison.OrdinalIgnoreCase))
			{
				return FieldKind.Bool;
			}
			if (string.Equals(value, "int", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "int32", StringComparison.OrdinalIgnoreCase))
			{
				return FieldKind.Int;
			}
			if (string.Equals(value, "float", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "single", StringComparison.OrdinalIgnoreCase))
			{
				return FieldKind.Float;
			}
			if (string.Equals(value, "string", StringComparison.OrdinalIgnoreCase))
			{
				return FieldKind.String;
			}
			throw new FormatException("Unsupported type '" + value + "'. Use bool, int, float, or string.");
		}

		private static object ParseDefault(FieldKind kind, string value)
		{
			switch (kind)
			{
			case FieldKind.Bool:
			{
				if (string.IsNullOrWhiteSpace(value))
				{
					return false;
				}
				if (bool.TryParse(value.Trim(), out var result2))
				{
					return result2;
				}
				if (value.Trim() == "1")
				{
					return true;
				}
				if (value.Trim() == "0")
				{
					return false;
				}
				throw new FormatException("Invalid bool default '" + value + "'.");
			}
			case FieldKind.Int:
			{
				if (string.IsNullOrWhiteSpace(value))
				{
					return 0;
				}
				if (!int.TryParse(value.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
				{
					throw new FormatException("Invalid int default '" + value + "'.");
				}
				return result;
			}
			case FieldKind.Float:
			{
				if (string.IsNullOrWhiteSpace(value))
				{
					return 0f;
				}
				if (!float.TryParse(value.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
				{
					throw new FormatException("Invalid float default '" + value + "'.");
				}
				return result3;
			}
			default:
			{
				if (string.IsNullOrEmpty(value))
				{
					return string.Empty;
				}
				string text = value.Trim();
				if (text.Length >= 2 && text[0] == '"' && text[text.Length - 1] == '"')
				{
					text = text.Substring(1, text.Length - 2);
				}
				return UnescapeString(text);
			}
			}
		}

		private static string UnescapeString(string value)
		{
			return value.Replace("\\\\", "\u0001").Replace("\\n", "\n").Replace("\\r", "\r")
				.Replace("\\t", "\t")
				.Replace("\\\"", "\"")
				.Replace("\u0001", "\\");
		}

		private static bool IsValidIdentifier(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return false;
			}
			char c = value[0];
			if (!char.IsLetter(c) && c != '_')
			{
				return false;
			}
			for (int i = 1; i < value.Length; i++)
			{
				char c2 = value[i];
				if (!char.IsLetterOrDigit(c2) && c2 != '_')
				{
					return false;
				}
			}
			return true;
		}

		private static void AddOrValidate(FieldSpec spec)
		{
			for (int i = 0; i < Specs.Count; i++)
			{
				FieldSpec fieldSpec = Specs[i];
				if (string.Equals(fieldSpec.Name, spec.Name, StringComparison.Ordinal))
				{
					if (fieldSpec.Type != spec.Type)
					{
						Log.LogError((object)("Conflicting PlayerData definitions for '" + spec.Name + "': " + fieldSpec.Type.ToString() + " from " + fieldSpec.Source + " and " + spec.Type.ToString() + " from " + spec.Source + "."));
					}
					return;
				}
			}
			Specs.Add(spec);
		}

		private static TypeDefinition FindPlayerData(ModuleDefinition module)
		{
			for (int i = 0; i < module.Types.Count; i++)
			{
				TypeDefinition val = module.Types[i];
				if (((MemberReference)val).Name == "PlayerData" && string.IsNullOrEmpty(((TypeReference)val).Namespace))
				{
					return val;
				}
			}
			return null;
		}

		private static FieldDefinition FindField(TypeDefinition type, string name)
		{
			for (int i = 0; i < type.Fields.Count; i++)
			{
				FieldDefinition val = type.Fields[i];
				if (((MemberReference)val).Name == name)
				{
					return val;
				}
			}
			return null;
		}

		private static TypeReference GetTypeReference(ModuleDefinition module, FieldKind kind)
		{
			return (TypeReference)(kind switch
			{
				FieldKind.Bool => module.TypeSystem.Boolean, 
				FieldKind.Int => module.TypeSystem.Int32, 
				FieldKind.Float => module.TypeSystem.Single, 
				_ => module.TypeSystem.String, 
			});
		}

		private static void InjectDefaults(TypeDefinition playerData, List<InjectedField> fields)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			MethodDefinition val = FindSetupNewPlayerData(playerData);
			if (val == null || !val.HasBody)
			{
				Log.LogError((object)"Could not find PlayerData.SetupNewPlayerData(bool); fields were injected but custom defaults were not.");
				return;
			}
			ILProcessor iLProcessor = val.Body.GetILProcessor();
			Instruction val2 = ((val.Body.Instructions.Count > 0) ? val.Body.Instructions[0] : null);
			if (val2 == null)
			{
				val2 = Instruction.Create(OpCodes.Ret);
				iLProcessor.Append(val2);
			}
			for (int i = 0; i < fields.Count; i++)
			{
				InjectedField injectedField = fields[i];
				iLProcessor.InsertBefore(val2, Instruction.Create(OpCodes.Ldarg_0));
				InsertLoadDefault(iLProcessor, val2, injectedField.Spec);
				iLProcessor.InsertBefore(val2, Instruction.Create(OpCodes.Stfld, (FieldReference)(object)injectedField.Field));
			}
			Log.LogInfo((object)"Injected default initialization into PlayerData.SetupNewPlayerData(bool).");
		}

		private static MethodDefinition FindSetupNewPlayerData(TypeDefinition playerData)
		{
			for (int i = 0; i < playerData.Methods.Count; i++)
			{
				MethodDefinition val = playerData.Methods[i];
				if (!(((MemberReference)val).Name != "SetupNewPlayerData") && ((MethodReference)val).Parameters.Count == 1 && !(((MemberReference)((ParameterReference)((MethodReference)val).Parameters[0]).ParameterType).FullName != "System.Boolean"))
				{
					return val;
				}
			}
			return null;
		}

		private static void InsertLoadDefault(ILProcessor il, Instruction before, FieldSpec spec)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			if (spec.Type == FieldKind.Bool)
			{
				bool flag = (bool)spec.DefaultValue;
				il.InsertBefore(before, Instruction.Create(flag ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
			}
			else if (spec.Type == FieldKind.Int)
			{
				il.InsertBefore(before, Instruction.Create(OpCodes.Ldc_I4, (int)spec.DefaultValue));
			}
			else if (spec.Type == FieldKind.Float)
			{
				il.InsertBefore(before, Instruction.Create(OpCodes.Ldc_R4, (float)spec.DefaultValue));
			}
			else if (!(spec.DefaultValue is string text))
			{
				il.InsertBefore(before, Instruction.Create(OpCodes.Ldnull));
			}
			else
			{
				il.InsertBefore(before, Instruction.Create(OpCodes.Ldstr, text));
			}
		}
	}
}