/// <summary> /// Creates a new mod from a folder. /// </summary> /// <param name="gamePath">Path to the game installation</param> /// <param name="folderPath">Path to folder</param> /// <returns></returns> private static ManagedMod FromFolder(string gamePath, string folderPath, Action <Progress> ProgressChanged = null) { // Get path information: folderPath = EnsureLongPathSupport(folderPath); string folderName = Path.GetFileName(folderPath); // Install mod: ManagedMod newMod = new ManagedMod(gamePath); newMod.Title = folderName; newMod.ArchiveName = folderName + ".ba2"; newMod.ManagedFolderName = folderName; if (!Utils.IsFileNameValid(newMod.ManagedFolderName) || Directory.Exists(newMod.ManagedFolderPath)) { newMod.ManagedFolderName = newMod.DefaultManagedFolderName; } // Copy folder: CopyDirectory(folderPath, newMod.ManagedFolderPath, ProgressChanged); ModActions.CleanUpFolder(newMod.ManagedFolderPath, ProgressChanged); ModActions.DetectOptimalModInstallationOptions(newMod); return(newMod); }
public static void Freeze(ManagedMod mod) { // TODO: Remove connection to ManagedMods // Only freeze if not already frozen: if (mod.Frozen && File.Exists(mod.FrozenArchivePath)) { // TODO: ModActions.Freeze: Should the mod get "refrozen"? //ManagedMods.Instance.logFile.WriteLine($"Cannot freeze a mod ('{mod.Title}') that is already frozen.\n"); return; } Directory.CreateDirectory(mod.FrozenDataPath); // Getting preset: Archive2.Preset preset = ModHelpers.GetArchive2Preset(mod); ModDeployment.LogFile.WriteLine($" Freezing mod '{mod.Title}'..."); ModDeployment.LogFile.WriteLine($" Format: {preset.format}"); ModDeployment.LogFile.WriteLine($" Compression: {preset.compression}"); ModDeployment.LogFile.WriteLine($" Destination: FrozenData\\{mod.FrozenArchiveName}"); // Create archive: Archive2.Create(mod.FrozenArchivePath, mod.ManagedFolderPath, preset); // Change DiskState and save: mod.Frozen = true; mod.FrozenCompression = mod.Compression; mod.FrozenFormat = mod.Format; }
/// <summary> /// Copies the folder and adds the mod to the list. /// Saves the xml file afterwards. /// </summary> public static void InstallFolder(ManagedMods mods, string folderPath, Action <Progress> ProgressChanged = null) { ManagedMod newMod = ModInstallations.FromFolder(mods.GamePath, folderPath, ProgressChanged); mods.Add(newMod); mods.Save(); ProgressChanged?.Invoke(Progress.Done("Mod folder installed.")); }
/// <summary> /// Extracts the archive and adds the mod to the list. /// Saves the xml file afterwards. /// </summary> /// <param name="useSourceBA2Archive">When false, creates a new "frozen" mod.</param> public static void InstallArchive(ManagedMods mods, string filePath, bool useSourceBA2Archive = false, Action <Progress> ProgressChanged = null) { ManagedMod newMod = ModInstallations.FromArchive(mods.GamePath, filePath, useSourceBA2Archive, ProgressChanged); mods.Add(newMod); mods.Save(); ProgressChanged?.Invoke(Progress.Done("Mod archive installed.")); }
/// <summary> /// Adds a new blank mod. /// </summary> public static void InstallBlank(ManagedMods mods) { ManagedMod newMod = new ManagedMod(mods.GamePath); newMod.Title = "Untitled"; newMod.ArchiveName = "untitled.ba2"; Directory.CreateDirectory(newMod.ManagedFolderPath); mods.Add(newMod); mods.Save(); }
public static void Unfreeze(ManagedMod mod) { // Delete *.ba2: if (File.Exists(mod.FrozenArchivePath)) { File.Delete(mod.FrozenArchivePath); } // Change DiskState and save: mod.Frozen = false; }
/// <summary> /// Copies and replaces from the external folder into the managed mod folder. /// </summary> /// <param name="copyFolder">If true, will copy the folder instead of it's contents.</param> public static void AddFolder(ManagedMod mod, string folderPath, bool copyFolder, Action <Progress> ProgressChanged = null) { string longFolderPath = EnsureLongPathSupport(folderPath); string folderName = Path.GetFileName(folderPath); CopyDirectory(longFolderPath, copyFolder ? Path.Combine(mod.ManagedFolderPath, folderName) : mod.ManagedFolderPath, ProgressChanged); // TODO: ModActions.CleanUpFolder(newMod.ManagedFolderPath, ProgressChanged); ProgressChanged?.Invoke(Progress.Done("Folder added to mod.")); }
/// <summary> /// Creates a deep copy of 'mod'. /// </summary> /// <param name="mod">The object it makes a copy of.</param> public ManagedMod(ManagedMod mod) { /* * Info */ this.title = mod.title; this.ID = mod.ID; this.URL = mod.URL; this.Version = mod.Version; this.guid = mod.guid; this.GamePath = mod.GamePath; this.ManagedFolderName = mod.ManagedFolderName; /* * General */ this.Enabled = mod.Enabled; this.Deployed = mod.Deployed; this.Method = mod.Method; this.PreviousMethod = mod.PreviousMethod; /* * SeparateBA2 */ this.archiveName = mod.archiveName; this.CurrentArchiveName = mod.CurrentArchiveName; this.Compression = mod.Compression; this.CurrentCompression = mod.CurrentCompression; this.Format = mod.Format; this.CurrentFormat = mod.CurrentFormat; /* * SeparateBA2 Frozen */ this.Freeze = mod.Freeze; this.Frozen = mod.Frozen; this.FrozenCompression = mod.FrozenCompression; this.FrozenFormat = mod.FrozenFormat; /* * Loose */ this.LooseFiles = new List <string>(mod.LooseFiles); this.RootFolder = mod.RootFolder; this.CurrentRootFolder = mod.CurrentRootFolder; }
/// <summary> /// Returns the legacy string of ArchiveFormat. /// /// public enum ArchiveFormat /// { /// Auto, /// General, /// Textures /// } /// </summary> /// <returns>"Auto", "General", or "Textures"</returns> private static string GetLegacyArchiveFormatName(ManagedMod mod) { switch (mod.CurrentFormat) { case ManagedMod.ArchiveFormat.General: return("General"); case ManagedMod.ArchiveFormat.Textures: return("Textures"); case ManagedMod.ArchiveFormat.Auto: default: return("Auto"); } }
/// <summary> /// Returns the legacy string of ArchiveCompression. /// /// public enum ArchiveCompression /// { /// Auto, /// Compressed, /// Uncompressed /// } /// </summary> /// <returns>"Auto", "Compressed", or "Uncompressed"</returns> private static string GetLegacyArchiveCompressionName(ManagedMod mod) { switch (mod.CurrentCompression) { case ManagedMod.ArchiveCompression.Compressed: return("Compressed"); case ManagedMod.ArchiveCompression.Uncompressed: return("Uncompressed"); case ManagedMod.ArchiveCompression.Auto: default: return("Auto"); } }
/// <summary> /// Returns the legacy string of the install method: /// /// public enum FileType /// { /// Loose, /// BundledBA2, /// SeparateBA2 /// } /// </summary> /// <returns>"Loose", "SeparateBA2", or "BundledBA2"</returns> private static string GetLegacyInstallMethodName(ManagedMod mod) { switch (mod.PreviousMethod) { case ManagedMod.DeploymentMethod.LooseFiles: return("Loose"); case ManagedMod.DeploymentMethod.SeparateBA2: return("SeparateBA2"); case ManagedMod.DeploymentMethod.BundledBA2: default: return("BundledBA2"); } }
/// <summary> /// Deletes all files of 'mod'. /// This includes the managed folder and frozen archive. /// </summary> private static void DeleteFiles(ManagedMod mod) { // Delete managed folder: if (Directory.Exists(mod.ManagedFolderPath)) { Directory.Delete(mod.ManagedFolderPath, true); } // Delete frozen archive: if (/*mod.Frozen && * mod.PreviousMethod == ManagedMod.DeploymentMethod.SeparateBA2 &&*/ File.Exists(mod.FrozenArchivePath)) { File.Delete(mod.FrozenArchivePath); } }
public static void Remove(ManagedMod mod, ResourceList resources, String GamePath) { if (mod.Deployed) { switch (mod.PreviousMethod) { case ManagedMod.DeploymentMethod.BundledBA2: LogFile.WriteLine($" Skipped (mod is bundled)"); break; case ManagedMod.DeploymentMethod.SeparateBA2: LogFile.WriteLine($" Deleting {mod.CurrentArchiveName}"); File.Delete(mod.CurrentArchivePath); resources.Remove(mod.CurrentArchiveName); break; case ManagedMod.DeploymentMethod.LooseFiles: LogFile.WriteLine($" Deleting loose files"); foreach (string relFilePath in mod.LooseFiles) { string installedFilePath = Path.GetFullPath(Path.Combine(GamePath, mod.CurrentRootFolder, relFilePath)); // .Replace("\\.\\", "\\") // Delete file, if existing: if (File.Exists(installedFilePath)) { File.Delete(installedFilePath); } // Rename backup, if there is one: if (File.Exists(installedFilePath + ".old")) { File.Move(installedFilePath + ".old", installedFilePath); } // Remove empty folders one by one, if existing: else { RemoveEmptyFolders(Path.GetDirectoryName(installedFilePath)); } } mod.LooseFiles.Clear(); break; } mod.Deployed = false; } }
/// <summary> /// Downloads and installs a mod from NexusMods. (by using NXM links) /// </summary> /// <param name="useSourceBA2Archive">When false, creates a new "frozen" mod.</param> public static bool InstallRemote(ManagedMods mods, string nxmLinkStr, bool useSourceBA2Archive = false, Action <Progress> ProgressChanged = null) { NXMLink nxmLink = NXMHandler.ParseLink(nxmLinkStr); // Get the download link from NexusMods: ProgressChanged(Progress.Indetermined("Requesting mod download link...")); string dlLinkStr = NMMod.RequestDownloadLink(nxmLink); if (dlLinkStr == null) { ProgressChanged?.Invoke(Progress.Aborted("Couldn't retrieve download link...")); return(false); } Uri dlLink = new Uri(dlLinkStr); string dlFileName = dlLink.Segments.Last(); string dlPath = Path.Combine(Shared.DownloadsFolder, dlFileName); // Download mod, unless we already have it: if (!File.Exists(dlPath)) { DownloadFile(dlLink.OriginalString, dlPath, ProgressChanged); } if (!File.Exists(dlPath)) { ProgressChanged?.Invoke(Progress.Aborted("Download failed.")); return(false); } // Get remote mod info: ProgressChanged(Progress.Indetermined("Requesting mod information and thumbnail...")); NMMod nmMod = NexusMods.RequestModInformation(nxmLink.modId); // Install mod: ProgressChanged(Progress.Indetermined($"Installing '{nmMod.Title}'...")); ManagedMod newMod = ModInstallations.FromArchive(mods.GamePath, dlPath, useSourceBA2Archive, ProgressChanged); newMod.Title = nmMod.Title; newMod.Version = nmMod.LatestVersion; newMod.URL = nmMod.URL; mods.Add(newMod); mods.Save(); ProgressChanged?.Invoke(Progress.Done($"'{nmMod.Title}' installed.")); return(true); }
/// <summary> /// Used in the deployment chain to copy individual files to a temporary folder. /// It sorts files into different temporary folders. /// Each temporary folder gets packed to a bundled *.ba2 archive. /// </summary> private static void CopyFilesToTempSorted(ManagedMod mod, DeployArchiveList archives) { // Iterate over each file in the managed folder: IEnumerable <string> files = Directory.EnumerateFiles(mod.ManagedFolderPath, "*.*", SearchOption.AllDirectories); foreach (string filePath in files) { FileInfo info = new FileInfo(filePath); string fileExtension = info.Extension.ToLower(); // Make a relative path: string relativePath = Utils.MakeRelativePath(mod.ManagedFolderPath, filePath); // Determine the type of archive: string destinationPath; if (relativePath.Trim().ToLower().StartsWith("sound") || relativePath.Trim().ToLower().StartsWith("music") || (new string[] { ".wav", ".xwm", ".fuz", ".lip" }).Contains(fileExtension)) { archives.SoundsArchive.Count++; destinationPath = Path.Combine(archives.SoundsArchive.TempPath, relativePath); } else if (fileExtension == ".dds") { archives.TexturesArchive.Count++; destinationPath = Path.Combine(archives.TexturesArchive.TempPath, relativePath); } else { archives.GeneralArchive.Count++; destinationPath = Path.Combine(archives.GeneralArchive.TempPath, relativePath); } // Copy the file to the correct temp folder: Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); if (Configuration.bUseHardlinks) { Utils.CreateHardLink(filePath, destinationPath, true); } else { File.Copy(filePath, destinationPath, true); } } }
/// <summary> /// Creates a new mod from any supported archive. (zip, tar, rar, 7z, ba2) /// BA2 files can be installed frozen if needed. /// </summary> /// <param name="gamePath">Path to the game installation</param> /// <param name="filePath">Path to archive</param> /// <param name="useSourceBA2Archive">When false, creates a new "frozen" mod.</param> /// <returns></returns> private static ManagedMod FromArchive(string gamePath, string filePath, bool useSourceBA2Archive = false, Action <Progress> ProgressChanged = null) { // Get path information: string longFilePath = EnsureLongPathSupport(filePath); string fileNameWOEx = Path.GetFileNameWithoutExtension(longFilePath); string fileExtension = Path.GetExtension(longFilePath); // Install mod: ManagedMod newMod = new ManagedMod(gamePath); newMod.Title = fileNameWOEx; newMod.ArchiveName = fileNameWOEx + ".ba2"; newMod.ManagedFolderName = fileNameWOEx; if (!Utils.IsFileNameValid(newMod.ManagedFolderName) || Directory.Exists(newMod.ManagedFolderPath)) { newMod.ManagedFolderName = newMod.DefaultManagedFolderName; } // Extract mod: ProgressChanged?.Invoke(Progress.Indetermined($"Extracting {Path.GetFileName(filePath)}")); ModInstallations.ExtractArchive(longFilePath, newMod.ManagedFolderPath); // Freeze mod conditionally: if (useSourceBA2Archive && fileExtension == ".ba2") { // Copy *.ba2 into FrozenData: FileInfo frozenPath = new FileInfo(newMod.FrozenArchivePath); ProgressChanged?.Invoke(Progress.Indetermined($"Copying {Path.GetFileName(filePath)} to {frozenPath.DirectoryName}")); Directory.CreateDirectory(frozenPath.DirectoryName); File.Copy(longFilePath, frozenPath.FullName, true); newMod.Frozen = true; newMod.Freeze = true; newMod.PreviousMethod = ManagedMod.DeploymentMethod.SeparateBA2; newMod.Method = ManagedMod.DeploymentMethod.SeparateBA2; } else { ModActions.CleanUpFolder(newMod.ManagedFolderPath, ProgressChanged); ModActions.DetectOptimalModInstallationOptions(newMod); } return(newMod); }
/// <summary> /// Extracts the archive and then copy and replaces from the temp folder into the managed mod folder. /// </summary> public static void AddArchive(ManagedMod mod, string filePath, Action <Progress> ProgressChanged = null) { string longFilePath = EnsureLongPathSupport(filePath); string tempFolderPath = Path.Combine(Path.GetTempPath(), $"tmp_{mod.guid}"); if (Directory.Exists(tempFolderPath)) { Directory.Delete(tempFolderPath, true); } Directory.CreateDirectory(tempFolderPath); ProgressChanged?.Invoke(Progress.Indetermined($"Extracting {Path.GetFileName(filePath)}")); ModInstallations.ExtractArchive(longFilePath, tempFolderPath); ModActions.CleanUpFolder(tempFolderPath, ProgressChanged); CopyDirectory(tempFolderPath, mod.ManagedFolderPath, ProgressChanged); Directory.Delete(tempFolderPath, true); ProgressChanged?.Invoke(Progress.Done("Archive added to mod.")); }
/// <summary> /// Used in the deployment chain to deploy a single mod with the Loose method. /// </summary> private static void DeployLooseFiles(ManagedMods mods, ManagedMod mod, String GamePath) { LogFile.WriteLine($" Installing mod '{mod.Title}' as LooseFiles"); mod.LooseFiles.Clear(); // Iterate over each file in the managed folder ... foreach (string filePath in Directory.EnumerateFiles(mod.ManagedFolderPath, "*.*", SearchOption.AllDirectories)) { // ... extract the relative path ... string relPath = Utils.MakeRelativePath(mod.ManagedFolderPath, filePath); mod.LooseFiles.Add(relPath); // ... determine the full destination path ... string destinationPath = Path.Combine(GamePath, mod.RootFolder, relPath); FileInfo destInfo = new FileInfo(destinationPath); Directory.CreateDirectory(destInfo.DirectoryName); // ... make a backup if the file already exists ... if (File.Exists(destinationPath) && !DoesLooseFileBelongToMod(mods, destinationPath) && !File.Exists(destinationPath + ".old")) { File.Move(destinationPath, destinationPath + ".old"); } // ... and copy the file (and replace it if necessary). LogFile.WriteLine($" Copying: \"{relPath}\""); if (Configuration.bUseHardlinks) { Utils.CreateHardLink(filePath, destinationPath, true); } else { File.Copy(filePath, destinationPath, true); } } mod.CurrentRootFolder = mod.RootFolder; mod.Deployed = true; mod.PreviousMethod = ManagedMod.DeploymentMethod.LooseFiles; }
/// <summary> /// Renames the managed folder. /// </summary> /// <param name="mod"></param> /// <param name="newFolderName"></param> /// <returns>True if successful, False otherwise</returns> public static bool RenameFolder(ManagedMod mod, string newFolderName) { // Don't rename if folder name is equal: if (mod.ManagedFolderName == newFolderName) { return(false); } // Don't rename if folder name is invalid: if (!Utils.IsFileNameValid(newFolderName)) { return(false); } // Don't rename if folder already exists: string newFolderPath = Path.Combine(mod.GamePath, "Mods", newFolderName); if (Directory.Exists(newFolderPath)) { return(false); } // Try to rename folder: try { Directory.Move(mod.ManagedFolderPath, newFolderPath); } catch { return(false); } // Successful? mod.ManagedFolderName = newFolderName; return(true); }
public static void DetectOptimalModInstallationOptions(ManagedMod mod, Action <Progress> ProgressChanged = null) { ProgressChanged?.Invoke(Progress.Indetermined("Detecting installation options.")); /* * Searching through folder: */ bool resourceFoldersFound = false; bool generalFoldersFound = false; bool texturesFolderFound = false; bool soundFoldersFound = false; bool stringsFolderFound = false; bool dataFolderFound = false; bool videoFolderFound = false; bool dllFound = false; foreach (string folderPath in Directory.EnumerateDirectories(mod.ManagedFolderPath)) { string folderName = Path.GetFileName(folderPath).ToLower(); if (ModHelpers.ResourceFolders.Contains(folderName)) { resourceFoldersFound = true; } if (ModHelpers.GeneralFolders.Contains(folderName)) { generalFoldersFound = true; } if (ModHelpers.TextureFolders.Contains(folderName)) { texturesFolderFound = true; } if (ModHelpers.SoundFolders.Contains(folderName)) { soundFoldersFound = true; } if (folderName == "strings") { stringsFolderFound = true; } if (folderName == "data") { dataFolderFound = true; } if (folderName == "video") { videoFolderFound = true; } } foreach (string filePath in Directory.EnumerateFiles(mod.ManagedFolderPath)) { string fileExtension = Path.GetExtension(filePath).ToLower(); if (fileExtension == ".dll") { dllFound = true; } } /* * Detecting optimal installation options: */ if (resourceFoldersFound) { mod.Method = ManagedMod.DeploymentMethod.SeparateBA2; mod.Format = ManagedMod.ArchiveFormat.Auto; mod.Compression = ManagedMod.ArchiveCompression.Auto; mod.RootFolder = "Data"; } if (stringsFolderFound || videoFolderFound) { mod.Method = ManagedMod.DeploymentMethod.LooseFiles; mod.RootFolder = "Data"; } if (dllFound || dataFolderFound) { mod.Method = ManagedMod.DeploymentMethod.LooseFiles; mod.RootFolder = "."; } if (generalFoldersFound) { mod.Method = ManagedMod.DeploymentMethod.SeparateBA2; mod.Format = ManagedMod.ArchiveFormat.General; mod.Compression = ManagedMod.ArchiveCompression.Compressed; mod.RootFolder = "Data"; } if (texturesFolderFound) { mod.Method = ManagedMod.DeploymentMethod.SeparateBA2; mod.Format = ManagedMod.ArchiveFormat.Textures; mod.Compression = ManagedMod.ArchiveCompression.Compressed; mod.RootFolder = "Data"; } if (soundFoldersFound) { mod.Method = ManagedMod.DeploymentMethod.SeparateBA2; mod.Format = ManagedMod.ArchiveFormat.General; mod.Compression = ManagedMod.ArchiveCompression.Uncompressed; mod.RootFolder = "Data"; } if (generalFoldersFound && texturesFolderFound || generalFoldersFound && soundFoldersFound || texturesFolderFound && soundFoldersFound) { mod.Method = ManagedMod.DeploymentMethod.BundledBA2; mod.Format = ManagedMod.ArchiveFormat.Auto; mod.Compression = ManagedMod.ArchiveCompression.Auto; mod.RootFolder = "Data"; } }
/// <summary> /// Used in the deployment chain to deploy a single mod with the SeparateBA2 method. /// Freezes a mod if necessary. /// </summary> private static void DeploySeparateArchive(ManagedMod mod, ResourceList resources) { LogFile.WriteLine($" Installing mod '{mod.Title}' as SeparateBA2"); // If mod is supposed to be deployed frozen... if (mod.Freeze) { // ... freeze if necessary ... if (!mod.Frozen) { //LogFile.WriteLine($" Freezing mod..."); ModActions.Freeze(mod); } LogFile.WriteLine($" Copying frozen archive..."); // ... and copy it to the Data folder. if (Configuration.bUseHardlinks) { Utils.CreateHardLink( mod.FrozenArchivePath, mod.ArchivePath, true); } else { File.Copy( mod.FrozenArchivePath, mod.ArchivePath, true); } } // If mod isn't supposed to be deployed frozen... else { // ... unfreeze mod if needed ... if (mod.Frozen) { LogFile.WriteLine($" Unfreezing mod..."); ModActions.Unfreeze(mod); } // Getting preset: Archive2.Preset preset = ModHelpers.GetArchive2Preset(mod); LogFile.WriteLine($" Creating new archive..."); LogFile.WriteLine($" Format: {preset.format}"); LogFile.WriteLine($" Compression: {preset.compression}"); // ... and create a new archive. Archive2.Create( mod.ArchivePath, mod.ManagedFolderPath, preset); } // Finally, update the disk state ... mod.CurrentArchiveName = mod.ArchiveName; mod.CurrentCompression = mod.Frozen ? mod.FrozenCompression : mod.Compression; mod.CurrentFormat = mod.Frozen ? mod.FrozenFormat : mod.Format; mod.Deployed = true; mod.PreviousMethod = ManagedMod.DeploymentMethod.SeparateBA2; // ... and add the archive to the resource list. resources.Add(mod.ArchiveName); LogFile.WriteLine($" Installed."); }
/// <summary> /// Loads and converts legacy managed mods to the new format. /// It adds them to an already existing ManagedMods object. /// </summary> public static void ConvertLegacy(ManagedMods mods, GameEdition edition, Action <Progress> ProgressChanged = null) { Directory.CreateDirectory(Path.Combine(mods.GamePath, "FrozenData")); XDocument xmlDoc = XDocument.Load(Path.Combine(mods.ModsPath, "manifest.xml")); // I added a doNotImport="true" attribute, so I can check, whether the manifest.xml has only been generated for backwards-compatibility. // If it exists, we can just skip the import: if (xmlDoc.Root.Attribute("doNotImport") != null) { ProgressChanged?.Invoke(Progress.Aborted("Import skipped.")); return; } // Make backups: File.Copy(Path.Combine(mods.ModsPath, "manifest.xml"), Path.Combine(mods.ModsPath, "manifest.old.xml"), true); if (File.Exists(Path.Combine(mods.ModsPath, "managed.xml"))) { File.Copy(Path.Combine(mods.ModsPath, "managed.xml"), Path.Combine(mods.ModsPath, "managed.old.xml"), true); } // Converting the legacy list will completely erase the current mod list: mods.Mods.Clear(); /* * Converting: */ int modCount = xmlDoc.Descendants("Mod").Count(); int modIndex = 0; foreach (XElement xmlMod in xmlDoc.Descendants("Mod")) { modIndex++; if (xmlMod.Attribute("modFolder") == null) { continue; } ManagedMod mod = new ManagedMod(mods.GamePath); string managedFolderName = xmlMod.Attribute("modFolder").Value; string managedFolderPath = Path.Combine(mods.ModsPath, managedFolderName); string frozenArchivePath = Path.Combine(mods.ModsPath, managedFolderName, "frozen.ba2"); bool isFrozen = File.Exists(frozenArchivePath); mod.ManagedFolderName = managedFolderName; if (xmlMod.Attribute("title") != null) { mod.Title = xmlMod.Attribute("title").Value; } string progressTitle = $"Converting \"{mod.Title}\" ({modIndex} of {modCount})"; float progressPercentage = (float)modIndex / (float)modCount; // In case the mod was "frozen" before, // we'll need to move the *.ba2 archive into the FrozenData folder and then extract it. if (isFrozen) { ProgressChanged?.Invoke(Progress.Ongoing($"{progressTitle}: Extracting *.ba2 archive...", progressPercentage)); File.Move(frozenArchivePath, mod.FrozenArchivePath); Archive2.Extract(mod.FrozenArchivePath, managedFolderPath); mod.Frozen = true; mod.Freeze = true; } // OBSOLETE: We need to rename the old folder to fit with the new format. /*if (Directory.Exists(managedFolderPath)) * { * ProgressChanged?.Invoke(Progress.Ongoing($"{progressTitle}: Moving managed folder...", progressPercentage)); * if (Directory.Exists(mod.ManagedFolderPath)) * Directory.Delete(mod.ManagedFolderPath, true); * Directory.Move(managedFolderPath, mod.ManagedFolderPath); * }*/ ProgressChanged?.Invoke(Progress.Ongoing($"{progressTitle}: Parsing XML...", progressPercentage)); if (xmlMod.Attribute("url") != null) { mod.URL = xmlMod.Attribute("url").Value; } if (xmlMod.Attribute("version") != null) { mod.Version = xmlMod.Attribute("version").Value; } if (xmlMod.Attribute("enabled") != null) { try { mod.Deployed = XmlConvert.ToBoolean(xmlMod.Attribute("enabled").Value); } catch { mod.Deployed = false; } mod.Enabled = mod.Deployed; } if (xmlMod.Attribute("installType") != null) { switch (xmlMod.Attribute("installType").Value) { case "Loose": mod.PreviousMethod = ManagedMod.DeploymentMethod.LooseFiles; break; case "SeparateBA2": mod.PreviousMethod = ManagedMod.DeploymentMethod.SeparateBA2; break; case "BA2Archive": // Backward compatibility case "BundledBA2": case "BundledBA2Textures": // Backward compatibility default: mod.PreviousMethod = ManagedMod.DeploymentMethod.BundledBA2; break; } mod.Method = mod.PreviousMethod; } if (xmlMod.Attribute("format") != null) { switch (xmlMod.Attribute("format").Value) { case "General": mod.CurrentFormat = ManagedMod.ArchiveFormat.General; break; case "DDS": // Backward compatibility case "Textures": mod.CurrentFormat = ManagedMod.ArchiveFormat.Textures; break; case "Auto": default: mod.CurrentFormat = ManagedMod.ArchiveFormat.Auto; break; } mod.Format = mod.CurrentFormat; } if (xmlMod.Attribute("compression") != null) { switch (xmlMod.Attribute("compression").Value) { case "Default": // Backward compatibility case "Compressed": mod.CurrentCompression = ManagedMod.ArchiveCompression.Compressed; break; case "None": // Backward compatibility case "Uncompressed": mod.CurrentCompression = ManagedMod.ArchiveCompression.Uncompressed; break; case "Auto": default: mod.CurrentCompression = ManagedMod.ArchiveCompression.Auto; break; } mod.Compression = mod.CurrentCompression; } if (xmlMod.Attribute("archiveName") != null) { mod.CurrentArchiveName = xmlMod.Attribute("archiveName").Value; mod.ArchiveName = mod.CurrentArchiveName; } if (xmlMod.Attribute("root") != null) { mod.CurrentRootFolder = xmlMod.Attribute("root").Value; mod.RootFolder = mod.CurrentRootFolder; foreach (XElement xmlFile in xmlMod.Descendants("File")) { if (xmlFile.Attribute("path") != null) { mod.LooseFiles.Add(xmlFile.Attribute("path").Value); } } } /*if (xmlMod.Attribute("frozen") != null) * { * frozen = XmlConvert.ToBoolean(xmlMod.Attribute("frozen").Value); * }*/ mods.Add(mod); } // Legacy resource list: if (IniFiles.Config.GetBool("Preferences", "bMultipleGameEditionsUsed", false)) { string backedUpList = IniFiles.Config.GetString("Mods", $"sResourceIndexFileList{edition}", ""); string actualList = IniFiles.F76Custom.GetString("Archive", "sResourceIndexFileList", ""); if (backedUpList != "") { mods.Resources.ReplaceRange(ResourceList.FromString(backedUpList)); } else if (actualList != "") { mods.Resources.ReplaceRange(ResourceList.FromString(actualList)); } } ProgressChanged?.Invoke(Progress.Ongoing("Saving XML...", 1f)); mods.Save(); ProgressChanged?.Invoke(Progress.Done("Legacy mods imported.")); }
/// <summary> /// Checks if enabled mods lower on the list overwrite files of enabled mods higher on the list. /// </summary> /// <returns>A list of conflicting mods.</returns> public static List <Conflict> GetConflictingFiles(List <ManagedMod> mods) { List <Conflict> conflictingMods = new List <Conflict>(); // Mods higher in the list (upperMod) can get overwritten by mods lower in the list (lowerMod). // Iterate over all mods: for (int i = 1; i < mods.Count; i++) { ManagedMod lowerMod = mods[i]; string lowerPath = lowerMod.ManagedFolderPath; // If not enabled or non-existant, we don't need to check. if (!lowerMod.Enabled || !Directory.Exists(lowerPath)) { continue; } // Get a list of files with relative paths: List <string> lowerRelPaths = new List <string>(); foreach (string filePath in Directory.EnumerateFiles(lowerPath, "*.*", SearchOption.AllDirectories)) { lowerRelPaths.Add(Utils.MakeRelativePath(lowerPath, filePath)); } // Iterate over all mods whose files could get overwritten: for (int l = 0; l < i; l++) { ManagedMod upperMod = mods[l]; string upperPath = upperMod.ManagedFolderPath; // If not enabled or non-existant, we don't need to check. if (!upperMod.Enabled || !Directory.Exists(upperPath)) { continue; } Conflict conflict = new Conflict(); conflict.conflictingFiles = new List <string>(); conflict.conflictText = lowerMod.Title + " overrides " + upperMod.Title; // For each file... foreach (string filePath in Directory.EnumerateFiles(upperPath, "*.*", SearchOption.AllDirectories)) { string relUpperPath = Utils.MakeRelativePath(upperPath, filePath); // ... check if it gets overwritten by the lower mod: if (lowerRelPaths.Contains(relUpperPath)) { // If it does, add it to the list of conflicting files: if (!conflict.conflictingFiles.Contains(relUpperPath)) { conflict.conflictingFiles.Add(relUpperPath); } } } // Add the conflict to the conflictingMods list, if files get overwritten: if (conflict.conflictingFiles.Count > 0) { conflictingMods.Add(conflict); } } } return(conflictingMods); }
/// <summary> /// Converts ManagedMod.ArchiveCompression and ManagedMod.ArchiveFormat to an Archive2.Preset. /// Automatically determines appropriate compression and format if needed. /// </summary> public static Archive2.Preset GetArchive2Preset(ManagedMod mod) { return(GetArchive2Preset(mod.ManagedFolderPath, mod.Format, mod.Compression)); }
public static ManagedMod Deserialize(XElement xmlMod, string GamePath) { if (xmlMod.Attribute("guid") == null) { throw new Exception("Invalid *.xml entry for mod."); } ManagedMod mod = new ManagedMod(GamePath, new Guid(xmlMod.Attribute("guid").Value)); if (xmlMod.Element("Title") == null) { throw new Exception("Invalid *.xml entry for mod."); } else { mod.Title = xmlMod.Element("Title").Value; } if (xmlMod.Element("Folder") != null) { mod.ManagedFolderName = xmlMod.Element("Folder").Value; } if (xmlMod.Element("Version") != null) { mod.Version = xmlMod.Element("Version").Value; } XElement xmlNexusMods = xmlMod.Element("NexusMods"); int modId; if (xmlNexusMods != null && xmlNexusMods.Attribute("id") != null && xmlNexusMods.Element("URL") != null) { if (xmlNexusMods.Attribute("id").TryParseInt(out modId)) { mod.ID = modId; } mod.URL = xmlNexusMods.Element("URL").Value; } XElement xmlDiskState = xmlMod.Element("DiskState"); if (xmlDiskState == null) { throw new Exception("Invalid *.xml entry for mod."); } XElement xmlCurrentDiskState = xmlDiskState.Element("Current"); if (xmlCurrentDiskState == null) { throw new Exception("Invalid *.xml entry for mod."); } if (xmlCurrentDiskState.Attribute("isDeployed") != null && xmlCurrentDiskState.Attribute("isDeployed").TryParseBool(out bool deployed)) { mod.Deployed = deployed; } if (xmlCurrentDiskState.Element("InstallationMethod") != null) { mod.PreviousMethod = GetMethod(xmlCurrentDiskState.Element("InstallationMethod").Value); } if (xmlCurrentDiskState.Element("ArchiveFormat") != null) { mod.CurrentFormat = GetFormat(xmlCurrentDiskState.Element("ArchiveFormat").Value); } if (xmlCurrentDiskState.Element("ArchiveCompression") != null) { mod.CurrentCompression = GetCompression(xmlCurrentDiskState.Element("ArchiveCompression").Value); } if (xmlCurrentDiskState.Element("ArchiveName") != null) { mod.CurrentArchiveName = xmlCurrentDiskState.Element("ArchiveName").Value; } if (xmlCurrentDiskState.Element("RootFolder") != null) { mod.CurrentRootFolder = xmlCurrentDiskState.Element("RootFolder").Value; } XElement xmlInstalledLooseFiles = xmlCurrentDiskState.Element("InstalledLooseFiles"); if (xmlInstalledLooseFiles != null) { foreach (XElement xmlFile in xmlInstalledLooseFiles.Descendants("File")) { if (xmlFile.Attribute("path") != null) { mod.LooseFiles.Add(xmlFile.Attribute("path").Value); } } } XElement xmlPendingDiskState = xmlDiskState.Element("Pending"); if (xmlPendingDiskState == null) { throw new Exception("Invalid *.xml entry for mod."); } if (xmlPendingDiskState.Attribute("isEnabled") != null && xmlPendingDiskState.Attribute("isEnabled").TryParseBool(out bool enabled)) { mod.Enabled = enabled; } if (xmlPendingDiskState.Element("InstallationMethod") != null) { mod.Method = GetMethod(xmlPendingDiskState.Element("InstallationMethod").Value); } if (xmlPendingDiskState.Element("ArchiveName") != null) { mod.ArchiveName = xmlPendingDiskState.Element("ArchiveName").Value; } if (xmlPendingDiskState.Element("ArchiveFormat") != null) { mod.Format = GetFormat(xmlPendingDiskState.Element("ArchiveFormat").Value); } if (xmlPendingDiskState.Element("ArchiveCompression") != null) { mod.Compression = GetCompression(xmlPendingDiskState.Element("ArchiveCompression").Value); } if (xmlPendingDiskState.Element("RootFolder") != null) { mod.RootFolder = xmlPendingDiskState.Element("RootFolder").Value; } XElement xmlFrozenDiskState = xmlDiskState.Element("FrozenData"); if (xmlFrozenDiskState == null) { throw new Exception("Invalid *.xml entry for mod."); } if (xmlFrozenDiskState.Attribute("isFrozen") != null && xmlFrozenDiskState.Attribute("isFrozen").TryParseBool(out bool frozen)) { mod.Frozen = frozen; } if (xmlFrozenDiskState.Attribute("freeze") != null && xmlFrozenDiskState.Attribute("freeze").TryParseBool(out bool freeze)) { mod.Freeze = freeze; } if (xmlFrozenDiskState.Element("ArchiveFormat") != null) { mod.FrozenFormat = GetFormat(xmlFrozenDiskState.Element("ArchiveFormat").Value); } if (xmlFrozenDiskState.Element("ArchiveCompression") != null) { mod.FrozenCompression = GetCompression(xmlFrozenDiskState.Element("ArchiveCompression").Value); } XElement xmlNotes = xmlMod.Element("Notes"); if (xmlNotes != null) { mod.Notes = xmlNotes.Value; } return(mod); }