static IEnumerable <BombComponent> GetUntestedComponents(List <KtaneModule> modules) { int progress = 0; // Get local mods that need to be tested if (!(typeof(ModManager).GetField("loadedMods", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(ModManager.Instance) is Dictionary <string, Mod> loadedMods)) { yield break; } var validLocalMods = loadedMods.Values .SelectMany(mod => mod.GetModObjects <BombComponent>()) .Where(bombComponent => { string moduleID = bombComponent.GetComponent <KMBombModule>()?.ModuleType ?? bombComponent.GetComponent <KMNeedyModule>()?.ModuleType; return(modules.Any(module => module.ModuleID == moduleID && module.TwitchPlays == null) || !modules.Any(module => module.ModuleID == moduleID)); }) .ToArray(); // Get mods that need to be loaded into the game to be tested var disabledParent = new GameObject(); gameObjects.Add(disabledParent); disabledParent.SetActive(false); var modWorkshopPath = SteamDirectory == null ? null : Path.GetFullPath(new[] { SteamDirectory, "steamapps", "workshop", "content", "341800" }.Aggregate(Path.Combine)); var validModules = modules.Where(module => { if (module.TwitchPlays != null || module.SteamID == null || module.Type == "Widget" || modWorkshopPath == null) { return(false); } var modPath = Path.Combine(modWorkshopPath, module.SteamID); return(Directory.Exists(modPath)); }).ToArray(); // Test local mods int total = validLocalMods.Length + validModules.Length; foreach (var component in validLocalMods) { alertText.text = $"Testing compatibility of:\n\"{component.GetModuleDisplayName()}\""; alertProgressBar.localScale = new Vector3((float)progress / total, 1, 1); yield return(component); progress++; } // Test loaded mods if (!ModdedAPI.TryGetAs("LoadMod", out Func <string, Mod> loadMod)) { loadMod = new Func <string, Mod>((path) => Mod.LoadMod(path, Assets.Scripts.Mods.ModInfo.ModSourceEnum.Local)); } foreach (var module in validModules) { DebugHelper.Log($"Loading module \"{module.Name}\" to test compatibility..."); alertText.text = $"Testing compatibility of:\n\"{module.Name}\""; alertProgressBar.localScale = new Vector3((float)progress / total, 1, 1); var modPath = Path.Combine(modWorkshopPath, module.SteamID); Mod mod = loadMod(modPath); Object[] loadedObjects = new Object[] { }; foreach (string fileName in mod.GetAssetBundlePaths()) { AssetBundle mainBundle = AssetBundle.LoadFromFile(fileName); if (mainBundle != null) { try { mod.LoadBundle(mainBundle); } catch (Exception ex) { DebugHelper.LogException(ex, $"Load of mod \"{mod.ModID}\" failed:"); } loadedObjects = mainBundle.LoadAllAssets <Object>(); mainBundle.Unload(false); } } mod.CallMethod("RemoveMissions"); mod.CallMethod("RemoveSoundOverrides"); if (mod != null) { string ModuleID = module.ModuleID; GameObject realModule = null; foreach (KMBombModule kmbombModule in mod.GetModObjects <KMBombModule>()) { string moduleType = kmbombModule.ModuleType; if (moduleType == ModuleID) { realModule = Object.Instantiate(kmbombModule.gameObject, disabledParent.transform); realModule.GetComponent <ModBombComponent>().OnLoadFromBundle(); } } foreach (KMNeedyModule kmneedyModule in mod.GetModObjects <KMNeedyModule>()) { string moduleType2 = kmneedyModule.ModuleType; if (moduleType2 == ModuleID) { realModule = Object.Instantiate(kmneedyModule.gameObject, disabledParent.transform); realModule.GetComponent <ModNeedyComponent>().OnLoadFromBundle(); } } if (realModule != null) { yield return(realModule.GetComponent <BombComponent>()); } mod.RemoveServiceObjects(); mod.Unload(); foreach (var loadedObject in loadedObjects) { // GameObjects can't be unloaded, only destroyed. if (loadedObject as GameObject) { Object.Destroy(loadedObject); continue; } Resources.UnloadAsset(loadedObject); } } progress++; } Object.Destroy(disabledParent); }