From b9fe314c3a3e006522e6dc8f09393ea1b10b0af4 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:44:19 -0700 Subject: [PATCH 01/10] Add in support for duping most formats This adds in support for meshes, textures, object variations blueprint bundles and(hopefully) subworlds to be duplicated --- .../DuplicateContextMenuItem.cs | 738 ++++++++++++++---- .../DuplicationPlugin.csproj | 155 ++-- .../Properties/AssemblyInfo.cs | 57 +- 3 files changed, 690 insertions(+), 260 deletions(-) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index 677c87463..09b647ede 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -1,156 +1,582 @@ -using DuplicationPlugin.Windows; -using Frosty.Core; -using Frosty.Core.Windows; -using FrostySdk; -using FrostySdk.Ebx; -using FrostySdk.IO; -using FrostySdk.Managers; -using FrostySdk.Resources; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace DuplicationPlugin -{ - public class TextureExtension : DuplicateAssetExtension - { - public override string AssetType => "TextureAsset"; - - public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) - { - // Duplicate the ebx - EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); - EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); - - // Get the original asset root object data - EbxAsset asset = App.AssetManager.GetEbx(entry); - dynamic textureAsset = asset.RootObject; - - // Get the original chunk and res entries - ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); - Texture texture = App.AssetManager.GetResAs(resEntry); - ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); - - // Duplicate the chunk - Guid chunkGuid = App.AssetManager.AddChunk(new NativeReader(texture.Data).ReadToEnd(), null, texture); - ChunkAssetEntry newChunkEntry = App.AssetManager.GetChunkEntry(chunkGuid); - - // Duplicate the res - ResAssetEntry newResEntry = App.AssetManager.AddRes(newName, ResourceType.Texture, resEntry.ResMeta, new NativeReader(App.AssetManager.GetRes(resEntry)).ReadToEnd()); - ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; - Texture newTexture = App.AssetManager.GetResAs(newResEntry); - newTexture.ChunkId = chunkGuid; - newTexture.AssetNameHash = (uint)Utils.HashString(newResEntry.Name, true); - - // Add the new chunk/res entries to the original bundles - newResEntry.AddedBundles.AddRange(resEntry.EnumerateBundles()); - newChunkEntry.AddedBundles.AddRange(chunkEntry.EnumerateBundles()); - - // Link the newly duplicates ebx, chunk, and res entries together - newResEntry.LinkAsset(newChunkEntry); - newEntry.LinkAsset(newResEntry); - - // Modify ebx and res - App.AssetManager.ModifyEbx(newEntry.Name, newAsset); - App.AssetManager.ModifyRes(newResEntry.Name, newTexture); - - return newEntry; - } - } - - public class DuplicateAssetExtension - { - public virtual string AssetType => null; - - public virtual EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) - { - EbxAsset asset = App.AssetManager.GetEbx(entry); - EbxAsset newAsset = null; - - if (createNew) - { - newAsset = new EbxAsset(TypeLibrary.CreateObject(newType.Name)); - } - else - { - using (EbxBaseWriter writer = EbxBaseWriter.CreateWriter(new MemoryStream(), EbxWriteFlags.DoNotSort | EbxWriteFlags.IncludeTransient)) - { - writer.WriteAsset(asset); - byte[] buf = writer.ToByteArray(); - using (EbxReader reader = EbxReader.CreateReader(new MemoryStream(buf))) - newAsset = reader.ReadAsset(); - } - } - - newAsset.SetFileGuid(Guid.NewGuid()); - - dynamic obj = newAsset.RootObject; - obj.Name = newName; - - AssetClassGuid guid = new AssetClassGuid(Utils.GenerateDeterministicGuid(newAsset.Objects, (Type)obj.GetType(), newAsset.FileGuid), -1); - obj.SetInstanceGuid(guid); - - EbxAssetEntry newEntry = App.AssetManager.AddEbx(newName, newAsset); - - newEntry.AddedBundles.AddRange(entry.EnumerateBundles()); - newEntry.ModifiedEntry.DependentAssets.AddRange(newAsset.Dependencies); - - return newEntry; - } - } - - public class DuplicateContextMenuItem : DataExplorerContextMenuExtension - { - private Dictionary extensions = new Dictionary(); - - public DuplicateContextMenuItem() - { - foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) - { - if (type.IsSubclassOf(typeof(DuplicateAssetExtension))) - { - var extension = (DuplicateAssetExtension)Activator.CreateInstance(type); - extensions.Add(extension.AssetType, extension); - } - } - extensions.Add("null", new DuplicateAssetExtension()); - } - - public override string ContextItemName => "Duplicate"; - - public override RelayCommand ContextItemClicked => new RelayCommand((o) => - { - EbxAssetEntry entry = App.SelectedAsset as EbxAssetEntry; - EbxAsset asset = App.AssetManager.GetEbx(entry); - - DuplicateAssetWindow win = new DuplicateAssetWindow(entry); - if (win.ShowDialog() == false) - return; - - string newName = win.SelectedPath + "/" + win.SelectedName; - newName = newName.Trim('/'); - - Type newType = win.SelectedType; - FrostyTaskWindow.Show("Duplicating asset", "", (task) => - { - try - { - string key = entry.Type; - if (!extensions.ContainsKey(entry.Type)) - key = "null"; - extensions[key].DuplicateAsset(entry, newName, newType != null, newType); - } - catch (Exception e) - { - App.Logger.Log($"Failed to duplicate {entry.Name}"); - } - }); - - App.EditorWindow.DataExplorer.RefreshAll(); - }); - } -} +using AtlasTexturePlugin; +using DuplicationPlugin.Windows; +using Frosty.Core; +using Frosty.Core.Viewport; +using Frosty.Core.Windows; +using Frosty.Hash; +using FrostySdk; +using FrostySdk.Ebx; +using FrostySdk.IO; +using FrostySdk.Managers; +using FrostySdk.Resources; +using MeshSetPlugin.Resources; +using SvgImagePlugin; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace DuplicationPlugin +{ + public class DuplicationTool + { + #region --Extensions-- + public class BlueprintBundleExtension : DuplicateAssetExtension + { + public override string AssetType => "BlueprintBundle"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName.ToLower(), createNew, newType); + + BundleEntry bundleEntry = App.AssetManager.GetBundleEntry(entry.Bundles[0]); + + EbxAssetEntry mvdb = App.AssetManager.EnumerateEbx("MeshVariationDatabase", bundleSubPath: bundleEntry.Name).First(); + + BundleEntry newBundleEntry = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.BlueprintBundle, bundleEntry.SuperBundleId); + newBundleEntry.Blueprint = newEntry; + + if (App.AssetManager.GetEbx(newName.ToLower() + "/MeshVariationDb_Win32") != null) + { + EbxAssetEntry newMvdb = base.DuplicateAsset(mvdb, newName.ToLower() + "/MeshVariationDb_Win32", false, null); + newMvdb.AddedBundles.Clear(); + newMvdb.AddToBundle(App.AssetManager.GetBundleId(newBundleEntry)); + + newEntry.AddedBundles.Clear(); + newEntry.AddToBundle(App.AssetManager.GetBundleId(newBundleEntry)); + } + + return newEntry; + } + } + + //ToDo: this doesn't work for SWBF2, so add in support for it + public class ObjectVariationExtension : DuplicateAssetExtension + { + public override string AssetType => "ObjectVariation"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + EbxAssetEntry ebxAssetEntry = base.DuplicateAsset(entry, newName, createNew, newType); + if (ebxAssetEntry == null) + { + return null; + } + EbxAsset ebx = App.AssetManager.GetEbx(ebxAssetEntry); + dynamic rootObject = ebx.RootObject; + + rootObject.NameHash = (uint)Fnv1.HashString(newName.ToLower()); //Setup the name hash + + //Warnings for the user, that way they know of potential Bundle Editor conflicts + string guidString = newName.Split('/').ToList().Last().Split('_').ToList().First(); //What the fuck is this + bool isValid = Guid.TryParse(guidString, out Guid originalMeshGuid); + if (!isValid) //Check if the name is valid + { + App.Logger.LogWarning("For the Bundle Editor to be able to add new variations to the MeshVariationDatabase, the first part of the name needs to be the original mesh guid. For example, 0cd5f035-1737-4056-8ac0-9cf4692084b7_ShockTrooperVariation"); + } + + else if (isValid) //If it is valid, then we will copy the materials from the original mesh into here + { + int Index = -1; //The material index we are currently on + + //Find the original mesh, based off of the root instance guid + EbxAsset meshEbx = new EbxAsset(); + foreach (EbxAssetEntry potentialMesh in App.AssetManager.EnumerateEbx("MeshAsset")) + { + if (App.AssetManager.GetEbx(potentialMesh).RootInstanceGuid == originalMeshGuid) + { + meshEbx = App.AssetManager.GetEbx(potentialMesh); + break; + } + } + + //Clear out the variation's materials + List list = new List(ebx.Objects.ToList()); + foreach (object matObject in list) + { + if (matObject.GetType().ToString().Split('.').ToList().Last() == "MeshMaterialVariation") + { + ebx.RemoveObject(matObject); + } + } + + //Create new materials in the variation + dynamic meshProperties = meshEbx.RootObject; + ResAssetEntry ResEntry = App.AssetManager.GetResEntry(meshProperties.MeshSetResource); + MeshSet meshSet = App.AssetManager.GetResAs(ResEntry); + foreach (PointerRef matPointer in meshProperties.Materials) + { + //Grab the mesh's material + dynamic meshMatProperties = matPointer.Internal as dynamic; + + //Create a new object + dynamic newMaterialV = TypeLibrary.CreateObject("MeshMaterialVariation"); + + AssetClassGuid guid = new AssetClassGuid(Utils.GenerateDeterministicGuid(ebx.Objects, (Type)newMaterialV.GetType(), ebx.FileGuid), -1); + newMaterialV.SetInstanceGuid(guid); + + //Write the properties + Index++; + MeshSetSection meshSection = meshSet.Lods[0].Sections[Index]; + //newMaterialV.__Id = ((dynamic)matPointer.Internal).GetInstanceGuid().ExportedGuid.ToString() + "_" + meshSection.Name; //Change the ID to be valid for the Bundle Editor + newMaterialV.Shader.TextureParameters = meshMatProperties.Shader.TextureParameters; + + ebx.AddRootObject(newMaterialV as object); + } + } + + return ebxAssetEntry; + } + } + + //ToDo: double check that this works for everything + public class SubworldExtension : DuplicateAssetExtension + { + public override string AssetType => "SubWorldData"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + EbxAssetEntry DuplicatedEbxEntry = base.DuplicateAsset(entry, newName, createNew, newType); + if (DuplicatedEbxEntry == null) + { + return null; + } + + //Remove bundles that already exist + DuplicatedEbxEntry.AddedBundles.Clear(); + + //Get the original super bundle, then create a new bundle and link it to that + BundleEntry originalBundleEntry = App.AssetManager.GetBundleEntry(entry.Bundles.FirstOrDefault()); + BundleEntry newBundleEntry = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.SubLevel, originalBundleEntry.SuperBundleId); + //Add the new asset to the new bundle + DuplicatedEbxEntry.AddToBundle(App.AssetManager.GetBundleId(newBundleEntry)); + App.Logger.Log("Created subworld {0}, and created a new bundle {1}", DuplicatedEbxEntry.Name, newBundleEntry.DisplayName); + + return DuplicatedEbxEntry; + } + } + + //Idk if this actually works, I am fairly certain it does and my testing in SWBF1 suggests it does, but the FX was also broken during those tests due to unrelated technical fuck-ups + public class AtlasTextureExtension : DuplicateAssetExtension + { + public override string AssetType => "AtlasTextureAsset"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + EbxAsset asset = App.AssetManager.GetEbx(entry); + dynamic textureAsset = asset.RootObject; + + // Get the original chunk and res entries + ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); + AtlasTexture texture = App.AssetManager.GetResAs(resEntry); + ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); + + // Duplicate the chunk + ChunkAssetEntry newChunkEntry = DuplicateChunk(chunkEntry); + + // Duplicate the res + ResAssetEntry newResEntry = DuplicateRes(resEntry, newName, ResourceType.AtlasTexture); + ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; + AtlasTexture newTexture = App.AssetManager.GetResAs(newResEntry); + newTexture.SetData(texture.Width, texture.Height, newChunkEntry.Id, App.AssetManager); + + // Link the newly duplicates ebx, chunk, and res entries together + newResEntry.LinkAsset(newChunkEntry); + newEntry.LinkAsset(newResEntry); + + // Modify ebx and res + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + App.AssetManager.ModifyRes(newResEntry.Name, newTexture); + + return newEntry; + } + } + + public class SoundWaveExtension : DuplicateAssetExtension + { + public override string AssetType => "SoundWaveAsset"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + dynamic SoundObjects = newAsset.RootObject; + + // Read the original sound chunks then duplicate them + foreach (var SoundChunk in SoundObjects.Chunks) + { + //Grab the original chunk entry, dupe it + ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(SoundChunk.ChunkId); + ChunkAssetEntry NewChunkEntry = DuplicateChunk(chunkEntry); + SoundChunk.ChunkId = NewChunkEntry.Id; + + //Add to bundles and link asset + NewChunkEntry.AddedBundles.AddRange(chunkEntry.EnumerateBundles()); + newEntry.LinkAsset(NewChunkEntry); + } + + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + return newEntry; + } + } + + public class MeshExtension : DuplicateAssetExtension + { + public override string AssetType => "MeshAsset"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + //2017 battlefront meshes always have lowercase names. This doesn't apply to all games, but its still safer to do so + newName = newName.ToLower(); + + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + EbxAsset asset = App.AssetManager.GetEbx(entry); + dynamic meshProperties = asset.RootObject; + dynamic newMeshProperties = newAsset.RootObject; + + //Get the original res entry and duplicate it + ResAssetEntry resAsset = App.AssetManager.GetResEntry(meshProperties.MeshSetResource); + ResAssetEntry newResAsset = DuplicateRes(resAsset, newName, ResourceType.MeshSet); + + //Since this is a mesh we need to get the meshSet for the duplicated entry and set it up + MeshSet meshSet = App.AssetManager.GetResAs(newResAsset); + meshSet.FullName = newResAsset.Name; + + //Go through all of the lods and duplicate their chunks + foreach (MeshSetLod lod in meshSet.Lods) + { + lod.Name = newResAsset.Name; + //Double check that the lod actually has a chunk id. If it doesn't this means the data is inline and we don't need to worry + if (lod.ChunkId != null && lod.ChunkId != Guid.Empty) + { + //Get the original chunk and dupe it + ChunkAssetEntry chunk = App.AssetManager.GetChunkEntry(lod.ChunkId); + ChunkAssetEntry newChunk = DuplicateChunk(chunk); + + //Now set the params for the lod + lod.ChunkId = newChunk.Id; + + //Link the res and chunk + newResAsset.LinkAsset(newChunk); + } + } + + //Set our new mesh's properties + newMeshProperties.MeshSetResource = newResAsset.ResRid; + newMeshProperties.NameHash = (uint)Utils.HashString(newName); + + //Link the res and ebx + newEntry.LinkAsset(newResAsset); + + //Stuff for SBDs since SWBF2 is weird + if (ProfilesLibrary.IsLoaded(ProfileVersion.StarWarsBattlefrontII)) + { + // Duplicate the sbd + ResAssetEntry oldShaderBlock = App.AssetManager.GetResEntry(entry.Name.ToLower() + "_mesh/blocks"); + ResAssetEntry newShaderBlock = DuplicateRes(oldShaderBlock, newResAsset.Name + "_mesh/blocks", ResourceType.ShaderBlockDepot); + ShaderBlockDepot newShaderBlockDepot = App.AssetManager.GetResAs(newShaderBlock); + + // TODO: hacky way to generate unique hashes + for (int i = 0; i < newShaderBlockDepot.ResourceCount; i++) + { + ShaderBlockResource res = newShaderBlockDepot.GetResource(i); + res.ChangeHash(meshSet.NameHash); + } + + // Change the references in the sbd + for (int lod = 0; lod < meshSet.Lods.Count; lod++) + { + ShaderBlockEntry sbEntry = newShaderBlockDepot.GetSectionEntry(lod); + ShaderBlockMeshVariationEntry sbMvEntry = newShaderBlockDepot.GetResource(sbEntry.Index + 1) as ShaderBlockMeshVariationEntry; + + // calculate new entry hash + sbEntry.SetHash(meshSet.NameHash, 0, lod); + sbMvEntry.SetHash(meshSet.NameHash, 0, lod); + + // Update the mesh guid + for (int section = 0; section < meshSet.Lods[lod].Sections.Count; section++) + { + MeshParamDbBlock mesh = sbEntry.GetMeshParams(section); + mesh.MeshAssetGuid = newAsset.RootInstanceGuid; + } + } + + App.AssetManager.ModifyRes(newShaderBlock.Name, newShaderBlockDepot); + + newResAsset.LinkAsset(newShaderBlock); + } + + //Modify the res and ebx + App.AssetManager.ModifyRes(newResAsset.Name, meshSet); + App.AssetManager.ModifyEbx(newName, newAsset); + + return newEntry; + } + } + + public class SvgExtension : DuplicateAssetExtension + { + public override string AssetType => "SvgImage"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + EbxAsset asset = App.AssetManager.GetEbx(entry); + dynamic textureAsset = asset.RootObject; + + // Get the original chunk and res entries + ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); + SvgImage texture = App.AssetManager.GetResAs(resEntry); + + //Duplicate the res + ResAssetEntry newResAsset = DuplicateRes(resEntry, newName, ResourceType.SvgImage); + + // Modify ebx and res + ((dynamic)newAsset.RootObject).Resource = newResAsset.ResRid; + newEntry.LinkAsset(newResAsset); + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + + return newEntry; + } + } + + public class TextureExtension : DuplicateAssetExtension + { + public override string AssetType => "TextureBaseAsset"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + EbxAsset asset = App.AssetManager.GetEbx(entry); + dynamic textureAsset = asset.RootObject; + + // Get the original chunk and res entries + ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); + Texture texture = App.AssetManager.GetResAs(resEntry); + ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); + + // Duplicate the chunk + Guid chunkGuid = App.AssetManager.AddChunk(new NativeReader(texture.Data).ReadToEnd(), null, texture); + ChunkAssetEntry newChunkEntry = App.AssetManager.GetChunkEntry(chunkGuid); + + // Duplicate the res + ResAssetEntry newResEntry = App.AssetManager.AddRes(newName, ResourceType.Texture, resEntry.ResMeta, new NativeReader(App.AssetManager.GetRes(resEntry)).ReadToEnd()); + ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; + Texture newTexture = App.AssetManager.GetResAs(newResEntry); + newTexture.ChunkId = chunkGuid; + newTexture.AssetNameHash = (uint)Utils.HashString(newResEntry.Name, true); + + // Add the new chunk/res entries to the original bundles + newResEntry.AddedBundles.AddRange(resEntry.EnumerateBundles()); + newChunkEntry.AddedBundles.AddRange(chunkEntry.EnumerateBundles()); + + // Link the newly duplicates ebx, chunk, and res entries together + newResEntry.LinkAsset(newChunkEntry); + newEntry.LinkAsset(newResEntry); + + // Modify ebx and res + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + App.AssetManager.ModifyRes(newResEntry.Name, newTexture); + + return newEntry; + } + } + + public class DuplicateAssetExtension + { + public virtual string AssetType => null; + + public virtual EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + EbxAsset asset = App.AssetManager.GetEbx(entry); + EbxAsset newAsset = null; + + if (createNew) + { + newAsset = new EbxAsset(TypeLibrary.CreateObject(newType.Name)); + } + else + { + using (EbxBaseWriter writer = EbxBaseWriter.CreateWriter(new MemoryStream(), EbxWriteFlags.DoNotSort | EbxWriteFlags.IncludeTransient)) + { + writer.WriteAsset(asset); + byte[] buf = writer.ToByteArray(); + using (EbxReader reader = EbxReader.CreateReader(new MemoryStream(buf))) + newAsset = reader.ReadAsset(); + } + } + + newAsset.SetFileGuid(Guid.NewGuid()); + + dynamic obj = newAsset.RootObject; + obj.Name = newName; + + AssetClassGuid guid = new AssetClassGuid(Utils.GenerateDeterministicGuid(newAsset.Objects, (Type)obj.GetType(), newAsset.FileGuid), -1); + obj.SetInstanceGuid(guid); + + EbxAssetEntry newEntry = App.AssetManager.AddEbx(newName, newAsset); + + newEntry.AddedBundles.AddRange(entry.EnumerateBundles()); + newEntry.ModifiedEntry.DependentAssets.AddRange(newAsset.Dependencies); + + return newEntry; + } + } + + #endregion + + #region --Chunk and res support-- + + public static ChunkAssetEntry DuplicateChunk(ChunkAssetEntry entry, Texture texture = null) + { + byte[] random = new byte[16]; + RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + while (true) + { + rng.GetBytes(random); + + random[15] |= 1; + + if (App.AssetManager.GetChunkEntry(new Guid(random)) == null) + { + break; + } + else + { + App.Logger.Log("Randomised onto old guid: " + random.ToString()); + } + } + Guid newGuid; + using (NativeReader reader = new NativeReader(App.AssetManager.GetChunk(entry))) + { + newGuid = App.AssetManager.AddChunk(reader.ReadToEnd(), new Guid(random), texture, entry.EnumerateBundles().ToArray()); + } + + ChunkAssetEntry newEntry = App.AssetManager.GetChunkEntry(newGuid); + foreach (int bId in entry.Bundles) + { + newEntry.AddToBundle(bId); + } + + App.Logger.Log(string.Format("Duped chunk {0} to {1}", entry.Name, newGuid)); + return newEntry; + } + + public static ResAssetEntry DuplicateRes(ResAssetEntry entry, string name, ResourceType resType) + { + if (App.AssetManager.GetResEntry(name) == null) + { + ResAssetEntry newEntry; + using (NativeReader reader = new NativeReader(App.AssetManager.GetRes(entry))) //ToDo: figure out why the hell this isn't adding res to bundles + { + newEntry = App.AssetManager.AddRes(name, resType, entry.ResMeta, reader.ReadToEnd(), entry.EnumerateBundles().ToArray()); + } + + //Hacky workaround to fix this not actually adding to bundles + if (newEntry.Bundles.Count == 0) + { + foreach (int bId in entry.Bundles) + { + newEntry.AddToBundle(bId); + } + } + + App.Logger.Log(string.Format("Duped res {0} to {1}", entry.Name, newEntry.Name)); + return newEntry; + } + else + { + App.Logger.Log(name + " already has a res files"); + return null; + } + } + + #endregion + + public class DuplicateContextMenuItem : DataExplorerContextMenuExtension + { + private Dictionary extensions = new Dictionary(); + + public DuplicateContextMenuItem() + { + foreach (var type in Assembly.GetExecutingAssembly().GetTypes()) + { + if (type.IsSubclassOf(typeof(DuplicateAssetExtension))) + { + var extension = (DuplicateAssetExtension)Activator.CreateInstance(type); + extensions.Add(extension.AssetType, extension); + } + } + extensions.Add("null", new DuplicateAssetExtension()); + } + + public override string ContextItemName => "Duplicate"; + + public override RelayCommand ContextItemClicked => new RelayCommand((o) => + { + EbxAssetEntry entry = App.SelectedAsset as EbxAssetEntry; + EbxAsset asset = App.AssetManager.GetEbx(entry); + + DuplicateAssetWindow win = new DuplicateAssetWindow(entry); + if (win.ShowDialog() == false) + return; + + string newName = win.SelectedPath + "/" + win.SelectedName; + newName = newName.Trim('/'); + + Type newType = win.SelectedType; + FrostyTaskWindow.Show("Duplicating asset", "", (task) => + { + if (!MeshVariationDb.IsLoaded) + MeshVariationDb.LoadVariations(task); + + try + { + string key = "null"; + foreach (string typekey in extensions.Keys) + { + if (TypeLibrary.IsSubClassOf(entry.Type, typekey)) + { + key = typekey; + break; + } + } + + extensions[key].DuplicateAsset(entry, newName, newType != null, newType); + } + catch (Exception e) + { + App.Logger.Log($"Failed to duplicate {entry.Name}"); + } + }); + + App.EditorWindow.DataExplorer.RefreshAll(); + }); + } + } +} diff --git a/Plugins/DuplicationPlugin/DuplicationPlugin.csproj b/Plugins/DuplicationPlugin/DuplicationPlugin.csproj index a8ecba115..50075e22d 100644 --- a/Plugins/DuplicationPlugin/DuplicationPlugin.csproj +++ b/Plugins/DuplicationPlugin/DuplicationPlugin.csproj @@ -1,77 +1,80 @@ - - - Developer - Debug;Release - Alpha;Release - Beta;Release - Final - x64 - net48 - BlankPlugin - BlankPlugin - Copyright © 2020 - MinimumRecommendedRules.ruleset - false - true - Library - - - - true - bin\Developer\Debug\ - DEBUG;TRACE - - - - bin\Release\Alpha\ - TRACE - true - - - - bin\Release\Beta\ - TRACE - true - - - - bin\Release\Final\ - TRACE - true - - - - - - - - - - - - false - - - false - - - false - - - false - - - - - - AddAssetWindow.xaml - - - - - - Designer - - - - - - - + + + Developer - Debug;Release - Alpha;Release - Beta;Release - Final + x64 + net48 + BlankPlugin + BlankPlugin + Copyright © 2020 + MinimumRecommendedRules.ruleset + false + true + Library + + + + true + bin\Developer\Debug\ + DEBUG;TRACE + + + + bin\Release\Alpha\ + TRACE + true + + + + bin\Release\Beta\ + TRACE + true + + + + bin\Release\Final\ + TRACE + true + + + + + + + + + + + + false + + + false + + + false + + + false + + + + + + + + + AddAssetWindow.xaml + + + + + + Designer + + + + + + + \ No newline at end of file diff --git a/Plugins/DuplicationPlugin/Properties/AssemblyInfo.cs b/Plugins/DuplicationPlugin/Properties/AssemblyInfo.cs index 0806b324b..2205e1182 100644 --- a/Plugins/DuplicationPlugin/Properties/AssemblyInfo.cs +++ b/Plugins/DuplicationPlugin/Properties/AssemblyInfo.cs @@ -1,29 +1,30 @@ -using DuplicationPlugin; -using Frosty.Core.Attributes; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Windows; - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4b612468-9b6a-4304-88a5-055c3575eb3d")] - -[assembly: PluginDisplayName("Asset Duplication")] -[assembly: PluginAuthor("Cade")] -[assembly: PluginVersion("1.0.0.0")] - +using DuplicationPlugin; +using Frosty.Core.Attributes; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +using static DuplicationPlugin.DuplicationTool; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4b612468-9b6a-4304-88a5-055c3575eb3d")] + +[assembly: PluginDisplayName("Asset Duplication")] +[assembly: PluginAuthor("Cade")] +[assembly: PluginVersion("1.0.0.0")] + [assembly: RegisterDataExplorerContextMenu(typeof(DuplicateContextMenuItem))] \ No newline at end of file From e953e92b24963859b5d2d80ce5f6f7e3f3ea25cb Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:31:12 -0700 Subject: [PATCH 02/10] Update with mophead's stuff(<3) Replace some of my stuff with mophead's since thats not working(thank you mophead <3) --- .../DuplicateContextMenuItem.cs | 246 ++++++------------ 1 file changed, 74 insertions(+), 172 deletions(-) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index 09b647ede..0681c5512 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -25,211 +25,143 @@ namespace DuplicationPlugin public class DuplicationTool { #region --Extensions-- - public class BlueprintBundleExtension : DuplicateAssetExtension + + public class SvgImageExtension : DuplicateAssetExtension { - public override string AssetType => "BlueprintBundle"; + public override string AssetType => "SvgImage"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { - // Duplicate the ebx - EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName.ToLower(), createNew, newType); - - BundleEntry bundleEntry = App.AssetManager.GetBundleEntry(entry.Bundles[0]); - - EbxAssetEntry mvdb = App.AssetManager.EnumerateEbx("MeshVariationDatabase", bundleSubPath: bundleEntry.Name).First(); + EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); + if (refEntry == null) + return null; + EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); + dynamic refRoot = refAsset.RootObject; - BundleEntry newBundleEntry = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.BlueprintBundle, bundleEntry.SuperBundleId); - newBundleEntry.Blueprint = newEntry; + ResAssetEntry resEntry = App.AssetManager.GetResEntry(refRoot.Resource); + SvgImage svgTexture = App.AssetManager.GetResAs(resEntry); - if (App.AssetManager.GetEbx(newName.ToLower() + "/MeshVariationDb_Win32") != null) + ResAssetEntry newResEntry = DuplicateRes(resEntry, refEntry.Name, ResourceType.SvgImage); + if (newResEntry != null) { - EbxAssetEntry newMvdb = base.DuplicateAsset(mvdb, newName.ToLower() + "/MeshVariationDb_Win32", false, null); - newMvdb.AddedBundles.Clear(); - newMvdb.AddToBundle(App.AssetManager.GetBundleId(newBundleEntry)); - - newEntry.AddedBundles.Clear(); - newEntry.AddToBundle(App.AssetManager.GetBundleId(newBundleEntry)); + refRoot.Resource = newResEntry.ResRid; + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); } - return newEntry; + return refEntry; } } - //ToDo: this doesn't work for SWBF2, so add in support for it - public class ObjectVariationExtension : DuplicateAssetExtension + public class SoundWaveExtension : DuplicateAssetExtension { - public override string AssetType => "ObjectVariation"; + public override string AssetType => "SoundWaveAsset"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { - EbxAssetEntry ebxAssetEntry = base.DuplicateAsset(entry, newName, createNew, newType); - if (ebxAssetEntry == null) - { + EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); + if (refEntry == null) return null; - } - EbxAsset ebx = App.AssetManager.GetEbx(ebxAssetEntry); - dynamic rootObject = ebx.RootObject; - - rootObject.NameHash = (uint)Fnv1.HashString(newName.ToLower()); //Setup the name hash - - //Warnings for the user, that way they know of potential Bundle Editor conflicts - string guidString = newName.Split('/').ToList().Last().Split('_').ToList().First(); //What the fuck is this - bool isValid = Guid.TryParse(guidString, out Guid originalMeshGuid); - if (!isValid) //Check if the name is valid - { - App.Logger.LogWarning("For the Bundle Editor to be able to add new variations to the MeshVariationDatabase, the first part of the name needs to be the original mesh guid. For example, 0cd5f035-1737-4056-8ac0-9cf4692084b7_ShockTrooperVariation"); - } + EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); + dynamic refRoot = refAsset.RootObject; - else if (isValid) //If it is valid, then we will copy the materials from the original mesh into here + foreach (dynamic chkref in refRoot.Chunks) { - int Index = -1; //The material index we are currently on - - //Find the original mesh, based off of the root instance guid - EbxAsset meshEbx = new EbxAsset(); - foreach (EbxAssetEntry potentialMesh in App.AssetManager.EnumerateEbx("MeshAsset")) + ChunkAssetEntry soundChunk = App.AssetManager.GetChunkEntry(chkref.ChunkId); + if (soundChunk != null) { - if (App.AssetManager.GetEbx(potentialMesh).RootInstanceGuid == originalMeshGuid) + ChunkAssetEntry newSoundChunk = DuplicateChunk(soundChunk); + if (newSoundChunk != null) { - meshEbx = App.AssetManager.GetEbx(potentialMesh); - break; + chkref.ChunkId = new Guid(newSoundChunk.Name); } } - - //Clear out the variation's materials - List list = new List(ebx.Objects.ToList()); - foreach (object matObject in list) - { - if (matObject.GetType().ToString().Split('.').ToList().Last() == "MeshMaterialVariation") - { - ebx.RemoveObject(matObject); - } - } - - //Create new materials in the variation - dynamic meshProperties = meshEbx.RootObject; - ResAssetEntry ResEntry = App.AssetManager.GetResEntry(meshProperties.MeshSetResource); - MeshSet meshSet = App.AssetManager.GetResAs(ResEntry); - foreach (PointerRef matPointer in meshProperties.Materials) - { - //Grab the mesh's material - dynamic meshMatProperties = matPointer.Internal as dynamic; - - //Create a new object - dynamic newMaterialV = TypeLibrary.CreateObject("MeshMaterialVariation"); - - AssetClassGuid guid = new AssetClassGuid(Utils.GenerateDeterministicGuid(ebx.Objects, (Type)newMaterialV.GetType(), ebx.FileGuid), -1); - newMaterialV.SetInstanceGuid(guid); - - //Write the properties - Index++; - MeshSetSection meshSection = meshSet.Lods[0].Sections[Index]; - //newMaterialV.__Id = ((dynamic)matPointer.Internal).GetInstanceGuid().ExportedGuid.ToString() + "_" + meshSection.Name; //Change the ID to be valid for the Bundle Editor - newMaterialV.Shader.TextureParameters = meshMatProperties.Shader.TextureParameters; - - ebx.AddRootObject(newMaterialV as object); - } } + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); - return ebxAssetEntry; + return refEntry; } } - //ToDo: double check that this works for everything - public class SubworldExtension : DuplicateAssetExtension + public class VisualUnlockBlueprintBundleExtension : DuplicateAssetExtension { - public override string AssetType => "SubWorldData"; + public override string AssetType => "BlueprintBundle"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { - EbxAssetEntry DuplicatedEbxEntry = base.DuplicateAsset(entry, newName, createNew, newType); - if (DuplicatedEbxEntry == null) - { - return null; - } + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + + // Add new bundle + BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.SubLevel, 0); - //Remove bundles that already exist - DuplicatedEbxEntry.AddedBundles.Clear(); + newEntry.AddedBundles.Clear(); + newEntry.AddedBundles.Add(App.AssetManager.GetBundleId(newBundle)); - //Get the original super bundle, then create a new bundle and link it to that - BundleEntry originalBundleEntry = App.AssetManager.GetBundleEntry(entry.Bundles.FirstOrDefault()); - BundleEntry newBundleEntry = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.SubLevel, originalBundleEntry.SuperBundleId); - //Add the new asset to the new bundle - DuplicatedEbxEntry.AddToBundle(App.AssetManager.GetBundleId(newBundleEntry)); - App.Logger.Log("Created subworld {0}, and created a new bundle {1}", DuplicatedEbxEntry.Name, newBundleEntry.DisplayName); + newBundle.Blueprint = newEntry; - return DuplicatedEbxEntry; + return newEntry; } } - //Idk if this actually works, I am fairly certain it does and my testing in SWBF1 suggests it does, but the FX was also broken during those tests due to unrelated technical fuck-ups - public class AtlasTextureExtension : DuplicateAssetExtension + public class SubWorldDataExtension : DuplicateAssetExtension { - public override string AssetType => "AtlasTextureAsset"; + public override string AssetType => "SubWorldData"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { // Duplicate the ebx EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); - EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); - // Get the original asset root object data - EbxAsset asset = App.AssetManager.GetEbx(entry); - dynamic textureAsset = asset.RootObject; + // Add new bundle + BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName, BundleType.SubLevel, 0); - // Get the original chunk and res entries - ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); - AtlasTexture texture = App.AssetManager.GetResAs(resEntry); - ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); + newEntry.AddedBundles.Clear(); + newEntry.AddedBundles.Add(App.AssetManager.GetBundleId(newBundle)); + //App.Logger.Log(App.AssetManager.GetBundleEntry(entry.Bundles[0]).SuperBundleId.ToString()); - // Duplicate the chunk - ChunkAssetEntry newChunkEntry = DuplicateChunk(chunkEntry); + newBundle.Blueprint = newEntry; + newEntry.LinkAsset(entry); - // Duplicate the res - ResAssetEntry newResEntry = DuplicateRes(resEntry, newName, ResourceType.AtlasTexture); - ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; - AtlasTexture newTexture = App.AssetManager.GetResAs(newResEntry); - newTexture.SetData(texture.Width, texture.Height, newChunkEntry.Id, App.AssetManager); - - // Link the newly duplicates ebx, chunk, and res entries together - newResEntry.LinkAsset(newChunkEntry); - newEntry.LinkAsset(newResEntry); + //List assets = new List(); + //assets.AddRange(App.AssetManager.EnumerateEbx()); + //assets.AddRange(App.AssetManager.EnumerateRes()); + //assets.AddRange(App.AssetManager.EnumerateChunks()); - // Modify ebx and res - App.AssetManager.ModifyEbx(newEntry.Name, newAsset); - App.AssetManager.ModifyRes(newResEntry.Name, newTexture); + //foreach (AssetEntry asset in assets) + // if (asset.IsInBundle(entry.Bundles[0]) && asset != entry) + // asset.AddToBundle(App.AssetManager.GetBundleId(newBundle)); return newEntry; } } - public class SoundWaveExtension : DuplicateAssetExtension + public class PathfindingExtension : DuplicateAssetExtension { - public override string AssetType => "SoundWaveAsset"; + + public override string AssetType => "PathfindingBlobAsset"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { - // Duplicate the ebx - EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); - EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); + if (refEntry == null) + return null; - // Get the original asset root object data - dynamic SoundObjects = newAsset.RootObject; - // Read the original sound chunks then duplicate them - foreach (var SoundChunk in SoundObjects.Chunks) + EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); + dynamic refRoot = refAsset.RootObject; + ChunkAssetEntry pathfindingChunk = App.AssetManager.GetChunkEntry(refRoot.Blob.BlobId); + if (pathfindingChunk != null) { - //Grab the original chunk entry, dupe it - ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(SoundChunk.ChunkId); - ChunkAssetEntry NewChunkEntry = DuplicateChunk(chunkEntry); - SoundChunk.ChunkId = NewChunkEntry.Id; - - //Add to bundles and link asset - NewChunkEntry.AddedBundles.AddRange(chunkEntry.EnumerateBundles()); - newEntry.LinkAsset(NewChunkEntry); + ChunkAssetEntry newPathfindingChunk = DuplicateChunk(pathfindingChunk); + if (newPathfindingChunk != null) + { + refRoot.Blob.BlobId = new Guid(newPathfindingChunk.Name); + } } - App.AssetManager.ModifyEbx(newEntry.Name, newAsset); - return newEntry; + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); + + return refEntry; } } @@ -331,36 +263,6 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName } } - public class SvgExtension : DuplicateAssetExtension - { - public override string AssetType => "SvgImage"; - - public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) - { - // Duplicate the ebx - EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); - EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); - - // Get the original asset root object data - EbxAsset asset = App.AssetManager.GetEbx(entry); - dynamic textureAsset = asset.RootObject; - - // Get the original chunk and res entries - ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); - SvgImage texture = App.AssetManager.GetResAs(resEntry); - - //Duplicate the res - ResAssetEntry newResAsset = DuplicateRes(resEntry, newName, ResourceType.SvgImage); - - // Modify ebx and res - ((dynamic)newAsset.RootObject).Resource = newResAsset.ResRid; - newEntry.LinkAsset(newResAsset); - App.AssetManager.ModifyEbx(newEntry.Name, newAsset); - - return newEntry; - } - } - public class TextureExtension : DuplicateAssetExtension { public override string AssetType => "TextureBaseAsset"; From da5b400f6e7adff3c9432a531490eacf1a1e45f3 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Wed, 9 Aug 2023 16:29:51 -0700 Subject: [PATCH 03/10] Support for AtlasTextures work on ObjectVariations Added support for atlas texture assets, fixed some issues(primarily it adding res and chunks to a bundle twice and the BPB duplication being set to the wrong bundle type) I have also started work on duplicating Object Variations though that isn't working currently for SWBF2 for other games its fine but for SWBF2 it will work until you change a texture param, if a texture param is modified then I think the SBD breaks, or its broken the moment its created and only corrupts after the texture param is changed something along those lines(perhaps the variation name hash isn't being fed into it correctly, and thats the cause? Should double check everything relating to the variation name hash). Will try to work on this tomorrow when I wake up --- Plugins/AtlasTexturePlugin/AtlasTexture.cs | 297 ++++++++++-------- .../AtlasTexturePlugin.csproj | 159 +++++----- .../DuplicateContextMenuItem.cs | 164 +++++++++- 3 files changed, 396 insertions(+), 224 deletions(-) diff --git a/Plugins/AtlasTexturePlugin/AtlasTexture.cs b/Plugins/AtlasTexturePlugin/AtlasTexture.cs index 00e1d5d12..3cc73f377 100644 --- a/Plugins/AtlasTexturePlugin/AtlasTexture.cs +++ b/Plugins/AtlasTexturePlugin/AtlasTexture.cs @@ -1,129 +1,168 @@ -using FrostySdk; -using FrostySdk.IO; -using FrostySdk.Managers; -using FrostySdk.Resources; -using System; -using System.IO; - -namespace AtlasTexturePlugin -{ - public class AtlasTexture : Resource - { - public ushort Width => width; - public ushort Height => height; - public Stream Data => data; - public Guid ChunkId => chunkId; - public int MipCount - { - get - { - if (ProfilesLibrary.DataVersion != (int)ProfileVersion.StarWarsBattlefrontII) - return 1; - - int tmpCount = 0; - for (int i = 0; i < mipSizes.Length; i++) - { - if (mipSizes[i] > 0) - tmpCount++; - } - return tmpCount; - } - } - - private ushort atlasType; - private ushort width; - private ushort height; - private ushort unknown2; - private float unknown3; - private float unknown4; - private Guid chunkId; - private Stream data; - private uint[] mipSizes = new uint[15]; - - public AtlasTexture() - { - } - - public override void Read(NativeReader reader, AssetManager am, ResAssetEntry entry, ModifiedResource modifiedData) - { - base.Read(reader, am, entry, modifiedData); - atlasType = reader.ReadUShort(); - width = reader.ReadUShort(); - height = reader.ReadUShort(); - unknown2 = reader.ReadUShort(); - unknown3 = reader.ReadFloat(); - unknown4 = reader.ReadFloat(); - chunkId = reader.ReadGuid(); - - if (ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) - { - for (int i = 0; i < 15; i++) - mipSizes[i] = reader.ReadUInt(); - } - - data = am.GetChunk(am.GetChunkEntry(chunkId)); - } - - public AtlasTexture(AtlasTexture other) - { - atlasType = other.atlasType; - unknown2 = other.unknown2; - unknown3 = other.unknown3; - unknown4 = other.unknown4; - chunkId = other.chunkId; - data = other.data; - } - - public void SetData(int w, int h, Guid newChunkId, AssetManager am) - { - width = (ushort)w; - height = (ushort)h; - chunkId = newChunkId; - data = am.GetChunk(am.GetChunkEntry(chunkId)); - - if (ProfilesLibrary.DataVersion != (int)ProfileVersion.StarWarsBattlefrontII) - return; - - uint totalSize = (uint)data.Length; - int stride = 4; - - for (int i = 0; i < 15; i++) - { - if (totalSize > 0) - { - w = Math.Max(1, w); - h = Math.Max(1, h); - - uint mipSize = (uint)(Math.Max(1, ((w + 3) / 4)) * stride * h); - mipSizes[i] = mipSize; - - totalSize -= mipSize; - w >>= 1; - h >>= 1; - } - } - } - - public override byte[] SaveBytes() - { - using (NativeWriter writer = new NativeWriter(new MemoryStream())) - { - writer.Write(atlasType); - writer.Write(width); - writer.Write(height); - writer.Write(unknown2); - writer.Write(unknown3); - writer.Write(unknown4); - writer.Write(chunkId); - - if (ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) - { - for (int i = 0; i < 15; i++) - writer.Write(mipSizes[i]); - } - - return writer.ToByteArray(); - } - } - } -} +using FrostySdk; +using FrostySdk.IO; +using FrostySdk.Managers; +using FrostySdk.Resources; +using System; +using System.IO; + +namespace AtlasTexturePlugin +{ + [Flags] + public enum AtlasFlags : ushort + { + NormalMap = 1, + PerFrameBorder = 2, + LightCookie = 4, + LightPrefilteredCookie = 8 + } + + public class AtlasTexture : Resource + { + public ushort Width => m_width; + public ushort Height => m_height; + public Stream Data => m_data; + public Guid ChunkId => m_chunkId; + public int MipCount + { + get + { + if (m_version < 3) + return 1; + + for (int i = 0; i < m_mipSizes.Length; i++) + { + if (m_mipSizes[i] == 0) + return i; + } + return m_mipSizes.Length; + } + } + public int Version => m_version; + public AtlasFlags AtlasType => m_atlasFlags; + public uint NameHash => m_nameHash; + public ushort Unknown => m_unknown; + public float BorderWidth => m_borderWidth; + public float BorderHeight => m_borderHeight; + + + private int m_version; + private uint m_nameHash; + private AtlasFlags m_atlasFlags; + private ushort m_width; + private ushort m_height; + private ushort m_unknown; + private float m_borderWidth; + private float m_borderHeight; + private Guid m_chunkId; + private Stream m_data; + private uint[] m_mipSizes = new uint[15]; + + public AtlasTexture() + { + } + + public override void Read(NativeReader reader, AssetManager am, ResAssetEntry entry, ModifiedResource modifiedData) + { + base.Read(reader, am, entry, modifiedData); + + m_version = BitConverter.ToInt32(resMeta, 0); + m_nameHash = BitConverter.ToUInt32(resMeta, 4); + + m_atlasFlags = (AtlasFlags)reader.ReadUShort(); + m_width = reader.ReadUShort(); + m_height = reader.ReadUShort(); + m_unknown = reader.ReadUShort(); + m_borderWidth = reader.ReadFloat(); + m_borderHeight = reader.ReadFloat(); + m_chunkId = reader.ReadGuid(); + + if (m_version >= 3) + { + for (int i = 0; i < 15; i++) + m_mipSizes[i] = reader.ReadUInt(); + } + + m_data = am.GetChunk(am.GetChunkEntry(m_chunkId)); + } + + public AtlasTexture(AtlasTexture other) + { + m_version = other.m_version; + m_nameHash = other.m_nameHash; + m_atlasFlags = other.m_atlasFlags; + m_unknown = other.m_unknown; + m_borderWidth = other.m_borderWidth; + m_borderHeight = other.m_borderHeight; + m_chunkId = other.m_chunkId; + m_data = other.m_data; + resMeta = other.resMeta; + } + + public void SetData(int w, int h, Guid newChunkId, AssetManager am) + { + m_width = (ushort)w; + m_height = (ushort)h; + m_chunkId = newChunkId; + m_data = am.GetChunk(am.GetChunkEntry(m_chunkId)); + + uint totalSize = (uint)m_data.Length; + int stride = 4; + + if (m_version >= 3) + { + for (int i = 0; i < 15; i++) + { + if (totalSize > 0) + { + w = Math.Max(1, w); + h = Math.Max(1, h); + + uint mipSize = (uint)(Math.Max(1, ((w + 3) / 4)) * stride * h); + m_mipSizes[i] = mipSize; + + totalSize -= mipSize; + w >>= 1; + h >>= 1; + } + } + } + } + + public void SetNameHash(uint nameHash) + { + m_nameHash = nameHash; + } + + public override byte[] SaveBytes() + { + using (NativeWriter writer = new NativeWriter(new MemoryStream())) + { + writer.Write((ushort)m_atlasFlags); + writer.Write(m_width); + writer.Write(m_height); + writer.Write(m_unknown); + writer.Write(m_borderWidth); + writer.Write(m_borderHeight); + writer.Write(m_chunkId); + + if (m_version >= 3) + { + for (int i = 0; i < 15; i++) + writer.Write(m_mipSizes[i]); + } + + unsafe + { + // update the res meta + fixed (byte* ptr = &resMeta[0]) + { + *(int*)(ptr + 0) = m_version; + *(uint*)(ptr + 4) = m_nameHash; + } + } + + return writer.ToByteArray(); + } + } + } +} diff --git a/Plugins/AtlasTexturePlugin/AtlasTexturePlugin.csproj b/Plugins/AtlasTexturePlugin/AtlasTexturePlugin.csproj index b2f3b3812..b28f636a9 100644 --- a/Plugins/AtlasTexturePlugin/AtlasTexturePlugin.csproj +++ b/Plugins/AtlasTexturePlugin/AtlasTexturePlugin.csproj @@ -1,80 +1,81 @@ - - - Developer - Debug;Release - Alpha;Release - Beta;Release - Final - x64 - net48 - AtlasTexturePlugin - AtlasTexturePlugin - Copyright © 2020 - MinimumRecommendedRules.ruleset - false - true - Library - - - - true - bin\Developer\Debug\ - DEBUG;TRACE - - - - bin\Release\Alpha\ - TRACE - true - - - - bin\Release\Beta\ - TRACE - true - - - - bin\Release\Final\ - TRACE - true - - - - - - - - - ..\..\FrostyEditor\ThirdParty\SharpDX.dll - False - - - ..\..\FrostyEditor\ThirdParty\SharpDX.D3DCompiler.dll - False - - - ..\..\FrostyEditor\ThirdParty\SharpDX.Direct3D11.dll - False - - - ..\..\FrostyEditor\ThirdParty\SharpDX.DXGI.dll - False - - - ..\..\FrostyEditor\ThirdParty\SharpDX.Mathematics.dll - False - - - - - - false - - - false - - - false - - - false - - + + + Developer - Debug;Release - Alpha;Release - Beta;Release - Final + x64 + net48 + AtlasTexturePlugin + AtlasTexturePlugin + Copyright © 2020 + MinimumRecommendedRules.ruleset + false + true + Library + true + + + + true + bin\Developer\Debug\ + DEBUG;TRACE + + + + bin\Release\Alpha\ + TRACE + true + + + + bin\Release\Beta\ + TRACE + true + + + + bin\Release\Final\ + TRACE + true + + + + + + + + + ..\..\FrostyEditor\ThirdParty\SharpDX.dll + False + + + ..\..\FrostyEditor\ThirdParty\SharpDX.D3DCompiler.dll + False + + + ..\..\FrostyEditor\ThirdParty\SharpDX.Direct3D11.dll + False + + + ..\..\FrostyEditor\ThirdParty\SharpDX.DXGI.dll + False + + + ..\..\FrostyEditor\ThirdParty\SharpDX.Mathematics.dll + False + + + + + + false + + + false + + + false + + + false + + \ No newline at end of file diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index 0681c5512..bb6ca0bf6 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -92,7 +92,7 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); // Add new bundle - BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.SubLevel, 0); + BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.BlueprintBundle, 0); newEntry.AddedBundles.Clear(); newEntry.AddedBundles.Add(App.AssetManager.GetBundleId(newBundle)); @@ -165,6 +165,151 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName } } + public class ObjectVariationExtension : DuplicateAssetExtension + { + public override string AssetType => "ObjectVariation"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + EbxAssetEntry ebxAssetEntry = base.DuplicateAsset(entry, newName, createNew, newType); + if (ebxAssetEntry == null) + { + return null; + } + EbxAsset ebx = App.AssetManager.GetEbx(ebxAssetEntry); + dynamic rootObject = ebx.RootObject; + + //SWBF2 has fancy res files for object variations, we need to dupe these + if (ProfilesLibrary.IsLoaded(ProfileVersion.StarWarsBattlefrontII)) + { + // Get the original name hash, this will be useful for when we change it + uint nameHash = rootObject.NameHash; + + foreach (MeshVariation mv in MeshVariationDb.FindVariations(nameHash)) + { + // Get meshSet + EbxAssetEntry meshEntry = App.AssetManager.GetEbxEntry(mv.MeshGuid); + EbxAsset meshAsset = App.AssetManager.GetEbx(meshEntry); + dynamic meshRoot = meshAsset.RootObject; + ResAssetEntry meshRes = App.AssetManager.GetResEntry(meshRoot.MeshSetResource); + MeshSet meshSet = App.AssetManager.GetResAs(meshRes); + + foreach (object matObject in ebx.RootObjects) // For each material in the new variation + { + // Check if this is actually a material + if (TypeLibrary.IsSubClassOf(matObject.GetType(), "MeshMaterialVariation") && ((dynamic)matObject).Shader.TextureParameters.Count == 0) + { + dynamic MatProperties = matObject as dynamic; + + AssetClassGuid guid = MatProperties.GetInstanceGuid(); + MeshVariationMaterial mm = null; + + foreach (MeshVariationMaterial mvm in mv.Materials) + { + if (mvm.MaterialVariationClassGuid == guid.ExportedGuid) + { + mm = mvm; + break; + } + } + + if (mm != null) + { + dynamic texParams = mm.TextureParameters; + foreach (dynamic param in texParams) + MatProperties.Shader.TextureParameters.Add(param); + } + } + } + + // Dupe sbd + ResAssetEntry resEntry = App.AssetManager.GetResEntry(entry.Name.ToLower() + "/" + meshEntry.Filename + "_" + (uint)Utils.HashString(meshEntry.Name, true) + "/shaderblocks_variation/blocks"); + ResAssetEntry newResEntry = DuplicateRes(resEntry, newName.ToLower() + "/" + meshEntry.Filename + "_" + (uint)Utils.HashString(meshEntry.Name, true) + "/shaderblocks_variation/blocks", ResourceType.ShaderBlockDepot); + ShaderBlockDepot newShaderBlockDepot = App.AssetManager.GetResAs(newResEntry); + + // change namehash so the sbd hash can be calculated corretcly + nameHash = (uint)Utils.HashString(newName, true); + rootObject.NameHash = nameHash; + + for (int i = 0; i < newShaderBlockDepot.ResourceCount; i++) + { + ShaderBlockResource res = newShaderBlockDepot.GetResource(i); + if (!(res is MeshParamDbBlock)) + res.ChangeHash(nameHash); + } + + // Change the references in the sbd + for (int lod = 0; lod < meshSet.Lods.Count; lod++) + { + ShaderBlockEntry sbEntry = newShaderBlockDepot.GetSectionEntry(lod); + ShaderBlockMeshVariationEntry sbMvEntry = newShaderBlockDepot.GetResource(sbEntry.Index + 1) as ShaderBlockMeshVariationEntry; + + sbEntry.SetHash(meshSet.NameHash, nameHash, lod); + sbMvEntry.SetHash(meshSet.NameHash, nameHash, lod); + } + + App.AssetManager.ModifyRes(newResEntry.Name, newShaderBlockDepot); + ebxAssetEntry.LinkAsset(newResEntry); + App.AssetManager.ModifyEbx(newName, ebx); + + break; + } + } + + //Other games just need the namehash, very simple! + else + { + //The NameHash needs to be the 32 bit Fnv1 of the lowercased name + rootObject.NameHash = (uint)Utils.HashString(newName, true); + } + + return ebxAssetEntry; + } + } + + public class AtlasTextureExtension : DuplicateAssetExtension + { + public override string AssetType => "AtlasTextureAsset"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + EbxAsset asset = App.AssetManager.GetEbx(entry); + dynamic textureAsset = asset.RootObject; + + // Get the original chunk and res entries + ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); + AtlasTexture texture = App.AssetManager.GetResAs(resEntry); + ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); + + // Duplicate the chunk + ChunkAssetEntry newChunkEntry = DuplicateChunk(chunkEntry); + + // Duplicate the res + ResAssetEntry newResEntry = DuplicateRes(resEntry, newName, ResourceType.AtlasTexture); + ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; + AtlasTexture newTexture = App.AssetManager.GetResAs(newResEntry); + + // Set the data in the Atlas Texture + newTexture.SetData(texture.Width, texture.Height, newChunkEntry.Id, App.AssetManager); + newTexture.SetNameHash((uint)Utils.HashString($"Output/Win32/{newResEntry.Name}.res", true)); + + // Link the newly duplicated ebx, chunk, and res entries together + newResEntry.LinkAsset(newChunkEntry); + newEntry.LinkAsset(newResEntry); + + // Modify ebx and res + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + App.AssetManager.ModifyRes(newResEntry.Name, newTexture); + + return newEntry; + } + } + public class MeshExtension : DuplicateAssetExtension { public override string AssetType => "MeshAsset"; @@ -196,7 +341,7 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName { lod.Name = newResAsset.Name; //Double check that the lod actually has a chunk id. If it doesn't this means the data is inline and we don't need to worry - if (lod.ChunkId != null && lod.ChunkId != Guid.Empty) + if (lod.ChunkId != Guid.Empty) { //Get the original chunk and dupe it ChunkAssetEntry chunk = App.AssetManager.GetChunkEntry(lod.ChunkId); @@ -380,10 +525,6 @@ public static ChunkAssetEntry DuplicateChunk(ChunkAssetEntry entry, Texture text } ChunkAssetEntry newEntry = App.AssetManager.GetChunkEntry(newGuid); - foreach (int bId in entry.Bundles) - { - newEntry.AddToBundle(bId); - } App.Logger.Log(string.Format("Duped chunk {0} to {1}", entry.Name, newGuid)); return newEntry; @@ -394,20 +535,11 @@ public static ResAssetEntry DuplicateRes(ResAssetEntry entry, string name, Resou if (App.AssetManager.GetResEntry(name) == null) { ResAssetEntry newEntry; - using (NativeReader reader = new NativeReader(App.AssetManager.GetRes(entry))) //ToDo: figure out why the hell this isn't adding res to bundles + using (NativeReader reader = new NativeReader(App.AssetManager.GetRes(entry))) { newEntry = App.AssetManager.AddRes(name, resType, entry.ResMeta, reader.ReadToEnd(), entry.EnumerateBundles().ToArray()); } - //Hacky workaround to fix this not actually adding to bundles - if (newEntry.Bundles.Count == 0) - { - foreach (int bId in entry.Bundles) - { - newEntry.AddToBundle(bId); - } - } - App.Logger.Log(string.Format("Duped res {0} to {1}", entry.Name, newEntry.Name)); return newEntry; } From e391b6dbc140f9797c10d28a029f936390b2fe6b Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Thu, 10 Aug 2023 06:54:11 -0700 Subject: [PATCH 04/10] Fix FindVariations not finding modified entries This adds in support for FindVariations not being able to search for modified Variations, as well as adds a bool to toggle this(so if you have a off case where you explicitly don't want modified/duped variations you can tell it not to) --- FrostyPlugin/Viewport/MeshVariationDb.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/FrostyPlugin/Viewport/MeshVariationDb.cs b/FrostyPlugin/Viewport/MeshVariationDb.cs index 833542d59..76962b5ec 100644 --- a/FrostyPlugin/Viewport/MeshVariationDb.cs +++ b/FrostyPlugin/Viewport/MeshVariationDb.cs @@ -352,8 +352,17 @@ public static MeshVariationDbEntry GetVariations(string name) return entry == null ? null : GetVariations(entry.Guid); } - public static IEnumerable FindVariations(uint hash) - { + public static IEnumerable FindVariations(uint hash, bool excludeModified = false) + { + if (!excludeModified) + { + foreach (MeshVariationDbEntry entry in ModifiedEntries.Values) + { + if (entry.ContainsVariation(hash)) + yield return entry.GetVariation(hash); + } + } + foreach (MeshVariationDbEntry entry in entries.Values) { if (entry.ContainsVariation(hash)) From 9b1dde32703bd4d0209dd9201519dc7103f13e15 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Thu, 10 Aug 2023 06:57:22 -0700 Subject: [PATCH 05/10] Finalize support for object variations This allows object variations to be fully duplicated, The Object Variation editor was modified so that it could support writing to new variations(using my previous commit) also added in a no valid MVDB error so when you dupe a object variation and don't add it to MVDBs it'll complain Also addressed requests cited --- .../DuplicateContextMenuItem.cs | 167 +++---- .../FrostyObjectVariationEditor.cs | 410 +++++++++--------- .../ObjectVariationAssetDefinition.cs | 42 +- .../ObjectVariationPlugin.csproj | 126 +++--- 4 files changed, 364 insertions(+), 381 deletions(-) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index bb6ca0bf6..99e87690c 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -10,7 +10,6 @@ using FrostySdk.Managers; using FrostySdk.Resources; using MeshSetPlugin.Resources; -using SvgImagePlugin; using System; using System.Collections.Generic; using System.IO; @@ -33,13 +32,11 @@ public class SvgImageExtension : DuplicateAssetExtension public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); - if (refEntry == null) - return null; + EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); dynamic refRoot = refAsset.RootObject; ResAssetEntry resEntry = App.AssetManager.GetResEntry(refRoot.Resource); - SvgImage svgTexture = App.AssetManager.GetResAs(resEntry); ResAssetEntry newResEntry = DuplicateRes(resEntry, refEntry.Name, ResourceType.SvgImage); if (newResEntry != null) @@ -59,22 +56,15 @@ public class SoundWaveExtension : DuplicateAssetExtension public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); - if (refEntry == null) - return null; + EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); dynamic refRoot = refAsset.RootObject; foreach (dynamic chkref in refRoot.Chunks) { ChunkAssetEntry soundChunk = App.AssetManager.GetChunkEntry(chkref.ChunkId); - if (soundChunk != null) - { - ChunkAssetEntry newSoundChunk = DuplicateChunk(soundChunk); - if (newSoundChunk != null) - { - chkref.ChunkId = new Guid(newSoundChunk.Name); - } - } + ChunkAssetEntry newSoundChunk = DuplicateChunk(soundChunk); + chkref.ChunkId = new Guid(newSoundChunk.Name); } App.AssetManager.ModifyEbx(refEntry.Name, refAsset); @@ -117,20 +107,10 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName newEntry.AddedBundles.Clear(); newEntry.AddedBundles.Add(App.AssetManager.GetBundleId(newBundle)); - //App.Logger.Log(App.AssetManager.GetBundleEntry(entry.Bundles[0]).SuperBundleId.ToString()); newBundle.Blueprint = newEntry; newEntry.LinkAsset(entry); - //List assets = new List(); - //assets.AddRange(App.AssetManager.EnumerateEbx()); - //assets.AddRange(App.AssetManager.EnumerateRes()); - //assets.AddRange(App.AssetManager.EnumerateChunks()); - - //foreach (AssetEntry asset in assets) - // if (asset.IsInBundle(entry.Bundles[0]) && asset != entry) - // asset.AddToBundle(App.AssetManager.GetBundleId(newBundle)); - return newEntry; } } @@ -143,9 +123,6 @@ public class PathfindingExtension : DuplicateAssetExtension public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); - if (refEntry == null) - return null; - EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); dynamic refRoot = refAsset.RootObject; @@ -171,99 +148,93 @@ public class ObjectVariationExtension : DuplicateAssetExtension public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { - EbxAssetEntry ebxAssetEntry = base.DuplicateAsset(entry, newName, createNew, newType); - if (ebxAssetEntry == null) - { - return null; - } - EbxAsset ebx = App.AssetManager.GetEbx(ebxAssetEntry); - dynamic rootObject = ebx.RootObject; + EbxAssetEntry newAssetEntry = base.DuplicateAsset(entry, newName, createNew, newType); - //SWBF2 has fancy res files for object variations, we need to dupe these + //Get the ebx and root object from our duped entry + EbxAsset newEbx = App.AssetManager.GetEbx(newAssetEntry); + dynamic newRootObject = newEbx.RootObject; + + // Get the ebx and root object from the original entry + EbxAsset oldEbx = App.AssetManager.GetEbx(entry); + dynamic oldRootObject = oldEbx.RootObject; + + // The NameHash needs to be the 32 bit Fnv1 of the lowercased name + newRootObject.NameHash = (uint)Utils.HashString(newName, true); + + // SWBF2 has fancy res files for object variations, we need to dupe these. Other games just need the namehash if (ProfilesLibrary.IsLoaded(ProfileVersion.StarWarsBattlefrontII)) { // Get the original name hash, this will be useful for when we change it - uint nameHash = rootObject.NameHash; + uint nameHash = oldRootObject.NameHash; - foreach (MeshVariation mv in MeshVariationDb.FindVariations(nameHash)) + // find a Mesh Variation entry with our NameHash + MeshVariation meshVariation = MeshVariationDb.FindVariations(nameHash, true).First(); + + // Get meshSet + EbxAssetEntry meshEntry = App.AssetManager.GetEbxEntry(meshVariation.MeshGuid); + EbxAsset meshAsset = App.AssetManager.GetEbx(meshEntry); + dynamic meshRoot = meshAsset.RootObject; + ResAssetEntry meshRes = App.AssetManager.GetResEntry(meshRoot.MeshSetResource); + MeshSet meshSet = App.AssetManager.GetResAs(meshRes); + + foreach (object matObject in newEbx.RootObjects) // For each material in the new variation { - // Get meshSet - EbxAssetEntry meshEntry = App.AssetManager.GetEbxEntry(mv.MeshGuid); - EbxAsset meshAsset = App.AssetManager.GetEbx(meshEntry); - dynamic meshRoot = meshAsset.RootObject; - ResAssetEntry meshRes = App.AssetManager.GetResEntry(meshRoot.MeshSetResource); - MeshSet meshSet = App.AssetManager.GetResAs(meshRes); - - foreach (object matObject in ebx.RootObjects) // For each material in the new variation + // Check if this is actually a material + if (TypeLibrary.IsSubClassOf(matObject.GetType(), "MeshMaterialVariation") && ((dynamic)matObject).Shader.TextureParameters.Count == 0) { - // Check if this is actually a material - if (TypeLibrary.IsSubClassOf(matObject.GetType(), "MeshMaterialVariation") && ((dynamic)matObject).Shader.TextureParameters.Count == 0) - { - dynamic MatProperties = matObject as dynamic; + dynamic matProperties = matObject as dynamic; - AssetClassGuid guid = MatProperties.GetInstanceGuid(); - MeshVariationMaterial mm = null; + AssetClassGuid guid = matProperties.GetInstanceGuid(); + MeshVariationMaterial mm = null; - foreach (MeshVariationMaterial mvm in mv.Materials) - { - if (mvm.MaterialVariationClassGuid == guid.ExportedGuid) - { - mm = mvm; - break; - } - } - - if (mm != null) + foreach (MeshVariationMaterial mvm in meshVariation.Materials) + { + if (mvm.MaterialVariationClassGuid == guid.ExportedGuid) { - dynamic texParams = mm.TextureParameters; - foreach (dynamic param in texParams) - MatProperties.Shader.TextureParameters.Add(param); + mm = mvm; + break; } } - } - // Dupe sbd - ResAssetEntry resEntry = App.AssetManager.GetResEntry(entry.Name.ToLower() + "/" + meshEntry.Filename + "_" + (uint)Utils.HashString(meshEntry.Name, true) + "/shaderblocks_variation/blocks"); - ResAssetEntry newResEntry = DuplicateRes(resEntry, newName.ToLower() + "/" + meshEntry.Filename + "_" + (uint)Utils.HashString(meshEntry.Name, true) + "/shaderblocks_variation/blocks", ResourceType.ShaderBlockDepot); - ShaderBlockDepot newShaderBlockDepot = App.AssetManager.GetResAs(newResEntry); - - // change namehash so the sbd hash can be calculated corretcly - nameHash = (uint)Utils.HashString(newName, true); - rootObject.NameHash = nameHash; - - for (int i = 0; i < newShaderBlockDepot.ResourceCount; i++) - { - ShaderBlockResource res = newShaderBlockDepot.GetResource(i); - if (!(res is MeshParamDbBlock)) - res.ChangeHash(nameHash); + if (mm != null) + { + dynamic texParams = mm.TextureParameters; + foreach (dynamic param in texParams) + matProperties.Shader.TextureParameters.Add(param); + } } + } - // Change the references in the sbd - for (int lod = 0; lod < meshSet.Lods.Count; lod++) - { - ShaderBlockEntry sbEntry = newShaderBlockDepot.GetSectionEntry(lod); - ShaderBlockMeshVariationEntry sbMvEntry = newShaderBlockDepot.GetResource(sbEntry.Index + 1) as ShaderBlockMeshVariationEntry; + // Dupe sbd + ResAssetEntry resEntry = App.AssetManager.GetResEntry(entry.Name.ToLower() + "/" + meshEntry.Filename + "_" + (uint)Utils.HashString(meshEntry.Name, true) + "/shaderblocks_variation/blocks"); + ResAssetEntry newResEntry = DuplicateRes(resEntry, newName.ToLower() + "/" + meshEntry.Filename + "_" + (uint)Utils.HashString(meshEntry.Name, true) + "/shaderblocks_variation/blocks", ResourceType.ShaderBlockDepot); + ShaderBlockDepot newShaderBlockDepot = App.AssetManager.GetResAs(newResEntry); - sbEntry.SetHash(meshSet.NameHash, nameHash, lod); - sbMvEntry.SetHash(meshSet.NameHash, nameHash, lod); - } + for (int i = 0; i < newShaderBlockDepot.ResourceCount; i++) + { + ShaderBlockResource res = newShaderBlockDepot.GetResource(i); + if (!(res is MeshParamDbBlock)) + res.ChangeHash(newRootObject.NameHash); + } - App.AssetManager.ModifyRes(newResEntry.Name, newShaderBlockDepot); - ebxAssetEntry.LinkAsset(newResEntry); - App.AssetManager.ModifyEbx(newName, ebx); + // Change the references in the sbd + for (int lod = 0; lod < meshSet.Lods.Count; lod++) + { + ShaderBlockEntry sbEntry = newShaderBlockDepot.GetSectionEntry(lod); + ShaderBlockMeshVariationEntry sbMvEntry = newShaderBlockDepot.GetResource(sbEntry.Index + 1) as ShaderBlockMeshVariationEntry; - break; + sbEntry.SetHash(meshSet.NameHash, newRootObject.NameHash, lod); + sbMvEntry.SetHash(meshSet.NameHash, newRootObject.NameHash, lod); } - } - //Other games just need the namehash, very simple! - else - { - //The NameHash needs to be the 32 bit Fnv1 of the lowercased name - rootObject.NameHash = (uint)Utils.HashString(newName, true); + App.AssetManager.ModifyRes(newResEntry.Name, newShaderBlockDepot); + newAssetEntry.LinkAsset(newResEntry); } - return ebxAssetEntry; + App.Logger.Log("Duped {0} with a namehash of {1}", newAssetEntry.Filename, newRootObject.NameHash.ToString()); + + App.AssetManager.ModifyEbx(newName, newEbx); + return newAssetEntry; } } diff --git a/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs b/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs index 7c9c3a58a..64c90aaee 100644 --- a/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs +++ b/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs @@ -1,199 +1,211 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using Frosty.Core.Viewport; -using FrostySdk; -using FrostySdk.Ebx; -using FrostySdk.Interfaces; -using FrostySdk.IO; -using FrostySdk.Managers; -using Frosty.Hash; -using Frosty.Core.Controls; -using Frosty.Core.Windows; -using Frosty.Core; -using MeshSetPlugin.Resources; - -namespace ObjectVariationPlugin -{ - [TemplatePart(Name = PART_AssetPropertyGrid, Type = typeof(FrostyPropertyGrid))] - public class FrostyObjectVariationEditor : FrostyAssetEditor - { - private const string PART_AssetPropertyGrid = "PART_AssetPropertyGrid"; - - private FrostyPropertyGrid pgAsset; - - private bool firstTimeLoad = true; - private List shaderBlockDepots = new List(); - private List meshVariations = new List(); - - public FrostyObjectVariationEditor(ILogger inLogger) - : base(inLogger) - { - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - pgAsset = GetTemplateChild(PART_AssetPropertyGrid) as FrostyPropertyGrid; - pgAsset.OnModified += PgAsset_OnModified; - - Loaded += FrostyObjectVariationEditor_Loaded; - } - - private void PgAsset_OnModified(object sender, ItemModifiedEventArgs e) - { - if (ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) - { - for (int i = 0; i < meshVariations.Count; i++) - { - EbxAsset meshEbx = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(meshVariations[i].MeshGuid)); - dynamic meshObj = meshEbx.RootObject; - - // load mesh asset associated with this variation - MeshSet meshAsset = App.AssetManager.GetResAs(App.AssetManager.GetResEntry(meshObj.MeshSetResource)); - - // iterate mesh lods - for (int j = 0; j < meshAsset.Lods.Count; j++) - { - var sbe = shaderBlockDepots[i].GetSectionEntry(j); - - // iterate mesh sections - for (int k = 0; k < meshAsset.Lods[j].Sections.Count; k++) - { - var texturesBlock = sbe.GetTextureParams(k); - var paramsBlock = sbe.GetParams(k); - - dynamic material = meshObj.Materials[meshAsset.Lods[j].Sections[k].MaterialId].Internal; - AssetClassGuid materialGuid = material.GetInstanceGuid(); - - // search mesh variation for appropriate variation material - foreach (var meshVariationMaterial in meshVariations[i].Materials) - { - if (meshVariationMaterial.MaterialGuid == materialGuid.ExportedGuid) - { - dynamic mvMaterial = Asset.GetObject(meshVariationMaterial.MaterialVariationClassGuid); - foreach (dynamic param in mvMaterial.Shader.BoolParameters) - { - string paramName = param.ParameterName; - bool value = param.Value; - - paramsBlock.SetParameterValue(paramName, value); - } - foreach (dynamic param in mvMaterial.Shader.VectorParameters) - { - string paramName = param.ParameterName; - dynamic vec = param.Value; - - paramsBlock.SetParameterValue(paramName, new float[] { vec.x, vec.y, vec.z, vec.w }); - } - foreach (dynamic param in mvMaterial.Shader.ConditionalParameters) - { - string value = param.Value; - PointerRef assetRef = param.ConditionalAsset; - - if (assetRef.Type == PointerRefType.External) - { - EbxAsset asset = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(assetRef.External.FileGuid)); - dynamic conditionalAsset = asset.RootObject; - - string conditionName = conditionalAsset.ConditionName; - byte idx = (byte)conditionalAsset.Branches.IndexOf(value); - - paramsBlock.SetParameterValue(conditionName, idx); - } - } - foreach (dynamic param in mvMaterial.Shader.TextureParameters) - { - string paramName = param.ParameterName; - PointerRef value = param.Value; - - texturesBlock.SetParameterValue(paramName, value.External.ClassGuid); - } - - texturesBlock.IsModified = true; - paramsBlock.IsModified = true; - - break; - } - } - } - } - - // modify the ShaderBlockDepot - App.AssetManager.ModifyRes(shaderBlockDepots[i].ResourceId, shaderBlockDepots[i]); - AssetEntry.LinkAsset(App.AssetManager.GetResEntry(shaderBlockDepots[i].ResourceId)); - } - } - } - - private void FrostyObjectVariationEditor_Loaded(object sender, RoutedEventArgs e) - { - if (firstTimeLoad) - { - if (ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) - { - if (!MeshVariationDb.IsLoaded) - { - FrostyTaskWindow.Show("Loading Variations", "", (task) => - { - MeshVariationDb.LoadVariations(task); - }); - } - - dynamic ebxData = RootObject; - - // store every unique mesh variation for this object variation - foreach (MeshVariation mvEntry in MeshVariationDb.FindVariations(ebxData.NameHash)) - meshVariations.Add(mvEntry); - - foreach (dynamic obj in RootObjects) - { - Type objType = obj.GetType(); - if (TypeLibrary.IsSubClassOf(objType, "MeshMaterialVariation")) - { - // use the first mesh variation to populate the texture parameters - // of this object variation - - if (obj.Shader.TextureParameters.Count == 0) - { - AssetClassGuid guid = obj.GetInstanceGuid(); - MeshVariationMaterial mm = null; - - foreach (MeshVariationMaterial mvm in meshVariations[0].Materials) - { - if (mvm.MaterialVariationClassGuid == guid.ExportedGuid) - { - mm = mvm; - break; - } - } - - if (mm != null) - { - dynamic texParams = mm.TextureParameters; - foreach (dynamic param in texParams) - obj.Shader.TextureParameters.Add(param); - } - } - } - } - - string path = AssetEntry.Name.ToLower(); - foreach (var mv in meshVariations) - { - // using the mesh variation mesh, obtain the relevant ShaderBlockDepot - EbxAssetEntry ebxEntry = App.AssetManager.GetEbxEntry(mv.MeshGuid); - ResAssetEntry resEntry = App.AssetManager.GetResEntry(path + "/" + ebxEntry.Filename + "_" + (uint)Fnv1.HashString(ebxEntry.Name.ToLower()) + "/shaderblocks_variation/blocks"); - if (resEntry != null) - { - shaderBlockDepots.Add(App.AssetManager.GetResAs(resEntry)); - } - } - } - - firstTimeLoad = false; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Windows; +using Frosty.Core.Viewport; +using FrostySdk; +using FrostySdk.Ebx; +using FrostySdk.Interfaces; +using FrostySdk.IO; +using FrostySdk.Managers; +using Frosty.Hash; +using Frosty.Core.Controls; +using Frosty.Core.Windows; +using Frosty.Core; +using MeshSetPlugin.Resources; + +namespace ObjectVariationPlugin +{ + [TemplatePart(Name = PART_AssetPropertyGrid, Type = typeof(FrostyPropertyGrid))] + public class FrostyObjectVariationEditor : FrostyAssetEditor + { + private const string PART_AssetPropertyGrid = "PART_AssetPropertyGrid"; + + private FrostyPropertyGrid pgAsset; + + private bool firstTimeLoad = true; + private List shaderBlockDepots = new List(); + private List meshVariations = new List(); + + public FrostyObjectVariationEditor(ILogger inLogger) + : base(inLogger) + { + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + pgAsset = GetTemplateChild(PART_AssetPropertyGrid) as FrostyPropertyGrid; + pgAsset.OnModified += PgAsset_OnModified; + + Loaded += FrostyObjectVariationEditor_Loaded; + } + + private void PgAsset_OnModified(object sender, ItemModifiedEventArgs e) + { + if (ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) + { + for (int i = 0; i < meshVariations.Count; i++) + { + EbxAsset meshEbx = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(meshVariations[i].MeshGuid)); + dynamic meshObj = meshEbx.RootObject; + + // load mesh asset associated with this variation + MeshSet meshAsset = App.AssetManager.GetResAs(App.AssetManager.GetResEntry(meshObj.MeshSetResource)); + + // iterate mesh lods + for (int j = 0; j < meshAsset.Lods.Count; j++) + { + var sbe = shaderBlockDepots[i].GetSectionEntry(j); + + // iterate mesh sections + for (int k = 0; k < meshAsset.Lods[j].Sections.Count; k++) + { + var texturesBlock = sbe.GetTextureParams(k); + var paramsBlock = sbe.GetParams(k); + + dynamic material = meshObj.Materials[meshAsset.Lods[j].Sections[k].MaterialId].Internal; + AssetClassGuid materialGuid = material.GetInstanceGuid(); + + // search mesh variation for appropriate variation material + foreach (var meshVariationMaterial in meshVariations[i].Materials) + { + if (meshVariationMaterial.MaterialGuid == materialGuid.ExportedGuid) + { + dynamic mvMaterial = Asset.GetObject(meshVariationMaterial.MaterialVariationClassGuid); + foreach (dynamic param in mvMaterial.Shader.BoolParameters) + { + string paramName = param.ParameterName; + bool value = param.Value; + + paramsBlock.SetParameterValue(paramName, value); + } + foreach (dynamic param in mvMaterial.Shader.VectorParameters) + { + string paramName = param.ParameterName; + dynamic vec = param.Value; + + paramsBlock.SetParameterValue(paramName, new float[] { vec.x, vec.y, vec.z, vec.w }); + } + foreach (dynamic param in mvMaterial.Shader.ConditionalParameters) + { + string value = param.Value; + PointerRef assetRef = param.ConditionalAsset; + + if (assetRef.Type == PointerRefType.External) + { + EbxAsset asset = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(assetRef.External.FileGuid)); + dynamic conditionalAsset = asset.RootObject; + + string conditionName = conditionalAsset.ConditionName; + byte idx = (byte)conditionalAsset.Branches.IndexOf(value); + + paramsBlock.SetParameterValue(conditionName, idx); + } + } + foreach (dynamic param in mvMaterial.Shader.TextureParameters) + { + string paramName = param.ParameterName; + PointerRef value = param.Value; + + texturesBlock.SetParameterValue(paramName, value.External.ClassGuid); + } + + texturesBlock.IsModified = true; + paramsBlock.IsModified = true; + + break; + } + } + } + } + + // modify the ShaderBlockDepot + App.AssetManager.ModifyRes(shaderBlockDepots[i].ResourceId, shaderBlockDepots[i]); + AssetEntry.LinkAsset(App.AssetManager.GetResEntry(shaderBlockDepots[i].ResourceId)); + } + } + } + + private void FrostyObjectVariationEditor_Loaded(object sender, RoutedEventArgs e) + { + MeshVariationDb.LoadModifiedVariations(); // make sure we load the modified variations too, that way we don't miss dupes + if (firstTimeLoad) + { + if (!MeshVariationDb.IsLoaded) + { + FrostyTaskWindow.Show("Loading Variations", "", (task) => + { + MeshVariationDb.LoadVariations(task); + }); + } + + dynamic ebxData = RootObject; + + // store every unique mesh variation for this object variation + foreach (MeshVariation mvEntry in MeshVariationDb.FindVariations(ebxData.NameHash)) + { + meshVariations.Add(mvEntry); + } + + foreach (dynamic obj in RootObjects) + { + Type objType = obj.GetType(); + if (TypeLibrary.IsSubClassOf(objType, "MeshMaterialVariation")) + { + // use the first mesh variation to populate the texture parameters + // of this object variation + + if (obj.Shader.TextureParameters.Count == 0) + { + AssetClassGuid guid = obj.GetInstanceGuid(); + MeshVariationMaterial mm = null; + + foreach (MeshVariationMaterial mvm in meshVariations[0].Materials) + { + if (mvm.MaterialVariationClassGuid == guid.ExportedGuid) + { + mm = mvm; + break; + } + } + + if (mm != null) + { + dynamic texParams = mm.TextureParameters; + foreach (dynamic param in texParams) + obj.Shader.TextureParameters.Add(param); + } + } + } + } + + string path = AssetEntry.Name.ToLower(); + foreach (var mv in meshVariations) + { + // using the mesh variation mesh, obtain the relevant ShaderBlockDepot + EbxAssetEntry ebxEntry = App.AssetManager.GetEbxEntry(mv.MeshGuid); + ResAssetEntry resEntry = App.AssetManager.GetResEntry(path + "/" + ebxEntry.Filename + "_" + (uint)Fnv1.HashString(ebxEntry.Name.ToLower()) + "/shaderblocks_variation/blocks"); + if (resEntry != null) + { + shaderBlockDepots.Add(App.AssetManager.GetResAs(resEntry)); + } + } + + //Double check that our data isn't incorrect + if (meshVariations.Count > 0 && ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) + { + firstTimeLoad = false; + } + + //If it is incorrect, then this data is rubbish and next time the asset is opened we should completely redo. + else + { + firstTimeLoad = true; + MeshVariationDb.IsLoaded = false; + App.Logger.LogError("Cannot find any Mesh Variation Databases which have this Object Variation. If the asset is duped, please add it to Mesh Variation Databases."); + } + } + } + } +} diff --git a/Plugins/ObjectVariationPlugin/ObjectVariationAssetDefinition.cs b/Plugins/ObjectVariationPlugin/ObjectVariationAssetDefinition.cs index a7bd2a790..5329b0a5f 100644 --- a/Plugins/ObjectVariationPlugin/ObjectVariationAssetDefinition.cs +++ b/Plugins/ObjectVariationPlugin/ObjectVariationAssetDefinition.cs @@ -1,21 +1,21 @@ -using Frosty.Core; -using Frosty.Core.Controls; -using FrostySdk.Interfaces; -using System.Windows.Media; - -namespace ObjectVariationPlugin -{ - public class ObjectVariationAssetDefinition : AssetDefinition - { - protected static ImageSource imageSource = new ImageSourceConverter().ConvertFromString("pack://application:,,,/ObjectVariationPlugin;component/Images/ObjectVariationFileType.png") as ImageSource; - public override ImageSource GetIcon() - { - return imageSource; - } - - public override FrostyAssetEditor GetEditor(ILogger logger) - { - return new FrostyObjectVariationEditor(logger); - } - } -} +using Frosty.Core; +using Frosty.Core.Controls; +using FrostySdk.Interfaces; +using System.Windows.Media; + +namespace ObjectVariationPlugin +{ + public class ObjectVariationAssetDefinition : AssetDefinition + { + protected static ImageSource imageSource = new ImageSourceConverter().ConvertFromString("pack://application:,,,/ObjectVariationPlugin;component/Images/ObjectVariationFileType.png") as ImageSource; + public override ImageSource GetIcon() + { + return imageSource; + } + + public override FrostyAssetEditor GetEditor(ILogger logger) + { + return new FrostyObjectVariationEditor(logger); + } + } +} diff --git a/Plugins/ObjectVariationPlugin/ObjectVariationPlugin.csproj b/Plugins/ObjectVariationPlugin/ObjectVariationPlugin.csproj index ee67c67e4..841871a08 100644 --- a/Plugins/ObjectVariationPlugin/ObjectVariationPlugin.csproj +++ b/Plugins/ObjectVariationPlugin/ObjectVariationPlugin.csproj @@ -1,64 +1,64 @@ - - - Developer - Debug;Release - Alpha;Release - Beta;Release - Final - x64 - net48 - ObjectVariationPlugin - ObjectVariationPlugin - Copyright © 2020 - MinimumRecommendedRules.ruleset - false - true - Library - - - - true - bin\Developer\Debug\ - DEBUG;TRACE - - - - bin\Release\Alpha\ - TRACE - true - - - - bin\Release\Beta\ - TRACE - true - - - - bin\Release\Final\ - TRACE - true - - - - - - - - - false - - - false - - - false - - - false - - - false - - - - - - + + + Developer - Debug;Release - Alpha;Release - Beta;Release - Final + x64 + net48 + ObjectVariationPlugin + ObjectVariationPlugin + Copyright © 2020 + MinimumRecommendedRules.ruleset + false + true + Library + + + + true + bin\Developer\Debug\ + DEBUG;TRACE + + + + bin\Release\Alpha\ + TRACE + true + + + + bin\Release\Beta\ + TRACE + true + + + + bin\Release\Final\ + TRACE + true + + + + + + + + + false + + + false + + + false + + + false + + + false + + + + + + \ No newline at end of file From 380c28165878ef5dbecc5dc6703ed04f0715c504 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:49:14 -0700 Subject: [PATCH 06/10] Add support for cloth Add duplication support for the 2 cloth types --- .../DuplicateContextMenuItem.cs | 214 ++++++++++++------ 1 file changed, 139 insertions(+), 75 deletions(-) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index 99e87690c..9971fbb36 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -25,9 +25,9 @@ public class DuplicationTool { #region --Extensions-- - public class SvgImageExtension : DuplicateAssetExtension + public class SoundWaveExtension : DuplicateAssetExtension { - public override string AssetType => "SvgImage"; + public override string AssetType => "SoundWaveAsset"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { @@ -36,22 +36,22 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); dynamic refRoot = refAsset.RootObject; - ResAssetEntry resEntry = App.AssetManager.GetResEntry(refRoot.Resource); - - ResAssetEntry newResEntry = DuplicateRes(resEntry, refEntry.Name, ResourceType.SvgImage); - if (newResEntry != null) + foreach (dynamic chkref in refRoot.Chunks) { - refRoot.Resource = newResEntry.ResRid; - App.AssetManager.ModifyEbx(refEntry.Name, refAsset); + ChunkAssetEntry soundChunk = App.AssetManager.GetChunkEntry(chkref.ChunkId); + ChunkAssetEntry newSoundChunk = DuplicateChunk(soundChunk); + chkref.ChunkId = newSoundChunk.Id; } + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); return refEntry; } } - public class SoundWaveExtension : DuplicateAssetExtension + public class PathfindingExtension : DuplicateAssetExtension { - public override string AssetType => "SoundWaveAsset"; + + public override string AssetType => "PathfindingBlobAsset"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { @@ -59,20 +59,25 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); dynamic refRoot = refAsset.RootObject; - - foreach (dynamic chkref in refRoot.Chunks) + ChunkAssetEntry pathfindingChunk = App.AssetManager.GetChunkEntry(refRoot.Blob.BlobId); + if (pathfindingChunk != null) { - ChunkAssetEntry soundChunk = App.AssetManager.GetChunkEntry(chkref.ChunkId); - ChunkAssetEntry newSoundChunk = DuplicateChunk(soundChunk); - chkref.ChunkId = new Guid(newSoundChunk.Name); + ChunkAssetEntry newPathfindingChunk = DuplicateChunk(pathfindingChunk); + if (newPathfindingChunk != null) + { + refRoot.Blob.BlobId = new Guid(newPathfindingChunk.Name); + } } + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); return refEntry; } } - public class VisualUnlockBlueprintBundleExtension : DuplicateAssetExtension + #region --Bundles-- + + public class BlueprintBundleExtension : DuplicateAssetExtension { public override string AssetType => "BlueprintBundle"; @@ -115,30 +120,59 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName } } - public class PathfindingExtension : DuplicateAssetExtension + #endregion + + #region --Meshes-- + + public class ClothWrappingExtension : DuplicateAssetExtension { + public override string AssetType => "ClothWrappingAsset"; - public override string AssetType => "PathfindingBlobAsset"; + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + dynamic newRoot = newAsset.RootObject; + + // Duplicate the res + ResAssetEntry resEntry = App.AssetManager.GetResEntry(newRoot.ClothWrappingAssetResource); + ResAssetEntry newResEntry = DuplicateRes(resEntry, newEntry.Name, ResourceType.EAClothEntityData); + + // Update the ebx + newRoot.ClothWrappingAssetResource = newResEntry.ResRid; + newEntry.LinkAsset(newResEntry); + + // Modify the ebx + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + + return newEntry; + } + } + + public class ClothExtension : DuplicateAssetExtension + { + public override string AssetType => "ClothAsset"; public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) { - EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + dynamic newRoot = newAsset.RootObject; - EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); - dynamic refRoot = refAsset.RootObject; - ChunkAssetEntry pathfindingChunk = App.AssetManager.GetChunkEntry(refRoot.Blob.BlobId); - if (pathfindingChunk != null) - { - ChunkAssetEntry newPathfindingChunk = DuplicateChunk(pathfindingChunk); - if (newPathfindingChunk != null) - { - refRoot.Blob.BlobId = new Guid(newPathfindingChunk.Name); - } - } + // Duplicate the res + ResAssetEntry resEntry = App.AssetManager.GetResEntry(newRoot.ClothAssetResource); + ResAssetEntry newResEntry = DuplicateRes(resEntry, newEntry.Name, ResourceType.EAClothAssetData); - App.AssetManager.ModifyEbx(refEntry.Name, refAsset); + // Update the ebx + newRoot.ClothAssetResource = newResEntry.ResRid; + newEntry.LinkAsset(newResEntry); - return refEntry; + // Modify the ebx + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + + return newEntry; } } @@ -238,49 +272,6 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName } } - public class AtlasTextureExtension : DuplicateAssetExtension - { - public override string AssetType => "AtlasTextureAsset"; - - public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) - { - // Duplicate the ebx - EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); - EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); - - // Get the original asset root object data - EbxAsset asset = App.AssetManager.GetEbx(entry); - dynamic textureAsset = asset.RootObject; - - // Get the original chunk and res entries - ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); - AtlasTexture texture = App.AssetManager.GetResAs(resEntry); - ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); - - // Duplicate the chunk - ChunkAssetEntry newChunkEntry = DuplicateChunk(chunkEntry); - - // Duplicate the res - ResAssetEntry newResEntry = DuplicateRes(resEntry, newName, ResourceType.AtlasTexture); - ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; - AtlasTexture newTexture = App.AssetManager.GetResAs(newResEntry); - - // Set the data in the Atlas Texture - newTexture.SetData(texture.Width, texture.Height, newChunkEntry.Id, App.AssetManager); - newTexture.SetNameHash((uint)Utils.HashString($"Output/Win32/{newResEntry.Name}.res", true)); - - // Link the newly duplicated ebx, chunk, and res entries together - newResEntry.LinkAsset(newChunkEntry); - newEntry.LinkAsset(newResEntry); - - // Modify ebx and res - App.AssetManager.ModifyEbx(newEntry.Name, newAsset); - App.AssetManager.ModifyRes(newResEntry.Name, newTexture); - - return newEntry; - } - } - public class MeshExtension : DuplicateAssetExtension { public override string AssetType => "MeshAsset"; @@ -379,6 +370,77 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName } } + #endregion + + #region --Textures-- + + public class AtlasTextureExtension : DuplicateAssetExtension + { + public override string AssetType => "AtlasTextureAsset"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + // Duplicate the ebx + EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); + EbxAsset newAsset = App.AssetManager.GetEbx(newEntry); + + // Get the original asset root object data + EbxAsset asset = App.AssetManager.GetEbx(entry); + dynamic textureAsset = asset.RootObject; + + // Get the original chunk and res entries + ResAssetEntry resEntry = App.AssetManager.GetResEntry(textureAsset.Resource); + AtlasTexture texture = App.AssetManager.GetResAs(resEntry); + ChunkAssetEntry chunkEntry = App.AssetManager.GetChunkEntry(texture.ChunkId); + + // Duplicate the chunk + ChunkAssetEntry newChunkEntry = DuplicateChunk(chunkEntry); + + // Duplicate the res + ResAssetEntry newResEntry = DuplicateRes(resEntry, newName, ResourceType.AtlasTexture); + ((dynamic)newAsset.RootObject).Resource = newResEntry.ResRid; + AtlasTexture newTexture = App.AssetManager.GetResAs(newResEntry); + + // Set the data in the Atlas Texture + newTexture.SetData(texture.Width, texture.Height, newChunkEntry.Id, App.AssetManager); + newTexture.SetNameHash((uint)Utils.HashString($"Output/Win32/{newResEntry.Name}.res", true)); + + // Link the newly duplicated ebx, chunk, and res entries together + newResEntry.LinkAsset(newChunkEntry); + newEntry.LinkAsset(newResEntry); + + // Modify ebx and res + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + App.AssetManager.ModifyRes(newResEntry.Name, newTexture); + + return newEntry; + } + } + + public class SvgImageExtension : DuplicateAssetExtension + { + public override string AssetType => "SvgImage"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + EbxAssetEntry refEntry = base.DuplicateAsset(entry, newName, createNew, newType); + + EbxAsset refAsset = App.AssetManager.GetEbx(refEntry); + dynamic refRoot = refAsset.RootObject; + + ResAssetEntry resEntry = App.AssetManager.GetResEntry(refRoot.Resource); + + ResAssetEntry newResEntry = DuplicateRes(resEntry, refEntry.Name, ResourceType.SvgImage); + if (newResEntry != null) + { + refRoot.Resource = newResEntry.ResRid; + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); + } + + return refEntry; + } + } + public class TextureExtension : DuplicateAssetExtension { public override string AssetType => "TextureBaseAsset"; @@ -425,6 +487,8 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName } } + #endregion + public class DuplicateAssetExtension { public virtual string AssetType => null; From 9b726381b597c44bf461105939fe3447302b09b4 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:57:08 -0700 Subject: [PATCH 07/10] Update object variation plugin to match requests I forgot to upload this earlier with my other stuff, this is just updated to match your requests --- .../FrostyObjectVariationEditor.cs | 119 +++++++++--------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs b/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs index 64c90aaee..b95877873 100644 --- a/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs +++ b/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs @@ -43,87 +43,84 @@ public override void OnApplyTemplate() private void PgAsset_OnModified(object sender, ItemModifiedEventArgs e) { - if (ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) + for (int i = 0; i < meshVariations.Count; i++) { - for (int i = 0; i < meshVariations.Count; i++) - { - EbxAsset meshEbx = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(meshVariations[i].MeshGuid)); - dynamic meshObj = meshEbx.RootObject; + EbxAsset meshEbx = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(meshVariations[i].MeshGuid)); + dynamic meshObj = meshEbx.RootObject; - // load mesh asset associated with this variation - MeshSet meshAsset = App.AssetManager.GetResAs(App.AssetManager.GetResEntry(meshObj.MeshSetResource)); + // load mesh asset associated with this variation + MeshSet meshAsset = App.AssetManager.GetResAs(App.AssetManager.GetResEntry(meshObj.MeshSetResource)); - // iterate mesh lods - for (int j = 0; j < meshAsset.Lods.Count; j++) - { - var sbe = shaderBlockDepots[i].GetSectionEntry(j); + // iterate mesh lods + for (int j = 0; j < meshAsset.Lods.Count; j++) + { + var sbe = shaderBlockDepots[i].GetSectionEntry(j); - // iterate mesh sections - for (int k = 0; k < meshAsset.Lods[j].Sections.Count; k++) - { - var texturesBlock = sbe.GetTextureParams(k); - var paramsBlock = sbe.GetParams(k); + // iterate mesh sections + for (int k = 0; k < meshAsset.Lods[j].Sections.Count; k++) + { + var texturesBlock = sbe.GetTextureParams(k); + var paramsBlock = sbe.GetParams(k); - dynamic material = meshObj.Materials[meshAsset.Lods[j].Sections[k].MaterialId].Internal; - AssetClassGuid materialGuid = material.GetInstanceGuid(); + dynamic material = meshObj.Materials[meshAsset.Lods[j].Sections[k].MaterialId].Internal; + AssetClassGuid materialGuid = material.GetInstanceGuid(); - // search mesh variation for appropriate variation material - foreach (var meshVariationMaterial in meshVariations[i].Materials) + // search mesh variation for appropriate variation material + foreach (var meshVariationMaterial in meshVariations[i].Materials) + { + if (meshVariationMaterial.MaterialGuid == materialGuid.ExportedGuid) { - if (meshVariationMaterial.MaterialGuid == materialGuid.ExportedGuid) + dynamic mvMaterial = Asset.GetObject(meshVariationMaterial.MaterialVariationClassGuid); + foreach (dynamic param in mvMaterial.Shader.BoolParameters) { - dynamic mvMaterial = Asset.GetObject(meshVariationMaterial.MaterialVariationClassGuid); - foreach (dynamic param in mvMaterial.Shader.BoolParameters) - { - string paramName = param.ParameterName; - bool value = param.Value; + string paramName = param.ParameterName; + bool value = param.Value; - paramsBlock.SetParameterValue(paramName, value); - } - foreach (dynamic param in mvMaterial.Shader.VectorParameters) - { - string paramName = param.ParameterName; - dynamic vec = param.Value; + paramsBlock.SetParameterValue(paramName, value); + } + foreach (dynamic param in mvMaterial.Shader.VectorParameters) + { + string paramName = param.ParameterName; + dynamic vec = param.Value; - paramsBlock.SetParameterValue(paramName, new float[] { vec.x, vec.y, vec.z, vec.w }); - } - foreach (dynamic param in mvMaterial.Shader.ConditionalParameters) - { - string value = param.Value; - PointerRef assetRef = param.ConditionalAsset; + paramsBlock.SetParameterValue(paramName, new float[] { vec.x, vec.y, vec.z, vec.w }); + } + foreach (dynamic param in mvMaterial.Shader.ConditionalParameters) + { + string value = param.Value; + PointerRef assetRef = param.ConditionalAsset; - if (assetRef.Type == PointerRefType.External) - { - EbxAsset asset = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(assetRef.External.FileGuid)); - dynamic conditionalAsset = asset.RootObject; + if (assetRef.Type == PointerRefType.External) + { + EbxAsset asset = App.AssetManager.GetEbx(App.AssetManager.GetEbxEntry(assetRef.External.FileGuid)); + dynamic conditionalAsset = asset.RootObject; - string conditionName = conditionalAsset.ConditionName; - byte idx = (byte)conditionalAsset.Branches.IndexOf(value); + string conditionName = conditionalAsset.ConditionName; + byte idx = (byte)conditionalAsset.Branches.IndexOf(value); - paramsBlock.SetParameterValue(conditionName, idx); - } + paramsBlock.SetParameterValue(conditionName, idx); } - foreach (dynamic param in mvMaterial.Shader.TextureParameters) - { - string paramName = param.ParameterName; - PointerRef value = param.Value; + } + foreach (dynamic param in mvMaterial.Shader.TextureParameters) + { + string paramName = param.ParameterName; + PointerRef value = param.Value; - texturesBlock.SetParameterValue(paramName, value.External.ClassGuid); - } + texturesBlock.SetParameterValue(paramName, value.External.ClassGuid); + } - texturesBlock.IsModified = true; - paramsBlock.IsModified = true; + texturesBlock.IsModified = true; + paramsBlock.IsModified = true; - break; - } + break; } } } - - // modify the ShaderBlockDepot - App.AssetManager.ModifyRes(shaderBlockDepots[i].ResourceId, shaderBlockDepots[i]); - AssetEntry.LinkAsset(App.AssetManager.GetResEntry(shaderBlockDepots[i].ResourceId)); } + + // modify the ShaderBlockDepot + App.AssetManager.ModifyRes(shaderBlockDepots[i].ResourceId, shaderBlockDepots[i]); + AssetEntry.LinkAsset(App.AssetManager.GetResEntry(shaderBlockDepots[i].ResourceId)); } } @@ -193,7 +190,7 @@ private void FrostyObjectVariationEditor_Loaded(object sender, RoutedEventArgs e } //Double check that our data isn't incorrect - if (meshVariations.Count > 0 && ProfilesLibrary.DataVersion == (int)ProfileVersion.StarWarsBattlefrontII) + if (meshVariations.Count > 0) { firstTimeLoad = false; } From 5d7ef5fdc2ad8cdba2769c627c1cadd3615db986 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:22:41 -0700 Subject: [PATCH 08/10] fix pathfinding chunk mistake sorry this took so long I was busy with something else --- Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index 9971fbb36..c6548d840 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -65,7 +65,7 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName ChunkAssetEntry newPathfindingChunk = DuplicateChunk(pathfindingChunk); if (newPathfindingChunk != null) { - refRoot.Blob.BlobId = new Guid(newPathfindingChunk.Name); + refRoot.Blob.BlobId = pathfindingChunk.Id; } } From 12a93dede82c962e2ad64aa2e55c793bd67b8c97 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Fri, 11 Aug 2023 00:24:08 -0700 Subject: [PATCH 09/10] fix bundle duplication to use original superbundle --- Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index c6548d840..49067abca 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -87,7 +87,8 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); // Add new bundle - BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.BlueprintBundle, 0); + BundleEntry oldBundle = App.AssetManager.GetBundleEntry(newEntry.AddedBundles[0]); + BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName.ToLower(), BundleType.BlueprintBundle, oldBundle.SuperBundleId); newEntry.AddedBundles.Clear(); newEntry.AddedBundles.Add(App.AssetManager.GetBundleId(newBundle)); @@ -108,7 +109,8 @@ public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName EbxAssetEntry newEntry = base.DuplicateAsset(entry, newName, createNew, newType); // Add new bundle - BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName, BundleType.SubLevel, 0); + BundleEntry oldBundle = App.AssetManager.GetBundleEntry(newEntry.AddedBundles[0]); + BundleEntry newBundle = App.AssetManager.AddBundle("win32/" + newName, BundleType.SubLevel, oldBundle.SuperBundleId); newEntry.AddedBundles.Clear(); newEntry.AddedBundles.Add(App.AssetManager.GetBundleId(newBundle)); From a1716ec151f66050b8c98881fe1cbc265a1131c8 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Fri, 11 Aug 2023 00:32:41 -0700 Subject: [PATCH 10/10] Update task window correctly This fixes a bug where Task Window will always display "loading variation Databases" even during the actual duplication process --- Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs index 49067abca..f43d4ad2b 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -638,6 +638,7 @@ public DuplicateContextMenuItem() } } + task.Update("Duplicating asset..."); extensions[key].DuplicateAsset(entry, newName, newType != null, newType); } catch (Exception e)