private static bool CanRandomize(ExportEntry export, bool isArchetypeCheck, out ExportEntry hairMeshExp)
        {
            hairMeshExp = null;
            if (export.IsDefaultObject)
            {
                return(false);
            }
            if (export.ClassName == "BioPawn" &&
                export.GetProperty <ObjectProperty>("m_oHairMesh") is ObjectProperty op &&
                op.ResolveToEntry(export.FileRef) is ExportEntry hairExp)
            {
                hairMeshExp = hairExp;
                return(true);
            }

            if (export.ClassName == "SFXSkeletalMeshActorMAT")
            {
                if (export.GetProperty <ObjectProperty>("HairMesh") is ObjectProperty opSKM &&
                    opSKM.ResolveToEntry(export.FileRef) is ExportEntry hairExpSKM)
                {
                    // Check if skeletal mesh is set locally or if it's done in the archetype
                    // if set to zero we should not add hair cause it will look bad

                    var skeletalMesh = hairExpSKM.GetProperty <ObjectProperty>("SkeletalMesh");
                    if (skeletalMesh == null)
                    {
                        // Look in archetype
                        if (export.Archetype != null)
                        {
                            ExportEntry arch = export.Archetype as ExportEntry;
                            if (export.Archetype is ImportEntry imp)
                            {
                                // oof
                                arch = EntryImporter.ResolveImport(imp, MERFileSystem.GetGlobalCache());
                            }
                            hairMeshExp = hairExpSKM;
                            var result = export.ObjectFlags.Has(UnrealFlags.EObjectFlags.ArchetypeObject) == isArchetypeCheck && CanRandomize(arch, true, out _); // look at archetype
                            if (result && !isArchetypeCheck)
                            {
                                Debug.WriteLine($"Running on {export.ObjectName.Instanced}");
                            }
                            return(result);
                        }
                    }
                    else
                    {
                        hairMeshExp = hairExpSKM;
                        var result = export.ObjectFlags.Has(UnrealFlags.EObjectFlags.ArchetypeObject) == isArchetypeCheck && skeletalMesh.Value != 0;
                        if (result && !isArchetypeCheck)
                        {
                            Debug.WriteLine($"Running on {export.ObjectName.Instanced}");
                        }
                        return(result);
                    }
                }
            }
            return(false);
        }
        public static void TestAllImportsInMERFS()
        {
            var dlcModPath = Path.Combine(MEDirectories.GetDefaultGamePath(MERFileSystem.Game), "BioGame", "DLC", $"DLC_MOD_{MERFileSystem.Game}Randomizer", "CookedPC");

            var packages = Directory.GetFiles(dlcModPath);

            var globalCache = MERFileSystem.GetGlobalCache();

            //var globalP = Path.Combine(dlcModPath, "BioP_Global.pcc");
            //if (File.Exists(globalP))
            //{
            //    // This is used for animation lookups.
            //    globalCache.InsertIntoCache(MEPackageHandler.OpenMEPackage(globalP));
            //}

            foreach (var p in packages)
            {
                if (p.RepresentsPackageFilePath())
                {
                    MERPackageCache localCache = new MERPackageCache();
                    Debug.WriteLine($"Checking package file {p}");
                    var pack = MEPackageHandler.OpenMEPackage(p);
                    foreach (var imp in pack.Imports)
                    {
                        if (imp.InstancedFullPath.StartsWith("Core.") || imp.InstancedFullPath.StartsWith("Engine."))
                        {
                            continue; // These have some natives are always in same file.
                        }
                        if (imp.InstancedFullPath == "BioVFX_Z_TEXTURES.Generic.Glass_Shards_Norm" && MERFileSystem.Game == MEGame.ME2)
                        {
                            continue; // This is... native for some reason?
                        }
                        var resolvedExp = EntryImporter.ResolveImport(imp, globalCache, localCache);
                        if (resolvedExp == null)
                        {
                            Debug.WriteLine($"Could not resolve import: {imp.InstancedFullPath}");
                        }
                    }
                }
            }
            Debug.WriteLine("Done checking imports");
        }
Beispiel #3
0
        /// <summary>
        /// Creates a ModelPreviewMaterial that renders as close to what the given <see cref="MaterialInstanceConstant"/> looks like as possible.
        /// </summary>
        /// <param name="texcache">The texture cache to request textures from.</param>
        /// <param name="mat">The material that this ModelPreviewMaterial will try to look like.</param>
        protected ModelPreviewMaterial(PreviewTextureCache texcache, MaterialInstanceConstant mat, PackageCache assetCache, List <PreloadedTextureData> preloadedTextures = null)
        {
            if (mat == null)
            {
                return;
            }
            Properties.Add("Name", mat.Export.ObjectName);
            foreach (var textureEntry in mat.Textures)
            {
                if (!Textures.ContainsKey(textureEntry.FullPath) && textureEntry.ClassName == "Texture2D")  //Apparently some assets are cubemaps, we don't want these.
                {
                    if (preloadedTextures != null)
                    {
                        var preloadedInfo = preloadedTextures.FirstOrDefault(x => x.MaterialExport == mat.Export && x.Mip.Export.ObjectName.Name == textureEntry.ObjectName.Name); //i don't like matching on object name but its export vs import here.
                        if (preloadedInfo != null)
                        {
                            Textures.Add(textureEntry.FullPath, texcache.LoadTexture(preloadedInfo.Mip.Export, preloadedInfo.Mip, preloadedInfo.decompressedTextureData));
                        }
                        else
                        {
                            Debug.WriteLine("Preloading error");
                        }
                        //if (textureEntry is ExportEntry texPort && preloadedMipInfo.Export != texPort) throw new Exception();
                        continue; //Don't further parse
                    }

                    if (textureEntry is ImportEntry import)
                    {
                        var extAsset = EntryImporter.ResolveImport(import, null, assetCache);
                        if (extAsset != null)
                        {
                            Textures.Add(textureEntry.FullPath, texcache.LoadTexture(extAsset));
                        }
                    }
                    else
                    {
                        Textures.Add(textureEntry.FullPath, texcache.LoadTexture(textureEntry as ExportEntry));
                    }
                }
            }
        }
Beispiel #4
0
        /// <summary>
        /// Creates a preview of the given <see cref="SkeletalMesh"/>.
        /// </summary>
        /// <param name="Device">The Direct3D device to use for buffer creation.</param>
        /// <param name="m">The mesh to generate a preview for.</param>
        /// <param name="texcache">The texture cache for loading textures.</param>
        public ModelPreview(Device Device, SkeletalMesh m, PreviewTextureCache texcache, PackageCache assetCache, PreloadedModelData preloadedData = null)
        {
            // STEP 1: MATERIALS
            if (preloadedData == null)
            {
                for (int i = 0; i < m.Materials.Length; i++)
                {
                    UIndex materialUIndex        = m.Materials[i];
                    MaterialInstanceConstant mat = null;
                    if (materialUIndex.value > 0)
                    {
                        mat = new MaterialInstanceConstant(m.Export.FileRef.GetUExport(materialUIndex.value));
                    }
                    else if (materialUIndex.value < 0)
                    {
                        // The material instance is an import!
                        ImportEntry matImport     = m.Export.FileRef.GetImport(materialUIndex.value);
                        var         externalAsset = EntryImporter.ResolveImport(matImport, null, assetCache);
                        if (externalAsset != null)
                        {
                            mat = new MaterialInstanceConstant(externalAsset);
                        }
                    }

                    if (mat != null)
                    {
                        ModelPreviewMaterial material;
                        // TODO: pick what material class best fits based on what properties the
                        // MaterialInstanceConstant mat has.
                        // For now, just use the default material.
                        material = new TexturedPreviewMaterial(texcache, mat, assetCache);
                        AddMaterial(material.Properties["Name"], material);
                    }
                }
            }
            else
            {
                //Preloaded
                //sections = preloadedData.sections;
                var uniqueMaterials = preloadedData.texturePreviewMaterials.Select(x => x.MaterialExport).Distinct();
                foreach (var mat in uniqueMaterials)
                {
                    var material = new TexturedPreviewMaterial(texcache, new MaterialInstanceConstant(mat), assetCache, preloadedData.texturePreviewMaterials);
                    AddMaterial(mat.ObjectName.Name, material);
                }
            }

            // STEP 2: LODS
            foreach (var lodmodel in m.LODModels)
            {
                // Vertices
                List <WorldVertex> vertices = new List <WorldVertex>(m.Export.Game == MEGame.ME1 ? lodmodel.ME1VertexBufferGPUSkin.Length : lodmodel.VertexBufferGPUSkin.VertexData.Length);
                if (m.Export.Game == MEGame.ME1)
                {
                    foreach (var vertex in lodmodel.ME1VertexBufferGPUSkin)
                    {
                        vertices.Add(new WorldVertex(new Vector3(-vertex.Position.X, vertex.Position.Z, vertex.Position.Y), Vector3.Zero, new Vector2(vertex.UV.X, vertex.UV.Y)));
                    }
                }
                else
                {
                    foreach (var vertex in lodmodel.VertexBufferGPUSkin.VertexData)
                    {
                        vertices.Add(new WorldVertex(new Vector3(-vertex.Position.X, vertex.Position.Z, vertex.Position.Y), Vector3.Zero, new Vector2(vertex.UV.X, vertex.UV.Y)));
                    }
                }
                // Triangles
                List <Triangle> triangles = new List <Triangle>(lodmodel.IndexBuffer.Length / 3);
                for (int i = 0; i < lodmodel.IndexBuffer.Length; i += 3)
                {
                    triangles.Add(new Triangle(lodmodel.IndexBuffer[i], lodmodel.IndexBuffer[i + 1], lodmodel.IndexBuffer[i + 2]));
                }
                WorldMesh mesh = new WorldMesh(Device, triangles, vertices);
                // Sections
                List <ModelPreviewSection> sections = new List <ModelPreviewSection>();
                foreach (var section in lodmodel.Sections)
                {
                    if (section.MaterialIndex < Materials.Count)
                    {
                        sections.Add(new ModelPreviewSection(Materials.Keys.ElementAt(section.MaterialIndex), section.BaseIndex, (uint)section.NumTriangles));
                    }
                }
                LODs.Add(new ModelPreviewLOD(mesh, sections));
            }
        }
        private void ReadMaterial(ExportEntry export, PackageCache assetCache = null)
        {
            if (export.ClassName == "Material")
            {
                var parsedMaterial = ObjectBinary.From <Material>(export);
                StaticParameterSet = (StaticParameterSet)parsedMaterial.SM3MaterialResource.ID;
                foreach (var v in parsedMaterial.SM3MaterialResource.UniformExpressionTextures)
                {
                    IEntry tex = export.FileRef.GetEntry(v.value);
                    if (tex != null)
                    {
                        Textures.Add(tex);
                    }
                }
            }
            else if (export.ClassName == "RvrEffectsMaterialUser")
            {
                var props = export.GetProperties();
                if (export.GetProperty <ObjectProperty>("m_pBaseMaterial") is ObjectProperty baseProp)
                {
                    // This is an instance... maybe?
                    if (baseProp.Value > 0)
                    {
                        // Local export
                        ReadMaterial(export.FileRef.GetUExport(baseProp.Value));
                    }
                    else
                    {
                        ImportEntry ie            = export.FileRef.GetImport(baseProp.Value);
                        var         externalEntry = EntryImporter.ResolveImport(ie, null, assetCache);
                        if (externalEntry != null)
                        {
                            ReadMaterial(externalEntry);
                        }
                    }
                }
            }
            else if (export.ClassName == "MaterialInstanceConstant")
            {
                if (ObjectBinary.From(export) is MaterialInstance matInstBin)
                {
                    StaticParameterSet = matInstBin.SM3StaticParameterSet;
                }
                //Read Local
                if (export.GetProperty <ArrayProperty <StructProperty> >("TextureParameterValues") is ArrayProperty <StructProperty> textureparams)
                {
                    foreach (var param in textureparams)
                    {
                        var paramValue = param.GetProp <ObjectProperty>("ParameterValue");
                        var texntry    = export.FileRef.GetEntry(paramValue.Value);
                        if (texntry?.ClassName == "Texture2D" && !Textures.Contains(texntry))
                        {
                            Textures.Add(texntry);
                        }
                    }
                }

                if (export.GetProperty <ArrayProperty <ObjectProperty> >("ReferencedTextures") is ArrayProperty <ObjectProperty> textures)
                {
                    foreach (var obj in textures)
                    {
                        var texntry = export.FileRef.GetEntry(obj.Value);
                        if (texntry.ClassName == "Texture2D" && !Textures.Contains(texntry))
                        {
                            Textures.Add(texntry);
                        }
                    }
                }

                //Read parent
                if (export.GetProperty <ObjectProperty>("Parent") is ObjectProperty parentObjProp)
                {
                    // This is an instance... maybe?
                    if (parentObjProp.Value > 0)
                    {
                        // Local export
                        ReadMaterial(export.FileRef.GetUExport(parentObjProp.Value));
                    }
                    else
                    {
                        ImportEntry ie            = export.FileRef.GetImport(parentObjProp.Value);
                        var         externalEntry = ModelPreview.FindExternalAsset(ie, null, null);
                        if (externalEntry != null)
                        {
                            ReadMaterial(externalEntry);
                        }
                    }
                }
            }
        }
        public static bool RandomizePackageActorsInConversation(IMEPackage package, RandomizationOption option)
        {
            var conversationStartExports = package.Exports.Where(CanRandomizeSeqActStartConvo).ToList();

            if (!conversationStartExports.Any())
            {
                return(false);
            }

            MERPackageCache localCache = new MERPackageCache();

            foreach (var convStart in conversationStartExports)
            {
                //if (convStart.UIndex < 13638)
                //    continue;
                if (!CanRandomizeConversationStart(convStart))
                {
                    continue;
                }
                var bioConvImportProp = convStart.GetProperty <ObjectProperty>("Conv");
                if (bioConvImportProp == null)
                {
                    continue; // Some conversation starts are just filler stubs and are missing data like Nor_340a/bZaeed
                }
                var           bioConvImport = bioConvImportProp.ResolveToEntry(package) as ImportEntry;
                List <string> inputTags     = new();

                // Shuffle the inputs to the conversation. We will store these and then have to update the Owner and Player tags
                // I think......
                var seqLinks = SeqTools.GetVariableLinksOfNode(convStart);

                // Dictionary of linked nodes, and var links that point to them.
                List <ExportEntry>          shufflableNodes = new List <ExportEntry>();
                List <SeqTools.VarLinkInfo> applicableLinks = new();
                ExportEntry ownerEntry      = null;
                ExportEntry playerEntry     = null;
                var         sequenceObjects = SeqTools.GetAllSequenceElements(convStart).OfType <ExportEntry>().ToList();
                foreach (var varilink in seqLinks)
                {
                    if (CanLinkBeRandomized(varilink, out _))
                    {
                        var connectedItem = varilink.LinkedNodes[0] as ExportEntry;
                        if (!shufflableNodes.Contains(connectedItem))
                        {
                            // Check to make sure node has a value
                            if (connectedItem.ClassName == "SeqVar_Object")
                            {
                                var objValue = connectedItem.GetProperty <ObjectProperty>("ObjValue");
                                if (objValue == null && !MERSeqTools.IsAssignedBioPawn(convStart, connectedItem, sequenceObjects))
                                {
                                    continue; // This is not a shufflable node, it is never assigned anything
                                }
                            }

                            shufflableNodes.Add(connectedItem);
                        }

                        if (varilink.LinkedNodes[0].ClassName == "SeqVar_Player")
                        {
                            playerEntry = varilink.LinkedNodes[0] as ExportEntry;
                        }
                        else if (varilink.LinkDesc == "Owner")
                        {
                            ownerEntry = varilink.LinkedNodes[0] as ExportEntry;
                        }

                        applicableLinks.Add(varilink);
                    }
                }

                if (shufflableNodes.Count <= 1)
                {
                    continue; // We cannot edit this one
                }
                if (shufflableNodes.Count == 2 && playerEntry != null && ownerEntry != null)
                {
                    continue; // We can't swap owner and player due to hardcoded owner and player tags
                }
                // Build shuffle map
                bool retry = true;
                Dictionary <int, int> shuffleMap = null;
                while (retry)
                {
                    shuffleMap = new Dictionary <int, int>();
                    var reAssigned = shufflableNodes.ToList();
                    reAssigned.Shuffle();

                    for (int i = 0; i < shufflableNodes.Count; i++)
                    {
                        var sourceEntry = shufflableNodes[i];
                        var remapEntry  = reAssigned.PullFirstItem();

                        int retryCount = reAssigned.Count;
                        while (retryCount > 0 && (sourceEntry == remapEntry || (ownerEntry == sourceEntry && remapEntry == ownerEntry)))
                        {
                            reAssigned.Add(remapEntry);
                            sourceEntry = reAssigned.PullFirstItem();
                            retryCount--;
                        }

                        if (sourceEntry == ownerEntry && remapEntry == playerEntry)
                        {
                            // Cannot set player to owner
                            break; // We must force a retry
                        }

                        shuffleMap[sourceEntry.UIndex] = remapEntry.UIndex;
                    }

                    if (shuffleMap.Count != shufflableNodes.Count)
                    {
                        continue; // Try again
                    }
                    // We did it
                    retry = false;
                }

                // Apply the shuffle map
                Dictionary <NameReference, ActorLookup> findActorMap = new();

                foreach (var varilink in applicableLinks)
                {
                    var repointedItem = shuffleMap[varilink.LinkedNodes[0].UIndex];
                    //if (varilink.LinkedNodes[0] == playerEntry || repointedItem == playerEntry.UIndex)
                    //{
                    // Build FindActor mapping for player
                    // it should be:
                    // Player -> [Some actor, like Pup1_1]
                    // Pup1_1 -> Player
                    // When parsing the interpdatas, change the findactor's
                    // by this methodology
                    BuildActorMap(findActorMap, varilink, repointedItem);
                    //}

                    // Actor map for updating the bioconversation

                    //Debug.WriteLine($"Shuffle actor on varilink in convo: {varilink.LinkedNodes[0].ObjectName.Instanced} => {package.GetUExport(repointedItem).ObjectName.Instanced}");
                    varilink.LinkedNodes[0] = package.GetUExport(repointedItem);
                }

                //SeqTools.PrintVarLinkInfo(seqLinks);

                // Write the updated links out.
                SeqTools.WriteVariableLinksToNode(convStart, seqLinks);



                // Update the localizations
                foreach (var loc in Localizations)
                {
                    var bioConversation = EntryImporter.ResolveImport(bioConvImport, MERFileSystem.GetGlobalCache(), localCache, loc);
                    var conv            = new ConversationExtended(bioConversation);
                    conv.LoadConversation(null, true);

                    // Step 1. Update tags via map
                    var allConvEntries = conv.ReplyList.ToList();
                    allConvEntries.AddRange(conv.EntryList);
                    foreach (var convNode in allConvEntries)
                    {
                        // Update speaker
                        if (convNode.IsReply)
                        {
                            // Player. We can't do anything (or can we?)
                        }
                        else
                        {
                            // Non-player node. We can change the tag
                            var speakerTag = convNode.SpeakerTag;

                            // Even though it is dictionary, since it is NameRef, it is considered case sensitive. We have to use case insensitive check
                            var newName = findActorMap.FirstOrDefault(x => x.Key.Instanced.Equals(speakerTag.SpeakerName, StringComparison.InvariantCultureIgnoreCase)).Value;
                            if (newName != null && newName.FindActor.Name != null)
                            {
                                var newTagIdx = conv.Speakers.FirstOrDefault(x => x.SpeakerName.Equals(newName.FindActor.Instanced, StringComparison.InvariantCultureIgnoreCase));
                                if (newTagIdx != null)
                                {
                                    convNode.SpeakerIndex = newTagIdx.SpeakerID;
                                }
                                else
                                {
                                    var newSpeaker = new SpeakerExtended(conv.Speakers.Count - 3, new NameReference(newName.FindActor.Name.ToLower(), newName.FindActor.Number));
                                    newSpeaker.FaceFX_Male   = speakerTag.FaceFX_Male;
                                    newSpeaker.FaceFX_Female = speakerTag.FaceFX_Male;

                                    conv.Speakers.Add(newSpeaker);
                                    convNode.SpeakerIndex = newSpeaker.SpeakerID;
                                    //Debugger.Break();
                                }
                            }
                            else
                            {
                                //Debugger.Break();
                            }
                        }



                        // Update interpolation data
                        if (convNode.Interpdata == null)
                        {
                            continue;
                        }
                        var interpData = convNode.Interpdata;

                        InterpData id    = new InterpData(interpData);
                        var        convo = id.InterpGroups.FirstOrDefault(x => x.GroupName == "Conversation");
                        if (convo != null)
                        {
                            foreach (var it in convo.Tracks)
                            {
                                var props     = it.Export.GetProperties();
                                var findActor = props.GetProp <NameProperty>("m_nmFindActor");
                                if (findActor != null && findActor.Value.Name != "Owner" && findActorMap.TryGetValue(findActor.Value, out var newInfo) && newInfo.FindActor.Name != null)
                                {
                                    //Debug.WriteLine($"Updating find actor info: {findActor.Value.Instanced} -> {newInfo.FindActor.Instanced}");
                                    findActor.Value = newInfo.FindActor;
                                    if (newInfo.FindMode != null)
                                    {
                                        props.AddOrReplaceProp(new EnumProperty(newInfo.FindMode.ToString(), "EActorTrackFindActorMode", MERFileSystem.Game, "m_eFindActorMode"));
                                    }
                                    else
                                    {
                                        props.RemoveNamedProperty("m_eFindActorMode");
                                    }
                                }

                                var lookAtKeys = props.GetProp <ArrayProperty <StructProperty> >("m_aLookAtKeys");
                                if (lookAtKeys != null)
                                {
                                    foreach (var lookAtS in lookAtKeys)
                                    {
                                        var lookAt = lookAtS.GetProp <NameProperty>("nmFindActor");

                                        if (lookAt.Value.Name != "Owner" && findActorMap.TryGetValue(lookAt.Value, out var newInfoLA) && newInfoLA.FindActor.Name != null)
                                        {
                                            //Debug.WriteLine($"Updating lookat find actor info: {lookAt.Value.Instanced} -> {newInfoLA.FindActor.Instanced}");
                                            if (newInfoLA.FindActor.Name == null)
                                            {
                                                Debugger.Break();
                                            }
                                            lookAt.Value = newInfoLA.FindActor;
                                            var lookatFindMode = newInfoLA.FindMode?.ToString();
                                            lookatFindMode ??= "ActorTrack_FindActorByTag"; // if it's null, set it to the default. As this is struct, the property must exist
                                            lookAtS.Properties.AddOrReplaceProp(new EnumProperty(lookatFindMode, "EActorTrackFindActorMode", MERFileSystem.Game, "eFindActorMode"));
                                        }
                                    }
                                }

                                it.Export.WriteProperties(props);

                                //if (IsAllowedFindActor(findActor))
                                //{
                                //    if (actorsToFind.All(x => x.FindActor.Instanced != findActor.Value.Instanced))
                                //    {

                                //        ActorLookup al = new ActorLookup() { FindActor = findActor.Value };
                                //        var lookupType = it.Export.GetProperty<EnumProperty>("m_eFindActorMode");
                                //        if (lookupType != null && Enum.TryParse<EActorTrackFindActorMode>(lookupType.Value.Name, out var result))
                                //        {
                                //            al.FindMode = result;
                                //        }

                                //        actorsToFind.Add(al);
                                //    }

                                //    // add track
                                //    tracksToUpdate.Add(it);
                                //}
                            }
                        }
                    }
                    conv.SerializeNodes(); // Write the updated info back
                    MERFileSystem.SavePackage(bioConversation.FileRef);
                }
            }

            //// Step 2. Build the remapping
            //if (actorsToFind.Count <= 1)
            //    return false; // Nothing to randomize

            //// Instanced name to NameReference
            //Dictionary<string, ActorLookup> actorRemap = new Dictionary<string, ActorLookup>();
            //var shuffledActors = actorsToFind.ToList();
            //var unshuffledActors = actorsToFind.Select(x => x.FindActor.Instanced).ToList();
            //shuffledActors.Shuffle();

            //Debug.WriteLine("");
            //while (shuffledActors.Count > 0)
            //{
            //    var sourceItem = unshuffledActors.PullFirstItem();
            //    var remappedItem = shuffledActors.PullFirstItem();
            //    actorRemap[sourceItem] = remappedItem;
            //    Debug.WriteLine($"Remapping actor: {sourceItem} => {remappedItem.FindActor.Instanced}");
            //}

            //// Step 3. Update all tracks with new remap
            //foreach (var track in tracksToUpdate)
            //{
            //    var props = track.Export.GetProperties();
            //    var findActor = props.GetProp<NameProperty>("m_nmFindActor");
            //    if (IsAllowedFindActor(findActor))
            //    {
            //        if (actorRemap[findActor.Value.Instanced].FindMode != null)
            //        {
            //            props.AddOrReplaceProp(new EnumProperty(actorRemap[findActor.Value.Instanced].FindMode.ToString(), "EActorTrackFindActorMode", MERFileSystem.Game, "m_eFindActorMode"));
            //        }
            //        else
            //        {
            //            props.RemoveNamedProperty("m_eFindActorNode");
            //        }
            //        findActor.Value = actorRemap[findActor.Value.Instanced].FindActor;
            //        track.Export.WriteProperties(props);
            //    }
            //}


            return(true);
        }
        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);
        }