Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Clover and Gloom Menus v1.0.0
RESTORE.dll
Decompiled a day agousing 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 agousing 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)); } } } }