mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 20:24:17 +01:00
188 lines
5.7 KiB
C#
188 lines
5.7 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.Interop.Services;
|
|
using Penumbra.Interop.Structs;
|
|
using Penumbra.Meta.Manipulations;
|
|
using Penumbra.String.Functions;
|
|
|
|
namespace Penumbra.Meta.Files;
|
|
|
|
/// <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;
|
|
private const ushort EntrySize = 2;
|
|
private const int IncreaseSize = 512;
|
|
|
|
public int Count
|
|
=> *(int*)Data;
|
|
|
|
private int Size
|
|
=> 4 + Count * (EntryDescSize + EntrySize);
|
|
|
|
public enum EstEntryChange
|
|
{
|
|
Unchanged,
|
|
Changed,
|
|
Added,
|
|
Removed,
|
|
}
|
|
|
|
public ushort this[GenderRace genderRace, ushort setId]
|
|
{
|
|
get
|
|
{
|
|
var (idx, exists) = FindEntry(genderRace, setId);
|
|
if (!exists)
|
|
return 0;
|
|
|
|
return *(ushort*)(Data + EntryDescSize * (Count + 1) + EntrySize * idx);
|
|
}
|
|
set => SetEntry(genderRace, setId, value);
|
|
}
|
|
|
|
private void InsertEntry(int idx, GenderRace genderRace, ushort setId, ushort skeletonId)
|
|
{
|
|
if (Length < Size + EntryDescSize + EntrySize)
|
|
ResizeResources(Length + IncreaseSize);
|
|
|
|
var control = (Info*)(Data + 4);
|
|
var entries = (ushort*)(control + Count);
|
|
|
|
for (var i = Count - 1; i >= idx; --i)
|
|
entries[i + 3] = entries[i];
|
|
|
|
entries[idx + 2] = skeletonId;
|
|
|
|
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];
|
|
|
|
control[idx] = new Info(genderRace, setId);
|
|
|
|
*(int*)Data = Count + 1;
|
|
}
|
|
|
|
private void RemoveEntry(int idx)
|
|
{
|
|
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 = 0; i < idx; ++i)
|
|
entries[i - 2] = entries[i];
|
|
|
|
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;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
|
private struct Info : IComparable<Info>
|
|
{
|
|
public readonly ushort SetId;
|
|
public readonly GenderRace GenderRace;
|
|
|
|
public Info(GenderRace gr, ushort setId)
|
|
{
|
|
GenderRace = gr;
|
|
SetId = setId;
|
|
}
|
|
|
|
public int CompareTo(Info other)
|
|
{
|
|
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)
|
|
{
|
|
var idx = data.BinarySearch(new Info(genderRace, setId));
|
|
return idx < 0 ? (~idx, false) : (idx, true);
|
|
}
|
|
|
|
private (int, bool) FindEntry(GenderRace genderRace, ushort setId)
|
|
{
|
|
var span = new ReadOnlySpan<Info>(Data + 4, Count);
|
|
return FindEntry(span, genderRace, setId);
|
|
}
|
|
|
|
public EstEntryChange SetEntry(GenderRace genderRace, ushort setId, ushort skeletonId)
|
|
{
|
|
var (idx, exists) = FindEntry(genderRace, setId);
|
|
if (exists)
|
|
{
|
|
var value = *(ushort*)(Data + 4 * (Count + 1) + 2 * idx);
|
|
if (value == skeletonId)
|
|
return EstEntryChange.Unchanged;
|
|
|
|
if (skeletonId == 0)
|
|
{
|
|
RemoveEntry(idx);
|
|
return EstEntryChange.Removed;
|
|
}
|
|
|
|
*(ushort*)(Data + 4 * (Count + 1) + 2 * idx) = skeletonId;
|
|
return EstEntryChange.Changed;
|
|
}
|
|
|
|
if (skeletonId == 0)
|
|
return EstEntryChange.Unchanged;
|
|
|
|
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);
|
|
}
|
|
|
|
public EstFile(MetaFileManager manager, EstManipulation.EstType estType)
|
|
: base(manager, (MetaIndex)estType)
|
|
{
|
|
var length = DefaultData.Length;
|
|
AllocateData(length + IncreaseSize);
|
|
Reset();
|
|
}
|
|
|
|
public ushort GetDefault(GenderRace genderRace, ushort setId)
|
|
=> GetDefault(Manager, Index, genderRace, setId);
|
|
|
|
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId)
|
|
{
|
|
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);
|
|
}
|
|
|
|
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, ushort setId)
|
|
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId);
|
|
|
|
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, ushort setId)
|
|
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId);
|
|
}
|