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); } }