/// <summary> /// Validates this mod can install against a game target with respect to the list of RequiredDLC. /// </summary> /// <param name="gameTarget">Target to validate against</param> /// <returns>List of missing DLC modules, or an empty list if none</returns> internal bool ValidateSingleOptionalRequiredDLCInstalled(GameTarget gameTarget) { if (gameTarget.Game != Game) { throw new Exception(@"Cannot validate a mod against a gametarget that is not for its game"); } if (Enumerable.Any <string>(OptionalSingleRequiredDLC)) { var requiredAnyDLC = Enumerable.Select <string, string>(OptionalSingleRequiredDLC, x => { if (Enum.TryParse(x, out ModJob.JobHeader parsedHeader) && ModJob.GetHeadersToDLCNamesMap(Game) .TryGetValue(parsedHeader, out var dlcname)) { return(dlcname); } return(x); }); var installedDLC = M3Directories.GetInstalledDLC(gameTarget); return(installedDLC.FirstOrDefault(x => requiredAnyDLC.Contains(x)) != null); } return(true); }
internal static bool IsOfficialDLCInstalled(ModJob.JobHeader header, GameTarget gameTarget) { if (header == ModJob.JobHeader.BALANCE_CHANGES) { return(true); //Don't check balance changes } if (header == ModJob.JobHeader.ME2_RCWMOD) { return(true); //Don't check } if (header == ModJob.JobHeader.ME1_CONFIG) { return(true); //Don't check } if (header == ModJob.JobHeader.BASEGAME) { return(true); //Don't check basegame } if (header == ModJob.JobHeader.CUSTOMDLC) { return(true); //Don't check custom dlc } if (header == ModJob.JobHeader.LOCALIZATION) { return(true); //Don't check localization } if (header == ModJob.JobHeader.TESTPATCH) { return(File.Exists(ME3Directory.GetTestPatchPath(gameTarget))); } else { return(MEDirectories.GetInstalledDLC(gameTarget).Contains(ModJob.GetHeadersToDLCNamesMap(gameTarget.Game)[header])); } }
/// <summary> /// Gets a list of all files that *may* be installed by a mod. /// </summary> /// <returns></returns> public List <string> GetAllInstallableFiles() { var list = new List <string>(); foreach (var job in InstallationJobs) { if (ModJob.IsVanillaJob(job, Game)) { // Basegame, Official DLC list.AddRange(job.FilesToInstall.Keys); } else if (job.Header == ModJob.JobHeader.CUSTOMDLC) { foreach (var cdlcDir in job.CustomDLCFolderMapping) { var dlcSourceDir = Path.Combine(ModPath, cdlcDir.Key); var files = Directory.GetFiles(dlcSourceDir, @"*", SearchOption.AllDirectories).Select(x => x.Substring(dlcSourceDir.Length + 1)); list.AddRange(files.Select(x => $@"{MEDirectories.GetDLCPath(Game, @"")}\{cdlcDir.Value}\{x}")); // do not localize } } foreach (var v in job.AlternateFiles) { if (v.Operation == AlternateFile.AltFileOperation.OP_INSTALL) { list.Add(v.ModFile); } if (v.Operation == AlternateFile.AltFileOperation.OP_APPLY_MULTILISTFILES) { foreach (var mlFile in v.MultiListSourceFiles) { list.Add(v.MultiListTargetPath + @"\" + mlFile); } } } foreach (var v in job.AlternateDLCs) { if (v.Operation == AlternateDLC.AltDLCOperation.OP_ADD_CUSTOMDLC || v.Operation == AlternateDLC.AltDLCOperation.OP_ADD_FOLDERFILES_TO_CUSTOMDLC) { var dlcSourceDir = Path.Combine(ModPath, v.AlternateDLCFolder); var files = Directory.GetFiles(dlcSourceDir, @"*", SearchOption.AllDirectories).Select(x => x.Substring(dlcSourceDir.Length + 1)); list.AddRange(files.Select(x => $@"{MEDirectories.GetDLCPath(Game, @"")}\{v.DestinationDLCFolder}\{x}")); //do not localize } if (v.Operation == AlternateDLC.AltDLCOperation.OP_ADD_MULTILISTFILES_TO_CUSTOMDLC) { foreach (var mlFile in v.MultiListSourceFiles) { list.Add(v.DestinationDLCFolder + @"\" + mlFile); } } } } return(list.Distinct().OrderBy(x => x).ToList()); }
public static void AddJob(ITexture2D tex2D, string ReplacingImage, int WhichGame, string pathBIOGame) { if (JobList.Count == 0) { Initialise(); } ModJob job = ModMaker.CreateTextureJob(tex2D, ReplacingImage, WhichGame, pathBIOGame); JobList.Add(job); }
private void ConvertModToLocalizationMod() { if (Window.GetWindow(this) is ModDescEditor ed) { ed.ConvertModToLocalizationMod(); } EditingMod.InstallationJobs.Clear(); TargetMod = ""; LocalizationJob = new ModJob(ModJob.JobHeader.LOCALIZATION, EditingMod); EditingMod.InstallationJobs.Add(LocalizationJob); }
public override void OnLoaded(object sender, RoutedEventArgs e) { if (HasLoaded) { return; } if (EditingMod.Game == MEGame.ME3) { BalanceChangesJob = EditingMod.GetJob(ModJob.JobHeader.BALANCE_CHANGES); BalanceChangesJob?.BuildParameterMap(EditingMod); } HasLoaded = true; }
public MixinManager() { MemoryAnalyzer.AddTrackedMemoryItem(@"Mixin Library Panel", new WeakReference(this)); DataContext = this; MixinHandler.LoadME3TweaksPackage(); AvailableOfficialMixins.ReplaceAll(MixinHandler.ME3TweaksPackageMixins.OrderBy(x => x.PatchName)); var backupPath = Utilities.GetGameBackupPath(Mod.MEGame.ME3); if (backupPath != null) { var dlcPath = MEDirectories.DLCPath(backupPath, Mod.MEGame.ME3); var headerTranslation = ModJob.GetHeadersToDLCNamesMap(Mod.MEGame.ME3); foreach (var mixin in AvailableOfficialMixins) { mixin.UIStatusChanging += MixinUIStatusChanging; if (mixin.TargetModule == ModJob.JobHeader.TESTPATCH) { string biogame = MEDirectories.BioGamePath(backupPath); var sfar = Path.Combine(biogame, @"Patches", @"PCConsole", @"Patch_001.sfar"); if (File.Exists(sfar)) { mixin.CanBeUsed = true; } } else if (mixin.TargetModule != ModJob.JobHeader.BASEGAME) { //DLC var resolvedPath = Path.Combine(dlcPath, headerTranslation[mixin.TargetModule]); if (Directory.Exists(resolvedPath)) { mixin.CanBeUsed = true; } } else { //BASEGAME mixin.CanBeUsed = true; } } } else { BottomLeftMessage = M3L.GetString(M3L.string_noGameBackupOfME3IsAvailableMixinsCannotBeUsedWithoutABackup); } ResetMixinsUIState(); LoadCommands(); InitializeComponent(); }
private void AddOfficialDLCJob() { var currentOfficialDLCJobs = EditingMod.InstallationJobs.Where(x => x.IsOfficialDLCJob(EditingMod.Game)).Select(x => x.Header).ToList(); var acceptableHeaders = ModJob.GetSupportedOfficialDLCHeaders(EditingMod.Game); var selectableOptions = acceptableHeaders.Except(currentOfficialDLCJobs).ToList(); var selection = DropdownSelectorDialog.GetSelection(Window.GetWindow(this), M3L.GetString(M3L.string_selectTask), selectableOptions, M3L.GetString(M3L.string_selectATaskHeader), M3L.GetString(M3L.string_chooser_selectOfficialDLCHeader)); if (selection is ModJob.JobHeader header) { ModJob job = new ModJob(header); EditingMod.InstallationJobs.Add(job); job.BuildParameterMap(EditingMod); OfficialDLCJobs.Add(job); } }
/// <summary> /// Create a texture ModJob from a tex2D with some pathing stuff. /// </summary> /// <param name="tex2D">Texture2D to build job from.</param> /// <param name="imgPath">Path of texture image to create job with.</param> /// <param name="WhichGame">Game to target.</param> /// <param name="pathBIOGame">Path to BIOGame of targeted game.</param> /// <returns>New ModJob based on provided image and Texture2D.</returns> public static ModJob CreateTextureJob(ITexture2D tex2D, string imgPath, int WhichGame, string pathBIOGame) { // KFreon: Get script string script = GenerateTextureScript(exec, tex2D.allPccs, tex2D.expIDs, tex2D.texName, WhichGame, pathBIOGame); ModJob job = new ModJob(); job.Script = script; // KFreon: Get image data using (FileStream stream = new FileStream(imgPath, FileMode.Open)) { FileInfo fs = new FileInfo(imgPath); byte[] buff = new byte[fs.Length]; stream.Read(buff, 0, buff.Length); job.data = buff; } job.Name = (tex2D.Mips > 1 ? "Upscale (with MIP's): " : "Upscale: ") + tex2D.texName; return job; }
/// <summary> /// Create a texture ModJob from a tex2D with some pathing stuff. /// </summary> /// <param name="tex2D">Texture2D to build job from.</param> /// <param name="imgPath">Path of texture image to create job with.</param> /// <param name="WhichGame">Game to target.</param> /// <param name="pathBIOGame">Path to BIOGame of targeted game.</param> /// <returns>New ModJob based on provided image and Texture2D.</returns> public static ModJob CreateTextureJob(ITexture2D tex2D, string imgPath, int WhichGame, string pathBIOGame) { // KFreon: Get script string script = GenerateTextureScript(exec, tex2D.allPccs, tex2D.expIDs, tex2D.texName, WhichGame, pathBIOGame); ModJob job = new ModJob(); job.Script = script; // KFreon: Get image data using (FileStream stream = new FileStream(imgPath, FileMode.Open)) { FileInfo fs = new FileInfo(imgPath); byte[] buff = new byte[fs.Length]; stream.Read(buff, 0, buff.Length); job.data = buff; } job.Name = (tex2D.Mips > 1 ? "Upscale (with MIP's): " : "Upscale: ") + tex2D.texName; return(job); }
/// <summary> /// Validates this mod can install against a game target with respect to the list of RequiredDLC. /// </summary> /// <param name="gameTarget">Target to validate against</param> /// <returns>List of missing DLC modules, or an empty list if none</returns> internal List <string> ValidateRequiredModulesAreInstalled(GameTarget gameTarget) { if (gameTarget.Game != Game) { throw new Exception(@"Cannot validate a mod against a gametarget that is not for its game"); } var requiredDLC = Enumerable.Select <string, string>(RequiredDLC, x => { if (Enum.TryParse(x, out ModJob.JobHeader parsedHeader) && ModJob.GetHeadersToDLCNamesMap(Game).TryGetValue(parsedHeader, out var dlcname)) { return(dlcname); } return(x); }); var installedDLC = M3Directories.GetInstalledDLC(gameTarget); return(requiredDLC.Except(installedDLC).ToList()); }
private void AddCustomDLC() { if (CustomDLCJob == null) { // Generate the job CustomDLCJob = new ModJob(ModJob.JobHeader.CUSTOMDLC); CustomDLCJob.BuildParameterMap(EditingMod); EditingMod.InstallationJobs.Add(CustomDLCJob); } var job = CustomDLCJob; CustomDLCJob = null; CustomDLCJob = job; // Rebind??/s var cdp = new MDCustomDLCParameter(); cdp.PropertyChanged += CustomDLCPropertyChanged; CustomDLCParameters.Add(cdp); //empty data }
private void ParseJob(XmlTextReader r, List<byte[]> dataList) { ModJob mj = new ModJob(); r.ReadToFollowing("job"); r.ReadToFollowing("type"); mj.type = Convert.ToInt32(r.ReadElementContentAsString()); int index, countbundles, counttocs; switch (mj.type) { case 0: case 2: r.ReadToFollowing("dataindex"); index = Convert.ToInt32(r.ReadElementContentAsString()); mj.data = dataList[index]; r.ReadToFollowing("respath"); mj.respath = r.ReadElementContentAsString(); r.ReadToFollowing("countbundles"); countbundles = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("counttocs"); counttocs = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("bundles"); mj.bundlePaths = new List<string>(); for (int i = 0; i < countbundles; i++) { r.ReadToFollowing("path"); mj.bundlePaths.Add(r.ReadElementContentAsString()); } r.ReadToFollowing("tocfiles"); mj.tocPaths = new List<string>(); for (int i = 0; i < counttocs; i++) { r.ReadToFollowing("path"); mj.tocPaths.Add(r.ReadElementContentAsString()); } break; case 1: r.ReadToFollowing("dataindex"); index = Convert.ToInt32(r.ReadElementContentAsString()); mj.data = dataList[index]; r.ReadToFollowing("respath"); mj.respath = r.ReadElementContentAsString(); r.ReadToFollowing("restype"); mj.restype = r.ReadElementContentAsString(); r.ReadToFollowing("countbundles"); countbundles = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("counttocs"); counttocs = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("bundles"); mj.bundlePaths = new List<string>(); for (int i = 0; i < countbundles; i++) { r.ReadToFollowing("path"); mj.bundlePaths.Add(r.ReadElementContentAsString()); } r.ReadToFollowing("tocfiles"); mj.tocPaths = new List<string>(); for (int i = 0; i < counttocs; i++) { r.ReadToFollowing("path"); mj.tocPaths.Add(r.ReadElementContentAsString()); } break; } jobs.Add(mj); }
private void AddBasegameTask() { BasegameJob = new ModJob(ModJob.JobHeader.BASEGAME); BasegameJob.BuildParameterMap(EditingMod); EditingMod.InstallationJobs.Add(BasegameJob); }
private void AddBalanceChangesJob() { BalanceChangesJob = new ModJob(ModJob.JobHeader.BALANCE_CHANGES, EditingMod); BalanceChangesJob.BuildParameterMap(EditingMod); EditingMod.InstallationJobs.Add(BalanceChangesJob); }
private void AddME1ConfigTask() { ModDir = ""; ConfigJob = new ModJob(ModJob.JobHeader.ME1_CONFIG, EditingMod); EditingMod.InstallationJobs.Add(ConfigJob); }
private void ParseJob(XmlTextReader r, List <byte[]> dataList) { ModJob mj = new ModJob(); r.ReadToFollowing("job"); r.ReadToFollowing("type"); mj.type = Convert.ToInt32(r.ReadElementContentAsString()); int index, countbundles, counttocs; switch (mj.type) { case 0: case 2: r.ReadToFollowing("dataindex"); index = Convert.ToInt32(r.ReadElementContentAsString()); mj.data = dataList[index]; r.ReadToFollowing("respath"); mj.respath = r.ReadElementContentAsString(); r.ReadToFollowing("countbundles"); countbundles = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("counttocs"); counttocs = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("bundles"); mj.bundlePaths = new List <string>(); for (int i = 0; i < countbundles; i++) { r.ReadToFollowing("path"); mj.bundlePaths.Add(r.ReadElementContentAsString()); } r.ReadToFollowing("tocfiles"); mj.tocPaths = new List <string>(); for (int i = 0; i < counttocs; i++) { r.ReadToFollowing("path"); mj.tocPaths.Add(r.ReadElementContentAsString()); } break; case 1: r.ReadToFollowing("dataindex"); index = Convert.ToInt32(r.ReadElementContentAsString()); mj.data = dataList[index]; r.ReadToFollowing("respath"); mj.respath = r.ReadElementContentAsString(); r.ReadToFollowing("restype"); mj.restype = r.ReadElementContentAsString(); r.ReadToFollowing("countbundles"); countbundles = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("counttocs"); counttocs = Convert.ToInt32(r.ReadElementContentAsString()); r.ReadToFollowing("bundles"); mj.bundlePaths = new List <string>(); for (int i = 0; i < countbundles; i++) { r.ReadToFollowing("path"); mj.bundlePaths.Add(r.ReadElementContentAsString()); } r.ReadToFollowing("tocfiles"); mj.tocPaths = new List <string>(); for (int i = 0; i < counttocs; i++) { r.ReadToFollowing("path"); mj.tocPaths.Add(r.ReadElementContentAsString()); } break; } jobs.Add(mj); }
private void buildInstallationQueue(ModJob job, CaseInsensitiveDictionary <InstallSourceFile> installationMapping, bool isSFAR) { CLog.Information(@"Building installation queue for " + job.Header, Settings.LogModInstallation); foreach (var entry in job.FilesToInstall) { //Key is destination, value is source file var destFile = entry.Key; var sourceFile = entry.Value; bool altApplied = false; foreach (var altFile in job.AlternateFiles.Where(x => x.IsSelected)) { Debug.WriteLine(@"Checking alt conditions for application: " + altFile.FriendlyName); if (altFile.Operation == AlternateFile.AltFileOperation.OP_NOTHING) { continue; //skip nothing } if (altFile.Operation == AlternateFile.AltFileOperation.OP_APPLY_MULTILISTFILES) { continue; //do not apply in the main loop. } if (altFile.Operation == AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES) { continue; //do not apply in the main loop. } if (altFile.ModFile.Equals(destFile, StringComparison.InvariantCultureIgnoreCase)) { //Alt applies to this file switch (altFile.Operation) { case AlternateFile.AltFileOperation.OP_NOINSTALL: CLog.Information($@"Not installing {destFile} for Alternate File {altFile.FriendlyName} due to operation OP_NOINSTALL", Settings.LogModInstallation); //we simply don't map as we just do a continue below. altApplied = true; break; case AlternateFile.AltFileOperation.OP_SUBSTITUTE: CLog.Information($@"Repointing {destFile} to {altFile.AltFile} for Alternate File {altFile.FriendlyName} due to operation OP_SUBSTITUTE", Settings.LogModInstallation); if (job.JobDirectory != null && (altFile.AltFile.StartsWith(job.JobDirectory) && job.Header == ModJob.JobHeader.CUSTOMDLC)) { installationMapping[destFile] = new InstallSourceFile(altFile.AltFile.Substring(job.JobDirectory.Length).TrimStart('/', '\\')) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } else { installationMapping[destFile] = new InstallSourceFile(altFile.AltFile) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } altApplied = true; break; case AlternateFile.AltFileOperation.OP_INSTALL: //same logic as substitute, just different logging. CLog.Information($@"Adding {sourceFile} to install (from {altFile.AltFile}) as part of Alternate File {altFile.FriendlyName} due to operation OP_INSTALL", Settings.LogModInstallation); if (job.JobDirectory != null && (altFile.AltFile.StartsWith(job.JobDirectory) && job.Header == ModJob.JobHeader.CUSTOMDLC)) { installationMapping[destFile] = new InstallSourceFile(altFile.AltFile.Substring(job.JobDirectory.Length).TrimStart('/', '\\')) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } else { installationMapping[destFile] = new InstallSourceFile(altFile.AltFile) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } altApplied = true; break; } break; } } if (altApplied) { continue; //no further processing for file } //installationMapping[sourceFile] = sourceFile; //Nothing different, just add to installation list installationMapping[destFile] = new InstallSourceFile(sourceFile); CLog.Information($@"Adding {job.Header} file to installation {(isSFAR ? @"SFAR" : @"unpacked")} queue: {entry.Value} -> {destFile}", Settings.LogModInstallation); //do not localize } //Apply autolist alternate files foreach (var altFile in job.AlternateFiles.Where(x => x.IsSelected && x.Operation == AlternateFile.AltFileOperation.OP_APPLY_MULTILISTFILES)) { foreach (var multifile in altFile.MultiListSourceFiles) { CLog.Information( $@"Adding multilist file {multifile} to install (from {altFile.MultiListRootPath}) as part of Alternate File {altFile.FriendlyName} due to operation OP_APPLY_MULTILISTFILES", Settings.LogModInstallation); string relativeSourcePath = altFile.MultiListRootPath + '\\' + multifile; var targetPath = altFile.MultiListTargetPath + '\\' + multifile; installationMapping[targetPath] = new InstallSourceFile(relativeSourcePath) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead //} //not sure if there should be an else case here. //else //{ // installationMapping[destFile] = new InstallSourceFile(multifile) // { // AltApplied = true, // IsFullRelativeFilePath = true // }; //use alternate file as key instead //} } } // Remove multilist noinstall files foreach (var altFile in job.AlternateFiles.Where(x => x.IsSelected && x.Operation == AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES)) { foreach (var multifile in altFile.MultiListSourceFiles) { CLog.Information( $@"Attempting to remove multilist file {multifile} from install (from {altFile.MultiListRootPath}) as part of Alternate File {altFile.FriendlyName} due to operation OP_NOINSTALL_MULTILISTFILES", Settings.LogModInstallation); string relativeSourcePath = altFile.MultiListRootPath + '\\' + multifile; var targetPath = altFile.MultiListTargetPath + '\\' + multifile; if (installationMapping.Remove(targetPath)) { CLog.Information($@" > Removed multilist file {targetPath} from installation", Settings.LogModInstallation); } else { Log.Warning($@"Failed to remove multilist file from installation queue as specified by altfile: {targetPath}, path not present in installation files"); } } } }
/// <summary> /// Builds the installation queues for the mod. Item 1 is the unpacked job mappings (per modjob) along with the list of custom dlc folders being installed. Item 2 is the list of modjobs, their sfar paths, and the list of source files to install for SFAR jobs. /// </summary> /// <param name="gameTarget"></param> /// <returns></returns> public (Dictionary <ModJob, (Dictionary <string, InstallSourceFile> unpackedJobMapping, List <string> dlcFoldersBeingInstalled)>, List <(ModJob job, string sfarPath, Dictionary <string, InstallSourceFile>)>) GetInstallationQueues(GameTarget gameTarget) { if (IsInArchive) { #if DEBUG if (Archive.IsDisposed()) { Debug.WriteLine(@">>> ARCHIVE IS DISPOSED"); } #endif if (File.Exists(ArchivePath) && (Archive == null || Archive.IsDisposed())) { Archive = new SevenZipExtractor(ArchivePath); //load archive file for inspection } else if (Archive != null && Archive.GetBackingStream() is SevenZip.ArchiveEmulationStreamProxy aesp && aesp.Source is MemoryStream ms) { var isExe = ArchivePath.EndsWith(@".exe", StringComparison.InvariantCultureIgnoreCase); Archive = isExe ? new SevenZipExtractor(ms, InArchiveFormat.Nsis) : new SevenZipExtractor(ms); MemoryAnalyzer.AddTrackedMemoryItem($@"Re-opened SVE archive for {ModName}", new WeakReference(Archive)); } } var gameDLCPath = M3Directories.GetDLCPath(gameTarget); var customDLCMapping = Enumerable.FirstOrDefault <ModJob>(InstallationJobs, x => x.Header == ModJob.JobHeader.CUSTOMDLC)?.CustomDLCFolderMapping; if (customDLCMapping != null) { //Make clone so original value is not modified customDLCMapping = new Dictionary <string, string>(customDLCMapping); //prevent altering the source object } var unpackedJobInstallationMapping = new Dictionary <ModJob, (Dictionary <string, InstallSourceFile> mapping, List <string> dlcFoldersBeingInstalled)>(); var sfarInstallationJobs = new List <(ModJob job, string sfarPath, Dictionary <string, InstallSourceFile> installationMapping)>(); foreach (var job in InstallationJobs) { Log.Information($@"Preprocessing installation job: {job.Header}"); var alternateFiles = Enumerable.Where <AlternateFile>(job.AlternateFiles, x => x.IsSelected && x.Operation != AlternateFile.AltFileOperation.OP_NOTHING && x.Operation != AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES).ToList(); var alternateDLC = Enumerable.Where <AlternateDLC>(job.AlternateDLCs, x => x.IsSelected).ToList(); if (job.Header == ModJob.JobHeader.CUSTOMDLC) { #region Installation: CustomDLC //Key = destination file, value = source file to install var installationMapping = new Dictionary <string, InstallSourceFile>(); unpackedJobInstallationMapping[job] = (installationMapping, new List <string>()); foreach (var altdlc in alternateDLC) { if (altdlc.Operation == AlternateDLC.AltDLCOperation.OP_ADD_CUSTOMDLC) { customDLCMapping[altdlc.AlternateDLCFolder] = altdlc.DestinationDLCFolder; } } foreach (var mapping in customDLCMapping) { //Mapping is done as DESTINATIONFILE = SOURCEFILE so you can override keys var source = FilesystemInterposer.PathCombine(IsInArchive, ModPath, mapping.Key); var target = Path.Combine(gameDLCPath, mapping.Value); //get list of all normal files we will install var allSourceDirFiles = FilesystemInterposer.DirectoryGetFiles(source, "*", SearchOption.AllDirectories, Archive).Select(x => x.Substring(ModPath.Length).TrimStart('\\')).ToList(); unpackedJobInstallationMapping[job].dlcFoldersBeingInstalled.Add(target); //loop over every file foreach (var sourceFile in allSourceDirFiles) { //Check against alt files bool altApplied = false; foreach (var altFile in alternateFiles) { if (altFile.ModFile.Equals(sourceFile, StringComparison.InvariantCultureIgnoreCase)) { //Alt applies to this file switch (altFile.Operation) { case AlternateFile.AltFileOperation.OP_NOINSTALL: CLog.Information($@"Not installing {sourceFile} for Alternate File {altFile.FriendlyName} due to operation OP_NOINSTALL", Settings.LogModInstallation); //we simply don't map as we just do a continue below. altApplied = true; break; case AlternateFile.AltFileOperation.OP_SUBSTITUTE: CLog.Information($@"Repointing {sourceFile} to {altFile.AltFile} for Alternate File {altFile.FriendlyName} due to operation OP_SUBSTITUTE", Settings.LogModInstallation); if (job.JobDirectory != null && altFile.AltFile.StartsWith((string)job.JobDirectory)) { installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile.Substring(job.JobDirectory.Length).TrimStart('/', '\\')) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } else { installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } altApplied = true; break; case AlternateFile.AltFileOperation.OP_INSTALL: //same logic as substitute, just different logging. CLog.Information($@"Adding {sourceFile} to install (from {altFile.AltFile}) as part of Alternate File {altFile.FriendlyName} due to operation OP_INSTALL", Settings.LogModInstallation); if (job.JobDirectory != null && altFile.AltFile.StartsWith((string)job.JobDirectory)) { installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile.Substring(job.JobDirectory.Length).TrimStart('/', '\\')) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } else { installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile) { AltApplied = true, IsFullRelativeFilePath = true }; //use alternate file as key instead } altApplied = true; break; } break; } } if (altApplied) { continue; //no further processing for file } var relativeDestStartIndex = sourceFile.IndexOf(mapping.Value); string destPath = sourceFile.Substring(relativeDestStartIndex); installationMapping[destPath] = new InstallSourceFile(sourceFile); //destination is mapped to source file that will replace it. } foreach (var altdlc in alternateDLC) { if (altdlc.Operation == AlternateDLC.AltDLCOperation.OP_ADD_FOLDERFILES_TO_CUSTOMDLC) { string alternatePathRoot = FilesystemInterposer.PathCombine(IsInArchive, ModPath, altdlc.AlternateDLCFolder); var filesToAdd = FilesystemInterposer.DirectoryGetFiles(alternatePathRoot, "*", SearchOption.AllDirectories, Archive).Select(x => x.Substring(ModPath.Length).TrimStart('\\')).ToList(); foreach (var fileToAdd in filesToAdd) { var destFile = Path.Combine(altdlc.DestinationDLCFolder, fileToAdd.Substring(altdlc.AlternateDLCFolder.Length).TrimStart('\\', '/')); CLog.Information($@"Adding extra CustomDLC file ({fileToAdd} => {destFile}) due to Alternate DLC {altdlc.FriendlyName}'s {altdlc.Operation}", Settings.LogModInstallation); installationMapping[destFile] = new InstallSourceFile(fileToAdd) { AltApplied = true }; } } else if (altdlc.Operation == AlternateDLC.AltDLCOperation.OP_ADD_MULTILISTFILES_TO_CUSTOMDLC) { string alternatePathRoot = FilesystemInterposer.PathCombine(IsInArchive, ModPath, altdlc.MultiListRootPath); foreach (var fileToAdd in altdlc.MultiListSourceFiles) { var sourceFile = FilesystemInterposer.PathCombine(IsInArchive, alternatePathRoot, fileToAdd).Substring(ModPath.Length).TrimStart('\\'); var destFile = Path.Combine(altdlc.DestinationDLCFolder, fileToAdd.TrimStart('\\', '/')); CLog.Information($@"Adding extra CustomDLC file (MultiList) ({sourceFile} => {destFile}) due to Alternate DLC {altdlc.FriendlyName}'s {altdlc.Operation}", Settings.LogModInstallation); installationMapping[destFile] = new InstallSourceFile(sourceFile) { AltApplied = true }; } } } // Process altfile removal of multilist, since it should be done last var fileRemoveAltFiles = Enumerable.Where <AlternateFile>(job.AlternateFiles, x => x.IsSelected && x.Operation == AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES); foreach (var altFile in fileRemoveAltFiles) { foreach (var multifile in altFile.MultiListSourceFiles) { CLog.Information($@"Attempting to remove multilist file {multifile} from install (from {altFile.MultiListTargetPath}) as part of Alternate File {altFile.FriendlyName} due to operation OP_NOINSTALL_MULTILISTFILES", Settings.LogModInstallation); string relativeSourcePath = altFile.MultiListRootPath + '\\' + multifile; var targetPath = altFile.MultiListTargetPath + '\\' + multifile; if (installationMapping.Remove(targetPath)) { CLog.Information($@" > Removed multilist file {targetPath} from installation", Settings.LogModInstallation); } } } } #endregion } else if (job.Header == ModJob.JobHeader.LOCALIZATION) { #region Installation: LOCALIZATION var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>(); unpackedJobInstallationMapping[job] = (installationMapping, new List <string>()); buildInstallationQueue(job, installationMapping, false); #endregion } else if (job.Header == ModJob.JobHeader.BASEGAME || job.Header == ModJob.JobHeader.BALANCE_CHANGES || job.Header == ModJob.JobHeader.ME1_CONFIG) { #region Installation: BASEGAME, BALANCE CHANGES, ME1 CONFIG var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>(); unpackedJobInstallationMapping[job] = (installationMapping, new List <string>()); buildInstallationQueue(job, installationMapping, false); #endregion } else if (Game == MEGame.ME3 && ModJob.ME3SupportedNonCustomDLCJobHeaders.Contains(job.Header)) //previous else if will catch BASEGAME { #region Installation: DLC Unpacked and SFAR (ME3 ONLY) if (M3Directories.IsOfficialDLCInstalled(job.Header, gameTarget)) { string sfarPath = job.Header == ModJob.JobHeader.TESTPATCH ? M3Directories.GetTestPatchSFARPath(gameTarget) : Path.Combine(gameDLCPath, ModJob.GetHeadersToDLCNamesMap(MEGame.ME3)[job.Header], @"CookedPCConsole", @"Default.sfar"); if (File.Exists(sfarPath)) { var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>(); if (new FileInfo(sfarPath).Length == 32) { //Unpacked unpackedJobInstallationMapping[job] = (installationMapping, new List <string>()); buildInstallationQueue(job, installationMapping, false); } else { //Packed //unpackedJobInstallationMapping[job] = installationMapping; buildInstallationQueue(job, installationMapping, true); sfarInstallationJobs.Add((job, sfarPath, installationMapping)); } } } else { Log.Warning($@"DLC not installed, skipping: {job.Header}"); } #endregion } else if (Game == MEGame.ME2 || Game == MEGame.ME1) { #region Installation: DLC Unpacked (ME1/ME2 ONLY) //Unpacked if (M3Directories.IsOfficialDLCInstalled(job.Header, gameTarget)) { var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>(); unpackedJobInstallationMapping[job] = (installationMapping, new List <string>()); buildInstallationQueue(job, installationMapping, false); } else { Log.Warning($@"DLC not installed, skipping: {job.Header}"); } #endregion } else { //?? Header throw new Exception(@"Unsupported installation job header! " + job.Header); } } return(unpackedJobInstallationMapping, sfarInstallationJobs); }