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 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 void ImportDLCFolder_BackgroundThread(object sender, DoWorkEventArgs e) { OperationInProgress = true; var sourceDir = Path.Combine(M3Directories.GetDLCPath(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 var m = new Mod(moddescPath, 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, M3Directories.GetDLCPath(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 Migration_ContentRendered(object sender, EventArgs e) { NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ME3CMMMigration"); nbw.DoWork += (a, b) => { bool cleanup = false; bool migrated = true; Log.Information(@">>>> ME3CMMMigration Thread"); Log.Information(@"Validate ME3CMM folders and files"); var exeDir = Utilities.GetMMExecutableDirectory(); var modsDir = Path.Combine(exeDir, @"mods"); var dataDir = Path.Combine(exeDir, @"data"); try { if (Directory.Exists(modsDir) && Directory.Exists(dataDir)) { Log.Information(@"mods and data dir exist."); // 1. MIGRATE MODS Log.Information(@"Step 1: Migrate mods"); MigratingModsTask.SetInProgress(); var targetModLibrary = Utilities.GetModsDirectory(); targetModLibrary = Path.Combine(targetModLibrary, @"ME3"); if (!Directory.Exists(targetModLibrary)) { Log.Information(@"Creating target mod library directory: " + targetModLibrary); Directory.CreateDirectory(targetModLibrary); } var sameRoot = Path.GetPathRoot(targetModLibrary) == Path.GetPathRoot(modsDir); var directoriesInModsDir = Directory.GetDirectories(modsDir); var numToMigrate = directoriesInModsDir.Count(x => File.Exists(Path.Combine(x, @"moddesc.ini"))); var numMigrated = 0; foreach (var modDirToMove in directoriesInModsDir) { var moddesc = Path.Combine(modDirToMove, @"moddesc.ini"); if (File.Exists(moddesc)) { numMigrated++; MigratingModsTask.TaskText = M3L.GetString(M3L.string_interp_migratingModsXoFY, numMigrated, numToMigrate); //Migrate this folder var targetDir = Path.Combine(targetModLibrary, Path.GetFileName(modDirToMove)); Log.Information($@"Migrating mod into ME3 directory: {modDirToMove} -> {targetDir}"); if (!Directory.Exists(targetDir)) { if (sameRoot) { Directory.Move(modDirToMove, targetDir); } else { Log.Information(@" >> Copying existing mod directory"); Directory.CreateDirectory(targetDir); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(modDirToMove), new DirectoryInfo(targetDir)); Log.Information(@" >> Deleting existing directory"); Utilities.DeleteFilesAndFoldersRecursively(modDirToMove); } Log.Information($@"Migrated {modDirToMove}"); } else { Log.Warning(@"Target directory already exists! Not migrating this directory."); } } } MigratingModsTask.SetDone(); Log.Information(@"Step 1: Finished mod migration"); // 2. MIGRATE SETTINGS MigratingSettings.SetInProgress(); Log.Information(@"Step 2: Begin settings migration"); var me3cmminif = Path.Combine(exeDir, @"me3cmm.ini"); if (File.Exists(me3cmminif)) { Log.Information(@"Migrating me3cmm.ini settings"); IniData me3cmmini = new FileIniDataParser().ReadFile(me3cmminif); var updaterServiceUsername = me3cmmini[@"UpdaterService"][@"username"]; if (string.IsNullOrWhiteSpace(Settings.UpdaterServiceUsername) && !string.IsNullOrWhiteSpace(updaterServiceUsername)) { Settings.UpdaterServiceUsername = updaterServiceUsername; Log.Information(@"Migrated Updater Service Username: "******"UpdaterService"][@"manifestspath"]; if (string.IsNullOrWhiteSpace(Settings.UpdaterServiceManifestStoragePath) && !string.IsNullOrWhiteSpace(manifestsPath)) { Settings.UpdaterServiceManifestStoragePath = manifestsPath; Log.Information(@"Migrated Updater Service Manifests Path: " + manifestsPath); } var lzmaStoragePath = me3cmmini[@"UpdaterService"][@"lzmastoragepath"]; if (string.IsNullOrWhiteSpace(Settings.UpdaterServiceLZMAStoragePath) && !string.IsNullOrWhiteSpace(lzmaStoragePath)) { Settings.UpdaterServiceLZMAStoragePath = lzmaStoragePath; Log.Information(@"Migrated Updater Service LZMA Storage Path: " + lzmaStoragePath); } //Modmaker Auto Injections var controllerModOption = me3cmmini[@"Settings"][@"controllermoduser"]; if (Settings.ModMakerControllerModOption == false && controllerModOption == "1") { Settings.ModMakerControllerModOption = true; //Set to true (default is false) Log.Information(@"Migrated Auto install controller mixins for ModMaker (true)"); } var keybindsInjectionOption = me3cmmini[@"Settings"][@"autoinjectkeybinds"]; if (Settings.ModMakerAutoInjectCustomKeybindsOption == false && keybindsInjectionOption == "1") { Settings.ModMakerAutoInjectCustomKeybindsOption = true; //Set to true (default is false) Log.Information(@"Migrated Auto inject keybinds for ModMaker (true)"); } //Settings.Save(); } //Migrate BIOGAME_DIRECTORIES var biogameDirsF = Path.Combine(dataDir, @"BIOGAME_DIRECTORIES"); if (File.Exists(biogameDirsF)) { var biodirs = File.ReadAllLines(biogameDirsF); foreach (var line in biodirs) { var gamepath = Directory.GetParent(line).FullName; Log.Information(@"Validating ME3CMM target: " + gamepath); GameTarget t = new GameTarget(MEGame.ME3, gamepath, false); var failureReason = t.ValidateTarget(); if (failureReason == null) { Utilities.AddCachedTarget(t); } else { Log.Error($@"Not migrating invalid target {gamepath}: {failureReason}"); } } } //Migrate ALOT Installer, if found var alotInstallerDir = Path.Combine(dataDir, @"ALOTInstaller"); if (Directory.Exists(alotInstallerDir)) { Log.Information(@"Migrating ALOTInstaller tool"); var externalToolsALOTInstaller = Path.Combine(dataDir, @"ExternalTools", @"ALOTInstaller"); Directory.CreateDirectory(Path.Combine(dataDir, @"ExternalTools")); Directory.Move(alotInstallerDir, externalToolsALOTInstaller); Log.Information(@"Migrated ALOTInstaller to ExternalTools"); } //Migrate ME3Explorer, if found var me3explorerDir = Path.Combine(dataDir, @"ME3Explorer"); if (Directory.Exists(me3explorerDir)) { Log.Information(@"Migrating ME3Explorer tool"); var externalToolsME3ExplorerDir = Path.Combine(dataDir, @"ExternalTools", @"ME3Explorer"); Directory.CreateDirectory(Path.Combine(dataDir, @"ExternalTools")); Directory.Move(me3explorerDir, externalToolsME3ExplorerDir); Log.Information(@"Migrated ME3Explorer to ExternalTools"); } //Migrate cached modmaker mods var modmakerCacheDir = Path.Combine(dataDir, @"modmaker", @"cache"); if (Directory.Exists(modmakerCacheDir)) { var modmakerXmls = Directory.GetFiles(modmakerCacheDir, @"*.xml"); if (modmakerXmls.Any()) { var mmNewCacheDir = Utilities.GetModmakerDefinitionsCache(); Log.Information(@"Migrating ME3Tweaks ModMaker cached files"); foreach (var f in modmakerXmls) { var fname = Path.GetFileName(f); var destName = Path.Combine(mmNewCacheDir, fname); if (!File.Exists(destName)) { Log.Information(@"Migrating modmaker mod delta definition file " + fname); File.Move(f, destName); } } Log.Information(@"Migrated ModMaker cached files"); } } //MIGRATE 7z.dll - this will only perform an interim fix (maybe network failure?) as we use 19.0 and ME3MM used 18.05 var me3mm7z = Path.Combine(dataDir, @"tools\ModManagerCommandLine\x64\7z.dll"); var target7z = Utilities.Get7zDllPath(); if (File.Exists(me3mm7z) && !File.Exists(target7z)) { Log.Information($@"Copying ME3MM 7z.dll to ME3Tweaks Mod Manager dll location: {me3mm7z} -> {target7z}"); File.Copy(me3mm7z, target7z, true); Log.Information(@"Copied ME3MM 7z dll"); } // Migrate DLC_AUTH_FAIL var me3mmAuthFail = Path.Combine(dataDir, @"help\DLC_AUTH_FAIL.png"); var targetAuthFail = Path.Combine(Utilities.GetLocalHelpResourcesDirectory(), @"DLC_AUTH_FAIL.png"); if (File.Exists(me3mmAuthFail) && !File.Exists(targetAuthFail)) { Log.Information($@"Copying DLC_AUTH_FAIL help resource to ME3Tweaks Mod Manager help resources location: {me3mmAuthFail} -> {targetAuthFail}"); File.Copy(me3mmAuthFail, targetAuthFail, true); Log.Information(@"Copied DLC_AUTH_FAIL"); } //MIGRATE MOD GROUPS (batch install queues) var modGroupsDir = Path.Combine(dataDir, @"modgroups"); if (Directory.Exists(modGroupsDir)) { Log.Information(@"Migrating batch mod groups"); var queues = Directory.EnumerateFiles(modGroupsDir, @"*.txt").ToList(); foreach (var queue in queues) { var biqDest = Path.Combine(Utilities.GetBatchInstallGroupsFolder(), Path.GetFileName(queue)); Log.Information($@"Migrating mod install group: {queue} -> {biqDest}"); File.Move(queue, biqDest, true); Log.Information(@"Migrated " + Path.GetFileName(queue)); } } // MIGRATE override var overrideDir = Path.Combine(dataDir, @"override"); if (Directory.Exists(overrideDir)) { Log.Information(@"Migrating override"); var filesInKBDir = Directory.EnumerateFiles(overrideDir, @"*.xml").ToList(); foreach (var file in filesInKBDir) { var keybindDir = Path.Combine(Utilities.GetKeybindsOverrideFolder(), @"me3-" + Path.GetFileName(file)); Log.Information($@"Migrating keybinds override: {file} -> {keybindDir}"); File.Move(file, keybindDir, true); Log.Information(@"Migrated " + Path.GetFileName(file)); } } MigratingSettings.SetDone(); Log.Information(@"Step 2: Finished settings migration"); // 3. CLEANUP App.Current.Dispatcher.Invoke(delegate { cleanup = M3L.ShowDialog(null, M3L.GetString(M3L.string_dialog_performMe3cmmCleanup), M3L.GetString(M3L.string_performCleanupQuestion), MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes) == MessageBoxResult.Yes; }); if (cleanup) { Log.Information(@"Step 3: Cleaning up"); CleaningUpTask.SetInProgress(); var directoriesInDataDir = Directory.GetFileSystemEntries(dataDir); foreach (var entry in directoriesInDataDir) { var name = Path.GetFileName(entry); if (Directory.Exists(entry)) { switch (name.ToLower()) { case @"deployed mods": case @"externaltools": // Created by M3 at this point case @"patch_001_extracted": case @"pccdumps": //guess these might be useful continue; default: try { Log.Information(@"Deleting directory: " + entry); Utilities.DeleteFilesAndFoldersRecursively(entry, true); } catch (Exception e) { Log.Error($@"Unable to delete item in data directory: {entry}, reason: {e.Message}"); } break; } } else if (File.Exists(entry)) { try { Log.Information(@"Cleanup: Deleting file " + entry); File.Delete(entry); } catch (Exception e) { Log.Error($@"Unable to delete {entry}: {e.Message}"); } } } // Install redirect to ensure user shortcuts continue to work var me3cmmPath = Path.Combine(exeDir, @"ME3CMM.exe"); Log.Information(@"Writing redirector to " + me3cmmPath); Utilities.ExtractInternalFile(@"MassEffectModManagerCore.updater.ME3CMM.exe", me3cmmPath, true); } else { Log.Information(@"Skipping step 3: cleanup due to user request."); } CleaningUpTask.SetDone(); Log.Information(@"Step 3: Cleaned up"); Thread.Sleep(3000); } else { migrated = false; Log.Error(@"mods and/or data dir don't exist! We will not attempt migration."); } } catch (Exception e) { migrated = false; Log.Error(@"Error in migration: " + e.Message); Crashes.TrackError(e); } Analytics.TrackEvent(@"ME3CMM Migration", new Dictionary <string, string>() { { @"Migrated", migrated.ToString() }, { @"Cleaned up", cleanup.ToString() }, }); Log.Information(@"<<<< Exiting ME3CMMMigration Thread"); }; nbw.RunWorkerCompleted += (a, b) => { if (b.Error != null) { Log.Error($@"Exception occurred in {nbw.Name} thread: {b.Error.Message}"); } Log.Information(@"Migration has completed."); M3L.ShowDialog(null, M3L.GetString(M3L.string_dialog_me3cmmMigrationCompleted)); Close(); }; nbw.RunWorkerAsync(); }
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 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); }
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(); }
private void BeginRestore() { if (Utilities.IsGameRunning(Game)) { M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialogCannotRestoreXWhileItIsRunning, Game.ToGameName()), M3L.GetString(M3L.string_gameRunning), MessageBoxButton.OK, MessageBoxImage.Error); return; } var useNewMethod = M3L.ShowDialog(window, M3L.GetString(M3L.string_beta_useNewRestoreMethod), M3L.GetString(M3L.string_useBetaFeatureQuestion), MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes; bool restore = RestoreTarget.IsCustomOption || useNewMethod; //custom option is restore to custom location restore = restore || M3L.ShowDialog(window, M3L.GetString(M3L.string_dialog_restoringXWillDeleteGameDir, Game.ToGameName()), 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; // Nuke the LODs if (!RestoreTarget.IsCustomOption && RestoreTarget.Game.IsOTGame()) { Log.Information($@"Resetting LODs for {RestoreTarget.Game}"); Utilities.SetLODs(RestoreTarget, false, false, false); } string restoreTargetPath = b.Argument as string; string backupPath = BackupLocation; if (!useNewMethod) { 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?"); } 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.GetDLCPath(Game, backupPath) + '\\'; //\ at end makes sure we are restoring a subdir int dlcSubStringLen = dlcFolderpath.Length; Debug.WriteLine(@"DLC Folder: " + dlcFolderpath); Debug.Write(@"DLC Folder 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('\\'); if (index > 0) //Files directly in the DLC directory won't have path sep { 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); // LE: Backup Config file so settings don't get lost string configText = null; var configPath = RestoreTarget.Game.IsLEGame() ? M3Directories.GetLODConfigFile(RestoreTarget) : null; if (File.Exists(configPath)) { configText = File.ReadAllText(configPath); // backup to memory } Log.Information($@"Copying backup to game directory: {backupPath} -> {restoreTargetPath}"); if (useNewMethod) { string CurrentRCFile = null; RoboCommand rc = new RoboCommand(); rc.CopyOptions.Destination = restoreTargetPath; rc.CopyOptions.Source = backupPath; rc.CopyOptions.Mirror = true; rc.CopyOptions.MultiThreadedCopiesCount = 2; rc.OnCopyProgressChanged += (sender, args) => { ProgressIndeterminate = false; ProgressValue = (int)args.CurrentFileProgress; ProgressMax = 100; }; rc.OnFileProcessed += (sender, args) => { if (args.ProcessedFile.Name.StartsWith(backupPath) && args.ProcessedFile.Name.Length > backupPath.Length) { CurrentRCFile = args.ProcessedFile.Name.Substring(backupPath.Length + 1); BackupStatusLine2 = M3L.GetString(M3L.string_interp_copyingX, CurrentRCFile); } }; rc.Start().Wait(); } else { 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"); if (configText != null) { // Restore config file try { Directory.CreateDirectory(Directory.GetParent(configPath).FullName); File.WriteAllText(configPath, configText); Log.Information(@"Restored config file"); } catch (Exception e) { Log.Error($@"Could not restore config file: {e.Message}"); } } BackupCopyFinished(restoreTargetPath); } }; 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 restoreTargetPath = 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 restoreTargetPath = m.FileName; if (Directory.Exists(restoreTargetPath)) { if (Directory.GetFiles(restoreTargetPath).Length > 0 || Directory.GetDirectories(restoreTargetPath).Length > 0) { Log.Warning($@"The selected restore directory is not empty: {restoreTargetPath}"); //Directory not empty if (!useNewMethod) { M3L.ShowDialog(window, M3L.GetString(M3L .string_dialogDirectoryIsNotEmptyLocationToRestoreToMustBeEmpty), M3L.GetString(M3L.string_cannotRestoreToThisLocation), MessageBoxButton.OK, MessageBoxImage.Error); return; } else { // Warn user var shouldContinue = MessageBoxResult.Yes == M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_directoryNotEmptyWillDeleteEverything, restoreTargetPath), M3L.GetString(M3L.string_directoryNotEmpty), MessageBoxButton.YesNo, MessageBoxImage.Warning); if (!shouldContinue) { return; } Log.Warning($@"The user is continuing to new-gen restore on existing directory anyways"); } } //TODO: PREVENT RESTORING TO DOCUMENTS/BIOWARE } Analytics.TrackEvent(@"Chose to restore game to custom location", new Dictionary <string, string>() { { @"Game", Game.ToString() }, { @"New-gen", useNewMethod.ToString() } }); } else { return; } } RefreshTargets = true; TaskbarHelper.SetProgress(0); TaskbarHelper.SetProgressState(TaskbarProgressBarState.Normal); nbw.RunWorkerAsync(restoreTargetPath); } }
private void Migration_ContentRendered(object sender, EventArgs e) { NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"ME3CMMMigration"); nbw.DoWork += (a, b) => { Log.Information(@">>>> ME3CMMMigration Thread"); Log.Information(@"Validate ME3CMM folders and files"); var exeDir = Utilities.GetMMExecutableDirectory(); //DEBUG ONLY //exeDir = @"E:\ME3CMM"; var modsDir = Path.Combine(exeDir, @"mods"); var dataDir = Path.Combine(exeDir, @"data"); if (Directory.Exists(modsDir) && Directory.Exists(dataDir)) { Log.Information(@"mods and data dir exist."); // 1. MIGRATE MODS Log.Information(@"Step 1: Migrate mods"); MigratingModsTask.SetInProgress(); var targetModLibrary = Utilities.GetModsDirectory(); //DEBUG ONLY //targetModLibrary = @"E:\ME3CMM\mods"; targetModLibrary = Path.Combine(targetModLibrary, @"ME3"); if (!Directory.Exists(targetModLibrary)) { Log.Information(@"Creating target mod library directory: " + targetModLibrary); Directory.CreateDirectory(targetModLibrary); } var sameRoot = Path.GetPathRoot(targetModLibrary) == Path.GetPathRoot(modsDir); var directoriesInModsDir = Directory.GetDirectories(modsDir); var numToMigrate = directoriesInModsDir.Count(x => File.Exists(Path.Combine(x, @"moddesc.ini"))); var numMigrated = 0; foreach (var modDirToMove in directoriesInModsDir) { var moddesc = Path.Combine(modDirToMove, @"moddesc.ini"); if (File.Exists(moddesc)) { numMigrated++; MigratingModsTask.TaskText = $"Migrating mods [{numMigrated}/{numToMigrate}]"; //Migrate this folder var targetDir = Path.Combine(modsDir, @"ME3", Path.GetFileName(modDirToMove)); Log.Information($@"Migrating mod into ME3 directory: {modDirToMove} -> {targetDir}"); if (!Directory.Exists(targetDir)) { if (sameRoot) { Directory.Move(modDirToMove, targetDir); } else { Log.Information(@"Copying existing mod directory"); Directory.CreateDirectory(targetDir); CopyDir.CopyAll_ProgressBar(new DirectoryInfo(modDirToMove), new DirectoryInfo(targetDir)); Log.Information(@"Deleting existing directory"); Utilities.DeleteFilesAndFoldersRecursively(modDirToMove); } Log.Information($@"Migrated {modDirToMove}"); //Thread.Sleep(200); } else { Log.Warning(@"Target directory already exists! Not migrating this directory."); } } } MigratingModsTask.SetDone(); Log.Information(@"Step 1: Finished mod migration"); // 2. MIGRATE SETTINGS MigratingSettings.SetInProgress(); Log.Information(@"Step 2: Begin settings migration"); var me3cmminif = Path.Combine(exeDir, @"me3cmm.ini"); if (File.Exists(me3cmminif)) { Log.Information(@"Migrating me3cmm.ini settings"); IniData me3cmmini = new FileIniDataParser().ReadFile(me3cmminif); var updaterServiceUsername = me3cmmini[@"UpdaterService"][@"username"]; if (string.IsNullOrWhiteSpace(Settings.UpdaterServiceUsername) && !string.IsNullOrWhiteSpace(updaterServiceUsername)) { Settings.UpdaterServiceUsername = updaterServiceUsername; Log.Information(@"Migrated Updater Service Username: "******"UpdaterService"][@"manifestspath"]; if (string.IsNullOrWhiteSpace(Settings.UpdaterServiceManifestStoragePath) && !string.IsNullOrWhiteSpace(manifestsPath)) { Settings.UpdaterServiceManifestStoragePath = manifestsPath; Log.Information(@"Migrated Updater Service Manifests Path: " + manifestsPath); } var lzmaStoragePath = me3cmmini[@"UpdaterService"][@"lzmastoragepath"]; if (string.IsNullOrWhiteSpace(Settings.UpdaterServiceLZMAStoragePath) && !string.IsNullOrWhiteSpace(lzmaStoragePath)) { Settings.UpdaterServiceLZMAStoragePath = updaterServiceUsername; Log.Information(@"Migrated Updater Service LZMA Storage Path: " + lzmaStoragePath); } //TODO: MODMAKER CONTROLLER AUTO-ADDINS Settings.Save(); } //Migrate BIOGAME_DIRECTORIES var biogameDirsF = Path.Combine(dataDir, @"BIOGAME_DIRECTORIES"); if (File.Exists(biogameDirsF)) { var biodirs = File.ReadAllLines(biogameDirsF); foreach (var line in biodirs) { var gamepath = Directory.GetParent(line).FullName; Log.Information(@"Validating ME3CMM target: " + gamepath); GameTarget t = new GameTarget(Mod.MEGame.ME3, gamepath, false); var failureReason = t.ValidateTarget(); if (failureReason == null) { Utilities.AddCachedTarget(t); } else { Log.Error($@"Not migrating invalid target {gamepath}: {failureReason}"); } } } //Migrate ALOT Installer, if found var alotInstallerDir = Path.Combine(dataDir, @"ALOTInstaller"); if (Directory.Exists(alotInstallerDir)) { Log.Information(@"Migrating ALOTInstaller tool"); var externalToolsALOTInstaller = Path.Combine(dataDir, @"ExternalTools", @"ALOTInstaller"); Directory.CreateDirectory(Path.Combine(dataDir, @"ExternalTools")); Directory.Move(alotInstallerDir, externalToolsALOTInstaller); Log.Information(@"Migrated ALOTInstaller to ExternalTools"); } //Migrate ME3Explorer, if found var me3explorerDir = Path.Combine(dataDir, @"ME3Explorer"); if (Directory.Exists(me3explorerDir)) { Log.Information(@"Migrating ME3Explorer tool"); var externalToolsME3ExplorerDir = Path.Combine(dataDir, @"ExternalTools", @"ME3Explorer"); Directory.CreateDirectory(Path.Combine(dataDir, @"ExternalTools")); Directory.Move(me3explorerDir, externalToolsME3ExplorerDir); Log.Information(@"Migrated ME3Explorer to ExternalTools"); } //MIGRATE 7z.dll - this will only perform an interim fix as we use 19.0 and ME3MM used 18.05 var me3mm7z = Path.Combine(dataDir, @"tools\ModManagerCommandLine\x64\7z.dll"); var target7z = Utilities.Get7zDllPath(); if (File.Exists(me3mm7z) && !File.Exists(target7z)) { Log.Information($@"Copying ME3CMM 7z.dll to ME3Tweaks Mod Manager dll location: {me3mm7z} -> {target7z}"); File.Copy(me3mm7z, target7z, true); Log.Information(@"Copied ME3MM 7z dll"); } //MIGRATE MOD GROUPS (batch install queues)/ //Migrate ME3Explorer, if found var modGroupsDir = Path.Combine(dataDir, @"modgroups"); if (Directory.Exists(modGroupsDir)) { Log.Information(@"Migrating batch mod groups"); var queues = Directory.EnumerateFiles(modGroupsDir, @"*.txt").ToList(); foreach (var queue in queues) { var biqDest = Path.Combine(Utilities.GetBatchInstallGroupsFolder(), Path.GetFileName(queue)); Log.Information($@"Migrating mod install group: {queue} -> {biqDest}"); File.Move(queue, biqDest, true); Log.Information(@"Migrated " + Path.GetFileName(queue)); } } MigratingSettings.SetDone(); Log.Information(@"Step 2: Finished settings migration"); // 3. CLEANUP Log.Information(@"Step 3: Cleaning up"); CleaningUpTask.SetInProgress(); CleaningUpTask.SetDone(); Log.Information(@"Step 3: Cleaned up"); Thread.Sleep(3000); } else { Log.Error(@"mods and/or data dir don't exist! We will not attempt migration."); } Log.Information(@"<<<< Exiting ME3CMMMigration Thread"); }; nbw.RunWorkerCompleted += (a, b) => { Log.Information(@"Migration has completed."); Close(); }; 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); 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); } } }