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")); }
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 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> /// 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); } } }
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); } } }
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 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)); } }
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(); }
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); }
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); }
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); }