private void InitializeFolderComboBox() { FolderComboBox.SelectedIndex = 0; List <string> sModFolders = new List <string> { "All Folders" }; var allModConfigs = GameConfig.ModConfigs.ToList(); Mods = ModDatabase.Get(SelectedGame) .OrderBy(x => GameConfig.GetModPriority(x.Id)) .Select(x => new ModViewModel(x, SelectedGame)) .ToList(); foreach (ModViewModel modViewModel in Mods) { Mod mod = (Mod)modViewModel; string modFolder = Path.GetFileName(Path.GetDirectoryName(mod.BaseDirectory)); if (modFolder != "Mods" && !Regex.IsMatch(modFolder, "Persona[^ ]*") && !sModFolders.Contains(modFolder)) //Any folder containing mod that isn't for sorting mods by game { sModFolders.Add(modFolder); } } FolderComboBox.ItemsSource = sModFolders.ToArray(); }
/********* ** Public methods *********/ /// <summary>Get manifest metadata for each folder in the given root path.</summary> /// <param name="toolkit">The mod toolkit.</param> /// <param name="rootPath">The root path to search for mods.</param> /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <returns>Returns the manifests by relative folder.</returns> public IEnumerable <IModMetadata> ReadManifests(ModToolkit toolkit, string rootPath, ModDatabase modDatabase) { foreach (ModFolder folder in toolkit.GetModFolders(rootPath)) { Manifest manifest = folder.Manifest; // parse internal data record (if any) ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // apply defaults if (manifest != null && dataRecord != null) { if (dataRecord.UpdateKey != null) { manifest.UpdateKeys = new[] { dataRecord.UpdateKey } } ; } // build metadata bool shouldIgnore = folder.Type == ModType.Ignored; ModMetadataStatus status = folder.ManifestParseError == ModParseError.None || shouldIgnore ? ModMetadataStatus.Found : ModMetadataStatus.Failed; yield return(new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore) .SetStatus(status, shouldIgnore ? "disabled by dot convention" : folder.ManifestParseErrorText)); } }
/********* ** Public methods *********/ /// <summary>Get manifest metadata for each folder in the given root path.</summary> /// <param name="toolkit">The mod toolkit.</param> /// <param name="rootPath">The root path to search for mods.</param> /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <returns>Returns the manifests by relative folder.</returns> public IEnumerable <IModMetadata> ReadManifests(ModToolkit toolkit, string rootPath, ModDatabase modDatabase) { foreach (ModFolder folder in toolkit.GetModFolders(rootPath)) { Manifest manifest = folder.Manifest; // parse internal data record (if any) ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // apply defaults if (manifest != null && dataRecord != null) { if (dataRecord.UpdateKey != null) { manifest.UpdateKeys = new[] { dataRecord.UpdateKey } } ; } // build metadata ModMetadataStatus status = folder.ManifestParseError == null || !folder.ShouldBeLoaded ? ModMetadataStatus.Found : ModMetadataStatus.Failed; string relativePath = PathUtilities.GetRelativePath(rootPath, folder.Directory.FullName); yield return(new ModMetadata(folder.DisplayName, folder.Directory.FullName, relativePath, manifest, dataRecord, isIgnored: !folder.ShouldBeLoaded) .SetStatus(status, !folder.ShouldBeLoaded ? "disabled by dot convention" : folder.ManifestParseError)); } }
/********* ** Public methods *********/ /// <summary>Get manifest metadata for each folder in the given root path.</summary> /// <param name="toolkit">The mod toolkit.</param> /// <param name="rootPath">The root path to search for mods.</param> /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <returns>Returns the manifests by relative folder.</returns> public IEnumerable <IModMetadata> ReadManifests(ModToolkit toolkit, string rootPath, ModDatabase modDatabase) { foreach (ModFolder folder in toolkit.GetModFolders(rootPath)) { Manifest manifest = folder.Manifest; // parse internal data record (if any) ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // apply defaults if (manifest != null && dataRecord != null) { if (dataRecord.UpdateKey != null) { manifest.UpdateKeys = new[] { dataRecord.UpdateKey } } ; } // build metadata ModMetadataStatus status = folder.ManifestParseError == null ? ModMetadataStatus.Found : ModMetadataStatus.Failed; yield return(new ModMetadata(folder.DisplayName, folder.Directory.FullName, manifest, dataRecord).SetStatus(status, folder.ManifestParseError)); } }
public void RefreshMods() { Mods = ModDatabase.Get(SelectedGame) .OrderBy(x => GameConfig.GetModPriority(x.Id)) .Select(x => new ModViewModel(x)) .ToList(); }
private void RefreshMods() { Mods = ModDatabase.Get(SelectedGame) .OrderBy(x => GameConfig.GetModPriority(x.Id)) .Select(x => new ModViewModel(x, SelectedGame)) .ToList(); ModGrid.ItemsSource = Mods; }
/// <summary> /// Refreshes the mods. /// Clears the nodestore, /// Sets up the modviewmodels from the moddatabase /// adds them to a list and the nodestore /// </summary> private void RefreshMods() { ModGrid.NodeStore.Clear(); Mods = ModDatabase.Get(SelectedGame) .OrderBy(x => GameConfig.GetModPriority(x.Id)) .Select(x => new ModViewModel(x)) .ToList(); Mods.ForEach(x => ModGrid.NodeStore.AddNode(x)); }
protected GameConfig() { OutputDirectoryPath = Path.Combine("Output", $"{Game}"); mModConfigs = new Dictionary <Guid, ModConfig>(); // Initialize default mod configs for game mods in the database int priority = 0; foreach (var mod in ModDatabase.Get(Game)) { AddModConfig(mod.Id, priority++, false); } }
/// <summary> /// Resolves a dependency and returns all DLL files that the dependency uses /// </summary> /// <param name="name"></param> /// <param name="verMajor"></param> /// <param name="verMinor"></param> /// <param name="canBeNewer"></param> /// <param name="searchDirs"></param> /// <param name="dllDir"></param> /// <param name="imageDir"></param> /// <param name="soundDir"></param> /// <param name="caller"></param> /// <returns></returns> public static IEnumerable <string> LoadDependency(string name, int verMajor, int verMinor, string dllDir, string mapDir, string assetDir, string caller, bool overwriteExisting) { List <string> _dependencyDlls = new List <string>(); if (!ModDatabase.Contains(name, verMajor)) { return(Enumerable.Empty <string>()); } var dbItem = ModDatabase.Get(name, verMajor); bool versionOk = true; if (verMajor > dbItem.Major) { versionOk = false; } else if (verMajor == dbItem.Major && verMinor < dbItem.Minor) { versionOk = false; } if (!versionOk) { throw new Exception("Could not resolve to an appropriate version of a dependency. All versions are too old."); } if (IsCircular(dbItem.File, caller)) { throw new Exception($"{name} and {caller} reference each other circularly. Cannot load either."); } //Resolve and load the dependency string errors; var module = ModLoader.LoadCompressedModFile(dbItem.File, dllDir, mapDir, assetDir, out errors, dbItem.UsesWhitelist, overwriteExisting); if (errors != null) { throw new Exception(errors); } //And then add all the dependencies _dependencyDlls.AddRange(module.Assemblies.Select(a => a.Location)); _dependencyDlls.AddRange(module.Dependencies.Select(a => a.Location)); return(_dependencyDlls.Distinct()); }
/********* ** Public methods *********/ /// <summary>Get manifest metadata for each folder in the given root path.</summary> /// <param name="toolkit">The mod toolkit.</param> /// <param name="rootPath">The root path to search for mods.</param> /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <returns>Returns the manifests by relative folder.</returns> public IEnumerable <IModMetadata> ReadManifests(ModToolkit toolkit, string rootPath, ModDatabase modDatabase) { foreach (ModFolder folder in toolkit.GetModFolders(rootPath)) { Manifest manifest = folder.Manifest; // parse internal data record (if any) ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // get display name string displayName = manifest?.Name; if (string.IsNullOrWhiteSpace(displayName)) { displayName = dataRecord?.DisplayName; } if (string.IsNullOrWhiteSpace(displayName)) { displayName = PathUtilities.GetRelativePath(rootPath, folder.ActualDirectory?.FullName ?? folder.SearchDirectory.FullName); } // apply defaults if (manifest != null && dataRecord != null) { if (dataRecord.UpdateKey != null) { manifest.UpdateKeys = new[] { dataRecord.UpdateKey } } ; } // build metadata ModMetadataStatus status = folder.ManifestParseError == null ? ModMetadataStatus.Found : ModMetadataStatus.Failed; yield return(new ModMetadata(displayName, folder.ActualDirectory?.FullName, manifest, dataRecord).SetStatus(status, folder.ManifestParseError)); } }
private void BuildButton_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrWhiteSpace(GameConfig.OutputDirectoryPath)) { MessageBox.Show(this, "Please specify an output directory in the settings.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } if (Mods.Count == 0) { MessageBox.Show(this, "No mods are available.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } if (!UpdateGameConfigEnabledMods()) { MessageBox.Show(this, "No mods are enabled.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } var task = Task.Factory.StartNew(() => { gEnabledMods = GameConfig.ModConfigs.Where(x => x.Enabled) .OrderBy(x => x.Priority) .Select(x => x.ModId) .Select(x => ModDatabase.Get(x)) .ToList(); Log.General.Info("Building mods:"); foreach (var enabledMod in gEnabledMods) { Log.General.Info($"\t{enabledMod.Title}"); } // Run prebuild scripts RunModScripts(gEnabledMods, "prebuild.bat"); var merger = new TopToBottomModMerger(); var merged = merger.Merge(gEnabledMods); // Todo var builder = ModBuilderManager.GetCompatibleModBuilders(SelectedGame).First().Create(); if (UltraISOUtility.Available) { if (SelectedGame == Game.Persona3) { builder = new Persona3IsoModBuilder(); } else if (SelectedGame == Game.Persona4) { builder = new Persona4IsoModBuilder(); } } Log.General.Info($"Output path: {GameConfig.OutputDirectoryPath}"); #if !DEBUG try #endif { builder.Build(merged, gEnabledMods, GameConfig.OutputDirectoryPath); } #if !DEBUG catch (InvalidConfigException exception) { InvokeOnUIThread( () => MessageBox.Show(this, $"SelectedGame configuration is invalid.\n{exception.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error)); return(false); } catch (MissingFileException exception) { InvokeOnUIThread( () => MessageBox.Show(this, $"A file is missing:\n{exception.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error)); return(false); } catch (Exception exception) { InvokeOnUIThread( () => MessageBox.Show( this, $"Unhandled exception occured while building:\n{exception.Message}\n{exception.StackTrace}", "Error", MessageBoxButton.OK, MessageBoxImage.Error)); #if DEBUG throw; #endif #pragma warning disable 162 return(false); #pragma warning restore 162 } #endif return(true); }, TaskCreationOptions.AttachedToParent); task.ContinueWith((t) => { InvokeOnUIThread(() => { if (t.Result) { MessageBox.Show(this, "Done building!", "Done", MessageBoxButton.OK, MessageBoxImage.None); RunModScripts(gEnabledMods, "postbuild.bat"); } }); }); }
/********* ** Private methods *********/ /// <summary>Sort a mod's dependencies by the order they should be loaded, and remove any mods that can't be loaded due to missing or conflicting dependencies.</summary> /// <param name="mods">The full list of mods being validated.</param> /// <param name="modDatabase">Handles access to SMAPI's internal mod metadata list.</param> /// <param name="mod">The mod whose dependencies to process.</param> /// <param name="states">The dependency state for each mod.</param> /// <param name="sortedMods">The list in which to save mods sorted by dependency order.</param> /// <param name="currentChain">The current change of mod dependencies.</param> /// <returns>Returns the mod dependency status.</returns> private ModDependencyStatus ProcessDependencies(IModMetadata[] mods, ModDatabase modDatabase, IModMetadata mod, IDictionary <IModMetadata, ModDependencyStatus> states, Stack <IModMetadata> sortedMods, ICollection <IModMetadata> currentChain) { // check if already visited switch (states[mod]) { // already sorted or failed case ModDependencyStatus.Sorted: case ModDependencyStatus.Failed: return(states[mod]); // dependency loop case ModDependencyStatus.Checking: // This should never happen. The higher-level mod checks if the dependency is // already being checked, so it can fail without visiting a mod twice. If this // case is hit, that logic didn't catch the dependency loop for some reason. throw new InvalidModStateException($"A dependency loop was not caught by the calling iteration ({string.Join(" => ", currentChain.Select(p => p.DisplayName))} => {mod.DisplayName}))."); // not visited yet, start processing case ModDependencyStatus.Queued: break; // sanity check default: throw new InvalidModStateException($"Unknown dependency status '{states[mod]}'."); } // collect dependencies ModDependency[] dependencies = this.GetDependenciesFrom(mod.Manifest, mods).ToArray(); // mark sorted if no dependencies if (!dependencies.Any()) { sortedMods.Push(mod); return(states[mod] = ModDependencyStatus.Sorted); } // mark failed if missing dependencies { string[] failedModNames = ( from entry in dependencies where entry.IsRequired && entry.Mod == null let displayName = modDatabase.Get(entry.ID)?.DisplayName ?? entry.ID let modUrl = modDatabase.GetModPageUrlFor(entry.ID) orderby displayName select modUrl != null ? $"{displayName}: {modUrl}" : displayName ).ToArray(); if (failedModNames.Any()) { sortedMods.Push(mod); mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.MissingDependencies, $"it requires mods which aren't installed ({string.Join(", ", failedModNames)})."); return(states[mod] = ModDependencyStatus.Failed); } } // dependency min version not met, mark failed { string[] failedLabels = ( from entry in dependencies where entry.Mod != null && entry.MinVersion != null && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version) select $"{entry.Mod.DisplayName} (needs {entry.MinVersion} or later)" ) .ToArray(); if (failedLabels.Any()) { sortedMods.Push(mod); mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.MissingDependencies, $"it needs newer versions of some mods: {string.Join(", ", failedLabels)}."); return(states[mod] = ModDependencyStatus.Failed); } } // process dependencies { states[mod] = ModDependencyStatus.Checking; // recursively sort dependencies foreach (var dependency in dependencies) { IModMetadata requiredMod = dependency.Mod; var subchain = new List <IModMetadata>(currentChain) { mod }; // ignore missing optional dependency if (!dependency.IsRequired && requiredMod == null) { continue; } // detect dependency loop if (states[requiredMod] == ModDependencyStatus.Checking) { sortedMods.Push(mod); mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.MissingDependencies, $"its dependencies have a circular reference: {string.Join(" => ", subchain.Select(p => p.DisplayName))} => {requiredMod.DisplayName})."); return(states[mod] = ModDependencyStatus.Failed); } // recursively process each dependency var substatus = this.ProcessDependencies(mods, modDatabase, requiredMod, states, sortedMods, subchain); switch (substatus) { // sorted successfully case ModDependencyStatus.Sorted: case ModDependencyStatus.Failed when !dependency.IsRequired: // ignore failed optional dependency break; // failed, which means this mod can't be loaded either case ModDependencyStatus.Failed: sortedMods.Push(mod); mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.MissingDependencies, $"it needs the '{requiredMod.DisplayName}' mod, which couldn't be loaded."); return(states[mod] = ModDependencyStatus.Failed); // unexpected status case ModDependencyStatus.Queued: case ModDependencyStatus.Checking: throw new InvalidModStateException($"Something went wrong sorting dependencies: mod '{requiredMod.DisplayName}' unexpectedly stayed in the '{substatus}' status."); // sanity check default: throw new InvalidModStateException($"Unknown dependency status '{states[mod]}'."); } } // all requirements sorted successfully sortedMods.Push(mod); return(states[mod] = ModDependencyStatus.Sorted); } }