Exemple #1
0
        public static void GenerateMergeDLC(GameTarget target, Guid guid)
        {
            // Generate M3 DLC Folder

            // Does not work for LE1/ME1!
            var sko = new StarterKitGeneratorWindow.StarterKitOptions()
            {
                ModGame                = target.Game,
                GenerateModdesc        = false,
                OutputFolderOverride   = M3Directories.GetDLCPath(target),
                ModDescription         = null,
                ModInternalName        = @"ME3Tweaks Mod Manager Merge DLC",
                ModInternalTLKID       = 1928304430,
                ModMountFlag           = target.Game.IsGame3() ? new MountFlag(EME3MountFileFlag.LoadsInSingleplayer) : new MountFlag(0, true),
                ModDeveloper           = @"ME3Tweaks Mod Manager",
                ModMountPriority       = 1900000000,
                ModDLCFolderNameSuffix = MERGE_DLC_FOLDERNAME.Substring(@"DLC_MOD_".Length)
            };

            StarterKitGeneratorWindow.CreateStarterKitMod(sko, null);
            MetaCMM mcmm = new MetaCMM()
            {
                ModName = @"ME3Tweaks Mod Manager Auto-Generated Merge DLC",
                Version = @"1.0"
            };

            mcmm.WriteMetaCMM(Path.Combine(M3Directories.GetDLCPath(target), MERGE_DLC_FOLDERNAME, @"_metacmm.txt"));
        }
        /// <summary>
        /// Validates this mod can install against a game target with respect to the list of RequiredDLC.
        /// </summary>
        /// <param name="gameTarget">Target to validate against</param>
        /// <returns>List of missing DLC modules, or an empty list if none</returns>
        internal bool ValidateSingleOptionalRequiredDLCInstalled(GameTarget gameTarget)
        {
            if (gameTarget.Game != Game)
            {
                throw new Exception(@"Cannot validate a mod against a gametarget that is not for its game");
            }

            if (Enumerable.Any <string>(OptionalSingleRequiredDLC))
            {
                var requiredAnyDLC = Enumerable.Select <string, string>(OptionalSingleRequiredDLC, x =>
                {
                    if (Enum.TryParse(x, out ModJob.JobHeader parsedHeader) && ModJob.GetHeadersToDLCNamesMap(Game)
                        .TryGetValue(parsedHeader, out var dlcname))
                    {
                        return(dlcname);
                    }

                    return(x);
                });
                var installedDLC = M3Directories.GetInstalledDLC(gameTarget);
                return(installedDLC.FirstOrDefault(x => requiredAnyDLC.Contains(x)) != null);
            }

            return(true);
        }
Exemple #3
0
        public static void RemoveMergeDLC(GameTarget target)
        {
            var mergePath = Path.Combine(M3Directories.GetDLCPath(target), MERGE_DLC_FOLDERNAME);

            if (Directory.Exists(mergePath))
            {
                Utilities.DeleteFilesAndFoldersRecursively(mergePath);
            }
        }
            private void SetupDisablerButtonText()
            {
                if (SelectedTarget != null)
                {
                    var d3d9Path = Path.Combine(M3Directories.GetExecutableDirectory(SelectedTarget), @"d3d9.dll");
                    if (File.Exists(d3d9Path))
                    {
                        // See if it ME3Tweaks disabler or some other tool
                        var fi = new FileInspector(d3d9Path);
                        foreach (var sig in fi.GetSignatures())
                        {
                            foreach (var signChain in sig.AdditionalCertificates)
                            {
                                try
                                {
                                    var outStr = signChain.Subject.Substring(3); //remove CN=
                                    outStr = outStr.Substring(0, outStr.IndexOf(','));
                                    if (outStr == @"Michael Perez")              //My signing cert name
                                    {
                                        D3D9Status            = M3L.GetString(M3L.string_overlayDisablerInstalled);
                                        DisablerButtonText    = M3L.GetString(M3L.string_uninstallDisabler);
                                        DisablerButtonEnabled = true;
                                        return;
                                    }
                                }
                                catch
                                {
                                }
                            }
                        }

                        D3D9Status            = M3L.GetString(M3L.string_otherD3d9dllInstalledOverlayDisabled);
                        DisablerButtonText    = M3L.GetString(M3L.string_cannotUninstallOtherD3d9File);
                        DisablerButtonEnabled = false;
                        return;
                    }

                    DisablerButtonEnabled = true;
                    D3D9Status            = M3L.GetString(M3L.string_overlayDisablerNotInstalled);
                    DisablerButtonText    = M3L.GetString(M3L.string_installDisabler);
                }
                else
                {
                    DisablerButtonEnabled = false;
                    DisablerButtonText    = M3L.GetString(M3L.string_installDisabler);

                    if (Targets.Any())
                    {
                        D3D9Status = M3L.GetString(M3L.string_noTargetSelected);
                    }
                    else
                    {
                        D3D9Status = M3L.GetString(M3L.string_noOriginBasedGameTargets);
                    }
                }
            }
        private static string GetPlotManagerPath(GameTarget target)
        {
            switch (target.Game)
            {
            case MEGame.ME1:
                return(Path.Combine(M3Directories.GetCookedPath(target), @"PlotManager.u"));

            case MEGame.LE1:
            case MEGame.ME2:
            case MEGame.LE2:
                return(Path.Combine(M3Directories.GetCookedPath(target), @"PlotManager.pcc"));
            }

            return(null);
        }
Exemple #6
0
 private void ToggleDLC()
 {
     try
     {
         var    dlcFPath       = M3Directories.GetDLCPath(target);
         var    currentDLCPath = Path.Combine(dlcFPath, DLCFolderName);
         string destPath       = Path.Combine(dlcFPath, Enabled ? @"x" + UIDLCFolderName : UIDLCFolderName);
         Directory.Move(currentDLCPath, destPath);
         Enabled       = !Enabled;
         DLCFolderName = Enabled ? UIDLCFolderName : @"x" + UIDLCFolderName;
     }
     catch (Exception e)
     {
         Log.Error($@"Error toggling DLC {DLCFolderName}: {e.Message}");
         M3L.ShowDialog(Application.Current?.MainWindow, M3L.GetString(M3L.string_interp_errorTogglingDLC, e.Message), M3L.GetString(M3L.string_error), MessageBoxButton.OK, MessageBoxImage.Error); //this needs updated to be better
     }
 }
        /// <summary>
        /// Validates this mod can install against a game target with respect to the list of RequiredDLC.
        /// </summary>
        /// <param name="gameTarget">Target to validate against</param>
        /// <returns>List of missing DLC modules, or an empty list if none</returns>
        internal List <string> ValidateRequiredModulesAreInstalled(GameTarget gameTarget)
        {
            if (gameTarget.Game != Game)
            {
                throw new Exception(@"Cannot validate a mod against a gametarget that is not for its game");
            }

            var requiredDLC = Enumerable.Select <string, string>(RequiredDLC, x =>
            {
                if (Enum.TryParse(x, out ModJob.JobHeader parsedHeader) && ModJob.GetHeadersToDLCNamesMap(Game).TryGetValue(parsedHeader, out var dlcname))
                {
                    return(dlcname);
                }
                return(x);
            });
            var installedDLC = M3Directories.GetInstalledDLC(gameTarget);

            return(requiredDLC.Except(installedDLC).ToList());
        }
        public void OnSelectedTargetChanged()
        {
            Supercedances.ClearEx();
            if (SelectedTarget != null)
            {
                // maps DLC folder name -> mount number
                var mountpriorities = M3Directories.GetMountPriorities(SelectedTarget);
                //maps filename to list of DLC in order of precedence
                var supercedances = M3Directories.GetFileSupercedances(SelectedTarget).Where(x => x.Value.Count > 1).ToList();
                foreach (var supercedance in supercedances)
                {
                    SupercedanceList sl = new SupercedanceList()
                    {
                        Filename    = supercedance.Key,
                        WinningFile = new SupercedanceFile()
                        {
                            DLCName       = supercedance.Value.First(),
                            MountPriority = mountpriorities[supercedance.Value.First()],
                            Game          = SelectedTarget.Game
                        },
                        Game = SelectedTarget.Game
                    };
                    sl.LosingFiles.ReplaceAll(supercedance.Value.Skip(1).Take(supercedance.Value.Count - 1).Select(x => new SupercedanceFile()
                    {
                        DLCName       = x,
                        MountPriority = mountpriorities[x],
                        Game          = SelectedTarget.Game
                    }));

                    Supercedances.Add(sl);
                    //var dlcname = supercedance.Value
                    //SupercedanceFile winningFile = new SupercedanceFile()
                    //{
                    //    Fi
                    //    MountPriority = mountpriorities[],
                    //    DLCName = supercedance.Value.First()
                    //};
                }
            }
            Supercedances.Sort(x => x.Filename);
        }
Exemple #9
0
        public bool ApplyUpdate(IMEPackage package, ExportEntry targetExport, MergeAssetCache1 assetsCache, Mod installingMod, GameTarget gameTarget)
        {
            FileLib fl;

            if (!assetsCache.FileLibs.TryGetValue(package.FilePath, out fl))
            {
                fl = new FileLib(package);
                bool initialized = fl.Initialize(new RelativePackageCache()
                {
                    RootPath = M3Directories.GetBioGamePath(gameTarget)
                }, gameTarget.TargetPath);
                if (!initialized)
                {
                    Log.Error($@"FileLib loading failed for package {targetExport.InstancedFullPath} ({targetExport.FileRef.FilePath}):");
                    foreach (var v in fl.InitializationLog.AllErrors)
                    {
                        Log.Error(v.Message);
                    }

                    throw new Exception(M3L.GetString(M3L.string_interp_fileLibInitMergeMod1Script, targetExport.InstancedFullPath, string.Join(Environment.NewLine, fl.InitializationLog.AllErrors)));
                }

                assetsCache.FileLibs[package.FilePath] = fl;
            }


            (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(targetExport, ScriptText, fl);
            if (log.AllErrors.Any())
            {
                Log.Error($@"Error compiling function {targetExport.InstancedFullPath}:");
                foreach (var l in log.AllErrors)
                {
                    Log.Error(l.Message);
                }

                throw new Exception(M3L.GetString(M3L.string_interp_mergefile_errorCompilingFunction, targetExport, string.Join(Environment.NewLine, log.AllErrors)));
            }

            return(true);
        }
Exemple #10
0
        public bool ApplyMergeMod(Mod associatedMod, GameTarget target, ref int numTotalDone, int numTotalMerges, Action <int, int, string, string> mergeProgressDelegate = null)
        {
            Log.Information($@"Applying {MergeModFilename}");
            var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(target.Game, true, gameRootOverride: target.TargetPath);

            if (target.Game == MEGame.LE2)
            {
                // SPECIAL CASE: LE2 EntryMenu is loaded before DLC version so first load of the file
                // will be basegame one. The majority of time this is likely the desirable
                // file so we only target this one instead.
                loadedFiles[@"EntryMenu.pcc"] = Path.Combine(M3Directories.GetCookedPath(target), @"EntryMenu.pcc");
            }

            int numDone = 0;

            foreach (var mf in FilesToMergeInto)
            {
                mf.ApplyChanges(target, loadedFiles, associatedMod, ref numTotalDone, numTotalMerges, mergeProgressDelegate);
                numDone++;
            }

            return(true);
        }
            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)
                    {
                        TaskbarHelper.SetProgress(d);
                    }
                    else if (b.UserState is TaskbarProgressBarState tbs)
                    {
                        TaskbarHelper.SetProgressState(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();
                    var installedDLC   = VanillaDatabaseService.GetInstalledOfficialDLC(targetToBackup);
                    var 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: ");
                        Log.Information(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;
                            }
                        });
                    }

                    Log.Information(@"Checking for TexturesMEM TFCs");
                    var memTextures = Directory.GetFiles(targetToBackup.TargetPath, @"TexturesMEM*.tfc", SearchOption.AllDirectories);

                    if (end)
                    {
                        return;
                    }
                    if (isVanilla && isDLCConsistent && !Enumerable.Any(dlcModsInstalled) && !Enumerable.Any(memTextures))
                    {
                        BackupStatus = M3L.GetString(M3L.string_waitingForUserInput);

                        string backupPath = null;
                        if (!targetToBackup.IsCustomOption)
                        {
                            // Creating a new backup
                            nbw.ReportProgress(0, TaskbarProgressBarState.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, TaskbarProgressBarState.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   = M3Directories.GetDLCPath(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, TaskbarProgressBarState.Normal);
                            }

                            BackupStatus = M3L.GetString(M3L.string_creatingBackup);
                            Log.Information($@"Backing up {targetToBackup.TargetPath} to {backupPath}");
                            nbw.ReportProgress(0, TaskbarProgressBarState.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 MEGame.ME1:
                        case MEGame.ME2:
                            Utilities.WriteRegistryKey(App.BACKUP_REGISTRY_KEY, Game + @"VanillaBackupLocation",
                                                       backupPath);
                            break;

                        case 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 (Enumerable.Any(dlcModsInstalled))
                    {
                        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));
                    }
                    else if (Enumerable.Any(memTextures))
                    {
                        Analytics.TrackEvent(@"Created a backup", new Dictionary <string, string>()
                        {
                            { @"Game", Game.ToString() },
                            { @"Result", @"Failure, TexturesMEM files found" }
                        });
                        b.Result = (M3L.GetString(M3L.string_leftoverTextureFilesFound),
                                    M3L.GetString(M3L.string_dialog_foundLeftoverTextureFiles));
                    }
                    EndBackup();
                };
                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 (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();
            }
        public static bool RunPlotManagerUpdate(GameTarget target)
        {
            Log.Information($@"Updating PlotManager for game: {target.TargetPath}");
            var supercedances = M3Directories.GetFileSupercedances(target, new[] { @".pmu" });
            Dictionary <string, string> funcMap = new();
            List <string> combinedNames         = new List <string>();

            if (supercedances.TryGetValue(@"PlotManagerUpdate.pmu", out var supercedanes))
            {
                supercedanes.Reverse(); // list goes from highest to lowest. We want to build in lowest to highest
                StringBuilder sb             = null;
                string        currentFuncNum = null;
                var           metaMaps       = M3Directories.GetMetaMappedInstalledDLC(target, false);
                foreach (var pmuDLCName in supercedanes)
                {
                    var uiName = metaMaps[pmuDLCName]?.ModName ?? ThirdPartyServices.GetThirdPartyModInfo(pmuDLCName, target.Game)?.modname ?? pmuDLCName;
                    combinedNames.Add(uiName);
                    var text = File.ReadAllLines(Path.Combine(M3Directories.GetDLCPath(target), pmuDLCName, target.Game.CookedDirName(), @"PlotManagerUpdate.pmu"));
                    foreach (var line in text)
                    {
                        if (line.StartsWith(@"public function bool F"))
                        {
                            if (sb != null)
                            {
                                funcMap[currentFuncNum] = sb.ToString();
                                Log.Information($@"PlotSync: Adding function {currentFuncNum} from {pmuDLCName}");
                                currentFuncNum = null;
                            }

                            sb = new StringBuilder();
                            sb.AppendLine(line);

                            // Method name
                            currentFuncNum = line.Substring(22);
                            currentFuncNum = currentFuncNum.Substring(0, currentFuncNum.IndexOf('('));
                            if (int.TryParse(currentFuncNum, out var num))
                            {
                                if (num <= 0)
                                {
                                    Log.Error($@"Skipping plot manager update: Conditional {num} is not a valid number for use. Values must be greater than 0 and less than 2 billion.");
                                    Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>()
                                    {
                                        { @"FunctionName", $@"F{currentFuncNum}" },
                                        { @"DLCName", pmuDLCName }
                                    });
                                    sb = null;
                                    return(false);
                                }
                                else if (num.ToString().Length != currentFuncNum.Length)
                                {
                                    Log.Error($@"Skipping plot manager update: Conditional {currentFuncNum} is not a valid number for use. Values must not contain leading zeros");
                                    Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>()
                                    {
                                        { @"FunctionName", $@"F{currentFuncNum}" },
                                        { @"DLCName", pmuDLCName }
                                    });
                                    sb = null;
                                    return(false);
                                }
                            }
                            else
                            {
                                Log.Error($@"Skipping plot manager update: Conditional {currentFuncNum} is not a valid number for use. Values must be greater than 0 and less than 2 billion.");
                                Analytics.TrackEvent(@"Bad plot manager function", new Dictionary <string, string>()
                                {
                                    { @"FunctionName", $@"F{currentFuncNum}" },
                                    { @"DLCName", pmuDLCName }
                                });
                                sb = null;
                                return(false);
                            }
                        }
                        else
                        {
                            sb?.AppendLine(line);
                        }
                    }

                    // Add final, if any was found
                    if (sb != null)
                    {
                        funcMap[currentFuncNum] = sb.ToString();
                        Log.Information($@"PlotSync: Adding function {currentFuncNum} from {pmuDLCName}");
                    }
                }
            }

            var pmPath = GetPlotManagerPath(target);
            var vpm    = Utilities.ExtractInternalFileToStream($@"MassEffectModManagerCore.modmanager.plotmanager.{target.Game}.PlotManager.{(target.Game == MEGame.ME1 ? @"u" : @"pcc")}"); // do not localize

            if (funcMap.Any())
            {
                var plotManager      = MEPackageHandler.OpenMEPackageFromStream(vpm, $@"PlotManager.{(target.Game == MEGame.ME1 ? @"u" : @"pcc")}"); // do not localize
                var clonableFunction = plotManager.Exports.FirstOrDefault(x => x.ClassName == @"Function");

                // STEP 1: ADD ALL NEW FUNCTIONS BEFORE WE INITIALIZE THE FILELIB.
                foreach (var v in funcMap)
                {
                    var pmKey = $@"BioAutoConditionals.F{v.Key}";
                    var exp   = plotManager.FindExport(pmKey);
                    if (exp == null)
                    {
                        // Adding a new conditional
                        exp            = EntryCloner.CloneEntry(clonableFunction);
                        exp.ObjectName = new NameReference($@"F{v.Key}", 0);
                        exp.FileRef.InvalidateLookupTable(); // We changed the name.

                        // Reduces trash
                        UFunction uf = ObjectBinary.From <UFunction>(exp);
                        uf.Children    = 0;
                        uf.ScriptBytes = Array.Empty <byte>(); // No script data
                        exp.WriteBinary(uf);
                        Log.Information($@"Generated new blank conditional function export: {exp.UIndex} {exp.InstancedFullPath}", Settings.LogModInstallation);
                    }
                }

                // Relink child chain
                UClass uc = ObjectBinary.From <UClass>(plotManager.FindExport(@"BioAutoConditionals"));
                uc.UpdateChildrenChain();
                uc.UpdateLocalFunctions();
                uc.Export.WriteBinary(uc);


                // STEP 2: UPDATE FUNCTIONS
                Stopwatch sw          = Stopwatch.StartNew();
                var       fl          = new FileLib(plotManager);
                bool      initialized = fl.Initialize(new RelativePackageCache()
                {
                    RootPath = M3Directories.GetBioGamePath(target)
                }, target.TargetPath, canUseBinaryCache: false);
                if (!initialized)
                {
                    Log.Error(@"Error initializing FileLib for plot manager sync:");
                    foreach (var v in fl.InitializationLog.AllErrors)
                    {
                        Log.Error(v.Message);
                    }
                    throw new Exception(M3L.GetString(M3L.string_interp_fileLibInitFailedPlotManager, string.Join(Environment.NewLine, fl.InitializationLog.AllErrors.Select(x => x.Message)))); //force localize
                }
                sw.Stop();
                Debug.WriteLine($@"Took {sw.ElapsedMilliseconds}ms to load filelib");

                bool relinkChain = false;
                foreach (var v in funcMap)
                {
                    var pmKey = $@"BioAutoConditionals.F{v.Key}";
                    Log.Information($@"Updating conditional entry: {pmKey}", Settings.LogModInstallation);
                    var exp = plotManager.FindExport(pmKey);
                    (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(exp, v.Value, fl);
                    if (log.AllErrors.Any())
                    {
                        Log.Error($@"Error compiling function {exp.InstancedFullPath}:");
                        foreach (var l in log.AllErrors)
                        {
                            Log.Error(l.Message);
                        }

                        throw new Exception(M3L.GetString(M3L.string_interp_errorCompilingFunctionReason, exp, string.Join('\n', log.AllErrors.Select(x => x.Message))));
                    }
                }

                if (plotManager.IsModified)
                {
                    plotManager.Save(pmPath, true);
                    // Update local file DB
                    var bgfe = new BasegameFileIdentificationService.BasegameCloudDBFile(pmPath.Substring(target.TargetPath.Length + 1), (int)new FileInfo(pmPath).Length, target.Game, M3L.GetString(M3L.string_interp_plotManagerSyncForX, string.Join(@", ", combinedNames)), Utilities.CalculateMD5(pmPath));
                    BasegameFileIdentificationService.AddLocalBasegameIdentificationEntries(new List <BasegameFileIdentificationService.BasegameCloudDBFile>(new[] { bgfe }));
                }
            }
            else
            {
                // Just write out vanilla.
                vpm.WriteToFile(pmPath);
            }

            return(true);
        }
        /// <summary>
        /// Builds the installation queues for the mod. Item 1 is the unpacked job mappings (per modjob) along with the list of custom dlc folders being installed. Item 2 is the list of modjobs, their sfar paths, and the list of source files to install for SFAR jobs.
        /// </summary>
        /// <param name="gameTarget"></param>
        /// <returns></returns>
        public (Dictionary <ModJob, (Dictionary <string, InstallSourceFile> unpackedJobMapping, List <string> dlcFoldersBeingInstalled)>, List <(ModJob job, string sfarPath, Dictionary <string, InstallSourceFile>)>) GetInstallationQueues(GameTarget gameTarget)
        {
            if (IsInArchive)
            {
#if DEBUG
                if (Archive.IsDisposed())
                {
                    Debug.WriteLine(@">>> ARCHIVE IS DISPOSED");
                }
#endif
                if (File.Exists(ArchivePath) && (Archive == null || Archive.IsDisposed()))
                {
                    Archive = new SevenZipExtractor(ArchivePath); //load archive file for inspection
                }
                else if (Archive != null && Archive.GetBackingStream() is SevenZip.ArchiveEmulationStreamProxy aesp && aesp.Source is MemoryStream ms)
                {
                    var isExe = ArchivePath.EndsWith(@".exe", StringComparison.InvariantCultureIgnoreCase);
                    Archive = isExe ? new SevenZipExtractor(ms, InArchiveFormat.Nsis) : new SevenZipExtractor(ms);
                    MemoryAnalyzer.AddTrackedMemoryItem($@"Re-opened SVE archive for {ModName}", new WeakReference(Archive));
                }
            }
            var gameDLCPath      = M3Directories.GetDLCPath(gameTarget);
            var customDLCMapping = Enumerable.FirstOrDefault <ModJob>(InstallationJobs, x => x.Header == ModJob.JobHeader.CUSTOMDLC)?.CustomDLCFolderMapping;
            if (customDLCMapping != null)
            {
                //Make clone so original value is not modified
                customDLCMapping = new Dictionary <string, string>(customDLCMapping); //prevent altering the source object
            }

            var unpackedJobInstallationMapping = new Dictionary <ModJob, (Dictionary <string, InstallSourceFile> mapping, List <string> dlcFoldersBeingInstalled)>();
            var sfarInstallationJobs           = new List <(ModJob job, string sfarPath, Dictionary <string, InstallSourceFile> installationMapping)>();
            foreach (var job in InstallationJobs)
            {
                Log.Information($@"Preprocessing installation job: {job.Header}");
                var alternateFiles = Enumerable.Where <AlternateFile>(job.AlternateFiles, x => x.IsSelected && x.Operation != AlternateFile.AltFileOperation.OP_NOTHING &&
                                                                      x.Operation != AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES).ToList();
                var alternateDLC = Enumerable.Where <AlternateDLC>(job.AlternateDLCs, x => x.IsSelected).ToList();
                if (job.Header == ModJob.JobHeader.CUSTOMDLC)
                {
                    #region Installation: CustomDLC
                    //Key = destination file, value = source file to install
                    var installationMapping = new Dictionary <string, InstallSourceFile>();
                    unpackedJobInstallationMapping[job] = (installationMapping, new List <string>());
                    foreach (var altdlc in alternateDLC)
                    {
                        if (altdlc.Operation == AlternateDLC.AltDLCOperation.OP_ADD_CUSTOMDLC)
                        {
                            customDLCMapping[altdlc.AlternateDLCFolder] = altdlc.DestinationDLCFolder;
                        }
                    }

                    foreach (var mapping in customDLCMapping)
                    {
                        //Mapping is done as DESTINATIONFILE = SOURCEFILE so you can override keys
                        var source = FilesystemInterposer.PathCombine(IsInArchive, ModPath, mapping.Key);
                        var target = Path.Combine(gameDLCPath, mapping.Value);

                        //get list of all normal files we will install
                        var allSourceDirFiles = FilesystemInterposer.DirectoryGetFiles(source, "*", SearchOption.AllDirectories, Archive).Select(x => x.Substring(ModPath.Length).TrimStart('\\')).ToList();
                        unpackedJobInstallationMapping[job].dlcFoldersBeingInstalled.Add(target);
                        //loop over every file
                        foreach (var sourceFile in allSourceDirFiles)
                        {
                            //Check against alt files
                            bool altApplied = false;
                            foreach (var altFile in alternateFiles)
                            {
                                if (altFile.ModFile.Equals(sourceFile, StringComparison.InvariantCultureIgnoreCase))
                                {
                                    //Alt applies to this file
                                    switch (altFile.Operation)
                                    {
                                    case AlternateFile.AltFileOperation.OP_NOINSTALL:
                                        CLog.Information($@"Not installing {sourceFile} for Alternate File {altFile.FriendlyName} due to operation OP_NOINSTALL", Settings.LogModInstallation);
                                        //we simply don't map as we just do a continue below.
                                        altApplied = true;
                                        break;

                                    case AlternateFile.AltFileOperation.OP_SUBSTITUTE:
                                        CLog.Information($@"Repointing {sourceFile} to {altFile.AltFile} for Alternate File {altFile.FriendlyName} due to operation OP_SUBSTITUTE", Settings.LogModInstallation);
                                        if (job.JobDirectory != null && altFile.AltFile.StartsWith((string)job.JobDirectory))
                                        {
                                            installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile.Substring(job.JobDirectory.Length).TrimStart('/', '\\'))
                                            {
                                                AltApplied             = true,
                                                IsFullRelativeFilePath = true
                                            };    //use alternate file as key instead
                                        }
                                        else
                                        {
                                            installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile)
                                            {
                                                AltApplied = true, IsFullRelativeFilePath = true
                                            };                                                                                                                                 //use alternate file as key instead
                                        }
                                        altApplied = true;
                                        break;

                                    case AlternateFile.AltFileOperation.OP_INSTALL:
                                        //same logic as substitute, just different logging.
                                        CLog.Information($@"Adding {sourceFile} to install (from {altFile.AltFile}) as part of Alternate File {altFile.FriendlyName} due to operation OP_INSTALL", Settings.LogModInstallation);
                                        if (job.JobDirectory != null && altFile.AltFile.StartsWith((string)job.JobDirectory))
                                        {
                                            installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile.Substring(job.JobDirectory.Length).TrimStart('/', '\\'))
                                            {
                                                AltApplied             = true,
                                                IsFullRelativeFilePath = true
                                            };     //use alternate file as key instead
                                        }
                                        else
                                        {
                                            installationMapping[sourceFile] = new InstallSourceFile(altFile.AltFile)
                                            {
                                                AltApplied = true, IsFullRelativeFilePath = true
                                            };                                                                                                                                 //use alternate file as key instead
                                        }
                                        altApplied = true;
                                        break;
                                    }
                                    break;
                                }
                            }

                            if (altApplied)
                            {
                                continue;             //no further processing for file
                            }
                            var    relativeDestStartIndex = sourceFile.IndexOf(mapping.Value);
                            string destPath = sourceFile.Substring(relativeDestStartIndex);
                            installationMapping[destPath] = new InstallSourceFile(sourceFile); //destination is mapped to source file that will replace it.
                        }

                        foreach (var altdlc in alternateDLC)
                        {
                            if (altdlc.Operation == AlternateDLC.AltDLCOperation.OP_ADD_FOLDERFILES_TO_CUSTOMDLC)
                            {
                                string alternatePathRoot = FilesystemInterposer.PathCombine(IsInArchive, ModPath, altdlc.AlternateDLCFolder);
                                var    filesToAdd        = FilesystemInterposer.DirectoryGetFiles(alternatePathRoot, "*", SearchOption.AllDirectories, Archive).Select(x => x.Substring(ModPath.Length).TrimStart('\\')).ToList();
                                foreach (var fileToAdd in filesToAdd)
                                {
                                    var destFile = Path.Combine(altdlc.DestinationDLCFolder, fileToAdd.Substring(altdlc.AlternateDLCFolder.Length).TrimStart('\\', '/'));
                                    CLog.Information($@"Adding extra CustomDLC file ({fileToAdd} => {destFile}) due to Alternate DLC {altdlc.FriendlyName}'s {altdlc.Operation}", Settings.LogModInstallation);

                                    installationMapping[destFile] = new InstallSourceFile(fileToAdd)
                                    {
                                        AltApplied = true
                                    };
                                }
                            }
                            else if (altdlc.Operation == AlternateDLC.AltDLCOperation.OP_ADD_MULTILISTFILES_TO_CUSTOMDLC)
                            {
                                string alternatePathRoot = FilesystemInterposer.PathCombine(IsInArchive, ModPath, altdlc.MultiListRootPath);
                                foreach (var fileToAdd in altdlc.MultiListSourceFiles)
                                {
                                    var sourceFile = FilesystemInterposer.PathCombine(IsInArchive, alternatePathRoot, fileToAdd).Substring(ModPath.Length).TrimStart('\\');
                                    var destFile   = Path.Combine(altdlc.DestinationDLCFolder, fileToAdd.TrimStart('\\', '/'));
                                    CLog.Information($@"Adding extra CustomDLC file (MultiList) ({sourceFile} => {destFile}) due to Alternate DLC {altdlc.FriendlyName}'s {altdlc.Operation}", Settings.LogModInstallation);

                                    installationMapping[destFile] = new InstallSourceFile(sourceFile)
                                    {
                                        AltApplied = true
                                    };
                                }
                            }
                        }

                        // Process altfile removal of multilist, since it should be done last
                        var fileRemoveAltFiles = Enumerable.Where <AlternateFile>(job.AlternateFiles, x => x.IsSelected && x.Operation == AlternateFile.AltFileOperation.OP_NOINSTALL_MULTILISTFILES);
                        foreach (var altFile in fileRemoveAltFiles)
                        {
                            foreach (var multifile in altFile.MultiListSourceFiles)
                            {
                                CLog.Information($@"Attempting to remove multilist file {multifile} from install (from {altFile.MultiListTargetPath}) as part of Alternate File {altFile.FriendlyName} due to operation OP_NOINSTALL_MULTILISTFILES", Settings.LogModInstallation);
                                string relativeSourcePath = altFile.MultiListRootPath + '\\' + multifile;

                                var targetPath = altFile.MultiListTargetPath + '\\' + multifile;
                                if (installationMapping.Remove(targetPath))
                                {
                                    CLog.Information($@" > Removed multilist file {targetPath} from installation",
                                                     Settings.LogModInstallation);
                                }
                            }
                        }
                    }
                    #endregion
                }
                else if (job.Header == ModJob.JobHeader.LOCALIZATION)
                {
                    #region Installation: LOCALIZATION
                    var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>();
                    unpackedJobInstallationMapping[job] = (installationMapping, new List <string>());
                    buildInstallationQueue(job, installationMapping, false);
                    #endregion
                }
                else if (job.Header == ModJob.JobHeader.BASEGAME || job.Header == ModJob.JobHeader.BALANCE_CHANGES || job.Header == ModJob.JobHeader.ME1_CONFIG)
                {
                    #region Installation: BASEGAME, BALANCE CHANGES, ME1 CONFIG
                    var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>();
                    unpackedJobInstallationMapping[job] = (installationMapping, new List <string>());
                    buildInstallationQueue(job, installationMapping, false);
                    #endregion
                }
                else if (Game == MEGame.ME3 && ModJob.ME3SupportedNonCustomDLCJobHeaders.Contains(job.Header)) //previous else if will catch BASEGAME
                {
                    #region Installation: DLC Unpacked and SFAR (ME3 ONLY)

                    if (M3Directories.IsOfficialDLCInstalled(job.Header, gameTarget))
                    {
                        string sfarPath = job.Header == ModJob.JobHeader.TESTPATCH ? M3Directories.GetTestPatchSFARPath(gameTarget) : Path.Combine(gameDLCPath, ModJob.GetHeadersToDLCNamesMap(MEGame.ME3)[job.Header], @"CookedPCConsole", @"Default.sfar");


                        if (File.Exists(sfarPath))
                        {
                            var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>();
                            if (new FileInfo(sfarPath).Length == 32)
                            {
                                //Unpacked
                                unpackedJobInstallationMapping[job] = (installationMapping, new List <string>());
                                buildInstallationQueue(job, installationMapping, false);
                            }
                            else
                            {
                                //Packed
                                //unpackedJobInstallationMapping[job] = installationMapping;
                                buildInstallationQueue(job, installationMapping, true);
                                sfarInstallationJobs.Add((job, sfarPath, installationMapping));
                            }
                        }
                    }
                    else
                    {
                        Log.Warning($@"DLC not installed, skipping: {job.Header}");
                    }
                    #endregion
                }
                else if (Game == MEGame.ME2 || Game == MEGame.ME1)
                {
                    #region Installation: DLC Unpacked (ME1/ME2 ONLY)
                    //Unpacked
                    if (M3Directories.IsOfficialDLCInstalled(job.Header, gameTarget))
                    {
                        var installationMapping = new CaseInsensitiveDictionary <InstallSourceFile>();
                        unpackedJobInstallationMapping[job] = (installationMapping, new List <string>());
                        buildInstallationQueue(job, installationMapping, false);
                    }
                    else
                    {
                        Log.Warning($@"DLC not installed, skipping: {job.Header}");
                    }

                    #endregion
                }
                else
                {
                    //?? Header
                    throw new Exception(@"Unsupported installation job header! " + job.Header);
                }
            }

            return(unpackedJobInstallationMapping, sfarInstallationJobs);
        }
        private void ImportSelectedFolder()
        {
            //Check destination path
            var destinationName = Utilities.SanitizePath(ModNameText);

            if (string.IsNullOrWhiteSpace(destinationName))
            {
                //cannot use this name
                Log.Error(@"Invalid mod name: " + ModNameText);
                M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_dialog_invalidModNameWillResolveToNothing), M3L.GetString(M3L.string_invalidModName), MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            //Check free space.
            var sourceDir = Path.Combine(M3Directories.GetDLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName);
            var library   = Utilities.GetModDirectoryForGame(SelectedTarget.Game);

            if (Utilities.DriveFreeBytes(library, out var freeBytes))
            {
                //Check enough space
                var sourceSize = Utilities.GetSizeOfDirectory(sourceDir);
                if (sourceSize > (long)freeBytes)
                {
                    //Not enough space
                    Log.Error($@"Not enough disk space to import mod. Required space: {FileSize.FormatSize(sourceSize)}, available space: {FileSize.FormatSize(freeBytes)}");
                    M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_insufficientDiskSpaceToImport, Path.GetPathRoot(library), FileSize.FormatSize(sourceSize), FileSize.FormatSize(freeBytes)), M3L.GetString(M3L.string_insufficientFreeDiskSpace), MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }

            //Check directory doesn't exist already
            var outDir = Path.Combine(library, destinationName);

            if (Directory.Exists(outDir))
            {
                var okToDelete = M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialog_importingWillDeleteExistingMod, outDir), M3L.GetString(M3L.string_sameNamedModInLibrary), MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (okToDelete == MessageBoxResult.No)
                {
                    return; //cancel
                }

                try
                {
                    Utilities.DeleteFilesAndFoldersRecursively(outDir);
                }
                catch (Exception e)
                {
                    M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_couldNotDeleteExistingModDirectory, e.Message), M3L.GetString(M3L.string_errorDeletingModFolder), MessageBoxButton.OK, MessageBoxImage.Error);
                    return; //abort
                }
            }

            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"GameDLCModImporter");

            nbw.DoWork             += ImportDLCFolder_BackgroundThread;
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occurred in {nbw.Name} thread: {b.Error.Message}");
                }
                else
                {
                    if (b.Error == null && b.Result != null)
                    {
                        Analytics.TrackEvent(@"Imported a mod from game installation", new Dictionary <string, string>()
                        {
                            { @"Game", SelectedTarget.Game.ToString() },
                            { @"Folder", SelectedDLCFolder.DLCFolderName }
                        });
                    }

                    OperationInProgress = false;
                    if (b.Error == null && b.Result != null)
                    {
                        OnClosing(new DataEventArgs(b.Result)); //avoid accessing b.Result if error occurred
                    }
                }
            };
            nbw.RunWorkerAsync();
        }
        private void 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);
                }
            }
        }
Exemple #16
0
        public static void RunGame2EmailMerge(GameTarget target)
        {
            M3MergeDLC.RemoveMergeDLC(target);
            var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(target.Game, gameRootOverride: target.TargetPath);

            // File to base modifications on
            using IMEPackage pcc = MEPackageHandler.OpenMEPackage(loadedFiles[@"BioD_Nor103_Messages.pcc"]);

            // Path to Message templates file - different files for ME2/LE2
            string ResourcesFilePath = $@"MassEffectModManagerCore.modmanager.emailmerge.{target.Game}.103Message_Template_{target.Game}";

            using IMEPackage resources = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream(ResourcesFilePath));

            // Startup file to place conditionals and transitions into
            using IMEPackage startup = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream(
                                                                                    $@"MassEffectModManagerCore.modmanager.mergedlc.{target.Game}.Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc"));


            var emailInfos        = new List <ME2EmailMergeFile>();
            var jsonSupercedances = M3Directories.GetFileSupercedances(target, new[] { @".json" });

            if (jsonSupercedances.TryGetValue(EMAIL_MERGE_MANIFEST_FILE, out var jsonList))
            {
                jsonList.Reverse();
                foreach (var dlc in jsonList)
                {
                    var jsonFile = Path.Combine(M3Directories.GetDLCPath(target), dlc, target.Game.CookedDirName(),
                                                EMAIL_MERGE_MANIFEST_FILE);
                    emailInfos.Add(JsonConvert.DeserializeObject <ME2EmailMergeFile>(File.ReadAllText(jsonFile)));
                }
            }

            // Sanity checks
            if (!emailInfos.Any() || !emailInfos.SelectMany(e => e.Emails).Any())
            {
                return;
            }

            if (emailInfos.Any(e => e.Game != target.Game))
            {
                throw new Exception("ME2 email merge manifest targets incorrect game");
            }

            // Startup File
            // Could replace this with full instanced path in M3 implementation
            ExportEntry stateEventMapExport = startup.Exports
                                              .First(e => e.ClassName == "BioStateEventMap" && e.ObjectName == "StateTransitionMap");
            BioStateEventMap StateEventMap    = stateEventMapExport.GetBinaryData <BioStateEventMap>();
            ExportEntry      ConditionalClass =
                startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals");

            #region Sequence Exports
            // Send message - All email conditionals are checked and emails transitions are triggered
            ExportEntry SendMessageContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Send_Messages");
            ExportEntry LastSendMessage      = KismetHelper.GetSequenceObjects(SendMessageContainer).OfType <ExportEntry>()
                                               .FirstOrDefault(e =>
            {
                var outbound = KismetHelper.GetOutboundLinksOfNode(e);
                return(outbound.Count == 1 && outbound[0].Count == 0);
            });
            ExportEntry TemplateSendMessage          = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Send_MessageTemplate");
            ExportEntry TemplateSendMessageBoolCheck = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Send_MessageTemplate_BoolCheck");


            // Mark Read - email ints are set to read
            // This is the only section that does not gracefully handle different DLC installations - DLC_CER is required atm
            ExportEntry MarkReadContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_Read");
            ExportEntry LastMarkRead      = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_Read.DLC_CER");
            ExportEntry MarkReadOutLink   = KismetHelper.GetOutboundLinksOfNode(LastMarkRead)[0][0].LinkedOp as ExportEntry;
            KismetHelper.RemoveOutputLinks(LastMarkRead);

            ExportEntry TemplateMarkRead           = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_ReadTemplate");
            ExportEntry TemplateMarkReadTransition = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_Read_Transition");


            // Display Messages - Str refs are passed through to GUI
            ExportEntry DisplayMessageContainer =
                pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Display_Messages");
            ExportEntry DisplayMessageOutLink =
                pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Display_Messages.SeqCond_CompareBool_0");

            ExportEntry LastDisplayMessage = SeqTools.FindOutboundConnectionsToNode(DisplayMessageOutLink, KismetHelper.GetSequenceObjects(DisplayMessageContainer).OfType <ExportEntry>())[0];
            KismetHelper.RemoveOutputLinks(LastDisplayMessage);
            var         DisplayMessageVariableLinks = LastDisplayMessage.GetProperty <ArrayProperty <StructProperty> >("VariableLinks");
            ExportEntry TemplateDisplayMessage      =
                resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Display_MessageTemplate");

            // Archive Messages - Message ints are set to 3
            ExportEntry ArchiveContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Archive_Message");
            ExportEntry ArchiveSwitch    = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Archive_Message.SeqAct_Switch_0");
            ExportEntry ArchiveOutLink   =
                pcc.FindExport(
                    @"TheWorld.PersistentLevel.Main_Sequence.Archive_Message.BioSeqAct_PMCheckConditional_1");
            ExportEntry ExampleSetInt  = KismetHelper.GetOutboundLinksOfNode(ArchiveSwitch)[0][0].LinkedOp as ExportEntry;
            ExportEntry ExamplePlotInt = SeqTools.GetVariableLinksOfNode(ExampleSetInt)[0].LinkedNodes[0] as ExportEntry;
            #endregion

            int messageID      = KismetHelper.GetOutboundLinksOfNode(ArchiveSwitch).Count + 1;
            int currentSwCount = ArchiveSwitch.GetProperty <IntProperty>("LinkCount").Value;

            foreach (var emailMod in emailInfos)
            {
                string modName = "DLC_MOD_" + emailMod.ModName;

                foreach (var email in emailMod.Emails)
                {
                    string emailName = modName + "_" + email.EmailName;

                    // Create send transition
                    int transitionId  = WriteTransition(StateEventMap, email.StatusPlotInt);
                    int conditionalId = WriteConditional(email.TriggerConditional);

                    #region SendMessage
                    //////////////
                    // SendMessage
                    //////////////

                    // Create seq object
                    var SMTemp = emailMod.InMemoryBool.HasValue ? TemplateSendMessageBoolCheck : TemplateSendMessage;
                    EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild,
                                                         SMTemp, pcc, SendMessageContainer, true, new RelinkerOptionsPackage(), out var outSendEntry);

                    var newSend = outSendEntry as ExportEntry;

                    // Set name, comment, add to sequence
                    newSend.ObjectName = new NameReference(emailName);
                    KismetHelper.AddObjectToSequence(newSend, SendMessageContainer);
                    KismetHelper.SetComment(newSend, emailName);
                    if (target.Game == MEGame.ME2)
                    {
                        newSend.WriteProperty(new StrProperty(emailName, "ObjName"));
                    }

                    // Set Trigger Conditional
                    var pmCheckConditionalSM = newSend.GetChildren()
                                               .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMCheckConditional" && e is ExportEntry);
                    if (pmCheckConditionalSM is ExportEntry conditional)
                    {
                        conditional.WriteProperty(new IntProperty(conditionalId, "m_nIndex"));
                        KismetHelper.SetComment(conditional, "Time for " + email.EmailName + "?");
                    }

                    // Set Send Transition
                    var pmExecuteTransitionSM = newSend.GetChildren()
                                                .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMExecuteTransition" && e is ExportEntry);
                    if (pmExecuteTransitionSM is ExportEntry transition)
                    {
                        transition.WriteProperty(new IntProperty(transitionId, "m_nIndex"));
                        KismetHelper.SetComment(transition, "Send " + email.EmailName + " message.");
                    }

                    // Set Send Transition
                    if (emailMod.InMemoryBool.HasValue)
                    {
                        var pmCheckStateSM = newSend.GetChildren()
                                             .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMCheckState" && e is ExportEntry);
                        if (pmCheckStateSM is ExportEntry checkState)
                        {
                            checkState.WriteProperty(new IntProperty(emailMod.InMemoryBool.Value, "m_nIndex"));
                            KismetHelper.SetComment(checkState, "Is " + emailMod.ModName + " installed?");
                        }
                    }

                    // Hook up output links
                    KismetHelper.CreateOutputLink(LastSendMessage, "Out", newSend);
                    LastSendMessage = newSend;
                    #endregion

                    #region MarkRead
                    ///////////
                    // MarkRead
                    ///////////

                    // Create seq object
                    var MRTemp = email.ReadTransition.HasValue ? TemplateMarkReadTransition : TemplateMarkRead;
                    EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild,
                                                         MRTemp, pcc, MarkReadContainer, true, new RelinkerOptionsPackage(), out var outMarkReadEntry);
                    var newMarkRead = outMarkReadEntry as ExportEntry;

                    // Set name, comment, add to sequence
                    newMarkRead.ObjectName = new NameReference(emailName);
                    KismetHelper.AddObjectToSequence(newMarkRead, MarkReadContainer);
                    KismetHelper.SetComment(newMarkRead, emailName);
                    if (target.Game == MEGame.ME2)
                    {
                        newMarkRead.WriteProperty(new StrProperty(emailName, "ObjName"));
                    }

                    // Set Plot Int
                    var storyManagerIntMR = newMarkRead.GetChildren()
                                            .FirstOrDefault(e => e.ClassName == "BioSeqVar_StoryManagerInt" && e is ExportEntry);
                    if (storyManagerIntMR is ExportEntry plotIntMR)
                    {
                        plotIntMR.WriteProperty(new IntProperty(email.StatusPlotInt, "m_nIndex"));
                        KismetHelper.SetComment(plotIntMR, email.EmailName);
                    }

                    if (email.ReadTransition.HasValue)
                    {
                        var pmExecuteTransitionMR = newMarkRead.GetChildren()
                                                    .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMExecuteTransition" && e is ExportEntry);
                        if (pmExecuteTransitionMR is ExportEntry transitionMR)
                        {
                            transitionMR.WriteProperty(new IntProperty(email.ReadTransition.Value, "m_nIndex"));
                            KismetHelper.SetComment(transitionMR, "Trigger " + email.EmailName + " read transition");
                        }
                    }

                    // Hook up output links
                    KismetHelper.CreateOutputLink(LastMarkRead, "Out", newMarkRead);
                    LastMarkRead = newMarkRead;
                    #endregion

                    #region DisplayEmail
                    ////////////////
                    // Display Email
                    ////////////////

                    // Create seq object
                    EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild,
                                                         TemplateDisplayMessage, pcc, DisplayMessageContainer, true, new RelinkerOptionsPackage(), out var outDisplayMessage);
                    var newDisplayMessage = outDisplayMessage as ExportEntry;

                    // Set name, comment, variable links, add to sequence
                    newDisplayMessage.ObjectName = new NameReference(emailName);
                    KismetHelper.AddObjectToSequence(newDisplayMessage, DisplayMessageContainer);
                    newDisplayMessage.WriteProperty(DisplayMessageVariableLinks);
                    KismetHelper.SetComment(newDisplayMessage, emailName);
                    if (target.Game == MEGame.ME2)
                    {
                        newDisplayMessage.WriteProperty(new StrProperty(emailName, "ObjName"));
                    }

                    var displayChildren = newDisplayMessage.GetChildren();

                    // Set Plot Int
                    var storyManagerIntDE = displayChildren.FirstOrDefault(e =>
                                                                           e.ClassName == "BioSeqVar_StoryManagerInt" && e is ExportEntry);
                    if (storyManagerIntDE is ExportEntry plotIntDE)
                    {
                        plotIntDE.WriteProperty(new IntProperty(email.StatusPlotInt, "m_nIndex"));
                    }

                    // Set Email ID
                    var emailIdDE = displayChildren.FirstOrDefault(e =>
                                                                   e.ClassName == "SeqVar_Int" && e is ExportEntry);
                    if (emailIdDE is ExportEntry EmailIDDE)
                    {
                        EmailIDDE.WriteProperty(new IntProperty(messageID, "IntValue"));
                    }

                    // Set Title StrRef
                    var titleStrRef = displayChildren.FirstOrDefault(e =>
                                                                     e.ClassName == "BioSeqVar_StrRef" && e is ExportEntry ee && ee.GetProperty <NameProperty>("VarName").Value == "Title StrRef");
                    if (titleStrRef is ExportEntry Title)
                    {
                        Title.WriteProperty(new StringRefProperty(email.TitleStrRef, "m_srValue"));
                    }

                    // Set Description StrRef
                    var descStrRef = displayChildren.FirstOrDefault(e =>
                                                                    e.ClassName == "BioSeqVar_StrRef" && e is ExportEntry ee && ee.GetProperty <NameProperty>("VarName").Value == "Desc StrRef");
                    if (descStrRef is ExportEntry Desc)
                    {
                        Desc.WriteProperty(new StringRefProperty(email.DescStrRef, "m_srValue"));
                    }

                    // Hook up output links
                    KismetHelper.CreateOutputLink(LastDisplayMessage, "Out", newDisplayMessage);
                    LastDisplayMessage = newDisplayMessage;
                    #endregion

                    #region ArchiveEmail
                    ////////////////
                    // Archive Email
                    ////////////////

                    var NewSetInt = EntryCloner.CloneEntry(ExampleSetInt);
                    KismetHelper.AddObjectToSequence(NewSetInt, ArchiveContainer);
                    KismetHelper.CreateOutputLink(NewSetInt, "Out", ArchiveOutLink);

                    KismetHelper.CreateNewOutputLink(ArchiveSwitch, "Link " + (messageID - 1), NewSetInt);

                    var NewPlotInt = EntryCloner.CloneEntry(ExamplePlotInt);
                    KismetHelper.AddObjectToSequence(NewPlotInt, ArchiveContainer);
                    NewPlotInt.WriteProperty(new IntProperty(email.StatusPlotInt, "m_nIndex"));
                    NewPlotInt.WriteProperty(new StrProperty(emailName, "m_sRefName"));

                    var linkedVars = SeqTools.GetVariableLinksOfNode(NewSetInt);
                    linkedVars[0].LinkedNodes = new List <IEntry>()
                    {
                        NewPlotInt
                    };
                    SeqTools.WriteVariableLinksToNode(NewSetInt, linkedVars);

                    messageID++;
                    currentSwCount++;
                    #endregion
                }
            }
            KismetHelper.CreateOutputLink(LastMarkRead, "Out", MarkReadOutLink);
            KismetHelper.CreateOutputLink(LastDisplayMessage, "Out", DisplayMessageOutLink);
            ArchiveSwitch.WriteProperty(new IntProperty(currentSwCount, "LinkCount"));

            stateEventMapExport.WriteBinary(StateEventMap);

            // Save Messages file into DLC
            var cookedDir   = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName());
            var outMessages = Path.Combine(cookedDir, @"BioD_Nor103_Messages.pcc");
            pcc.Save(outMessages);

            // Save Startup file into DLC
            var startupF = Path.Combine(cookedDir, $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc");
            startup.Save(startupF);
        }
        private void CompileIntoGame()
        {
            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"MixinManager CompileIntoGameThread");
            List <string>         failedApplications = new List <string>();

            nbw.DoWork += (a, b) =>
            {
                BottomLeftMessage   = M3L.GetString(M3L.string_compilingMixins);
                OperationInProgress = true;
                //DEBUG STUFF
#if DEBUG
                int numCoresToApplyWith = 1;
#else
                var numCoresToApplyWith = Environment.ProcessorCount;
                if (numCoresToApplyWith > 4)
                {
                    numCoresToApplyWith = 4;                          //no more than 4 as this uses a lot of memory
                }
#endif

                var mixins = AvailableOfficialMixins.Where(x => x.UISelectedForUse).ToList();
                MixinHandler.LoadPatchDataForMixins(mixins); //before dynamic
                void failedApplicationCallback(string str)
                {
                    failedApplications.Add(str);
                }

                var compilingListsPerModule = MixinHandler.GetMixinApplicationList(mixins, failedApplicationCallback);
                if (Enumerable.Any(failedApplications))
                {
                    //Error building list
                    Log.Information(@"Aborting mixin install due to incompatible selection of mixins");
                    return;
                }

                ProgressBarMax   = mixins.Count();
                ProgressBarValue = 0;
                int numdone = 0;

                void completedSingleApplicationCallback()
                {
                    var val = Interlocked.Increment(ref numdone);

                    ProgressBarValue = val;
                }

                //Mixins are ready to be applied
                Parallel.ForEach(compilingListsPerModule,
                                 new ParallelOptions
                {
                    MaxDegreeOfParallelism = Environment.ProcessorCount > numCoresToApplyWith
                    ? numCoresToApplyWith
                    : Environment.ProcessorCount
                }, mapping =>
                {
                    var dlcFolderName = ModMakerCompiler.ModmakerChunkNameToDLCFoldername(mapping.Key.ToString());
                    //var outdir = Path.Combine(modpath, ModMakerCompiler.HeaderToDefaultFoldername(mapping.Key), @"CookedPCConsole");
                    //Directory.CreateDirectory(outdir);
                    if (mapping.Key == ModJob.JobHeader.BASEGAME)
                    {
                        //basegame
                        foreach (var file in mapping.Value)
                        {
                            try
                            {
                                using var vanillaPackageAsStream = VanillaDatabaseService.FetchBasegameFile(MEGame.ME3, Path.GetFileName(file.Key));
                                //packageAsStream.WriteToFile(@"C:\users\dev\desktop\compressed.pcc");
                                using var decompressedStream = MEPackage.GetDecompressedPackageStream(vanillaPackageAsStream, false, true);
                                decompressedStream.Position  = 0;
                                var vanillaPackage           = MEPackageHandler.OpenMEPackageFromStream(decompressedStream, $@"Vanilla - {Path.GetFileName(file.Key)}");
                                //decompressedStream.WriteToFile(@"C:\users\dev\desktop\decompressed.pcc");

                                using var mixinModifiedStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, true,
                                                                                         completedSingleApplicationCallback, failedApplicationCallback);
                                mixinModifiedStream.Position = 0;
                                var modifiedPackage          = MEPackageHandler.OpenMEPackageFromStream(mixinModifiedStream, $@"Mixin Modified - {Path.GetFileName(file.Key)}");

                                // three way merge: get target stream
                                var targetFile    = Path.Combine(M3Directories.GetCookedPath(SelectedInstallTarget), Path.GetFileName(file.Key));
                                var targetPackage = MEPackageHandler.OpenMEPackage(targetFile);

                                var merged = ThreeWayPackageMerge.AttemptMerge(vanillaPackage, modifiedPackage, targetPackage);
                                if (merged)
                                {
                                    targetPackage.Save(compress: true);
                                    Log.Information(@"Three way merge succeeded for " + targetFile);
                                }
                                else
                                {
                                    Log.Error(@"Could not merge three way merge into " + targetFile);
                                }
                                //var outfile = Path.Combine(outdir, Path.GetFileName(file.Key));
                                //package.save(outfile, false); // don't compress
                                //finalStream.WriteToFile(outfile);
                                //File.WriteAllBytes(outfile, finalStream.ToArray());
                            }
                            catch (Exception e)
                            {
                                var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName));
                                Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}");
                                failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message));
                            }
                        }
                    }
                    else
                    {
                        //dlc
                        var dlcPackage        = VanillaDatabaseService.FetchVanillaSFAR(dlcFolderName); //do not have to open file multiple times.
                        var targetCookedPCDir = Path.Combine(M3Directories.GetDLCPath(SelectedInstallTarget), dlcFolderName, @"CookedPCConsole");
                        var sfar      = mapping.Key == ModJob.JobHeader.TESTPATCH ? M3Directories.GetTestPatchSFARPath(SelectedInstallTarget) : Path.Combine(targetCookedPCDir, @"Default.sfar");
                        bool unpacked = new FileInfo(sfar).Length == 32;
                        DLCPackage targetDLCPackage = unpacked ? null : new DLCPackage(sfar); //cache SFAR target

                        foreach (var file in mapping.Value)
                        {
                            try
                            {
                                using var vanillaPackageAsStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, file.Key, forcedDLC: dlcPackage);
                                using var decompressedStream     = MEPackage.GetDecompressedPackageStream(vanillaPackageAsStream);
                                decompressedStream.Position      = 0;
                                var vanillaPackage            = MEPackageHandler.OpenMEPackageFromStream(decompressedStream, $@"VanillaDLC - {Path.GetFileName(file.Key)}");
                                using var mixinModifiedStream = MixinHandler.ApplyMixins(decompressedStream, file.Value, true, completedSingleApplicationCallback, failedApplicationCallback);
                                mixinModifiedStream.Position  = 0;
                                var modifiedPackage           = MEPackageHandler.OpenMEPackageFromStream(mixinModifiedStream, $@"Mixin Modified - {Path.GetFileName(file.Key)}");

                                // three way merge: get target stream
                                // must see if DLC is unpacked first

                                MemoryStream targetFileStream = null;

                                //Packed
                                if (unpacked)
                                {
                                    targetFileStream = new MemoryStream(File.ReadAllBytes(Path.Combine(targetCookedPCDir, file.Key)));
                                }
                                else
                                {
                                    targetFileStream = VanillaDatabaseService.FetchFileFromVanillaSFAR(dlcFolderName, Path.GetFileName(file.Key), forcedDLC: targetDLCPackage);
                                }

                                var targetPackage = MEPackageHandler.OpenMEPackageFromStream(targetFileStream, $@"Target package {dlcFolderName} - {file.Key}, from SFAR: {unpacked}");

                                var merged = ThreeWayPackageMerge.AttemptMerge(vanillaPackage, modifiedPackage, targetPackage);
                                if (merged)
                                {
                                    if (unpacked)
                                    {
                                        targetPackage.Save(Path.Combine(targetCookedPCDir, file.Key));
                                        Log.Information(@"Three way merge succeeded for " + targetPackage.FilePath);
                                    }
                                    else
                                    {
                                        var finalSTream = targetPackage.SaveToStream(false); // No compress. Not sure if we should support doing that though.
                                        targetDLCPackage.ReplaceEntry(finalSTream.ToArray(), targetDLCPackage.FindFileEntry(Path.GetFileName(file.Key)));
                                        Log.Information(@"Three way merge succeeded for " + targetPackage.FilePath);
                                    }
                                }
                                else
                                {
                                    Log.Error(@"Could not three way merge into: " + targetFileStream);
                                }
                            }
                            catch (Exception e)
                            {
                                var mixinsStr = string.Join(@", ", file.Value.Select(x => x.PatchName));
                                Log.Error($@"Error in mixin application for file {file.Key}: {e.Message}");
                                failedApplicationCallback(M3L.GetString(M3L.string_interp_errorApplyingMixinsForFile, mixinsStr, file.Key, e.Message));
                            }

                            //finalStream.WriteToFile(outfile);
                        }
                    }
                });

                MixinHandler.FreeME3TweaksPatchData();
                var percent = 0; //this is used to save a localization
                BottomLeftMessage = M3L.GetString(M3L.string_interp_runningAutoTOCOnGamePercentX, percent);

                //Run autotoc
                void tocingUpdate(int percent)
                {
                    BottomLeftMessage = M3L.GetString(M3L.string_interp_runningAutoTOCOnGamePercentX, percent);
                }

                AutoTOC.RunTOCOnGameTarget(SelectedInstallTarget, tocingUpdate);

                //Generate moddesc
                //IniData ini = new IniData();
                //ini[@"ModManager"][@"cmmver"] = App.HighestSupportedModDesc.ToString(CultureInfo.InvariantCulture); //prevent commas
                //ini[@"ModInfo"][@"game"] = @"ME3";
                //ini[@"ModInfo"][@"modname"] = modname;
                //ini[@"ModInfo"][@"moddev"] = App.AppVersionHR;
                //ini[@"ModInfo"][@"moddesc"] = M3L.GetString(M3L.string_compiledFromTheFollowingMixins);
                //ini[@"ModInfo"][@"modver"] = @"1.0";

                //generateRepaceFilesMapping(ini, modpath);
                //File.WriteAllText(Path.Combine(modpath, @"moddesc.ini"), ini.ToString());
            };
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occurred in {nbw.Name} thread: {b.Error.Message}");
                }
                OperationInProgress = false;
                ClearMixinHandler();
                if (failedApplications.Count > 0)
                {
                    var ld = new ListDialog(failedApplications, M3L.GetString(M3L.string_failedToApplyAllMixins), M3L.GetString(M3L.string_theFollowingMixinsFailedToApply), mainwindow);
                    ld.ShowDialog();
                }

                /*if (modpath != null)
                 * {
                 *  OnClosing(new DataEventArgs(modpath));
                 * }
                 * else
                 * {*/
                BottomLeftMessage = M3L.GetString(M3L.string_mixinsInstalledMaybe);
                //}
            };
            CompilePanelButton.IsOpen = false;
            nbw.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 ToggleDisabler()
            {
                NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"OIGDisablerThread");

                nbw.DoWork += async(a, b) =>
                {
                    if (!Utilities.IsGameRunning(Game))
                    {
                        var d3d9Path = Path.Combine(M3Directories.GetExecutableDirectory(SelectedTarget), @"d3d9.dll");
                        if (!File.Exists(d3d9Path))
                        {
                            if (File.Exists(Utilities.GetOriginOverlayDisableFile()))
                            {
                                Log.Information(@"Installing origin overlay disabler from cache to " + d3d9Path);
                                try
                                {
                                    File.Copy(Utilities.GetOriginOverlayDisableFile(), d3d9Path);
                                }
                                catch (Exception e)
                                {
                                    Log.Error($@"Error installing d3d9.dll: {e.Message}");
                                }
                            }
                            else
                            {
                                var client = new GitHubClient(new ProductHeaderValue(@"ME3TweaksModManager"));
                                try
                                {
                                    var releases = await client.Repository.Release.GetAll(@"ME3Tweaks", @"d3d9-blank-proxy");

                                    if (releases.Count > 0)
                                    {
                                        Log.Information(@"Parsing release information from github");

                                        //The release we want to check is always the latest with assets that is not a pre-release
                                        var latestRel = releases.FirstOrDefault(x => !x.Prerelease && x.Assets.Count > 0);
                                        if (latestRel != null)
                                        {
                                            var downloadUrl        = latestRel.Assets[0].BrowserDownloadUrl;
                                            var downloadedZipAsset = OnlineContent.DownloadToMemory(downloadUrl);
                                            using var zf = new ZipArchive(downloadedZipAsset.result);
                                            var d3d9 = zf.Entries.First(x => x.FullName == @"d3d9.dll");
                                            if (d3d9 != null)
                                            {
                                                await using var data = d3d9.Open();
                                                var memStream = new MemoryStream();
                                                data.CopyTo(memStream);
                                                try
                                                {
                                                    Log.Information(@"Installing origin overlay disabler from memory to " + d3d9Path);
                                                    memStream.WriteToFile(d3d9Path); //install
                                                    Log.Information(@"Caching d3d9 disabler");
                                                    memStream.WriteToFile(Utilities.GetOriginOverlayDisableFile());
                                                }
                                                catch (Exception e)
                                                {
                                                    Log.Error(@"Cannot install/cache disabler: " + e.Message);
                                                }
                                            }
                                        }
                                    }
                                }
                                catch (Exception e)
                                {
                                    Log.Error(@"Error checking for tool update: " + e);
                                }
                            }
                        }
                        else
                        {
                            Log.Information(@"Deleting " + d3d9Path);
                            try
                            {
                                File.Delete(d3d9Path);
                            }
                            catch (Exception e)
                            {
                                Log.Error($@"Error deleting d3d9.dll: {e.Message}");
                            }
                        }
                    }
                };
                nbw.RunWorkerCompleted += (await, b) =>
                {
                    SetupDisablerButtonText();
                };
                nbw.RunWorkerAsync();
            }
Exemple #20
0
        /// <summary>
        /// Launches the game. This call is blocking as it may wait for Steam to run, so it should be run on a background thread.
        /// </summary>
        /// <param name="target"></param>
        public static void LaunchGame(GameTarget target)
        {
            // Update LODs for target
            if (Settings.AutoUpdateLODs4K || Settings.AutoUpdateLODs2K)
            {
                target.UpdateLODs(Settings.AutoUpdateLODs2K);
            }

            var exe             = M3Directories.GetExecutablePath(target);
            var exeDir          = M3Directories.GetExecutableDirectory(target);
            var environmentVars = new Dictionary <string, string>();

            if (target.GameSource != null)
            {
                // IS GAME STEAM BASED?
                if (target.GameSource.Contains(@"Steam"))
                {
                    var steamInstallPath =
                        Utilities.GetRegistrySettingString(@"HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Valve\Steam",
                                                           @"InstallPath");
                    if (steamInstallPath != null && Directory.Exists(steamInstallPath))
                    {
                        environmentVars[@"SteamPath"] = steamInstallPath;

                        // Ensure steam is running and ready
                        var steamExe = Path.Combine(steamInstallPath, @"steam.exe");
                        EnsureSteamRunning(steamExe); // Can block up for some time!
                    }

                    int gameId = 0;
                    switch (target.Game)
                    {
                    case MEGame.ME1:
                        gameId = 17460;
                        break;

                    case MEGame.ME2:
                        gameId = 24980;
                        break;

                    case MEGame.ME3:
                        gameId = 1238020;
                        break;
                    }

                    environmentVars[@"SteamAppId"]         = gameId.ToString();
                    environmentVars[@"SteamGameId"]        = gameId.ToString();
                    environmentVars[@"SteamOverlayGameId"] = gameId.ToString();

                    // Make steam_appid.txt next to exe. It can help launch game if steam.exe is already running
                    var steamappidfile = Path.Combine(exeDir, @"steam_appid.txt");
                    if (!File.Exists(steamappidfile))
                    {
                        try
                        {
                            File.WriteAllText(steamappidfile, gameId.ToString());
                        }
                        catch (Exception e)
                        {
                            Log.Error($@"Could not install steam_appid.txt: {e.Message}");
                        }
                    }
                }
                else if (target.GameSource.Contains(@"Origin") && target.RegistryActive && target.Game < MEGame.ME3 && Settings.LaunchGamesThroughOrigin) // Must be registry active or origin will run the wrong game.
                {
                    // ME2 seems to have lots of problems directly running due to it's licensing system
                    // We should try to run it through Origin to avoid this problem

                    var parFile = Path.Combine(exeDir, Path.GetFileNameWithoutExtension(exe) + @".par");
                    if (target.Game == MEGame.ME2 && File.Exists(parFile))
                    {
                        var fInfo = new FileInfo(exe);
                        if (fInfo.Length < 5 * FileSize.MebiByte)
                        {
                            // Does this executable need swapped? MassEffect2.exe does not seem to reliably run through Origin and just exits early for some reason
                        }
                        var parContents = PARTools.DecodePAR(File.ReadAllBytes(parFile));
                        var contentIds  = parContents[@"Base"].GetValue(@"ContentId")?.Value;

                        if (!string.IsNullOrWhiteSpace(contentIds))
                        {
                            exe = $@"origin://launchgame/{contentIds}";
                        }
                    }
                }
            }

            Utilities.RunProcess(exe, (string)null, false, true, false, false, environmentVars);
            Thread.Sleep(3500); // Keep task alive for a bit
        }
Exemple #21
0
        public void TestASIManager()
        {
            GlobalTest.Init();
            Random random = new Random();

            Console.WriteLine(@"Loading ASI Manager Manifest");
            ASIManager.LoadManifest();

            var games = new[] { MEGame.ME1, MEGame.ME2, MEGame.ME3 };

            foreach (var game in games)
            {
                var        root   = GlobalTest.GetTestGameFoldersDirectory(game);
                var        normal = Path.Combine(root, "normal");
                GameTarget gt     = new GameTarget(game, normal, true, false, isTest: true);

                var asiDir = M3Directories.GetASIPath(gt);
                if (Directory.Exists(asiDir))
                {
                    // Clean slate
                    Utilities.DeleteFilesAndFoldersRecursively(asiDir);
                }

                var asisForGame = ASIManager.GetASIModsByGame(game);

                // 1: Test Installs of upgrades of versions
                foreach (var asi in asisForGame)
                {
                    // Install every version of an ASI and then ensure only one ASI of that type exists in the directory.
                    foreach (var v in asi.Versions)
                    {
                        var sourceBools = new bool?[] { true, false, null }; //online, local, let app decide
                        foreach (var sourceBool in sourceBools)
                        {
                            // INSTALL FROM SOURCE
                            Console.WriteLine($@"Install source variable: {sourceBool}");
                            Assert.IsTrue(ASIManager.InstallASIToTarget(v, gt, sourceBool),
                                          $"Installation of ASI failed: {v.Name}");
                            Assert.AreEqual(1, Directory.GetFiles(asiDir).Length,
                                            "The count of files in the ASI directory is not 1 after install of an ASI!");

                            // Check is installed
                            var installedASIs = gt.GetInstalledASIs().OfType <KnownInstalledASIMod>().ToList();
                            Assert.AreEqual(1, installedASIs.Count,
                                            "The amount of installed ASIs as fetched by GameTarget GetInstalledASIs() is not equal to 1!");

                            // Check it maps to the correct one.
                            var instASI = installedASIs.First();
                            Assert.AreEqual(v, instASI.AssociatedManifestItem, "The parsed installed ASI does not match the one we fed to ASIManager.InstallASIToTarget()!");

                            // Rename it to something random so the next version has to find it by the hash and not the filename.
                            var newPath = Path.Combine(asiDir, Guid.NewGuid() + ".asi");
                            File.Move(instASI.InstalledPath, newPath, false);

                            // Ensure it still can be found.
                            installedASIs = gt.GetInstalledASIs().OfType <KnownInstalledASIMod>().ToList();
                            Assert.AreEqual(1, installedASIs.Count, "The amount of installed ASIs as fetched by GameTarget GetInstalledASIs() is not equal to 1 after renaming the file!");

                            // Make multiple clones, to ensure all old ones get deleted on upgrades.
                            for (int i = 0; i < 5; i++)
                            {
                                var clonePath = Path.Combine(asiDir, instASI.AssociatedManifestItem.InstalledPrefix + i + ".asi");
                                File.Copy(newPath, clonePath, true);
                            }

                            installedASIs = gt.GetInstalledASIs().OfType <KnownInstalledASIMod>().ToList();
                            Assert.AreEqual(6, installedASIs.Count, "The amount of installed ASIs as fetched by GameTarget GetInstalledASIs() is not equal to 6 after cloning the file 5 times!");
                        }
                    }

                    var finalASIsPreRandomization = gt.GetInstalledASIs();
                    int randomCount = 0;
                    foreach (var iam in finalASIsPreRandomization)
                    {
                        // Test randomly editing it.
                        byte[] randomData = new byte[256];
                        random.NextBytes(randomData);
                        File.WriteAllBytes(iam.InstalledPath, randomData);
                        randomCount++;

                        var unknownInstalledASIs = gt.GetInstalledASIs().OfType <UnknownInstalledASIMod>().ToList();
                        Assert.AreEqual(randomCount, unknownInstalledASIs.Count, "Writing random bytes to installed ASI made amount of installed ASIs not correct!");
                    }

                    foreach (var v in finalASIsPreRandomization)
                    {
                        // Test uninstall and remove
                        Assert.IsTrue(v.Uninstall(), $"ASI failed to uninstall: {v.InstalledPath}");
                    }
                    Assert.AreEqual(0, Directory.GetFiles(asiDir).Length, "Leftover files remain after uninstalling all ASIs from target");
                }
            }
        }
        /// <summary>
        /// Installs the specific version of an ASI to the specified target
        /// </summary>
        /// <param name="modVersion"></param>
        /// <param name="target"></param>
        /// <param name="forceSource">Null to let application choose the source, true to force online, false to force local cache. This parameter is used for testing</param>
        /// <returns></returns>
        public static bool InstallASIToTarget(ASIModVersion asi, GameTarget target, bool?forceSource = null)
        {
            if (asi.Game != target.Game)
            {
                throw new Exception($@"ASI {asi.Name} cannot be installed to game {target.Game}");
            }
            Log.Information($@"Processing ASI installation request: {asi.Name} v{asi.Version} -> {target.TargetPath}");
            string destinationFilename  = $@"{asi.InstalledPrefix}-v{asi.Version}.asi";
            string cachedPath           = Path.Combine(CachedASIsFolder, destinationFilename);
            string destinationDirectory = M3Directories.GetASIPath(target);

            if (!Directory.Exists(destinationDirectory))
            {
                Log.Information(@"Creating ASI directory in game: " + destinationDirectory);
                Directory.CreateDirectory(destinationDirectory);
            }
            string finalPath = Path.Combine(destinationDirectory, destinationFilename);

            var installedASIs = target.GetInstalledASIs();
            // Delete existing ASIs from the same group to ensure we don't install the same mod
            var  existingSameGroupMods            = installedASIs.OfType <KnownInstalledASIMod>().Where(x => x.AssociatedManifestItem.OwningMod == asi.OwningMod).ToList();
            bool hasExistingVersionOfModInstalled = false;

            if (existingSameGroupMods.Any())
            {
                foreach (var v in existingSameGroupMods)
                {
                    if (v.Hash == asi.Hash && !forceSource.HasValue && !hasExistingVersionOfModInstalled) //If we are forcing a source, we should always install. Delete duplicates past the first one
                    {
                        Log.Information($@"{v.AssociatedManifestItem.Name} is already installed. We will not remove the existing correct installed ASI for this install request");
                        hasExistingVersionOfModInstalled = true;
                        continue; //Don't delete this one. We are already installed. There is no reason to install it again.
                    }
                    Log.Information($@"Deleting existing ASI from same group: {v.InstalledPath}");
                    v.Uninstall();
                    installedASIs.Remove(v);
                }
            }

            // Remove any conflicting ASIs
            foreach (var v in installedASIs.OfType <KnownInstalledASIMod>())
            {
                if (asi.OtherGroupsToDeleteOnInstall.Contains(v.AssociatedManifestItem.OwningMod.UpdateGroupId))
                {
                    // Delete tis other ASI
                    v.Uninstall();
                }
            }

            if (hasExistingVersionOfModInstalled)
            {
                return(true); // The ASI was already installed. There is nothing left to do
            }

            // Install the ASI
            //if (forceSource == null || forceSource.Value == false)
            //{
            //    Debug.WriteLine("Hit me");
            //}
            string md5;
            bool   useLocal = forceSource.HasValue && !forceSource.Value; // false (forceLocal)

            if (!useLocal && !forceSource.HasValue)
            {
                useLocal = File.Exists(cachedPath);
            }
            if (useLocal)
            {
                //Check hash first
                md5 = Utilities.CalculateMD5(cachedPath);
                if (md5 == asi.Hash)
                {
                    Log.Information($@"Copying ASI from cached library to destination: {cachedPath} -> {finalPath}");

                    File.Copy(cachedPath, finalPath, true);
                    Log.Information($@"Installed ASI to {finalPath}");
                    Analytics.TrackEvent(@"Installed ASI", new Dictionary <string, string>()
                    {
                        { @"Filename", Path.GetFileNameWithoutExtension(finalPath) }
                    });
                    return(true);
                }
            }

            if (!forceSource.HasValue || forceSource.Value)
            {
                WebRequest request = WebRequest.Create(asi.DownloadLink);
                Log.Information(@"Fetching remote ASI from server");
                try
                {
                    using WebResponse response = request.GetResponse();
                    var memoryStream = new MemoryStream();
                    response.GetResponseStream().CopyTo(memoryStream);
                    //MD5 check on file for security
                    md5 = Utilities.CalculateMD5(memoryStream);
                    if (md5 != asi.Hash)
                    {
                        //ERROR!
                        Log.Error(@"Downloaded ASI did not match the manifest! It has the wrong hash.");
                        return(false);
                    }

                    Log.Information(@"Fetched remote ASI from server. Installing ASI to " + finalPath);
                    memoryStream.WriteToFile(finalPath);
                    Log.Information(@"ASI successfully installed.");
                    Analytics.TrackEvent(@"Installed ASI", new Dictionary <string, string>()
                    {
                        { @"Filename", Path.GetFileNameWithoutExtension(finalPath) }
                    });

                    //Cache ASI
                    if (!Directory.Exists(CachedASIsFolder))
                    {
                        Log.Information(@"Creating cached ASIs folder");
                        Directory.CreateDirectory(CachedASIsFolder);
                    }

                    Log.Information(@"Caching ASI to local ASI library: " + cachedPath);
                    memoryStream.WriteToFile(cachedPath);
                    return(true);
                }
                catch (Exception e)
                {
                    Log.Error($@"Error downloading ASI from {asi.DownloadLink}: {e.Message}");
                }
            }

            // We could not install the ASI
            return(false);
        }
Exemple #23
0
        public static void BuildBioPGlobal(GameTarget target)
        {
            M3MergeDLC.RemoveMergeDLC(target);
            var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(target.Game, gameRootOverride: target.TargetPath);

            //var mergeFiles = loadedFiles.Where(x =>
            //    x.Key.StartsWith(@"BioH_") && x.Key.Contains(@"_DLC_MOD_") && x.Key.EndsWith(@".pcc") && !x.Key.Contains(@"_LOC_") && !x.Key.Contains(@"_Explore."));

            Log.Information($@"SQMMERGE: Building BioP_Global");
            var appearanceInfo = new CaseInsensitiveDictionary <List <SquadmateInfoSingle> >();

            int appearanceId       = 255; // starting
            int currentConditional = STARTING_OUTFIT_CONDITIONAL;

            // Scan squadmate merge files
            var sqmSupercedances = M3Directories.GetFileSupercedances(target, new[] { @".sqm" });

            if (sqmSupercedances.TryGetValue(SQUADMATE_MERGE_MANIFEST_FILE, out var infoList))
            {
                infoList.Reverse();
                foreach (var dlc in infoList)
                {
                    Log.Information($@"SQMMERGE: Processing {dlc}");

                    var jsonFile    = Path.Combine(M3Directories.GetDLCPath(target), dlc, target.Game.CookedDirName(), SQUADMATE_MERGE_MANIFEST_FILE);
                    var infoPackage = JsonConvert.DeserializeObject <SquadmateMergeInfo>(File.ReadAllText(jsonFile));
                    if (!infoPackage.Validate(dlc, target, loadedFiles))
                    {
                        continue; // skip this
                    }

                    // Enumerate all outfits listed for a single squadmate
                    foreach (var outfit in infoPackage.Outfits)
                    {
                        List <SquadmateInfoSingle> list;

                        // See if we already have an outfit list for this squadmate, maybe from another mod...
                        if (!appearanceInfo.TryGetValue(outfit.HenchName, out list))
                        {
                            list = new List <SquadmateInfoSingle>();
                            appearanceInfo[outfit.HenchName] = list;
                        }

                        outfit.ConditionalIndex = currentConditional++; // This is always incremented, so it might appear out of order in game files depending on how mod order is processed, that should be okay though.
                        outfit.AppearanceId     = appearanceId++;       // may need adjusted
                        outfit.DLCName          = dlc;
                        list.Add(outfit);
                        Log.Information($@"SQMMERGE: ConditionalIndex for {outfit.HenchName} appearanceid {outfit.AppearanceId}: {outfit.ConditionalIndex}");
                    }
                }
            }

            if (appearanceInfo.Any())
            {
                var biopGlobal      = MEPackageHandler.OpenMEPackage(loadedFiles[@"BioP_Global.pcc"]);
                var lsk             = biopGlobal.Exports.FirstOrDefault(x => x.ClassName == @"LevelStreamingKismet");
                var persistentLevel = biopGlobal.FindExport(@"TheWorld.PersistentLevel");

                // Clone LevelStreamingKismets
                foreach (var sqm in appearanceInfo.Values)
                {
                    foreach (var outfit in sqm)
                    {
                        var fName  = outfit.HenchPackage;
                        var newLSK = EntryCloner.CloneEntry(lsk);
                        newLSK.WriteProperty(new NameProperty(fName, @"PackageName"));

                        if (target.Game.IsGame3())
                        {
                            // Game 3 has _Explore files too
                            fName += @"_Explore";
                            newLSK = EntryCloner.CloneEntry(lsk);
                            newLSK.WriteProperty(new NameProperty(fName, @"PackageName"));
                        }
                    }
                }

                // Update BioWorldInfo
                // Doesn't have consistent number so we can't find it by instanced full path
                var bioWorldInfo = biopGlobal.Exports.FirstOrDefault(x => x.ClassName == @"BioWorldInfo");

                var props = bioWorldInfo.GetProperties();

                // Update Plot Streaming
                var plotStreaming = props.GetProp <ArrayProperty <StructProperty> >(@"PlotStreaming");
                foreach (var sqm in appearanceInfo.Values)
                {
                    foreach (var outfit in sqm)
                    {
                        // find item to add to
                        buildPlotElementObject(plotStreaming, outfit, target.Game, false);
                        if (target.Game.IsGame3())
                        {
                            buildPlotElementObject(plotStreaming, outfit, target.Game, true);
                        }
                    }
                }


                // Update StreamingLevels
                var streamingLevels = props.GetProp <ArrayProperty <ObjectProperty> >(@"StreamingLevels");
                streamingLevels.ReplaceAll(biopGlobal.Exports.Where(x => x.ClassName == @"LevelStreamingKismet").Select(x => new ObjectProperty(x)));

                bioWorldInfo.WriteProperties(props);


                M3MergeDLC.GenerateMergeDLC(target, Guid.NewGuid());

                // Save BioP_Global into DLC
                var cookedDir = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName());
                var outP      = Path.Combine(cookedDir, @"BioP_Global.pcc");
                biopGlobal.Save(outP);

                // Generate conditionals file
                if (target.Game.IsGame3())
                {
                    CNDFile cnd = new CNDFile();
                    cnd.ConditionalEntries = new List <CNDFile.ConditionalEntry>();

                    foreach (var sqm in appearanceInfo.Values)
                    {
                        foreach (var outfit in sqm)
                        {
                            var scText   = $@"(plot.ints[{GetSquadmateOutfitInt(outfit.HenchName, target.Game)}] == i{outfit.MemberAppearanceValue})";
                            var compiled = ME3ConditionalsCompiler.Compile(scText);
                            cnd.ConditionalEntries.Add(new CNDFile.ConditionalEntry()
                            {
                                Data = compiled, ID = outfit.ConditionalIndex
                            });
                        }
                    }

                    cnd.ToFile(Path.Combine(cookedDir, $@"Conditionals{M3MergeDLC.MERGE_DLC_FOLDERNAME}.cnd"));
                }
                else if (target.Game.IsGame2())
                {
                    var startupF = Path.Combine(cookedDir, $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc");
                    var startup  = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream(
                                                                                $@"MassEffectModManagerCore.modmanager.mergedlc.{target.Game}.Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc"));
                    var conditionalClass =
                        startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals");

                    // Add Conditional Functions
                    FileLib fl          = new FileLib(startup);
                    bool    initialized = fl.Initialize(new RelativePackageCache()
                    {
                        RootPath = M3Directories.GetBioGamePath(target)
                    });
                    if (!initialized)
                    {
                        throw new Exception(
                                  $@"FileLib for script update could not initialize, cannot install conditionals");
                    }


                    var funcToClone =
                        startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals.TemplateFunction");
                    foreach (var sqm in appearanceInfo.Values)
                    {
                        foreach (var outfit in sqm)
                        {
                            var func = EntryCloner.CloneEntry(funcToClone);
                            func.ObjectName = $@"F{outfit.ConditionalIndex}";
                            func.indexValue = 0;

                            var scText = new StreamReader(Utilities.GetResourceStream(
                                                              $@"MassEffectModManagerCore.modmanager.squadmates.{target.Game}.HasOutfitOnConditional.txt"))
                                         .ReadToEnd();

                            scText = scText.Replace(@"%CONDITIONALNUM%", outfit.ConditionalIndex.ToString());
                            scText = scText.Replace(@"%SQUADMATEOUTFITPLOTINT%", outfit.AppearanceId.ToString());
                            scText = scText.Replace(@"%OUTFITINDEX%", outfit.MemberAppearanceValue.ToString());

                            (_, MessageLog log) = UnrealScriptCompiler.CompileFunction(func, scText, fl);
                            if (log.AllErrors.Any())
                            {
                                Log.Error($@"Error compiling function {func.InstancedFullPath}:");
                                foreach (var l in log.AllErrors)
                                {
                                    Log.Error(l.Message);
                                }

                                throw new Exception(M3L.GetString(M3L.string_interp_errorCompilingConditionalFunction, func, string.Join('\n', log.AllErrors.Select(x => x.Message))));
                            }
                        }
                    }


                    // Relink the conditionals chain
                    UClass uc = ObjectBinary.From <UClass>(conditionalClass);
                    uc.UpdateLocalFunctions();
                    uc.UpdateChildrenChain();
                    conditionalClass.WriteBinary(uc);

                    startup.Save(startupF);
                }


                // Add startup package, member appearances
                if (target.Game.IsGame2())
                {
                    var bioEngine = Path.Combine(cookedDir, @"BIOEngine.ini");
                    var ini       = DuplicatingIni.LoadIni(bioEngine);

                    var startupSection = ini.GetOrAddSection(@"Engine.StartupPackages");

                    startupSection.Entries.Add(new DuplicatingIni.IniEntry(@"+DLCStartupPackage", $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}"));
                    startupSection.Entries.Add(new DuplicatingIni.IniEntry(@"+Package", $@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}"));

                    ini.WriteToFile(bioEngine);
                }
                else if (target.Game.IsGame3())
                {
                    var mergeCoalFile = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName(), $@"Default_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.bin");
                    var mergeCoal     = CoalescedConverter.DecompileGame3ToMemory(new MemoryStream(File.ReadAllBytes(mergeCoalFile)));

                    // Member appearances
                    var bioUiDoc = XDocument.Parse(mergeCoal[@"BIOUI.xml"]);

                    foreach (var sqm in appearanceInfo.Values)
                    {
                        foreach (var outfit in sqm)
                        {
                            var entry = new Game3CoalescedValueEntry()
                            {
                                Section = @"sfxgame.sfxguidata_teamselect",
                                Name    = @"selectappearances",
                                Type    = 3,
                                Value   = StringStructParser.BuildCommaSeparatedSplitValueList(outfit.ToPropertyDictionary(), @"AvailableImage", @"HighlightImage", @"DeadImage", @"SilhouetteImage")
                            };
                            Game3CoalescedHelper.AddArrayEntry(bioUiDoc, entry);
                        }
                    }

                    mergeCoal[@"BIOUI.xml"] = bioUiDoc.ToString();

                    // Dynamic load mapping
                    var bioEngineDoc = XDocument.Parse(mergeCoal[@"BIOEngine.xml"]);

                    foreach (var sqm in appearanceInfo.Values)
                    {
                        foreach (var outfit in sqm)
                        {
                            //
                            // * <Section name="sfxgame.sfxengine">
                            // <Property name="dynamicloadmapping">
                            // <Value type="3">(ObjectName="BIOG_GesturesConfigDLC.RuntimeData",SeekFreePackageName="GesturesConfigDLC")</Value>

                            var entry = new Game3CoalescedValueEntry()
                            {
                                Section = @"sfxgame.sfxengine",
                                Name    = @"dynamicloadmapping",
                                Type    = 3
                            };

                            entry.Values.Add($"(ObjectName=\"{outfit.AvailableImage}\",SeekFreePackageName=\"SFXHenchImages_{outfit.DLCName}\")"); // do not localize
                            entry.Values.Add($"(ObjectName=\"{outfit.HighlightImage}\",SeekFreePackageName=\"SFXHenchImages_{outfit.DLCName}\")"); // do not localize
                            Game3CoalescedHelper.AddArrayEntry(bioEngineDoc, entry);
                        }
                    }

                    mergeCoal[@"BIOEngine.xml"] = bioEngineDoc.ToString();


                    CoalescedConverter.CompileFromMemory(mergeCoal).WriteToFile(mergeCoalFile);
                }
            }
        }
Exemple #24
0
        public void TestM3DirectoryResults()
        {
            GlobalTest.Init();
            List <GameTarget> targets = new List <GameTarget>();
            var root = GlobalTest.GetTestGameFoldersDirectory(MEGame.ME1);

            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(MEGame.ME1, d, false, false);
                gt.ValidateTarget();
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            root = GlobalTest.GetTestGameFoldersDirectory(MEGame.ME2);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(MEGame.ME2, d, false, false);
                gt.ValidateTarget();
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }
            root = GlobalTest.GetTestGameFoldersDirectory(MEGame.ME3);
            foreach (var d in Directory.GetDirectories(root))
            {
                GameTarget gt = new GameTarget(MEGame.ME3, d, false, false);
                gt.ValidateTarget();
                if (gt.IsValid)
                {
                    targets.Add(gt);
                }
            }

            foreach (var target in targets)
            {
                string expectedDLCPath;
                string expectedASIPath;
                string expectedBioGamePath;
                string expectedCookedPath;
                string expectedExecutableDir;
                if (target.Game == MEGame.ME1)
                {
                    expectedDLCPath       = Path.Combine(target.TargetPath, @"DLC");
                    expectedASIPath       = Path.Combine(target.TargetPath, @"Binaries", @"asi");
                    expectedBioGamePath   = Path.Combine(target.TargetPath, @"BioGame");
                    expectedCookedPath    = Path.Combine(target.TargetPath, @"BioGame", @"CookedPC");
                    expectedExecutableDir = Path.Combine(target.TargetPath, @"Binaries");
                }
                else if (target.Game == MEGame.ME2)
                {
                    expectedDLCPath       = Path.Combine(target.TargetPath, @"BioGame", @"DLC");
                    expectedASIPath       = Path.Combine(target.TargetPath, @"Binaries", @"asi");
                    expectedBioGamePath   = Path.Combine(target.TargetPath, @"BioGame");
                    expectedCookedPath    = Path.Combine(target.TargetPath, @"BioGame", @"CookedPC");
                    expectedExecutableDir = Path.Combine(target.TargetPath, @"Binaries");
                }
                else
                {
                    expectedDLCPath       = Path.Combine(target.TargetPath, @"BIOGame", @"DLC");
                    expectedASIPath       = Path.Combine(target.TargetPath, @"Binaries", @"Win32", @"asi");
                    expectedBioGamePath   = Path.Combine(target.TargetPath, @"BIOGame");
                    expectedCookedPath    = Path.Combine(target.TargetPath, @"BIOGame", @"CookedPCConsole");
                    expectedExecutableDir = Path.Combine(target.TargetPath, @"Binaries", @"Win32");
                }

                Assert.AreEqual(expectedDLCPath, M3Directories.GetDLCPath(target));
                Assert.AreEqual(expectedASIPath, M3Directories.GetASIPath(target));
                Assert.AreEqual(expectedBioGamePath, M3Directories.GetBioGamePath(target));
                Assert.AreEqual(expectedCookedPath, M3Directories.GetCookedPath(target));
                Assert.AreEqual(expectedExecutableDir, M3Directories.GetExecutableDirectory(target));
            }
        }
Exemple #25
0
        public static async void StartRestore(MainWindow mw, bool isQuick, Action postRestoreDelegate = null)
        {
            var pd = await mw.ShowProgressAsync("Restoring game", "Preparing to restore game");

            pd.SetIndeterminate();
            await Task.Run(() =>
            {
                if (isQuick)
                {
                    // Nuke the DLC
                    MERLog.Information(@"Quick restore started");
                    pd.SetMessage("Removing randomize DLC component");
                    var dlcModPath = MERFileSystem.GetDLCModPath();
                    if (Directory.Exists(dlcModPath))
                    {
                        MERLog.Information($@"Deleting {dlcModPath}");
                        Utilities.DeleteFilesAndFoldersRecursively(dlcModPath);
                    }

                    mw.DLCComponentInstalled = false;


                    // Restore basegame only files
                    pd.SetMessage("Restoring randomized basegame files");
                    var isControllerModInstalled = SFXGame.IsControllerBasedInstall();

                    var backupPath       = BackupService.GetGameBackupPath(MERFileSystem.Game, out _, false);
                    var gameCookedPath   = M3Directories.GetCookedPath(Locations.GetTarget(MERFileSystem.Game));
                    var backupCookedPath = MEDirectories.GetCookedPath(MERFileSystem.Game, backupPath);
                    foreach (var bgf in MERFileSystem.alwaysBasegameFiles)
                    {
                        var srcPath  = Path.Combine(backupCookedPath, bgf);
                        var destPath = Path.Combine(gameCookedPath, bgf);
                        MERLog.Information($@"Restoring {bgf}");
                        File.Copy(srcPath, destPath, true);
                    }

                    if (isControllerModInstalled)
                    {
                        // We must also restore Coalesced.ini or it will reference a UI that is no longer available and game will not boot
                        MERLog.Information(@"Controller based install detected, also restoring Coalesced.ini to prevent startup crash");
                        File.Copy(Path.Combine(backupPath, "BioGame", "Config", "PC", "Cooked", "Coalesced.ini"), Path.Combine(Locations.GetTarget(MERFileSystem.Game).TargetPath, "BioGame", "Config", "PC", "Cooked", "Coalesced.ini"), true);
                    }

                    // Delete basegame TFC
                    var baseTFC = MERFileSystem.GetTFCPath(false);
                    if (File.Exists(baseTFC))
                    {
                        File.Delete(baseTFC);
                    }

                    // Done!
                }
                else
                {
                    // Full restore
                    var target = Locations.GetTarget(MERFileSystem.Game);
                    MERLog.Information($@"Performing full game restore on {target.TargetPath} target after restore");

                    object syncObj = new object();
                    BackupHandler.GameRestore gr = new BackupHandler.GameRestore(MERFileSystem.Game)
                    {
                        ConfirmationCallback = (title, message) =>
                        {
                            bool response = false;
                            Application.Current.Dispatcher.Invoke(async() =>
                            {
                                response = await mw.ShowMessageAsync(title, message,
                                                                     MessageDialogStyle.AffirmativeAndNegative,
                                                                     new MetroDialogSettings()
                                {
                                    AffirmativeButtonText = "OK",
                                    NegativeButtonText    = "Cancel",
                                }) == MessageDialogResult.Affirmative;
                                lock (syncObj)
                                {
                                    Monitor.Pulse(syncObj);
                                }
                            });
                            lock (syncObj)
                            {
                                Monitor.Wait(syncObj);
                            }

                            return(response);
                        },
                        BlockingErrorCallback = (title, message) => { Application.Current.Dispatcher.Invoke(async() => { await mw.ShowMessageAsync(title, message); }); },
                        RestoreErrorCallback  = (title, message) => { Application.Current.Dispatcher.Invoke(async() => { await mw.ShowMessageAsync(title, message); }); },
                        UpdateStatusCallback  = message =>
                                                Application.Current.Dispatcher.Invoke(() => pd.SetMessage(message)),
                        UpdateProgressCallback = (done, total) =>
                                                 Application.Current.Dispatcher.Invoke(() =>
                        {
                            pd.SetProgress(done * 1d / total);
                            if (total != 0)
                            {
                                TaskbarHelper.SetProgressState(TaskbarProgressBarState.Normal);
                                TaskbarHelper.SetProgress(done * 1.0 / total);
                            }
                        }),
                        SetProgressIndeterminateCallback = indeterminate => Application.Current.Dispatcher.Invoke(() =>
                        {
                            if (indeterminate)
                            {
                                pd.SetIndeterminate();
                            }
                            TaskbarHelper.SetProgressState(indeterminate ? TaskbarProgressBarState.Indeterminate : TaskbarProgressBarState.Normal);
                        }),
                        SelectDestinationDirectoryCallback = (title, message) =>
                        {
                            string selectedPath = null;
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                // Not sure if this has to be synced
                                CommonOpenFileDialog ofd = new CommonOpenFileDialog()
                                {
                                    Title            = "Select restore destination directory",
                                    IsFolderPicker   = true,
                                    EnsurePathExists = true
                                };
                                if (ofd.ShowDialog() == CommonFileDialogResult.Ok)
                                {
                                    selectedPath = ofd.FileName;
                                }
                            });
                            return(selectedPath);
                        }
                    };
                    gr.PerformRestore(target.TargetPath);
                    mw.DLCComponentInstalled = false;
                    MERLog.Information(@"Reloading target after restore");
                    target.ReloadGameTarget(false, false);
                    mw.SetupTargetDescriptionText();
                }
            }).ContinueWithOnUIThread(async x =>
            {
                TaskbarHelper.SetProgressState(TaskbarProgressBarState.NoProgress);
                await pd.CloseAsync();
                postRestoreDelegate?.Invoke();
            });
        }
        public void SetupInitialSelection(GameTarget target, Mod mod)
        {
            UIIsSelectable = false; //Reset
            IsSelected     = false; //Reset
            if (Condition == AltDLCCondition.COND_MANUAL)
            {
                IsSelected = CheckedByDefault;
                if (DLCRequirementsForManual != null)
                {
                    var dlc = M3Directories.GetInstalledDLC(target);

                    if (mod.ModDescTargetVersion >= 6.3)
                    {
                        var requiredDLC           = DLCRequirementsForManual.Where(x => !x.StartsWith(@"-") || x.StartsWith(@"+")).Select(x => x.TrimStart('+')); // none or + means 'must exist'
                        var notPresentDLCRequired = DLCRequirementsForManual.Where(x => x.StartsWith(@"-")).Select(x => x.TrimStart('-'));
                        UIIsSelectable = dlc.ContainsAll(requiredDLC, StringComparer.InvariantCultureIgnoreCase) && dlc.ContainsNone(notPresentDLCRequired, StringComparer.InvariantCultureIgnoreCase);
                    }
                    else
                    {
                        // Previous logic. Left here to ensure nothing changes.
                        UIIsSelectable = dlc.ContainsAll(DLCRequirementsForManual, StringComparer.InvariantCultureIgnoreCase);
                    }

                    if (!UIIsSelectable && mod.ModDescTargetVersion >= 6.2)
                    {
                        // Mod Manager 6.2: If requirements are not met this option is forcibly not checked.
                        // Mods targeting Moddesc 6 or 6.1 will possibly be bugged if they used this feature
                        IsSelected = false;
                    }
                    CLog.Information($@" > AlternateDLC SetupInitialSelection() {FriendlyName}: UISelectable: {UIIsSelectable}, conducted DLCRequirements check.", Settings.LogModInstallation);
                }
                else
                {
                    UIIsSelectable = true;
                }

                return;
            }

            var installedDLC = M3Directories.GetInstalledDLC(target);

            switch (Condition)
            {
            case AltDLCCondition.COND_DLC_NOT_PRESENT:
            case AltDLCCondition.COND_ANY_DLC_NOT_PRESENT:
                IsSelected = !ConditionalDLC.All(i => installedDLC.Contains(i, StringComparer.CurrentCultureIgnoreCase));
                break;

            case AltDLCCondition.COND_DLC_PRESENT:
            case AltDLCCondition.COND_ANY_DLC_PRESENT:
                IsSelected = ConditionalDLC.Any(i => installedDLC.Contains(i, StringComparer.CurrentCultureIgnoreCase));
                break;

            case AltDLCCondition.COND_ALL_DLC_NOT_PRESENT:
                IsSelected = !ConditionalDLC.Any(i => installedDLC.Contains(i, StringComparer.CurrentCultureIgnoreCase));
                break;

            case AltDLCCondition.COND_ALL_DLC_PRESENT:
                IsSelected = ConditionalDLC.All(i => installedDLC.Contains(i, StringComparer.CurrentCultureIgnoreCase));
                break;

            case AltDLCCondition.COND_SPECIFIC_SIZED_FILES:
            {
                var selected = true;
                foreach (var reqPair in RequiredSpecificFiles)
                {
                    if (selected)
                    {
                        var targetFile = Path.Combine(target.TargetPath, reqPair.Key);
                        selected &= File.Exists(targetFile) && new FileInfo(targetFile).Length == reqPair.Value;
                    }
                }

                IsSelected = selected;
            }
            break;

            case AltDLCCondition.COND_SPECIFIC_DLC_SETUP:
            {
                var selected = true;
                foreach (var condDlc in ConditionalDLC)
                {
                    if (selected)
                    {
                        bool existenceRule = condDlc.Substring(0, 1) == @"+";
                        var  dlcfoldername = condDlc.Substring(1);

                        if (existenceRule)
                        {
                            selected &= installedDLC.Contains(dlcfoldername, StringComparer.CurrentCultureIgnoreCase);
                        }
                        else
                        {
                            selected &= !installedDLC.Contains(dlcfoldername, StringComparer.CurrentCultureIgnoreCase);
                        }
                    }
                }

                IsSelected = selected;
            }
            break;
            }

            UIIsSelectable = false; //autos
            //IsSelected; //autos
        }
        public static bool RunTOCOnGameTarget(GameTarget target, Action <int> percentDoneCallback = null)
        {
            Log.Information(@"Autotocing game: " + target.TargetPath);

            //get toc target folders, ensuring we clean up the inputs a bit.
            string baseDir    = Path.GetFullPath(Path.Combine(target.TargetPath, @"BIOGame"));
            string dlcDirRoot = M3Directories.GetDLCPath(target);

            if (!Directory.Exists(dlcDirRoot))
            {
                Log.Error(@"Specified game directory does not appear to be a Mass Effect 3 root game directory (DLC folder missing).");
                return(false);
            }

            var tocTargets = (new DirectoryInfo(dlcDirRoot)).GetDirectories().Select(x => x.FullName).Where(x => Path.GetFileName(x).StartsWith(@"DLC_", StringComparison.OrdinalIgnoreCase)).ToList();

            tocTargets.Add(baseDir);
            tocTargets.Add(Path.Combine(target.TargetPath, @"BIOGame\Patches\PCConsole\Patch_001.sfar"));

            //Debug.WriteLine("Found TOC Targets:");
            //tocTargets.ForEach(x => Debug.WriteLine(x));
            //Debug.WriteLine("=====Generating TOC Files=====");
            int done = 0;

            foreach (var tocTarget in tocTargets)
            {
                string sfar = Path.Combine(tocTarget, SFAR_SUBPATH);
                if (tocTarget.EndsWith(@".sfar"))
                {
                    //TestPatch
                    var        watch     = Stopwatch.StartNew();
                    DLCPackage dlc       = new DLCPackage(tocTarget);
                    var        tocResult = dlc.UpdateTOCbin();
                    watch.Stop();
                    if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATE_NOT_NECESSARY)
                    {
                        Log.Information($@"TOC is already up to date in {tocTarget}");
                    }
                    else if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATED)
                    {
                        var elapsedMs = watch.ElapsedMilliseconds;
                        Log.Information($@"{tocTarget} - Ran SFAR TOC, took {elapsedMs}ms");
                    }
                }
                else if (ME3Directory.OfficialDLCNames.ContainsKey(Path.GetFileName(tocTarget)))
                {
                    //Official DLC
                    if (File.Exists(sfar))
                    {
                        if (new FileInfo(sfar).Length == 32) //DLC is unpacked for sure
                        {
                            CreateUnpackedTOC(tocTarget);
                        }
                        else
                        {
                            //AutoTOC it - SFAR is not unpacked
                            var watch = System.Diagnostics.Stopwatch.StartNew();

                            DLCPackage dlc       = new DLCPackage(sfar);
                            var        tocResult = dlc.UpdateTOCbin();
                            watch.Stop();
                            if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_ERROR_NO_ENTRIES)
                            {
                                Log.Information($@"No DLC entries in SFAR... Suspicious. Creating empty TOC for {tocTarget}");
                                CreateUnpackedTOC(tocTarget);
                            }
                            else if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATE_NOT_NECESSARY)
                            {
                                Log.Information($@"TOC is already up to date in {tocTarget}");
                            }
                            else if (tocResult == DLCPackage.DLCTOCUpdateResult.RESULT_UPDATED)
                            {
                                var elapsedMs = watch.ElapsedMilliseconds;
                                Log.Information($@"{Path.GetFileName(tocTarget)} - Ran SFAR TOC, took {elapsedMs}ms");
                            }
                        }
                    }
                }
                else
                {
                    //TOC it unpacked style
                    // Console.WriteLine(foldername + ", - UNPACKED TOC");
                    CreateUnpackedTOC(tocTarget);
                }

                done++;
                percentDoneCallback?.Invoke((int)Math.Floor(done * 100.0 / tocTargets.Count));
            }
            return(true);
        }