More Mtrl and ShPk editing (thanks @aers)

This commit is contained in:
Exter-N 2023-02-15 22:20:38 +01:00 committed by Ottermandias
parent 0c17892f03
commit 33231959b2
4 changed files with 648 additions and 247 deletions

View file

@ -7,19 +7,25 @@ public partial class ShpkFile
{ {
public byte[] Write() public byte[] Write()
{ {
if (SubViewKeys.Length != 2)
{
throw new InvalidDataException();
}
using var stream = new MemoryStream(); using var stream = new MemoryStream();
using var blobs = new MemoryStream(); using var blobs = new MemoryStream();
var strings = new StringPool(ReadOnlySpan<byte>.Empty);
using (var w = new BinaryWriter(stream)) using (var w = new BinaryWriter(stream))
{ {
w.Write(ShPkMagic); w.Write(ShPkMagic);
w.Write(Unknown1); w.Write(Version);
w.Write(DirectXVersion switch w.Write(DirectXVersion switch
{ {
DXVersion.DirectX9 => DX9Magic, DXVersion.DirectX9 => DX9Magic,
DXVersion.DirectX11 => DX11Magic, DXVersion.DirectX11 => DX11Magic,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}); });
long offsetsPosition = stream.Position; var offsetsPosition = stream.Position;
w.Write(0u); // Placeholder for file size w.Write(0u); // Placeholder for file size
w.Write(0u); // Placeholder for blobs offset w.Write(0u); // Placeholder for blobs offset
w.Write(0u); // Placeholder for strings offset w.Write(0u); // Placeholder for strings offset
@ -29,15 +35,15 @@ public partial class ShpkFile
w.Write((uint)MaterialParams.Length); w.Write((uint)MaterialParams.Length);
w.Write((uint)Constants.Length); w.Write((uint)Constants.Length);
w.Write((uint)Samplers.Length); w.Write((uint)Samplers.Length);
w.Write((uint)UnknownA.Length); w.Write((uint)UAVs.Length);
w.Write((uint)UnknownB.Length); w.Write((uint)SystemKeys.Length);
w.Write((uint)UnknownC.Length); w.Write((uint)SceneKeys.Length);
w.Write(Unknown2); w.Write((uint)MaterialKeys.Length);
w.Write(Unknown3); w.Write((uint)Nodes.Length);
w.Write(Unknown4); w.Write((uint)Items.Length);
WriteShaderArray(w, VertexShaders, blobs, Strings); WriteShaderArray(w, VertexShaders, blobs, strings);
WriteShaderArray(w, PixelShaders, blobs, Strings); WriteShaderArray(w, PixelShaders, blobs, strings);
foreach (var materialParam in MaterialParams) foreach (var materialParam in MaterialParams)
{ {
@ -46,16 +52,68 @@ public partial class ShpkFile
w.Write(materialParam.ByteSize); w.Write(materialParam.ByteSize);
} }
WriteResourceArray(w, Constants, Strings); WriteResourceArray(w, Constants, strings);
WriteResourceArray(w, Samplers, Strings); WriteResourceArray(w, Samplers, strings);
WriteResourceArray(w, UAVs, strings);
w.Write(Unknowns.Item1); foreach (var key in SystemKeys)
w.Write(Unknowns.Item2); {
w.Write(Unknowns.Item3); 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); foreach (var node in Nodes)
WriteUInt32PairArray(w, UnknownB); {
WriteUInt32PairArray(w, UnknownC); 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); w.Write(AdditionalData);
@ -63,7 +121,7 @@ public partial class ShpkFile
blobs.WriteTo(stream); blobs.WriteTo(stream);
var stringsOffset = (int)stream.Position; var stringsOffset = (int)stream.Position;
Strings.Data.WriteTo(stream); strings.Data.WriteTo(stream);
var fileSize = (int)stream.Position; var fileSize = (int)stream.Position;
@ -102,13 +160,12 @@ public partial class ShpkFile
w.Write(blobSize); w.Write(blobSize);
w.Write((ushort)shader.Constants.Length); w.Write((ushort)shader.Constants.Length);
w.Write((ushort)shader.Samplers.Length); w.Write((ushort)shader.Samplers.Length);
w.Write((ushort)shader.UnknownX.Length); w.Write((ushort)shader.UAVs.Length);
w.Write((ushort)shader.UnknownY.Length); w.Write((ushort)0);
WriteResourceArray(w, shader.Constants, strings); WriteResourceArray(w, shader.Constants, strings);
WriteResourceArray(w, shader.Samplers, strings); WriteResourceArray(w, shader.Samplers, strings);
WriteResourceArray(w, shader.UnknownX, strings); WriteResourceArray(w, shader.UAVs, strings);
WriteResourceArray(w, shader.UnknownY, strings);
} }
} }

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Lumina.Data.Parsing;
using Lumina.Extensions; using Lumina.Extensions;
using Lumina.Misc; using Lumina.Misc;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
@ -33,8 +34,7 @@ public partial class ShpkFile : IWritable
public DXVersion DirectXVersion; public DXVersion DirectXVersion;
public Resource[] Constants; public Resource[] Constants;
public Resource[] Samplers; public Resource[] Samplers;
public Resource[] UnknownX; public Resource[] UAVs;
public Resource[] UnknownY;
public byte[] AdditionalHeader; public byte[] AdditionalHeader;
private byte[] _blob; private byte[] _blob;
private DisassembledShader? _disassembly; 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); 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) public void UpdateResources(ShpkFile file)
{ {
if (_disassembly == null) if (_disassembly == null)
@ -119,39 +129,56 @@ public partial class ShpkFile : IWritable
} }
var constants = new List<Resource>(); var constants = new List<Resource>();
var samplers = new List<Resource>(); var samplers = new List<Resource>();
var uavs = new List<Resource>();
foreach (var binding in _disassembly.ResourceBindings) foreach (var binding in _disassembly.ResourceBindings)
{ {
switch (binding.Type) switch (binding.Type)
{ {
case DisassembledShader.ResourceType.ConstantBuffer: case DisassembledShader.ResourceType.ConstantBuffer:
var name = NormalizeResourceName(binding.Name); var name = NormalizeResourceName(binding.Name);
// We want to preserve IDs as much as possible, and to deterministically generate new ones, to maximize compatibility. // 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); var id = GetConstantByName(name)?.Id ?? file.GetConstantByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
constants.Add(new Resource constants.Add(new Resource
{ {
Id = id, Id = id,
Name = name, Name = name,
Slot = (ushort)binding.Slot, Slot = (ushort)binding.Slot,
Size = (ushort)binding.RegisterCount, Size = (ushort)binding.RegisterCount,
Used = binding.Used, Used = binding.Used,
UsedDynamically = binding.UsedDynamically,
}); });
break; break;
case DisassembledShader.ResourceType.Texture: case DisassembledShader.ResourceType.Texture:
name = NormalizeResourceName(binding.Name); 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 samplers.Add(new Resource
{ {
Id = id, Id = id,
Name = name, Name = name,
Slot = (ushort)binding.Slot, Slot = (ushort)binding.Slot,
Size = (ushort)binding.Slot, Size = (ushort)binding.Slot,
Used = binding.Used, 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; break;
} }
} }
Constants = constants.ToArray(); Constants = constants.ToArray();
Samplers = samplers.ToArray(); Samplers = samplers.ToArray();
UAVs = uavs.ToArray();
} }
private void UpdateUsed() private void UpdateUsed()
@ -160,6 +187,7 @@ public partial class ShpkFile : IWritable
{ {
var cbUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>(); var cbUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
var tUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>(); var tUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
var uUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
foreach (var binding in _disassembly.ResourceBindings) foreach (var binding in _disassembly.ResourceBindings)
{ {
switch (binding.Type) switch (binding.Type)
@ -170,39 +198,36 @@ public partial class ShpkFile : IWritable
case DisassembledShader.ResourceType.Texture: case DisassembledShader.ResourceType.Texture:
tUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically); tUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
break; 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<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> used)
{ {
if (cbUsage.TryGetValue(Constants[i].Name, out var usage)) for (var i = 0; i < resources.Length; ++i)
{ {
Constants[i].Used = usage.Item1; if (used.TryGetValue(resources[i].Name, out var usage))
Constants[i].UsedDynamically = usage.Item2; {
} resources[i].Used = usage.Item1;
else resources[i].UsedDynamically = usage.Item2;
{ }
Constants[i].Used = null; else
Constants[i].UsedDynamically = null; {
} resources[i].Used = null;
} resources[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;
} }
} }
CopyUsed(Constants, cbUsage);
CopyUsed(Samplers, tUsage);
CopyUsed(UAVs, uUsage);
} }
else else
{ {
ClearUsed(Constants); ClearUsed(Constants);
ClearUsed(Samplers); ClearUsed(Samplers);
ClearUsed(UAVs);
} }
} }
@ -231,6 +256,37 @@ public partial class ShpkFile : IWritable
public ushort ByteSize; 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 class StringPool
{ {
public MemoryStream Data; public MemoryStream Data;
@ -317,7 +373,7 @@ public partial class ShpkFile : IWritable
public const uint MaterialParamsConstantId = 0x64D12851u; public const uint MaterialParamsConstantId = 0x64D12851u;
public uint Unknown1; public uint Version;
public DXVersion DirectXVersion; public DXVersion DirectXVersion;
public Shader[] VertexShaders; public Shader[] VertexShaders;
public Shader[] PixelShaders; public Shader[] PixelShaders;
@ -325,15 +381,14 @@ public partial class ShpkFile : IWritable
public MaterialParam[] MaterialParams; public MaterialParam[] MaterialParams;
public Resource[] Constants; public Resource[] Constants;
public Resource[] Samplers; public Resource[] Samplers;
public (uint, uint)[] UnknownA; public Resource[] UAVs;
public (uint, uint)[] UnknownB; public Key[] SystemKeys;
public (uint, uint)[] UnknownC; public Key[] SceneKeys;
public uint Unknown2; public Key[] MaterialKeys;
public uint Unknown3; public Key[] SubViewKeys;
public uint Unknown4; public Node[] Nodes;
public (uint, uint, uint) Unknowns; public Item[] Items;
public byte[] AdditionalData; 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; } public bool Valid { get; private set; }
private bool _changed; 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); 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 // Activator.CreateInstance can't use a ctor with a default value so this has to be made explicit
public ShpkFile(byte[] data) public ShpkFile(byte[] data)
: this(data, false) : this(data, false)
@ -378,7 +468,7 @@ public partial class ShpkFile : IWritable
{ {
throw new InvalidDataException(); throw new InvalidDataException();
} }
Unknown1 = r.ReadUInt32(); Version = r.ReadUInt32();
DirectXVersion = r.ReadUInt32() switch DirectXVersion = r.ReadUInt32() switch
{ {
DX9Magic => DXVersion.DirectX9, DX9Magic => DXVersion.DirectX9,
@ -397,40 +487,59 @@ public partial class ShpkFile : IWritable
var materialParamCount = r.ReadUInt32(); var materialParamCount = r.ReadUInt32();
var constantCount = r.ReadUInt32(); var constantCount = r.ReadUInt32();
var samplerCount = r.ReadUInt32(); var samplerCount = r.ReadUInt32();
var unknownACount = r.ReadUInt32(); var uavCount = r.ReadUInt32();
var unknownBCount = r.ReadUInt32(); var systemKeyCount = r.ReadUInt32();
var unknownCCount = r.ReadUInt32(); var sceneKeyCount = r.ReadUInt32();
Unknown2 = r.ReadUInt32(); var materialKeyCount = r.ReadUInt32();
Unknown3 = r.ReadUInt32(); var nodeCount = r.ReadUInt32();
Unknown4 = r.ReadUInt32(); var itemCount = r.ReadUInt32();
var blobs = new ReadOnlySpan<byte>(data, (int)blobsOffset, (int)(stringsOffset - blobsOffset)); var blobs = new ReadOnlySpan<byte>(data, (int)blobsOffset, (int)(stringsOffset - blobsOffset));
Strings = new StringPool(new ReadOnlySpan<byte>(data, (int)stringsOffset, (int)(data.Length - stringsOffset))); var strings = new StringPool(new ReadOnlySpan<byte>(data, (int)stringsOffset, (int)(data.Length - stringsOffset)));
VertexShaders = ReadShaderArray(r, (int)vertexShaderCount, DisassembledShader.ShaderStage.Vertex, 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); PixelShaders = ReadShaderArray(r, (int)pixelShaderCount, DisassembledShader.ShaderStage.Pixel, DirectXVersion, disassemble, blobs, strings);
MaterialParams = r.ReadStructuresAsArray<MaterialParam>((int)materialParamCount); MaterialParams = r.ReadStructuresAsArray<MaterialParam>((int)materialParamCount);
Constants = ReadResourceArray(r, (int)constantCount, Strings); Constants = ReadResourceArray(r, (int)constantCount, strings);
Samplers = ReadResourceArray(r, (int)samplerCount, Strings); Samplers = ReadResourceArray(r, (int)samplerCount, strings);
UAVs = ReadResourceArray(r, (int)uavCount, strings);
var unk1 = r.ReadUInt32(); SystemKeys = ReadKeyArray(r, (int)systemKeyCount);
var unk2 = r.ReadUInt32(); SceneKeys = ReadKeyArray(r, (int)sceneKeyCount);
var unk3 = r.ReadUInt32(); MaterialKeys = ReadKeyArray(r, (int)materialKeyCount);
Unknowns = (unk1, unk2, unk3);
UnknownA = ReadUInt32PairArray(r, (int)unknownACount); var subViewKey1Default = r.ReadUInt32();
UnknownB = ReadUInt32PairArray(r, (int)unknownBCount); var subViewKey2Default = r.ReadUInt32();
UnknownC = ReadUInt32PairArray(r, (int)unknownCCount);
AdditionalData = r.ReadBytes((int)(blobsOffset - r.BaseStream.Position)); SubViewKeys = new Key[] {
new Key
{
Id = 1,
DefaultValue = subViewKey1Default,
Values = Array.Empty<uint>(),
},
new Key
{
Id = 2,
DefaultValue = subViewKey2Default,
Values = Array.Empty<uint>(),
},
};
Nodes = ReadNodeArray(r, (int)nodeCount, SystemKeys.Length, SceneKeys.Length, MaterialKeys.Length, SubViewKeys.Length);
Items = r.ReadStructuresAsArray<Item>((int)itemCount);
AdditionalData = r.ReadBytes((int)(blobsOffset - r.BaseStream.Position)); // This should be empty, but just in case.
if (disassemble) if (disassemble)
{ {
UpdateUsed(); UpdateUsed();
} }
UpdateKeyValues();
Valid = true; Valid = true;
_changed = false; _changed = false;
} }
@ -439,11 +548,12 @@ public partial class ShpkFile : IWritable
{ {
var constants = new Dictionary<uint, Resource>(); var constants = new Dictionary<uint, Resource>();
var samplers = new Dictionary<uint, Resource>(); var samplers = new Dictionary<uint, Resource>();
static void CollectResources(Dictionary<uint, Resource> resources, Resource[] shaderResources, Func<uint, Resource?> getExistingById, bool isSamplers) var uavs = new Dictionary<uint, Resource>();
static void CollectResources(Dictionary<uint, Resource> resources, Resource[] shaderResources, Func<uint, Resource?> getExistingById, DisassembledShader.ResourceType type)
{ {
foreach (var resource in shaderResources) 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; continue;
} }
@ -452,8 +562,8 @@ public partial class ShpkFile : IWritable
{ {
Id = resource.Id, Id = resource.Id,
Name = resource.Name, Name = resource.Name,
Slot = existing?.Slot ?? (isSamplers ? (ushort)2 : (ushort)65535), Slot = existing?.Slot ?? (type == DisassembledShader.ResourceType.ConstantBuffer ? (ushort)65535 : (ushort)2),
Size = isSamplers ? (existing?.Size ?? 0) : Math.Max(carry.Size, resource.Size), Size = type == DisassembledShader.ResourceType.ConstantBuffer ? Math.Max(carry.Size, resource.Size) : (existing?.Size ?? 0),
Used = null, Used = null,
UsedDynamically = null, UsedDynamically = null,
}; };
@ -461,16 +571,19 @@ public partial class ShpkFile : IWritable
} }
foreach (var shader in VertexShaders) foreach (var shader in VertexShaders)
{ {
CollectResources(constants, shader.Constants, GetConstantById, false); CollectResources(constants, shader.Constants, GetConstantById, DisassembledShader.ResourceType.ConstantBuffer);
CollectResources(samplers, shader.Samplers, GetSamplerById, true); CollectResources(samplers, shader.Samplers, GetSamplerById, DisassembledShader.ResourceType.Sampler);
CollectResources(uavs, shader.UAVs, GetUAVById, DisassembledShader.ResourceType.UAV);
} }
foreach (var shader in PixelShaders) foreach (var shader in PixelShaders)
{ {
CollectResources(constants, shader.Constants, GetConstantById, false); CollectResources(constants, shader.Constants, GetConstantById, DisassembledShader.ResourceType.ConstantBuffer);
CollectResources(samplers, shader.Samplers, GetSamplerById, true); CollectResources(samplers, shader.Samplers, GetSamplerById, DisassembledShader.ResourceType.Sampler);
CollectResources(uavs, shader.UAVs, GetUAVById, DisassembledShader.ResourceType.UAV);
} }
Constants = constants.Values.ToArray(); Constants = constants.Values.ToArray();
Samplers = samplers.Values.ToArray(); Samplers = samplers.Values.ToArray();
UAVs = uavs.Values.ToArray();
UpdateUsed(); UpdateUsed();
MaterialParamsSize = (GetConstantById(MaterialParamsConstantId)?.Size ?? 0u) << 4; MaterialParamsSize = (GetConstantById(MaterialParamsConstantId)?.Size ?? 0u) << 4;
foreach (var param in MaterialParams) foreach (var param in MaterialParams)
@ -484,7 +597,8 @@ public partial class ShpkFile : IWritable
{ {
var cUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>(); var cUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
var sUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>(); var sUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
static void CollectUsage(Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> usage, Resource[] resources) var uUsage = new Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
static void CollectUsed(Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> usage, Resource[] resources)
{ {
foreach (var resource in resources) foreach (var resource in resources)
{ {
@ -502,42 +616,75 @@ public partial class ShpkFile : IWritable
usage[resource.Id] = (combined, carry.Item2 | (resource.UsedDynamically ?? 0)); usage[resource.Id] = (combined, carry.Item2 | (resource.UsedDynamically ?? 0));
} }
} }
static void CopyUsed(Resource[] resources, Dictionary<uint, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> 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) foreach (var shader in VertexShaders)
{ {
CollectUsage(cUsage, shader.Constants); CollectUsed(cUsage, shader.Constants);
CollectUsage(sUsage, shader.Samplers); CollectUsed(sUsage, shader.Samplers);
CollectUsed(uUsage, shader.UAVs);
} }
foreach (var shader in PixelShaders) foreach (var shader in PixelShaders)
{ {
CollectUsage(cUsage, shader.Constants); CollectUsed(cUsage, shader.Constants);
CollectUsage(sUsage, shader.Samplers); 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<uint>[] InitializeValueSet(Key[] keys)
=> Array.ConvertAll(keys, key => new HashSet<uint>()
{
key.DefaultValue,
});
static void CollectValues(HashSet<uint>[] 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; valueSets[i].Add(values[i]);
Constants[i].UsedDynamically = usage.Item2;
}
else
{
Constants[i].Used = null;
Constants[i].UsedDynamically = null;
} }
} }
for (var i = 0; i < Samplers.Length; ++i) static void CopyValues(Key[] keys, HashSet<uint>[] valueSets)
{ {
if (sUsage.TryGetValue(Samplers[i].Id, out var usage)) for (var i = 0; i < keys.Length; ++i)
{ {
Samplers[i].Used = usage.Item1; keys[i].Values = valueSets[i].ToArray();
Samplers[i].UsedDynamically = usage.Item2;
}
else
{
Samplers[i].Used = null;
Samplers[i].UsedDynamically = null;
} }
} }
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() public void SetInvalid()
@ -606,8 +753,11 @@ public partial class ShpkFile : IWritable
var blobSize = r.ReadUInt32(); var blobSize = r.ReadUInt32();
var constantCount = r.ReadUInt16(); var constantCount = r.ReadUInt16();
var samplerCount = r.ReadUInt16(); var samplerCount = r.ReadUInt16();
var unknownXCount = r.ReadUInt16(); var uavCount = r.ReadUInt16();
var unknownYCount = r.ReadUInt16(); if (r.ReadUInt16() != 0)
{
throw new NotImplementedException();
}
var rawBlob = blobs.Slice((int)blobOffset, (int)blobSize); var rawBlob = blobs.Slice((int)blobOffset, (int)blobSize);
@ -617,8 +767,7 @@ public partial class ShpkFile : IWritable
shader.DirectXVersion = directX; shader.DirectXVersion = directX;
shader.Constants = ReadResourceArray(r, constantCount, strings); shader.Constants = ReadResourceArray(r, constantCount, strings);
shader.Samplers = ReadResourceArray(r, samplerCount, strings); shader.Samplers = ReadResourceArray(r, samplerCount, strings);
shader.UnknownX = ReadResourceArray(r, unknownXCount, strings); shader.UAVs = ReadResourceArray(r, uavCount, strings);
shader.UnknownY = ReadResourceArray(r, unknownYCount, strings);
shader.AdditionalHeader = rawBlob[..extraHeaderSize].ToArray(); shader.AdditionalHeader = rawBlob[..extraHeaderSize].ToArray();
shader.Blob = rawBlob[extraHeaderSize..].ToArray(); shader.Blob = rawBlob[extraHeaderSize..].ToArray();
@ -628,15 +777,49 @@ public partial class ShpkFile : IWritable
return ret; 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) for (var i = 0; i < count; ++i)
{ {
var first = r.ReadUInt32(); var id = r.ReadUInt32();
var second = r.ReadUInt32(); var defaultValue = r.ReadUInt32();
ret[i] = (first, second); ret[i] = new Key
{
Id = id,
DefaultValue = defaultValue,
Values = Array.Empty<uint>(),
};
}
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<uint>(systemKeyCount);
var sceneKeys = r.ReadStructuresAsArray<uint>(sceneKeyCount);
var materialKeys = r.ReadStructuresAsArray<uint>(materialKeyCount);
var subViewKeys = r.ReadStructuresAsArray<uint>(subViewKeyCount);
var passes = r.ReadStructuresAsArray<Pass>((int)passCount);
ret[i] = new Node
{
Id = id,
PassIndices = passIndices,
SystemKeys = systemKeys,
SceneKeys = sceneKeys,
MaterialKeys = materialKeys,
SubViewKeys = subViewKeys,
Passes = passes,
};
} }
return ret; return ret;

View file

@ -10,14 +10,13 @@ using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using ImGuiNET; using ImGuiNET;
using Lumina.Data.Parsing; using Lumina.Data.Parsing;
using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.String.Functions; using Penumbra.String.Functions;
using Penumbra.Util; using Penumbra.Util;
using static OtterGui.Raii.ImRaii; using static Penumbra.GameData.Files.ShpkFile;
namespace Penumbra.UI.Classes; namespace Penumbra.UI.Classes;
@ -27,6 +26,7 @@ public partial class ModEditWindow
private readonly FileDialogManager _materialFileDialog = ConfigWindow.SetupFileManager(); private readonly FileDialogManager _materialFileDialog = ConfigWindow.SetupFileManager();
private uint _materialNewKeyId = 0;
private uint _materialNewConstantId = 0; private uint _materialNewConstantId = 0;
private uint _materialNewSamplerId = 0; private uint _materialNewSamplerId = 0;
@ -191,48 +191,165 @@ public partial class ModEditWindow
ret = true; ret = true;
} }
ImRaii.TreeNode( $"Has associated ShPk file (for advanced editing): {( file.AssociatedShpk != null ? "Yes" : "No" )}", ImGuiTreeNodeFlags.Leaf ).Dispose(); 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 default {file.ShaderPackage.Name}", "Penumbra Advanced Editing", NotificationType.Error );
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 ); }
} );
} }
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" ); using var t = ImRaii.TreeNode( "Shader Keys" );
if( t ) if( t )
{ {
var definedKeys = new HashSet< uint >();
foreach( var (key, idx) in file.ShaderPackage.ShaderKeys.WithIndex() ) 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 ) if( t2 )
{ {
ImRaii.TreeNode( $"Category: 0x{key.Category:X8} ({key.Category})", ImGuiTreeNodeFlags.Leaf ).Dispose(); if( !disabled )
ImRaii.TreeNode( $"Value: 0x{key.Value:X8} ({key.Value})", ImGuiTreeNodeFlags.Leaf ).Dispose(); {
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 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 ); var materialParams = file.AssociatedShpk?.GetConstantById( ShpkFile.MaterialParamsConstantId );
@ -352,7 +469,7 @@ public partial class ModEditWindow
foreach( var constant in missingConstants ) foreach( var constant in missingConstants )
{ {
var (constantName, _) = MaterialParamRangeName( materialParams?.Name ?? "", constant.ByteOffset >> 2, constant.ByteSize >> 2 ); 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; selectedConstant = constant;
_materialNewConstantId = constant.Id; _materialNewConstantId = constant.Id;
@ -378,7 +495,7 @@ public partial class ModEditWindow
} }
if( file.ShaderPackage.Samplers.Length > 0 || file.Textures.Length > 0 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" ); using var t = ImRaii.TreeNode( "Samplers" );
if( t ) if( t )
@ -472,7 +589,7 @@ public partial class ModEditWindow
{ {
foreach( var sampler in missingSamplers ) 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; selectedSampler = sampler;
_materialNewSamplerId = sampler.Id; _materialNewSamplerId = sampler.Id;

View file

@ -14,6 +14,7 @@ using Penumbra.GameData.Files;
using Penumbra.Util; using Penumbra.Util;
using Lumina.Data.Parsing; using Lumina.Data.Parsing;
using static OtterGui.Raii.ImRaii; using static OtterGui.Raii.ImRaii;
using static Penumbra.GameData.Files.ShpkFile;
namespace Penumbra.UI.Classes; namespace Penumbra.UI.Classes;
@ -76,7 +77,7 @@ public partial class ModEditWindow
using var t = ImRaii.TreeNode( $"{objectName} #{idx}" ); using var t = ImRaii.TreeNode( $"{objectName} #{idx}" );
if( t ) 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 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 defaultName = new string( objectName.Where( char.IsUpper ).ToArray() ).ToLower() + idx.ToString();
var blob = shader.Blob; 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 ) 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 ); ChatUtil.NotificationMessage( $"Could not export {defaultName}{extension} to {Path.GetFileName( name )}:\n{e.Message}", "Penumbra Advanced Editing", NotificationType.Error );
return; 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 ) if( !disabled )
{ {
ImGui.SameLine(); 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 ) if( !success )
{ {
@ -146,10 +147,9 @@ public partial class ModEditWindow
} }
} }
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, disabled ); ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, true );
ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, disabled ); ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, true );
ret |= DrawShaderPackageResourceArray( "Unknown Type X Resources", "slot", true, shader.UnknownX, disabled ); ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "slot", true, shader.UAVs, true );
ret |= DrawShaderPackageResourceArray( "Unknown Type Y Resources", "slot", true, shader.UnknownY, disabled );
if( shader.AdditionalHeader.Length > 0 ) 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 ) if( t2 )
{ {
@ -326,7 +326,7 @@ public partial class ModEditWindow
foreach( var start in starts ) foreach( var start in starts )
{ {
var name = MaterialParamName( false, start )!; var name = MaterialParamName( false, start )!;
if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}" ) ) if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}", start == _shaderPackageNewMaterialParamStart ) )
{ {
_shaderPackageNewMaterialParamStart = ( ushort )start; _shaderPackageNewMaterialParamStart = ( ushort )start;
} }
@ -352,7 +352,7 @@ public partial class ModEditWindow
foreach( var end in ends ) foreach( var end in ends )
{ {
var name = MaterialParamName( false, end )!; var name = MaterialParamName( false, end )!;
if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}" ) ) if( ImGui.Selectable( $"{materialParams?.Name ?? ""}{name}", end == _shaderPackageNewMaterialParamEnd ) )
{ {
_shaderPackageNewMaterialParamEnd = ( ushort )end; _shaderPackageNewMaterialParamEnd = ( ushort )end;
} }
@ -390,7 +390,7 @@ public partial class ModEditWindow
return ret; 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 ) if( resources.Length == 0 )
{ {
@ -407,65 +407,85 @@ public partial class ModEditWindow
foreach( var (buf, idx) in resources.WithIndex() ) 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 ) if( t2 )
{ {
var used = new List< string >(); if( !disabled )
if( withSize )
{ {
foreach( var (components, i) in ( buf.Used ?? Array.Empty<DisassembledShader.VectorComponents>() ).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<string>();
if( withSize )
{
foreach( var (components, i) in ( buf.Used ?? Array.Empty<DisassembledShader.VectorComponents>() ).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: case 0:
break; break;
case DisassembledShader.VectorComponents.All: case DisassembledShader.VectorComponents.All:
used.Add( $"[{i}]" ); used.Add( "[*]" );
break; break;
default: 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; break;
} }
} }
switch( buf.UsedDynamically ?? 0 ) else
{ {
case 0: var components = ( ( buf.Used != null && buf.Used.Length > 0 ) ? buf.Used[0] : 0 ) | ( buf.UsedDynamically ?? 0 );
break; if( ( components & DisassembledShader.VectorComponents.X ) != 0 )
case DisassembledShader.VectorComponents.All: {
used.Add( "[*]" ); used.Add( "Red" );
break; }
default: if( ( components & DisassembledShader.VectorComponents.Y ) != 0 )
used.Add( $"[*].{new string( buf.UsedDynamically!.Value.ToString().Where( char.IsUpper ).ToArray() ).ToLower()}" ); {
break; used.Add( "Green" );
}
if( ( components & DisassembledShader.VectorComponents.Z ) != 0 )
{
used.Add( "Blue" );
}
if( ( components & DisassembledShader.VectorComponents.W ) != 0 )
{
used.Add( "Alpha" );
}
} }
} if( used.Count > 0 )
else
{
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" ); 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; return false;
} }
ImRaii.TreeNode( $"Version: 0x{file.Version:X8}", ImGuiTreeNodeFlags.Leaf ).Dispose();
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, file.Constants, disabled ); ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, file.Constants, disabled );
ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, file.Samplers, 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 ) 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 ) 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 ) if( file.AdditionalData.Length > 0 )
{ {
using var t = ImRaii.TreeNode( $"Additional Data (Size: {file.AdditionalData.Length})###AdditionalData" ); 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; return ret;
} }
} }