diff --git a/LethalLevelLoader/LethalLevelLoader.csproj b/LethalLevelLoader/LethalLevelLoader.csproj index b05116b..044617d 100644 --- a/LethalLevelLoader/LethalLevelLoader.csproj +++ b/LethalLevelLoader/LethalLevelLoader.csproj @@ -64,6 +64,7 @@ + diff --git a/LethalLevelLoader/Plugin.cs b/LethalLevelLoader/Plugin.cs index c8e7274..ae3ddbb 100644 --- a/LethalLevelLoader/Plugin.cs +++ b/LethalLevelLoader/Plugin.cs @@ -48,6 +48,8 @@ private void Awake() Logger.LogInfo($"LethalLevelLoader loaded!!"); + ConfigLoader.BindGeneralConfigs(); + //We do this here to try and assure this doesn't accidently catch anything from any AssetBundles LevelLoader.vanillaWaterShader = Shader.Find("Shader Graphs/WaterShaderHDRP"); if (LevelLoader.vanillaWaterShader == null) @@ -74,8 +76,6 @@ private void Awake() else assetBundleLoaderObject.hideFlags = HideFlags.HideAndDontSave; AssetBundleLoader.onBundlesFinishedLoading += AssetBundleLoader.LoadContentInBundles; - - ConfigLoader.BindGeneralConfigs(); } internal static void OnBeforeSetupInvoke() diff --git a/LethalLevelLoader/Tools/AssetBundleLoader.cs b/LethalLevelLoader/Tools/AssetBundleLoader.cs index 9bfd61a..f5553a4 100644 --- a/LethalLevelLoader/Tools/AssetBundleLoader.cs +++ b/LethalLevelLoader/Tools/AssetBundleLoader.cs @@ -1,4 +1,4 @@ -using DunGen; +using DunGen; using DunGen.Graph; using HarmonyLib; using System; @@ -12,6 +12,7 @@ using System.Runtime.CompilerServices; using TMPro; using Unity.Netcode; +using UnityEditor; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.SceneManagement; @@ -141,6 +142,51 @@ internal void LoadBundles() CurrentLoadingStatus = LoadingStatus.Complete; onBundlesFinishedLoading?.Invoke(); } + + try + { + LoadAssetsInEditor(); + } + catch (FileNotFoundException) { } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LoadAssetsInEditor() + { + if (!Application.isEditor) + return; + + var modAssets = AssetDatabase.FindAssets("t:ExtendedMod"); + var extendedMods = new HashSet(obtainedExtendedModsDictionary.Values); + + foreach (var modAsset in modAssets) + { + var modPath = AssetDatabase.GUIDToAssetPath(modAsset); + var mod = AssetDatabase.LoadAssetAtPath(modPath); + if (extendedMods.Contains(mod)) + { + DebugHelper.Log($"Skipping already-registered extended mod in editor: {modPath}", DebugType.Developer); + continue; + } + DebugHelper.Log($"Loading extended mod in editor: {modPath}", DebugType.Developer); + RegisterExtendedMod(mod); + } + + var contentAssets = AssetDatabase.FindAssets("t:ExtendedContent"); + var extendedContents = new HashSet(obtainedExtendedModsDictionary.Values.SelectMany(m => m.ExtendedContents)); + + foreach (var contentAsset in contentAssets) + { + var contentPath = AssetDatabase.GUIDToAssetPath(contentAsset); + var content = AssetDatabase.LoadAssetAtPath(contentPath); + if (extendedContents.Contains(content)) + { + DebugHelper.Log($"Skipping already-registered extended content in editor: {contentPath}", DebugType.Developer); + continue; + } + DebugHelper.Log($"Loading extended content in editor: {contentPath}", DebugType.Developer); + RegisterNewExtendedContent(content, "EditorContent"); + } } internal static void OnBundlesFinishedLoadingInvoke() @@ -215,6 +261,11 @@ internal static void RegisterExtendedMod(ExtendedMod extendedMod) ExtendedMod matchingExtendedMod = null; foreach (ExtendedMod registeredExtendedMod in obtainedExtendedModsDictionary.Values) { + if (extendedMod == registeredExtendedMod) + { + DebugHelper.Log($"Skipping registration of duplicate extended mod {extendedMod.ModName}!", DebugType.Developer); + return; + } if (extendedMod.ModMergeSetting == ModMergeSetting.MatchingModName && registeredExtendedMod.ModMergeSetting == ModMergeSetting.MatchingModName) { if (registeredExtendedMod.ModName == extendedMod.ModName) @@ -251,11 +302,12 @@ internal static void RegisterExtendedMod(ExtendedMod extendedMod) } else { - obtainedExtendedModsDictionary.Add(extendedMod.AuthorName, extendedMod); + obtainedExtendedModsDictionary.Add(extendedMod.ModName, extendedMod); List serializedExtendedContents = new List(extendedMod.ExtendedContents); extendedMod.UnregisterAllExtendedContent(); foreach (ExtendedContent extendedContent in serializedExtendedContents) { + DebugHelper.Log($"Registering {extendedContent} to {extendedMod.ModName}", DebugType.Developer); try { extendedMod.RegisterExtendedContent(extendedContent); @@ -308,10 +360,10 @@ internal static void OnBundlesFinishedLoading() //foreach (KeyValuePair loadedAssetBundles in assetBundleLoadTimes) //DebugHelper.Log(loadedAssetBundles.Key + " Loaded In " + loadedAssetBundles.Value, DebugType.User); - foreach (KeyValuePair obtainedExtendedMod in obtainedExtendedModsDictionary) + foreach (ExtendedMod obtainedExtendedMod in obtainedExtendedModsDictionary.Values) { - PatchedContent.ExtendedMods.Add(obtainedExtendedMod.Value); - DebugHelper.DebugExtendedMod(obtainedExtendedMod.Value); + PatchedContent.ExtendedMods.Add(obtainedExtendedMod); + DebugHelper.DebugExtendedMod(obtainedExtendedMod); } PatchedContent.ExtendedMods = new List(PatchedContent.ExtendedMods.OrderBy(o => o.ModName).ToList()); @@ -402,7 +454,7 @@ internal static ExtendedMod GetOrCreateExtendedMod(string contentSourceName) //This function should probably just be in NetworkRegisterContent internal static void LoadContentInBundles() { - bool foundExtendedLevelScene; + DebugHelper.Log($"Finding scenes to patch into NetworkSceneManager.", DebugType.Developer); List obtainedExtendedModsList = obtainedExtendedModsDictionary.Values.OrderBy(o => o.ModName).ToList(); List sceneNames = new List(); @@ -416,32 +468,46 @@ internal static void LoadContentInBundles() sceneNames.Add(sceneName.Name); } + IEnumerable allSceneBundles = assetBundles.Values.Where(b => b.isStreamedSceneAssetBundle); + IEnumerable allEditorScenes = GetAllScenePathsInEditor(); + + DebugHelper.Log($"All bundle scenes:\n{allSceneBundles.SelectMany(b => b.GetAllScenePaths()).Join(p => p, "\n")}", DebugType.Developer); + DebugHelper.Log($"All editor scenes:\n{allEditorScenes.Join(p => p, "\n")}", DebugType.Developer); + + var sceneNamesAndPaths = new Dictionary(); + + foreach (var assetBundle in allSceneBundles) + { + foreach (var scenePath in assetBundle.GetAllScenePaths()) + { + var sceneName = GetSceneName(scenePath); + if (!sceneNamesAndPaths.TryAdd(sceneName, scenePath)) + DebugHelper.LogWarning($"Duplicate scene name {sceneName} found in asset bundle {assetBundle.name}", DebugType.Developer); + } + } + + foreach (var scenePath in allEditorScenes) + { + var sceneName = GetSceneName(scenePath); + if (!sceneNamesAndPaths.TryAdd(sceneName, scenePath)) + DebugHelper.LogWarning($"Duplicate scene name {sceneName} found in editor scenes", DebugType.Developer); + } + foreach (ExtendedMod extendedMod in obtainedExtendedModsList) { foreach (ExtendedLevel extendedLevel in new List(extendedMod.ExtendedLevels)) { - foundExtendedLevelScene = false; - string debugString = "Could Not Find Scene File For ExtendedLevel: " + extendedLevel.SelectableLevel.name + ", Unregistering Early. \nSelectable Scene Name Is: " + extendedLevel.SelectableLevel.sceneName + ". Scenes Found In Bundles Are: " + "\n"; - foreach (KeyValuePair assetBundle in assetBundles) - if (assetBundle.Value != null && assetBundle.Value.isStreamedSceneAssetBundle) - foreach (string scenePath in assetBundle.Value.GetAllScenePaths()) - { - debugString += ", " + GetSceneName(scenePath); - if (sceneNames.Contains(GetSceneName(scenePath))) - { - //DebugHelper.Log("Found Scene File For ExtendedLevel: " + extendedLevel.selectableLevel.name + ". Scene Path Is: " + scenePath); - foundExtendedLevelScene = true; - NetworkScenePatcher.AddScenePath(GetSceneName(scenePath)); - if (!PatchedContent.AllLevelSceneNames.Contains(GetSceneName(scenePath))) - PatchedContent.AllLevelSceneNames.Add(GetSceneName(scenePath)); - } - } - - if (foundExtendedLevelScene == false) + if (!sceneNamesAndPaths.TryGetValue(extendedLevel.SelectableLevel.sceneName, out string path)) { - DebugHelper.LogError(debugString, DebugType.User); + DebugHelper.LogError($"Could not find scene {extendedLevel.SelectableLevel.sceneName} for level {extendedLevel.SelectableLevel.name}. Scene names are: {sceneNamesAndPaths.Keys.Join(s => s, ", ")}", DebugType.User); extendedMod.UnregisterExtendedContent(extendedLevel); + continue; } + + DebugHelper.LogError($"Found scene with name {extendedLevel.SelectableLevel.sceneName} and path {path}", DebugType.Developer); + NetworkScenePatcher.AddScenePath(extendedLevel.SelectableLevel.sceneName, path); + if (!PatchedContent.AllLevelSceneNames.Contains(extendedLevel.SelectableLevel.sceneName)) + PatchedContent.AllLevelSceneNames.Add(extendedLevel.SelectableLevel.sceneName); } } @@ -449,6 +515,29 @@ internal static void LoadContentInBundles() DebugHelper.Log("Loaded SceneName: " + loadedSceneName, DebugType.Developer); } + private static string[] GetAllScenePathsInEditor() + { + try + { + return GetAllScenePathsInEditorImpl(); + } + catch (FileNotFoundException) + { + return []; + } + } + + private static string[] GetAllScenePathsInEditorImpl() + { + var sceneGUIDs = AssetDatabase.FindAssets("t:Scene"); + var scenePaths = new string[sceneGUIDs.Length]; + + for (var i = 0; i < scenePaths.Length; i++) + scenePaths[i] = AssetDatabase.GUIDToAssetPath(sceneGUIDs[i]); + + return scenePaths; + } + internal static void InitializeBundles() { foreach (ExtendedMod extendedMod in PatchedContent.ExtendedMods) diff --git a/LethalLevelLoader/Tools/NetworkScenePatcher.cs b/LethalLevelLoader/Tools/NetworkScenePatcher.cs index 10d1c01..34ce024 100644 --- a/LethalLevelLoader/Tools/NetworkScenePatcher.cs +++ b/LethalLevelLoader/Tools/NetworkScenePatcher.cs @@ -3,11 +3,13 @@ using MonoMod.Cil; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Unity.Netcode; +using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using static HookHelper; @@ -15,21 +17,23 @@ public static class NetworkScenePatcher { // start of script - static List scenePaths = new(); + static List sceneNames = new(); + static Dictionary scenePaths = new(); static Dictionary scenePathToBuildIndex = new(); static Dictionary buildIndexToScenePath = new(); static Dictionary sceneHashToScenePath = new(); - public static void AddScenePath(string scenePath) + public static void AddScenePath(string sceneName, string scenePath) { - if (scenePaths.Contains(scenePath)) + if (sceneNames.Contains(sceneName)) { //Debug.LogError($"Can not add scene path {scenePath} to the network scene patcher! (already exists in scene paths list)"); return; } - DebugHelper.Log("Adding ScenePath: " + scenePath, DebugType.User); - scenePaths.Add(scenePath); + DebugHelper.Log("Adding ScenePath: " + sceneName, DebugType.User); + sceneNames.Add(sceneName); + scenePaths.TryAdd(sceneName, scenePath); } @@ -48,6 +52,8 @@ internal static void Patch() hooks.ILHook("SceneHashFromNameOrPath", ReplaceBuildIndexByScenePath); hooks.ILHook("ValidateSceneEvent", ReplaceBuildIndexByScenePath); hooks.ILHook("ScenePathFromHash", ReplaceScenePathByBuildIndex); + + hooks.ILHook("LoadSceneAsync", ReplaceLoadSceneAsync); } internal static void Unpatch() { @@ -95,6 +101,47 @@ static int GetBuildIndexByScenePath(string scenePath) } return val; } + + static void ReplaceLoadSceneAsync(ILContext il) + { + ILCursor script = new(il); + MethodInfo replacement = methodof(LoadSceneAsync); + + while (script.TryGotoNext(instr => instr.MatchCall(typeof(SceneManager), "LoadSceneAsync"))) + { + script.Remove(); + script.Emit(OpCodes.Call, replacement); + } + } + + static AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode mode) + { + DebugHelper.Log($"Loading scene {sceneName}.", DebugType.Developer); + var result = SceneManager.LoadSceneAsync(sceneName, mode); + + if (result == null) + { + try + { + return LoadSceneInEditorAsync(sceneName, mode); + } + catch (FileNotFoundException) { } + } + + return result; + } + + static AsyncOperation LoadSceneInEditorAsync(string sceneName, LoadSceneMode mode) + { + DebugHelper.Log($"Trying to load scene {sceneName} through EditorSceneManager.", DebugType.Developer); + if (!scenePaths.TryGetValue(sceneName, out var path)) + { + DebugHelper.Log($"Failed to find scene path for scene {sceneName} to load in editor.", DebugType.Developer); + return null; + } + return EditorSceneManager.LoadSceneAsyncInPlayMode(path, new LoadSceneParameters(mode)); + } + static void GenerateScenesInBuild_Hook(Action orig, NetworkSceneManager self) { scenePathToBuildIndex.Clear(); @@ -104,10 +151,10 @@ static void GenerateScenesInBuild_Hook(Action orig, Network orig(self); int count = SceneManager.sceneCountInBuildSettings; - for (int i = 0; i < scenePaths.Count; i++) + for (int i = 0; i < sceneNames.Count; i++) { int buildIndex = count + i; - string scenePath = scenePaths[i]; + string scenePath = sceneNames[i]; uint hash = scenePath.Hash32(); self.HashToBuildIndex.Add(hash, buildIndex);