private void LoadME2FilesList() { var me2files = new List <BackupFile>(); var bup = Utilities.GetGameBackupPath(Mod.MEGame.ME2); if (bup != null) { var target = new GameTarget(Mod.MEGame.ME2, bup, false); var cookedPath = MEDirectories.CookedPath(target); foreach (var f in Extensions.GetFiles(cookedPath, @"\.pcc|\.tfc|\.afc|\.bin|\.tlk", SearchOption.AllDirectories)) { me2files.Add(new BackupFile(@"BASEGAME", Path.GetFileName(f))); } var dlcDir = MEDirectories.DLCPath(target); var officialDLC = VanillaDatabaseService.GetInstalledOfficialDLC(target); foreach (var v in officialDLC) { var cookedDLCPath = Path.Combine(dlcDir, v, @"CookedPC"); if (Directory.Exists(cookedDLCPath)) { foreach (var f in Directory.EnumerateFiles(cookedDLCPath, @"*.pcc", SearchOption.TopDirectoryOnly)) { me2files.Add(new BackupFile(v, Path.GetFileName(f))); } } } } Application.Current.Dispatcher.Invoke(delegate { ME2Files.ReplaceAll(me2files); }); Debug.WriteLine(@"Num ME2 files: " + ME2Files.Count); }
public void PopulateDLCMods(Func <InstalledDLCMod, bool> deleteConfirmationCallback, Action notifyDeleted) { UIInstalledDLCMods.ClearEx(); var dlcDir = MEDirectories.DLCPath(this); var installedMods = MEDirectories.GetInstalledDLC(this).Where(x => !MEDirectories.OfficialDLC(Game).Contains(x, StringComparer.InvariantCultureIgnoreCase)).Select(x => new InstalledDLCMod(Path.Combine(dlcDir, x), Game, deleteConfirmationCallback, notifyDeleted)).ToList(); UIInstalledDLCMods.AddRange(installedMods); }
public void PopulateDLCMods(bool includeDisabled, Func <InstalledDLCMod, bool> deleteConfirmationCallback = null, Action notifyDeleted = null, bool modNamePrefersTPMI = false) { var dlcDir = MEDirectories.DLCPath(this); var installedMods = MEDirectories.GetInstalledDLC(this, includeDisabled).Where(x => !MEDirectories.OfficialDLC(Game).Contains(x.TrimStart('x'), StringComparer.InvariantCultureIgnoreCase)); //Must run on UI thread Application.Current.Dispatcher.Invoke(delegate { UIInstalledDLCMods.ClearEx(); UIInstalledDLCMods.AddRange(installedMods.Select(x => new InstalledDLCMod(Path.Combine(dlcDir, x), Game, deleteConfirmationCallback, notifyDeleted, modNamePrefersTPMI)).ToList().OrderBy(x => x.ModName)); }); }
private void LoadME3FilesList() { var me3files = new List <BackupFile>(); var bup = BackupService.GetGameBackupPath(Mod.MEGame.ME3); if (bup != null) { var target = new GameTarget(Mod.MEGame.ME3, bup, false); var cookedPath = MEDirectories.CookedPath(target); foreach (var f in Extensions.GetFiles(cookedPath, @"\.pcc|\.tfc|\.afc|\.bin|\.tlk", SearchOption.AllDirectories)) { me3files.Add(new BackupFile(@"BASEGAME", Path.GetFileName(f))); } me3files.Sort(); //sort basegame var dlcDir = MEDirectories.DLCPath(target); var officialDLC = VanillaDatabaseService.GetInstalledOfficialDLC(target); foreach (var v in officialDLC) { var sfarPath = Path.Combine(dlcDir, v, @"CookedPCConsole", @"Default.sfar"); if (File.Exists(sfarPath)) { var filesToAdd = new List <BackupFile>(); DLCPackage dlc = new DLCPackage(sfarPath); foreach (var f in dlc.Files) { filesToAdd.Add(new BackupFile(v, Path.GetFileName(f.FileName))); } filesToAdd.Sort(); me3files.AddRange(filesToAdd); } } //TESTPATCH var tpPath = ME3Directory.GetTestPatchPath(target); if (File.Exists(tpPath)) { var filesToAdd = new List <BackupFile>(); DLCPackage dlc = new DLCPackage(tpPath); foreach (var f in dlc.Files) { filesToAdd.Add(new BackupFile(@"TESTPATCH", Path.GetFileName(f.FileName))); } filesToAdd.Sort(); me3files.AddRange(filesToAdd); } } Application.Current.Dispatcher.Invoke(delegate { ME3Files.ReplaceAll(me3files); }); Debug.WriteLine(@"Num ME3 files: " + ME3Files.Count); }
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 ToggleDLC() { try { var dlcFPath = MEDirectories.DLCPath(target); var currentDLCPath = Path.Combine(dlcFPath, DLCFolderName); string destPath = Path.Combine(dlcFPath, Enabled ? @"x" + UIDLCFolderName : UIDLCFolderName); Directory.Move(currentDLCPath, destPath); Enabled = !Enabled; DLCFolderName = Enabled ? UIDLCFolderName : @"x" + UIDLCFolderName; } catch (Exception e) { Log.Error($"Error toggling DLC {DLCFolderName}: {e.Message}"); Xceed.Wpf.Toolkit.MessageBox.Show("Error toggling DLC: " + e.Message); } }
private void ToggleDLC() { try { var dlcFPath = MEDirectories.DLCPath(target); var currentDLCPath = Path.Combine(dlcFPath, DLCFolderName); string destPath = Path.Combine(dlcFPath, Enabled ? @"x" + UIDLCFolderName : UIDLCFolderName); Directory.Move(currentDLCPath, destPath); Enabled = !Enabled; DLCFolderName = Enabled ? UIDLCFolderName : @"x" + UIDLCFolderName; } catch (Exception e) { Log.Error($@"Error toggling DLC {DLCFolderName}: {e.Message}"); M3L.ShowDialog(Application.Current?.MainWindow, M3L.GetString(M3L.string_interp_errorTogglingDLC, e.Message), M3L.GetString(M3L.string_error), MessageBoxButton.OK, MessageBoxImage.Error); //this needs updated to be better } }
private void LoadME1FilesList() { var me1files = new List <BackupFile>(); var bup = BackupService.GetGameBackupPath(Mod.MEGame.ME1); if (bup != null) { var target = new GameTarget(Mod.MEGame.ME1, bup, false); var cookedPath = MEDirectories.CookedPath(target); foreach (var f in Extensions.GetFiles(cookedPath, @"\.u|\.upk|\.sfm", SearchOption.AllDirectories)) { me1files.Add(new BackupFile(@"BASEGAME", Path.GetFileName(f))); } me1files.Sort(); //sort basegame var dlcDir = MEDirectories.DLCPath(target); var officialDLC = VanillaDatabaseService.GetInstalledOfficialDLC(target); foreach (var v in officialDLC) { var cookedDLCPath = Path.Combine(dlcDir, v, @"CookedPC"); if (Directory.Exists(cookedDLCPath)) { var filesToAdd = new List <BackupFile>(); foreach (var f in Extensions.GetFiles(cookedDLCPath, @"\.u|\.upk|\.sfm", SearchOption.AllDirectories)) { filesToAdd.Add(new BackupFile(v, Path.GetFileName(f))); } filesToAdd.Sort(); me1files.AddRange(filesToAdd); } } } Application.Current.Dispatcher.Invoke(delegate { ME1Files.ReplaceAll(me1files); }); Debug.WriteLine(@"Num ME1 files: " + ME2Files.Count); }
private void BeginRestore() { if (Utilities.IsGameRunning(Game)) { Xceed.Wpf.Toolkit.MessageBox.Show(window, $"Cannot restore {Utilities.GetGameName(Game)} while it is running.", $"Game is running", MessageBoxButton.OK, MessageBoxImage.Error); return; } NamedBackgroundWorker bw = new NamedBackgroundWorker(Game.ToString() + "Backup"); bw.DoWork += (a, b) => { RestoreInProgress = true; string restoreTargetPath = b.Argument as string; string backupPath = BackupLocation; BackupStatusLine2 = "Deleting existing game installation"; if (Directory.Exists(restoreTargetPath)) { if (Directory.GetFiles(restoreTargetPath).Any() || Directory.GetDirectories(restoreTargetPath).Any()) { Log.Information("Deleting existing game directory: " + restoreTargetPath); try { bool deletedDirectory = Utilities.DeleteFilesAndFoldersRecursively(restoreTargetPath); if (deletedDirectory != true) { b.Result = RestoreResult.ERROR_COULD_NOT_DELETE_GAME_DIRECTORY; return; } } catch (Exception ex) { //todo: handle this better Log.Error("Exception deleting game directory: " + restoreTargetPath + ": " + ex.Message); b.Result = RestoreResult.EXCEPTION_DELETING_GAME_DIRECTORY; return; } } } else { Log.Error("Game directory not found! Was it removed while the app was running?"); } //Todo: Revert LODs, remove IndirectSound settings (MEUITM) //Log.Information("Reverting lod settings"); //string exe = BINARY_DIRECTORY + MEM_EXE_NAME; //string args = "--remove-lods --gameid " + BACKUP_THREAD_GAME; //Utilities.runProcess(exe, args); //if (BACKUP_THREAD_GAME == 1) //{ // string iniPath = IniSettingsHandler.GetConfigIniPath(1); // if (File.Exists(iniPath)) // { // Log.Information("Reverting Indirect Sound ini fix for ME1"); // IniFile engineConf = new IniFile(iniPath); // engineConf.DeleteKey("DeviceName", "ISACTAudio.ISACTAudioDevice"); // } //} var created = Utilities.CreateDirectoryWithWritePermission(restoreTargetPath); if (!created) { b.Result = RestoreResult.ERROR_COULD_NOT_CREATE_DIRECTORY; return; } BackupStatusLine2 = "Restoring game from backup"; if (restoreTargetPath != null) { //callbacks #region callbacks void fileCopiedCallback() { ProgressValue++; } string dlcFolderpath = MEDirectories.DLCPath(backupPath, Game) + '\\'; //\ at end makes sure we are restoring a subdir int dlcSubStringLen = dlcFolderpath.Length; Debug.WriteLine("DLC Folder: " + dlcFolderpath); Debug.Write("DLC Fodler path len:" + dlcFolderpath); bool aboutToCopyCallback(string fileBeingCopied) { if (fileBeingCopied.Contains("\\cmmbackup\\")) { return(false); //do not copy cmmbackup files } Debug.WriteLine(fileBeingCopied); if (fileBeingCopied.StartsWith(dlcFolderpath, StringComparison.InvariantCultureIgnoreCase)) { //It's a DLC! string dlcname = fileBeingCopied.Substring(dlcSubStringLen); int index = dlcname.IndexOf('\\'); try { dlcname = dlcname.Substring(0, index); if (MEDirectories.OfficialDLCNames(RestoreTarget.Game).TryGetValue(dlcname, out var hrName)) { BackupStatusLine2 = "Restoring " + hrName; } else { BackupStatusLine2 = "Restoring " + dlcname; } } catch (Exception e) { Crashes.TrackError(e, new Dictionary <string, string>() { { "Source", "Restore UI display callback" }, { "Value", fileBeingCopied }, { "DLC Folder path", dlcFolderpath } }); } } else { //It's basegame if (fileBeingCopied.EndsWith(".bik")) { BackupStatusLine2 = "Restoring Movies"; } else if (new FileInfo(fileBeingCopied).Length > 52428800) { BackupStatusLine2 = "Restoring " + Path.GetFileName(fileBeingCopied); } else { BackupStatusLine2 = "Restoring BASEGAME"; } } return(true); } void totalFilesToCopyCallback(int total) { ProgressValue = 0; ProgressIndeterminate = false; ProgressMax = total; } #endregion BackupStatus = "Restoring game"; Log.Information("Copying backup to game directory: " + backupPath + " -> " + restoreTargetPath); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(backupPath), new DirectoryInfo(restoreTargetPath), totalItemsToCopyCallback: totalFilesToCopyCallback, aboutToCopyCallback: aboutToCopyCallback, fileCopiedCallback: fileCopiedCallback, ignoredExtensions: new[] { "*.pdf", "*.mp3" }); Log.Information("Restore of game data has completed"); } //Check for cmmvanilla file and remove it present string cmmVanilla = Path.Combine(restoreTargetPath, "cmm_vanilla"); if (File.Exists(cmmVanilla)) { Log.Information("Removing cmm_vanilla file"); File.Delete(cmmVanilla); } Log.Information("Restore thread wrapping up"); b.Result = RestoreResult.RESTORE_OK; }; bw.RunWorkerCompleted += (a, b) => { if (b.Result is RestoreResult result) { switch (result) { case RestoreResult.ERROR_COULD_NOT_CREATE_DIRECTORY: Analytics.TrackEvent("Restored game", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Failure, Could not create target directory" } }); Xceed.Wpf.Toolkit.MessageBox.Show("Could not create the game directory after it was deleted due to an error. View the logs from the help menu for more information.", "Error restoring game", MessageBoxButton.OK, MessageBoxImage.Error); break; case RestoreResult.ERROR_COULD_NOT_DELETE_GAME_DIRECTORY: Analytics.TrackEvent("Restored game", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Failure, Could not delete existing game directory" } }); Xceed.Wpf.Toolkit.MessageBox.Show("Could not fully delete the game directory. It may have files or folders still open from various programs. Part of the game may have been deleted. View the logs from the help menu for more information.", "Error restoring game", MessageBoxButton.OK, MessageBoxImage.Error); break; case RestoreResult.EXCEPTION_DELETING_GAME_DIRECTORY: Analytics.TrackEvent("Restored game", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Failure, Excpetion deleting existing game directory" } }); Xceed.Wpf.Toolkit.MessageBox.Show("An error occured while deleting the game directory. View the logs from the help menu for more information.", "Error restoring game", MessageBoxButton.OK, MessageBoxImage.Error); break; case RestoreResult.RESTORE_OK: Analytics.TrackEvent("Restored game", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Success" } }); break; } } EndRestore(); CommandManager.InvalidateRequerySuggested(); }; var restTarget = RestoreTarget.TargetPath; if (RestoreTarget.IsCustomOption) { CommonOpenFileDialog m = new CommonOpenFileDialog { IsFolderPicker = true, EnsurePathExists = true, Title = "Select new restore destination" }; if (m.ShowDialog() == CommonFileDialogResult.Ok) { //Check empty restTarget = m.FileName; if (Directory.Exists(restTarget)) { if (Directory.GetFiles(restTarget).Length > 0 || Directory.GetDirectories(restTarget).Length > 0) { //Directory not empty Xceed.Wpf.Toolkit.MessageBox.Show("Directory is not empty. Location to restore to must be empty.", "Cannot restore to this location", MessageBoxButton.OK, MessageBoxImage.Error); return; } } Analytics.TrackEvent("Chose to restore game to custom location", new Dictionary <string, string>() { { "Game", Game.ToString() } }); } else { return; } } RefreshTargets = true; bw.RunWorkerAsync(restTarget); }
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); }
private void ImportSelectedFolder() { //Check destination path var destinationName = Utilities.SanitizePath(ModNameText); if (string.IsNullOrWhiteSpace(destinationName)) { //cannot use this name Log.Error(@"Invalid mod name: " + ModNameText); M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_dialog_invalidModNameWillResolveToNothing), M3L.GetString(M3L.string_invalidModName), MessageBoxButton.OK, MessageBoxImage.Error); return; } //Check free space. var sourceDir = Path.Combine(MEDirectories.DLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName); var library = Utilities.GetModDirectoryForGame(SelectedTarget.Game); if (Utilities.DriveFreeBytes(library, out var freeBytes)) { //Check enough space var sourceSize = Utilities.GetSizeOfDirectory(sourceDir); if (sourceSize > (long)freeBytes) { //Not enough space Log.Error($@"Not enough disk space to import mod. Required space: {ByteSize.FromBytes(sourceSize)}, available space: {ByteSize.FromBytes(freeBytes)}"); M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_insufficientDiskSpaceToImport, Path.GetPathRoot(library), ByteSize.FromBytes(sourceSize), ByteSize.FromBytes(freeBytes)), M3L.GetString(M3L.string_insufficientFreeDiskSpace), MessageBoxButton.OK, MessageBoxImage.Error); return; } } //Check directory doesn't exist already var outDir = Path.Combine(library, destinationName); if (Directory.Exists(outDir)) { var okToDelete = M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialog_importingWillDeleteExistingMod, outDir), M3L.GetString(M3L.string_sameNamedModInLibrary), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (okToDelete == MessageBoxResult.No) { return; //cancel } try { Utilities.DeleteFilesAndFoldersRecursively(outDir); } catch (Exception e) { M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_couldNotDeleteExistingModDirectory, e.Message), M3L.GetString(M3L.string_errorDeletingModFolder), MessageBoxButton.OK, MessageBoxImage.Error); return; //abort } } NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"GameDLCModImporter"); nbw.DoWork += ImportDLCFolder_BackgroundThread; nbw.RunWorkerCompleted += (a, b) => { if (b.Error != null) { Log.Error($@"Exception occurred in {nbw.Name} thread: {b.Error.Message}"); } else { if (b.Error == null && b.Result != null) { Analytics.TrackEvent(@"Imported a mod from game installation", new Dictionary <string, string>() { { @"Game", SelectedTarget.Game.ToString() }, { @"Folder", SelectedDLCFolder.DLCFolderName } }); } OperationInProgress = false; if (b.Error == null && b.Result != null) { OnClosing(new DataEventArgs(b.Result)); //avoid accessing b.Result if error occurred } } }; nbw.RunWorkerAsync(); }
private void BeginBackup() { if (Utilities.IsGameRunning(BackupSourceTarget.Game)) { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_cannotBackupGameWhileRunning, Utilities.GetGameName(BackupSourceTarget.Game)), M3L.GetString(M3L.string_gameRunning), MessageBoxButton.OK, MessageBoxImage.Error); return; } NamedBackgroundWorker bw = new NamedBackgroundWorker(Game.ToString() + @"Backup"); bw.DoWork += (a, b) => { BackupInProgress = true; List <string> nonVanillaFiles = new List <string>(); void nonVanillaFileFoundCallback(string filepath) { Log.Error($@"Non-vanilla file found: {filepath}"); nonVanillaFiles.Add(filepath); } List <string> inconsistentDLC = new List <string>(); void inconsistentDLCFoundCallback(string filepath) { if (BackupSourceTarget.Supported) { Log.Error($@"DLC is in an inconsistent state: {filepath}"); inconsistentDLC.Add(filepath); } else { Log.Error(@"Detected an inconsistent DLC, likely due to an unofficial copy of the game"); } } ProgressVisible = true; ProgressIndeterminate = true; BackupStatus = M3L.GetString(M3L.string_validatingBackupSource); VanillaDatabaseService.LoadDatabaseFor(Game); bool isVanilla = VanillaDatabaseService.ValidateTargetAgainstVanilla(BackupSourceTarget, nonVanillaFileFoundCallback); bool isDLCConsistent = VanillaDatabaseService.ValidateTargetDLCConsistency(BackupSourceTarget, inconsistentDLCCallback: inconsistentDLCFoundCallback); List <string> dlcModsInstalled = VanillaDatabaseService.GetInstalledDLCMods(BackupSourceTarget); List <string> installedDLC = VanillaDatabaseService.GetInstalledOfficialDLC(BackupSourceTarget); List <string> allOfficialDLC = MEDirectories.OfficialDLC(BackupSourceTarget.Game); bool end = false; if (installedDLC.Count() < allOfficialDLC.Count()) { var dlcList = string.Join("\n - ", allOfficialDLC.Except(installedDLC).Select(x => $@"{MEDirectories.OfficialDLCNames(BackupSourceTarget.Game)[x]} ({x})")); //do not localize dlcList = @" - " + dlcList; Application.Current.Dispatcher.Invoke(delegate { var cancelDueToNotAllDLC = M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_notAllDLCInstalled, dlcList), M3L.GetString(M3L.string_someDlcNotInstalled), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (cancelDueToNotAllDLC == MessageBoxResult.No) { end = true; EndBackup(); return; } }); } if (!end) { if (isVanilla && isDLCConsistent && dlcModsInstalled.Count == 0) { BackupStatus = M3L.GetString(M3L.string_waitingForUserInput); string backupPath = null; Application.Current.Dispatcher.Invoke(delegate { Log.Information(@"Prompting user to select backup destination"); CommonOpenFileDialog m = new CommonOpenFileDialog { IsFolderPicker = true, EnsurePathExists = true, Title = M3L.GetString(M3L.string_selectBackupDestination) }; if (m.ShowDialog() == CommonFileDialogResult.Ok) { backupPath = m.FileName; Log.Information(@"Backup path chosen: " + backupPath); //Check empty if (Directory.Exists(backupPath)) { if (Directory.GetFiles(backupPath).Length > 0 || Directory.GetDirectories(backupPath).Length > 0) { //Directory not empty Log.Error(@"Selected backup directory is not empty."); M3L.ShowDialog(window, M3L.GetString(M3L.string_directoryIsNotEmptyMustBeEmpty), M3L.GetString(M3L.string_directoryNotEmpty), MessageBoxButton.OK, MessageBoxImage.Error); end = true; EndBackup(); return; } } //Check space Utilities.GetDiskFreeSpaceEx(backupPath, out var freeBytes, out var totalBytes, out var totalFreeBytes); var requiredSpace = Utilities.GetSizeOfDirectory(BackupSourceTarget.TargetPath) * 1.1; //10% buffer if (freeBytes < requiredSpace) { //Not enough space. Log.Error( $@"Not enough disk spcae to create backup at {backupPath}. Required space: {ByteSize.FromBytes(requiredSpace)} Free space: {ByteSize.FromBytes(freeBytes)}"); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogInsufficientDiskSpace, Path.GetPathRoot(backupPath), ByteSize.FromBytes(freeBytes).ToString(), ByteSize.FromBytes(requiredSpace).ToString()), M3L.GetString(M3L.string_insufficientDiskSpace), MessageBoxButton.OK, MessageBoxImage.Error); end = true; EndBackup(); return; } //Check it is not subdirectory of the game (we might want ot check its not subdir of a target) foreach (var target in AvailableBackupSources) { if (backupPath.IsSubPathOf(target.TargetPath)) { //Not enough space. Log.Error( $@"A backup cannot be created in a subdirectory of a game. {backupPath} is a subdir of {BackupSourceTarget.TargetPath}"); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogBackupCannotBeSubdirectoryOfGame, backupPath, target.TargetPath), M3L.GetString(M3L.string_cannotCreateBackup), MessageBoxButton.OK, MessageBoxImage.Error); end = true; EndBackup(); return; } } } else { end = true; EndBackup(); return; } }); if (end) { return; } #region callbacks void fileCopiedCallback() { ProgressValue++; } string dlcFolderpath = MEDirectories.DLCPath(BackupSourceTarget) + '\\'; int dlcSubStringLen = dlcFolderpath.Length; bool aboutToCopyCallback(string file) { try { if (file.Contains(@"\cmmbackup\")) { return(false); //do not copy cmmbackup files } if (file.StartsWith(dlcFolderpath)) { //It's a DLC! string dlcname = file.Substring(dlcSubStringLen); dlcname = dlcname.Substring(0, dlcname.IndexOf('\\')); if (MEDirectories.OfficialDLCNames(BackupSourceTarget.Game) .TryGetValue(dlcname, out var hrName)) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, hrName); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, dlcname); } } else { //It's basegame if (file.EndsWith(@".bik")) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.GetString(M3L.string_movies)); } else if (new FileInfo(file).Length > 52428800) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, Path.GetFileName(file)); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.GetString(M3L.string_basegame)); } } } catch (Exception e) { Crashes.TrackError(e, new Dictionary <string, string>() { { @"dlcFolderpath", dlcFolderpath }, { @"dlcSubStringLen", dlcSubStringLen.ToString() }, { @"file", file } }); } return(true); } void totalFilesToCopyCallback(int total) { ProgressValue = 0; ProgressIndeterminate = false; ProgressMax = total; } #endregion BackupStatus = M3L.GetString(M3L.string_creatingBackup); Log.Information($@"Backing up {BackupSourceTarget.TargetPath} to {backupPath}"); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(BackupSourceTarget.TargetPath), new DirectoryInfo(backupPath), totalItemsToCopyCallback: totalFilesToCopyCallback, aboutToCopyCallback: aboutToCopyCallback, fileCopiedCallback: fileCopiedCallback, ignoredExtensions: new[] { @"*.pdf", @"*.mp3" }); switch (Game) { case Mod.MEGame.ME1: case Mod.MEGame.ME2: Utilities.WriteRegistryKey(App.BACKUP_REGISTRY_KEY, Game + @"VanillaBackupLocation", backupPath); break; case Mod.MEGame.ME3: Utilities.WriteRegistryKey(App.REGISTRY_KEY_ME3CMM, @"VanillaCopyLocation", backupPath); break; } Log.Information($@"Writing cmm_vanilla"); File.Create(Path.Combine(backupPath, "cmm_vanilla")).Close(); Log.Information($@"Backup completed."); Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Success" } }); EndBackup(); return; }
private async void ImportDLCFolder_BackgroundThread(object sender, DoWorkEventArgs e) { OperationInProgress = true; var sourceDir = Path.Combine(MEDirectories.DLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName); // Check for MEMI, we will not allow importing files with MEMI foreach (var file in Directory.GetFiles(sourceDir, @"*.*", SearchOption.AllDirectories)) { if (file.RepresentsPackageFilePath() && Utilities.HasALOTMarker(file)) { Log.Error($@"Found a file marked as texture modded: {file}. These files cannot be imported into mod manager"); Application.Current.Dispatcher.Invoke(delegate { M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_cannotImportModTextureMarkersFound), M3L.GetString(M3L.string_cannotImportMod), MessageBoxButton.OK, MessageBoxImage.Error); }); return; } } var library = Utilities.GetModDirectoryForGame(SelectedTarget.Game); var destinationName = Utilities.SanitizePath(ModNameText); var modFolder = Path.Combine(library, destinationName); var copyDestination = Path.Combine(modFolder, SelectedDLCFolder.DLCFolderName); var outInfo = Directory.CreateDirectory(copyDestination); Log.Information($@"Importing mod: {sourceDir} -> {copyDestination}"); int numToDo = 0; int numDone = 0; void totalItemToCopyCallback(int total) { numToDo = total; ProgressBarMax = total; } void fileCopiedCallback() { numDone++; ProgressBarValue = numDone; } CopyDir.CopyAll_ProgressBar(new DirectoryInfo(sourceDir), outInfo, totalItemToCopyCallback, fileCopiedCallback); //Write a moddesc IniData ini = new IniData(); ini[@"ModManager"][@"cmmver"] = App.HighestSupportedModDesc.ToString(CultureInfo.InvariantCulture); //prevent commas ini[@"ModInfo"][@"game"] = SelectedTarget.Game.ToString(); ini[@"ModInfo"][@"modname"] = ModNameText; ini[@"ModInfo"][@"moddev"] = M3L.GetString(M3L.string_importedFromGame); ini[@"ModInfo"][@"moddesc"] = M3L.GetString(M3L.string_defaultDescriptionForImportedMod, Utilities.GetGameName(SelectedTarget.Game), DateTime.Now); ini[@"ModInfo"][@"modver"] = M3L.GetString(M3L.string_unknown); ini[@"ModInfo"][@"unofficial"] = @"true"; ini[@"ModInfo"][@"importedby"] = App.BuildNumber.ToString(); ini[@"CUSTOMDLC"][@"sourcedirs"] = SelectedDLCFolder.DLCFolderName; ini[@"CUSTOMDLC"][@"destdirs"] = SelectedDLCFolder.DLCFolderName; var moddescPath = Path.Combine(modFolder, @"moddesc.ini"); File.WriteAllText(moddescPath, ini.ToString()); //Generate and load mod Mod m = new Mod(moddescPath, Mod.MEGame.ME3); e.Result = m; Log.Information(@"Mod import complete."); Analytics.TrackEvent(@"Imported already installed mod", new Dictionary <string, string>() { { @"Mod name", m.ModName }, { @"Game", SelectedTarget.Game.ToString() }, { @"Folder name", SelectedDLCFolder.DLCFolderName } }); if (!CurrentModInTPMI) { //Submit telemetry to ME3Tweaks try { TPMITelemetrySubmissionForm.TelemetryPackage tp = TPMITelemetrySubmissionForm.GetTelemetryPackageForDLC(SelectedTarget.Game, MEDirectories.DLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName, SelectedDLCFolder.DLCFolderName, //same as foldername as this is already installed ModNameText, @"N/A", ModSiteText, null ); tp.SubmitPackage(); } catch (Exception ex) { Log.Error(@"Cannot submit telemetry: " + ex.Message); } } }
private void StartGuiCompatibilityScanner() { NamedBackgroundWorker bw = new NamedBackgroundWorker(@"GUICompatibilityScanner"); bw.DoWork += (a, b) => { Percent = 0; ActionString = M3L.GetString(M3L.string_preparingCompatGenerator); ActionSubstring = M3L.GetString(M3L.string_pleaseWait); var installedDLCMods = VanillaDatabaseService.GetInstalledDLCMods(target); var numTotalDLCMods = installedDLCMods.Count; var uiModInstalled = installedDLCMods.Intersect(DLCUIModFolderNames).Any(); var dlcRoot = MEDirectories.DLCPath(target); if (uiModInstalled) { var nonUIinstalledDLCMods = installedDLCMods.Except(DLCUIModFolderNamesIncludingPatch).ToList(); if (nonUIinstalledDLCMods.Count < numTotalDLCMods && nonUIinstalledDLCMods.Count > 0) { //Get UI library bool xbxLibrary = installedDLCMods.Contains(@"DLC_CON_XBX"); bool uiscalinglibrary = installedDLCMods.Contains(@"DLC_CON_UIScaling"); if (!xbxLibrary && !uiscalinglibrary) { uiscalinglibrary = installedDLCMods.Contains(@"DLC_CON_UIScaling_Shared"); } if (xbxLibrary && uiscalinglibrary) { //can't have both! Not supported. Application.Current.Dispatcher.Invoke(delegate { Log.Error(@"Cannot make compat pack: Both ISM and SP Controller are installed, this is not supported."); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogCannotGenerateCompatPackInvalidConfig), M3L.GetString(M3L.string_invalidConfiguration), MessageBoxButton.OK, MessageBoxImage.Error); OnClosing(DataEventArgs.Empty); }); b.Result = GUICompatibilityThreadResult.INVALID_UI_MOD_CONFIG; return; } void progressCallback(long done, long total) { ActionString = M3L.GetString(M3L.string_downloadingUiLibrary); ActionSubstring = xbxLibrary ? @"DLC_CON_XBX" : @"DLC_CON_UIScaling"; Percent = getPercent(done, total); } var uiLibraryPath = GetUILibraryPath(xbxLibrary ? @"DLC_CON_XBX" : @"DLC_CON_UIScaling", true, progressCallback); if (uiLibraryPath == null) { Log.Error(@"Required UI library could not be downloaded."); Application.Current.Dispatcher.Invoke(delegate { M3L.ShowDialog(window, M3L.GetString(M3L.string_cannotGeneratorCompatPackCouldNotDownload), M3L.GetString(M3L.string_couldNotAcquireUiLibrary), MessageBoxButton.OK, MessageBoxImage.Error); OnClosing(DataEventArgs.Empty); }); b.Result = GUICompatibilityThreadResult.NO_UI_LIBRARY; return; } //Open UI library SevenZipExtractor libraryArchive = new SevenZipExtractor(uiLibraryPath); List <string> libraryGUIs = libraryArchive.ArchiveFileData.Where(x => !x.IsDirectory).Select(x => x.FileName.Substring(Path.GetFileNameWithoutExtension(uiLibraryPath).Length + 1)).Select(x => x.Substring(0, x.Length - 4)).ToList(); //remove / on end too //We have UI mod(s) installed and at least one other DLC mod. var supercedanceList = getFileSupercedances().Where(x => x.Value.Any(x => !DLCUIModFolderNamesIncludingPatch.Contains(x))).ToDictionary(p => p.Key, p => p.Value); //Find GUIs ConcurrentDictionary <string, string> filesToBePatched = new ConcurrentDictionary <string, string>(); //Dictionary because there is no ConcurrentList. Keys and values are idenitcal. ActionString = M3L.GetString(M3L.string_scanningForGuiExports); ActionSubstring = M3L.GetString(M3L.string_pleaseWait); Percent = 0; int done = 0; string singlesuffix = M3L.GetString(M3L.string_singularFile); string pluralsuffix = M3L.GetString(M3L.string_pluralFiles); Parallel.ForEach(supercedanceList, new ParallelOptions() { MaxDegreeOfParallelism = 4 }, (pair) => { var firstNonUIModDlc = pair.Value.FirstOrDefault(x => !DLCUIModFolderNamesIncludingPatch.Contains(x)); if (firstNonUIModDlc != null) { //Scan file. var packagefile = Path.Combine(dlcRoot, firstNonUIModDlc, target.Game == MEGame.ME3 ? @"CookedPCConsole" : @"CookedPC", pair.Key); Log.Information(@"Scanning file for GFXMovieInfo exports: " + packagefile); if (!File.Exists(packagefile)) { throw new Exception($@"Package file for inspecting GUIs in was not found: {packagefile}"); } var package = MEPackageHandler.OpenMEPackage(packagefile); var guiExports = package.Exports.Where(x => !x.IsDefaultObject && x.ClassName == @"GFxMovieInfo").ToList(); if (guiExports.Count > 0) { //potential item needing replacement //Check GUI library to see if we have anything. foreach (var export in guiExports) { if (libraryGUIs.Contains(export.GetFullPath, StringComparer.InvariantCultureIgnoreCase)) { //match filesToBePatched[packagefile] = packagefile; ActionSubstring = M3L.GetString(M3L.string_interp_XFilesNeedToBePatched, filesToBePatched.Count.ToString(), filesToBePatched.Count == 1 ? singlesuffix : pluralsuffix); Log.Information($@"{firstNonUIModDlc} {pair.Key} has GUI export that is in UI library, marking for patching. Trigger: {export.GetFullPath}"); break; } } } } Interlocked.Increment(ref done); Percent = getPercent(done, supercedanceList.Count); }); if (filesToBePatched.Count > 0) { Log.Information(@"A GUI compatibility patch is required for this game configuration"); b.Result = GUICompatibilityThreadResult.REQUIRED; var generatedMod = GenerateCompatibilityPackForFiles(nonUIinstalledDLCMods, filesToBePatched.Keys.ToList(), libraryArchive); b.Result = GUICompatibilityThreadResult.GENERATED_PACK; Application.Current.Dispatcher.Invoke(delegate { ((MainWindow)window).LoadMods(generatedMod); }); //reload to this mod } } Log.Information(@"A GUI compatibility patch is not required for this game configuration"); b.Result = GUICompatibilityThreadResult.NOT_REQUIRED; } else { Log.Information(@"No UI mods are installed - no GUI compatibility pack required"); b.Result = GUICompatibilityThreadResult.NO_UI_MODS_INSTALLED; } }; bw.RunWorkerCompleted += (a, b) => { if (b.Result is GUICompatibilityThreadResult gctr) { Analytics.TrackEvent(@"Generated a UI compatibility pack", new Dictionary <string, string>() { { @"Result", gctr.ToString() } }); OnClosing(DataEventArgs.Empty); } else { throw new Exception(@"GUI Compatibility generator thread did not return a result! Please report this to ME3Tweaks"); } }; bw.RunWorkerAsync(); }
private void InstallModBackgroundThread(object sender, DoWorkEventArgs e) { Log.Information($"Mod Installer Background thread starting"); var installationJobs = ModBeingInstalled.InstallationJobs; var gamePath = gameTarget.TargetPath; var gameDLCPath = MEDirectories.DLCPath(gameTarget); Directory.CreateDirectory(gameDLCPath); //me1/me2 missing dlc might not have this folder //Check we can install var missingRequiredDLC = ModBeingInstalled.ValidateRequiredModulesAreInstalled(gameTarget); if (missingRequiredDLC.Count > 0) { e.Result = (ModInstallCompletedStatus.INSTALL_FAILED_REQUIRED_DLC_MISSING, missingRequiredDLC); return; } //Check/warn on official headers if (!PrecheckHeaders(gameDLCPath, installationJobs)) { e.Result = ModInstallCompletedStatus.INSTALL_FAILED_USER_CANCELED_MISSING_MODULES; return; } //todo: If statment on this Utilities.InstallBinkBypass(gameTarget); //Always install binkw32, don't bother checking if it is already ASI version. //Prepare queues (Dictionary <ModJob, (Dictionary <string, string> fileMapping, List <string> dlcFoldersBeingInstalled)> unpackedJobMappings, List <(ModJob job, string sfarPath, Dictionary <string, string> sfarInstallationMapping)> sfarJobs)installationQueues = ModBeingInstalled.GetInstallationQueues(gameTarget); if (gameTarget.ALOTInstalled) { //Check if any packages are being installed. If there are, we will block this installation. bool installsPackageFile = false; foreach (var jobMappings in installationQueues.unpackedJobMappings) { installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(".pcc", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(".u", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(".upk", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(".sfm", StringComparison.InvariantCultureIgnoreCase)); } foreach (var jobMappings in installationQueues.sfarJobs) { installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(".pcc", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(".u", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(".upk", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(".sfm", StringComparison.InvariantCultureIgnoreCase)); } if (installsPackageFile) { if (Settings.DeveloperMode) { Log.Warning("ALOT is installed and user is attemping to install a mod (in developer mode). Prompting user to cancel installation"); bool cancel = false; Application.Current.Dispatcher.Invoke(delegate { var res = Xceed.Wpf.Toolkit.MessageBox.Show(Window.GetWindow(this), $"ALOT is installed and this mod installs package files. Continuing to install this mod will likely cause broken textures to occur or game crashes due to invalid texture pointers and possibly empty mips. It will also put your ALOT installation into an unsupported configuration.\n\nContinue to install {ModBeingInstalled.ModName}? You have been warned.", $"Broken textures warning", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No); cancel = res == MessageBoxResult.No; }); if (cancel) { e.Result = ModInstallCompletedStatus.USER_CANCELED_INSTALLATION; return; } Log.Warning("User installing mod anyways even with ALOT installed"); } else { Log.Error("ALOT is installed. Installing mods that install package files after installing ALOT is not permitted."); //ALOT Installed, this is attempting to install a package file e.Result = ModInstallCompletedStatus.INSTALL_FAILED_ALOT_BLOCKING; return; } } } Action = $"Installing"; PercentVisibility = Visibility.Visible; Percent = 0; int numdone = 0; //Calculate number of installation tasks beforehand int numFilesToInstall = installationQueues.unpackedJobMappings.Select(x => x.Value.fileMapping.Count).Sum(); numFilesToInstall += installationQueues.sfarJobs.Select(x => x.sfarInstallationMapping.Count).Sum() * (ModBeingInstalled.IsInArchive ? 2 : 1); //*2 as we have to extract and install Debug.WriteLine("Number of expected installation tasks: " + numFilesToInstall); void FileInstalledCallback(string target) { numdone++; Debug.WriteLine("Installed: " + target); Action = "Installing"; var now = DateTime.Now; if (numdone > numFilesToInstall) { Debug.WriteLine($"Percentage calculated is wrong. Done: {numdone} NumToDoTotal: {numFilesToInstall}"); } if ((now - lastPercentUpdateTime).Milliseconds > PERCENT_REFRESH_COOLDOWN) { //Don't update UI too often. Once per second is enough. Percent = (int)(numdone * 100.0 / numFilesToInstall); lastPercentUpdateTime = now; } } //Stage: Unpacked files build map Dictionary <string, string> fullPathMappingDisk = new Dictionary <string, string>(); Dictionary <int, string> fullPathMappingArchive = new Dictionary <int, string>(); SortedSet <string> customDLCsBeingInstalled = new SortedSet <string>(); foreach (var unpackedQueue in installationQueues.unpackedJobMappings) { foreach (var originalMapping in unpackedQueue.Value.fileMapping) { //always unpacked //if (unpackedQueue.Key == ModJob.JobHeader.CUSTOMDLC || unpackedQueue.Key == ModJob.JobHeader.BALANCE_CHANGES || unpackedQueue.Key == ModJob.JobHeader.BASEGAME) //{ string sourceFile; if (unpackedQueue.Key.JobDirectory == null) { sourceFile = FilesystemInterposer.PathCombine(ModBeingInstalled.IsInArchive, ModBeingInstalled.ModPath, originalMapping.Value); } else { sourceFile = FilesystemInterposer.PathCombine(ModBeingInstalled.IsInArchive, ModBeingInstalled.ModPath, unpackedQueue.Key.JobDirectory, originalMapping.Value); } if (unpackedQueue.Key.Header == ModJob.JobHeader.ME1_CONFIG) { var destFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "BioWare", "Mass Effect", "Config", originalMapping.Key); if (ModBeingInstalled.IsInArchive) { int archiveIndex = ModBeingInstalled.Archive.ArchiveFileNames.IndexOf(sourceFile, StringComparer.InvariantCultureIgnoreCase); fullPathMappingArchive[archiveIndex] = destFile; //used for extraction indexing if (archiveIndex == -1) { Log.Error("Archive Index is -1 for file " + sourceFile + ". This will probably throw an exception!"); Debugger.Break(); } fullPathMappingDisk[sourceFile] = destFile; //used for redirection } else { fullPathMappingDisk[sourceFile] = destFile; } } else { var destFile = Path.Combine(unpackedQueue.Key.Header == ModJob.JobHeader.CUSTOMDLC ? MEDirectories.DLCPath(gameTarget) : gameTarget.TargetPath, originalMapping.Key); //official //Extract Custom DLC name if (unpackedQueue.Key.Header == ModJob.JobHeader.CUSTOMDLC) { var custDLC = destFile.Substring(gameDLCPath.Length, destFile.Length - gameDLCPath.Length).TrimStart('\\', '/'); var nextSlashIndex = custDLC.IndexOf('\\'); if (nextSlashIndex == -1) { nextSlashIndex = custDLC.IndexOf('/'); } if (nextSlashIndex != -1) { custDLC = custDLC.Substring(0, nextSlashIndex); customDLCsBeingInstalled.Add(custDLC); } } if (ModBeingInstalled.IsInArchive) { int archiveIndex = ModBeingInstalled.Archive.ArchiveFileNames.IndexOf(sourceFile, StringComparer.InvariantCultureIgnoreCase); fullPathMappingArchive[archiveIndex] = destFile; //used for extraction indexing if (archiveIndex == -1) { Log.Error("Archive Index is -1 for file " + sourceFile + ". This will probably throw an exception!"); Debugger.Break(); } fullPathMappingDisk[sourceFile] = destFile; //used for redirection } else { fullPathMappingDisk[sourceFile] = destFile; } } //} } } //Substage: Add SFAR staging targets string sfarStagingDirectory = (ModBeingInstalled.IsInArchive && installationQueues.sfarJobs.Count > 0) ? Directory.CreateDirectory(Path.Combine(Utilities.GetTempPath(), "SFARJobStaging")).FullName : null; //don't make directory if we don't need one if (sfarStagingDirectory != null) { foreach (var sfarJob in installationQueues.sfarJobs) { foreach (var fileToInstall in sfarJob.sfarInstallationMapping) { string sourceFile = FilesystemInterposer.PathCombine(ModBeingInstalled.IsInArchive, ModBeingInstalled.ModPath, sfarJob.job.JobDirectory, fileToInstall.Value); int archiveIndex = ModBeingInstalled.Archive.ArchiveFileNames.IndexOf(sourceFile, StringComparer.InvariantCultureIgnoreCase); if (archiveIndex == -1) { Log.Error("Archive Index is -1 for file " + sourceFile + ". This will probably throw an exception!"); Debugger.Break(); } string destFile = Path.Combine(sfarStagingDirectory, sfarJob.job.JobDirectory, fileToInstall.Value); fullPathMappingArchive[archiveIndex] = destFile; //used for extraction indexing fullPathMappingDisk[sourceFile] = destFile; //used for redirection Debug.WriteLine($"SFAR Disk Staging: {fileToInstall.Key} => {destFile}"); } } } foreach (var cdbi in customDLCsBeingInstalled) { var path = Path.Combine(gameDLCPath, cdbi); if (Directory.Exists(path)) { Log.Information("Deleting existing DLC directory: " + path); Utilities.DeleteFilesAndFoldersRecursively(path); } } //Stage: Unpacked files installation if (!ModBeingInstalled.IsInArchive) { //Direct copy Log.Information($"Installing {fullPathMappingDisk.Count} unpacked files into game directory"); CopyDir.CopyFiles_ProgressBar(fullPathMappingDisk, FileInstalledCallback); } else { Action = "Loading mod archive"; //Extraction to destination string installationRedirectCallback(ArchiveFileInfo info) { var inArchivePath = info.FileName; var redirectedPath = fullPathMappingDisk[inArchivePath]; Debug.WriteLine($"Redirecting {inArchivePath} to {redirectedPath}"); return(redirectedPath); } ModBeingInstalled.Archive.FileExtractionStarted += (sender, args) => { //CLog.Information("Extracting mod file for installation: " + args.FileInfo.FileName, Settings.LogModInstallation); }; List <string> filesInstalled = new List <string>(); List <string> filesToInstall = installationQueues.unpackedJobMappings.SelectMany(x => x.Value.fileMapping.Keys).ToList(); ModBeingInstalled.Archive.FileExtractionFinished += (sender, args) => { if (args.FileInfo.IsDirectory) { return; //ignore } if (!fullPathMappingArchive.ContainsKey(args.FileInfo.Index)) { return; //archive extracted this file (in memory) but did not do anything with this file (7z) } FileInstalledCallback(args.FileInfo.FileName); filesInstalled.Add(args.FileInfo.FileName); //Debug.WriteLine($"{args.FileInfo.FileName} as file { numdone}"); //Debug.WriteLine(numdone); }; ModBeingInstalled.Archive.ExtractFiles(gameTarget.TargetPath, installationRedirectCallback, fullPathMappingArchive.Keys.ToArray()); //directory parameter shouldn't be used here as we will be redirecting everything //filesInstalled.Sort(); //filesToInstall.Sort(); //Debug.WriteLine("Files installed:"); //foreach (var f in filesInstalled) //{ // Debug.WriteLine(f); //} //Debug.WriteLine("Files expected:"); //foreach (var f in filesToInstall) //{ // Debug.WriteLine(f); //} } //Write MetaCMM List <string> addedDLCFolders = new List <string>(); foreach (var v in installationQueues.unpackedJobMappings) { addedDLCFolders.AddRange(v.Value.dlcFoldersBeingInstalled); } foreach (var addedDLCFolder in addedDLCFolders) { var metacmm = Path.Combine(addedDLCFolder, "_metacmm.txt"); ModBeingInstalled.HumanReadableCustomDLCNames.TryGetValue(Path.GetFileName(addedDLCFolder), out var assignedDLCName); string contents = $"{assignedDLCName ?? ModBeingInstalled.ModName}\n{ModBeingInstalled.ModVersionString}\n{App.BuildNumber}\n{Guid.NewGuid().ToString()}"; File.WriteAllText(metacmm, contents); } //Stage: SFAR Installation foreach (var sfarJob in installationQueues.sfarJobs) { InstallIntoSFAR(sfarJob, ModBeingInstalled, FileInstalledCallback, ModBeingInstalled.IsInArchive ? sfarStagingDirectory : null); } //Main installation step has completed CLog.Information("Main stage of mod installation has completed", Settings.LogModInstallation); Percent = (int)(numdone * 100.0 / numFilesToInstall); //Remove outdated custom DLC foreach (var outdatedDLCFolder in ModBeingInstalled.OutdatedCustomDLC) { var outdatedDLCInGame = Path.Combine(gameDLCPath, outdatedDLCFolder); if (Directory.Exists(outdatedDLCInGame)) { Log.Information("Deleting outdated custom DLC folder: " + outdatedDLCInGame); Utilities.DeleteFilesAndFoldersRecursively(outdatedDLCInGame); } } //Install supporting ASI files if necessary //Todo: Upgrade to version detection code from ME3EXP to prevent conflicts Action = "Installing support files"; CLog.Information("Installing supporting ASI files", Settings.LogModInstallation); if (ModBeingInstalled.Game == Mod.MEGame.ME1) { Utilities.InstallEmbeddedASI("ME1-DLC-ModEnabler-v1.0", 1.0, gameTarget); } else if (ModBeingInstalled.Game == Mod.MEGame.ME2) { //None right now } else { //Todo: Port detection code from ME3Exp //Utilities.InstallEmbeddedASI("ME3Logger_truncating-v1.0", 1.0, gameTarget); if (ModBeingInstalled.GetJob(ModJob.JobHeader.BALANCE_CHANGES) != null) { Utilities.InstallEmbeddedASI("BalanceChangesReplacer-v2.0", 2.0, gameTarget); } } if (sfarStagingDirectory != null) { Utilities.DeleteFilesAndFoldersRecursively(Utilities.GetTempPath()); } if (numFilesToInstall == numdone) { e.Result = ModInstallCompletedStatus.INSTALL_SUCCESSFUL; Action = "Installed"; } else { Log.Warning($"Number of completed items does not equal the amount of items to install! Number installed {numdone} Number expected: {numFilesToInstall}"); e.Result = ModInstallCompletedStatus.INSTALL_WRONG_NUMBER_OF_COMPLETED_ITEMS; } }
public static bool RunTOCOnGameTarget(GameTarget target, Action <int> percentDoneCallback = null) { Log.Information(@"Autotocing game: " + target.TargetPath); //get toc target folders, ensuring we clean up the inputs a bit. string baseDir = Path.GetFullPath(Path.Combine(target.TargetPath, @"BIOGame")); string dlcDirRoot = MEDirectories.DLCPath(target); if (!Directory.Exists(dlcDirRoot)) { Log.Error(@"Specified game directory does not appear to be a Mass Effect 3 root game directory (DLC folder missing)."); return(false); } var tocTargets = (new DirectoryInfo(dlcDirRoot)).GetDirectories().Select(x => x.FullName).Where(x => Path.GetFileName(x).StartsWith(@"DLC_", StringComparison.OrdinalIgnoreCase)).ToList(); tocTargets.Add(baseDir); tocTargets.Add(Path.Combine(target.TargetPath, @"BIOGame\Patches\PCConsole\Patch_001.sfar")); //Debug.WriteLine("Found TOC Targets:"); tocTargets.ForEach(x => Debug.WriteLine(x)); //Debug.WriteLine("=====Generating TOC Files====="); int done = 0; foreach (var tocTarget in tocTargets) { string sfar = Path.Combine(tocTarget, SFAR_SUBPATH); if (tocTarget.EndsWith(@".sfar")) { //TestPatch var watch = Stopwatch.StartNew(); DLCPackage dlc = new DLCPackage(tocTarget); var tocResult = dlc.UpdateTOCbin(); watch.Stop(); if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATE_NOT_NECESSARY) { Log.Information($@"TOC is already up to date in {tocTarget}"); } else if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATED) { var elapsedMs = watch.ElapsedMilliseconds; Log.Information($@"{tocTarget} - Ran SFAR TOC, took {elapsedMs}ms"); } } else if (ME3Directory.OfficialDLCNames.ContainsKey(Path.GetFileName(tocTarget))) { //Official DLC if (File.Exists(sfar)) { if (new FileInfo(sfar).Length == 32) //DLC is unpacked for sure { CreateUnpackedTOC(tocTarget); } else { //AutoTOC it - SFAR is not unpacked var watch = System.Diagnostics.Stopwatch.StartNew(); DLCPackage dlc = new DLCPackage(sfar); var tocResult = dlc.UpdateTOCbin(); watch.Stop(); if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_ERROR_NO_ENTRIES) { Log.Information($@"No DLC entries in SFAR... Suspicious. Creating empty TOC for {tocTarget}"); CreateUnpackedTOC(tocTarget); } else if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATE_NOT_NECESSARY) { Log.Information($@"TOC is already up to date in {tocTarget}"); } else if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATED) { var elapsedMs = watch.ElapsedMilliseconds; Log.Information($@"{Path.GetFileName(tocTarget)} - Ran SFAR TOC, took {elapsedMs}ms"); } } } } else { //TOC it unpacked style // Console.WriteLine(foldername + ", - UNPACKED TOC"); CreateUnpackedTOC(tocTarget); } done++; percentDoneCallback?.Invoke((int)Math.Floor(done * 100.0 / tocTargets.Count)); } return(true); }
private void BeginRestore() { if (Utilities.IsGameRunning(Game)) { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialogCannotRestoreXWhileItIsRunning, Utilities.GetGameName(Game)), M3L.GetString(M3L.string_gameRunning), MessageBoxButton.OK, MessageBoxImage.Error); return; } bool restore = RestoreTarget.IsCustomOption; //custom option is restore to custom location restore = restore || M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_restoringXWillDeleteGameDir, Utilities.GetGameName(Game)), M3L.GetString(M3L.string_gameTargetWillBeDeleted), MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes; if (restore) { NamedBackgroundWorker nbw = new NamedBackgroundWorker(Game + @"-Restore"); nbw.WorkerReportsProgress = true; nbw.ProgressChanged += (a, b) => { if (b.UserState is double d) { TaskbarHelper.SetProgress(d); } }; nbw.DoWork += (a, b) => { RestoreInProgress = true; string restoreTargetPath = b.Argument as string; string backupPath = BackupLocation; BackupStatusLine2 = M3L.GetString(M3L.string_deletingExistingGameInstallation); if (Directory.Exists(restoreTargetPath)) { if (Directory.GetFiles(restoreTargetPath).Any() || Directory.GetDirectories(restoreTargetPath).Any()) { Log.Information(@"Deleting existing game directory: " + restoreTargetPath); try { bool deletedDirectory = Utilities.DeleteFilesAndFoldersRecursively(restoreTargetPath); if (deletedDirectory != true) { b.Result = RestoreResult.ERROR_COULD_NOT_DELETE_GAME_DIRECTORY; return; } } catch (Exception ex) { //todo: handle this better Log.Error($@"Exception deleting game directory: {restoreTargetPath}: {ex.Message}"); b.Result = RestoreResult.EXCEPTION_DELETING_GAME_DIRECTORY; return; } } } else { Log.Error(@"Game directory not found! Was it removed while the app was running?"); } //Todo: Revert LODs, remove IndirectSound settings (MEUITM) var created = Utilities.CreateDirectoryWithWritePermission(restoreTargetPath); if (!created) { b.Result = RestoreResult.ERROR_COULD_NOT_CREATE_DIRECTORY; return; } BackupStatusLine2 = M3L.GetString(M3L.string_restoringGameFromBackup); if (restoreTargetPath != null) { //callbacks #region callbacks void fileCopiedCallback() { ProgressValue++; if (ProgressMax != 0) { nbw.ReportProgress(0, ProgressValue * 1.0 / ProgressMax); } } string dlcFolderpath = MEDirectories.DLCPath(backupPath, Game) + '\\'; //\ at end makes sure we are restoring a subdir int dlcSubStringLen = dlcFolderpath.Length; Debug.WriteLine(@"DLC Folder: " + dlcFolderpath); Debug.Write(@"DLC Fodler path len:" + dlcFolderpath); bool aboutToCopyCallback(string fileBeingCopied) { if (fileBeingCopied.Contains(@"\cmmbackup\")) { return(false); //do not copy cmmbackup files } Debug.WriteLine(fileBeingCopied); if (fileBeingCopied.StartsWith(dlcFolderpath, StringComparison.InvariantCultureIgnoreCase)) { //It's a DLC! string dlcname = fileBeingCopied.Substring(dlcSubStringLen); int index = dlcname.IndexOf('\\'); try { dlcname = dlcname.Substring(0, index); if (MEDirectories.OfficialDLCNames(RestoreTarget.Game).TryGetValue(dlcname, out var hrName)) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_restoringX, hrName); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_restoringX, dlcname); } } catch (Exception e) { Crashes.TrackError(e, new Dictionary <string, string>() { { @"Source", @"Restore UI display callback" }, { @"Value", fileBeingCopied }, { @"DLC Folder path", dlcFolderpath } }); } } else { //It's basegame if (fileBeingCopied.EndsWith(@".bik")) { BackupStatusLine2 = M3L.GetString(M3L.string_restoringMovies); } else if (new FileInfo(fileBeingCopied).Length > 52428800) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_restoringX, Path.GetFileName(fileBeingCopied)); } else { BackupStatusLine2 = M3L.GetString(M3L.string_restoringBasegame); } } return(true); } void totalFilesToCopyCallback(int total) { ProgressValue = 0; ProgressIndeterminate = false; ProgressMax = total; } #endregion BackupStatus = M3L.GetString(M3L.string_restoringGame); Log.Information($@"Copying backup to game directory: {backupPath} -> {restoreTargetPath}"); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(backupPath), new DirectoryInfo(restoreTargetPath), totalItemsToCopyCallback: totalFilesToCopyCallback, aboutToCopyCallback: aboutToCopyCallback, fileCopiedCallback: fileCopiedCallback, ignoredExtensions: new[] { @"*.pdf", @"*.mp3" }); Log.Information(@"Restore of game data has completed"); } //Check for cmmvanilla file and remove it present string cmmVanilla = Path.Combine(restoreTargetPath, @"cmm_vanilla"); if (File.Exists(cmmVanilla)) { Log.Information(@"Removing cmm_vanilla file"); File.Delete(cmmVanilla); } Log.Information(@"Restore thread wrapping up"); RestoreTarget.ReloadGameTarget(); b.Result = RestoreResult.RESTORE_OK; }; nbw.RunWorkerCompleted += (a, b) => { if (b.Error != null) { Log.Error($@"Exception occurred in {nbw.Name} thread: {b.Error.Message}"); } TaskbarHelper.SetProgressState(TaskbarProgressBarState.NoProgress); if (b.Result is RestoreResult result) { switch (result) { case RestoreResult.ERROR_COULD_NOT_CREATE_DIRECTORY: Analytics.TrackEvent(@"Restored game", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, Could not create target directory" } }); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogCouldNotCreateGameDirectoryAfterDeletion), M3L.GetString(M3L.string_errorRestoringGame), MessageBoxButton.OK, MessageBoxImage.Error); break; case RestoreResult.ERROR_COULD_NOT_DELETE_GAME_DIRECTORY: Analytics.TrackEvent(@"Restored game", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, Could not delete existing game directory" } }); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogcouldNotFullyDeleteGameDirectory), M3L.GetString(M3L.string_errorRestoringGame), MessageBoxButton.OK, MessageBoxImage.Error); break; case RestoreResult.EXCEPTION_DELETING_GAME_DIRECTORY: Analytics.TrackEvent(@"Restored game", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, Exception deleting existing game directory" } }); M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogErrorOccuredDeletingGameDirectory), M3L.GetString(M3L.string_errorRestoringGame), MessageBoxButton.OK, MessageBoxImage.Error); break; case RestoreResult.RESTORE_OK: Analytics.TrackEvent(@"Restored game", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Success" } }); break; } } EndRestore(); CommandManager.InvalidateRequerySuggested(); }; var restTarget = RestoreTarget.TargetPath; if (RestoreTarget.IsCustomOption) { CommonOpenFileDialog m = new CommonOpenFileDialog { IsFolderPicker = true, EnsurePathExists = true, Title = M3L.GetString(M3L.string_selectNewRestoreDestination) }; if (m.ShowDialog() == CommonFileDialogResult.Ok) { //Check empty restTarget = m.FileName; if (Directory.Exists(restTarget)) { if (Directory.GetFiles(restTarget).Length > 0 || Directory.GetDirectories(restTarget).Length > 0) { //Directory not empty M3L.ShowDialog(window, M3L.GetString(M3L.string_dialogDirectoryIsNotEmptyLocationToRestoreToMustBeEmpty), M3L.GetString(M3L.string_cannotRestoreToThisLocation), MessageBoxButton.OK, MessageBoxImage.Error); return; } //TODO: PREVENT RESTORING TO DOCUMENTS/BIOWARE } Analytics.TrackEvent(@"Chose to restore game to custom location", new Dictionary <string, string>() { { @"Game", Game.ToString() } }); } else { return; } } RefreshTargets = true; TaskbarHelper.SetProgress(0); TaskbarHelper.SetProgressState(TaskbarProgressBarState.Normal); nbw.RunWorkerAsync(restTarget); } }
private void InstallModBackgroundThread(object sender, DoWorkEventArgs e) { Log.Information(@"Mod Installer Background thread starting"); var installationJobs = ModBeingInstalled.InstallationJobs; var gameDLCPath = MEDirectories.DLCPath(gameTarget); Directory.CreateDirectory(gameDLCPath); //me1/me2 missing dlc might not have this folder //Check we can install var missingRequiredDLC = ModBeingInstalled.ValidateRequiredModulesAreInstalled(gameTarget); if (missingRequiredDLC.Count > 0) { e.Result = (ModInstallCompletedStatus.INSTALL_FAILED_REQUIRED_DLC_MISSING, missingRequiredDLC); return; } //Check/warn on official headers if (!PrecheckHeaders(installationJobs)) { e.Result = ModInstallCompletedStatus.INSTALL_FAILED_USER_CANCELED_MISSING_MODULES; return; } //todo: If statment on this Utilities.InstallBinkBypass(gameTarget); //Always install binkw32, don't bother checking if it is already ASI version. if (ModBeingInstalled.Game == Mod.MEGame.ME2 && ModBeingInstalled.GetJob(ModJob.JobHeader.ME2_RCWMOD) != null && installationJobs.Count == 1) { e.Result = InstallAttachedRCWMod(); return; } //Prepare queues (Dictionary <ModJob, (Dictionary <string, string> fileMapping, List <string> dlcFoldersBeingInstalled)> unpackedJobMappings, List <(ModJob job, string sfarPath, Dictionary <string, string> sfarInstallationMapping)> sfarJobs)installationQueues = ModBeingInstalled.GetInstallationQueues(gameTarget); var readOnlyTargets = ModBeingInstalled.GetAllRelativeReadonlyTargets(me1ConfigReadOnlyOption.IsSelected); if (gameTarget.ALOTInstalled) { //Check if any packages are being installed. If there are, we will block this installation. bool installsPackageFile = false; foreach (var jobMappings in installationQueues.unpackedJobMappings) { installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(@".pcc", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(@".u", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(@".upk", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.Value.fileMapping.Keys.Any(x => x.EndsWith(@".sfm", StringComparison.InvariantCultureIgnoreCase)); } foreach (var jobMappings in installationQueues.sfarJobs) { installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(@".pcc", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(@".u", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(@".upk", StringComparison.InvariantCultureIgnoreCase)); installsPackageFile |= jobMappings.sfarInstallationMapping.Keys.Any(x => x.EndsWith(@".sfm", StringComparison.InvariantCultureIgnoreCase)); } if (installsPackageFile) { if (Settings.DeveloperMode) { Log.Warning(@"ALOT is installed and user is attempting to install a mod (in developer mode). Prompting user to cancel installation"); bool cancel = false; Application.Current.Dispatcher.Invoke(delegate { var res = M3L.ShowDialog(Window.GetWindow(this), M3L.GetString(M3L.string_interp_devModeAlotInstalledWarning, ModBeingInstalled.ModName), M3L.GetString(M3L.string_brokenTexturesWarning), MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No); cancel = res == MessageBoxResult.No; }); if (cancel) { e.Result = ModInstallCompletedStatus.USER_CANCELED_INSTALLATION; return; } Log.Warning(@"User installing mod anyways even with ALOT installed"); } else { Log.Error(@"ALOT is installed. Installing mods that install package files after installing ALOT is not permitted."); //ALOT Installed, this is attempting to install a package file e.Result = ModInstallCompletedStatus.INSTALL_FAILED_ALOT_BLOCKING; return; } } } Action = M3L.GetString(M3L.string_installing); PercentVisibility = Visibility.Visible; Percent = 0; int numdone = 0; //Calculate number of installation tasks beforehand int numFilesToInstall = installationQueues.unpackedJobMappings.Select(x => x.Value.fileMapping.Count).Sum(); numFilesToInstall += installationQueues.sfarJobs.Select(x => x.sfarInstallationMapping.Count).Sum() * (ModBeingInstalled.IsInArchive ? 2 : 1); //*2 as we have to extract and install Debug.WriteLine(@"Number of expected installation tasks: " + numFilesToInstall); void FileInstalledCallback(string target) { numdone++; Debug.WriteLine(@"Installed: " + target); Action = M3L.GetString(M3L.string_installing); var now = DateTime.Now; if (numdone > numFilesToInstall) { Debug.WriteLine($@"Percentage calculated is wrong. Done: {numdone} NumToDoTotal: {numFilesToInstall}"); } if ((now - lastPercentUpdateTime).Milliseconds > PERCENT_REFRESH_COOLDOWN) { //Don't update UI too often. Once per second is enough. Percent = (int)(numdone * 100.0 / numFilesToInstall); lastPercentUpdateTime = now; } } //Stage: Unpacked files build map Dictionary <string, string> fullPathMappingDisk = new Dictionary <string, string>(); Dictionary <int, string> fullPathMappingArchive = new Dictionary <int, string>(); SortedSet <string> customDLCsBeingInstalled = new SortedSet <string>(); List <string> mappedReadOnlyTargets = new List <string>(); foreach (var unpackedQueue in installationQueues.unpackedJobMappings) { foreach (var originalMapping in unpackedQueue.Value.fileMapping) { //always unpacked //if (unpackedQueue.Key == ModJob.JobHeader.CUSTOMDLC || unpackedQueue.Key == ModJob.JobHeader.BALANCE_CHANGES || unpackedQueue.Key == ModJob.JobHeader.BASEGAME) //{ //Resolve source file path string sourceFile; if (unpackedQueue.Key.JobDirectory == null) { sourceFile = FilesystemInterposer.PathCombine(ModBeingInstalled.IsInArchive, ModBeingInstalled.ModPath, originalMapping.Value); } else { sourceFile = FilesystemInterposer.PathCombine(ModBeingInstalled.IsInArchive, ModBeingInstalled.ModPath, unpackedQueue.Key.JobDirectory, originalMapping.Value); } if (unpackedQueue.Key.Header == ModJob.JobHeader.ME1_CONFIG) { var destFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"BioWare", @"Mass Effect", @"Config", originalMapping.Key); if (ModBeingInstalled.IsInArchive) { int archiveIndex = ModBeingInstalled.Archive.ArchiveFileNames.IndexOf(sourceFile, StringComparer.InvariantCultureIgnoreCase); fullPathMappingArchive[archiveIndex] = destFile; //used for extraction indexing if (archiveIndex == -1) { Log.Error($@"Archive Index is -1 for file {sourceFile}. This will probably throw an exception!"); Debugger.Break(); } fullPathMappingDisk[sourceFile] = destFile; //used for redirection } else { fullPathMappingDisk[sourceFile] = destFile; } } else { var destFile = Path.Combine(unpackedQueue.Key.Header == ModJob.JobHeader.CUSTOMDLC ? MEDirectories.DLCPath(gameTarget) : gameTarget.TargetPath, originalMapping.Key); //official //Extract Custom DLC name if (unpackedQueue.Key.Header == ModJob.JobHeader.CUSTOMDLC) { var custDLC = destFile.Substring(gameDLCPath.Length, destFile.Length - gameDLCPath.Length).TrimStart('\\', '/'); var nextSlashIndex = custDLC.IndexOf('\\'); if (nextSlashIndex == -1) { nextSlashIndex = custDLC.IndexOf('/'); } if (nextSlashIndex != -1) { custDLC = custDLC.Substring(0, nextSlashIndex); customDLCsBeingInstalled.Add(custDLC); } } if (ModBeingInstalled.IsInArchive) { int archiveIndex = ModBeingInstalled.Archive.ArchiveFileNames.IndexOf(sourceFile, StringComparer.InvariantCultureIgnoreCase); fullPathMappingArchive[archiveIndex] = destFile; //used for extraction indexing if (archiveIndex == -1) { Log.Error($@"Archive Index is -1 for file {sourceFile}. This will probably throw an exception!"); Debugger.Break(); } } fullPathMappingDisk[sourceFile] = destFile; //archive also uses this for redirection } if (readOnlyTargets.Contains(originalMapping.Key)) { CLog.Information(@"Adding resolved read only target: " + originalMapping.Key + @" -> " + fullPathMappingDisk[sourceFile], Settings.LogModInstallation); mappedReadOnlyTargets.Add(fullPathMappingDisk[sourceFile]); } //} } } //Substage: Add SFAR staging targets string sfarStagingDirectory = (ModBeingInstalled.IsInArchive && installationQueues.sfarJobs.Count > 0) ? Directory.CreateDirectory(Path.Combine(Utilities.GetTempPath(), @"SFARJobStaging")).FullName : null; //don't make directory if we don't need one if (sfarStagingDirectory != null) { foreach (var sfarJob in installationQueues.sfarJobs) { foreach (var fileToInstall in sfarJob.sfarInstallationMapping) { string sourceFile = FilesystemInterposer.PathCombine(ModBeingInstalled.IsInArchive, ModBeingInstalled.ModPath, sfarJob.job.JobDirectory, fileToInstall.Value); int archiveIndex = ModBeingInstalled.Archive.ArchiveFileNames.IndexOf(sourceFile, StringComparer.InvariantCultureIgnoreCase); if (archiveIndex == -1) { Log.Error($@"Archive Index is -1 for file {sourceFile}. This will probably throw an exception!"); Debugger.Break(); } string destFile = Path.Combine(sfarStagingDirectory, sfarJob.job.JobDirectory, fileToInstall.Value); fullPathMappingArchive[archiveIndex] = destFile; //used for extraction indexing fullPathMappingDisk[sourceFile] = destFile; //used for redirection Debug.WriteLine($@"SFAR Disk Staging: {fileToInstall.Key} => {destFile}"); } } } //Check we have enough disk space long requiredSpaceToInstall = 0L; if (ModBeingInstalled.IsInArchive) { foreach (var f in ModBeingInstalled.Archive.ArchiveFileData) { if (fullPathMappingArchive.ContainsKey(f.Index)) { //we are installing this file requiredSpaceToInstall += (long)f.Size; } } } else { foreach (var file in fullPathMappingDisk) { requiredSpaceToInstall += new FileInfo(file.Key).Length; } } Utilities.DriveFreeBytes(gameTarget.TargetPath, out var freeSpaceOnTargetDisk); requiredSpaceToInstall = (long)(requiredSpaceToInstall * 1.05); //+5% for some overhead if (requiredSpaceToInstall > (long)freeSpaceOnTargetDisk && freeSpaceOnTargetDisk != 0) { string driveletter = Path.GetPathRoot(gameTarget.TargetPath); Log.Error($@"Insufficient disk space to install mod. Required: {ByteSize.FromBytes(requiredSpaceToInstall)}, available on {driveletter}: {ByteSize.FromBytes(freeSpaceOnTargetDisk)}"); Application.Current.Dispatcher.Invoke(() => { string message = M3L.GetString(M3L.string_interp_dialogNotEnoughSpaceToInstall, driveletter, ModBeingInstalled.ModName, ByteSize.FromBytes(requiredSpaceToInstall).ToString(), ByteSize.FromBytes(freeSpaceOnTargetDisk).ToString()); Xceed.Wpf.Toolkit.MessageBox.Show(window, message, M3L.GetString(M3L.string_insufficientDiskSpace), MessageBoxButton.OK, MessageBoxImage.Error); }); e.Result = ModInstallCompletedStatus.INSTALL_ABORTED_NOT_ENOUGH_SPACE; return; } //Delete existing custom DLC mods with same name foreach (var cdbi in customDLCsBeingInstalled) { var path = Path.Combine(gameDLCPath, cdbi); if (Directory.Exists(path)) { Log.Information($@"Deleting existing DLC directory: {path}"); Utilities.DeleteFilesAndFoldersRecursively(path); } } //Stage: Unpacked files installation if (!ModBeingInstalled.IsInArchive) { //Direct copy Log.Information($@"Installing {fullPathMappingDisk.Count} unpacked files into game directory"); CopyDir.CopyFiles_ProgressBar(fullPathMappingDisk, FileInstalledCallback); } else { Action = M3L.GetString(M3L.string_loadingModArchive); //Extraction to destination string installationRedirectCallback(ArchiveFileInfo info) { var inArchivePath = info.FileName; var redirectedPath = fullPathMappingDisk[inArchivePath]; Debug.WriteLine($@"Redirecting {inArchivePath} to {redirectedPath}"); return(redirectedPath); } ModBeingInstalled.Archive.FileExtractionStarted += (sender, args) => { //CLog.Information("Extracting mod file for installation: " + args.FileInfo.FileName, Settings.LogModInstallation); }; List <string> filesInstalled = new List <string>(); List <string> filesToInstall = installationQueues.unpackedJobMappings.SelectMany(x => x.Value.fileMapping.Keys).ToList(); ModBeingInstalled.Archive.FileExtractionFinished += (sender, args) => { if (args.FileInfo.IsDirectory) { return; //ignore } if (!fullPathMappingArchive.ContainsKey(args.FileInfo.Index)) { return; //archive extracted this file (in memory) but did not do anything with this file (7z) } FileInstalledCallback(args.FileInfo.FileName); filesInstalled.Add(args.FileInfo.FileName); //Debug.WriteLine($"{args.FileInfo.FileName} as file { numdone}"); //Debug.WriteLine(numdone); }; ModBeingInstalled.Archive.ExtractFiles(gameTarget.TargetPath, installationRedirectCallback, fullPathMappingArchive.Keys.ToArray()); //directory parameter shouldn't be used here as we will be redirecting everything } //Write MetaCMM List <string> addedDLCFolders = new List <string>(); foreach (var v in installationQueues.unpackedJobMappings) { addedDLCFolders.AddRange(v.Value.dlcFoldersBeingInstalled); } foreach (var addedDLCFolder in addedDLCFolders) { var metacmm = Path.Combine(addedDLCFolder, @"_metacmm.txt"); ModBeingInstalled.HumanReadableCustomDLCNames.TryGetValue(Path.GetFileName(addedDLCFolder), out var assignedDLCName); string contents = $"{assignedDLCName ?? ModBeingInstalled.ModName}\n{ModBeingInstalled.ModVersionString}\n{App.BuildNumber}\n{Guid.NewGuid().ToString()}"; //Do not localize File.WriteAllText(metacmm, contents); } //Stage: SFAR Installation foreach (var sfarJob in installationQueues.sfarJobs) { InstallIntoSFAR(sfarJob, ModBeingInstalled, FileInstalledCallback, ModBeingInstalled.IsInArchive ? sfarStagingDirectory : null); } //Main installation step has completed CLog.Information(@"Main stage of mod installation has completed", Settings.LogModInstallation); Percent = (int)(numdone * 100.0 / numFilesToInstall); //Mark items read only foreach (var readonlytarget in mappedReadOnlyTargets) { CLog.Information(@"Setting file to read-only: " + readonlytarget, Settings.LogModInstallation); File.SetAttributes(readonlytarget, File.GetAttributes(readonlytarget) | FileAttributes.ReadOnly); } //Remove outdated custom DLC foreach (var outdatedDLCFolder in ModBeingInstalled.OutdatedCustomDLC) { var outdatedDLCInGame = Path.Combine(gameDLCPath, outdatedDLCFolder); if (Directory.Exists(outdatedDLCInGame)) { Log.Information(@"Deleting outdated custom DLC folder: " + outdatedDLCInGame); Utilities.DeleteFilesAndFoldersRecursively(outdatedDLCInGame); } } //Install supporting ASI files if necessary //Todo: Upgrade to version detection code from ME3EXP to prevent conflicts Action = M3L.GetString(M3L.string_installingSupportFiles); PercentVisibility = Visibility.Collapsed; CLog.Information(@"Installing supporting ASI files", Settings.LogModInstallation); if (ModBeingInstalled.Game == Mod.MEGame.ME1) { //Todo: Convert to ASI Manager installer Utilities.InstallASIByGroupID(gameTarget, @"DLC Mod Enabler", 16); //16 = DLC M -od Enabler //Utilities.InstallEmbeddedASI(@"ME1-DLC-ModEnabler-v1.0", 1.0, gameTarget); //Todo: Switch to ASI Manager } else if (ModBeingInstalled.Game == Mod.MEGame.ME2) { //None right now } else { if (ModBeingInstalled.GetJob(ModJob.JobHeader.BALANCE_CHANGES) != null) { Utilities.InstallASIByGroupID(gameTarget, @"Balance Changes Replacer", 5); //Utilities.InstallASIByGroupID(gameTarget, @"ME3Logger-Truncating", 5); //Utilities.InstallEmbeddedASI(@"BalanceChangesReplacer-v2.0", 2.0, gameTarget); //todo: Switch to ASI Manager } } if (sfarStagingDirectory != null) { Utilities.DeleteFilesAndFoldersRecursively(Utilities.GetTempPath()); } if (numFilesToInstall == numdone) { e.Result = ModInstallCompletedStatus.INSTALL_SUCCESSFUL; Action = M3L.GetString(M3L.string_installed); } else { Log.Warning($@"Number of completed items does not equal the amount of items to install! Number installed {numdone} Number expected: {numFilesToInstall}"); e.Result = ModInstallCompletedStatus.INSTALL_WRONG_NUMBER_OF_COMPLETED_ITEMS; } }
private void BeginBackup() { if (Utilities.IsGameRunning(BackupSourceTarget.Game)) { Xceed.Wpf.Toolkit.MessageBox.Show(window, $"Cannot backup {Utilities.GetGameName(BackupSourceTarget.Game)} while it is running.", $"Game is running", MessageBoxButton.OK, MessageBoxImage.Error); return; } NamedBackgroundWorker bw = new NamedBackgroundWorker(Game.ToString() + "Backup"); bw.DoWork += (a, b) => { BackupInProgress = true; List <string> nonVanillaFiles = new List <string>(); void nonVanillaFileFoundCallback(string filepath) { Log.Error("Non-vanilla file found: " + filepath); nonVanillaFiles.Add(filepath); } List <string> inconsistentDLC = new List <string>(); void inconsistentDLCFoundCallback(string filepath) { if (BackupSourceTarget.Supported) { Log.Error("DLC is in an inconsistent state: " + filepath); inconsistentDLC.Add(filepath); } else { Log.Error("Detected an inconsistent DLC, likely due to an unofficial copy of the game"); } } ProgressVisible = true; ProgressIndeterminate = true; BackupStatus = "Validating backup source"; VanillaDatabaseService.LoadDatabaseFor(Game); bool isVanilla = VanillaDatabaseService.ValidateTargetAgainstVanilla(BackupSourceTarget, nonVanillaFileFoundCallback); bool isDLCConsistent = VanillaDatabaseService.ValidateTargetDLCConsistency(BackupSourceTarget, inconsistentDLCCallback: inconsistentDLCFoundCallback); List <string> dlcModsInstalled = VanillaDatabaseService.GetInstalledDLCMods(BackupSourceTarget); if (isVanilla && isDLCConsistent && dlcModsInstalled.Count == 0) { BackupStatus = "Waiting for user input"; string backupPath = null; bool end = false; Application.Current.Dispatcher.Invoke(delegate { CommonOpenFileDialog m = new CommonOpenFileDialog { IsFolderPicker = true, EnsurePathExists = true, Title = "Select backup destination" }; if (m.ShowDialog() == CommonFileDialogResult.Ok) { //Check empty backupPath = m.FileName; if (Directory.Exists(backupPath)) { if (Directory.GetFiles(backupPath).Length > 0 || Directory.GetDirectories(backupPath).Length > 0) { //Directory not empty MessageBox.Show("Directory is not empty. A backup destination must be empty."); end = true; EndBackup(); return; } } } else { end = true; EndBackup(); return; } }); if (end) { return; } #region callbacks void fileCopiedCallback() { ProgressValue++; } string dlcFolderpath = MEDirectories.DLCPath(BackupSourceTarget) + '\\'; int dlcSubStringLen = dlcFolderpath.Length; bool aboutToCopyCallback(string file) { if (file.Contains("\\cmmbackup\\")) { return(false); //do not copy cmmbackup files } if (file.StartsWith(dlcFolderpath)) { //It's a DLC! string dlcname = file.Substring(dlcSubStringLen); dlcname = dlcname.Substring(0, dlcname.IndexOf('\\')); if (MEDirectories.OfficialDLCNames(BackupSourceTarget.Game).TryGetValue(dlcname, out var hrName)) { BackupStatusLine2 = "Backing up " + hrName; } else { BackupStatusLine2 = "Backing up " + dlcname; } } else { //It's basegame if (file.EndsWith(".bik")) { BackupStatusLine2 = "Backing up Movies"; } else if (new FileInfo(file).Length > 52428800) { BackupStatusLine2 = "Backing up " + Path.GetFileName(file); } else { BackupStatusLine2 = "Backing up BASEGAME"; } } return(true); } void totalFilesToCopyCallback(int total) { ProgressValue = 0; ProgressIndeterminate = false; ProgressMax = total; } #endregion BackupStatus = "Creating backup"; CopyDir.CopyAll_ProgressBar(new DirectoryInfo(BackupSourceTarget.TargetPath), new DirectoryInfo(backupPath), totalItemsToCopyCallback: totalFilesToCopyCallback, aboutToCopyCallback: aboutToCopyCallback, fileCopiedCallback: fileCopiedCallback, ignoredExtensions: new[] { "*.pdf", "*.mp3" }); switch (Game) { case Mod.MEGame.ME1: case Mod.MEGame.ME2: Utilities.WriteRegistryKey(App.BACKUP_REGISTRY_KEY, Game + "VanillaBackupLocation", backupPath); break; case Mod.MEGame.ME3: Utilities.WriteRegistryKey(App.REGISTRY_KEY_ME3CMM, "VanillaCopyLocation", backupPath); break; } Analytics.TrackEvent("Created a backup", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Success" } }); EndBackup(); return; } if (!isVanilla) { //Show UI for non vanilla Analytics.TrackEvent("Created a backup", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Failure, Game modified" } }); b.Result = (nonVanillaFiles, "Cannot backup modified game", "The following files do not match the vanilla database and appear to be modified. Due to these files being modified, a backup cannot be taken of this installation."); } else if (!isDLCConsistent) { Analytics.TrackEvent("Created a backup", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Failure, DLC inconsistent" } }); if (BackupSourceTarget.Supported) { b.Result = (inconsistentDLC, "Inconsistent DLC detected", "The following DLC are in an inconsistent state; they have a packed SFAR file but contain unpacked game files. The configuration of these DLC are not supported, the unpacked files must be manually removed."); } else { b.Result = ("Inconsistent DLC detected", "Inconsistent DLC was detected. This may be due to using an unofficial copy of the game."); } } else if (dlcModsInstalled.Count > 0) { Analytics.TrackEvent("Created a backup", new Dictionary <string, string>() { { "Game", Game.ToString() }, { "Result", "Failure, DLC mods found" } }); b.Result = (dlcModsInstalled, "DLC mods are installed", "The following DLC folders were detected that are not part of the official game by BioWare. Backups cannot include DLC mods - the game must be unmodified."); } EndBackup(); }; bw.RunWorkerCompleted += (a, b) => { if (b.Result is (List <string> listItems, string title, string text)) { ListDialog ld = new ListDialog(listItems, title, text, window); ld.Show(); } else if (b.Result is (string errortitle, string message)) { Xceed.Wpf.Toolkit.MessageBox.Show(message, errortitle, MessageBoxButton.OK, MessageBoxImage.Error); } CommandManager.InvalidateRequerySuggested(); }; bw.RunWorkerAsync(); }
/// <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); }
private void BeginBackup() { var targetToBackup = BackupSourceTarget; if (!targetToBackup.IsCustomOption) { if (Utilities.IsGameRunning(targetToBackup.Game)) { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_cannotBackupGameWhileRunning, Utilities.GetGameName(BackupSourceTarget.Game)), M3L.GetString(M3L.string_gameRunning), MessageBoxButton.OK, MessageBoxImage.Error); return; } } else { // Point to existing game installation Log.Information(@"BeginBackup() with IsCustomOption."); var linkWarning = M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_linkTargetWontBeModdable), M3L.GetString(M3L.string_linkWarning), MessageBoxButton.OKCancel, MessageBoxImage.Warning); if (linkWarning == MessageBoxResult.Cancel) { Log.Information(@"User aborted linking due to dialog"); return; } Log.Information(@"Prompting user to select executable of link target"); var gameexe = Utilities.PromptForGameExecutable(new[] { Game }); if (gameexe == null) { return; } targetToBackup = new GameTarget(Game, Utilities.GetGamePathFromExe(Game, gameexe), false, true); if (AvailableBackupSources.Any(x => x.TargetPath.Equals(targetToBackup.TargetPath, StringComparison.InvariantCultureIgnoreCase))) { // Can't point to an existing modding target Log.Error(@"This target is not valid to point to as a backup: It is listed a modding target already, it must be removed as a target first"); M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialog_linkFailedAlreadyATarget), M3L.GetString(M3L.string_cannotLinkGameCopy), MessageBoxButton.OK, MessageBoxImage.Error); return; } var validationFailureReason = targetToBackup.ValidateTarget(ignoreCmmVanilla: true); if (!targetToBackup.IsValid) { Log.Error(@"This installation is not valid to point to as a backup: " + validationFailureReason); M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialog_linkFailedInvalidTarget, validationFailureReason), M3L.GetString(M3L.string_invalidGameCopy), MessageBoxButton.OK, MessageBoxImage.Error); return; } } NamedBackgroundWorker nbw = new NamedBackgroundWorker(Game + @"Backup"); nbw.WorkerReportsProgress = true; nbw.ProgressChanged += (a, b) => { if (b.UserState is double d) { window.TaskBarItemInfoHandler.ProgressValue = d; } else if (b.UserState is TaskbarItemProgressState tbs) { window.TaskBarItemInfoHandler.ProgressState = tbs; } }; nbw.DoWork += (a, b) => { Log.Information(@"Starting the backup thread. Checking path: " + targetToBackup.TargetPath); BackupInProgress = true; bool end = false; List <string> nonVanillaFiles = new List <string>(); void nonVanillaFileFoundCallback(string filepath) { Log.Error($@"Non-vanilla file found: {filepath}"); nonVanillaFiles.Add(filepath); } List <string> inconsistentDLC = new List <string>(); void inconsistentDLCFoundCallback(string filepath) { if (targetToBackup.Supported) { Log.Error($@"DLC is in an inconsistent state: {filepath}"); inconsistentDLC.Add(filepath); } else { Log.Error(@"Detected an inconsistent DLC, likely due to an unofficial copy of the game"); } } ProgressVisible = true; ProgressIndeterminate = true; BackupStatus = M3L.GetString(M3L.string_validatingBackupSource); Log.Information(@"Checking target is vanilla"); bool isVanilla = VanillaDatabaseService.ValidateTargetAgainstVanilla(targetToBackup, nonVanillaFileFoundCallback); Log.Information(@"Checking DLC consistency"); bool isDLCConsistent = VanillaDatabaseService.ValidateTargetDLCConsistency(targetToBackup, inconsistentDLCCallback: inconsistentDLCFoundCallback); Log.Information(@"Checking only vanilla DLC is installed"); List <string> dlcModsInstalled = VanillaDatabaseService.GetInstalledDLCMods(targetToBackup).Select(x => { var tpmi = ThirdPartyServices.GetThirdPartyModInfo(x, targetToBackup.Game); if (tpmi != null) { return($@"{x} ({tpmi.modname})"); } return(x); }).ToList(); List <string> installedDLC = VanillaDatabaseService.GetInstalledOfficialDLC(targetToBackup); List <string> allOfficialDLC = MEDirectories.OfficialDLC(targetToBackup.Game); if (installedDLC.Count() < allOfficialDLC.Count()) { var dlcList = string.Join("\n - ", allOfficialDLC.Except(installedDLC).Select(x => $@"{MEDirectories.OfficialDLCNames(targetToBackup.Game)[x]} ({x})")); //do not localize dlcList = @" - " + dlcList; Log.Information(@"The following dlc will be missing in the backup if user continues: " + dlcList); Application.Current.Dispatcher.Invoke(delegate { var cancelDueToNotAllDLC = M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_notAllDLCInstalled, dlcList), M3L.GetString(M3L.string_someDlcNotInstalled), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (cancelDueToNotAllDLC == MessageBoxResult.No) { end = true; EndBackup(); return; } }); } if (end) { return; } if (isVanilla && isDLCConsistent && dlcModsInstalled.Count == 0) { BackupStatus = M3L.GetString(M3L.string_waitingForUserInput); string backupPath = null; if (!targetToBackup.IsCustomOption) { // Creating a new backup nbw.ReportProgress(0, TaskDialogProgressBarState.Paused); Application.Current.Dispatcher.Invoke(delegate { Log.Information(@"Prompting user to select backup destination"); CommonOpenFileDialog m = new CommonOpenFileDialog { IsFolderPicker = true, EnsurePathExists = true, Title = M3L.GetString(M3L.string_selectBackupDestination) }; if (m.ShowDialog() == CommonFileDialogResult.Ok) { backupPath = m.FileName; Log.Information(@"Backup path chosen: " + backupPath); bool okToBackup = validateBackupPath(backupPath, targetToBackup); if (!okToBackup) { end = true; EndBackup(); return; } } else { end = true; EndBackup(); return; } }); if (end) { return; } nbw.ReportProgress(0, TaskbarItemProgressState.Indeterminate); } else { Log.Information(@"Linking existing backup at " + targetToBackup.TargetPath); backupPath = targetToBackup.TargetPath; // Linking existing backup Application.Current.Dispatcher.Invoke(delegate { bool okToBackup = validateBackupPath(targetToBackup.TargetPath, targetToBackup); if (!okToBackup) { end = true; EndBackup(); return; } }); } if (end) { return; } if (!targetToBackup.IsCustomOption) { #region callbacks and copy code // Copy to new backup void fileCopiedCallback() { ProgressValue++; if (ProgressMax > 0) { nbw.ReportProgress(0, ProgressValue * 1.0 / ProgressMax); } } string dlcFolderpath = MEDirectories.DLCPath(targetToBackup) + '\\'; int dlcSubStringLen = dlcFolderpath.Length; bool aboutToCopyCallback(string file) { try { if (file.Contains(@"\cmmbackup\")) { return(false); //do not copy cmmbackup files } if (file.StartsWith(dlcFolderpath, StringComparison.InvariantCultureIgnoreCase)) { //It's a DLC! string dlcname = file.Substring(dlcSubStringLen); var dlcFolderNameEndPos = dlcname.IndexOf('\\'); if (dlcFolderNameEndPos > 0) { dlcname = dlcname.Substring(0, dlcFolderNameEndPos); if (MEDirectories.OfficialDLCNames(targetToBackup.Game) .TryGetValue(dlcname, out var hrName)) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, hrName); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, dlcname); } } else { // Loose files in the DLC folder BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.GetString(M3L.string_basegame)); } } else { //It's basegame if (file.EndsWith(@".bik")) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.GetString(M3L.string_movies)); } else if (new FileInfo(file).Length > 52428800) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, Path.GetFileName(file)); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.GetString(M3L.string_basegame)); } } } catch (Exception e) { Crashes.TrackError(e, new Dictionary <string, string>() { { @"dlcFolderpath", dlcFolderpath }, { @"dlcSubStringLen", dlcSubStringLen.ToString() }, { @"file", file } }); } return(true); } void totalFilesToCopyCallback(int total) { ProgressValue = 0; ProgressIndeterminate = false; ProgressMax = total; nbw.ReportProgress(0, TaskbarItemProgressState.Normal); } BackupStatus = M3L.GetString(M3L.string_creatingBackup); Log.Information($@"Backing up {targetToBackup.TargetPath} to {backupPath}"); nbw.ReportProgress(0, TaskbarItemProgressState.Normal); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(targetToBackup.TargetPath), new DirectoryInfo(backupPath), totalItemsToCopyCallback: totalFilesToCopyCallback, aboutToCopyCallback: aboutToCopyCallback, fileCopiedCallback: fileCopiedCallback, ignoredExtensions: new[] { @"*.pdf", @"*.mp3" }); #endregion } // Write key switch (Game) { case Mod.MEGame.ME1: case Mod.MEGame.ME2: Utilities.WriteRegistryKey(App.BACKUP_REGISTRY_KEY, Game + @"VanillaBackupLocation", backupPath); break; case Mod.MEGame.ME3: Utilities.WriteRegistryKey(App.REGISTRY_KEY_ME3CMM, @"VanillaCopyLocation", backupPath); break; } var cmmvanilla = Path.Combine(backupPath, @"cmm_vanilla"); if (!File.Exists(cmmvanilla)) { Log.Information($@"Writing cmm_vanilla to " + cmmvanilla); File.Create(cmmvanilla).Close(); } Log.Information($@"Backup completed."); Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Success" }, { @"Type", targetToBackup.IsCustomOption ? @"Linked" : @"Copy" } }); EndBackup(); return; } if (!isVanilla) { //Show UI for non vanilla Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, Game modified" } }); b.Result = (nonVanillaFiles, M3L.GetString(M3L.string_cannotBackupModifiedGame), M3L.GetString(M3L.string_followingFilesDoNotMatchTheVanillaDatabase)); } else if (!isDLCConsistent) { Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, DLC inconsistent" } }); if (targetToBackup.Supported) { b.Result = (inconsistentDLC, M3L.GetString(M3L.string_inconsistentDLCDetected), M3L.GetString(M3L.string_dialogTheFollowingDLCAreInAnInconsistentState)); } else { b.Result = (M3L.GetString(M3L.string_inconsistentDLCDetected), M3L.GetString(M3L.string_inconsistentDLCDetectedUnofficialGame)); } } else if (dlcModsInstalled.Count > 0) { Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, DLC mods found" } }); b.Result = (dlcModsInstalled, M3L.GetString(M3L.string_dlcModsAreInstalled), M3L.GetString(M3L.string_dialogDLCModsWereDetectedCannotBackup)); } EndBackup(); }; nbw.RunWorkerCompleted += (a, b) => { if (b.Error != null) { Log.Error($@"Exception occured in {nbw.Name} thread: {b.Error.Message}"); } window.TaskBarItemInfoHandler.ProgressState = TaskbarItemProgressState.None; if (b.Result is (List <string> listItems, string title, string text)) { ListDialog ld = new ListDialog(listItems, title, text, window); ld.Show(); } else if (b.Result is (string errortitle, string message)) { M3L.ShowDialog(window, message, errortitle, MessageBoxButton.OK, MessageBoxImage.Error); } CommandManager.InvalidateRequerySuggested(); }; nbw.RunWorkerAsync(); }
private void CompileIntoGame() { NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"MixinManager CompileIntoGameThread"); List <string> failedApplications = new List <string>(); nbw.DoWork += (a, b) => { BottomLeftMessage = M3L.GetString(M3L.string_compilingMixins); OperationInProgress = true; //DEBUG STUFF #if DEBUG int numCoresToApplyWith = 1; #else var numCoresToApplyWith = Environment.ProcessorCount; if (numCoresToApplyWith > 4) { numCoresToApplyWith = 4; //no more than 4 as this uses a lot of memory } #endif var mixins = AvailableOfficialMixins.Where(x => x.UISelectedForUse).ToList(); MixinHandler.LoadPatchDataForMixins(mixins); //before dynamic void failedApplicationCallback(string str) { failedApplications.Add(str); } var compilingListsPerModule = MixinHandler.GetMixinApplicationList(mixins, failedApplicationCallback); if (failedApplications.Any()) { //Error building list Log.Information(@"Aborting mixin install due to incompatible selection of mixins"); return; } ProgressBarMax = mixins.Count(); ProgressBarValue = 0; int numdone = 0; void completedSingleApplicationCallback() { var val = Interlocked.Increment(ref numdone); ProgressBarValue = val; } //Mixins are ready to be applied Parallel.ForEach(compilingListsPerModule, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount > numCoresToApplyWith ? numCoresToApplyWith : Environment.ProcessorCount }, mapping => { var dlcFolderName = ModMakerCompiler.ModmakerChunkNameToDLCFoldername(mapping.Key.ToString()); //var outdir = Path.Combine(modpath, ModMakerCompiler.HeaderToDefaultFoldername(mapping.Key), @"CookedPCConsole"); //Directory.CreateDirectory(outdir); if (mapping.Key == ModJob.JobHeader.BASEGAME) { //basegame foreach (var file in mapping.Value) { try { using var vanillaPackageAsStream = VanillaDatabaseService.FetchBasegameFile(Mod.MEGame.ME3, Path.GetFileName(file.Key)); //packageAsStream.WriteToFile(@"C:\users\dev\desktop\compressed.pcc"); using var decompressedStream = MEPackage.GetDecompressedPackageStream(vanillaPackageAsStream, true); decompressedStream.Position = 0; var vanillaPackage = MEPackageHandler.OpenMEPackage(decompressedStream, $@"Vanilla - {Path.GetFileName(file.Key)}"); //decompressedStream.WriteToFile(@"C:\users\dev\desktop\decompressed.pcc"); using var mixinModifiedStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, completedSingleApplicationCallback, failedApplicationCallback); mixinModifiedStream.Position = 0; var modifiedPackage = MEPackageHandler.OpenMEPackage(mixinModifiedStream, $@"Mixin Modified - {Path.GetFileName(file.Key)}"); // three way merge: get target stream var targetFile = Path.Combine(MEDirectories.CookedPath(SelectedInstallTarget), Path.GetFileName(file.Key)); var targetPackage = MEPackageHandler.OpenMEPackage(targetFile); var merged = ThreeWayPackageMerge.AttemptMerge(vanillaPackage, modifiedPackage, targetPackage); if (merged) { targetPackage.save(); Log.Information("Three way merge succeeded for " + targetFile); } else { Log.Error("Could not merge three way merge into " + targetFile); } //var outfile = Path.Combine(outdir, Path.GetFileName(file.Key)); //package.save(outfile, false); // don't compress //finalStream.WriteToFile(outfile); //File.WriteAllBytes(outfile, finalStream.ToArray()); } catch (Exception e) { var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName)); Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}"); failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message)); } } } else { //dlc var dlcPackage = VanillaDatabaseService.FetchVanillaSFAR(dlcFolderName); //do not have to open file multiple times. var targetCookedPCDir = Path.Combine(MEDirectories.DLCPath(SelectedInstallTarget), dlcFolderName, @"CookedPCConsole"); var sfar = mapping.Key == ModJob.JobHeader.TESTPATCH ? Utilities.GetTestPatchPath(SelectedInstallTarget) : Path.Combine(targetCookedPCDir, @"Default.sfar"); bool unpacked = new FileInfo(sfar).Length == 32; DLCPackage targetDLCPackage = unpacked ? null : new DLCPackage(sfar); //cache SFAR target foreach (var file in mapping.Value) { try { using var vanillaPackageAsStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, file.Key, forcedDLC: dlcPackage); using var decompressedStream = MEPackage.GetDecompressedPackageStream(vanillaPackageAsStream); decompressedStream.Position = 0; var vanillaPackage = MEPackageHandler.OpenMEPackage(decompressedStream, $@"VanillaDLC - {Path.GetFileName(file.Key)}"); using var mixinModifiedStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, completedSingleApplicationCallback, failedApplicationCallback); mixinModifiedStream.Position = 0; var modifiedPackage = MEPackageHandler.OpenMEPackage(mixinModifiedStream, $@"Mixin Modified - {Path.GetFileName(file.Key)}"); // three way merge: get target stream // must see if DLC is unpacked first MemoryStream targetFileStream = null; //Packed if (unpacked) { targetFileStream = new MemoryStream(File.ReadAllBytes(Path.Combine(targetCookedPCDir, file.Key))); } else { targetFileStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, Path.GetFileName(file.Key), forcedDLC: targetDLCPackage); } var targetPackage = MEPackageHandler.OpenMEPackage(targetFileStream, $@"Target package {dlcFolderName} - {file.Key}, from SFAR: {unpacked}"); var merged = ThreeWayPackageMerge.AttemptMerge(vanillaPackage, modifiedPackage, targetPackage); if (merged) { if (unpacked) { targetPackage.save(); Log.Information("Three way merge succeeded for " + targetPackage.FilePath); } else { var finalSTream = targetPackage.saveToStream(); targetDLCPackage.ReplaceEntry(finalSTream.ToArray(), targetDLCPackage.FindFileEntry(Path.GetFileName(file.Key))); Log.Information("Three way merge succeeded for " + targetPackage.FileSourceForDebugging); } } else { Log.Error("Could not merge three way merge into " + targetFileStream); } } catch (Exception e) { var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName)); Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}"); failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message)); } //finalStream.WriteToFile(outfile); } } }); MixinHandler.FreeME3TweaksPatchData(); var percent = 0; //this is used to save a localization BottomLeftMessage = $"Running AutoTOC on game {percent}%"; //Run autotoc void tocingUpdate(int percent) { BottomLeftMessage = $"Running AutoTOC on game {percent}%"; } AutoTOC.RunTOCOnGameTarget(SelectedInstallTarget, tocingUpdate); //Generate moddesc //IniData ini = new IniData(); //ini[@"ModManager"][@"cmmver"] = App.HighestSupportedModDesc.ToString(CultureInfo.InvariantCulture); //prevent commas //ini[@"ModInfo"][@"game"] = @"ME3"; //ini[@"ModInfo"][@"modname"] = modname; //ini[@"ModInfo"][@"moddev"] = App.AppVersionHR; //ini[@"ModInfo"][@"moddesc"] = M3L.GetString(M3L.string_compiledFromTheFollowingMixins); //ini[@"ModInfo"][@"modver"] = @"1.0"; //generateRepaceFilesMapping(ini, modpath); //File.WriteAllText(Path.Combine(modpath, @"moddesc.ini"), ini.ToString()); }; nbw.RunWorkerCompleted += (a, b) => { OperationInProgress = false; ClearMixinHandler(); if (failedApplications.Count > 0) { var ld = new ListDialog(failedApplications, M3L.GetString(M3L.string_failedToApplyAllMixins), M3L.GetString(M3L.string_theFollowingMixinsFailedToApply), mainwindow); ld.ShowDialog(); } /*if (modpath != null) * { * OnClosing(new DataEventArgs(modpath)); * } * else * {*/ BottomLeftMessage = "Mixins installed, maybe. Check logs"; //} }; CompilePanelButton.IsOpen = false; nbw.RunWorkerAsync(); }
private void BeginBackup() { if (Utilities.IsGameRunning(BackupSourceTarget.Game)) { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_cannotBackupGameWhileRunning, Utilities.GetGameName(BackupSourceTarget.Game)), M3L.GetString(M3L.string_gameRunning), MessageBoxButton.OK, MessageBoxImage.Error); return; } NamedBackgroundWorker bw = new NamedBackgroundWorker(Game.ToString() + @"Backup"); bw.DoWork += (a, b) => { BackupInProgress = true; List <string> nonVanillaFiles = new List <string>(); void nonVanillaFileFoundCallback(string filepath) { Log.Error($@"Non-vanilla file found: {filepath}"); nonVanillaFiles.Add(filepath); } List <string> inconsistentDLC = new List <string>(); void inconsistentDLCFoundCallback(string filepath) { if (BackupSourceTarget.Supported) { Log.Error($@"DLC is in an inconsistent state: {filepath}"); inconsistentDLC.Add(filepath); } else { Log.Error(@"Detected an inconsistent DLC, likely due to an unofficial copy of the game"); } } ProgressVisible = true; ProgressIndeterminate = true; BackupStatus = M3L.GetString(M3L.string_validatingBackupSource); VanillaDatabaseService.LoadDatabaseFor(Game); bool isVanilla = VanillaDatabaseService.ValidateTargetAgainstVanilla(BackupSourceTarget, nonVanillaFileFoundCallback); bool isDLCConsistent = VanillaDatabaseService.ValidateTargetDLCConsistency(BackupSourceTarget, inconsistentDLCCallback: inconsistentDLCFoundCallback); List <string> dlcModsInstalled = VanillaDatabaseService.GetInstalledDLCMods(BackupSourceTarget); if (isVanilla && isDLCConsistent && dlcModsInstalled.Count == 0) { BackupStatus = M3L.GetString(M3L.string_waitingForUserInput); string backupPath = null; bool end = false; Application.Current.Dispatcher.Invoke(delegate { Log.Error(@"Prompting user to select backup destination"); CommonOpenFileDialog m = new CommonOpenFileDialog { IsFolderPicker = true, EnsurePathExists = true, Title = M3L.GetString(M3L.string_selectBackupDestination) }; if (m.ShowDialog() == CommonFileDialogResult.Ok) { //Check empty backupPath = m.FileName; if (Directory.Exists(backupPath)) { if (Directory.GetFiles(backupPath).Length > 0 || Directory.GetDirectories(backupPath).Length > 0) { //Directory not empty Log.Error(@"Selected backup directory is not empty."); M3L.ShowDialog(window, M3L.GetString(M3L.string_directoryIsNotEmptyMustBeEmpty), M3L.GetString(M3L.string_directoryNotEmpty), MessageBoxButton.OK, MessageBoxImage.Error); end = true; EndBackup(); return; } } } else { end = true; EndBackup(); return; } }); if (end) { return; } #region callbacks void fileCopiedCallback() { ProgressValue++; } string dlcFolderpath = MEDirectories.DLCPath(BackupSourceTarget) + '\\'; int dlcSubStringLen = dlcFolderpath.Length; bool aboutToCopyCallback(string file) { if (file.Contains(@"\cmmbackup\")) { return(false); //do not copy cmmbackup files } if (file.StartsWith(dlcFolderpath)) { //It's a DLC! string dlcname = file.Substring(dlcSubStringLen); dlcname = dlcname.Substring(0, dlcname.IndexOf('\\')); if (MEDirectories.OfficialDLCNames(BackupSourceTarget.Game).TryGetValue(dlcname, out var hrName)) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, hrName); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, dlcname); } } else { //It's basegame if (file.EndsWith(@".bik")) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.string_movies); } else if (new FileInfo(file).Length > 52428800) { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, Path.GetFileName(file)); } else { BackupStatusLine2 = M3L.GetString(M3L.string_interp_backingUpX, M3L.string_basegame); } } return(true); } void totalFilesToCopyCallback(int total) { ProgressValue = 0; ProgressIndeterminate = false; ProgressMax = total; } #endregion BackupStatus = M3L.GetString(M3L.string_creatingBackup); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(BackupSourceTarget.TargetPath), new DirectoryInfo(backupPath), totalItemsToCopyCallback: totalFilesToCopyCallback, aboutToCopyCallback: aboutToCopyCallback, fileCopiedCallback: fileCopiedCallback, ignoredExtensions: new[] { @"*.pdf", @"*.mp3" }); switch (Game) { case Mod.MEGame.ME1: case Mod.MEGame.ME2: Utilities.WriteRegistryKey(App.BACKUP_REGISTRY_KEY, Game + @"VanillaBackupLocation", backupPath); break; case Mod.MEGame.ME3: Utilities.WriteRegistryKey(App.REGISTRY_KEY_ME3CMM, @"VanillaCopyLocation", backupPath); break; } Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Success" } }); EndBackup(); return; } if (!isVanilla) { //Show UI for non vanilla Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, Game modified" } }); b.Result = (nonVanillaFiles, M3L.GetString(M3L.string_cannotBackupModifiedGame), M3L.GetString(M3L.string_followingFilesDoNotMatchTheVanillaDatabase)); } else if (!isDLCConsistent) { Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, DLC inconsistent" } }); if (BackupSourceTarget.Supported) { b.Result = (inconsistentDLC, M3L.GetString(M3L.string_inconsistentDLCDetected), M3L.GetString(M3L.string_dialogTheFollowingDLCAreInAnInconsistentState)); } else { b.Result = (M3L.GetString(M3L.string_inconsistentDLCDetected), M3L.GetString(M3L.string_inconsistentDLCDetectedUnofficialGame)); } } else if (dlcModsInstalled.Count > 0) { Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"Result", @"Failure, DLC mods found" } }); b.Result = (dlcModsInstalled, M3L.GetString(M3L.string_dlcModsAreInstalled), M3L.GetString(M3L.string_dialogDLCModsWereDetectedCannotBackup)); } EndBackup(); }; bw.RunWorkerCompleted += (a, b) => { if (b.Result is (List <string> listItems, string title, string text)) { ListDialog ld = new ListDialog(listItems, title, text, window); ld.Show(); } else if (b.Result is (string errortitle, string message)) { M3L.ShowDialog(window, message, errortitle, MessageBoxButton.OK, MessageBoxImage.Error); } CommandManager.InvalidateRequerySuggested(); }; bw.RunWorkerAsync(); }
private async void ImportDLCFolder_BackgroundThread(object sender, DoWorkEventArgs e) { OperationInProgress = true; var sourceDir = Path.Combine(MEDirectories.DLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName); var library = Utilities.GetModDirectoryForGame(SelectedTarget.Game); var destinationName = Utilities.SanitizePath(ModNameText); var modFolder = Path.Combine(library, destinationName); var copyDestination = Path.Combine(modFolder, SelectedDLCFolder.DLCFolderName); var outInfo = Directory.CreateDirectory(copyDestination); Log.Information($@"Importing mod: {sourceDir} -> {copyDestination}"); int numToDo = 0; int numDone = 0; void totalItemToCopyCallback(int total) { numToDo = total; ProgressBarMax = total; } void fileCopiedCallback() { numDone++; ProgressBarValue = numDone; } CopyDir.CopyAll_ProgressBar(new DirectoryInfo(sourceDir), outInfo, totalItemToCopyCallback, fileCopiedCallback); //Write a moddesc IniData ini = new IniData(); ini[@"ModManager"][@"cmmver"] = App.HighestSupportedModDesc.ToString(CultureInfo.InvariantCulture); //prevent commas ini[@"ModInfo"][@"game"] = SelectedTarget.Game.ToString(); ini[@"ModInfo"][@"modname"] = ModNameText; ini[@"ModInfo"][@"moddev"] = M3L.GetString(M3L.string_importedFromGame); ini[@"ModInfo"][@"moddesc"] = M3L.GetString(M3L.string_defaultDescriptionForImportedMod, Utilities.GetGameName(SelectedTarget.Game), DateTime.Now); ini[@"ModInfo"][@"modver"] = M3L.GetString(M3L.string_unknown); ini[@"CUSTOMDLC"][@"sourcedirs"] = SelectedDLCFolder.DLCFolderName; ini[@"CUSTOMDLC"][@"destdirs"] = SelectedDLCFolder.DLCFolderName; var moddescPath = Path.Combine(modFolder, @"moddesc.ini"); File.WriteAllText(moddescPath, ini.ToString()); //Generate and load mod Mod m = new Mod(moddescPath, Mod.MEGame.ME3); e.Result = m; Log.Information(@"Mod import complete."); if (!CurrentModInTPMI) { //Submit telemetry to ME3Tweaks try { TPMITelemetrySubmissionForm.TelemetryPackage tp = TPMITelemetrySubmissionForm.GetTelemetryPackageForDLC(SelectedTarget.Game, MEDirectories.DLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName, SelectedDLCFolder.DLCFolderName, //same as foldername as this is already installed ModNameText, @"N/A", ModSiteText, null ); tp.SubmitPackage(); } catch (Exception ex) { Log.Error(@"Cannot submit telemetry: " + ex.Message); } } }