mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
159 lines
6 KiB
C#
159 lines
6 KiB
C#
using Dalamud.Hooking;
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
|
using OtterGui.Classes;
|
|
using OtterGui.Services;
|
|
using Penumbra.GameData;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Files.MaterialStructs;
|
|
using Penumbra.GameData.Interop;
|
|
using Penumbra.GameData.Structs;
|
|
|
|
namespace Glamourer.Interop.Material;
|
|
|
|
public sealed unsafe class PrepareColorSet
|
|
: EventWrapperPtr12Ref34<CharacterBase, MaterialResourceHandle, StainIds, nint, PrepareColorSet.Priority>, IHookService
|
|
{
|
|
private readonly UpdateColorSets _updateColorSets;
|
|
|
|
public enum Priority
|
|
{
|
|
/// <seealso cref="MaterialManager.OnPrepareColorSet"/>
|
|
MaterialManager = 0,
|
|
}
|
|
|
|
public PrepareColorSet(HookManager hooks, UpdateColorSets updateColorSets)
|
|
: base("Prepare Color Set ")
|
|
{
|
|
_updateColorSets = updateColorSets;
|
|
_task = hooks.CreateHook<Delegate>(Name, Sigs.PrepareColorSet, Detour, true);
|
|
}
|
|
|
|
private readonly Task<Hook<Delegate>> _task;
|
|
|
|
public nint Address
|
|
=> (nint)CharacterBase.MemberFunctionPointers.Destroy;
|
|
|
|
public void Enable()
|
|
=> _task.Result.Enable();
|
|
|
|
public void Disable()
|
|
=> _task.Result.Disable();
|
|
|
|
public Task Awaiter
|
|
=> _task;
|
|
|
|
public bool Finished
|
|
=> _task.IsCompletedSuccessfully;
|
|
|
|
private delegate Texture* Delegate(MaterialResourceHandle* material, StainId stainId1, StainId stainId2);
|
|
|
|
private Texture* Detour(MaterialResourceHandle* material, StainId stainId1, StainId stainId2)
|
|
{
|
|
Glamourer.Log.Excessive($"[{Name}] Triggered with 0x{(nint)material:X} {stainId1.Id} {stainId2.Id}.");
|
|
var characterBase = _updateColorSets.Get();
|
|
if (!characterBase.IsCharacterBase)
|
|
return _task.Result.Original(material, stainId1, stainId2);
|
|
|
|
var ret = nint.Zero;
|
|
var stainIds = new StainIds(stainId1, stainId2);
|
|
Invoke(characterBase.AsCharacterBase, material, ref stainIds, ref ret);
|
|
if (ret != nint.Zero)
|
|
return (Texture*)ret;
|
|
|
|
return _task.Result.Original(material, stainIds.Stain1, stainIds.Stain2);
|
|
}
|
|
|
|
public static bool TryGetColorTable(MaterialResourceHandle* material, StainIds stainIds,
|
|
out ColorTable.Table table)
|
|
{
|
|
if (material->ColorTable == null)
|
|
{
|
|
table = default;
|
|
return false;
|
|
}
|
|
|
|
var newTable = *(ColorTable.Table*)material->ColorTable;
|
|
if (GetDyeTable(material, out var dyeTable))
|
|
{
|
|
if (stainIds.Stain1.Id != 0)
|
|
((delegate* unmanaged<MaterialResourceHandle*, ushort*, byte, Half*, uint, void>)MaterialResourceHandle.MemberFunctionPointers
|
|
.ReadStainingTemplate)(material, dyeTable, stainIds.Stain1.Id, (Half*)(&newTable), 0);
|
|
|
|
if (stainIds.Stain2.Id != 0)
|
|
((delegate* unmanaged<MaterialResourceHandle*, ushort*, byte, Half*, uint, void>)MaterialResourceHandle.MemberFunctionPointers
|
|
.ReadStainingTemplate)(material, dyeTable, stainIds.Stain2.Id, (Half*)(&newTable), 1);
|
|
}
|
|
|
|
table = newTable;
|
|
return true;
|
|
}
|
|
|
|
/// <summary> Assumes the actor is valid. </summary>
|
|
public static bool TryGetColorTable(Actor actor, MaterialValueIndex index, out ColorTable.Table table, out ColorRow.Mode mode)
|
|
{
|
|
var idx = index.SlotIndex * MaterialService.MaterialsPerModel + index.MaterialIndex;
|
|
if (!index.TryGetModel(actor, out var model))
|
|
{
|
|
mode = ColorRow.Mode.Dawntrail;
|
|
table = default;
|
|
return false;
|
|
}
|
|
|
|
var handle = (MaterialResourceHandle*)model.AsCharacterBase->Materials[idx];
|
|
if (handle == null)
|
|
{
|
|
mode = ColorRow.Mode.Dawntrail;
|
|
table = default;
|
|
return false;
|
|
}
|
|
|
|
mode = GetMode(handle);
|
|
return TryGetColorTable(handle, GetStains(), out table);
|
|
|
|
StainIds GetStains()
|
|
{
|
|
switch (index.DrawObject)
|
|
{
|
|
case MaterialValueIndex.DrawObjectType.Human:
|
|
return index.SlotIndex < 10 ? actor.Model.GetArmor(((uint)index.SlotIndex).ToEquipSlot()).Stains : StainIds.None;
|
|
case MaterialValueIndex.DrawObjectType.Mainhand:
|
|
var mainhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject;
|
|
return mainhand.IsWeapon ? StainIds.FromWeapon(*mainhand.AsWeapon) : StainIds.None;
|
|
case MaterialValueIndex.DrawObjectType.Offhand:
|
|
var offhand = (Model)actor.AsCharacter->DrawData.WeaponData[1].DrawObject;
|
|
return offhand.IsWeapon ? StainIds.FromWeapon(*offhand.AsWeapon) : StainIds.None;
|
|
default: return StainIds.None;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary> Get the shader mode of the material. </summary>
|
|
public static ColorRow.Mode GetMode(MaterialResourceHandle* handle)
|
|
=> handle == null
|
|
? ColorRow.Mode.Dawntrail
|
|
: handle->ShpkNameSpan.SequenceEqual("characterlegacy.shpk"u8)
|
|
? ColorRow.Mode.Legacy
|
|
: ColorRow.Mode.Dawntrail;
|
|
|
|
/// <summary> Get the correct dye table for a material. </summary>
|
|
private static bool GetDyeTable(MaterialResourceHandle* material, out ushort* ptr)
|
|
{
|
|
ptr = null;
|
|
if (material->AdditionalDataSize is 0 || material->AdditionalData is null)
|
|
return false;
|
|
|
|
var flags1 = material->AdditionalData[0];
|
|
if ((flags1 & 0xF0) is 0)
|
|
{
|
|
ptr = (ushort*)material + 0x100;
|
|
return true;
|
|
}
|
|
|
|
var flags2 = material->AdditionalData[1];
|
|
var offset = 4 * (1 << (flags1 >> 4)) * (1 << (flags2 & 0x0F));
|
|
ptr = (ushort*)material->DataSet + offset;
|
|
return true;
|
|
}
|
|
}
|