From 279c7c6f8f6467e9881240c94a54cd23d9e29977 Mon Sep 17 00:00:00 2001 From: Y wingpilot2 <136618828+Ywingpilot2@users.noreply.github.com> Date: Fri, 11 Aug 2023 00:33:27 -0700 Subject: [PATCH] [DuplicationPlugin] Added support for more types (#276) --- FrostyPlugin/Viewport/MeshVariationDb.cs | 13 +- Plugins/AtlasTexturePlugin/AtlasTexture.cs | 297 ++++--- .../AtlasTexturePlugin.csproj | 159 ++-- .../DuplicateContextMenuItem.cs | 810 ++++++++++++++---- .../DuplicationPlugin.csproj | 155 ++-- .../Properties/AssemblyInfo.cs | 57 +- .../FrostyObjectVariationEditor.cs | 407 ++++----- .../ObjectVariationAssetDefinition.cs | 42 +- .../ObjectVariationPlugin.csproj | 126 +-- 9 files changed, 1313 insertions(+), 753 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)) 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 677c87463..f43d4ad2b 100644 --- a/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs +++ b/Plugins/DuplicationPlugin/DuplicateContextMenuItem.cs @@ -1,156 +1,654 @@ -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 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 SoundWaveExtension : DuplicateAssetExtension + { + public override string AssetType => "SoundWaveAsset"; + + 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; + + foreach (dynamic chkref in refRoot.Chunks) + { + 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 PathfindingExtension : DuplicateAssetExtension + { + + public override string AssetType => "PathfindingBlobAsset"; + + 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; + ChunkAssetEntry pathfindingChunk = App.AssetManager.GetChunkEntry(refRoot.Blob.BlobId); + if (pathfindingChunk != null) + { + ChunkAssetEntry newPathfindingChunk = DuplicateChunk(pathfindingChunk); + if (newPathfindingChunk != null) + { + refRoot.Blob.BlobId = pathfindingChunk.Id; + } + } + + App.AssetManager.ModifyEbx(refEntry.Name, refAsset); + + return refEntry; + } + } + + #region --Bundles-- + + 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, createNew, newType); + + // Add new bundle + 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)); + + newBundle.Blueprint = newEntry; + + return newEntry; + } + } + + public class SubWorldDataExtension : DuplicateAssetExtension + { + 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); + + // Add new bundle + 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)); + + newBundle.Blueprint = newEntry; + newEntry.LinkAsset(entry); + + return newEntry; + } + } + + #endregion + + #region --Meshes-- + + public class ClothWrappingExtension : DuplicateAssetExtension + { + public override string AssetType => "ClothWrappingAsset"; + + 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) + { + // 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.ClothAssetResource); + ResAssetEntry newResEntry = DuplicateRes(resEntry, newEntry.Name, ResourceType.EAClothAssetData); + + // Update the ebx + newRoot.ClothAssetResource = newResEntry.ResRid; + newEntry.LinkAsset(newResEntry); + + // Modify the ebx + App.AssetManager.ModifyEbx(newEntry.Name, newAsset); + + return newEntry; + } + } + + public class ObjectVariationExtension : DuplicateAssetExtension + { + public override string AssetType => "ObjectVariation"; + + public override EbxAssetEntry DuplicateAsset(EbxAssetEntry entry, string newName, bool createNew, Type newType) + { + EbxAssetEntry newAssetEntry = base.DuplicateAsset(entry, newName, createNew, newType); + + //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 = oldRootObject.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 + { + // 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 meshVariation.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); + + for (int i = 0; i < newShaderBlockDepot.ResourceCount; i++) + { + ShaderBlockResource res = newShaderBlockDepot.GetResource(i); + if (!(res is MeshParamDbBlock)) + res.ChangeHash(newRootObject.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, newRootObject.NameHash, lod); + sbMvEntry.SetHash(meshSet.NameHash, newRootObject.NameHash, lod); + } + + App.AssetManager.ModifyRes(newResEntry.Name, newShaderBlockDepot); + newAssetEntry.LinkAsset(newResEntry); + } + + App.Logger.Log("Duped {0} with a namehash of {1}", newAssetEntry.Filename, newRootObject.NameHash.ToString()); + + App.AssetManager.ModifyEbx(newName, newEbx); + return newAssetEntry; + } + } + + 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 != 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; + } + } + + #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"; + + 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; + } + } + + #endregion + + 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); + + 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))) + { + newEntry = App.AssetManager.AddRes(name, resType, entry.ResMeta, reader.ReadToEnd(), entry.EnumerateBundles().ToArray()); + } + + 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; + } + } + + task.Update("Duplicating asset..."); + 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 diff --git a/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs b/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs index 7c9c3a58a..b95877873 100644 --- a/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs +++ b/Plugins/ObjectVariationPlugin/FrostyObjectVariationEditor.cs @@ -1,199 +1,208 @@ -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) + { + 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) + { + 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