Meta stuff is terrible.

This commit is contained in:
Ottermandias 2023-04-16 13:18:43 +02:00
parent 0186f176d0
commit 1d82e882ed
35 changed files with 1265 additions and 1247 deletions

View file

@ -8,46 +8,46 @@ using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
// The human.cmp file contains many character-relevant parameters like color sets.
// We only support manipulating the racial scaling parameters at the moment.
/// <summary>
/// The human.cmp file contains many character-relevant parameters like color sets.
/// We only support manipulating the racial scaling parameters at the moment.
/// </summary>
public sealed unsafe class CmpFile : MetaBaseFile
{
public static readonly CharacterUtility.InternalIndex InternalIndex =
CharacterUtility.ReverseIndices[ ( int )MetaIndex.HumanCmp ];
CharacterUtility.ReverseIndices[(int)MetaIndex.HumanCmp];
private const int RacialScalingStart = 0x2A800;
public float this[ SubRace subRace, RspAttribute attribute ]
public float this[SubRace subRace, RspAttribute attribute]
{
get => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 );
set => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ) = value;
get => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4);
set => *(float*)(Data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4) = value;
}
public override void Reset()
=> MemoryUtility.MemCpyUnchecked( Data, ( byte* )DefaultData.Data, DefaultData.Length );
=> MemoryUtility.MemCpyUnchecked(Data, (byte*)DefaultData.Data, DefaultData.Length);
public void Reset( IEnumerable< (SubRace, RspAttribute) > entries )
public void Reset(IEnumerable<(SubRace, RspAttribute)> entries)
{
foreach( var (r, a) in entries )
{
this[ r, a ] = GetDefault( r, a );
}
foreach (var (r, a) in entries)
this[r, a] = GetDefault(Manager, r, a);
}
public CmpFile()
: base( MetaIndex.HumanCmp )
public CmpFile(MetaFileManager manager)
: base(manager, MetaIndex.HumanCmp)
{
AllocateData( DefaultData.Length );
AllocateData(DefaultData.Length);
Reset();
}
public static float GetDefault( SubRace subRace, RspAttribute attribute )
public static float GetDefault(MetaFileManager manager, SubRace subRace, RspAttribute attribute)
{
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( InternalIndex ).Address;
return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 );
var data = (byte*)manager.CharacterUtility.DefaultResource(InternalIndex).Address;
return *(float*)(data + RacialScalingStart + ToRspIndex(subRace) * RspEntry.ByteSize + (int)attribute * 4);
}
private static int ToRspIndex( SubRace subRace )
private static int ToRspIndex(SubRace subRace)
=> subRace switch
{
SubRace.Midlander => 0,
@ -67,6 +67,6 @@ public sealed unsafe class CmpFile : MetaBaseFile
SubRace.Rava => 70,
SubRace.Veena => 71,
SubRace.Unknown => 0,
_ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ),
_ => throw new ArgumentOutOfRangeException(nameof(subRace), subRace, null),
};
}
}

View file

@ -8,14 +8,15 @@ using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
// EQDP file structure:
// [Identifier][BlockSize:ushort][BlockCount:ushort]
// BlockCount x [BlockHeader:ushort]
// Containing offsets for blocks, ushort.Max means collapsed.
// Offsets are based on the end of the header, so 0 means IdentifierSize + 4 + BlockCount x 2.
// ExpandedBlockCount x [Entry]
// Expanded Eqdp File just expands all blocks for easy read and write access to single entries and to keep the same memory for it.
/// <summary>
/// EQDP file structure:
/// [Identifier][BlockSize:ushort][BlockCount:ushort]
/// BlockCount x [BlockHeader:ushort]
/// Containing offsets for blocks, ushort.Max means collapsed.
/// Offsets are based on the end of the header, so 0 means IdentifierSize + 4 + BlockCount x 2.
/// ExpandedBlockCount x [Entry]
/// Expanded Eqdp File just expands all blocks for easy read and write access to single entries and to keep the same memory for it.
/// </summary>
public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
{
private const ushort BlockHeaderSize = 2;
@ -28,117 +29,103 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public readonly int DataOffset;
public ushort Identifier
=> *( ushort* )Data;
=> *(ushort*)Data;
public ushort BlockSize
=> *( ushort* )( Data + 2 );
=> *(ushort*)(Data + 2);
public ushort BlockCount
=> *( ushort* )( Data + 4 );
=> *(ushort*)(Data + 4);
public int Count
=> ( Length - DataOffset ) / EqdpEntrySize;
=> (Length - DataOffset) / EqdpEntrySize;
public EqdpEntry this[ int idx ]
public EqdpEntry this[int idx]
{
get
{
if( idx >= Count || idx < 0 )
{
if (idx >= Count || idx < 0)
throw new IndexOutOfRangeException();
}
return ( EqdpEntry )( *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) );
return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * idx));
}
set
{
if( idx >= Count || idx < 0 )
{
if (idx >= Count || idx < 0)
throw new IndexOutOfRangeException();
}
*( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) = ( ushort )value;
*(ushort*)(Data + DataOffset + EqdpEntrySize * idx) = (ushort)value;
}
}
public override void Reset()
{
var def = ( byte* )DefaultData.Data;
MemoryUtility.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
var def = (byte*)DefaultData.Data;
MemoryUtility.MemCpyUnchecked(Data, def, IdentifierSize + PreambleSize);
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
var controlPtr = (ushort*)(def + IdentifierSize + PreambleSize);
var dataBasePtr = controlPtr + BlockCount;
var myDataPtr = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount );
var myControlPtr = ( ushort* )( Data + IdentifierSize + PreambleSize );
for( var i = 0; i < BlockCount; ++i )
var myDataPtr = (ushort*)(Data + IdentifierSize + PreambleSize + 2 * BlockCount);
var myControlPtr = (ushort*)(Data + IdentifierSize + PreambleSize);
for (var i = 0; i < BlockCount; ++i)
{
if( controlPtr[ i ] == CollapsedBlock )
{
MemoryUtility.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
}
if (controlPtr[i] == CollapsedBlock)
MemoryUtility.MemSet(myDataPtr, 0, BlockSize * EqdpEntrySize);
else
{
MemoryUtility.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
}
MemoryUtility.MemCpyUnchecked(myDataPtr, dataBasePtr + controlPtr[i], BlockSize * EqdpEntrySize);
myControlPtr[ i ] = ( ushort )( i * BlockSize );
myDataPtr += BlockSize;
myControlPtr[i] = (ushort)(i * BlockSize);
myDataPtr += BlockSize;
}
MemoryUtility.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) );
MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data));
}
public void Reset( IEnumerable< int > entries )
public void Reset(IEnumerable<int> entries)
{
foreach( var entry in entries )
{
this[ entry ] = GetDefault( entry );
}
foreach (var entry in entries)
this[entry] = GetDefault(entry);
}
public ExpandedEqdpFile( GenderRace raceCode, bool accessory )
: base( CharacterUtilityData.EqdpIdx( raceCode, accessory ) )
public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory)
: base(manager, CharacterUtilityData.EqdpIdx(raceCode, accessory))
{
var def = ( byte* )DefaultData.Data;
var blockSize = *( ushort* )( def + IdentifierSize );
var totalBlockCount = *( ushort* )( def + IdentifierSize + 2 );
var def = (byte*)DefaultData.Data;
var blockSize = *(ushort*)(def + IdentifierSize);
var totalBlockCount = *(ushort*)(def + IdentifierSize + 2);
var totalBlockSize = blockSize * EqdpEntrySize;
DataOffset = IdentifierSize + PreambleSize + totalBlockCount * BlockHeaderSize;
var fullLength = DataOffset + totalBlockCount * totalBlockSize;
fullLength += ( FileAlignment - ( fullLength & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 );
AllocateData( fullLength );
var fullLength = DataOffset + totalBlockCount * totalBlockSize;
fullLength += (FileAlignment - (fullLength & (FileAlignment - 1))) & (FileAlignment - 1);
AllocateData(fullLength);
Reset();
}
public EqdpEntry GetDefault( int setIdx )
=> GetDefault( Index, setIdx );
public EqdpEntry GetDefault(int setIdx)
=> GetDefault(Manager, Index, setIdx);
public static EqdpEntry GetDefault( CharacterUtility.InternalIndex idx, int setIdx )
=> GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( idx ).Address, setIdx );
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, int setIdx)
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setIdx);
public static EqdpEntry GetDefault( byte* data, int setIdx )
public static EqdpEntry GetDefault(byte* data, int setIdx)
{
var blockSize = *( ushort* )( data + IdentifierSize );
var totalBlockCount = *( ushort* )( data + IdentifierSize + 2 );
var blockSize = *(ushort*)(data + IdentifierSize);
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
var blockIdx = setIdx / blockSize;
if( blockIdx >= totalBlockCount )
{
if (blockIdx >= totalBlockCount)
return 0;
}
var block = ( ( ushort* )( data + IdentifierSize + PreambleSize ) )[ blockIdx ];
if( block == CollapsedBlock )
{
var block = ((ushort*)(data + IdentifierSize + PreambleSize))[blockIdx];
if (block == CollapsedBlock)
return 0;
}
var blockData = ( ushort* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2 );
return ( EqdpEntry )( *( blockData + setIdx % blockSize ) );
var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2);
return (EqdpEntry)(*(blockData + setIdx % blockSize));
}
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )
=> GetDefault( CharacterUtility.ReverseIndices[ ( int )CharacterUtilityData.EqdpIdx( raceCode, accessory ) ], setIdx );
}
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, int setIdx)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setIdx);
}

View file

@ -9,11 +9,13 @@ using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
// EQP/GMP Structure:
// 64 x [Block collapsed or not bit]
// 159 x [EquipmentParameter:ulong]
// (CountSetBits(Block Collapsed or not) - 1) x 160 x [EquipmentParameter:ulong]
// Item 0 does not exist and is sent to Item 1 instead.
/// <summary>
/// EQP/GMP Structure:
/// 64 x [Block collapsed or not bit]
/// 159 x [EquipmentParameter:ulong]
/// (CountSetBits(Block Collapsed or not) - 1) x 160 x [EquipmentParameter:ulong]
/// Item 0 does not exist and is sent to Item 1 instead.
/// </summary>
public unsafe class ExpandedEqpGmpBase : MetaBaseFile
{
protected const int BlockSize = 160;
@ -24,19 +26,19 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public const int Count = BlockSize * NumBlocks;
public ulong ControlBlock
=> *( ulong* )Data;
=> *(ulong*)Data;
protected ulong GetInternal( int idx )
protected ulong GetInternal(int idx)
{
return idx switch
{
>= Count => throw new IndexOutOfRangeException(),
<= 1 => *( ( ulong* )Data + 1 ),
_ => *( ( ulong* )Data + idx ),
<= 1 => *((ulong*)Data + 1),
_ => *((ulong*)Data + idx),
};
}
protected void SetInternal( int idx, ulong value )
protected void SetInternal(int idx, ulong value)
{
idx = idx switch
{
@ -45,67 +47,62 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
_ => idx,
};
*( ( ulong* )Data + idx ) = value;
*((ulong*)Data + idx) = value;
}
protected virtual void SetEmptyBlock( int idx )
protected virtual void SetEmptyBlock(int idx)
{
MemoryUtility.MemSet( Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize );
MemoryUtility.MemSet(Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize);
}
public sealed override void Reset()
{
var ptr = ( byte* )DefaultData.Data;
var controlBlock = *( ulong* )ptr;
var ptr = (byte*)DefaultData.Data;
var controlBlock = *(ulong*)ptr;
var expandedBlocks = 0;
for( var i = 0; i < NumBlocks; ++i )
for (var i = 0; i < NumBlocks; ++i)
{
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
if( !collapsed )
var collapsed = ((controlBlock >> i) & 1) == 0;
if (!collapsed)
{
MemoryUtility.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize );
MemoryUtility.MemCpyUnchecked(Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize,
BlockSize * EntrySize);
expandedBlocks++;
}
else
{
SetEmptyBlock( i );
SetEmptyBlock(i);
}
}
*( ulong* )Data = ulong.MaxValue;
*(ulong*)Data = ulong.MaxValue;
}
public ExpandedEqpGmpBase( bool gmp )
: base( gmp ? MetaIndex.Gmp : MetaIndex.Eqp )
public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp)
: base(manager, gmp ? MetaIndex.Gmp : MetaIndex.Eqp)
{
AllocateData( MaxSize );
AllocateData(MaxSize);
Reset();
}
protected static ulong GetDefaultInternal( CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def )
protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def)
{
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource(fileIndex).Address;
if( setIdx == 0 )
{
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
if (setIdx == 0)
setIdx = 1;
}
var blockIdx = setIdx / BlockSize;
if( blockIdx >= NumBlocks )
{
if (blockIdx >= NumBlocks)
return def;
}
var control = *( ulong* )data;
var control = *(ulong*)data;
var blockBit = 1ul << blockIdx;
if( ( control & blockBit ) == 0 )
{
if ((control & blockBit) == 0)
return def;
}
var count = BitOperations.PopCount( control & ( blockBit - 1 ) );
var count = BitOperations.PopCount(control & (blockBit - 1));
var idx = setIdx % BlockSize;
var ptr = ( ulong* )data + BlockSize * count + idx;
var ptr = (ulong*)data + BlockSize * count + idx;
return *ptr;
}
}
@ -113,44 +110,40 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
{
public static readonly CharacterUtility.InternalIndex InternalIndex =
CharacterUtility.ReverseIndices[ (int) MetaIndex.Eqp ];
CharacterUtility.ReverseIndices[(int)MetaIndex.Eqp];
public ExpandedEqpFile()
: base( false )
public ExpandedEqpFile(MetaFileManager manager)
: base(manager, false)
{ }
public EqpEntry this[ int idx ]
public EqpEntry this[int idx]
{
get => ( EqpEntry )GetInternal( idx );
set => SetInternal( idx, ( ulong )value );
get => (EqpEntry)GetInternal(idx);
set => SetInternal(idx, (ulong)value);
}
public static EqpEntry GetDefault( int setIdx )
=> ( EqpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )Eqp.DefaultEntry );
public static EqpEntry GetDefault(MetaFileManager manager, int setIdx)
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry);
protected override unsafe void SetEmptyBlock( int idx )
protected override unsafe void SetEmptyBlock(int idx)
{
var blockPtr = ( ulong* )( Data + idx * BlockSize * EntrySize );
var blockPtr = (ulong*)(Data + idx * BlockSize * EntrySize);
var endPtr = blockPtr + BlockSize;
for( var ptr = blockPtr; ptr < endPtr; ++ptr )
{
*ptr = ( ulong )Eqp.DefaultEntry;
}
for (var ptr = blockPtr; ptr < endPtr; ++ptr)
*ptr = (ulong)Eqp.DefaultEntry;
}
public void Reset( IEnumerable< int > entries )
public void Reset(IEnumerable<int> entries)
{
foreach( var entry in entries )
{
this[ entry ] = GetDefault( entry );
}
foreach (var entry in entries)
this[entry] = GetDefault(Manager, entry);
}
public IEnumerator< EqpEntry > GetEnumerator()
public IEnumerator<EqpEntry> GetEnumerator()
{
for( var idx = 1; idx < Count; ++idx )
yield return this[ idx ];
for (var idx = 1; idx < Count; ++idx)
yield return this[idx];
}
IEnumerator IEnumerable.GetEnumerator()
@ -160,35 +153,33 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
{
public static readonly CharacterUtility.InternalIndex InternalIndex =
CharacterUtility.ReverseIndices[( int )MetaIndex.Gmp];
CharacterUtility.ReverseIndices[(int)MetaIndex.Gmp];
public ExpandedGmpFile()
: base( true )
public ExpandedGmpFile(MetaFileManager manager)
: base(manager, true)
{ }
public GmpEntry this[ int idx ]
public GmpEntry this[int idx]
{
get => ( GmpEntry )GetInternal( idx );
set => SetInternal( idx, ( ulong )value );
get => (GmpEntry)GetInternal(idx);
set => SetInternal(idx, (ulong)value);
}
public static GmpEntry GetDefault( int setIdx )
=> ( GmpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )GmpEntry.Default );
public static GmpEntry GetDefault(MetaFileManager manager, int setIdx)
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default);
public void Reset( IEnumerable< int > entries )
public void Reset(IEnumerable<int> entries)
{
foreach( var entry in entries )
{
this[ entry ] = GetDefault( entry );
}
foreach (var entry in entries)
this[entry] = GetDefault(Manager, entry);
}
public IEnumerator<GmpEntry> GetEnumerator()
{
for( var idx = 1; idx < Count; ++idx )
for (var idx = 1; idx < Count; ++idx)
yield return this[idx];
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}
}

View file

@ -8,11 +8,13 @@ using Penumbra.String.Functions;
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]
/// <summary>
/// EST Structure:
/// 1x [NumEntries : UInt32]
/// Apparently entries need to be sorted.
/// #NumEntries x [SetId : UInt16] [RaceId : UInt16]
/// #NumEntries x [SkeletonId : UInt16]
/// </summary>
public sealed unsafe class EstFile : MetaBaseFile
{
private const ushort EntryDescSize = 4;
@ -20,10 +22,10 @@ public sealed unsafe class EstFile : MetaBaseFile
private const int IncreaseSize = 512;
public int Count
=> *( int* )Data;
=> *(int*)Data;
private int Size
=> 4 + Count * ( EntryDescSize + EntrySize );
=> 4 + Count * (EntryDescSize + EntrySize);
public enum EstEntryChange
{
@ -33,176 +35,154 @@ public sealed unsafe class EstFile : MetaBaseFile
Removed,
}
public ushort this[ GenderRace genderRace, ushort setId ]
public ushort this[GenderRace genderRace, ushort setId]
{
get
{
var (idx, exists) = FindEntry( genderRace, setId );
if( !exists )
{
var (idx, exists) = FindEntry(genderRace, setId);
if (!exists)
return 0;
}
return *( ushort* )( Data + EntryDescSize * ( Count + 1 ) + EntrySize * idx );
return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
}
set => SetEntry( genderRace, setId, value );
set => SetEntry(genderRace, setId, value);
}
private void InsertEntry( int idx, GenderRace genderRace, ushort setId, ushort skeletonId )
private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId)
{
if( Length < Size + EntryDescSize + EntrySize )
{
ResizeResources( Length + IncreaseSize );
}
if (Length < Size + EntryDescSize + EntrySize)
ResizeResources(Length + IncreaseSize);
var control = ( Info* )( Data + 4 );
var entries = ( ushort* )( control + Count );
var control = (Info*)(Data + 4);
var entries = (ushort*)(control + Count);
for( var i = Count - 1; i >= idx; --i )
{
entries[ i + 3 ] = entries[ i ];
}
for (var i = Count - 1; i >= idx; --i)
entries[i + 3] = entries[i];
entries[ idx + 2 ] = skeletonId;
entries[idx + 2] = skeletonId;
for( var i = idx - 1; i >= 0; --i )
{
entries[ i + 2 ] = entries[ i ];
}
for (var i = idx - 1; i >= 0; --i)
entries[i + 2] = entries[i];
for( var i = Count - 1; i >= idx; --i )
{
control[ i + 1 ] = control[ i ];
}
for (var i = Count - 1; i >= idx; --i)
control[i + 1] = control[i];
control[ idx ] = new Info( genderRace, setId );
control[idx] = new Info(genderRace, setId);
*( int* )Data = Count + 1;
*(int*)Data = Count + 1;
}
private void RemoveEntry( int idx )
private void RemoveEntry(int idx)
{
var control = ( Info* )( Data + 4 );
var entries = ( ushort* )( control + Count );
var control = (Info*)(Data + 4);
var entries = (ushort*)(control + Count);
for( var i = idx; i < Count; ++i )
{
control[ i ] = control[ i + 1 ];
}
for (var i = idx; i < Count; ++i)
control[i] = control[i + 1];
for( var i = 0; i < idx; ++i )
{
entries[ i - 2 ] = entries[ i ];
}
for (var i = 0; i < idx; ++i)
entries[i - 2] = entries[i];
for( var i = idx; i < Count - 1; ++i )
{
entries[ i - 2 ] = entries[ i + 1 ];
}
for (var i = idx; i < Count - 1; ++i)
entries[i - 2] = entries[i + 1];
entries[ Count - 3 ] = 0;
entries[ Count - 2 ] = 0;
entries[ Count - 1 ] = 0;
*( int* )Data = Count - 1;
entries[Count - 3] = 0;
entries[Count - 2] = 0;
entries[Count - 1] = 0;
*(int*)Data = Count - 1;
}
[StructLayout( LayoutKind.Sequential, Size = 4 )]
private struct Info : IComparable< Info >
[StructLayout(LayoutKind.Sequential, Size = 4)]
private struct Info : IComparable<Info>
{
public readonly ushort SetId;
public readonly GenderRace GenderRace;
public Info( GenderRace gr, ushort setId )
public Info(GenderRace gr, ushort setId)
{
GenderRace = gr;
SetId = setId;
}
public int CompareTo( Info other )
public int CompareTo(Info other)
{
var genderRaceComparison = GenderRace.CompareTo( other.GenderRace );
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo( other.SetId );
var genderRaceComparison = GenderRace.CompareTo(other.GenderRace);
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo(other.SetId);
}
}
private static (int, bool) FindEntry( ReadOnlySpan< Info > data, GenderRace genderRace, ushort setId )
private static (int, bool) FindEntry(ReadOnlySpan<Info> data, GenderRace genderRace, ushort setId)
{
var idx = data.BinarySearch( new Info( genderRace, setId ) );
return idx < 0 ? ( ~idx, false ) : ( idx, true );
var idx = data.BinarySearch(new Info(genderRace, setId));
return idx < 0 ? (~idx, false) : (idx, true);
}
private (int, bool) FindEntry( GenderRace genderRace, ushort setId )
private (int, bool) FindEntry(GenderRace genderRace, ushort setId)
{
var span = new ReadOnlySpan< Info >( Data + 4, Count );
return FindEntry( span, genderRace, setId );
var span = new ReadOnlySpan<Info>(Data + 4, Count);
return FindEntry(span, genderRace, setId);
}
public EstEntryChange SetEntry( GenderRace genderRace, ushort setId, ushort skeletonId )
public EstEntryChange SetEntry(GenderRace genderRace, ushort setId, ushort skeletonId)
{
var (idx, exists) = FindEntry( genderRace, setId );
if( exists )
var (idx, exists) = FindEntry(genderRace, setId);
if (exists)
{
var value = *( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx );
if( value == skeletonId )
{
var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx);
if (value == skeletonId)
return EstEntryChange.Unchanged;
}
if( skeletonId == 0 )
if (skeletonId == 0)
{
RemoveEntry( idx );
RemoveEntry(idx);
return EstEntryChange.Removed;
}
*( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx ) = skeletonId;
*(ushort*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
return EstEntryChange.Changed;
}
if( skeletonId == 0 )
{
if (skeletonId == 0)
return EstEntryChange.Unchanged;
}
InsertEntry( idx, genderRace, setId, skeletonId );
InsertEntry(idx, genderRace, setId, skeletonId);
return EstEntryChange.Added;
}
public override void Reset()
{
var (d, length) = DefaultData;
var data = ( byte* )d;
MemoryUtility.MemCpyUnchecked( Data, data, length );
MemoryUtility.MemSet( Data + length, 0, Length - length );
var data = (byte*)d;
MemoryUtility.MemCpyUnchecked(Data, data, length);
MemoryUtility.MemSet(Data + length, 0, Length - length);
}
public EstFile( EstManipulation.EstType estType )
: base( ( MetaIndex )estType )
public EstFile(MetaFileManager manager, EstManipulation.EstType estType)
: base(manager, (MetaIndex)estType)
{
var length = DefaultData.Length;
AllocateData( length + IncreaseSize );
AllocateData(length + IncreaseSize);
Reset();
}
public ushort GetDefault( GenderRace genderRace, ushort setId )
=> GetDefault( Index, genderRace, setId );
public ushort GetDefault(GenderRace genderRace, ushort setId)
=> GetDefault(Manager, Index, genderRace, setId);
public static ushort GetDefault( CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId )
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId)
{
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( index ).Address;
var count = *( int* )data;
var span = new ReadOnlySpan< Info >( data + 4, count );
var (idx, found) = FindEntry( span, genderRace, setId );
if( !found )
{
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
var count = *(int*)data;
var span = new ReadOnlySpan<Info>(data + 4, count);
var (idx, found) = FindEntry(span, genderRace, setId);
if (!found)
return 0;
}
return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize );
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
}
public static ushort GetDefault( MetaIndex metaIndex, GenderRace genderRace, ushort setId )
=> GetDefault( CharacterUtility.ReverseIndices[ ( int )metaIndex ], genderRace, setId );
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, ushort setId)
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId);
public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId )
=> GetDefault( ( MetaIndex )estType, genderRace, setId );
}
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, ushort setId)
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId);
}

View file

@ -3,15 +3,16 @@ using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Files;
// EVP file structure:
// [Identifier:3 bytes, EVP]
// [NumModels:ushort]
// NumModels x [ModelId:ushort]
// Containing the relevant model IDs. Seems to be sorted.
// NumModels x [DataArray]:512 Byte]
// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet.
// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect.
/// <summary>
/// EVP file structure:
/// [Identifier:3 bytes, EVP]
/// [NumModels:ushort]
/// NumModels x [ModelId:ushort]
/// Containing the relevant model IDs. Seems to be sorted.
/// NumModels x [DataArray]:512 Byte]
/// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet.
/// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect.
/// </summary>
public unsafe class EvpFile : MetaBaseFile
{
public const int FlagArraySize = 512;
@ -26,45 +27,39 @@ public unsafe class EvpFile : MetaBaseFile
}
public int NumModels
=> Data[ 3 ];
=> Data[3];
public ReadOnlySpan< ushort > ModelSetIds
public ReadOnlySpan<ushort> ModelSetIds
=> new(Data + 4, NumModels);
public ushort ModelSetId( int idx )
=> idx >= 0 && idx < NumModels ? ( ( ushort* )( Data + 4 ) )[ idx ] : ushort.MaxValue;
public ushort ModelSetId(int idx)
=> idx >= 0 && idx < NumModels ? ((ushort*)(Data + 4))[idx] : ushort.MaxValue;
public ReadOnlySpan< EvpFlag > Flags( int idx )
public ReadOnlySpan<EvpFlag> Flags(int idx)
=> new(Data + 4 + idx * FlagArraySize, FlagArraySize);
public EvpFlag Flag( ushort modelSet, int arrayIndex )
public EvpFlag Flag(ushort modelSet, int arrayIndex)
{
if( arrayIndex is >= FlagArraySize or < 0 )
{
if (arrayIndex is >= FlagArraySize or < 0)
return EvpFlag.None;
}
var ids = ModelSetIds;
for( var i = 0; i < ids.Length; ++i )
for (var i = 0; i < ids.Length; ++i)
{
var model = ids[ i ];
if( model < modelSet )
{
var model = ids[i];
if (model < modelSet)
continue;
}
if( model > modelSet )
{
if (model > modelSet)
break;
}
return Flags( i )[ arrayIndex ];
return Flags(i)[arrayIndex];
}
return EvpFlag.None;
}
public EvpFile()
: base( ( MetaIndex )1 ) // TODO: Name
public EvpFile(MetaFileManager manager)
: base(manager, (MetaIndex)1) // TODO: Name
{ }
}
}

View file

@ -1,11 +1,10 @@
using System;
using System.Numerics;
using Newtonsoft.Json;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Services;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.String.Functions;
@ -16,7 +15,7 @@ public class ImcException : Exception
public readonly ImcManipulation Manipulation;
public readonly string GamePath;
public ImcException( ImcManipulation manip, Utf8GamePath path )
public ImcException(ImcManipulation manip, Utf8GamePath path)
{
Manipulation = manip;
GamePath = path.ToString();
@ -34,44 +33,42 @@ public unsafe class ImcFile : MetaBaseFile
private const int PreambleSize = 4;
public int ActualLength
=> NumParts * sizeof( ImcEntry ) * ( Count + 1 ) + PreambleSize;
=> NumParts * sizeof(ImcEntry) * (Count + 1) + PreambleSize;
public int Count
=> CountInternal( Data );
=> CountInternal(Data);
public readonly Utf8GamePath Path;
public readonly int NumParts;
public ReadOnlySpan< ImcEntry > Span
=> new(( ImcEntry* )( Data + PreambleSize ), ( Length - PreambleSize ) / sizeof( ImcEntry ));
public ReadOnlySpan<ImcEntry> Span
=> new((ImcEntry*)(Data + PreambleSize), (Length - PreambleSize) / sizeof(ImcEntry));
private static int CountInternal( byte* data )
=> *( ushort* )data;
private static int CountInternal(byte* data)
=> *(ushort*)data;
private static ushort PartMask( byte* data )
=> *( ushort* )( data + 2 );
private static ushort PartMask(byte* data)
=> *(ushort*)(data + 2);
private static ImcEntry* VariantPtr( byte* data, int partIdx, int variantIdx )
private static ImcEntry* VariantPtr(byte* data, int partIdx, int variantIdx)
{
var flag = 1 << partIdx;
if( ( PartMask( data ) & flag ) == 0 || variantIdx > CountInternal( data ) )
{
if ((PartMask(data) & flag) == 0 || variantIdx > CountInternal(data))
return null;
}
var numParts = BitOperations.PopCount( PartMask( data ) );
var ptr = ( ImcEntry* )( data + PreambleSize );
var numParts = BitOperations.PopCount(PartMask(data));
var ptr = (ImcEntry*)(data + PreambleSize);
ptr += variantIdx * numParts + partIdx;
return ptr;
}
public ImcEntry GetEntry( int partIdx, int variantIdx )
public ImcEntry GetEntry(int partIdx, int variantIdx)
{
var ptr = VariantPtr( Data, partIdx, variantIdx );
var ptr = VariantPtr(Data, partIdx, variantIdx);
return ptr == null ? new ImcEntry() : *ptr;
}
public static int PartIndex( EquipSlot slot )
public static int PartIndex(EquipSlot slot)
=> slot switch
{
EquipSlot.Head => 0,
@ -87,52 +84,44 @@ public unsafe class ImcFile : MetaBaseFile
_ => 0,
};
public bool EnsureVariantCount( int numVariants )
public bool EnsureVariantCount(int numVariants)
{
if( numVariants <= Count )
{
if (numVariants <= Count)
return true;
}
var oldCount = Count;
*( ushort* )Data = ( ushort )numVariants;
if( ActualLength > Length )
*(ushort*)Data = (ushort)numVariants;
if (ActualLength > Length)
{
var newLength = ( ( ( ActualLength - 1 ) >> 7 ) + 1 ) << 7;
Penumbra.Log.Verbose( $"Resized IMC {Path} from {Length} to {newLength}." );
ResizeResources( newLength );
var newLength = (((ActualLength - 1) >> 7) + 1) << 7;
Penumbra.Log.Verbose($"Resized IMC {Path} from {Length} to {newLength}.");
ResizeResources(newLength);
}
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
for( var i = oldCount + 1; i < numVariants + 1; ++i )
{
MemoryUtility.MemCpyUnchecked( defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof( ImcEntry ) );
}
var defaultPtr = (ImcEntry*)(Data + PreambleSize);
for (var i = oldCount + 1; i < numVariants + 1; ++i)
MemoryUtility.MemCpyUnchecked(defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof(ImcEntry));
Penumbra.Log.Verbose( $"Expanded IMC {Path} from {oldCount} to {numVariants} variants." );
Penumbra.Log.Verbose($"Expanded IMC {Path} from {oldCount} to {numVariants} variants.");
return true;
}
public bool SetEntry( int partIdx, int variantIdx, ImcEntry entry )
public bool SetEntry(int partIdx, int variantIdx, ImcEntry entry)
{
if( partIdx >= NumParts )
if (partIdx >= NumParts)
return false;
EnsureVariantCount(variantIdx);
var variantPtr = VariantPtr(Data, partIdx, variantIdx);
if (variantPtr == null)
{
Penumbra.Log.Error("Error during expansion of imc file.");
return false;
}
EnsureVariantCount( variantIdx );
var variantPtr = VariantPtr( Data, partIdx, variantIdx );
if( variantPtr == null )
{
Penumbra.Log.Error( "Error during expansion of imc file." );
if (variantPtr->Equals(entry))
return false;
}
if( variantPtr->Equals( entry ) )
{
return false;
}
*variantPtr = entry;
return true;
@ -141,70 +130,64 @@ public unsafe class ImcFile : MetaBaseFile
public override void Reset()
{
var file = DalamudServices.SGameData.GetFile( Path.ToString() );
fixed( byte* ptr = file!.Data )
var file = DalamudServices.SGameData.GetFile(Path.ToString());
fixed (byte* ptr = file!.Data)
{
MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length );
MemoryUtility.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
MemoryUtility.MemCpyUnchecked(Data, ptr, file.Data.Length);
MemoryUtility.MemSet(Data + file.Data.Length, 0, Length - file.Data.Length);
}
}
public ImcFile( ImcManipulation manip )
: base( 0 )
public ImcFile(MetaFileManager manager, ImcManipulation manip)
: base(manager, 0)
{
Path = manip.GamePath();
var file = DalamudServices.SGameData.GetFile( Path.ToString() );
if( file == null )
{
throw new ImcException( manip, Path );
}
var file = manager.GameData.GetFile(Path.ToString());
if (file == null)
throw new ImcException(manip, Path);
fixed( byte* ptr = file.Data )
fixed (byte* ptr = file.Data)
{
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
AllocateData( file.Data.Length );
MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length );
NumParts = BitOperations.PopCount(*(ushort*)(ptr + 2));
AllocateData(file.Data.Length);
MemoryUtility.MemCpyUnchecked(Data, ptr, file.Data.Length);
}
}
public static ImcEntry GetDefault( Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists )
=> GetDefault( path.ToString(), slot, variantIdx, out exists );
public static ImcEntry GetDefault(MetaFileManager manager, Utf8GamePath path, EquipSlot slot, int variantIdx, out bool exists)
=> GetDefault(manager, path.ToString(), slot, variantIdx, out exists);
public static ImcEntry GetDefault( string path, EquipSlot slot, int variantIdx, out bool exists )
public static ImcEntry GetDefault(MetaFileManager manager, string path, EquipSlot slot, int variantIdx, out bool exists)
{
var file = DalamudServices.SGameData.GetFile( path );
var file = manager.GameData.GetFile(path);
exists = false;
if( file == null )
{
if (file == null)
throw new Exception();
}
fixed( byte* ptr = file.Data )
fixed (byte* ptr = file.Data)
{
var entry = VariantPtr( ptr, PartIndex( slot ), variantIdx );
if( entry != null )
{
exists = true;
return *entry;
}
var entry = VariantPtr(ptr, PartIndex(slot), variantIdx);
if (entry == null)
return new ImcEntry();
return new ImcEntry();
exists = true;
return *entry;
}
}
public void Replace( ResourceHandle* resource )
public void Replace(ResourceHandle* resource)
{
var (data, length) = resource->GetData();
var newData = Penumbra.MetaFileManager.AllocateDefaultMemory( ActualLength, 8 );
if( newData == null )
var newData = Penumbra.MetaFileManager.AllocateDefaultMemory(ActualLength, 8);
if (newData == null)
{
Penumbra.Log.Error( $"Could not replace loaded IMC data at 0x{( ulong )resource:X}, allocation failed." );
Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed.");
return;
}
MemoryUtility.MemCpyUnchecked( newData, Data, ActualLength );
MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength);
Penumbra.MetaFileManager.Free( data, length );
resource->SetData( ( IntPtr )newData, ActualLength );
Penumbra.MetaFileManager.Free(data, length);
resource->SetData((IntPtr)newData, ActualLength);
}
}
}

View file

@ -8,79 +8,78 @@ namespace Penumbra.Meta.Files;
public unsafe class MetaBaseFile : IDisposable
{
public byte* Data { get; private set; }
public int Length { get; private set; }
public CharacterUtility.InternalIndex Index { get; }
protected readonly MetaFileManager Manager;
public MetaBaseFile( MetaIndex idx )
=> Index = CharacterUtility.ReverseIndices[ ( int )idx ];
public byte* Data { get; private set; }
public int Length { get; private set; }
public CharacterUtility.InternalIndex Index { get; }
public MetaBaseFile(MetaFileManager manager, MetaIndex idx)
{
Manager = manager;
Index = CharacterUtility.ReverseIndices[(int)idx];
}
protected (IntPtr Data, int Length) DefaultData
=> Penumbra.CharacterUtility.DefaultResource( Index );
=> Manager.CharacterUtility.DefaultResource(Index);
// Reset to default values.
/// <summary> Reset to default values. </summary>
public virtual void Reset()
{ }
// Obtain memory.
protected void AllocateData( int length )
/// <summary> Obtain memory. </summary>
protected void AllocateData(int length)
{
Length = length;
Data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( length );
if( length > 0 )
{
GC.AddMemoryPressure( length );
}
Data = (byte*)Manager.AllocateFileMemory(length);
if (length > 0)
GC.AddMemoryPressure(length);
}
// Free memory.
/// <summary> Free memory. </summary>
protected void ReleaseUnmanagedResources()
{
var ptr = ( IntPtr )Data;
MemoryHelper.GameFree( ref ptr, ( ulong )Length );
if( Length > 0 )
{
GC.RemoveMemoryPressure( Length );
}
var ptr = (IntPtr)Data;
MemoryHelper.GameFree(ref ptr, (ulong)Length);
if (Length > 0)
GC.RemoveMemoryPressure(Length);
Length = 0;
Data = null;
}
// Resize memory while retaining data.
protected void ResizeResources( int newLength )
/// <summary> Resize memory while retaining data. </summary>
protected void ResizeResources(int newLength)
{
if( newLength == Length )
{
if (newLength == Length)
return;
}
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
if( newLength > Length )
var data = (byte*)Manager.AllocateFileMemory((ulong)newLength);
if (newLength > Length)
{
MemoryUtility.MemCpyUnchecked( data, Data, Length );
MemoryUtility.MemSet( data + Length, 0, newLength - Length );
MemoryUtility.MemCpyUnchecked(data, Data, Length);
MemoryUtility.MemSet(data + Length, 0, newLength - Length);
}
else
{
MemoryUtility.MemCpyUnchecked( data, Data, newLength );
MemoryUtility.MemCpyUnchecked(data, Data, newLength);
}
ReleaseUnmanagedResources();
GC.AddMemoryPressure( newLength );
GC.AddMemoryPressure(newLength);
Data = data;
Length = newLength;
}
// Manually free memory.
/// <summary> Manually free memory. </summary>
public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize( this );
GC.SuppressFinalize(this);
}
~MetaBaseFile()
{
ReleaseUnmanagedResources();
}
}
}

View file

@ -0,0 +1,85 @@
using System;
using System.Runtime.CompilerServices;
using Dalamud.Data;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.GameData;
using Penumbra.Interop.Services;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
namespace Penumbra.Meta;
public unsafe class MetaFileManager
{
internal readonly Configuration Config;
internal readonly CharacterUtility CharacterUtility;
internal readonly ResidentResourceManager ResidentResources;
internal readonly DataManager GameData;
internal readonly ActiveCollections ActiveCollections;
internal readonly ValidityChecker ValidityChecker;
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, DataManager gameData,
ActiveCollections activeCollections, Configuration config, ValidityChecker validityChecker)
{
CharacterUtility = characterUtility;
ResidentResources = residentResources;
GameData = gameData;
ActiveCollections = activeCollections;
Config = config;
ValidityChecker = validityChecker;
SignatureHelper.Initialise(this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void SetFile(MetaBaseFile? file, MetaIndex metaIndex)
{
if (file == null)
CharacterUtility.ResetResource(metaIndex);
else
CharacterUtility.SetResource(metaIndex, (nint)file.Data, file.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public CharacterUtility.MetaList.MetaReverter TemporarilySetFile(MetaBaseFile? file, MetaIndex metaIndex)
=> file == null
? CharacterUtility.TemporarilyResetResource(metaIndex)
: CharacterUtility.TemporarilySetResource(metaIndex, (nint)file.Data, file.Length);
public void ApplyDefaultFiles(ModCollection collection)
{
if (ActiveCollections.Default != collection || !CharacterUtility.Ready || !Config.EnableMods)
return;
ResidentResources.Reload();
collection._cache?.MetaManipulations.SetFiles();
}
/// <summary>
/// Allocate in the games space for file storage.
/// We only need this if using any meta file.
/// </summary>
[Signature(Sigs.GetFileSpace)]
private readonly nint _getFileSpaceAddress = nint.Zero;
public IMemorySpace* GetFileSpace()
=> ((delegate* unmanaged<IMemorySpace*>)_getFileSpaceAddress)();
public void* AllocateFileMemory(ulong length, ulong alignment = 0)
=> GetFileSpace()->Malloc(length, alignment);
public void* AllocateFileMemory(int length, int alignment = 0)
=> AllocateFileMemory((ulong)length, (ulong)alignment);
public void* AllocateDefaultMemory(ulong length, ulong alignment = 0)
=> GetFileSpace()->Malloc(length, alignment);
public void* AllocateDefaultMemory(int length, int alignment = 0)
=> IMemorySpace.GetDefaultSpace()->Malloc((ulong)length, (ulong)alignment);
public void Free(nint ptr, int length)
=> IMemorySpace.Free((void*)ptr, (ulong)length);
}