Add material file parsing and writing.

This commit is contained in:
Ottermandias 2022-08-13 21:20:32 +02:00
parent 5ac3a903f6
commit 5e9cb77415
2 changed files with 229 additions and 0 deletions

View file

@ -0,0 +1,96 @@
using System.IO;
using System.Linq;
using System.Text;
namespace Penumbra.GameData.Files;
public partial class MtrlFile
{
public byte[] Write()
{
using var stream = new MemoryStream();
using( var w = new BinaryWriter( stream ) )
{
const int materialHeaderSize = 4 + 2 + 2 + 2 + 2 + 1 + 1 + 1 + 1;
w.BaseStream.Seek( materialHeaderSize, SeekOrigin.Begin );
ushort cumulativeStringOffset = 0;
foreach( var texture in Textures )
{
w.Write( cumulativeStringOffset );
w.Write( texture.Flags );
cumulativeStringOffset += ( ushort )( texture.Path.Length + 1 );
}
foreach( var colorSet in UvColorSets.Concat( ColorSets ) )
{
w.Write( cumulativeStringOffset );
w.Write( colorSet.Index );
cumulativeStringOffset += ( ushort )( colorSet.Name.Length + 1 );
}
foreach( var text in Textures.Select( t => t.Path )
.Concat( UvColorSets.Concat( ColorSets ).Select( c => c.Name ).Append( ShaderPackage.Name ) ) )
{
w.Write( Encoding.UTF8.GetBytes( text ) );
w.Write( ( byte )'\0' );
}
w.Write( AdditionalData );
foreach( var color in ColorSetData )
{
w.Write( color );
}
w.Write( ( ushort )( ShaderPackage.ShaderValues.Length * 4 ) );
w.Write( ( ushort )ShaderPackage.ShaderKeys.Length );
w.Write( ( ushort )ShaderPackage.Constants.Length );
w.Write( ( ushort )ShaderPackage.Samplers.Length );
w.Write( ShaderPackage.Unk );
foreach( var key in ShaderPackage.ShaderKeys )
{
w.Write( key.Category );
w.Write( key.Value );
}
foreach( var constant in ShaderPackage.Constants )
{
w.Write( constant.Id );
w.Write( constant.Value );
}
foreach( var sampler in ShaderPackage.Samplers )
{
w.Write( sampler.SamplerId );
w.Write( sampler.Flags );
w.Write( sampler.TextureIndex );
w.Write( ( ushort )0 );
w.Write( ( byte )0 );
}
foreach( var value in ShaderPackage.ShaderValues )
{
w.Write( value );
}
WriteHeader( w, ( ushort )w.BaseStream.Position, cumulativeStringOffset );
}
return stream.ToArray();
}
private void WriteHeader( BinaryWriter w, ushort fileSize, ushort shaderPackageNameOffset )
{
w.BaseStream.Seek( 0, SeekOrigin.Begin );
w.Write( Version );
w.Write( fileSize );
w.Write( ( ushort )( ColorSetData.Length * 2 ) );
w.Write( ( ushort )( shaderPackageNameOffset + ShaderPackage.Name.Length + 1 ) );
w.Write( shaderPackageNameOffset );
w.Write( ( byte )Textures.Length );
w.Write( ( byte )UvColorSets.Length );
w.Write( ( byte )ColorSets.Length );
w.Write( ( byte )AdditionalData.Length );
}
}

View file

@ -0,0 +1,133 @@
using System;
using System.IO;
using System.Text;
using Lumina.Data.Parsing;
using Lumina.Extensions;
namespace Penumbra.GameData.Files;
public partial class MtrlFile
{
public struct ColorSet
{
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 uint Version;
public Texture[] Textures;
public ColorSet[] UvColorSets;
public ColorSet[] ColorSets;
public ushort[] ColorSetData;
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 );
UvColorSets = ReadColorSetOffsets( 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 )
{
UvColorSets[ i ].Name = UseOffset( strings, uvOffsets[ i ] );
}
for( var i = 0; i < colorSetCount; ++i )
{
ColorSets[ i ].Name = UseOffset( strings, colorOffsets[ i ] );
}
ShaderPackage.Name = UseOffset( strings, shaderPackageNameOffset );
AdditionalData = r.ReadBytes( additionalDataSize );
ColorSetData = r.ReadStructuresAsArray< ushort >( dataSetSize / 2 );
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 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 ] );
}
}