mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-30 12:23:41 +01:00
Meta stuff is terrible.
This commit is contained in:
parent
0186f176d0
commit
1d82e882ed
35 changed files with 1265 additions and 1247 deletions
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
85
Penumbra/Meta/MetaFileManager.cs
Normal file
85
Penumbra/Meta/MetaFileManager.cs
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue