Penumbra/Penumbra/Meta/Files/EqdpFile.cs
2024-06-18 17:52:34 +02:00

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);
}