private bool InternalInitialize() { try { _symbols = StandardLibrary.GetSymbolTable(); var files = EntryImporter.GetPossibleAssociatedFiles(Pcc); var gameFiles = MELoadedFiles.GetFilesLoadedInGame(Pcc.Game); foreach (var fileName in Enumerable.Reverse(files)) { if (gameFiles.TryGetValue(fileName, out string path) && File.Exists(path)) { using var pcc = MEPackageHandler.OpenMEPackage(path); if (!StandardLibrary.ResolveAllClassesInPackage(pcc, ref _symbols)) { return(false); } } } return(StandardLibrary.ResolveAllClassesInPackage(Pcc, ref _symbols)); } catch (Exception e) { return(false); } }
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.AddedItems.Count == 1 && e.AddedItems[0] is LoggerInfo info) { foreach ((string fileName, string filePath) in MELoadedFiles.GetFilesLoadedInGame(Game)) { if (Path.GetFileNameWithoutExtension(fileName.ToLower()) == info.packageName) { using (var package = MEPackageHandler.OpenMEPackage(filePath)) { foreach (ExportEntry exp in package.Exports) { if (exp.ClassName == info.className && exp.ObjectName == info.objectName && exp.ParentName == info.sequenceName) { ExportFound(filePath, exp.UIndex); return; } } } } } MessageBox.Show("Could not find matching export!"); } }
public static void InstallDLC_MOD_Interop() { if (Directory.Exists(ModInstallPath)) { FileSystemHelper.DeleteFilesAndFoldersRecursively(ModInstallPath); } Dictionary <string, string> fileMap = MELoadedFiles.GetFilesLoadedInGame(MEGame.ME3, true); string sourcePath = Path.Combine(App.ExecFolder, modName); FileSystem.CopyDirectory(sourcePath, ModInstallPath); PadCamPathFile(); InteropHelper.InstallInteropASI(); string consoleExtASIWritePath = Path.Combine(ME3Directory.ExecutableFolder, "asi", consoleExtASIName); if (File.Exists(consoleExtASIWritePath)) { File.Delete(consoleExtASIWritePath); } File.Copy(Path.Combine(App.ExecFolder, consoleExtASIName), consoleExtASIWritePath); const string bioPGlobalFileName = "BioP_Global.pcc"; const string bioPGlobalNCFileName = "BioP_Global_NC.pcc"; var sourceFiles = new List <(string filePath, string md5)> { AugmentAndInstall(bioPGlobalFileName), AugmentAndInstall(bioPGlobalNCFileName) }; File.WriteAllText(InstallInfoPath, JsonConvert.SerializeObject(new InstallInfo { InstallTime = DateTime.Now, Version = CURRENT_VERSION, SourceFiles = sourceFiles })); AutoTOCWPF.GenerateAllTOCs(); (string, string) AugmentAndInstall(string fileName) { string existingFile = fileMap[fileName]; using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(existingFile)) { AugmentMapToLoadLiveEditor(pcc); pcc.Save(Path.Combine(ModInstallPath, "CookedPCConsole", fileName)); } return(existingFile, InteropHelper.CalculateMD5(existingFile)); } }
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 static void LoadMusicList() { var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(MERFileSystem.Game, true, includeAFCs: true); string fileContents = MERUtilities.GetEmbeddedStaticFilesTextFile("musiclistme2.json"); var musicList = JsonConvert.DeserializeObject <List <MusicStreamInfo> >(fileContents); AvailableMusicPool = new(); foreach (var mus in musicList) { // Check it has AFC available if (loadedFiles.TryGetValue(mus.Filename + ".afc", out _)) { AvailableMusicPool.Add(mus); } else { Debug.WriteLine($"Music not available, not adding to pool: {mus.Filename}"); } } AvailableMusicPool.Shuffle(); StaticMusicPool = AvailableMusicPool.ToList(); }
public string GetPathToAFC() { //Check if pcc-stored if (IsPCCStored) { return(null); //it's pcc stored. we will return null for this case since we already coded for "". } //Look in currect directory first string path = Path.Combine(Path.GetDirectoryName(Export.FileRef.FilePath), Filename + ".afc"); if (File.Exists(path)) { return(path); //in current directory of this pcc file } var gameFiles = MELoadedFiles.GetFilesLoadedInGame(Export.FileRef.Game, includeAFCs: true); gameFiles.TryGetValue(Filename + ".afc", out string afcPath); return(afcPath ?? ""); }
// Todo: ME3Exp 5.1: Get rid of this and use the import resolver. It must support a cache so we don't constnatly open packages internal static ExportEntry FindExternalAsset(ImportEntry entry, List <ExportEntry> alreadyLoadedPackageEntries, List <IMEPackage> openedPackages) { //Debug.WriteLine("Finding external asset " + entry.GetFullPath); if (entry.Game == MEGame.ME1) { var sourcePackageInternalPath = entry.FullPath.Substring(entry.FullPath.IndexOf('.') + 1); string baseName = entry.FileRef.FollowLink(entry.idxLink).Split('.')[0].ToUpper() + ".upk"; //Get package filename var preloadedPackageEntry = alreadyLoadedPackageEntries?.FirstOrDefault(x => Path.GetFileName(x.FileRef.FilePath).Equals(baseName, StringComparison.InvariantCultureIgnoreCase)); if (preloadedPackageEntry == null && MELoadedFiles.GetFilesLoadedInGame(MEGame.ME1).TryGetValue(baseName, out string packagePath)) { var package = MEPackageHandler.OpenMEPackage(packagePath); if (openedPackages != null && !openedPackages.Contains(package)) { openedPackages.Add(package); } var foundExp = package.Exports.FirstOrDefault(exp => exp.FullPath == sourcePackageInternalPath && exp.ClassName == entry.ClassName); if (foundExp != null) { return(foundExp); } if (openedPackages == null) { package.Dispose(); } } else { Debug.WriteLine("ME1 External Asset lookup: Using existing preloaded export package"); var foundExp = preloadedPackageEntry.FileRef.Exports.FirstOrDefault(exp => exp.FullPath == sourcePackageInternalPath && exp.ClassName == entry.ClassName); if (foundExp != null) { return(foundExp); } } } else { // Next, split the filename by underscores string filenameWithoutExtension = Path.GetFileNameWithoutExtension(entry.FileRef.FilePath).ToLower(); string containingDirectory = Path.GetDirectoryName(entry.FileRef.FilePath); var packagesToCheck = new List <string>(); var gameFiles = MELoadedFiles.GetFilesLoadedInGame(entry.Game); if (filenameWithoutExtension.StartsWith("bioa_") || filenameWithoutExtension.StartsWith("biod_")) { string[] parts = filenameWithoutExtension.Split('_'); if (parts.Length >= 2) //BioA_Nor_WowThatsAlot310.pcc { string bioad = $"{parts[0]}_{parts[1]}.pcc"; string filename = Path.Combine(containingDirectory, bioad); //BioA_Nor.pcc if (File.Exists(filename)) { packagesToCheck.Add(filename); } else { if (gameFiles.TryGetValue(filename, out string inGamePath)) { packagesToCheck.Add(inGamePath); } } string biop = $"BioP_{parts[1]}.pcc"; filename = Path.Combine(containingDirectory, biop); //BioP_Nor.pcc if (File.Exists(filename)) { packagesToCheck.Add(filename); } else { if (gameFiles.TryGetValue(filename, out string inGamePath)) { packagesToCheck.Add(inGamePath); } } } } // Add globals packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "SFXGame.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "EntryMenu.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), entry.Game == MEGame.ME3 ? "Startup.pcc" : "Startup_INT.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "Engine.pcc")); packagesToCheck.Add(Path.Combine(MEDirectories.GetCookedPath(entry.Game), "Engine.u")); //ME1 foreach (string packagePath in packagesToCheck) { if (File.Exists(packagePath)) { var preloadedPackageEntry = alreadyLoadedPackageEntries?.FirstOrDefault(x => Path.GetFileName(x.FileRef.FilePath).Equals(packagePath, StringComparison.InvariantCultureIgnoreCase)); if (preloadedPackageEntry == null) { var sentry = searchPackageForEntry(packagePath, entry.FullPath, entry.ClassName, openedPackages); if (sentry != null) { return(sentry); } } else { Debug.WriteLine("ME2/3 External Asset lookup: Using existing preloaded export package"); var foundExp = preloadedPackageEntry.FileRef.Exports.FirstOrDefault(exp => exp.FullPath == entry.FullPath && exp.ClassName == entry.ClassName); if (foundExp != null) { return(foundExp); } } } } } Debug.WriteLine("Could not find external asset: " + entry.FullPath); return(null); }
public static string Replace(this Texture2D t2d, Image image, PropertyCollection props, string fileSourcePath = null, string forcedTFCName = null) { string errors = ""; var textureCache = forcedTFCName ?? t2d.GetTopMip().TextureCacheName; string fmt = t2d.TextureFormat; PixelFormat pixelFormat = Image.getPixelFormatType(fmt); t2d.RemoveEmptyMipsFromMipList(); // Not sure what this does? // Remove all but one mip? //if (Export.Game == MEGame.ME1 && texture.mipMapsList.Count < 6) //{ // for (int i = texture.mipMapsList.Count - 1; i != 0; i--) // texture.mipMapsList.RemoveAt(i); //} PixelFormat newPixelFormat = pixelFormat; //Changing Texture Type. Not sure what this is, exactly. //if (mod.markConvert) // newPixelFormat = changeTextureType(pixelFormat, image.pixelFormat, ref package, ref texture); if (!image.checkDDSHaveAllMipmaps() || t2d.Mips.Count > 1 && image.mipMaps.Count() <= 1 || image.pixelFormat != newPixelFormat) //(!mod.markConvert && image.pixelFormat != pixelFormat)) { bool dxt1HasAlpha = false; byte dxt1Threshold = 128; if (pixelFormat == PixelFormat.DXT1 && props.GetProp <EnumProperty>("CompressionSettings") is EnumProperty compressionSettings && compressionSettings.Value.Name == "TC_OneBitAlpha") { dxt1HasAlpha = true; if (image.pixelFormat == PixelFormat.ARGB || image.pixelFormat == PixelFormat.DXT3 || image.pixelFormat == PixelFormat.DXT5) { errors += "Warning: Texture was converted from full alpha to binary alpha." + Environment.NewLine; } } //Generate lower mips image.correctMips(newPixelFormat, dxt1HasAlpha, dxt1Threshold); } if (t2d.Mips.Count == 1) { var topMip = image.mipMaps[0]; image.mipMaps.Clear(); image.mipMaps.Add(topMip); } else { // remove lower mipmaps from source image which not exist in game data //Not sure what this does since we just generated most of these mips for (int t = 0; t < image.mipMaps.Count(); t++) { if (image.mipMaps[t].origWidth <= t2d.Mips[0].width && image.mipMaps[t].origHeight <= t2d.Mips[0].height && t2d.Mips.Count > 1) { if (!t2d.Mips.Exists(m => m.width == image.mipMaps[t].origWidth && m.height == image.mipMaps[t].origHeight)) { image.mipMaps.RemoveAt(t--); } } } // put empty mips if missing for (int t = 0; t < t2d.Mips.Count; t++) { if (t2d.Mips[t].width <= image.mipMaps[0].origWidth && t2d.Mips[t].height <= image.mipMaps[0].origHeight) { if (!image.mipMaps.Exists(m => m.origWidth == t2d.Mips[t].width && m.origHeight == t2d.Mips[t].height)) { MipMap mipmap = new MipMap(t2d.Mips[t].width, t2d.Mips[t].height, pixelFormat); image.mipMaps.Add(mipmap); } } } } //if (!texture.properties.exists("LODGroup")) // texture.properties.setByteValue("LODGroup", "TEXTUREGROUP_Character", "TextureGroup", 1025); List <byte[]> compressedMips = new List <byte[]>(); for (int m = 0; m < image.mipMaps.Count(); m++) { if (t2d.Export.Game == MEGame.ME2) { compressedMips.Add(TextureCompression.CompressTexture(image.mipMaps[m].data, StorageTypes.extLZO)); //LZO } else { compressedMips.Add(TextureCompression.CompressTexture(image.mipMaps[m].data, StorageTypes.extZlib)); //ZLib } } List <Texture2DMipInfo> mipmaps = new List <Texture2DMipInfo>(); for (int m = 0; m < image.mipMaps.Count(); m++) { Texture2DMipInfo mipmap = new Texture2DMipInfo(); mipmap.Export = t2d.Export; mipmap.width = image.mipMaps[m].origWidth; mipmap.height = image.mipMaps[m].origHeight; mipmap.TextureCacheName = textureCache; if (t2d.Mips.Exists(x => x.width == mipmap.width && x.height == mipmap.height)) { var oldMip = t2d.Mips.First(x => x.width == mipmap.width && x.height == mipmap.height); mipmap.storageType = oldMip.storageType; } else { mipmap.storageType = t2d.Mips[0].storageType; if (t2d.Mips.Count() > 1) { //Will implement later. ME3Explorer won't support global relinking, that's MEM's job. //if (Export.Game == MEGame.ME1 && matched.linkToMaster == -1) //{ // if (mipmap.storageType == StorageTypes.pccUnc) // { // mipmap.storageType = StorageTypes.pccLZO; // } //} //else if (Export.Game == MEGame.ME1 && matched.linkToMaster != -1) //{ // if (mipmap.storageType == StorageTypes.pccUnc || // mipmap.storageType == StorageTypes.pccLZO || // mipmap.storageType == StorageTypes.pccZlib) // { // mipmap.storageType = StorageTypes.extLZO; // } //} //else } } //ME2,ME3: Force compression type (not implemented yet) if (t2d.Export.Game == MEGame.ME3) { if (mipmap.storageType == StorageTypes.extLZO) //ME3 LZO -> ZLIB { mipmap.storageType = StorageTypes.extZlib; } if (mipmap.storageType == StorageTypes.pccLZO) //ME3 PCC LZO -> PCCZLIB { mipmap.storageType = StorageTypes.pccZlib; } if (mipmap.storageType == StorageTypes.extUnc) //ME3 Uncomp -> ZLib { mipmap.storageType = StorageTypes.extZlib; } //Leave here for future. WE might need this after dealing with double compression //if (mipmap.storageType == StorageTypes.pccUnc && mipmap.width > 32) //ME3 Uncomp -> ZLib // mipmap.storageType = StorageTypes.pccZlib; if (mipmap.storageType == StorageTypes.pccUnc && m < image.mipMaps.Count() - 6 && textureCache != null) //Moving texture to store externally. { mipmap.storageType = StorageTypes.extZlib; } } else if (t2d.Export.Game == MEGame.ME2) { if (mipmap.storageType == StorageTypes.extZlib) //ME2 ZLib -> LZO { mipmap.storageType = StorageTypes.extLZO; } if (mipmap.storageType == StorageTypes.pccZlib) //M2 ZLib -> LZO { mipmap.storageType = StorageTypes.pccLZO; } if (mipmap.storageType == StorageTypes.extUnc) //ME2 Uncomp -> LZO { mipmap.storageType = StorageTypes.extLZO; } //Leave here for future. We might neable this after dealing with double compression //if (mipmap.storageType == StorageTypes.pccUnc && mipmap.width > 32) //ME2 Uncomp -> LZO // mipmap.storageType = StorageTypes.pccLZO; if (mipmap.storageType == StorageTypes.pccUnc && m < image.mipMaps.Count() - 6 && textureCache != null) //Moving texture to store externally. make sure bottom 6 are pcc stored { mipmap.storageType = StorageTypes.extLZO; } } //Investigate. this has something to do with archive storage types //if (mod.arcTexture != null) //{ // if (mod.arcTexture[m].storageType != mipmap.storageType) // { // mod.arcTexture = null; // } //} mipmap.width = image.mipMaps[m].width; mipmap.height = image.mipMaps[m].height; mipmaps.Add(mipmap); if (t2d.Mips.Count() == 1) { break; } } #region MEM code comments. Should probably leave for reference //if (texture.properties.exists("TextureFileCacheName")) //{ // string archive = texture.properties.getProperty("TextureFileCacheName").valueName; // if (mod.arcTfcDLC && mod.arcTfcName != archive) // mod.arcTexture = null; // if (mod.arcTexture == null) // { // archiveFile = Path.Combine(GameData.MainData, archive + ".tfc"); // if (matched.path.ToLowerInvariant().Contains("\\dlc")) // { // mod.arcTfcDLC = true; // string DLCArchiveFile = Path.Combine(Path.GetDirectoryName(GameData.GamePath + matched.path), archive + ".tfc"); // if (File.Exists(DLCArchiveFile)) // archiveFile = DLCArchiveFile; // else if (!File.Exists(archiveFile)) // { // List<string> files = Directory.GetFiles(GameData.bioGamePath, archive + ".tfc", // SearchOption.AllDirectories).Where(item => item.EndsWith(".tfc", StringComparison.OrdinalIgnoreCase)).ToList(); // if (files.Count == 1) // archiveFile = files[0]; // else if (files.Count == 0) // { // using (FileStream fs = new FileStream(DLCArchiveFile, FileMode.CreateNew, FileAccess.Write)) // { // fs.WriteFromBuffer(texture.properties.getProperty("TFCFileGuid").valueStruct); // } // archiveFile = DLCArchiveFile; // newTfcFile = true; // } // else // throw new Exception("More instnces of TFC file: " + archive + ".tfc"); // } // } // else // { // mod.arcTfcDLC = false; // } // // check if texture fit in old space // for (int mip = 0; mip < image.mipMaps.Count(); mip++) // { // Texture.MipMap testMipmap = new Texture.MipMap(); // testMipmap.width = image.mipMaps[mip].origWidth; // testMipmap.height = image.mipMaps[mip].origHeight; // if (ExistMipmap(testMipmap.width, testMipmap.height)) // testMipmap.storageType = texture.getMipmap(testMipmap.width, testMipmap.height).storageType; // else // { // oldSpace = false; // break; // } // if (testMipmap.storageType == StorageTypes.extZlib || // testMipmap.storageType == StorageTypes.extLZO) // { // Texture.MipMap oldTestMipmap = texture.getMipmap(testMipmap.width, testMipmap.height); // if (mod.cacheCprMipmaps[mip].Length > oldTestMipmap.compressedSize) // { // oldSpace = false; // break; // } // } // if (texture.mipMapsList.Count() == 1) // break; // } // long fileLength = new FileInfo(archiveFile).Length; // if (!oldSpace && fileLength + 0x5000000 > 0x80000000) // { // archiveFile = ""; // foreach (TFCTexture newGuid in guids) // { // archiveFile = Path.Combine(GameData.MainData, newGuid.name + ".tfc"); // if (!File.Exists(archiveFile)) // { // texture.properties.setNameValue("TextureFileCacheName", newGuid.name); // texture.properties.setStructValue("TFCFileGuid", "Guid", newGuid.guid); // using (FileStream fs = new FileStream(archiveFile, FileMode.CreateNew, FileAccess.Write)) // { // fs.WriteFromBuffer(newGuid.guid); // } // newTfcFile = true; // break; // } // else // { // fileLength = new FileInfo(archiveFile).Length; // if (fileLength + 0x5000000 < 0x80000000) // { // texture.properties.setNameValue("TextureFileCacheName", newGuid.name); // texture.properties.setStructValue("TFCFileGuid", "Guid", newGuid.guid); // break; // } // } // archiveFile = ""; // } // if (archiveFile == "") // throw new Exception("No free TFC texture file!"); // } // } // else // { // texture.properties.setNameValue("TextureFileCacheName", mod.arcTfcName); // texture.properties.setStructValue("TFCFileGuid", "Guid", mod.arcTfcGuid); // } //} #endregion int allextmipssize = 0; for (int m = 0; m < image.mipMaps.Count(); m++) { Texture2DMipInfo x = mipmaps[m]; var compsize = image.mipMaps[m].data.Length; if (x.storageType == StorageTypes.extZlib || x.storageType == StorageTypes.extLZO || x.storageType == StorageTypes.extUnc) { allextmipssize += compsize; //compsize on Unc textures is same as LZO/ZLib } } //todo: check to make sure TFC will not be larger than 2GiB Guid tfcGuid = Guid.NewGuid(); //make new guid as storage bool locallyStored = mipmaps[0].storageType == StorageTypes.pccUnc || mipmaps[0].storageType == StorageTypes.pccZlib || mipmaps[0].storageType == StorageTypes.pccLZO; for (int m = 0; m < image.mipMaps.Count(); m++) { Texture2DMipInfo mipmap = mipmaps[m]; //if (mipmap.width > 32) //{ // if (mipmap.storageType == StorageTypes.pccUnc) // { // mipmap.storageType = Export.Game == MEGame.ME2 ? StorageTypes.pccLZO : StorageTypes.pccZlib; // } // if (mipmap.storageType == StorageTypes.extUnc) // { // mipmap.storageType = Export.Game == MEGame.ME2 ? StorageTypes.extLZO : StorageTypes.extZlib; // } //} mipmap.uncompressedSize = image.mipMaps[m].data.Length; if (t2d.Export.Game == MEGame.ME1) { if (mipmap.storageType == StorageTypes.pccLZO || mipmap.storageType == StorageTypes.pccZlib) { mipmap.Mip = compressedMips[m]; mipmap.compressedSize = mipmap.Mip.Length; } else if (mipmap.storageType == StorageTypes.pccUnc) { mipmap.compressedSize = mipmap.uncompressedSize; mipmap.Mip = image.mipMaps[m].data; } else { throw new Exception("Unknown mip storage type!"); } } else { if (mipmap.storageType == StorageTypes.extZlib || mipmap.storageType == StorageTypes.extLZO) { if (compressedMips.Count != image.mipMaps.Count()) { throw new Exception("Amount of compressed mips does not match number of mips of incoming image!"); } mipmap.Mip = compressedMips[m]; mipmap.compressedSize = mipmap.Mip.Length; } if (mipmap.storageType == StorageTypes.pccUnc || mipmap.storageType == StorageTypes.extUnc) { mipmap.compressedSize = mipmap.uncompressedSize; mipmap.Mip = image.mipMaps[m].data; } if (mipmap.storageType == StorageTypes.pccLZO || mipmap.storageType == StorageTypes.pccZlib) { mipmap.Mip = compressedMips[m]; mipmap.compressedSize = mipmap.Mip.Length; } if (mipmap.storageType == StorageTypes.extZlib || mipmap.storageType == StorageTypes.extLZO || mipmap.storageType == StorageTypes.extUnc) { if (!string.IsNullOrEmpty(mipmap.TextureCacheName) && mipmap.Export.Game != MEGame.ME1) { //Check local dir string tfcarchive = mipmap.TextureCacheName + ".tfc"; var localDirectoryTFCPath = Path.Combine(Path.GetDirectoryName(mipmap.Export.FileRef.FilePath), tfcarchive); if (File.Exists(localDirectoryTFCPath)) { try { using (FileStream fs = new FileStream(localDirectoryTFCPath, FileMode.Open, FileAccess.ReadWrite)) { tfcGuid = fs.ReadGuid(); fs.Seek(0, SeekOrigin.End); mipmap.externalOffset = (int)fs.Position; fs.Write(mipmap.Mip, 0, mipmap.compressedSize); } } catch (Exception e) { throw new Exception("Problem appending to TFC file " + tfcarchive + ": " + e.Message); } continue; } //Check game var gameFiles = MELoadedFiles.GetFilesLoadedInGame(mipmap.Export.Game, includeTFCs: true); if (gameFiles.TryGetValue(tfcarchive, out string archiveFile)) { try { using (FileStream fs = new FileStream(archiveFile, FileMode.Open, FileAccess.ReadWrite)) { tfcGuid = fs.ReadGuid(); fs.Seek(0, SeekOrigin.End); mipmap.externalOffset = (int)fs.Position; fs.Write(mipmap.Mip, 0, mipmap.compressedSize); } } catch (Exception e) { throw new Exception("Problem appending to TFC file " + archiveFile + ": " + e.Message); } continue; } //Cache not found. Make new TFC try { using (FileStream fs = new FileStream(localDirectoryTFCPath, FileMode.OpenOrCreate, FileAccess.Write)) { fs.WriteGuid(tfcGuid); mipmap.externalOffset = (int)fs.Position; fs.Write(mipmap.Mip, 0, mipmap.compressedSize); } } catch (Exception e) { throw new Exception("Problem creating new TFC file " + tfcarchive + ": " + e.Message); } continue; } } } mipmaps[m] = mipmap; if (t2d.Mips.Count() == 1) { break; } } t2d.ReplaceMips(mipmaps); //Set properties // The bottom 6 mips are apparently always pcc stored. If there is less than 6 mips, set neverstream to true, which tells game // and toolset to never look into archives for mips. //if (Export.Game == MEGame.ME2 || Export.Game == MEGame.ME3) //{ // if (texture.properties.exists("TextureFileCacheName")) // { // if (texture.mipMapsList.Count < 6) // { // mipmap.storageType = StorageTypes.pccUnc; // texture.properties.setBoolValue("NeverStream", true); // } // else // { // if (Export.Game == MEGame.ME2) // mipmap.storageType = StorageTypes.extLZO; // else // mipmap.storageType = StorageTypes.extZlib; // } // } //} var hasNeverStream = props.GetProp <BoolProperty>("NeverStream") != null; if (locallyStored) { // Rules for default neverstream // 1. Must be Package Stored // 2. Must have at least 6 not empty mips if (mipmaps.Count >= 6) { props.AddOrReplaceProp(new BoolProperty(true, "NeverStream")); } // Side case: NeverStream property was already set, we should respect the value // But won't that always be set here? // Is there a time where we should remove NeverStream? I can't see any logical way // neverstream would be here } if (mipmaps.Count < 6) { props.RemoveNamedProperty("NeverStream"); } if (!locallyStored) { props.AddOrReplaceProp(tfcGuid.ToGuidStructProp("TFCFileGuid")); if (mipmaps[0].storageType == StorageTypes.extLZO || mipmaps[0].storageType == StorageTypes.extUnc || mipmaps[0].storageType == StorageTypes.extZlib) { //Requires texture cache name props.AddOrReplaceProp(new NameProperty(textureCache, "TextureFileCacheName")); } else { //Should not have texture cache name var cacheProp = props.GetProp <NameProperty>("TextureFileCacheName"); if (cacheProp != null) { props.Remove(cacheProp); } } } else { props.RemoveNamedProperty("TFCFileGuid"); } props.AddOrReplaceProp(new IntProperty(t2d.Mips.First().width, "SizeX")); props.AddOrReplaceProp(new IntProperty(t2d.Mips.First().height, "SizeY")); if (t2d.Export.Game < MEGame.ME3 && fileSourcePath != null) { props.AddOrReplaceProp(new StrProperty(fileSourcePath, "SourceFilePath")); props.AddOrReplaceProp(new StrProperty(File.GetLastWriteTimeUtc(fileSourcePath).ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), "SourceFileTimestamp")); } var mipTailIdx = props.GetProp <IntProperty>("MipTailBaseIdx"); if (mipTailIdx != null) { mipTailIdx.Value = t2d.Mips.Count - 1; } EndianReader mem = new EndianReader(new MemoryStream()) { Endian = t2d.Export.FileRef.Endian }; props.WriteTo(mem.Writer, t2d.Export.FileRef); mem.Position = 0; var test = PropertyCollection.ReadProps(t2d.Export, mem.BaseStream, "Texture2D", true, true); //do not set properties as this may interfere with some other code. may change later. int propStart = t2d.Export.GetPropertyStart(); var pos = mem.Position; mem.Position = 0; byte[] propData = mem.ToArray(); if (t2d.Export.Game == MEGame.ME3) { t2d.Export.Data = t2d.Export.Data.Take(propStart).Concat(propData).Concat(t2d.SerializeNewData()).ToArray(); } else { var array = t2d.Export.Data.Take(propStart).Concat(propData).ToArray(); var testdata = new MemoryStream(array); var test2 = PropertyCollection.ReadProps(t2d.Export, testdata, "Texture2D", true, true, t2d.Export); //do not set properties as this may interfere with some other code. may change later. //ME2 post-data is this right? t2d.Export.Data = t2d.Export.Data.Take(propStart).Concat(propData).Concat(t2d.SerializeNewData()).ToArray(); } //using (MemoryStream newData = new MemoryStream()) //{ // newData.WriteFromBuffer(texture.properties.toArray()); // newData.WriteFromBuffer(texture.toArray(0, false)); // filled later // package.setExportData(matched.exportID, newData.ToArray()); //} //using (MemoryStream newData = new MemoryStream()) //{ // newData.WriteFromBuffer(texture.properties.toArray()); // newData.WriteFromBuffer(texture.toArray(package.exportsTable[matched.exportID].dataOffset + (uint)newData.Position)); // package.setExportData(matched.exportID, newData.ToArray()); //} //Since this is single replacement, we don't want to relink to master //We want to ensure names are different though, will have to implement into UI //if (Export.Game == MEGame.ME1) //{ // if (matched.linkToMaster == -1) // mod.masterTextures.Add(texture.mipMapsList, entryMap.listIndex); //} //else //{ // if (triggerCacheArc) // { // mod.arcTexture = texture.mipMapsList; // mod.arcTfcGuid = texture.properties.getProperty("TFCFileGuid").valueStruct; // mod.arcTfcName = texture.properties.getProperty("TextureFileCacheName").valueName; // } //} return(errors); }
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); } } }
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); }