Пример #1
0
            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();
        }
Пример #5
0
            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);
                }
            }
Пример #6
0
            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();
        }
Пример #10
0
            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);
                }
            }
        }