diff --git a/MareSynchronos/Interop/ColorTableHook.cs b/MareSynchronos/Interop/ColorTableHook.cs new file mode 100644 index 0000000..9b0c272 --- /dev/null +++ b/MareSynchronos/Interop/ColorTableHook.cs @@ -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 _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 _prepareColorTable = null!; +#pragma warning restore CS0649 + #endregion + + public ColorTableHook(ILogger 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(originalDataSetPtr, @this->DataSetSize); + var pool = ArrayPool.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); + } + } +} diff --git a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs index aca4638..b7e2633 100644 --- a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs @@ -32,6 +32,7 @@ public class MareConfig : IMareConfiguration public bool LogTraceLog { get; set; } = false; public bool LogEvents { get; set; } = true; public bool HoldCombatApplication { get; set; } = true; + public bool AttemptColorTableProtection { get; set; } = true; public double MaxLocalCacheInGiB { get; set; } = 20; public bool OpenGposeImportOnGposeStart { get; set; } = false; public bool OpenPopupOnAdd { get; set; } = true; diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 8c9f5bf..63cc14d 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -1,8 +1,8 @@  - + ClubPenguinSync - 1.7.0.6 + 1.7.0.7 https://github.com/Rawrington/ClubPenguinSync/ diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index ddbd5ea..81323c6 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -161,6 +161,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 51dfee1..2891e2c 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -8,6 +8,7 @@ using Dalamud.Utility; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Comparer; using MareSynchronos.FileCache; +using MareSynchronos.Interop; using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration.Models; @@ -25,6 +26,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text.Json; namespace MareSynchronos.UI; @@ -50,6 +52,7 @@ public class SettingsUi : WindowMediatorSubscriberBase private readonly PlayerPerformanceService _playerPerformanceService; private readonly AccountRegistrationService _registerService; private readonly ServerConfigurationManager _serverConfigurationManager; + private readonly ColorTableHook _colorTableHook; private readonly UiSharedService _uiShared; private bool _deleteAccountPopupModalShown = false; private string _lastTab = string.Empty; @@ -77,11 +80,12 @@ public class SettingsUi : WindowMediatorSubscriberBase FileCacheManager fileCacheManager, FileCompactor fileCompactor, ApiController apiController, IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor, - DalamudUtilService dalamudUtilService, AccountRegistrationService registerService) : base(logger, mediator, "Club Penguin Sync Settings", performanceCollector) + DalamudUtilService dalamudUtilService, AccountRegistrationService registerService, ColorTableHook colorTableHook) : base(logger, mediator, "Club Penguin Sync Settings", performanceCollector) { _configService = configService; _pairManager = pairManager; _chatService = chatService; + _colorTableHook = colorTableHook; _guiHookService = guiHookService; _serverConfigurationManager = serverConfigurationManager; _playerPerformanceConfigService = playerPerformanceConfigService; @@ -640,6 +644,22 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); } + bool attemptColorTableProtection = _configService.Current.AttemptColorTableProtection; + if (ImGui.Checkbox("[EXPERIMENTAL] Attempt to fix potential ColorTable crashes.", ref attemptColorTableProtection)) + { + if (attemptColorTableProtection) + { + _colorTableHook.EnableHooks(); + } + else + { + _colorTableHook.DisableHooks(); + } + //this shouldnt need the mediator as this is the ONLY source of truth. + _configService.Current.AttemptColorTableProtection = attemptColorTableProtection; + _configService.Save(); + } + bool serializedApplications = _configService.Current.SerialApplication; if (ImGui.Checkbox("Serialized player applications", ref serializedApplications)) {