diff --git a/Penumbra.GameData/Files/ShpkFile.Write.cs b/Penumbra.GameData/Files/ShpkFile.Write.cs index 89effe84..dd8b70ac 100644 --- a/Penumbra.GameData/Files/ShpkFile.Write.cs +++ b/Penumbra.GameData/Files/ShpkFile.Write.cs @@ -7,19 +7,25 @@ public partial class ShpkFile { public byte[] Write() { + if (SubViewKeys.Length != 2) + { + throw new InvalidDataException(); + } + using var stream = new MemoryStream(); using var blobs = new MemoryStream(); + var strings = new StringPool(ReadOnlySpan.Empty); using (var w = new BinaryWriter(stream)) { w.Write(ShPkMagic); - w.Write(Unknown1); + w.Write(Version); w.Write(DirectXVersion switch { DXVersion.DirectX9 => DX9Magic, DXVersion.DirectX11 => DX11Magic, _ => throw new NotImplementedException(), }); - long offsetsPosition = stream.Position; + var offsetsPosition = stream.Position; w.Write(0u); // Placeholder for file size w.Write(0u); // Placeholder for blobs offset w.Write(0u); // Placeholder for strings offset @@ -29,15 +35,15 @@ public partial class ShpkFile w.Write((uint)MaterialParams.Length); w.Write((uint)Constants.Length); w.Write((uint)Samplers.Length); - w.Write((uint)UnknownA.Length); - w.Write((uint)UnknownB.Length); - w.Write((uint)UnknownC.Length); - w.Write(Unknown2); - w.Write(Unknown3); - w.Write(Unknown4); + w.Write((uint)UAVs.Length); + w.Write((uint)SystemKeys.Length); + w.Write((uint)SceneKeys.Length); + w.Write((uint)MaterialKeys.Length); + w.Write((uint)Nodes.Length); + w.Write((uint)Items.Length); - WriteShaderArray(w, VertexShaders, blobs, Strings); - WriteShaderArray(w, PixelShaders, blobs, Strings); + WriteShaderArray(w, VertexShaders, blobs, strings); + WriteShaderArray(w, PixelShaders, blobs, strings); foreach (var materialParam in MaterialParams) { @@ -46,16 +52,68 @@ public partial class ShpkFile w.Write(materialParam.ByteSize); } - WriteResourceArray(w, Constants, Strings); - WriteResourceArray(w, Samplers, Strings); + WriteResourceArray(w, Constants, strings); + WriteResourceArray(w, Samplers, strings); + WriteResourceArray(w, UAVs, strings); - w.Write(Unknowns.Item1); - w.Write(Unknowns.Item2); - w.Write(Unknowns.Item3); + foreach (var key in SystemKeys) + { + w.Write(key.Id); + w.Write(key.DefaultValue); + } + foreach (var key in SceneKeys) + { + w.Write(key.Id); + w.Write(key.DefaultValue); + } + foreach (var key in MaterialKeys) + { + w.Write(key.Id); + w.Write(key.DefaultValue); + } + foreach (var key in SubViewKeys) + { + w.Write(key.DefaultValue); + } - WriteUInt32PairArray(w, UnknownA); - WriteUInt32PairArray(w, UnknownB); - WriteUInt32PairArray(w, UnknownC); + foreach (var node in Nodes) + { + if (node.PassIndices.Length != 16 || node.SystemKeys.Length != SystemKeys.Length || node.SceneKeys.Length != SceneKeys.Length || node.MaterialKeys.Length != MaterialKeys.Length || node.SubViewKeys.Length != SubViewKeys.Length) + { + throw new InvalidDataException(); + } + w.Write(node.Id); + w.Write(node.Passes.Length); + w.Write(node.PassIndices); + foreach (var key in node.SystemKeys) + { + w.Write(key); + } + foreach (var key in node.SceneKeys) + { + w.Write(key); + } + foreach (var key in node.MaterialKeys) + { + w.Write(key); + } + foreach (var key in node.SubViewKeys) + { + w.Write(key); + } + foreach (var pass in node.Passes) + { + w.Write(pass.Id); + w.Write(pass.VertexShader); + w.Write(pass.PixelShader); + } + } + + foreach (var item in Items) + { + w.Write(item.Id); + w.Write(item.Node); + } w.Write(AdditionalData); @@ -63,7 +121,7 @@ public partial class ShpkFile blobs.WriteTo(stream); var stringsOffset = (int)stream.Position; - Strings.Data.WriteTo(stream); + strings.Data.WriteTo(stream); var fileSize = (int)stream.Position; @@ -102,13 +160,12 @@ public partial class ShpkFile w.Write(blobSize); w.Write((ushort)shader.Constants.Length); w.Write((ushort)shader.Samplers.Length); - w.Write((ushort)shader.UnknownX.Length); - w.Write((ushort)shader.UnknownY.Length); + w.Write((ushort)shader.UAVs.Length); + w.Write((ushort)0); WriteResourceArray(w, shader.Constants, strings); WriteResourceArray(w, shader.Samplers, strings); - WriteResourceArray(w, shader.UnknownX, strings); - WriteResourceArray(w, shader.UnknownY, strings); + WriteResourceArray(w, shader.UAVs, strings); } } diff --git a/Penumbra.GameData/Files/ShpkFile.cs b/Penumbra.GameData/Files/ShpkFile.cs index ae25a7c5..12192a43 100644 --- a/Penumbra.GameData/Files/ShpkFile.cs +++ b/Penumbra.GameData/Files/ShpkFile.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Lumina.Data.Parsing; using Lumina.Extensions; using Lumina.Misc; using Penumbra.GameData.Data; @@ -33,8 +34,7 @@ public partial class ShpkFile : IWritable public DXVersion DirectXVersion; public Resource[] Constants; public Resource[] Samplers; - public Resource[] UnknownX; - public Resource[] UnknownY; + public Resource[] UAVs; public byte[] AdditionalHeader; private byte[] _blob; private DisassembledShader? _disassembly; @@ -111,6 +111,16 @@ public partial class ShpkFile : IWritable return Samplers.Select(res => new Resource?(res)).FirstOrDefault(res => res!.Value.Name == name); } + public Resource? GetUAVById(uint id) + { + return UAVs.Select(res => new Resource?(res)).FirstOrDefault(res => res!.Value.Id == id); + } + + public Resource? GetUAVByName(string name) + { + return UAVs.Select(res => new Resource?(res)).FirstOrDefault(res => res!.Value.Name == name); + } + public void UpdateResources(ShpkFile file) { if (_disassembly == null) @@ -119,39 +129,56 @@ public partial class ShpkFile : IWritable } var constants = new List(); var samplers = new List(); + var uavs = new List(); foreach (var binding in _disassembly.ResourceBindings) { switch (binding.Type) { case DisassembledShader.ResourceType.ConstantBuffer: var name = NormalizeResourceName(binding.Name); - // We want to preserve IDs as much as possible, and to deterministically generate new ones, to maximize compatibility. - var id = GetConstantByName(name)?.Id ?? file.GetConstantByName(name)?.Id ?? Crc32.Get(name); + // We want to preserve IDs as much as possible, and to deterministically generate new ones in a way that's most compliant with the native ones, to maximize compatibility. + var id = GetConstantByName(name)?.Id ?? file.GetConstantByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu); constants.Add(new Resource { - Id = id, - Name = name, - Slot = (ushort)binding.Slot, - Size = (ushort)binding.RegisterCount, - Used = binding.Used, + Id = id, + Name = name, + Slot = (ushort)binding.Slot, + Size = (ushort)binding.RegisterCount, + Used = binding.Used, + UsedDynamically = binding.UsedDynamically, }); break; case DisassembledShader.ResourceType.Texture: name = NormalizeResourceName(binding.Name); - id = GetSamplerByName(name)?.Id ?? file.GetSamplerByName(name)?.Id ?? Crc32.Get(name); + id = GetSamplerByName(name)?.Id ?? file.GetSamplerByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu); samplers.Add(new Resource { - Id = id, - Name = name, - Slot = (ushort)binding.Slot, - Size = (ushort)binding.Slot, - Used = binding.Used, + Id = id, + Name = name, + Slot = (ushort)binding.Slot, + Size = (ushort)binding.Slot, + Used = binding.Used, + UsedDynamically = binding.UsedDynamically, + }); + break; + case DisassembledShader.ResourceType.UAV: + name = NormalizeResourceName(binding.Name); + id = GetUAVByName(name)?.Id ?? file.GetUAVByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu); + uavs.Add(new Resource + { + Id = id, + Name = name, + Slot = (ushort)binding.Slot, + Size = (ushort)binding.Slot, + Used = binding.Used, + UsedDynamically = binding.UsedDynamically, }); break; } } Constants = constants.ToArray(); Samplers = samplers.ToArray(); + UAVs = uavs.ToArray(); } private void UpdateUsed() @@ -160,6 +187,7 @@ public partial class ShpkFile : IWritable { var cbUsage = new Dictionary(); var tUsage = new Dictionary(); + var uUsage = new Dictionary(); foreach (var binding in _disassembly.ResourceBindings) { switch (binding.Type) @@ -170,39 +198,36 @@ public partial class ShpkFile : IWritable case DisassembledShader.ResourceType.Texture: tUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically); break; + case DisassembledShader.ResourceType.UAV: + uUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically); + break; } } - for (var i = 0; i < Constants.Length; ++i) + static void CopyUsed(Resource[] resources, Dictionary used) { - if (cbUsage.TryGetValue(Constants[i].Name, out var usage)) + for (var i = 0; i < resources.Length; ++i) { - Constants[i].Used = usage.Item1; - Constants[i].UsedDynamically = usage.Item2; - } - else - { - Constants[i].Used = null; - Constants[i].UsedDynamically = null; - } - } - for (var i = 0; i < Samplers.Length; ++i) - { - if (tUsage.TryGetValue(Samplers[i].Name, out var usage)) - { - Samplers[i].Used = usage.Item1; - Samplers[i].UsedDynamically = usage.Item2; - } - else - { - Samplers[i].Used = null; - Samplers[i].UsedDynamically = null; + if (used.TryGetValue(resources[i].Name, out var usage)) + { + resources[i].Used = usage.Item1; + resources[i].UsedDynamically = usage.Item2; + } + else + { + resources[i].Used = null; + resources[i].UsedDynamically = null; + } } } + CopyUsed(Constants, cbUsage); + CopyUsed(Samplers, tUsage); + CopyUsed(UAVs, uUsage); } else { ClearUsed(Constants); ClearUsed(Samplers); + ClearUsed(UAVs); } } @@ -231,6 +256,37 @@ public partial class ShpkFile : IWritable public ushort ByteSize; } + public struct Pass + { + public uint Id; + public uint VertexShader; + public uint PixelShader; + } + + public struct Key + { + public uint Id; + public uint DefaultValue; + public uint[] Values; + } + + public struct Node + { + public uint Id; + public byte[] PassIndices; + public uint[] SystemKeys; + public uint[] SceneKeys; + public uint[] MaterialKeys; + public uint[] SubViewKeys; + public Pass[] Passes; + } + + public struct Item + { + public uint Id; + public uint Node; + } + public class StringPool { public MemoryStream Data; @@ -317,7 +373,7 @@ public partial class ShpkFile : IWritable public const uint MaterialParamsConstantId = 0x64D12851u; - public uint Unknown1; + public uint Version; public DXVersion DirectXVersion; public Shader[] VertexShaders; public Shader[] PixelShaders; @@ -325,15 +381,14 @@ public partial class ShpkFile : IWritable public MaterialParam[] MaterialParams; public Resource[] Constants; public Resource[] Samplers; - public (uint, uint)[] UnknownA; - public (uint, uint)[] UnknownB; - public (uint, uint)[] UnknownC; - public uint Unknown2; - public uint Unknown3; - public uint Unknown4; - public (uint, uint, uint) Unknowns; + public Resource[] UAVs; + public Key[] SystemKeys; + public Key[] SceneKeys; + public Key[] MaterialKeys; + public Key[] SubViewKeys; + public Node[] Nodes; + public Item[] Items; public byte[] AdditionalData; - public StringPool Strings; // Cannot be safely discarded yet, we don't know if AdditionalData references it public bool Valid { get; private set; } private bool _changed; @@ -363,6 +418,41 @@ public partial class ShpkFile : IWritable return Samplers.Select(res => new Resource?(res)).FirstOrDefault(res => res!.Value.Name == name); } + public Resource? GetUAVById(uint id) + { + return UAVs.Select(res => new Resource?(res)).FirstOrDefault(res => res!.Value.Id == id); + } + + public Resource? GetUAVByName(string name) + { + return UAVs.Select(res => new Resource?(res)).FirstOrDefault(res => res!.Value.Name == name); + } + + public Key? GetSystemKeyById(uint id) + { + return SystemKeys.Select(key => new Key?(key)).FirstOrDefault(key => key!.Value.Id == id); + } + + public Key? GetSceneKeyById(uint id) + { + return SceneKeys.Select(key => new Key?(key)).FirstOrDefault(key => key!.Value.Id == id); + } + + public Key? GetMaterialKeyById(uint id) + { + return MaterialKeys.Select(key => new Key?(key)).FirstOrDefault(key => key!.Value.Id == id); + } + + public Node? GetNodeById(uint id) + { + return Nodes.Select(node => new Node?(node)).FirstOrDefault(node => node!.Value.Id == id); + } + + public Item? GetItemById(uint id) + { + return Items.Select(item => new Item?(item)).FirstOrDefault(item => item!.Value.Id == id); + } + // Activator.CreateInstance can't use a ctor with a default value so this has to be made explicit public ShpkFile(byte[] data) : this(data, false) @@ -378,7 +468,7 @@ public partial class ShpkFile : IWritable { throw new InvalidDataException(); } - Unknown1 = r.ReadUInt32(); + Version = r.ReadUInt32(); DirectXVersion = r.ReadUInt32() switch { DX9Magic => DXVersion.DirectX9, @@ -397,40 +487,59 @@ public partial class ShpkFile : IWritable var materialParamCount = r.ReadUInt32(); var constantCount = r.ReadUInt32(); var samplerCount = r.ReadUInt32(); - var unknownACount = r.ReadUInt32(); - var unknownBCount = r.ReadUInt32(); - var unknownCCount = r.ReadUInt32(); - Unknown2 = r.ReadUInt32(); - Unknown3 = r.ReadUInt32(); - Unknown4 = r.ReadUInt32(); + var uavCount = r.ReadUInt32(); + var systemKeyCount = r.ReadUInt32(); + var sceneKeyCount = r.ReadUInt32(); + var materialKeyCount = r.ReadUInt32(); + var nodeCount = r.ReadUInt32(); + var itemCount = r.ReadUInt32(); var blobs = new ReadOnlySpan(data, (int)blobsOffset, (int)(stringsOffset - blobsOffset)); - Strings = new StringPool(new ReadOnlySpan(data, (int)stringsOffset, (int)(data.Length - stringsOffset))); + var strings = new StringPool(new ReadOnlySpan(data, (int)stringsOffset, (int)(data.Length - stringsOffset))); - VertexShaders = ReadShaderArray(r, (int)vertexShaderCount, DisassembledShader.ShaderStage.Vertex, DirectXVersion, disassemble, blobs, Strings); - PixelShaders = ReadShaderArray(r, (int)pixelShaderCount, DisassembledShader.ShaderStage.Pixel, DirectXVersion, disassemble, blobs, Strings); + VertexShaders = ReadShaderArray(r, (int)vertexShaderCount, DisassembledShader.ShaderStage.Vertex, DirectXVersion, disassemble, blobs, strings); + PixelShaders = ReadShaderArray(r, (int)pixelShaderCount, DisassembledShader.ShaderStage.Pixel, DirectXVersion, disassemble, blobs, strings); MaterialParams = r.ReadStructuresAsArray((int)materialParamCount); - Constants = ReadResourceArray(r, (int)constantCount, Strings); - Samplers = ReadResourceArray(r, (int)samplerCount, Strings); + Constants = ReadResourceArray(r, (int)constantCount, strings); + Samplers = ReadResourceArray(r, (int)samplerCount, strings); + UAVs = ReadResourceArray(r, (int)uavCount, strings); - var unk1 = r.ReadUInt32(); - var unk2 = r.ReadUInt32(); - var unk3 = r.ReadUInt32(); - Unknowns = (unk1, unk2, unk3); + SystemKeys = ReadKeyArray(r, (int)systemKeyCount); + SceneKeys = ReadKeyArray(r, (int)sceneKeyCount); + MaterialKeys = ReadKeyArray(r, (int)materialKeyCount); - UnknownA = ReadUInt32PairArray(r, (int)unknownACount); - UnknownB = ReadUInt32PairArray(r, (int)unknownBCount); - UnknownC = ReadUInt32PairArray(r, (int)unknownCCount); + var subViewKey1Default = r.ReadUInt32(); + var subViewKey2Default = r.ReadUInt32(); - AdditionalData = r.ReadBytes((int)(blobsOffset - r.BaseStream.Position)); + SubViewKeys = new Key[] { + new Key + { + Id = 1, + DefaultValue = subViewKey1Default, + Values = Array.Empty(), + }, + new Key + { + Id = 2, + DefaultValue = subViewKey2Default, + Values = Array.Empty(), + }, + }; + + Nodes = ReadNodeArray(r, (int)nodeCount, SystemKeys.Length, SceneKeys.Length, MaterialKeys.Length, SubViewKeys.Length); + Items = r.ReadStructuresAsArray((int)itemCount); + + AdditionalData = r.ReadBytes((int)(blobsOffset - r.BaseStream.Position)); // This should be empty, but just in case. if (disassemble) { UpdateUsed(); } + UpdateKeyValues(); + Valid = true; _changed = false; } @@ -439,11 +548,12 @@ public partial class ShpkFile : IWritable { var constants = new Dictionary(); var samplers = new Dictionary(); - static void CollectResources(Dictionary resources, Resource[] shaderResources, Func getExistingById, bool isSamplers) + var uavs = new Dictionary(); + static void CollectResources(Dictionary resources, Resource[] shaderResources, Func getExistingById, DisassembledShader.ResourceType type) { foreach (var resource in shaderResources) { - if (resources.TryGetValue(resource.Id, out var carry) && isSamplers) + if (resources.TryGetValue(resource.Id, out var carry) && type != DisassembledShader.ResourceType.ConstantBuffer) { continue; } @@ -452,8 +562,8 @@ public partial class ShpkFile : IWritable { Id = resource.Id, Name = resource.Name, - Slot = existing?.Slot ?? (isSamplers ? (ushort)2 : (ushort)65535), - Size = isSamplers ? (existing?.Size ?? 0) : Math.Max(carry.Size, resource.Size), + Slot = existing?.Slot ?? (type == DisassembledShader.ResourceType.ConstantBuffer ? (ushort)65535 : (ushort)2), + Size = type == DisassembledShader.ResourceType.ConstantBuffer ? Math.Max(carry.Size, resource.Size) : (existing?.Size ?? 0), Used = null, UsedDynamically = null, }; @@ -461,16 +571,19 @@ public partial class ShpkFile : IWritable } foreach (var shader in VertexShaders) { - CollectResources(constants, shader.Constants, GetConstantById, false); - CollectResources(samplers, shader.Samplers, GetSamplerById, true); + CollectResources(constants, shader.Constants, GetConstantById, DisassembledShader.ResourceType.ConstantBuffer); + CollectResources(samplers, shader.Samplers, GetSamplerById, DisassembledShader.ResourceType.Sampler); + CollectResources(uavs, shader.UAVs, GetUAVById, DisassembledShader.ResourceType.UAV); } foreach (var shader in PixelShaders) { - CollectResources(constants, shader.Constants, GetConstantById, false); - CollectResources(samplers, shader.Samplers, GetSamplerById, true); + CollectResources(constants, shader.Constants, GetConstantById, DisassembledShader.ResourceType.ConstantBuffer); + CollectResources(samplers, shader.Samplers, GetSamplerById, DisassembledShader.ResourceType.Sampler); + CollectResources(uavs, shader.UAVs, GetUAVById, DisassembledShader.ResourceType.UAV); } Constants = constants.Values.ToArray(); Samplers = samplers.Values.ToArray(); + UAVs = uavs.Values.ToArray(); UpdateUsed(); MaterialParamsSize = (GetConstantById(MaterialParamsConstantId)?.Size ?? 0u) << 4; foreach (var param in MaterialParams) @@ -484,7 +597,8 @@ public partial class ShpkFile : IWritable { var cUsage = new Dictionary(); var sUsage = new Dictionary(); - static void CollectUsage(Dictionary usage, Resource[] resources) + var uUsage = new Dictionary(); + static void CollectUsed(Dictionary usage, Resource[] resources) { foreach (var resource in resources) { @@ -502,42 +616,75 @@ public partial class ShpkFile : IWritable usage[resource.Id] = (combined, carry.Item2 | (resource.UsedDynamically ?? 0)); } } + static void CopyUsed(Resource[] resources, Dictionary used) + { + for (var i = 0; i < resources.Length; ++i) + { + if (used.TryGetValue(resources[i].Id, out var usage)) + { + resources[i].Used = usage.Item1; + resources[i].UsedDynamically = usage.Item2; + } + else + { + resources[i].Used = null; + resources[i].UsedDynamically = null; + } + } + } foreach (var shader in VertexShaders) { - CollectUsage(cUsage, shader.Constants); - CollectUsage(sUsage, shader.Samplers); + CollectUsed(cUsage, shader.Constants); + CollectUsed(sUsage, shader.Samplers); + CollectUsed(uUsage, shader.UAVs); } foreach (var shader in PixelShaders) { - CollectUsage(cUsage, shader.Constants); - CollectUsage(sUsage, shader.Samplers); + CollectUsed(cUsage, shader.Constants); + CollectUsed(sUsage, shader.Samplers); + CollectUsed(uUsage, shader.UAVs); } - for (var i = 0; i < Constants.Length; ++i) + CopyUsed(Constants, cUsage); + CopyUsed(Samplers, sUsage); + CopyUsed(UAVs, uUsage); + } + + public void UpdateKeyValues() + { + static HashSet[] InitializeValueSet(Key[] keys) + => Array.ConvertAll(keys, key => new HashSet() + { + key.DefaultValue, + }); + static void CollectValues(HashSet[] valueSets, uint[] values) { - if (cUsage.TryGetValue(Constants[i].Id, out var usage)) + for (var i = 0; i < valueSets.Length; ++i) { - Constants[i].Used = usage.Item1; - Constants[i].UsedDynamically = usage.Item2; - } - else - { - Constants[i].Used = null; - Constants[i].UsedDynamically = null; + valueSets[i].Add(values[i]); } } - for (var i = 0; i < Samplers.Length; ++i) + static void CopyValues(Key[] keys, HashSet[] valueSets) { - if (sUsage.TryGetValue(Samplers[i].Id, out var usage)) + for (var i = 0; i < keys.Length; ++i) { - Samplers[i].Used = usage.Item1; - Samplers[i].UsedDynamically = usage.Item2; - } - else - { - Samplers[i].Used = null; - Samplers[i].UsedDynamically = null; + keys[i].Values = valueSets[i].ToArray(); } } + var systemKeyValues = InitializeValueSet(SystemKeys); + var sceneKeyValues = InitializeValueSet(SceneKeys); + var materialKeyValues = InitializeValueSet(MaterialKeys); + var subViewKeyValues = InitializeValueSet(SubViewKeys); + foreach (var node in Nodes) + { + CollectValues(systemKeyValues, node.SystemKeys); + CollectValues(sceneKeyValues, node.SceneKeys); + CollectValues(materialKeyValues, node.MaterialKeys); + CollectValues(subViewKeyValues, node.SubViewKeys); + } + CopyValues(SystemKeys, systemKeyValues); + CopyValues(SceneKeys, sceneKeyValues); + CopyValues(MaterialKeys, materialKeyValues); + CopyValues(SubViewKeys, subViewKeyValues); } public void SetInvalid() @@ -606,8 +753,11 @@ public partial class ShpkFile : IWritable var blobSize = r.ReadUInt32(); var constantCount = r.ReadUInt16(); var samplerCount = r.ReadUInt16(); - var unknownXCount = r.ReadUInt16(); - var unknownYCount = r.ReadUInt16(); + var uavCount = r.ReadUInt16(); + if (r.ReadUInt16() != 0) + { + throw new NotImplementedException(); + } var rawBlob = blobs.Slice((int)blobOffset, (int)blobSize); @@ -617,8 +767,7 @@ public partial class ShpkFile : IWritable shader.DirectXVersion = directX; shader.Constants = ReadResourceArray(r, constantCount, strings); shader.Samplers = ReadResourceArray(r, samplerCount, strings); - shader.UnknownX = ReadResourceArray(r, unknownXCount, strings); - shader.UnknownY = ReadResourceArray(r, unknownYCount, strings); + shader.UAVs = ReadResourceArray(r, uavCount, strings); shader.AdditionalHeader = rawBlob[..extraHeaderSize].ToArray(); shader.Blob = rawBlob[extraHeaderSize..].ToArray(); @@ -628,15 +777,49 @@ public partial class ShpkFile : IWritable return ret; } - private static (uint, uint)[] ReadUInt32PairArray(BinaryReader r, int count) + private static Key[] ReadKeyArray(BinaryReader r, int count) { - var ret = new (uint, uint)[count]; + var ret = new Key[count]; for (var i = 0; i < count; ++i) { - var first = r.ReadUInt32(); - var second = r.ReadUInt32(); + var id = r.ReadUInt32(); + var defaultValue = r.ReadUInt32(); - ret[i] = (first, second); + ret[i] = new Key + { + Id = id, + DefaultValue = defaultValue, + Values = Array.Empty(), + }; + } + + return ret; + } + + private static Node[] ReadNodeArray(BinaryReader r, int count, int systemKeyCount, int sceneKeyCount, int materialKeyCount, int subViewKeyCount) + { + var ret = new Node[count]; + for (var i = 0; i < count; ++i) + { + var id = r.ReadUInt32(); + var passCount = r.ReadUInt32(); + var passIndices = r.ReadBytes(16); + var systemKeys = r.ReadStructuresAsArray(systemKeyCount); + var sceneKeys = r.ReadStructuresAsArray(sceneKeyCount); + var materialKeys = r.ReadStructuresAsArray(materialKeyCount); + var subViewKeys = r.ReadStructuresAsArray(subViewKeyCount); + var passes = r.ReadStructuresAsArray((int)passCount); + + ret[i] = new Node + { + Id = id, + PassIndices = passIndices, + SystemKeys = systemKeys, + SceneKeys = sceneKeys, + MaterialKeys = materialKeys, + SubViewKeys = subViewKeys, + Passes = passes, + }; } return ret; diff --git a/Penumbra/UI/Classes/ModEditWindow.Materials.cs b/Penumbra/UI/Classes/ModEditWindow.Materials.cs index d0a0c4bb..41e513d6 100644 --- a/Penumbra/UI/Classes/ModEditWindow.Materials.cs +++ b/Penumbra/UI/Classes/ModEditWindow.Materials.cs @@ -10,14 +10,13 @@ using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Internal.Notifications; using ImGuiNET; using Lumina.Data.Parsing; -using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; using Penumbra.GameData.Files; using Penumbra.String.Classes; using Penumbra.String.Functions; using Penumbra.Util; -using static OtterGui.Raii.ImRaii; +using static Penumbra.GameData.Files.ShpkFile; namespace Penumbra.UI.Classes; @@ -27,6 +26,7 @@ public partial class ModEditWindow private readonly FileDialogManager _materialFileDialog = ConfigWindow.SetupFileManager(); + private uint _materialNewKeyId = 0; private uint _materialNewConstantId = 0; private uint _materialNewSamplerId = 0; @@ -191,48 +191,165 @@ public partial class ModEditWindow ret = true; } ImRaii.TreeNode( $"Has associated ShPk file (for advanced editing): {( file.AssociatedShpk != null ? "Yes" : "No" )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - if( !disabled && ImGui.Button( "Associate modded ShPk file" ) ) + if( !disabled ) { - _materialFileDialog.OpenFileDialog( $"Associate modded ShPk file...", ".shpk", ( success, name ) => + if( ImGui.Button( "Associate custom ShPk file" ) ) { - if( !success ) + _materialFileDialog.OpenFileDialog( $"Associate custom ShPk file...", ".shpk", ( success, name ) => { - return; - } + if( !success ) + { + return; + } - try + try + { + file.AssociatedShpk = new ShpkFile( File.ReadAllBytes( name ) ); + } + catch( Exception e ) + { + Penumbra.Log.Error( $"Could not load ShPk file {name}:\n{e}" ); + ChatUtil.NotificationMessage( $"Could not load {Path.GetFileName( name )}:\n{e.Message}", "Penumbra Advanced Editing", NotificationType.Error ); + return; + } + ChatUtil.NotificationMessage( $"Advanced Shader Resources for this material will now be based on the supplied {Path.GetFileName( name )}", "Penumbra Advanced Editing", NotificationType.Success ); + } ); + } + ImGui.SameLine(); + if( ImGui.Button( "Associate default ShPk file" ) ) + { + var shpk = LoadAssociatedShpk( file.ShaderPackage.Name ); + if( null != shpk ) { - file.AssociatedShpk = new ShpkFile( File.ReadAllBytes( name ) ); + file.AssociatedShpk = shpk; + ChatUtil.NotificationMessage( $"Advanced Shader Resources for this material will now be based on the default {file.ShaderPackage.Name}", "Penumbra Advanced Editing", NotificationType.Success ); } - catch( Exception e ) + else { - Penumbra.Log.Error( $"Could not load ShPk file {name}:\n{e}" ); - ChatUtil.NotificationMessage( $"Could not load {Path.GetFileName( name )}:\n{e.Message}", "Penumbra Advanced Editing", NotificationType.Error ); - return; + ChatUtil.NotificationMessage( $"Could not load default {file.ShaderPackage.Name}", "Penumbra Advanced Editing", NotificationType.Error ); } - ChatUtil.NotificationMessage( $"Advanced Shader Resources for this material will now be based on the supplied {Path.GetFileName( name )}", "Penumbra Advanced Editing", NotificationType.Success ); - } ); + } } - if( file.ShaderPackage.ShaderKeys.Length > 0 ) + if( file.ShaderPackage.ShaderKeys.Length > 0 || !disabled && file.AssociatedShpk != null && file.AssociatedShpk.MaterialKeys.Length > 0 ) { using var t = ImRaii.TreeNode( "Shader Keys" ); if( t ) { + var definedKeys = new HashSet< uint >(); + foreach( var (key, idx) in file.ShaderPackage.ShaderKeys.WithIndex() ) { - using var t2 = ImRaii.TreeNode( $"Shader Key #{idx}", file.ShaderPackage.ShaderKeys.Length == 1 ? ImGuiTreeNodeFlags.DefaultOpen : 0 ); + definedKeys.Add( key.Category ); + using var t2 = ImRaii.TreeNode( $"#{idx}: 0x{key.Category:X8} = 0x{key.Value:X8}###{idx}: 0x{key.Category:X8}", disabled ? ImGuiTreeNodeFlags.Leaf : 0 ); if( t2 ) { - ImRaii.TreeNode( $"Category: 0x{key.Category:X8} ({key.Category})", ImGuiTreeNodeFlags.Leaf ).Dispose(); - ImRaii.TreeNode( $"Value: 0x{key.Value:X8} ({key.Value})", ImGuiTreeNodeFlags.Leaf ).Dispose(); + if( !disabled ) + { + var shpkKey = file.AssociatedShpk?.GetMaterialKeyById( key.Category ); + if( shpkKey.HasValue ) + { + ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f ); + using var c = ImRaii.Combo( "Value", $"0x{key.Value:X8}" ); + if( c ) + { + foreach( var value in shpkKey.Value.Values ) + { + if( ImGui.Selectable( $"0x{value:X8}", value == key.Value ) ) + { + file.ShaderPackage.ShaderKeys[idx].Value = value; + ret = true; + } + } + } + } + if( ImGui.Button( "Remove Key" ) ) + { + ArrayRemove( ref file.ShaderPackage.ShaderKeys, idx ); + ret = true; + } + } + } + } + + if( !disabled && file.AssociatedShpk != null ) + { + var missingKeys = file.AssociatedShpk.MaterialKeys.Where( key => !definedKeys.Contains( key.Id ) ).ToArray(); + if( missingKeys.Length > 0 ) + { + var selectedKey = Array.Find( missingKeys, key => key.Id == _materialNewKeyId ); + if( Array.IndexOf( missingKeys, selectedKey ) < 0 ) + { + selectedKey = missingKeys[0]; + _materialNewKeyId = selectedKey.Id; + } + ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f ); + using( var c = ImRaii.Combo( "##NewConstantId", $"ID: 0x{selectedKey.Id:X8}" ) ) + { + if( c ) + { + foreach( var key in missingKeys ) + { + if( ImGui.Selectable( $"ID: 0x{key.Id:X8}", key.Id == _materialNewKeyId ) ) + { + selectedKey = key; + _materialNewKeyId = key.Id; + } + } + } + } + ImGui.SameLine(); + if( ImGui.Button( "Add Key" ) ) + { + ArrayAdd( ref file.ShaderPackage.ShaderKeys, new ShaderKey + { + Category = selectedKey.Id, + Value = selectedKey.DefaultValue, + } ); + ret = true; + } } } } } + if( file.AssociatedShpk != null ) + { + var definedKeys = new Dictionary< uint, uint >(); + foreach( var key in file.ShaderPackage.ShaderKeys ) + { + definedKeys[key.Category] = key.Value; + } + var materialKeys = Array.ConvertAll(file.AssociatedShpk.MaterialKeys, key => + { + if( definedKeys.TryGetValue( key.Id, out var value ) ) + { + return value; + } + else + { + return key.DefaultValue; + } + } ); + var vertexShaders = new IndexSet( file.AssociatedShpk.VertexShaders.Length, false ); + var pixelShaders = new IndexSet( file.AssociatedShpk.PixelShaders.Length, false ); + foreach( var node in file.AssociatedShpk.Nodes ) + { + if( node.MaterialKeys.WithIndex().All( key => key.Value == materialKeys[key.Index] ) ) + { + foreach( var pass in node.Passes ) + { + vertexShaders.Add( ( int )pass.VertexShader ); + pixelShaders.Add( ( int )pass.PixelShader ); + } + } + } + ImRaii.TreeNode( $"Vertex Shaders: {( vertexShaders.Count > 0 ? string.Join( ", ", vertexShaders.Select( i => $"#{i}" ) ) : "???" )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + ImRaii.TreeNode( $"Pixel Shaders: {( pixelShaders.Count > 0 ? string.Join( ", ", pixelShaders.Select( i => $"#{i}" ) ) : "???" )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + if( file.ShaderPackage.Constants.Length > 0 || file.ShaderPackage.ShaderValues.Length > 0 - || file.AssociatedShpk != null && file.AssociatedShpk.Constants.Length > 0 ) + || !disabled && file.AssociatedShpk != null && file.AssociatedShpk.Constants.Length > 0 ) { var materialParams = file.AssociatedShpk?.GetConstantById( ShpkFile.MaterialParamsConstantId ); @@ -352,7 +469,7 @@ public partial class ModEditWindow foreach( var constant in missingConstants ) { var (constantName, _) = MaterialParamRangeName( materialParams?.Name ?? "", constant.ByteOffset >> 2, constant.ByteSize >> 2 ); - if( ImGui.Selectable( $"{constantName} (ID: 0x{constant.Id:X8})" ) ) + if( ImGui.Selectable( $"{constantName} (ID: 0x{constant.Id:X8})", constant.Id == _materialNewConstantId ) ) { selectedConstant = constant; _materialNewConstantId = constant.Id; @@ -378,7 +495,7 @@ public partial class ModEditWindow } if( file.ShaderPackage.Samplers.Length > 0 || file.Textures.Length > 0 - || file.AssociatedShpk != null && file.AssociatedShpk.Samplers.Any( sampler => sampler.Slot == 2 ) ) + || !disabled && file.AssociatedShpk != null && file.AssociatedShpk.Samplers.Any( sampler => sampler.Slot == 2 ) ) { using var t = ImRaii.TreeNode( "Samplers" ); if( t ) @@ -472,7 +589,7 @@ public partial class ModEditWindow { foreach( var sampler in missingSamplers ) { - if( ImGui.Selectable( $"{sampler.Name} (ID: 0x{sampler.Id:X8})" ) ) + if( ImGui.Selectable( $"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == _materialNewSamplerId ) ) { selectedSampler = sampler; _materialNewSamplerId = sampler.Id; diff --git a/Penumbra/UI/Classes/ModEditWindow.ShaderPackages.cs b/Penumbra/UI/Classes/ModEditWindow.ShaderPackages.cs index 4facc34f..cb3aaf3c 100644 --- a/Penumbra/UI/Classes/ModEditWindow.ShaderPackages.cs +++ b/Penumbra/UI/Classes/ModEditWindow.ShaderPackages.cs @@ -14,6 +14,7 @@ using Penumbra.GameData.Files; using Penumbra.Util; using Lumina.Data.Parsing; using static OtterGui.Raii.ImRaii; +using static Penumbra.GameData.Files.ShpkFile; namespace Penumbra.UI.Classes; @@ -76,7 +77,7 @@ public partial class ModEditWindow using var t = ImRaii.TreeNode( $"{objectName} #{idx}" ); if( t ) { - if( ImGui.Button( $"Export Shader Blob ({shader.Blob.Length} bytes)" ) ) + if( ImGui.Button( $"Export Shader Program Blob ({shader.Blob.Length} bytes)" ) ) { var extension = file.DirectXVersion switch { @@ -86,7 +87,7 @@ public partial class ModEditWindow }; var defaultName = new string( objectName.Where( char.IsUpper ).ToArray() ).ToLower() + idx.ToString(); var blob = shader.Blob; - _shaderPackageFileDialog.SaveFileDialog( $"Export {objectName} #{idx} Blob to...", extension, defaultName, extension, ( success, name ) => + _shaderPackageFileDialog.SaveFileDialog( $"Export {objectName} #{idx} Program Blob to...", extension, defaultName, extension, ( success, name ) => { if( !success ) { @@ -103,15 +104,15 @@ public partial class ModEditWindow ChatUtil.NotificationMessage( $"Could not export {defaultName}{extension} to {Path.GetFileName( name )}:\n{e.Message}", "Penumbra Advanced Editing", NotificationType.Error ); return; } - ChatUtil.NotificationMessage( $"Shader Blob {defaultName}{extension} exported successfully to {Path.GetFileName( name )}", "Penumbra Advanced Editing", NotificationType.Success ); + ChatUtil.NotificationMessage( $"Shader Program Blob {defaultName}{extension} exported successfully to {Path.GetFileName( name )}", "Penumbra Advanced Editing", NotificationType.Success ); } ); } if( !disabled ) { ImGui.SameLine(); - if( ImGui.Button( "Replace Shader Blob" ) ) + if( ImGui.Button( "Replace Shader Program Blob" ) ) { - _shaderPackageFileDialog.OpenFileDialog( $"Replace {objectName} #{idx} Blob...", "Shader Blobs{.o,.cso,.dxbc,.dxil}", ( success, name ) => + _shaderPackageFileDialog.OpenFileDialog( $"Replace {objectName} #{idx} Program Blob...", "Shader Program Blobs{.o,.cso,.dxbc,.dxil}", ( success, name ) => { if( !success ) { @@ -146,10 +147,9 @@ public partial class ModEditWindow } } - ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, disabled ); - ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, disabled ); - ret |= DrawShaderPackageResourceArray( "Unknown Type X Resources", "slot", true, shader.UnknownX, disabled ); - ret |= DrawShaderPackageResourceArray( "Unknown Type Y Resources", "slot", true, shader.UnknownY, disabled ); + ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, true ); + ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, true ); + ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "slot", true, shader.UAVs, true ); if( shader.AdditionalHeader.Length > 0 ) { @@ -160,7 +160,7 @@ public partial class ModEditWindow } } - using( var t2 = ImRaii.TreeNode( "Raw Disassembly" ) ) + using( var t2 = ImRaii.TreeNode( "Raw Program Disassembly" ) ) { if( t2 ) { @@ -326,7 +326,7 @@ public partial class ModEditWindow foreach( var start in starts ) { var name = MaterialParamName( false, start )!; - if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}" ) ) + if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}", start == _shaderPackageNewMaterialParamStart ) ) { _shaderPackageNewMaterialParamStart = ( ushort )start; } @@ -352,7 +352,7 @@ public partial class ModEditWindow foreach( var end in ends ) { var name = MaterialParamName( false, end )!; - if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}" ) ) + if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}", end == _shaderPackageNewMaterialParamEnd ) ) { _shaderPackageNewMaterialParamEnd = ( ushort )end; } @@ -390,7 +390,7 @@ public partial class ModEditWindow return ret; } - private static bool DrawShaderPackageResourceArray( string arrayName, string slotLabel, bool withSize, ShpkFile.Resource[] resources, bool _ ) + private static bool DrawShaderPackageResourceArray( string arrayName, string slotLabel, bool withSize, ShpkFile.Resource[] resources, bool disabled ) { if( resources.Length == 0 ) { @@ -407,65 +407,85 @@ public partial class ModEditWindow foreach( var (buf, idx) in resources.WithIndex() ) { - using var t2 = ImRaii.TreeNode( $"#{idx}: {buf.Name} (ID: 0x{buf.Id:X8}), {slotLabel}: {buf.Slot}" + ( withSize ? $", size: {buf.Size} registers" : string.Empty ), ( buf.Used != null ) ? 0 : ImGuiTreeNodeFlags.Leaf ); + using var t2 = ImRaii.TreeNode( $"#{idx}: {buf.Name} (ID: 0x{buf.Id:X8}), {slotLabel}: {buf.Slot}" + ( withSize ? $", size: {buf.Size} registers###{idx}: {buf.Name} (ID: 0x{buf.Id:X8})" : string.Empty ), ( !disabled || buf.Used != null ) ? 0 : ImGuiTreeNodeFlags.Leaf ); if( t2 ) { - var used = new List< string >(); - if( withSize ) + if( !disabled ) { - foreach( var (components, i) in ( buf.Used ?? Array.Empty() ).WithIndex() ) + // FIXME this probably doesn't belong here + static unsafe bool InputUInt16( string label, ref ushort v, ImGuiInputTextFlags flags ) { - switch( components ) + fixed( ushort* v2 = &v ) + { + return ImGui.InputScalar( label, ImGuiDataType.U16, new nint( v2 ), nint.Zero, nint.Zero, "%hu", flags ); + } + } + + ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f ); + if( InputUInt16( $"{char.ToUpper( slotLabel[0] )}{slotLabel[1..].ToLower()}", ref resources[idx].Slot, ImGuiInputTextFlags.None ) ) + { + ret = true; + } + } + if( buf.Used != null ) + { + var used = new List(); + if( withSize ) + { + foreach( var (components, i) in ( buf.Used ?? Array.Empty() ).WithIndex() ) + { + switch( components ) + { + case 0: + break; + case DisassembledShader.VectorComponents.All: + used.Add( $"[{i}]" ); + break; + default: + used.Add( $"[{i}].{new string( components.ToString().Where( char.IsUpper ).ToArray() ).ToLower()}" ); + break; + } + } + switch( buf.UsedDynamically ?? 0 ) { case 0: break; case DisassembledShader.VectorComponents.All: - used.Add( $"[{i}]" ); + used.Add( "[*]" ); break; default: - used.Add( $"[{i}].{new string( components.ToString().Where( char.IsUpper ).ToArray() ).ToLower()}" ); + used.Add( $"[*].{new string( buf.UsedDynamically!.Value.ToString().Where( char.IsUpper ).ToArray() ).ToLower()}" ); break; } } - switch( buf.UsedDynamically ?? 0 ) + else { - case 0: - break; - case DisassembledShader.VectorComponents.All: - used.Add( "[*]" ); - break; - default: - used.Add( $"[*].{new string( buf.UsedDynamically!.Value.ToString().Where( char.IsUpper ).ToArray() ).ToLower()}" ); - break; + var components = ( ( buf.Used != null && buf.Used.Length > 0 ) ? buf.Used[0] : 0 ) | ( buf.UsedDynamically ?? 0 ); + if( ( components & DisassembledShader.VectorComponents.X ) != 0 ) + { + used.Add( "Red" ); + } + if( ( components & DisassembledShader.VectorComponents.Y ) != 0 ) + { + used.Add( "Green" ); + } + if( ( components & DisassembledShader.VectorComponents.Z ) != 0 ) + { + used.Add( "Blue" ); + } + if( ( components & DisassembledShader.VectorComponents.W ) != 0 ) + { + used.Add( "Alpha" ); + } } - } - else - { - var components = ( ( buf.Used != null && buf.Used.Length > 0 ) ? buf.Used[0] : 0 ) | (buf.UsedDynamically ?? 0); - if( ( components & DisassembledShader.VectorComponents.X ) != 0 ) + if( used.Count > 0 ) { - used.Add( "Red" ); + ImRaii.TreeNode( $"Used: {string.Join( ", ", used )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); } - if( ( components & DisassembledShader.VectorComponents.Y ) != 0 ) + else { - used.Add( "Green" ); + ImRaii.TreeNode( "Unused", ImGuiTreeNodeFlags.Leaf ).Dispose(); } - if( ( components & DisassembledShader.VectorComponents.Z ) != 0 ) - { - used.Add( "Blue" ); - } - if( ( components & DisassembledShader.VectorComponents.W ) != 0 ) - { - used.Add( "Alpha" ); - } - } - if( used.Count > 0 ) - { - ImRaii.TreeNode( $"Used: {string.Join(", ", used)}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - } - else - { - ImRaii.TreeNode( "Unused", ImGuiTreeNodeFlags.Leaf ).Dispose(); } } } @@ -482,56 +502,91 @@ public partial class ModEditWindow return false; } + ImRaii.TreeNode( $"Version: 0x{file.Version:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, file.Constants, disabled ); ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, file.Samplers, disabled ); + ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "type", false, file.UAVs, disabled ); - if( file.UnknownA.Length > 0 ) + static bool DrawKeyArray( string arrayName, bool withId, ShpkFile.Key[] keys, bool _ ) { - using var t = ImRaii.TreeNode( $"Unknown Type A Structures ({file.UnknownA.Length})" ); + if( keys.Length == 0 ) + { + return false; + } + + using var t = ImRaii.TreeNode( arrayName ); + if( !t ) + { + return false; + } + + foreach( var (key, idx) in keys.WithIndex() ) + { + using var t2 = ImRaii.TreeNode( withId ? $"#{idx}: ID: 0x{key.Id:X8}" : $"#{idx}" ); + if( t2 ) + { + ImRaii.TreeNode( $"Default Value: 0x{key.DefaultValue:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + ImRaii.TreeNode( $"Known Values: {string.Join( ", ", Array.ConvertAll( key.Values, value => $"0x{value:X8}" ) )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + } + + return false; + } + + ret |= DrawKeyArray( "System Keys", true, file.SystemKeys, disabled ); + ret |= DrawKeyArray( "Scene Keys", true, file.SceneKeys, disabled ); + ret |= DrawKeyArray( "Material Keys", true, file.MaterialKeys, disabled ); + ret |= DrawKeyArray( "Sub-View Keys", false, file.SubViewKeys, disabled ); + + if( file.Nodes.Length > 0 ) + { + using var t = ImRaii.TreeNode( $"Nodes ({file.Nodes.Length})" ); if( t ) { - foreach( var (unk, idx) in file.UnknownA.WithIndex() ) + foreach( var (node, idx) in file.Nodes.WithIndex() ) { - ImRaii.TreeNode( $"#{idx}: 0x{unk.Item1:X8}, 0x{unk.Item2:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + using var t2 = ImRaii.TreeNode( $"#{idx}: ID: 0x{node.Id:X8}" ); + if( t2 ) + { + foreach( var (key, keyIdx) in node.SystemKeys.WithIndex() ) + { + ImRaii.TreeNode( $"System Key 0x{file.SystemKeys[keyIdx].Id:X8} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + foreach( var (key, keyIdx) in node.SceneKeys.WithIndex() ) + { + ImRaii.TreeNode( $"Scene Key 0x{file.SceneKeys[keyIdx].Id:X8} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + foreach( var (key, keyIdx) in node.MaterialKeys.WithIndex() ) + { + ImRaii.TreeNode( $"Material Key 0x{file.MaterialKeys[keyIdx].Id:X8} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + foreach( var (key, keyIdx) in node.SubViewKeys.WithIndex() ) + { + ImRaii.TreeNode( $"Sub-View Key #{keyIdx} = 0x{key:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + ImRaii.TreeNode( $"Pass Indices: {string.Join( ' ', node.PassIndices.Select( c => $"{c:X2}" ) )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + foreach( var (pass, passIdx) in node.Passes.WithIndex() ) + { + ImRaii.TreeNode( $"Pass #{passIdx}: ID: 0x{pass.Id:X8}, Vertex Shader #{pass.VertexShader}, Pixel Shader #{pass.PixelShader}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + } + } } } } - if( file.UnknownB.Length > 0 ) + if( file.Items.Length > 0 ) { - using var t = ImRaii.TreeNode( $"Unknown Type B Structures ({file.UnknownB.Length})" ); + using var t = ImRaii.TreeNode( $"Items ({file.Items.Length})" ); if( t ) { - foreach( var (unk, idx) in file.UnknownB.WithIndex() ) + foreach( var (item, idx) in file.Items.WithIndex() ) { - ImRaii.TreeNode( $"#{idx}: 0x{unk.Item1:X8}, 0x{unk.Item2:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); + ImRaii.TreeNode( $"#{idx}: ID: 0x{item.Id:X8}, node: {item.Node}", ImGuiTreeNodeFlags.Leaf ).Dispose(); } } } - if( file.UnknownC.Length > 0 ) - { - using var t = ImRaii.TreeNode( $"Unknown Type C Structures ({file.UnknownC.Length})" ); - if( t ) - { - foreach( var (unk, idx) in file.UnknownC.WithIndex() ) - { - ImRaii.TreeNode( $"#{idx}: 0x{unk.Item1:X8}, 0x{unk.Item2:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - } - } - } - - using( var t = ImRaii.TreeNode( $"Misc. Unknown Fields" ) ) - { - if( t ) - { - ImRaii.TreeNode( $"#1 (at 0x0004): 0x{file.Unknown1:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - ImRaii.TreeNode( $"#2 (at 0x003C): 0x{file.Unknown2:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - ImRaii.TreeNode( $"#3 (at 0x0040): 0x{file.Unknown3:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - ImRaii.TreeNode( $"#4 (at 0x0044): 0x{file.Unknown4:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose(); - } - } - if( file.AdditionalData.Length > 0 ) { using var t = ImRaii.TreeNode( $"Additional Data (Size: {file.AdditionalData.Length})###AdditionalData" ); @@ -541,17 +596,6 @@ public partial class ModEditWindow } } - using( var t = ImRaii.TreeNode( $"String Pool" ) ) - { - if( t ) - { - foreach( var offset in file.Strings.StartingOffsets ) - { - ImGui.Text( file.Strings.GetNullTerminatedString( offset ) ); - } - } - } - return ret; } } \ No newline at end of file