private Dictionary <string, List <string> > getFileSupercedances() { Mod.MEGame game = target.Game; //make dictionary from basegame files var fileListMapping = new CaseInsensitiveDictionary <List <string> >(); var directories = MELoadedFiles.GetEnabledDLC(target).OrderBy(dir => MELoadedFiles.GetMountPriority(dir, target.Game)); foreach (string directory in directories) { var dlc = Path.GetFileName(directory); if (MEDirectories.OfficialDLC(target.Game).Contains(dlc)) { continue; //skip } foreach (string filePath in MELoadedFiles.GetCookedFiles(target.Game, directory, false)) { string fileName = Path.GetFileName(filePath); if (fileName != null && fileName.RepresentsPackageFilePath()) { if (fileListMapping.TryGetValue(fileName, out var supercedingList)) { supercedingList.Insert(0, dlc); } else { fileListMapping[fileName] = new List <string>(new[] { dlc }); } } } } return(fileListMapping); }
/// <summary> /// Gets a list of superceding package files from the DLC of the game. Only files in DLC mods are returned /// </summary> /// <param name="target">Target to get supercedances for</param> /// <returns>Dictionary mapping filename to list of DLCs that contain that file, in order of highest priority to lowest</returns> public static Dictionary <string, List <string> > GetFileSupercedances(GameTarget target, string[] additionalExtensionsToInclude = null) { //make dictionary from basegame files var fileListMapping = new CaseInsensitiveDictionary <List <string> >(); var directories = MELoadedFiles.GetEnabledDLCFolders(target.Game, target.TargetPath).OrderBy(dir => MELoadedFiles.GetMountPriority(dir, target.Game)).ToList(); foreach (string directory in directories) { var dlc = Path.GetFileName(directory); if (MEDirectories.OfficialDLC(target.Game).Contains(dlc)) { continue; //skip } foreach (string filePath in MELoadedFiles.GetCookedFiles(target.Game, directory, false, additionalExtensions: additionalExtensionsToInclude)) { string fileName = Path.GetFileName(filePath); if (fileName != null && fileName.RepresentsPackageFilePath() || (additionalExtensionsToInclude != null && additionalExtensionsToInclude.Contains(Path.GetExtension(fileName)))) { if (fileListMapping.TryGetValue(fileName, out var supercedingList)) { supercedingList.Insert(0, dlc); } else { fileListMapping[fileName] = new List <string>(new[] { dlc }); } } } } return(fileListMapping); }
public void PopulateDLCMods(Func <InstalledDLCMod, bool> deleteConfirmationCallback, Action notifyDeleted) { UIInstalledDLCMods.ClearEx(); var dlcDir = MEDirectories.DLCPath(this); var installedMods = MEDirectories.GetInstalledDLC(this).Where(x => !MEDirectories.OfficialDLC(Game).Contains(x, StringComparer.InvariantCultureIgnoreCase)).Select(x => new InstalledDLCMod(Path.Combine(dlcDir, x), Game, deleteConfirmationCallback, notifyDeleted)).ToList(); UIInstalledDLCMods.AddRange(installedMods); }
/// <summary> /// Maps each DLC folder to it's MetaCMM file, if one exists. Otherwise it is mapped to null /// </summary> /// <param name="target"></param> /// <returns></returns> public static Dictionary <string, MetaCMM> GetMetaMappedInstalledDLC(GameTarget target, bool includeOfficial = true) { var installedDLC = GetInstalledDLC(target); var metamap = new Dictionary <string, MetaCMM>(); var dlcpath = GetDLCPath(target); foreach (var v in installedDLC) { if (!includeOfficial && MEDirectories.OfficialDLC(target.Game).Contains(v)) { continue; // This is not a mod } var meta = Path.Combine(dlcpath, v, @"_metacmm.txt"); MetaCMM mf = null; if (File.Exists(meta)) { mf = new MetaCMM(meta); } metamap[v] = mf; } return(metamap); }
/// <summary> /// Checks if the specified DLC folder name is protected (official DLC names and __metadata) /// </summary> /// <param name="dlcFolderName">DLC folder name (DLC_CON_MP2)</param> /// <param name="game">Game to test against</param> /// <returns>True if protected, false otherwise</returns> internal static bool IsProtectedDLCFolder(string dlcFolderName, Mod.MEGame game) => dlcFolderName.Equals("__metadata", StringComparison.InvariantCultureIgnoreCase) && MEDirectories.OfficialDLC(game).Contains(dlcFolderName, StringComparer.InvariantCultureIgnoreCase);
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 CheckModForAFCCompactability(DeploymentChecklistItem item) { bool hasError = false; item.HasError = false; item.ItemText = M3L.GetString(M3L.string_checkingAudioReferencesInMod); var referencedFiles = ModBeingDeployed.GetAllRelativeReferences().Select(x => Path.Combine(ModBeingDeployed.ModPath, x)).ToList(); int numChecked = 0; GameTarget validationTarget = mainWindow.InstallationTargets.FirstOrDefault(x => x.Game == ModBeingDeployed.Game); List <string> gameFiles = MEDirectories.EnumerateGameFiles(validationTarget.Game, validationTarget.TargetPath); var errors = new List <string>(); Dictionary <string, MemoryStream> cachedAudio = new Dictionary <string, MemoryStream>(); foreach (var f in referencedFiles) { if (_closed) { return; } numChecked++; item.ItemText = $@"{M3L.GetString(M3L.string_checkingAudioReferencesInMod)} [{numChecked}/{referencedFiles.Count}]"; if (f.RepresentsPackageFilePath()) { var package = MEPackageHandler.OpenMEPackage(f); var wwiseStreams = package.Exports.Where(x => x.ClassName == @"WwiseStream" && !x.IsDefaultObject).ToList(); foreach (var wwisestream in wwiseStreams) { if (_closed) { return; } //Check each reference. var afcNameProp = wwisestream.GetProperty <NameProperty>(@"Filename"); if (afcNameProp != null) { string afcNameWithExtension = afcNameProp + @".afc"; int audioSize = BitConverter.ToInt32(wwisestream.Data, wwisestream.Data.Length - 8); int audioOffset = BitConverter.ToInt32(wwisestream.Data, wwisestream.Data.Length - 4); string afcPath = null; Stream audioStream = null; var localDirectoryAFCPath = Path.Combine(Path.GetDirectoryName(wwisestream.FileRef.FilePath), afcNameWithExtension); bool isInOfficialArea = false; if (File.Exists(localDirectoryAFCPath)) { //local afc afcPath = localDirectoryAFCPath; } else { //Check game var fullPath = gameFiles.FirstOrDefault(x => Path.GetFileName(x).Equals(afcNameWithExtension, StringComparison.InvariantCultureIgnoreCase)); if (fullPath != null) { afcPath = fullPath; isInOfficialArea = MEDirectories.IsInBasegame(afcPath, validationTarget) || MEDirectories.IsInOfficialDLC(afcPath, validationTarget); } else if (cachedAudio.TryGetValue(afcNameProp.Value.Name, out var cachedAudioStream)) { audioStream = cachedAudioStream; //isInOfficialArea = true; //cached from vanilla SFAR } else if (MEDirectories.OfficialDLC(validationTarget.Game).Any(x => afcNameProp.Value.Name.StartsWith(x))) { var dlcName = afcNameProp.Value.Name.Substring(0, afcNameProp.Value.Name.LastIndexOf(@"_", StringComparison.InvariantCultureIgnoreCase)); var audio = VanillaDatabaseService.FetchFileFromVanillaSFAR(validationTarget, dlcName, afcNameWithExtension); if (audio != null) { cachedAudio[afcNameProp.Value.Name] = audio; } audioStream = audio; //isInOfficialArea = true; as this is in a vanilla SFAR we don't test against this since it will be correct. continue; } else { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_couldNotFindReferencedAFC, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath, afcNameProp.ToString())); continue; } } if (afcPath != null) { audioStream = new FileStream(afcPath, FileMode.Open); } try { audioStream.Seek(audioOffset, SeekOrigin.Begin); if (audioStream.ReadStringASCIINull(4) != @"RIFF") { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_invalidAudioPointer, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath)); if (audioStream is FileStream) { audioStream.Close(); } continue; } //attempt to seek audio length. audioStream.Seek(audioSize + 4, SeekOrigin.Current); //Check if this file is in basegame if (isInOfficialArea) { //Verify offset is not greater than vanilla size var vanillaInfo = VanillaDatabaseService.GetVanillaFileInfo(validationTarget, afcPath.Substring(validationTarget.TargetPath.Length + 1)); if (vanillaInfo == null) { Crashes.TrackError(new Exception($@"Vanilla information was null when performing vanilla file check for {afcPath.Substring(validationTarget.TargetPath.Length + 1)}")); } if (audioOffset >= vanillaInfo[0].size) { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; errors.Add(M3L.GetString(M3L.string_interp_audioStoredInOfficialAFC, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath)); } } if (audioStream is FileStream) { audioStream.Close(); } } catch (Exception e) { hasError = true; item.Icon = FontAwesomeIcon.TimesCircle; item.Foreground = Brushes.Red; item.Spinning = false; if (audioStream is FileStream) { audioStream.Close(); } errors.Add(M3L.GetString(M3L.string_errorValidatingAudioReference, wwisestream.FileRef.FilePath, wwisestream.GetInstancedFullPath, e.Message)); continue; } } } } } if (!hasError) { item.Foreground = Brushes.Green; item.Icon = FontAwesomeIcon.CheckCircle; item.ItemText = M3L.GetString(M3L.string_noAudioIssuesWereDetected); item.ToolTip = M3L.GetString(M3L.string_validationOK); } else { item.Errors = errors; item.ItemText = M3L.GetString(M3L.string_audioIssuesWereDetected); item.ToolTip = M3L.GetString(M3L.string_validationFailed); } item.HasError = hasError; cachedAudio.Clear(); }
public void PopulateDLCMods(bool includeDisabled, Func <InstalledDLCMod, bool> deleteConfirmationCallback = null, Action notifyDeleted = null, bool modNamePrefersTPMI = false) { var dlcDir = MEDirectories.DLCPath(this); var installedMods = MEDirectories.GetInstalledDLC(this, includeDisabled).Where(x => !MEDirectories.OfficialDLC(Game).Contains(x.TrimStart('x'), StringComparer.InvariantCultureIgnoreCase)); //Must run on UI thread Application.Current.Dispatcher.Invoke(delegate { UIInstalledDLCMods.ClearEx(); UIInstalledDLCMods.AddRange(installedMods.Select(x => new InstalledDLCMod(Path.Combine(dlcDir, x), Game, deleteConfirmationCallback, notifyDeleted, modNamePrefersTPMI)).ToList().OrderBy(x => x.ModName)); }); }
private void 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; }