using System.Collections; using System.Collections.Generic; using System.Reflection.Emit; using ExtensibleSaveFormat; using HarmonyLib; using Studio; namespace KKAPI.Studio.SaveLoad { public static partial class StudioSaveLoadApi { private static class Hooks { /// /// A lookup for original dicKey IDs. It is generated on scene import, and only useful then. /// Key is the new ID in the scene (needs to be used currently), while Value is the old ID /// in the save file (same as at the time scene was saved). /// public static readonly Dictionary ImportDictionary = new Dictionary(); private static bool _loadOrImportSuccess; private static int _newIndex; public static void SetupHooks() { BepInEx.Harmony.HarmonyWrapper.PatchAll(typeof(Hooks)); ExtendedSave.SceneBeingImported += path => _loadOrImportSuccess = true; ExtendedSave.SceneBeingLoaded += path => _loadOrImportSuccess = true; } [HarmonyPostfix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.GetNewIndex))] public static void GetNewIndex(int __result) { _newIndex = __result; } /// /// The original code reads the dicKey of an object on import and does nothing with it. Capture that variable and use it to /// construct an import dictionary. /// GetNewIndex is called before this and will be used to get the dicKey used in the scene. /// [HarmonyTranspiler] [HarmonyPatch(typeof(ObjectInfo), nameof(ObjectInfo.Load))] public static IEnumerable ObjectInfoLoadTranspiler(IEnumerable instructions) { foreach (var x in instructions) { if (x.opcode == OpCodes.Pop) { x.opcode = OpCodes.Call; x.operand = typeof(Hooks).GetMethod(nameof(SetImportDictionary), AccessTools.all); } yield return x; } } private static void SetImportDictionary(int originalDicKey) { ImportDictionary[_newIndex] = originalDicKey; } [HarmonyPrefix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.InitScene))] public static void InitScenePrefix() { ImportDictionary.Clear(); } [HarmonyPostfix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.InitScene))] public static void InitScenePostfix() { if (!LoadInProgress && !ImportInProgress) SceneLoadComplete(SceneOperationKind.Clear); } [HarmonyPostfix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.ImportScene))] public static void ImportScenePostfix() { SceneLoadComplete(SceneOperationKind.Import); ImportDictionary.Clear(); } [HarmonyPrefix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.ImportScene))] public static void ImportScenePrefix() { ImportDictionary.Clear(); ImportInProgress = true; } [HarmonyPostfix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.LoadSceneCoroutine))] public static void LoadSceneCoroutinePostfix(ref IEnumerator __result) { // Setup a coroutine postfix var original = __result; __result = new[] { original, LoadCoPostfixCo() }.GetEnumerator(); } private static IEnumerator LoadCoPostfixCo() { SceneLoadComplete(SceneOperationKind.Load); yield break; } [HarmonyPrefix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.LoadSceneCoroutine))] public static void LoadSceneCoroutinePrefix() { ImportDictionary.Clear(); LoadInProgress = true; } [HarmonyPostfix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.LoadScene))] public static void LoadScenePostfix() { SceneLoadComplete(SceneOperationKind.Load); } [HarmonyPrefix] [HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.LoadScene))] public static void LoadScenePrefix() { ImportDictionary.Clear(); LoadInProgress = true; } [HarmonyPostfix, HarmonyPatch(typeof(global::Studio.Studio), nameof(global::Studio.Studio.Duplicate))] public static void DuplicatePostfix() { OnObjectsBeingCopied(); ImportDictionary.Clear(); } private static void SceneLoadComplete(SceneOperationKind operation) { LoadInProgress = false; ImportInProgress = false; if (_loadOrImportSuccess || operation == SceneOperationKind.Clear) { _loadOrImportSuccess = false; OnSceneBeingLoaded(operation); } } } } }