/// <summary>
        /// Adds a file to the add/replace list of files to install. This will replace an existing file in the mapping if the destination path is the same.
        /// </summary>
        /// <param name="destRelativePath">Relative in-game path (from game root) to install file to.</param>
        /// <param name="sourceRelativePath">Relative (to mod root) path of new file to install</param>
        /// <param name="ignoreLoadErrors">Ignore checking if new file exists on disk</param>
        /// <param name="mod">Mod to parse against</param>
        /// <returns>string of failure reason. null if OK.</returns>
        internal string AddFileToInstall(string destRelativePath, string sourceRelativePath, Mod mod)
        {
            //Security check
            if (!checkExtension(sourceRelativePath, out string failReason))
            {
                return(failReason);
            }
            string checkingSourceFile;

            if (JobDirectory != null)
            {
                checkingSourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, JobDirectory, sourceRelativePath);
            }
            else
            {
                //root (legacy)
                checkingSourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, sourceRelativePath);
            }
            if (!FilesystemInterposer.FileExists(checkingSourceFile, mod.Archive))
            {
                return(M3L.GetString(M3L.string_interp_validation_modjob_replacementFileSpecifiedByJobDoesntExist, checkingSourceFile));
            }
            FilesToInstall[destRelativePath.Replace('/', '\\').TrimStart('\\')] = sourceRelativePath.Replace('/', '\\');
            return(null);
        }
Beispiel #2
0
        /// <summary>
        /// Adds a file to the add/replace list of files to install. This will replace an existing file in the mapping if the destination path is the same.
        /// </summary>
        /// <param name="destRelativePath">Relative in-game path (from game root) to install file to.</param>
        /// <param name="sourceRelativePath">Relative (to mod root) path of new file to install</param>
        /// <param name="ignoreLoadErrors">Ignore checking if new file exists on disk</param>
        /// <param name="mod">Mod to parse against</param>
        /// <returns>string of failure reason. null if OK.</returns>
        internal string AddFileToInstall(string destRelativePath, string sourceRelativePath, Mod mod)
        {
            //Security check
            if (!checkExtension(sourceRelativePath, out string failReason))
            {
                return(failReason);
            }
            string checkingSourceFile;

            if (JobDirectory != null)
            {
                checkingSourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, JobDirectory, sourceRelativePath);
            }
            else
            {
                //root (legacy)
                checkingSourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, sourceRelativePath);
            }
            if (!FilesystemInterposer.FileExists(checkingSourceFile, mod.Archive))
            {
                return($"Failed to add replacement file to mod job: {checkingSourceFile} does not exist but is specified by the job");
            }
            FilesToInstall[destRelativePath.Replace('/', '\\').TrimStart('\\')] = sourceRelativePath.Replace('/', '\\');
            return(null);
        }
        /// <summary>
        /// Gets all files referenced by this mod. This does not include moddessc.ini.
        /// </summary>
        /// <param name="archive">Archive, if this mod is in an archive.</param>
        /// <returns></returns>
        public List <string> GetAllRelativeReferences(SevenZipExtractor archive = null)
        {
            var references = new List <string>();

            //references.Add("moddesc.ini"); //Moddesc is implicitly referenced by the mod.
            //Replace or Add references
            foreach (var job in InstallationJobs)
            {
                foreach (var jobFile in job.FilesToInstall.Values)
                {
                    if (job.JobDirectory == @".")
                    {
                        references.Add(jobFile);
                    }
                    else
                    {
                        references.Add(job.JobDirectory + @"\" + jobFile);
                    }
                }
                foreach (var dlc in job.AlternateDLCs)
                {
                    if (dlc.HasRelativeFiles())
                    {
                        var files = FilesystemInterposer.DirectoryGetFiles(FilesystemInterposer.PathCombine(IsInArchive, ModPath, dlc.AlternateDLCFolder), "*", SearchOption.AllDirectories, archive).Select(x => IsInArchive ? x : x.Substring(ModPath.Length + 1)).ToList();
                        references.AddRange(files);
                    }
                }
                foreach (var file in job.AlternateFiles)
                {
                    if (file.HasRelativeFile())
                    {
                        if (IsInArchive)
                        {
                            references.Add(FilesystemInterposer.PathCombine(true, ModPath, file.AltFile));
                        }
                        else
                        {
                            references.Add(file.AltFile);
                        }
                    }
                }

                foreach (var customDLCmapping in job.CustomDLCFolderMapping)
                {
                    references.AddRange(FilesystemInterposer.DirectoryGetFiles(FilesystemInterposer.PathCombine(IsInArchive, ModPath, customDLCmapping.Key), "*", SearchOption.AllDirectories, archive).Select(x => IsInArchive ? x : x.Substring(ModPath.Length + 1)).ToList());
                }
            }
            references.AddRange(AdditionalDeploymentFiles);
            foreach (var additionalDeploymentDir in AdditionalDeploymentFolders)
            {
                references.AddRange(FilesystemInterposer.DirectoryGetFiles(FilesystemInterposer.PathCombine(IsInArchive, ModPath, additionalDeploymentDir), "*", SearchOption.AllDirectories, archive).Select(x => IsInArchive ? x : x.Substring(ModPath.Length + 1)).ToList());
            }
            return(references);
        }
        /// <summary>
        /// Adds a file to the add/replace list of files to install. This will not replace an existing file in the mapping if the destination path is the same, it will instead throw an error.
        /// </summary>
        /// <param name="destRelativePath">Relative in-game path (from game root) to install file to.</param>
        /// <param name="sourceRelativePath">Relative (to mod root) path of new file to install</param>
        /// <param name="mod">Mod to parse against</param>
        /// <returns>string of failure reason. null if OK.</returns>
        internal string AddAdditionalFileToInstall(string destRelativePath, string sourceRelativePath, Mod mod)
        {
            //Security check
            if (!checkExtension(sourceRelativePath, out string failReason))
            {
                return(failReason);
            }
            var checkingSourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, JobDirectory, sourceRelativePath);

            if (!FilesystemInterposer.FileExists(checkingSourceFile, mod.Archive))
            {
                return(M3L.GetString(M3L.string_interp_validation_modjob_additionalFileSpecifiedByJobDoesntExist, checkingSourceFile));
            }
            if (FilesToInstall.ContainsKey(destRelativePath))
            {
                return(M3L.GetString(M3L.string_interp_validation_modjob_additionalFileAlreadyMarkedForModification, destRelativePath));
            }
            FilesToInstall[destRelativePath.Replace('/', '\\').TrimStart('\\')] = sourceRelativePath.Replace('/', '\\');
            return(null);
        }
Beispiel #5
0
        /// <summary>
        /// Adds a file to the add/replace list of files to install. This will not replace an existing file in the mapping if the destination path is the same, it will instead throw an error.
        /// </summary>
        /// <param name="destRelativePath">Relative in-game path (from game root) to install file to.</param>
        /// <param name="sourceRelativePath">Relative (to mod root) path of new file to install</param>
        /// <param name="mod">Mod to parse against</param>
        /// <returns>string of failure reason. null if OK.</returns>
        internal string AddAdditionalFileToInstall(string destRelativePath, string sourceRelativePath, Mod mod)
        {
            //Security check
            if (!checkExtension(sourceRelativePath, out string failReason))
            {
                return(failReason);
            }
            var checkingSourceFile = FilesystemInterposer.PathCombine(mod.IsInArchive, mod.ModPath, JobDirectory, sourceRelativePath);

            if (!FilesystemInterposer.FileExists(checkingSourceFile, mod.Archive))
            {
                return($"Failed to add additional file to mod job: {checkingSourceFile} does not exist but is specified by the job");
            }
            if (FilesToInstall.ContainsKey(destRelativePath))
            {
                return($"Failed to add additional file to mod job: {destRelativePath} already is marked for modification. Files that are in the addfiles descriptor cannot overlap each other or replacement files.");
            }
            FilesToInstall[destRelativePath.Replace('/', '\\').TrimStart('\\')] = sourceRelativePath.Replace('/', '\\');
            return(null);
        }
        /// <summary>
        /// Gets all files referenced by this mod. This does not include moddessc.ini by default
        /// </summary>
        /// <param name="includeModdesc">Include moddesc.ini in the results</param>
        /// <param name="archive">Archive, if this mod is in an archive.</param>
        /// <returns></returns>
        public List <string> GetAllRelativeReferences(bool includeModdesc = false, SevenZipExtractor archive = null)
        {
            var references = new List <string>();

            //references.Add("moddesc.ini"); //Moddesc is implicitly referenced by the mod.
            //Replace or Add references
            foreach (var job in InstallationJobs)
            {
                foreach (var jobFile in job.FilesToInstall.Values)
                {
                    if (job.JobDirectory == @"." || job.JobDirectory == null)
                    {
                        references.Add(jobFile);
                    }
                    else
                    {
                        references.Add(job.JobDirectory + @"\" + jobFile);
                    }
                }
                foreach (var dlc in job.AlternateDLCs)
                {
                    if (dlc.HasRelativeFiles())
                    {
                        if (dlc.AlternateDLCFolder != null)
                        {
                            var files = FilesystemInterposer.DirectoryGetFiles(FilesystemInterposer.PathCombine(IsInArchive, ModPath, dlc.AlternateDLCFolder), "*", SearchOption.AllDirectories, archive).Select(x => (IsInArchive && ModPath.Length == 0) ? x : x.Substring(ModPath.Length + 1)).ToList();
                            references.AddRange(files);
                        }
                        else if (dlc.MultiListSourceFiles != null)
                        {
                            foreach (var mf in dlc.MultiListSourceFiles)
                            {
                                var relpath = Path.Combine(ModPath, dlc.MultiListRootPath, mf).Substring(ModPath.Length + 1);
                                references.Add(relpath);
                            }
                        }
                    }
                }
                foreach (var file in job.AlternateFiles)
                {
                    if (file.HasRelativeFile())
                    {
                        if (file.AltFile != null)
                        {
                            //Commented out: AltFile should be direct path to file from mod root, we should only put in relative path
                            //if (IsInArchive)
                            //{
                            //    references.Add(FilesystemInterposer.PathCombine(true, ModPath, file.AltFile));
                            //}
                            //else
                            //{
                            references.Add(file.AltFile);
                            //}
                        }
                        else if (file.MultiListSourceFiles != null)
                        {
                            foreach (var mf in file.MultiListSourceFiles)
                            {
                                var relPath = FilesystemInterposer.PathCombine(IsInArchive, ModPath, file.MultiListRootPath, mf);
                                //Should this be different from above AltFile?
                                if (IsInArchive)
                                {
                                    references.Add(relPath);
                                }
                                else
                                {
                                    references.Add(relPath.Substring(ModPath.Length + 1));
                                }
                            }
                        }
                    }
                }

                foreach (var customDLCmapping in job.CustomDLCFolderMapping)
                {
                    references.AddRange(FilesystemInterposer.DirectoryGetFiles(FilesystemInterposer.PathCombine(IsInArchive, ModPath, customDLCmapping.Key), "*", SearchOption.AllDirectories, archive).Select(x => (IsInArchive && ModPath.Length == 0) ? x : x.Substring(ModPath.Length + 1)).ToList());
                }
            }
            references.AddRange(AdditionalDeploymentFiles);
            foreach (var additionalDeploymentDir in AdditionalDeploymentFolders)
            {
                references.AddRange(FilesystemInterposer.DirectoryGetFiles(FilesystemInterposer.PathCombine(IsInArchive, ModPath, additionalDeploymentDir), "*", SearchOption.AllDirectories, archive).Select(x => (IsInArchive && ModPath.Length == 0) ? x : x.Substring(ModPath.Length + 1)).ToList());
            }
            if (includeModdesc && GetJob(ModJob.JobHeader.ME2_RCWMOD) == null)
            {
                references.Add(ModDescPath.Substring(ModPath.Length).TrimStart('/', '\\'));
                //references.Add(ModDescPath.TrimStart('/', '\\'));
            }
            return(references.Distinct(StringComparer.InvariantCultureIgnoreCase).ToList());
        }
        public (Dictionary <ModJob, (Dictionary <string, string> unpackedJobMapping, List <string> dlcFoldersBeingInstalled)>, List <(ModJob job, string sfarPath, Dictionary <string, string>)>) GetInstallationQueues(GameTarget gameTarget)
        {
            if (IsInArchive)
            {
                Archive = new SevenZipExtractor(ArchivePath);              //load archive file for inspection
            }
            var gameDLCPath      = MEDirectories.DLCPath(gameTarget);
            var customDLCMapping = InstallationJobs.FirstOrDefault(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, string> mapping, List <string> dlcFoldersBeingInstalled)>();
            var sfarInstallationJobs           = new List <(ModJob job, string sfarPath, Dictionary <string, string> installationMapping)>();

            foreach (var job in InstallationJobs)
            {
                Log.Information($"Preprocessing installation job: {job.Header}");
                var alternateFiles = job.AlternateFiles.Where(x => x.IsSelected).ToList();
                var alternateDLC   = job.AlternateDLCs.Where(x => x.IsSelected).ToList();
                if (job.Header == ModJob.JobHeader.CUSTOMDLC)
                {
                    #region Installation: CustomDLC
                    var installationMapping = new Dictionary <string, string>();
                    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 = Directory.GetFiles(source, "*", SearchOption.AllDirectories).Select(x => x.Substring(ModPath.Length)).ToList();
                        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)
                            {
                                //todo: Support wildcards if OP_NOINSTALL
                                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);
                                        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);
                                        installationMapping[altFile.AltFile] = sourceFile;     //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] = 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);
                                //Todo: Change to Filesystem Interposer to support installation from archives
                                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] = fileToAdd;
                                }
                            }
                        }

                        //Log.Information($"Copying CustomDLC to target: {source} -> {target}");
                        //CopyDir.CopyFiles_ProgressBar(installatnionMapping, FileInstalledCallback);
                        //Log.Information($"Installed CustomDLC {mapping.Value}");
                    }

                    #endregion
                }
                else if (job.Header == ModJob.JobHeader.ME2_COALESCED)
                {
                }
                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 Dictionary <string, string>();
                    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 (MEDirectories.IsOfficialDLCInstalled(job.Header, gameTarget))
                    {
                        Debug.WriteLine("Building installation queue for header: " + job.Header);
                        string sfarPath = job.Header == ModJob.JobHeader.TESTPATCH ? Utilities.GetTestPatchPath(gameTarget) : Path.Combine(gameDLCPath, ModJob.GetHeadersToDLCNamesMap(MEGame.ME3)[job.Header], "CookedPCConsole", "Default.sfar");


                        if (File.Exists(sfarPath))
                        {
                            var installationMapping = new Dictionary <string, string>();
                            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 (MEDirectories.IsOfficialDLCInstalled(job.Header, gameTarget))
                    {
                        var installationMapping = new Dictionary <string, string>();
                        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);
        }
Beispiel #8
0
        public void ExtractFromArchive(string archivePath, string outputFolderPath, bool compressPackages,
                                       Action <string> updateTextCallback = null, Action <DetailedProgressEventArgs> extractingCallback = null, Action <string, int, int> compressedPackageCallback = null,
                                       bool testRun = false)
        {
            if (!IsInArchive)
            {
                throw new Exception(@"Cannot extract a mod that is not part of an archive.");
            }
            compressPackages &= Game == MEGame.ME3; //ME3 ONLY FOR NOW
            var isExe       = archivePath.EndsWith(@".exe", StringComparison.InvariantCultureIgnoreCase);
            var archiveFile = isExe ? new SevenZipExtractor(archivePath, InArchiveFormat.Nsis) : new SevenZipExtractor(archivePath);

            using (archiveFile)
            {
                var fileIndicesToExtract = new List <int>();
                var referencedFiles      = GetAllRelativeReferences(!IsVirtualized, archiveFile);
                if (isExe)
                {
                    //remap to mod root. Not entirely sure if this needs to be done for sub mods?
                    referencedFiles = referencedFiles.Select(x => FilesystemInterposer.PathCombine(IsInArchive, ModPath, x)).ToList(); //remap to in-archive paths so they match entry paths
                }
                foreach (var info in archiveFile.ArchiveFileData)
                {
                    if (!info.IsDirectory && (ModPath == "" || info.FileName.Contains(ModPath)))
                    {
                        var relativedName = isExe ? info.FileName : info.FileName.Substring(ModPath.Length).TrimStart('\\');
                        if (referencedFiles.Contains(relativedName))
                        {
                            Log.Information(@"Adding file to extraction list: " + info.FileName);
                            fileIndicesToExtract.Add(info.Index);
                        }
                    }
                }
                #region old

                /*
                 * bool fileAdded = false;
                 * //moddesc.ini
                 * if (info.FileName == ModDescPath)
                 * {
                 * //Debug.WriteLine("Add file to extraction list: " + info.FileName);
                 * fileIndicesToExtract.Add(info.Index);
                 * continue;
                 * }
                 *
                 * //Check each job
                 * foreach (ModJob job in InstallationJobs)
                 * {
                 * if (job.Header == ModJob.JobHeader.CUSTOMDLC)
                 * {
                 #region Extract Custom DLC
                 *  foreach (var localCustomDLCFolder in job.CustomDLCFolderMapping.Keys)
                 *  {
                 *      if (info.FileName.StartsWith(FilesystemInterposer.PathCombine(IsInArchive, ModPath, localCustomDLCFolder)))
                 *      {
                 *          //Debug.WriteLine("Add file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 *  //Alternate files
                 *  foreach (var alt in job.AlternateFiles)
                 *  {
                 *      if (alt.AltFile != null && info.FileName.Equals(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AltFile), StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add alternate file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 *  //Alternate DLC
                 *  foreach (var alt in job.AlternateDLCs)
                 *  {
                 *      if (info.FileName.StartsWith(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AlternateDLCFolder), StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add alternate dlc file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 #endregion
                 * }
                 * else
                 * {
                 #region Official headers
                 *
                 *  foreach (var inSubDirFile in job.FilesToInstall.Values)
                 *  {
                 *      var inArchivePath = FilesystemInterposer.PathCombine(IsInArchive, ModPath, job.JobDirectory, inSubDirFile); //keep relative if unpacked mod, otherwise use full in-archive path for extraction
                 *      if (info.FileName.Equals(inArchivePath, StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *  //Alternate files
                 *  foreach (var alt in job.AlternateFiles)
                 *  {
                 *      if (alt.AltFile != null && info.FileName.Equals(FilesystemInterposer.PathCombine(IsInArchive, ModPath, alt.AltFile), StringComparison.InvariantCultureIgnoreCase))
                 *      {
                 *          //Debug.WriteLine("Add alternate file to extraction list: " + info.FileName);
                 *          fileIndicesToExtract.Add(info.Index);
                 *          fileAdded = true;
                 *          break;
                 *      }
                 *  }
                 *
                 *  if (fileAdded) break;
                 *
                 #endregion
                 * }
                 * }
                 * }*/
                #endregion
                archiveFile.Progressing += (sender, args) => { extractingCallback?.Invoke(args); };
                string outputFilePathMapping(ArchiveFileInfo entryInfo)
                {
                    Log.Information(@"Mapping extraction target for " + entryInfo.FileName);

                    string entryPath = entryInfo.FileName;

                    if (ExeExtractionTransform != null && ExeExtractionTransform.PatchRedirects.Any(x => x.index == entryInfo.Index))
                    {
                        Log.Information(@"Extracting vpatch file at index " + entryInfo.Index);
                        return(Path.Combine(Utilities.GetVPatchRedirectsFolder(), ExeExtractionTransform.PatchRedirects.First(x => x.index == entryInfo.Index).outfile));
                    }

                    if (ExeExtractionTransform != null && ExeExtractionTransform.NoExtractIndexes.Any(x => x == entryInfo.Index))
                    {
                        Log.Information(@"Extracting file to trash (not used): " + entryPath);
                        return(Path.Combine(Utilities.GetTempPath(), @"Trash", @"trashfile"));
                    }

                    if (ExeExtractionTransform != null && ExeExtractionTransform.AlternateRedirects.Any(x => x.index == entryInfo.Index))
                    {
                        var outfile = ExeExtractionTransform.AlternateRedirects.First(x => x.index == entryInfo.Index).outfile;
                        Log.Information($@"Extracting file with redirection: {entryPath} {outfile}");
                        return(Path.Combine(outputFolderPath, outfile));
                    }

                    //Archive path might start with a \. Substring may return value that start with a \
                    var subModPath = entryPath /*.TrimStart('\\')*/.Substring(ModPath.Length).TrimStart('\\');
                    var path       = Path.Combine(outputFolderPath, subModPath);


                    //Debug.WriteLine("remapping output: " + entryPath + " -> " + path);
                    return(path);
                }

                if (compressPackages)
                {
                    compressionQueue = new BlockingCollection <string>();
                }

                int numberOfPackagesToCompress = referencedFiles.Count(x => x.RepresentsPackageFilePath());
                int compressedPackageCount     = 0;
                NamedBackgroundWorker compressionThread;
                if (compressPackages)
                {
                    compressionThread         = new NamedBackgroundWorker(@"ImportingCompressionThread");
                    compressionThread.DoWork += (a, b) =>
                    {
                        try
                        {
                            while (true)
                            {
                                var package = compressionQueue.Take();
                                //updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)));
                                var p = MEPackageHandler.OpenMEPackage(package);
                                //Check if any compressed textures.
                                bool shouldNotCompress = false;
                                foreach (var texture in p.Exports.Where(x => x.IsTexture()))
                                {
                                    var storageType = Texture2D.GetTopMipStorageType(texture);
                                    shouldNotCompress |= storageType == ME3Explorer.Unreal.StorageTypes.pccLZO || storageType == ME3Explorer.Unreal.StorageTypes.pccZlib;
                                    if (!shouldNotCompress)
                                    {
                                        break;
                                    }
                                }

                                if (!shouldNotCompress)
                                {
                                    compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress);
                                    Log.Information(@"Compressing package: " + package);
                                    p.save(true);
                                }
                                else
                                {
                                    Log.Information(@"Not compressing package due to file containing compressed textures: " + package);
                                }


                                Interlocked.Increment(ref compressedPackageCount);
                                compressedPackageCallback?.Invoke(M3L.GetString(M3L.string_interp_compressedX, Path.GetFileName(package)), compressedPackageCount, numberOfPackagesToCompress);
                            }
                        }
                        catch (InvalidOperationException)
                        {
                            //Done.
                            lock (compressionCompletedSignaler)
                            {
                                Monitor.Pulse(compressionCompletedSignaler);
                            }
                        }
                    };
                    compressionThread.RunWorkerAsync();
                }
                archiveFile.FileExtractionFinished += (sender, args) =>
                {
                    if (compressPackages)
                    {
                        var fToCompress = outputFilePathMapping(args.FileInfo);
                        if (fToCompress.RepresentsPackageFilePath())
                        {
                            //Debug.WriteLine("Adding to blocking queue");
                            compressionQueue.TryAdd(fToCompress);
                        }
                    }
                };

                if (!testRun)
                {
                    Log.Information(@"Extracting files...");
                    archiveFile.ExtractFiles(outputFolderPath, outputFilePathMapping, fileIndicesToExtract.ToArray());
                }
                else
                {
                    //test run mode
                    if (fileIndicesToExtract.Count != referencedFiles.Count)
                    {
                        throw new Exception("The amount of referenced files does not match the amount of files that are going to be extracted!");
                    }
                }
                Log.Information(@"File extraction completed.");


                compressionQueue?.CompleteAdding();
                if (compressPackages && numberOfPackagesToCompress > 0 && numberOfPackagesToCompress > compressedPackageCount)
                {
                    Log.Information(@"Waiting for compression of packages to complete.");
                    while (!compressionQueue.IsCompleted)
                    {
                        lock (compressionCompletedSignaler)
                        {
                            Monitor.Wait(compressionCompletedSignaler);
                        }
                    }

                    Log.Information(@"Package compression has completed.");
                }
                ModPath = outputFolderPath;
                if (IsVirtualized)
                {
                    var parser = new IniDataParser().Parse(VirtualizedIniText);
                    parser[@"ModInfo"][@"modver"] = ModVersionString; //In event relay service resolved this
                    if (!testRun)
                    {
                        File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), parser.ToString());
                    }
                    IsVirtualized = false; //no longer virtualized
                }

                if (ExeExtractionTransform != null)
                {
                    if (ExeExtractionTransform.VPatches.Any())
                    {
                        var vpat = Utilities.GetCachedExecutablePath(@"vpat.exe");
                        if (!testRun)
                        {
                            Utilities.ExtractInternalFile(@"MassEffectModManagerCore.modmanager.executables.vpat.exe", vpat, true);
                        }
                        //Handle VPatching
                        foreach (var transform in ExeExtractionTransform.VPatches)
                        {
                            var patchfile  = Path.Combine(Utilities.GetVPatchRedirectsFolder(), transform.patchfile);
                            var inputfile  = Path.Combine(ModPath, transform.inputfile);
                            var outputfile = Path.Combine(ModPath, transform.outputfile);

                            var args = $"\"{patchfile}\" \"{inputfile}\" \"{outputfile}\""; //do not localize
                            if (!testRun)
                            {
                                Directory.CreateDirectory(Directory.GetParent(outputfile).FullName); //ensure output directory exists as vpatch will not make one.
                            }
                            Log.Information($@"VPatching file into alternate: {inputfile} to {outputfile}");
                            updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_vPatchingIntoAlternate, Path.GetFileName(inputfile)));
                            if (!testRun)
                            {
                                Utilities.RunProcess(vpat, args, true, false, false, true);
                            }
                        }
                    }

                    //Handle copyfile
                    foreach (var copyfile in ExeExtractionTransform.CopyFiles)
                    {
                        string srcfile  = Path.Combine(ModPath, copyfile.inputfile);
                        string destfile = Path.Combine(ModPath, copyfile.outputfile);
                        Log.Information($@"Applying transform copyfile: {srcfile} -> {destfile}");
                        if (!testRun)
                        {
                            File.Copy(srcfile, destfile, true);
                        }
                    }

                    if (ExeExtractionTransform.PostTransformModdesc != null)
                    {
                        //fetch online moddesc for this mod.
                        Log.Information(@"Fetching post-transform third party moddesc.");
                        var moddesc = OnlineContent.FetchThirdPartyModdesc(ExeExtractionTransform.PostTransformModdesc);
                        if (!testRun)
                        {
                            File.WriteAllText(Path.Combine(ModPath, @"moddesc.ini"), moddesc);
                        }
                    }
                }

                //int packagesCompressed = 0;
                //if (compressPackages)
                //{
                //    var packages = Utilities.GetPackagesInDirectory(ModPath, true);
                //    extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0));
                //    foreach (var package in packages)
                //    {
                //        updateTextCallback?.Invoke(M3L.GetString(M3L.string_interp_compressingX, Path.GetFileName(package)));
                //        Log.Information("Compressing package: " + package);
                //        var p = MEPackageHandler.OpenMEPackage(package);
                //        p.save(true);

                //        packagesCompressed++;
                //        extractingCallback?.Invoke(new ProgressEventArgs((byte)(packagesCompressed * 100.0 / packages.Count), 0));
                //    }
                //}
            }
        }
Beispiel #9
0
        /// <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)
            {
                Archive = new SevenZipExtractor(ArchivePath);              //load archive file for inspection
            }
            var gameDLCPath      = MEDirectories.DLCPath(gameTarget);
            var customDLCMapping = InstallationJobs.FirstOrDefault(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 = job.AlternateFiles.Where(x => x.IsSelected && x.Operation != AlternateFile.AltFileOperation.OP_NOTHING &&
                                                              x.Operation != AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES).ToList();
                var alternateDLC = job.AlternateDLCs.Where(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(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(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 = job.AlternateFiles.Where(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 (MEDirectories.IsOfficialDLCInstalled(job.Header, gameTarget))
                    {
                        string sfarPath = job.Header == ModJob.JobHeader.TESTPATCH ? ME3Directory.GetTestPatchPath(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 (MEDirectories.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);
        }