mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-31 12:53:47 +01:00
133 lines
5 KiB
C#
133 lines
5 KiB
C#
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Structs;
|
|
using Penumbra.Interop.Services;
|
|
using Penumbra.Interop.Structs;
|
|
using Penumbra.Meta.Manipulations;
|
|
using Penumbra.String.Functions;
|
|
|
|
namespace Penumbra.Meta.Files;
|
|
|
|
/// <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;
|
|
private const ushort PreambleSize = 4;
|
|
private const ushort CollapsedBlock = ushort.MaxValue;
|
|
private const ushort IdentifierSize = 2;
|
|
private const ushort EqdpEntrySize = 2;
|
|
private const int FileAlignment = 1 << 9;
|
|
|
|
public readonly int DataOffset;
|
|
|
|
public ushort Identifier
|
|
=> *(ushort*)Data;
|
|
|
|
public ushort BlockSize
|
|
=> *(ushort*)(Data + 2);
|
|
|
|
public ushort BlockCount
|
|
=> *(ushort*)(Data + 4);
|
|
|
|
public int Count
|
|
=> (Length - DataOffset) / EqdpEntrySize;
|
|
|
|
public EqdpEntry this[PrimaryId id]
|
|
{
|
|
get
|
|
{
|
|
if (id.Id >= Count)
|
|
throw new IndexOutOfRangeException();
|
|
|
|
return (EqdpEntry)(*(ushort*)(Data + DataOffset + EqdpEntrySize * id.Id));
|
|
}
|
|
set
|
|
{
|
|
if (id.Id >= Count)
|
|
throw new IndexOutOfRangeException();
|
|
|
|
*(ushort*)(Data + DataOffset + EqdpEntrySize * id.Id) = (ushort)value;
|
|
}
|
|
}
|
|
|
|
public override void Reset()
|
|
{
|
|
var def = (byte*)DefaultData.Data;
|
|
MemoryUtility.MemCpyUnchecked(Data, 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)
|
|
{
|
|
if (controlPtr[i] == CollapsedBlock)
|
|
MemoryUtility.MemSet(myDataPtr, 0, BlockSize * EqdpEntrySize);
|
|
else
|
|
MemoryUtility.MemCpyUnchecked(myDataPtr, dataBasePtr + controlPtr[i], BlockSize * EqdpEntrySize);
|
|
|
|
myControlPtr[i] = (ushort)(i * BlockSize);
|
|
myDataPtr += BlockSize;
|
|
}
|
|
|
|
MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data));
|
|
}
|
|
|
|
public void Reset(IEnumerable<PrimaryId> entries)
|
|
{
|
|
foreach (var entry in entries)
|
|
this[entry] = GetDefault(entry);
|
|
}
|
|
|
|
public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory)
|
|
: base(manager, manager.MarshalAllocator, CharacterUtilityData.EqdpIdx(raceCode, accessory))
|
|
{
|
|
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);
|
|
Reset();
|
|
}
|
|
|
|
public EqdpEntry GetDefault(PrimaryId primaryId)
|
|
=> GetDefault(Manager, Index, primaryId);
|
|
|
|
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, PrimaryId primaryId)
|
|
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, primaryId);
|
|
|
|
public static EqdpEntry GetDefault(byte* data, PrimaryId primaryId)
|
|
{
|
|
var blockSize = *(ushort*)(data + IdentifierSize);
|
|
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
|
|
|
|
var blockIdx = primaryId.Id / blockSize;
|
|
if (blockIdx >= totalBlockCount)
|
|
return 0;
|
|
|
|
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 + primaryId.Id % blockSize));
|
|
}
|
|
|
|
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId)
|
|
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId);
|
|
|
|
public static EqdpEntry GetDefault(MetaFileManager manager, EqdpIdentifier identifier)
|
|
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)identifier.FileIndex()], identifier.SetId);
|
|
}
|