Added Color Table Hook from MemShield, currently Experimental and default on as a test.

This commit is contained in:
2025-09-02 19:51:36 +01:00
parent 5cac38f446
commit 4853afd1eb
5 changed files with 131 additions and 3 deletions

View File

@@ -0,0 +1,106 @@
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using MareSynchronos.MareConfiguration;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Core;
using System.Buffers;
namespace MareSynchronos.Interop;
public unsafe sealed class ColorTableHook : IDisposable
{
// Based on https://github.com/Exter-N/MemShield/blob/main/MemShield/Plugin.cs
private readonly ILogger<ColorTableHook> _logger;
private const int ColorTableSize = 2048;
private const int ColorDyeTableSize = 128;
private readonly MareConfigService _configService;
#region signatures
#pragma warning disable CS0649
[Signature("E8 ?? ?? ?? ?? 49 89 04 3E",
DetourName = nameof(PrepareColorTableDetour))]
private Hook<MaterialResourceHandle.Delegates.PrepareColorTable> _prepareColorTable = null!;
#pragma warning restore CS0649
#endregion
public ColorTableHook(ILogger<ColorTableHook> logger, IGameInteropProvider gameInteropProvider, MareConfigService configService)
{
_logger = logger;
_configService = configService;
logger.LogInformation("Initializing ColorTableHook");
gameInteropProvider.InitializeFromAttributes(this);
if (_configService.Current.AttemptColorTableProtection)
{
_prepareColorTable.Enable();
}
}
public void Dispose()
{
if (_configService.Current.AttemptColorTableProtection)
{
_prepareColorTable.Disable();
}
_prepareColorTable.Dispose();
}
public void EnableHooks()
{
_prepareColorTable.Enable();
}
public void DisableHooks()
{
_prepareColorTable.Disable();
}
private static int GetDataSetExpectedSize(uint dataFlags)
=> (dataFlags & 16) != 0
? ColorTableSize + ((dataFlags & 8) != 0 ? ColorDyeTableSize : 0)
: 0;
private Texture* PrepareColorTableDetour(MaterialResourceHandle* @this, byte stain0Id, byte stain1Id)
{
if(!_configService.Current.AttemptColorTableProtection)
{
return _prepareColorTable.Original(@this, stain0Id, stain1Id);
}
var expectedSize = GetDataSetExpectedSize(@this->DataFlags);
if (@this->DataSetSize >= expectedSize)
return _prepareColorTable.Original(@this, stain0Id, stain1Id);
var originalDataSetPtr = @this->DataSet;
var originalDataSet = new ReadOnlySpan<byte>(originalDataSetPtr, @this->DataSetSize);
var pool = ArrayPool<byte>.Shared;
var buffer = pool.Rent(expectedSize);
try
{
var bufferSpan = buffer.AsSpan(..expectedSize);
originalDataSet.CopyTo(bufferSpan);
bufferSpan[@this->DataSetSize..].Clear();
fixed (byte* bufferPtr = buffer)
{
@this->DataSet = bufferPtr;
try
{
return _prepareColorTable.Original(@this, stain0Id, stain1Id);
}
finally
{
@this->DataSet = originalDataSetPtr;
}
}
}
finally
{
pool.Return(buffer);
}
}
}