Change EST files to be sorted and thus work.

This commit is contained in:
Ottermandias 2022-01-06 11:46:53 +01:00
parent 19b295bbc3
commit aa7d71530d

View file

@ -4,154 +4,159 @@ using System.Linq;
using Lumina.Data; using Lumina.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Penumbra.Meta.Files namespace Penumbra.Meta.Files;
// EST Structure:
// 1x [NumEntries : UInt32]
// Apparently entries need to be sorted.
// #NumEntries x [SetId : UInt16] [RaceId : UInt16]
// #NumEntries x [SkeletonId : UInt16]
public class EstFile
{ {
// EST Structure: private const ushort EntryDescSize = 4;
// 1x [NumEntries : UInt32] private const ushort EntrySize = 2;
// #NumEntries x [SetId : UInt16] [RaceId : UInt16]
// #NumEntries x [SkeletonId : UInt16] private readonly SortedList< GenderRace, SortedList< ushort, ushort > > _entries = new();
public class EstFile private uint NumEntries { get; set; }
private EstFile( EstFile clone )
{ {
private const ushort EntryDescSize = 4; NumEntries = clone.NumEntries;
private const ushort EntrySize = 2; _entries = new SortedList< GenderRace, SortedList< ushort, ushort > >( clone._entries.Count );
foreach( var (genderRace, data) in clone._entries )
private readonly Dictionary< GenderRace, Dictionary< ushort, ushort > > _entries = new();
private uint NumEntries { get; set; }
private EstFile( EstFile clone )
{ {
NumEntries = clone.NumEntries; var dict = new SortedList< ushort, ushort >( data.Count );
_entries = new Dictionary< GenderRace, Dictionary< ushort, ushort > >( clone._entries.Count ); foreach( var (setId, value) in data )
foreach( var kvp in clone._entries )
{ {
var dict = kvp.Value.ToDictionary( k => k.Key, k => k.Value ); dict.Add( setId, value );
_entries.Add( kvp.Key, dict );
} }
_entries.Add( genderRace, dict );
}
}
public EstFile Clone()
=> new(this);
private bool DeleteEntry( GenderRace gr, ushort setId )
{
if( !_entries.TryGetValue( gr, out var setDict ) )
{
return false;
} }
public EstFile Clone() if( !setDict.ContainsKey( setId ) )
=> new( this );
private bool DeleteEntry( GenderRace gr, ushort setId )
{ {
if( !_entries.TryGetValue( gr, out var setDict ) ) return false;
{
return false;
}
if( !setDict.ContainsKey( setId ) )
{
return false;
}
setDict.Remove( setId );
if( setDict.Count == 0 )
{
_entries.Remove( gr );
}
--NumEntries;
return true;
} }
private (bool, bool) AddEntry( GenderRace gr, ushort setId, ushort entry ) setDict.Remove( setId );
if( setDict.Count == 0 )
{ {
if( !_entries.TryGetValue( gr, out var setDict ) ) _entries.Remove( gr );
{ }
_entries[ gr ] = new Dictionary< ushort, ushort >();
setDict = _entries[ gr ];
}
if( setDict.TryGetValue( setId, out var oldEntry ) ) --NumEntries;
{ return true;
if( oldEntry == entry ) }
{
return ( false, false );
}
setDict[ setId ] = entry; private (bool, bool) AddEntry( GenderRace gr, ushort setId, ushort entry )
return ( false, true ); {
if( !_entries.TryGetValue( gr, out var setDict ) )
{
_entries[ gr ] = new SortedList< ushort, ushort >();
setDict = _entries[ gr ];
}
if( setDict.TryGetValue( setId, out var oldEntry ) )
{
if( oldEntry == entry )
{
return ( false, false );
} }
setDict[ setId ] = entry; setDict[ setId ] = entry;
return ( true, true ); return ( false, true );
} }
public bool SetEntry( GenderRace gr, ushort setId, ushort entry ) setDict[ setId ] = entry;
return ( true, true );
}
public bool SetEntry( GenderRace gr, ushort setId, ushort entry )
{
if( entry == 0 )
{ {
if( entry == 0 ) return DeleteEntry( gr, setId );
{
return DeleteEntry( gr, setId );
}
var (addedNew, changed) = AddEntry( gr, setId, entry );
if( !addedNew )
{
return changed;
}
++NumEntries;
return true;
} }
public ushort GetEntry( GenderRace gr, ushort setId ) var (addedNew, changed) = AddEntry( gr, setId, entry );
if( !addedNew )
{ {
if( !_entries.TryGetValue( gr, out var setDict ) ) return changed;
{
return 0;
}
return !setDict.TryGetValue( setId, out var entry ) ? ( ushort )0 : entry;
} }
public byte[] WriteBytes() ++NumEntries;
return true;
}
public ushort GetEntry( GenderRace gr, ushort setId )
{
if( !_entries.TryGetValue( gr, out var setDict ) )
{ {
using MemoryStream mem = new( ( int )( 4 + ( EntryDescSize + EntrySize ) * NumEntries ) ); return 0;
using BinaryWriter bw = new( mem );
bw.Write( NumEntries );
foreach( var kvp1 in _entries )
{
foreach( var kvp2 in kvp1.Value )
{
bw.Write( kvp2.Key );
bw.Write( ( ushort )kvp1.Key );
}
}
foreach( var kvp2 in _entries.SelectMany( kvp1 => kvp1.Value ) )
{
bw.Write( kvp2.Value );
}
return mem.ToArray();
} }
return !setDict.TryGetValue( setId, out var entry ) ? ( ushort )0 : entry;
}
public EstFile( FileResource file ) public byte[] WriteBytes()
{
using MemoryStream mem = new(( int )( 4 + ( EntryDescSize + EntrySize ) * NumEntries ));
using BinaryWriter bw = new(mem);
bw.Write( NumEntries );
foreach( var kvp1 in _entries )
{ {
file.Reader.BaseStream.Seek( 0, SeekOrigin.Begin ); foreach( var kvp2 in kvp1.Value )
NumEntries = file.Reader.ReadUInt32();
var currentEntryDescOffset = 4;
var currentEntryOffset = 4 + EntryDescSize * NumEntries;
for( var i = 0; i < NumEntries; ++i )
{ {
file.Reader.BaseStream.Seek( currentEntryDescOffset, SeekOrigin.Begin ); bw.Write( kvp2.Key );
currentEntryDescOffset += EntryDescSize; bw.Write( ( ushort )kvp1.Key );
var setId = file.Reader.ReadUInt16();
var raceId = ( GenderRace )file.Reader.ReadUInt16();
if( !raceId.IsValid() )
{
continue;
}
file.Reader.BaseStream.Seek( currentEntryOffset, SeekOrigin.Begin );
currentEntryOffset += EntrySize;
var entry = file.Reader.ReadUInt16();
AddEntry( raceId, setId, entry );
} }
} }
foreach( var kvp2 in _entries.SelectMany( kvp1 => kvp1.Value ) )
{
bw.Write( kvp2.Value );
}
return mem.ToArray();
}
public EstFile( FileResource file )
{
file.Reader.BaseStream.Seek( 0, SeekOrigin.Begin );
NumEntries = file.Reader.ReadUInt32();
var currentEntryDescOffset = 4;
var currentEntryOffset = 4 + EntryDescSize * NumEntries;
for( var i = 0; i < NumEntries; ++i )
{
file.Reader.BaseStream.Seek( currentEntryDescOffset, SeekOrigin.Begin );
currentEntryDescOffset += EntryDescSize;
var setId = file.Reader.ReadUInt16();
var raceId = ( GenderRace )file.Reader.ReadUInt16();
if( !raceId.IsValid() )
{
continue;
}
file.Reader.BaseStream.Seek( currentEntryOffset, SeekOrigin.Begin );
currentEntryOffset += EntrySize;
var entry = file.Reader.ReadUInt16();
AddEntry( raceId, setId, entry );
}
} }
} }