/// <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);
        }
示例#2
0
 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());
        }
示例#4
0
        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);
        }
示例#5
0
        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;
 }
示例#7
0
        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);
            }
        }
示例#9
0
        /// <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;
        }
示例#10
0
        /// <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
        }
示例#13
0
 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);
 }
示例#16
0
 private void AddME1ConfigTask()
 {
     ModDir    = "";
     ConfigJob = new ModJob(ModJob.JobHeader.ME1_CONFIG, EditingMod);
     EditingMod.InstallationJobs.Add(ConfigJob);
 }
示例#17
0
        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);
        }