public static void ConvertTo(this MEPackage package, MEGame newGame, string tfcPath = null, bool preserveMaterialInstances = false) { MEGame oldGame = package.Game; var prePropBinary = new List <byte[]>(package.ExportCount); var propCollections = new List <PropertyCollection>(package.ExportCount); var postPropBinary = new List <ObjectBinary>(package.ExportCount); if (oldGame == MEGame.ME1 && newGame != MEGame.ME1) { int idx = package.Names.IndexOf("BIOC_Base"); if (idx >= 0) { package.replaceName(idx, "SFXGame"); } } else if (newGame == MEGame.ME1) { int idx = package.Names.IndexOf("SFXGame"); if (idx >= 0) { package.replaceName(idx, "BIOC_Base"); } } //fix up Default_ package.Imports if (newGame == MEGame.ME3) { using IMEPackage core = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "Core.pcc")); using IMEPackage engine = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "Engine.pcc")); using IMEPackage sfxGame = MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "SFXGame.pcc")); foreach (ImportEntry defImp in package.Imports.Where(imp => imp.ObjectName.Name.StartsWith("Default_")).ToList()) { string packageName = defImp.FullPath.Split('.')[0]; IMEPackage pck = packageName switch { "Core" => core, "Engine" => engine, "SFXGame" => sfxGame, _ => null }; if (pck != null && pck.Exports.FirstOrDefault(exp => exp.ObjectName == defImp.ObjectName) is ExportEntry defExp) { List <IEntry> impChildren = defImp.GetChildren(); List <IEntry> expChildren = defExp.GetChildren(); foreach (IEntry expChild in expChildren) { if (impChildren.FirstOrDefault(imp => imp.ObjectName == expChild.ObjectName) is ImportEntry matchingImp) { impChildren.Remove(matchingImp); } else { package.AddImport(new ImportEntry(package) { idxLink = defImp.UIndex, ClassName = expChild.ClassName, ObjectName = expChild.ObjectName, PackageFile = defImp.PackageFile }); } } foreach (IEntry impChild in impChildren) { EntryPruner.TrashEntries(package, impChild.GetAllDescendants().Prepend(impChild)); } } } } //purge MaterialExpressions if (newGame == MEGame.ME3) { var entriesToTrash = new List <IEntry>(); foreach (ExportEntry mat in package.Exports.Where(exp => exp.ClassName == "Material").ToList()) { entriesToTrash.AddRange(mat.GetAllDescendants()); } EntryPruner.TrashEntries(package, entriesToTrash.ToHashSet()); } EntryPruner.TrashIncompatibleEntries(package, oldGame, newGame); foreach (ExportEntry export in package.Exports) { //convert stack, or just get the pre-prop binary if no stack prePropBinary.Add(ExportBinaryConverter.ConvertPrePropBinary(export, newGame)); PropertyCollection props = export.ClassName == "Class" ? null : EntryPruner.RemoveIncompatibleProperties(package, export.GetProperties(), export.ClassName, newGame); propCollections.Add(props); //convert binary data postPropBinary.Add(ExportBinaryConverter.ConvertPostPropBinary(export, newGame, props)); //writes header in whatever format is correct for newGame export.RegenerateHeader(newGame, true); } package.setGame(newGame); for (int i = 0; i < package.Exports.Count; i++) { package.Exports[i].WritePrePropsAndPropertiesAndBinary(prePropBinary[i], propCollections[i], postPropBinary[i]); } if (newGame != MEGame.ME3) //Fix Up Textures before Materials { foreach (ExportEntry texport in package.Exports.Where(exp => exp.IsTexture())) { texport.WriteProperty(new BoolProperty(true, "NeverStream")); } } else if (package.Exports.Any(exp => exp.IsTexture() && Texture2D.GetTexture2DMipInfos(exp, null) .Any(mip => mip.storageType == StorageTypes.pccLZO || mip.storageType == StorageTypes.pccZlib))) { //ME3 can't deal with compressed textures in a pcc, so we'll need to stuff them into a tfc tfcPath ??= Path.ChangeExtension(package.FilePath, "tfc"); string tfcName = Path.GetFileNameWithoutExtension(tfcPath); using var tfc = new FileStream(tfcPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); Guid tfcGuid; if (tfc.Length >= 16) { tfcGuid = tfc.ReadGuid(); tfc.SeekEnd(); } else { tfcGuid = Guid.NewGuid(); tfc.WriteGuid(tfcGuid); } foreach (ExportEntry texport in package.Exports.Where(exp => exp.IsTexture())) { List <Texture2DMipInfo> mips = Texture2D.GetTexture2DMipInfos(texport, null); var offsets = new List <int>(); foreach (Texture2DMipInfo mipInfo in mips) { if (mipInfo.storageType == StorageTypes.pccLZO || mipInfo.storageType == StorageTypes.pccZlib) { offsets.Add((int)tfc.Position); byte[] mip = mipInfo.storageType == StorageTypes.pccLZO ? TextureCompression.CompressTexture(Texture2D.GetTextureData(mipInfo, texport.Game), StorageTypes.extZlib) : Texture2D.GetTextureData(mipInfo, texport.Game, decompress: false); tfc.WriteFromBuffer(mip); } } offsets.Add((int)tfc.Position); texport.WriteBinary(ExportBinaryConverter.ConvertTexture2D(texport, package.Game, offsets, StorageTypes.extZlib)); texport.WriteProperty(new NameProperty(tfcName, "TextureFileCacheName")); texport.WriteProperty(tfcGuid.ToGuidStructProp("TFCFileGuid")); } } if (oldGame == MEGame.ME3 && newGame != MEGame.ME3) { int idx = package.Names.IndexOf("location"); if (idx >= 0) { package.replaceName(idx, "Location"); } } else if (newGame == MEGame.ME3) { int idx = package.Names.IndexOf("Location"); if (idx >= 0) { package.replaceName(idx, "location"); } } if (newGame == MEGame.ME3) //Special handling where materials have been ported between games. { //change all materials to default material, but try to preserve diff and norm textures using var resourcePCC = MEPackageHandler.OpenMEPackageFromStream(ME3ExplorerCoreUtilities.GetCustomAppResourceStream(MEGame.ME3)); var defaultmaster = resourcePCC.Exports.First(exp => exp.ObjectName == "NormDiffMaterial"); var materiallist = package.Exports.Where(exp => exp.ClassName == "Material" || exp.ClassName == "MaterialInstanceConstant").ToList(); foreach (var mat in materiallist) { Debug.WriteLine($"Fixing up {mat.FullPath}"); var masterMat = defaultmaster; var hasDefaultMaster = true; UIndex[] textures = Array.Empty <UIndex>(); if (mat.ClassName == "Material") { textures = ObjectBinary.From <Material>(mat).SM3MaterialResource.UniformExpressionTextures; switch (mat.FullPath) { case "BioT_Volumetric.LAG_MM_Volumetric": case "BioT_Volumetric.LAG_MM_FalloffSphere": case "BioT_LevelMaster.Materials.Opaque_MM": case "BioT_LevelMaster.Materials.GUI_Lit_MM": case "BioT_LevelMaster.Materials.Signage.MM_GUIMaster_Emissive": case "BioT_LevelMaster.Materials.Signage.MM_GUIMaster_Emissive_Fallback": case "BioT_LevelMaster.Materials.Opaque_Standard_MM": case "BioT_LevelMaster.Tech_Inset_MM": case "BioT_LevelMaster.Tech_Border_MM": case "BioT_LevelMaster.Brushed_Metal": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == mat.FullPath); hasDefaultMaster = false; break; default: break; } } else if (mat.GetProperty <BoolProperty>("bHasStaticPermutationResource")?.Value == true) { if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && package.GetEntry(parentProp.Value) is IEntry parent && parent.ClassName == "Material") { switch (parent.FullPath) { case "BioT_LevelMaster.Materials.Opaque_MM": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Opaque_MM_INST"); hasDefaultMaster = false; break; case "BIOG_APL_MASTER_MATERIAL.Placeable_MM": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Placeable_MM_INST"); hasDefaultMaster = false; break; case "BioT_LevelMaster.Materials.Opaque_Standard_MM": masterMat = resourcePCC.Exports.First(exp => exp.FullPath == "Materials.Opaque_Standard_MM_INST"); hasDefaultMaster = false; break; default: textures = ObjectBinary.From <MaterialInstance>(mat).SM3StaticPermutationResource.UniformExpressionTextures; break; } if (!hasDefaultMaster && mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams) { textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray(); } } } else if (preserveMaterialInstances) { continue; } else if (mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> texParams) { textures = texParams.Select(structProp => new UIndex(structProp.GetProp <ObjectProperty>("ParameterValue")?.Value ?? 0)).ToArray(); } else if (mat.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentProp && package.GetEntry(parentProp.Value) is ExportEntry parent && parent.ClassName == "Material") { textures = ObjectBinary.From <Material>(parent).SM3MaterialResource.UniformExpressionTextures; } if (hasDefaultMaster) { EntryImporter.ReplaceExportDataWithAnother(masterMat, mat); int norm = 0; int diff = 0; foreach (UIndex texture in textures) { if (package.GetEntry(texture) is IEntry tex) { if (diff == 0 && tex.ObjectName.Name.Contains("diff", StringComparison.OrdinalIgnoreCase)) { diff = texture; } else if (norm == 0 && tex.ObjectName.Name.Contains("norm", StringComparison.OrdinalIgnoreCase)) { norm = texture; } } } if (diff == 0) { diff = EntryImporter.GetOrAddCrossImportOrPackage("EngineMaterials.DefaultDiffuse", resourcePCC, package).UIndex; } var matBin = ObjectBinary.From <Material>(mat); matBin.SM3MaterialResource.UniformExpressionTextures = new UIndex[] { norm, diff }; mat.WriteBinary(matBin); mat.Class = package.Imports.First(imp => imp.ObjectName == "Material"); } else if (mat.ClassName == "Material") { var mmparent = EntryImporter.GetOrAddCrossImportOrPackage(masterMat.ParentFullPath, resourcePCC, package); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, masterMat, package, mmparent, true, out IEntry targetexp); mat.ReplaceAllReferencesToThisOne(targetexp); EntryPruner.TrashEntryAndDescendants(mat); } else if (mat.ClassName == "MaterialInstanceConstant") { try { var matprops = mat.GetProperties(); var parentlightguid = masterMat.GetProperty <StructProperty>("ParentLightingGuid"); matprops.AddOrReplaceProp(parentlightguid); var mguid = masterMat.GetProperty <StructProperty>("m_Guid"); matprops.AddOrReplaceProp(mguid); var lguid = masterMat.GetProperty <StructProperty>("LightingGuid"); matprops.AddOrReplaceProp(lguid); var masterBin = ObjectBinary.From <MaterialInstance>(masterMat); var matBin = ObjectBinary.From <MaterialInstance>(mat); var staticResTextures3 = masterBin.SM3StaticPermutationResource.UniformExpressionTextures.ToList(); var newtextures3 = new List <UIndex>(); var staticResTextures2 = masterBin.SM2StaticPermutationResource.UniformExpressionTextures.ToList(); var newtextures2 = new List <UIndex>(); IEntry norm = null; IEntry diff = null; IEntry spec = null; foreach (var texref in textures) { IEntry texEnt = package.GetEntry(texref); string texName = texEnt?.ObjectName ?? "None"; if (texName.ToLowerInvariant().Contains("norm")) { norm = texEnt; } else if (texName.ToLowerInvariant().Contains("diff")) { diff = texEnt; } else if (texName.ToLowerInvariant().Contains("spec")) { spec = texEnt; } else if (texName.ToLowerInvariant().Contains("msk")) { spec = texEnt; } } foreach (var texidx in staticResTextures2) { var masterTxt = resourcePCC.GetEntry(texidx); IEntry newTxtEnt = masterTxt; switch (masterTxt?.ObjectName.Name) { case "DefaultDiffuse": if (diff != null) { newTxtEnt = diff; } break; case "DefaultNormal": if (norm != null) { newTxtEnt = norm; } break; case "Gray": //Spec if (spec != null) { newTxtEnt = spec; } break; default: break; } var newtexidx = package.Exports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; if (newtexidx == 0) { newtexidx = package.Imports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; } if (newTxtEnt == masterTxt && newtexidx == 0) { var texparent = EntryImporter.GetOrAddCrossImportOrPackage(newTxtEnt.ParentFullPath, resourcePCC, package); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, newTxtEnt, package, texparent, true, out IEntry newtext); newtextures2.Add(newtext?.UIndex ?? 0); } else { newtextures2.Add(newtexidx); } } foreach (var texidx in staticResTextures3) { var masterTxt = resourcePCC.GetEntry(texidx); IEntry newTxtEnt = masterTxt; switch (masterTxt?.ObjectName) { case "DefaultDiffuse": if (diff != null) { newTxtEnt = diff; } break; case "DefaultNormal": if (norm != null) { newTxtEnt = norm; } break; case "Gray": //Spec if (spec != null) { newTxtEnt = spec; } break; default: break; } var newtexidx = package.Exports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; if (newtexidx == 0) { newtexidx = package.Imports.FirstOrDefault(x => x.FullPath == newTxtEnt.FullPath)?.UIndex ?? 0; } if (newTxtEnt == masterTxt && newtexidx == 0) { var texparent = EntryImporter.GetOrAddCrossImportOrPackage(newTxtEnt.ParentFullPath, resourcePCC, package); EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, newTxtEnt, package, texparent, true, out IEntry newtext); newtextures3.Add(newtext?.UIndex ?? 0); } else { newtextures3.Add(newtexidx); } } masterBin.SM2StaticPermutationResource.UniformExpressionTextures = newtextures2.ToArray(); masterBin.SM3StaticPermutationResource.UniformExpressionTextures = newtextures3.ToArray(); mat.WritePropertiesAndBinary(matprops, masterBin); } catch { Debug.WriteLine("MaterialInstanceConversion error"); } } } } } }
public static PropertyCollection GetSequenceObjectDefaults(IMEPackage pcc, ClassInfo info) { MEGame game = pcc.Game; PropertyCollection defaults = new PropertyCollection(); if (info.ClassName == "Sequence") { defaults.Add(new ArrayProperty <ObjectProperty>("SequenceObjects")); } else if (!info.IsA(SequenceVariableName, game)) { ArrayProperty <StructProperty> varLinksProp = null; ArrayProperty <StructProperty> outLinksProp = null; ArrayProperty <StructProperty> eventLinksProp = null; ArrayProperty <StructProperty> inLinksProp = null; Dictionary <string, ClassInfo> classes = UnrealObjectInfo.GetClasses(game); try { ClassInfo classInfo = info; while (classInfo != null && (varLinksProp is null || outLinksProp is null || eventLinksProp is null || game == MEGame.ME1 && inLinksProp is null)) { string filepath = Path.Combine(MEDirectories.GetBioGamePath(game), classInfo.pccPath); Stream loadStream = null; if (File.Exists(classInfo.pccPath)) { loadStream = new MemoryStream(File.ReadAllBytes(classInfo.pccPath)); } else if (classInfo.pccPath == UnrealObjectInfo.Me3ExplorerCustomNativeAdditionsName) { loadStream = ME3ExplorerCoreUtilities.GetCustomAppResourceStream(game); } else if (File.Exists(filepath)) { loadStream = new MemoryStream(File.ReadAllBytes(filepath)); } else if (game == MEGame.ME1) { filepath = Path.Combine(ME1Directory.DefaultGamePath, classInfo.pccPath); //for files from ME1 DLC if (File.Exists(filepath)) { loadStream = new MemoryStream(File.ReadAllBytes(filepath)); } } if (loadStream != null) { using IMEPackage importPCC = MEPackageHandler.OpenMEPackageFromStream(loadStream); ExportEntry classExport = importPCC.GetUExport(classInfo.exportIndex); UClass classBin = ObjectBinary.From <UClass>(classExport); ExportEntry classDefaults = importPCC.GetUExport(classBin.Defaults); foreach (var prop in classDefaults.GetProperties()) { if (varLinksProp == null && prop.Name == "VariableLinks" && prop is ArrayProperty <StructProperty> vlp) { varLinksProp = vlp; //relink ExpectedType foreach (StructProperty varLink in varLinksProp) { if (varLink.GetProp <ObjectProperty>("ExpectedType") is ObjectProperty expectedTypeProp && importPCC.TryGetEntry(expectedTypeProp.Value, out IEntry expectedVar) && EntryImporter.EnsureClassIsInFile(pcc, expectedVar.ObjectName, RelinkResultsAvailable: EntryImporterExtended.ShowRelinkResults) is IEntry portedExpectedVar) { expectedTypeProp.Value = portedExpectedVar.UIndex; } } } if (outLinksProp == null && prop.Name == "OutputLinks" && prop is ArrayProperty <StructProperty> olp) { outLinksProp = olp; } if (eventLinksProp == null && prop.Name == "EventLinks" && prop is ArrayProperty <StructProperty> elp) { eventLinksProp = elp; //relink ExpectedType foreach (StructProperty eventLink in eventLinksProp) { if (eventLink.GetProp <ObjectProperty>("ExpectedType") is ObjectProperty expectedTypeProp && importPCC.TryGetEntry(expectedTypeProp.Value, out IEntry expectedVar) && EntryImporter.EnsureClassIsInFile(pcc, expectedVar.ObjectName, RelinkResultsAvailable: EntryImporterExtended.ShowRelinkResults) is IEntry portedExpectedVar) { expectedTypeProp.Value = portedExpectedVar.UIndex; } } } // Jan 31 2021 change by Mgamerz: Not sure why it only adds input links if it's ME1 // I removed it to let other games work too //if (game == MEGame.ME1 && inLinksProp is null && prop.Name == "InputLinks" && prop is ArrayProperty<StructProperty> ilp) if (inLinksProp is null && prop.Name == "InputLinks" && prop is ArrayProperty <StructProperty> ilp) { inLinksProp = ilp; } } } classes.TryGetValue(classInfo.baseClass, out classInfo); } } catch { // ignored } if (varLinksProp != null) { defaults.Add(varLinksProp); } if (outLinksProp != null) { defaults.Add(outLinksProp); } if (eventLinksProp != null) { defaults.Add(eventLinksProp); } if (inLinksProp != null) { defaults.Add(inLinksProp); } //remove links if empty if (defaults.GetProp <ArrayProperty <StructProperty> >("OutputLinks") is { } outLinks&& outLinks.IsEmpty()) { defaults.Remove(outLinks); } if (defaults.GetProp <ArrayProperty <StructProperty> >("VariableLinks") is { } varLinks&& varLinks.IsEmpty()) { defaults.Remove(varLinks); } if (defaults.GetProp <ArrayProperty <StructProperty> >("EventLinks") is { } eventLinks&& eventLinks.IsEmpty()) { defaults.Remove(eventLinks); } if (defaults.GetProp <ArrayProperty <StructProperty> >("InputLinks") is { } inputLinks&& inputLinks.IsEmpty()) { defaults.Remove(inputLinks); } } int objInstanceVersion = UnrealObjectInfo.getSequenceObjectInfo(game, info.ClassName)?.ObjInstanceVersion ?? 1; defaults.Add(new IntProperty(objInstanceVersion, "ObjInstanceVersion")); return(defaults); }