/// <summary> /// Used in the deployment chain to deploy mods with the BundledBA2 method. /// </summary> private static void DeployBundledArchives(ManagedMods mods, bool freezeArchives = false, bool invalidateFrozenArchives = true) { LogFile.WriteLine($" Installing BundledBA2 mods..."); DeployArchiveList archives = new DeployArchiveList(mods.GamePath); // We want to use frozen archives but haven't invalidated them... // ... so just copy bundled archives if available: if (freezeArchives && !invalidateFrozenArchives) { CopyFrozenBundledArchives(mods.Resources, archives); return; } // Otherwise iterate over each enabled mod... foreach (ManagedMod mod in mods) { if (mod.Enabled && mod.Method == ManagedMod.DeploymentMethod.BundledBA2) { LogFile.WriteLine($" Copy files of mod '{mod.Title}' to temp folder..."); // ... copy it's files into temporary folders ... CopyFilesToTempSorted(mod, archives); mod.Deployed = true; mod.PreviousMethod = ManagedMod.DeploymentMethod.BundledBA2; } } // ... and pack those folders to archives. PackBundledArchives(mods.Resources, archives, freezeArchives); }
public static void RemoveAll(ManagedMods mods) { LogFile.WriteLine("Removing all installed mods"); // Delete bundled archives: DeployArchiveList deployArchives = new DeployArchiveList(mods.GamePath); foreach (DeployArchive deployArchive in deployArchives) { LogFile.WriteLine($" Removing {deployArchive.ArchiveName}"); if (File.Exists(deployArchive.GetArchivePath())) { File.Delete(deployArchive.GetArchivePath()); } mods.Resources.Remove(deployArchive.ArchiveName); } LogFile.WriteLine($" Deleting temporary folders"); deployArchives.DeleteTempFolder(); // Remove mods: foreach (ManagedMod mod in mods) { LogFile.WriteLine($" Removing mod {mod.Title}"); ModDeployment.Remove(mod, mods.Resources, mods.GamePath); } mods.Save(); }
/// <summary> /// Deletes all files of the mod and removes it from the list. /// Saves the xml file afterwards. /// </summary> public static void DeleteMod(ManagedMods mods, int index, Action <Progress> ProgressChanged = null) { ModActions.DeleteFiles(mods[index]); mods.RemoveAt(index); mods.Save(); ProgressChanged?.Invoke(Progress.Done("Mod deleted.")); }
/// <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> /// Freezes the mods. /// </summary> public static void Freeze(ManagedMods mods, IEnumerable <int> indices) { foreach (int index in indices) { ModActions.Freeze(mods[index]); } mods.Save(); }
/// <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> /// 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(); }
/// <summary> /// Deletes multiple mods and removes them from the list. /// Saves the xml file afterwards. /// </summary> public static void DeleteMods(ManagedMods mods, List <int> indices, Action <Progress> ProgressChanged = null) { indices = indices.OrderByDescending(i => i).ToList(); int fi = 0; int count = indices.Count(); foreach (int index in indices) { ProgressChanged?.Invoke(Progress.Ongoing($"Deleting mod {++fi} of {count}.", (float)(fi - 1) / (float)count)); ModActions.DeleteMod(mods, index); } ProgressChanged?.Invoke(Progress.Done($"{count} mods deleted.")); }
/// <summary> /// Unfreezes the mods. /// </summary> public static void Unfreeze(ManagedMods mods, IEnumerable <int> indices, Action <Progress> ProgressChanged = null) { int count = indices.Count(); int n = 1; foreach (int index in indices) { ModActions.Unfreeze(mods[index]); ProgressChanged?.Invoke(Progress.Ongoing($"Unfreezing {n} of {count} mod(s)...", (float)n++ / (float)count)); } mods.Save(); ProgressChanged?.Invoke(Progress.Done($"{count} mod(s) thawed.")); }
/// <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> /// Searches through each mod.LooseFiles entry to find if the file belongs to a mod. /// </summary> /// <param name="mods"></param> /// <param name="fullPath">Has to be a full path, not a relative path</param> /// <returns>true, if a mod has installed this file. false otherwise.</returns> private static bool DoesLooseFileBelongToMod(ManagedMods mods, string fullPath) { foreach (ManagedMod mod in mods) { if (mod.PreviousMethod == ManagedMod.DeploymentMethod.LooseFiles) { foreach (string relPath in mod.LooseFiles) { string installedPath = Path.Combine(mods.GamePath, mod.RootFolder, relPath); if (installedPath == fullPath) { return(true); } } } } return(false); }
/// <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> /// Generates and saves a legacy "manifest.xml" for backwards-compatibility. /// </summary> /// <param name="mods">Generate a legacy xml for these mods.</param> public static void GenerateLegacyXML(ManagedMods mods) { // I ported the old Serialize methods of the old Mod and old ManagedMods classes. // It now uses the new format, but generates an *.xml that is compatible with older versions. // Since there is now way more information saved: It uses the current disk state, not the pending one. (PreviousMethod, CurrentArchiveName, etc.) // In case the user wants to downgrade, they can. XDocument xmlDoc = new XDocument(); XElement xmlRoot = new XElement("Mods"); xmlRoot.Add(new XAttribute("doNotImport", true)); xmlDoc.Add(xmlRoot); xmlDoc.AddFirst(new XComment($"\n This file has been generated by v{Shared.VERSION} for backwards-compatibility.\n It not actually being used anymore.\n")); foreach (ManagedMod mod in mods) { XElement xmlMod = new XElement("Mod", new XAttribute("title", mod.Title), new XAttribute("enabled", mod.Deployed), new XAttribute("modFolder", mod.ManagedFolderName), new XAttribute("installType", GetLegacyInstallMethodName(mod))); if (mod.URL != "") { xmlMod.Add(new XAttribute("url", mod.URL)); } if (mod.Version != "") { xmlMod.Add(new XAttribute("version", mod.Version)); } if (mod.PreviousMethod == ManagedMod.DeploymentMethod.SeparateBA2 && mod.CurrentArchiveName != null) { xmlMod.Add(new XAttribute("archiveName", mod.CurrentArchiveName)); xmlMod.Add(new XAttribute("compression", GetLegacyArchiveCompressionName(mod))); xmlMod.Add(new XAttribute("format", GetLegacyArchiveFormatName(mod))); // We can't set the frozen flag anymore, because we store frozen archives differently now: //if (mod.Frozen) //xmlMod.Add(new XAttribute("frozen", mod.Frozen)); } if (mod.PreviousMethod == ManagedMod.DeploymentMethod.LooseFiles && mod.CurrentRootFolder != "") { xmlMod.Add(new XAttribute("root", mod.CurrentRootFolder)); } if (mod.PreviousMethod == ManagedMod.DeploymentMethod.LooseFiles && mod.Deployed) { foreach (String filePath in mod.LooseFiles) { xmlMod.Add(new XElement("File", new XAttribute("path", filePath))); } } xmlRoot.Add(xmlMod); } xmlDoc.Save(Path.Combine(mods.ModsPath, "manifest.xml")); }
/// <summary> /// Unfreezes the mod. /// </summary> public static void Unfreeze(ManagedMods mods, int index, Action <Progress> ProgressChanged = null) { ModActions.Unfreeze(mods[index]); mods.Save(); ProgressChanged?.Invoke(Progress.Done("Mod thawed.")); }
public static void Deploy(ManagedMods mods, Action <Progress> ProgressChanged, bool invalidateBundledFrozenArchives = true) { LogFile.WriteLine("\n\n"); LogFile.WriteTimeStamp(); LogFile.WriteLine($"Version {Shared.VERSION}, deploying..."); LogFile.WriteLine($"Game path: {mods.GamePath}"); // TODO: More descriptive ProgressChanged ProgressChanged?.Invoke(Progress.Indetermined("Deploying...")); // Check for conflicts: LogFile.WriteLine("Checking for conflicting archive names..."); List <ModHelpers.Conflict> conflicts = ModHelpers.GetConflictingArchiveNames(mods.Mods); if (conflicts.Count > 0) { LogFile.WriteLine("Conflicts found, abort."); foreach (ModHelpers.Conflict conflict in conflicts) { LogFile.WriteLine($" Conflict: {conflict.conflictText}"); } throw new DeploymentFailedException("Conflicting archive names."); } // Restore *.dll files: RestoreAddedDLLs(mods.GamePath); // Remove all currently deployed mods: ProgressChanged?.Invoke(Progress.Indetermined("Removing mods...")); ModDeployment.RemoveAll(mods); mods.Save(); // If mods are enabled: if (!mods.ModsDisabled) { LogFile.WriteLine("Installing mods..."); // Deploy all SeparateBA2 and Loose mods: foreach (ManagedMod mod in mods) { ProgressChanged?.Invoke(Progress.Indetermined($"Deploying {mod.Title}...")); if (mod.Enabled && Directory.Exists(mod.ManagedFolderPath) && !Utils.IsDirectoryEmpty(mod.ManagedFolderPath)) { switch (mod.Method) { case ManagedMod.DeploymentMethod.SeparateBA2: DeploySeparateArchive(mod, mods.Resources); mods.Save(); break; case ManagedMod.DeploymentMethod.LooseFiles: DeployLooseFiles(mods, mod, mods.GamePath); mods.Save(); break; } } } // Deploy all BundledBA2 mods: ProgressChanged?.Invoke(Progress.Indetermined($"Building bundled archives...")); ModDeployment.DeployBundledArchives(mods, IniFiles.Config.GetBool("Mods", "bFreezeBundledArchives", false), invalidateBundledFrozenArchives); mods.Save(); ProgressChanged?.Invoke(Progress.Done("Mods deployed.")); } else { ProgressChanged?.Invoke(Progress.Done("Mods removed.")); } LogFile.WriteLine("Deployment finished."); LogFile.WriteLine($"Resource list ({mods.Resources.Count} files): \"{mods.Resources}\""); }
/// <summary> /// Freezes the mod. /// </summary> public static void Freeze(ManagedMods mods, int index) { ModActions.Freeze(mods[index]); mods.Save(); }
/// <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> /// Looks through the resource lists in the *.ini and imports *.ba2 archives. /// </summary> public static void ImportInstalledMods(ManagedMods mods, Action <Progress> ProgressChanged = null) { // TODO: ProgressChanged for ImportInstalledMods ProgressChanged?.Invoke(Progress.Indetermined("Importing already installed mods...")); // Get all archives: ResourceList IndexFileList = ResourceList.GetResourceIndexFileList(); ResourceList Archive2List = ResourceList.GetResourceArchive2List(); /* * Prepare list: */ // Add all archives: List <string> installedMods = new List <string>(); installedMods.AddRange(IndexFileList); installedMods.AddRange(Archive2List); installedMods.AddRange(mods.Resources); // Remove bundled archives: installedMods = installedMods.FindAll(e => !e.ToLower().Contains("bundled")); // Remove currently managed archives: foreach (ManagedMod mod in mods) { if (mod.PreviousMethod == ManagedMod.DeploymentMethod.SeparateBA2) { installedMods.Remove(mod.CurrentArchiveName); } } // Ignore any game files ("SeventySix - *.ba2"): foreach (string archiveName in IndexFileList) { if (archiveName.Trim().ToLower().StartsWith("seventysix")) { installedMods.Remove(archiveName); } } foreach (string archiveName in Archive2List) { if (archiveName.Trim().ToLower().StartsWith("seventysix")) { installedMods.Remove(archiveName); } } foreach (string archiveName in mods.Resources) { if (archiveName.Trim().ToLower().StartsWith("seventysix")) { installedMods.Remove(archiveName); } } /* * Import installed mods: */ foreach (string archiveName in installedMods) { string path = Path.Combine(mods.GamePath, "Data", archiveName); if (File.Exists(path) && !archiveName.Trim().ToLower().StartsWith("seventysix")) { // Import archive: ModInstallations.InstallArchive(mods, path, true); File.Delete(path); // Remove from lists: IndexFileList.Remove(archiveName); Archive2List.Remove(archiveName); } } // Save *.ini files: IndexFileList.CommitToINI(); Archive2List.CommitToINI(); //IniFiles.Instance.SaveAll(); mods.Save(); }