using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; using System.Text; using Lumina.Data.Parsing; using Lumina.Extensions; namespace Penumbra.GameData.Files; public partial class MtrlFile : IWritable { public struct UvSet { public string Name; public ushort Index; } public unsafe struct ColorSet { public struct Row { public const int Size = 32; private fixed ushort _data[16]; public Vector3 Diffuse { get => new(ToFloat( 0 ), ToFloat( 1 ), ToFloat( 2 )); set { _data[ 0 ] = FromFloat( value.X ); _data[ 1 ] = FromFloat( value.Y ); _data[ 2 ] = FromFloat( value.Z ); } } public Vector3 Specular { get => new(ToFloat( 4 ), ToFloat( 5 ), ToFloat( 6 )); set { _data[ 4 ] = FromFloat( value.X ); _data[ 5 ] = FromFloat( value.Y ); _data[ 6 ] = FromFloat( value.Z ); } } public Vector3 Emissive { get => new(ToFloat( 8 ), ToFloat( 9 ), ToFloat( 10 )); set { _data[ 8 ] = FromFloat( value.X ); _data[ 9 ] = FromFloat( value.Y ); _data[ 10 ] = FromFloat( value.Z ); } } public Vector2 MaterialRepeat { get => new(ToFloat( 12 ), ToFloat( 15 )); set { _data[ 12 ] = FromFloat( value.X ); _data[ 15 ] = FromFloat( value.Y ); } } public Vector2 MaterialSkew { get => new(ToFloat( 13 ), ToFloat( 14 )); set { _data[ 13 ] = FromFloat( value.X ); _data[ 14 ] = FromFloat( value.Y ); } } public float SpecularStrength { get => ToFloat( 3 ); set => _data[ 3 ] = FromFloat( value ); } public float GlossStrength { get => ToFloat( 7 ); set => _data[ 7 ] = FromFloat( value ); } public ushort TileSet { get => (ushort) (ToFloat(11) * 64f); set => _data[ 11 ] = FromFloat(value / 64f); } private float ToFloat( int idx ) => ( float )BitConverter.UInt16BitsToHalf( _data[ idx ] ); private static ushort FromFloat( float x ) => BitConverter.HalfToUInt16Bits( ( Half )x ); } public struct RowArray : IEnumerable< Row > { public const int NumRows = 16; private fixed byte _rowData[NumRows * Row.Size]; public ref Row this[ int i ] { get { fixed( byte* ptr = _rowData ) { return ref ( ( Row* )ptr )[ i ]; } } } public IEnumerator< Row > GetEnumerator() { for( var i = 0; i < NumRows; ++i ) { yield return this[ i ]; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public ReadOnlySpan< byte > AsBytes() { fixed( byte* ptr = _rowData ) { return new ReadOnlySpan< byte >( ptr, NumRows * Row.Size ); } } } public RowArray Rows; public string Name; public ushort Index; } public unsafe struct ColorDyeSet { public struct Row { private ushort _data; public ushort Template { get => ( ushort )( _data >> 5 ); set => _data = ( ushort )( ( _data & 0x1F ) | ( value << 5 ) ); } public bool Diffuse { get => ( _data & 0x01 ) != 0; set => _data = ( ushort )( value ? _data | 0x01 : _data & 0xFFFE ); } public bool Specular { get => ( _data & 0x02 ) != 0; set => _data = ( ushort )( value ? _data | 0x02 : _data & 0xFFFD ); } public bool Emissive { get => ( _data & 0x04 ) != 0; set => _data = ( ushort )( value ? _data | 0x04 : _data & 0xFFFB ); } public bool Gloss { get => ( _data & 0x08 ) != 0; set => _data = ( ushort )( value ? _data | 0x08 : _data & 0xFFF7 ); } public bool SpecularStrength { get => ( _data & 0x10 ) != 0; set => _data = ( ushort )( value ? _data | 0x10 : _data & 0xFFEF ); } } public struct RowArray : IEnumerable< Row > { public const int NumRows = 16; private fixed ushort _rowData[NumRows]; public ref Row this[ int i ] { get { fixed( ushort* ptr = _rowData ) { return ref ( ( Row* )ptr )[ i ]; } } } public IEnumerator< Row > GetEnumerator() { for( var i = 0; i < NumRows; ++i ) { yield return this[ i ]; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public ReadOnlySpan< byte > AsBytes() { fixed( ushort* ptr = _rowData ) { return new ReadOnlySpan< byte >( ptr, NumRows * sizeof( ushort ) ); } } } public RowArray Rows; public string Name; public ushort Index; } public struct Texture { public string Path; public ushort Flags; } public struct Constant { public uint Id; public uint Value; } public struct ShaderPackageData { public string Name; public ShaderKey[] ShaderKeys; public Constant[] Constants; public Sampler[] Samplers; public float[] ShaderValues; public uint Unk; } public readonly uint Version; public Texture[] Textures; public UvSet[] UvSets; public ColorSet[] ColorSets; public ColorDyeSet[] ColorDyeSets; public ShaderPackageData ShaderPackage; public byte[] AdditionalData; public MtrlFile( byte[] data ) { using var stream = new MemoryStream( data ); using var r = new BinaryReader( stream ); Version = r.ReadUInt32(); r.ReadUInt16(); // file size var dataSetSize = r.ReadUInt16(); var stringTableSize = r.ReadUInt16(); var shaderPackageNameOffset = r.ReadUInt16(); var textureCount = r.ReadByte(); var uvSetCount = r.ReadByte(); var colorSetCount = r.ReadByte(); var additionalDataSize = r.ReadByte(); Textures = ReadTextureOffsets( r, textureCount, out var textureOffsets ); UvSets = ReadUvSetOffsets( r, uvSetCount, out var uvOffsets ); ColorSets = ReadColorSetOffsets( r, colorSetCount, out var colorOffsets ); var strings = r.ReadBytes( stringTableSize ); for( var i = 0; i < textureCount; ++i ) { Textures[ i ].Path = UseOffset( strings, textureOffsets[ i ] ); } for( var i = 0; i < uvSetCount; ++i ) { UvSets[ i ].Name = UseOffset( strings, uvOffsets[ i ] ); } for( var i = 0; i < colorSetCount; ++i ) { ColorSets[ i ].Name = UseOffset( strings, colorOffsets[ i ] ); } ColorDyeSets = ColorSets.Length * ColorSet.RowArray.NumRows * ColorSet.Row.Size < dataSetSize ? ColorSets.Select( c => new ColorDyeSet { Index = c.Index, Name = c.Name } ).ToArray() : Array.Empty< ColorDyeSet >(); ShaderPackage.Name = UseOffset( strings, shaderPackageNameOffset ); AdditionalData = r.ReadBytes( additionalDataSize ); for( var i = 0; i < ColorSets.Length; ++i ) { ColorSets[ i ].Rows = r.ReadStructure< ColorSet.RowArray >(); } for( var i = 0; i < ColorDyeSets.Length; ++i ) { ColorDyeSets[ i ].Rows = r.ReadStructure< ColorDyeSet.RowArray >(); } var shaderValueListSize = r.ReadUInt16(); var shaderKeyCount = r.ReadUInt16(); var constantCount = r.ReadUInt16(); var samplerCount = r.ReadUInt16(); ShaderPackage.Unk = r.ReadUInt32(); ShaderPackage.ShaderKeys = r.ReadStructuresAsArray< ShaderKey >( shaderKeyCount ); ShaderPackage.Constants = r.ReadStructuresAsArray< Constant >( constantCount ); ShaderPackage.Samplers = r.ReadStructuresAsArray< Sampler >( samplerCount ); ShaderPackage.ShaderValues = r.ReadStructuresAsArray< float >( shaderValueListSize / 4 ); } private static Texture[] ReadTextureOffsets( BinaryReader r, int count, out ushort[] offsets ) { var ret = new Texture[count]; offsets = new ushort[count]; for( var i = 0; i < count; ++i ) { offsets[ i ] = r.ReadUInt16(); ret[ i ].Flags = r.ReadUInt16(); } return ret; } private static UvSet[] ReadUvSetOffsets( BinaryReader r, int count, out ushort[] offsets ) { var ret = new UvSet[count]; offsets = new ushort[count]; for( var i = 0; i < count; ++i ) { offsets[ i ] = r.ReadUInt16(); ret[ i ].Index = r.ReadUInt16(); } return ret; } private static ColorSet[] ReadColorSetOffsets( BinaryReader r, int count, out ushort[] offsets ) { var ret = new ColorSet[count]; offsets = new ushort[count]; for( var i = 0; i < count; ++i ) { offsets[ i ] = r.ReadUInt16(); ret[ i ].Index = r.ReadUInt16(); } return ret; } private static string UseOffset( ReadOnlySpan< byte > strings, ushort offset ) { strings = strings[ offset.. ]; var end = strings.IndexOf( ( byte )'\0' ); return Encoding.UTF8.GetString( strings[ ..end ] ); } }