private static bool ForcedRun(ExportEntry hairMeshExport) { if (hairMeshExport.GetProperty <ObjectProperty>("SkeletalMesh") is ObjectProperty obj && obj.Value != 0 && obj.ResolveToEntry(hairMeshExport.FileRef) is IEntry entry) { var isfemaleHair = entry.ObjectName.Name.StartsWith("HMF_HIR_"); var newHair = isfemaleHair ? HairListFemale.RandomElement() : HairListMale.RandomElement(); if (newHair.ObjectName.Name == entry.ObjectName.Name) { return(false); // We are not changing this } MERLog.Information($"{Path.GetFileName(hairMeshExport.FileRef.FilePath)} Changing hair mesh: {entry.ObjectName} -> {newHair.ObjectName}, object {hairMeshExport.FullPath}, class {entry.ClassName}"); var newHairMdl = PackageTools.PortExportIntoPackage(hairMeshExport.FileRef, newHair); var mdlBin = ObjectBinary.From <SkeletalMesh>(newHairMdl); obj.Value = newHairMdl.UIndex; hairMeshExport.WriteProperty(obj); // Update the materials var materials = hairMeshExport.GetProperty <ArrayProperty <ObjectProperty> >("Materials"); if (materials != null && materials.Any()) { //if (materials.Count() != 1) // Debugger.Break(); var mat = materials[0].ResolveToEntry(hairMeshExport.FileRef) as ExportEntry; if (mat != null) { mat.WriteProperty(new ObjectProperty(mdlBin.Materials[0], "Parent")); var parentMat = mdlBin.Materials[0] > 0 ? hairMeshExport.FileRef.GetUExport(mdlBin.Materials[0]) : EntryImporter.ResolveImport(hairMeshExport.FileRef.GetImport(mdlBin.Materials[0])); // Need to match child to parent params that start with HAIR var parentMatTextureParms = parentMat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues"); if (parentMatTextureParms != null) { var parentMatHairParms = parentMatTextureParms.Where(x => x.Properties.GetProp <NameProperty>("ParameterName").Value.Name.StartsWith("HAIR_")).ToList(); // Need to match child to parent params that start with HAIR var matTextureParms = mat.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues"); if (matTextureParms != null) { var matHairParms = matTextureParms.Where(x => x.Properties.GetProp <NameProperty>("ParameterName").Value.Name.StartsWith("HAIR_")).ToList(); // Map them foreach (var matHairParm in matHairParms) { var locName = matHairParm.Properties.GetProp <NameProperty>("ParameterName"); var matchingParent = parentMatHairParms.FirstOrDefault(x => x.Properties.GetProp <NameProperty>("ParameterName").Value == locName.Value); // Assign it if (matchingParent != null) { matHairParm.Properties.AddOrReplaceProp(matchingParent.GetProp <ObjectProperty>("ParameterValue")); } } mat.WriteProperty(matTextureParms); } } else { } } //foreach (var mat in materials) //{ // mdlBin. //} } return(true); } return(false); }
/// <summary> /// Ports an export into a package. Checks if the export already exists, and if it does, returns that instead. /// </summary> /// <param name="targetPackage">The target package to port into.</param> /// <param name="sourceExport">The source export to port over, including all dependencies and references.</param> /// <param name="targetLink">The target link UIndex. Only used if createParentPackages is false.</param> /// <param name="createParentPackages">If the export should be ported in the same way as it was cooked into the package natively, e.g. create the parent package paths. The export must directly sit under a Package or an exception will be thrown.</param> /// <param name="ensureMemoryUniqueness">If this object is an instance, such as a sequence object, and should be made memory-unique so it is properly used</param> /// <returns></returns> public static ExportEntry PortExportIntoPackage(IMEPackage targetPackage, ExportEntry sourceExport, int targetLink = 0, bool createParentPackages = true, bool ensureMemoryUniqueness = false, bool useMemorySafeImport = false, PackageCache cache = null) { #if DEBUG // in preprocessor to prevent this from running in release mode if (sourceExport.FileRef.FilePath != null && targetPackage.FilePath != null) { Debug.WriteLine($"Porting {sourceExport.InstancedFullPath} from {Path.GetFileName(sourceExport.FileRef.FilePath)} into {Path.GetFileName(targetPackage.FilePath)}"); } #endif var existing = targetPackage.FindExport(sourceExport.InstancedFullPath); if (existing != null) { return(existing); } // Create parent hierarchy IEntry newParent = null; if (createParentPackages) { List <IEntry> parents = new List <IEntry>(); var parent = sourceExport.Parent; while (parent != null) { if (parent.ClassName != "Package") { throw new Exception("Parent is not package!"); } parents.Add(parent); parent = parent.Parent; } // Create the parents parents.Reverse(); foreach (var p in parents) { var sourceFullPath = p.InstancedFullPath; var matchingParent = targetPackage.FindEntry(sourceFullPath); if (matchingParent != null) { newParent = matchingParent; continue; } newParent = ExportCreator.CreatePackageExport(targetPackage, p.ObjectName, newParent); } } else { newParent = targetPackage.GetEntry(targetLink); } IEntry newEntry; if (!useMemorySafeImport) { Dictionary <IEntry, IEntry> crossPCCObjectMap = new Dictionary <IEntry, IEntry>(); // Not sure what this is used for these days. Should probably just be part of the method var relinkResults = EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, sourceExport, targetPackage, newParent, true, out newEntry, crossPCCObjectMap); if (relinkResults.Any()) { Debugger.Break(); } } else { // Memory safe, fixes upstream var relinkedResults = EntryExporter.ExportExportToPackage(sourceExport, targetPackage, out newEntry, MERFileSystem.GetGlobalCache(), cache); if (relinkedResults.Any()) { Debugger.Break(); } } #if DEBUG //(sourceExport.FileRef as MEPackage).CompareToPackageDetailed(targetPackage); #endif // Helps ensure we don't have memory duplicates if (ensureMemoryUniqueness) { newEntry.ObjectName = targetPackage.GetNextIndexedName(newEntry.ObjectName); } return(newEntry as ExportEntry); }
public override void SetUp() { base.SetUp(); EntryImporter.Import(EntryImporterTest.SourceDirectory, ContentDirectory); }
public static void ScanStuff(PackageEditorWPF pewpf) { //var filePaths = MELoadedFiles.GetOfficialFiles(MEGame.ME3);//.Concat(MELoadedFiles.GetOfficialFiles(MEGame.ME2));//.Concat(MELoadedFiles.GetOfficialFiles(MEGame.ME1)); //var filePaths = MELoadedFiles.GetAllFiles(game); /*"Core.pcc", "Engine.pcc", "GameFramework.pcc", "GFxUI.pcc", "WwiseAudio.pcc", "SFXOnlineFoundation.pcc", "SFXGame.pcc" */ var filePaths = new[] { "Core.pcc", "Engine.pcc", "GameFramework.pcc", "GFxUI.pcc", "WwiseAudio.pcc", "SFXOnlineFoundation.pcc" }.Select(f => Path.Combine(ME3Directory.CookedPCPath, f)); var interestingExports = new List <EntryStringPair>(); var foundClasses = new HashSet <string>(); //new HashSet<string>(BinaryInterpreterWPF.ParsableBinaryClasses); var foundProps = new Dictionary <string, string>(); var unkOpcodes = new List <int>();//Enumerable.Range(0x5B, 8).ToList(); unkOpcodes.Add(0); unkOpcodes.Add(1); var unkOpcodesInfo = unkOpcodes.ToDictionary(i => i, i => new OpcodeInfo()); var comparisonDict = new Dictionary <string, (byte[] original, byte[] newData)>(); var extraInfo = new HashSet <string>(); pewpf.IsBusy = true; pewpf.BusyText = "Scanning"; Task.Run(() => { //preload base files for faster scanning using var baseFiles = MEPackageHandler.OpenMEPackages(EntryImporter.FilesSafeToImportFrom(MEGame.ME3) .Select(f => Path.Combine(ME3Directory.CookedPCPath, f))); baseFiles.Add( MEPackageHandler.OpenMEPackage(Path.Combine(ME3Directory.CookedPCPath, "BIOP_MP_COMMON.pcc"))); foreach (string filePath in filePaths) { //ScanShaderCache(filePath); //ScanMaterials(filePath); //ScanStaticMeshComponents(filePath); //ScanLightComponents(filePath); //ScanLevel(filePath); //if (findClass(filePath, "ShaderCache", true)) break; //findClassesWithBinary(filePath); ScanScripts2(filePath); //if (interestingExports.Count > 0) //{ // break; //} //if (resolveImports(filePath)) break; } }).ContinueWithOnUIThread(prevTask => { pewpf.IsBusy = false; interestingExports.Add(new EntryStringPair(null, string.Join("\n", extraInfo))); var listDlg = new ListDialog(interestingExports, "Interesting Exports", "", pewpf) { DoubleClickEntryHandler = entryItem => { if (entryItem?.Entry is IEntry entryToSelect) { PackageEditorWPF p = new PackageEditorWPF(); p.Show(); p.LoadFile(entryToSelect.FileRef.FilePath, entryToSelect.UIndex); p.Activate(); if (comparisonDict.TryGetValue($"{entryToSelect.UIndex} {entryToSelect.FileRef.FilePath}", out (byte[] original, byte[] newData)val)) { File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "original.bin"), val.original); File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "new.bin"), val.newData); } } } }; listDlg.Show(); }); #region extra scanning functions bool findClass(string filePath, string className, bool withBinary = false) { Debug.WriteLine($" {filePath}"); using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { //if (!pcc.IsCompressed) return false; var exports = pcc.Exports.Where(exp => !exp.IsDefaultObject && exp.IsA(className)); foreach (ExportEntry exp in exports) { try { //Debug.WriteLine($"{exp.UIndex}: {filePath}"); var originalData = exp.Data; exp.WriteBinary(ObjectBinary.From(exp)); var newData = exp.Data; if (!originalData.SequenceEqual(newData)) { interestingExports.Add(exp); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "original.bin"), originalData); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "new.bin"), newData); return(true); } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(exp, $"{exception}")); return(true); } } } return(false); } void findClassesWithBinary(string filePath) { using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { foreach (ExportEntry exp in pcc.Exports.Where(exp => !exp.IsDefaultObject)) { try { if (!foundClasses.Contains(exp.ClassName) && exp.propsEnd() < exp.DataSize) { if (ObjectBinary.From(exp) != null) { foundClasses.Add(exp.ClassName); } else if (exp.GetBinaryData().Any(b => b != 0)) { foundClasses.Add(exp.ClassName); interestingExports.Add(exp); } } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(exp, $"{exp.UIndex}: {filePath}\n{exception}")); } } } } void ScanShaderCache(string filePath) { using (IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath)) { ExportEntry shaderCache = pcc.Exports.FirstOrDefault(exp => exp.ClassName == "ShaderCache"); if (shaderCache == null) { return; } int oldDataOffset = shaderCache.DataOffset; try { MemoryStream binData = new MemoryStream(shaderCache.Data); binData.JumpTo(shaderCache.propsEnd() + 1); int nameList1Count = binData.ReadInt32(); binData.Skip(nameList1Count * 12); int namelist2Count = binData.ReadInt32(); //namelist2 binData.Skip(namelist2Count * 12); int shaderCount = binData.ReadInt32(); for (int i = 0; i < shaderCount; i++) { binData.Skip(24); int nextShaderOffset = binData.ReadInt32() - oldDataOffset; binData.Skip(14); if (binData.ReadInt32() != 1111577667) //CTAB { interestingExports.Add(new EntryStringPair(null, $"{binData.Position - 4}: {filePath}")); return; } binData.JumpTo(nextShaderOffset); } int vertexFactoryMapCount = binData.ReadInt32(); binData.Skip(vertexFactoryMapCount * 12); int materialShaderMapCount = binData.ReadInt32(); for (int i = 0; i < materialShaderMapCount; i++) { binData.Skip(16); int switchParamCount = binData.ReadInt32(); binData.Skip(switchParamCount * 32); int componentMaskParamCount = binData.ReadInt32(); //if (componentMaskParamCount != 0) //{ // interestingExports.Add($"{i}: {filePath}"); // return; //} binData.Skip(componentMaskParamCount * 44); int normalParams = binData.ReadInt32(); if (normalParams != 0) { interestingExports.Add(new EntryStringPair(null, $"{i}: {filePath}")); return; } binData.Skip(normalParams * 29); int unrealVersion = binData.ReadInt32(); int licenseeVersion = binData.ReadInt32(); if (unrealVersion != 684 || licenseeVersion != 194) { interestingExports.Add(new EntryStringPair(null, $"{binData.Position - 8}: {filePath}")); return; } int nextMaterialShaderMapOffset = binData.ReadInt32() - oldDataOffset; binData.JumpTo(nextMaterialShaderMapOffset); } } catch (Exception exception) { Console.WriteLine(exception); interestingExports.Add(new EntryStringPair(null, $"{filePath}\n{exception}")); } } } void ScanScripts(string filePath) { using IMEPackage pcc = MEPackageHandler.OpenMEPackage(filePath); foreach (ExportEntry exp in pcc.Exports.Where(exp => !exp.IsDefaultObject)) { try { if ((exp.ClassName == "State" || exp.ClassName == "Function") && ObjectBinary.From(exp) is UStruct uStruct) { byte[] data = exp.Data; (_, List <BytecodeSingularToken> tokens) = Bytecode.ParseBytecode(uStruct.ScriptBytes, exp); foreach (var token in tokens) { if (token.CurrentStack.Contains("UNKNOWN") || token.OpCodeString.Contains("UNKNOWN")) { interestingExports.Add(exp); } if (unkOpcodes.Contains(token.OpCode)) { int refUIndex = EndianReader.ToInt32(data, token.StartPos + 1, pcc.Endian); IEntry entry = pcc.GetEntry(refUIndex); if (entry != null && (entry.ClassName == "ByteProperty")) { var info = unkOpcodesInfo[token.OpCode]; info.Usages.Add(pcc.FilePath, exp.UIndex, token.StartPos); info.PropTypes.Add(refUIndex switch { 0 => "Null", _ when entry != null => entry.ClassName, _ => "Invalid" }); if (entry != null) { if (entry.Parent == exp) { info.PropLocations.Add("Local"); } else if (entry.Parent == (exp.Parent.ClassName == "State" ? exp.Parent.Parent : exp.Parent)) { info.PropLocations.Add("ThisClass"); } else if (entry.Parent.ClassName == "Function") { info.PropLocations.Add("OtherFunction"); } else if (exp.Parent.IsA(entry.Parent.ObjectName)) { info.PropLocations.Add("AncestorClass"); } else { info.PropLocations.Add("OtherClass"); } } } } }
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(Utilities.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"); } } } } } }