From 327b6275c02f3f42170cdbfc7d5ffca23a58a744 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 29 Feb 2024 01:16:01 +0100 Subject: [PATCH] refactor a little bit --- MareSynchronos/FileCache/CacheMonitor.cs | 20 +- MareSynchronos/FileCache/FileCacheManager.cs | 13 +- MareSynchronos/Interop/Ipc/IIpcCaller.cs | 7 + .../Interop/Ipc/IpcCallerCustomize.cs | 117 +++ .../Interop/Ipc/IpcCallerGlamourer.cs | 205 +++++ MareSynchronos/Interop/Ipc/IpcCallerHeels.cs | 94 ++ .../Interop/Ipc/IpcCallerHonorific.cs | 133 +++ .../Interop/Ipc/IpcCallerPenumbra.cs | 328 +++++++ MareSynchronos/Interop/Ipc/IpcManager.cs | 52 ++ MareSynchronos/Interop/Ipc/RedrawManager.cs | 51 ++ MareSynchronos/Interop/IpcManager.cs | 802 ------------------ .../PlayerData/Export/MareCharaFileManager.cs | 26 +- .../Factories/PairHandlerFactory.cs | 2 +- .../PlayerData/Factories/PlayerDataFactory.cs | 17 +- .../PlayerData/Handlers/GameObjectHandler.cs | 1 - .../PlayerData/Handlers/PairHandler.cs | 58 +- MareSynchronos/Plugin.cs | 16 +- MareSynchronos/Services/Mediator/Messages.cs | 5 +- .../PluginWarningNotificationService.cs | 8 +- MareSynchronos/UI/DataAnalysisUi.cs | 4 +- MareSynchronos/UI/SettingsUi.cs | 6 +- MareSynchronos/UI/UISharedService.cs | 16 +- MareSynchronos/Utils/Crypto.cs | 4 +- 23 files changed, 1091 insertions(+), 894 deletions(-) create mode 100644 MareSynchronos/Interop/Ipc/IIpcCaller.cs create mode 100644 MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs create mode 100644 MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs create mode 100644 MareSynchronos/Interop/Ipc/IpcCallerHeels.cs create mode 100644 MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs create mode 100644 MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs create mode 100644 MareSynchronos/Interop/Ipc/IpcManager.cs create mode 100644 MareSynchronos/Interop/Ipc/RedrawManager.cs delete mode 100644 MareSynchronos/Interop/IpcManager.cs diff --git a/MareSynchronos/FileCache/CacheMonitor.cs b/MareSynchronos/FileCache/CacheMonitor.cs index 18efcf8..509d799 100644 --- a/MareSynchronos/FileCache/CacheMonitor.cs +++ b/MareSynchronos/FileCache/CacheMonitor.cs @@ -1,4 +1,4 @@ -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; @@ -33,7 +33,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase _fileCompactor = fileCompactor; Mediator.Subscribe(this, (_) => { - StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); + StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); StartMareWatcher(configService.Current.CacheFolder); InvokeScan(); }); @@ -42,7 +42,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (_) => { StartMareWatcher(configService.Current.CacheFolder); - StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); + StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); InvokeScan(); }); Mediator.Subscribe(this, (msg) => @@ -50,9 +50,9 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase StartPenumbraWatcher(msg.ModDirectory); InvokeScan(); }); - if (_ipcManager.CheckPenumbraApi() && !string.IsNullOrEmpty(_ipcManager.PenumbraModDirectory)) + if (_ipcManager.Penumbra.APIAvailable && !string.IsNullOrEmpty(_ipcManager.Penumbra.PenumbraModDirectory)) { - StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); + StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); } if (configService.Current.HasValidSetup()) { @@ -466,7 +466,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase private void FullFileScan(CancellationToken ct) { TotalFiles = 1; - var penumbraDir = _ipcManager.PenumbraModDirectory; + var penumbraDir = _ipcManager.Penumbra.PenumbraModDirectory; bool penDirExists = true; bool cacheDirExists = true; if (string.IsNullOrEmpty(penumbraDir) || !Directory.Exists(penumbraDir)) @@ -558,7 +558,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase { if (ct.IsCancellationRequested) return; - if (!_ipcManager.CheckPenumbraApi()) + if (!_ipcManager.Penumbra.APIAvailable) { Logger.LogWarning("Penumbra not available"); return; @@ -605,7 +605,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase Logger.LogTrace("Threads exited"); - if (!_ipcManager.CheckPenumbraApi()) + if (!_ipcManager.Penumbra.APIAvailable) { Logger.LogWarning("Penumbra not available"); return; @@ -628,7 +628,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase Logger.LogTrace("Scanner validated existing db files"); - if (!_ipcManager.CheckPenumbraApi()) + if (!_ipcManager.Penumbra.APIAvailable) { Logger.LogWarning("Penumbra not available"); return; @@ -648,7 +648,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase { if (ct.IsCancellationRequested) return; - if (!_ipcManager.CheckPenumbraApi()) + if (!_ipcManager.Penumbra.APIAvailable) { Logger.LogWarning("Penumbra not available"); return; diff --git a/MareSynchronos/FileCache/FileCacheManager.cs b/MareSynchronos/FileCache/FileCacheManager.cs index 2d11e57..b7db84e 100644 --- a/MareSynchronos/FileCache/FileCacheManager.cs +++ b/MareSynchronos/FileCache/FileCacheManager.cs @@ -1,5 +1,5 @@ using LZ4; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; @@ -53,8 +53,8 @@ public sealed class FileCacheManager : IHostedService if (!fi.Exists) return null; _logger.LogTrace("Creating file entry for {path}", path); var fullName = fi.FullName.ToLowerInvariant(); - if (!fullName.Contains(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null; - string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); + if (!fullName.Contains(_ipcManager.Penumbra.PenumbraModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null; + string prefixedPath = fullName.Replace(_ipcManager.Penumbra.PenumbraModDirectory!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); return CreateFileCacheEntity(fi, prefixedPath); } @@ -158,7 +158,8 @@ public sealed class FileCacheManager : IHostedService private FileCacheEntity? GetFileCacheByPath(string path) { - var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase); + var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant() + .Replace(_ipcManager.Penumbra.PenumbraModDirectory!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase); var entry = _fileCaches.SelectMany(v => v.Value).FirstOrDefault(f => f.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)); if (entry == null) @@ -180,7 +181,7 @@ public sealed class FileCacheManager : IHostedService { var cleanedPaths = paths.Distinct(StringComparer.OrdinalIgnoreCase).ToDictionary(p => p, p => p.Replace("/", "\\", StringComparison.OrdinalIgnoreCase) - .Replace(_ipcManager.PenumbraModDirectory!, _ipcManager.PenumbraModDirectory!.EndsWith('\\') ? PenumbraPrefix + '\\' : PenumbraPrefix, StringComparison.OrdinalIgnoreCase) + .Replace(_ipcManager.Penumbra.PenumbraModDirectory!, _ipcManager.Penumbra.PenumbraModDirectory!.EndsWith('\\') ? PenumbraPrefix + '\\' : PenumbraPrefix, StringComparison.OrdinalIgnoreCase) .Replace(_configService.Current.CacheFolder, _configService.Current.CacheFolder.EndsWith('\\') ? CachePrefix + '\\' : CachePrefix, StringComparison.OrdinalIgnoreCase) .Replace("\\\\", "\\", StringComparison.Ordinal), StringComparer.OrdinalIgnoreCase); @@ -352,7 +353,7 @@ public sealed class FileCacheManager : IHostedService { if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase)) { - fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(PenumbraPrefix, _ipcManager.PenumbraModDirectory, StringComparison.Ordinal)); + fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(PenumbraPrefix, _ipcManager.Penumbra.PenumbraModDirectory, StringComparison.Ordinal)); } else if (fileCache.PrefixedFilePath.StartsWith(CachePrefix, StringComparison.OrdinalIgnoreCase)) { diff --git a/MareSynchronos/Interop/Ipc/IIpcCaller.cs b/MareSynchronos/Interop/Ipc/IIpcCaller.cs new file mode 100644 index 0000000..faa993a --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IIpcCaller.cs @@ -0,0 +1,7 @@ +namespace MareSynchronos.Interop.Ipc; + +public interface IIpcCaller : IDisposable +{ + bool APIAvailable { get; } + void CheckAPI(); +} diff --git a/MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs b/MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs new file mode 100644 index 0000000..df13caa --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs @@ -0,0 +1,117 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using Dalamud.Utility; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerCustomize : IIpcCaller +{ + private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion; + private readonly ICallGateSubscriber _customizePlusGetBodyScale; + private readonly ICallGateSubscriber _customizePlusOnScaleUpdate; + private readonly ICallGateSubscriber _customizePlusRevertCharacter; + private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; + private readonly ILogger _logger; + private readonly DalamudUtilService _dalamudUtil; + private readonly MareMediator _mareMediator; + + public IpcCallerCustomize(ILogger logger, DalamudPluginInterface dalamudPluginInterface, + DalamudUtilService dalamudUtil, MareMediator mareMediator) + { + _customizePlusApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("CustomizePlus.GetApiVersion"); + _customizePlusGetBodyScale = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.GetProfileFromCharacter"); + _customizePlusRevertCharacter = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.RevertCharacter"); + _customizePlusSetBodyScaleToCharacter = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.SetProfileToCharacter"); + _customizePlusOnScaleUpdate = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.OnProfileUpdate"); + + _customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange); + _logger = logger; + _dalamudUtil = dalamudUtil; + _mareMediator = mareMediator; + + CheckAPI(); + } + + public bool APIAvailable { get; private set; } = false; + + public async Task RevertAsync(nint character) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + _logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X")); + _customizePlusRevertCharacter!.InvokeAction(c); + } + }).ConfigureAwait(false); + } + + public async Task SetBodyScaleAsync(nint character, string scale) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale)); + _logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X")); + if (scale.IsNullOrEmpty()) + { + _customizePlusRevertCharacter!.InvokeAction(c); + } + else + { + _customizePlusSetBodyScaleToCharacter!.InvokeAction(decodedScale, c); + } + } + }).ConfigureAwait(false); + } + + public async Task GetScaleAsync(nint character) + { + if (!APIAvailable) return null; + var scale = await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + return _customizePlusGetBodyScale.InvokeFunc(c); + } + + return string.Empty; + }).ConfigureAwait(false); + if (string.IsNullOrEmpty(scale)) return string.Empty; + return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale)); + } + + public void CheckAPI() + { + try + { + var version = _customizePlusApiVersion.InvokeFunc(); + APIAvailable = (version.Item1 == 3 && version.Item2 >= 0); + } + catch + { + APIAvailable = false; + } + } + + private void OnCustomizePlusScaleChange(string? profileName, string? scale) + { + _mareMediator.Publish(new CustomizePlusMessage(profileName ?? string.Empty)); + } + + public void Dispose() + { + _customizePlusOnScaleUpdate.Unsubscribe(OnCustomizePlusScaleChange); + } +} diff --git a/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs b/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs new file mode 100644 index 0000000..5c98c2e --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs @@ -0,0 +1,205 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerGlamourer : IIpcCaller +{ + private readonly ICallGateSubscriber<(int, int)> _glamourerApiVersions; + private readonly ICallGateSubscriber? _glamourerApplyAll; + private readonly ICallGateSubscriber? _glamourerGetAllCustomization; + private readonly ICallGateSubscriber _glamourerRevert; + private readonly ICallGateSubscriber _glamourerRevertByName; + private readonly ICallGateSubscriber _glamourerUnlock; + private readonly ILogger _logger; + private readonly DalamudPluginInterface _pi; + private readonly DalamudUtilService _dalamudUtil; + private readonly MareMediator _mareMediator; + private readonly RedrawManager _redrawManager; + private bool _shownGlamourerUnavailable = false; + private readonly uint LockCode = 0x6D617265; + + public IpcCallerGlamourer(ILogger logger, DalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator, + RedrawManager redrawManager) + { + _glamourerApiVersions = pi.GetIpcSubscriber<(int, int)>("Glamourer.ApiVersions"); + _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomizationFromCharacter"); + _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacterLock"); + _glamourerRevert = pi.GetIpcSubscriber("Glamourer.RevertCharacterLock"); + _glamourerRevertByName = pi.GetIpcSubscriber("Glamourer.RevertLock"); + _glamourerUnlock = pi.GetIpcSubscriber("Glamourer.UnlockName"); + + pi.GetIpcSubscriber, object?>("Glamourer.StateChanged").Subscribe((type, address, customize) => GlamourerChanged(address)); + _logger = logger; + _pi = pi; + _dalamudUtil = dalamudUtil; + _mareMediator = mareMediator; + _redrawManager = redrawManager; + CheckAPI(); + } + + public bool APIAvailable { get; private set; } + + public void CheckAPI() + { + bool apiAvailable = false; + try + { + var version = _glamourerApiVersions.InvokeFunc(); + bool versionValid = (_pi.InstalledPlugins + .FirstOrDefault(p => string.Equals(p.InternalName, "Glamourer", StringComparison.OrdinalIgnoreCase)) + ?.Version ?? new Version(0, 0, 0, 0)) >= new Version(1, 0, 6, 1); + if (version.Item1 == 0 && version.Item2 >= 1 && versionValid) + { + apiAvailable = true; + } + _shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable; + + APIAvailable = apiAvailable; + } + catch + { + APIAvailable = apiAvailable; + } + finally + { + if (!apiAvailable && !_shownGlamourerUnavailable) + { + _shownGlamourerUnavailable = true; + _mareMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare. If you just updated Glamourer, ignore this message.", + NotificationType.Error)); + } + } + } + + public void Dispose() + { + _pi.GetIpcSubscriber, object?>("Glamourer.StateChanged").Unsubscribe((type, address, customize) => GlamourerChanged(address)); + } + + public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) + { + if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; + + await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false); + + try + { + + await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => + { + try + { + logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); + _glamourerApplyAll!.InvokeAction(customization, chara, LockCode); + } + catch (Exception) + { + logger.LogWarning("[{appid}] Failed to apply Glamourer data", applicationId); + } + }).ConfigureAwait(false); + } + finally + { + _redrawManager.RedrawSemaphore.Release(); + } + } + + public async Task GetCharacterCustomizationAsync(IntPtr character) + { + if (!APIAvailable) return string.Empty; + try + { + return await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + return _glamourerGetAllCustomization!.InvokeFunc(c); + } + return string.Empty; + }).ConfigureAwait(false); + } + catch + { + return string.Empty; + } + } + + public async Task RevertAsync(ILogger logger, string name, GameObjectHandler handler, Guid applicationId, CancellationToken token) + { + if ((!APIAvailable) || _dalamudUtil.IsZoning) return; + try + { + await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false); + await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => + { + try + { + logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); + _glamourerUnlock.InvokeFunc(name, LockCode); + logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId); + _glamourerRevert.InvokeAction(chara, LockCode); + logger.LogDebug("[{appid}] Calling On IPC: PenumbraRedraw", applicationId); + _mareMediator.Publish(new PenumbraRedrawCharacterMessage(chara)); + } + catch (Exception ex) + { + logger.LogWarning(ex, "[{appid}] Error during GlamourerRevert", applicationId); + } + }).ConfigureAwait(false); + } + finally + { + _redrawManager.RedrawSemaphore.Release(); + } + } + + public async Task RevertByNameAsync(ILogger logger, string name, Guid applicationId) + { + if ((!APIAvailable) || _dalamudUtil.IsZoning) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + try + { + logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId); + _glamourerRevertByName.InvokeAction(name, LockCode); + logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); + _glamourerUnlock.InvokeFunc(name, LockCode); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error during Glamourer RevertByName"); + } + }).ConfigureAwait(false); + } + + public void RevertByName(ILogger logger, string name, Guid applicationId) + { + if ((!APIAvailable) || _dalamudUtil.IsZoning) return; + try + { + logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId); + _glamourerRevertByName.InvokeAction(name, LockCode); + logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); + _glamourerUnlock.InvokeFunc(name, LockCode); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error during Glamourer RevertByName"); + } + } + + private void GlamourerChanged(nint address) + { + _mareMediator.Publish(new GlamourerChangedMessage(address)); + } + +} diff --git a/MareSynchronos/Interop/Ipc/IpcCallerHeels.cs b/MareSynchronos/Interop/Ipc/IpcCallerHeels.cs new file mode 100644 index 0000000..633359a --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerHeels.cs @@ -0,0 +1,94 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerHeels : IIpcCaller +{ + private readonly ILogger _logger; + private readonly MareMediator _mareMediator; + private readonly DalamudUtilService _dalamudUtil; + private readonly ICallGateSubscriber<(int, int)> _heelsGetApiVersion; + private readonly ICallGateSubscriber _heelsGetOffset; + private readonly ICallGateSubscriber _heelsOffsetUpdate; + private readonly ICallGateSubscriber _heelsRegisterPlayer; + private readonly ICallGateSubscriber _heelsUnregisterPlayer; + + public IpcCallerHeels(ILogger logger, DalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator) + { + _logger = logger; + _mareMediator = mareMediator; + _dalamudUtil = dalamudUtil; + _heelsGetApiVersion = pi.GetIpcSubscriber<(int, int)>("SimpleHeels.ApiVersion"); + _heelsGetOffset = pi.GetIpcSubscriber("SimpleHeels.GetLocalPlayer"); + _heelsRegisterPlayer = pi.GetIpcSubscriber("SimpleHeels.RegisterPlayer"); + _heelsUnregisterPlayer = pi.GetIpcSubscriber("SimpleHeels.UnregisterPlayer"); + _heelsOffsetUpdate = pi.GetIpcSubscriber("SimpleHeels.LocalChanged"); + + _heelsOffsetUpdate.Subscribe(HeelsOffsetChange); + + CheckAPI(); + } + + public bool APIAvailable { get; private set; } = false; + + private void HeelsOffsetChange(string offset) + { + _mareMediator.Publish(new HeelsOffsetMessage()); + } + + public async Task GetOffsetAsync() + { + if (!APIAvailable) return string.Empty; + return await _dalamudUtil.RunOnFrameworkThread(_heelsGetOffset.InvokeFunc).ConfigureAwait(false); + } + + public async Task RestoreOffsetForPlayerAsync(IntPtr character) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj != null) + { + _logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X")); + _heelsUnregisterPlayer.InvokeAction(gameObj); + } + }).ConfigureAwait(false); + } + + public async Task SetOffsetForPlayerAsync(IntPtr character, string data) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj != null) + { + _logger.LogTrace("Applying Heels data to {chara}", character.ToString("X")); + _heelsRegisterPlayer.InvokeAction(gameObj, data); + } + }).ConfigureAwait(false); + } + + public void CheckAPI() + { + try + { + APIAvailable = _heelsGetApiVersion.InvokeFunc() is { Item1: 1, Item2: >= 1 }; + } + catch + { + APIAvailable = false; + } + } + + public void Dispose() + { + _heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange); + } +} diff --git a/MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs b/MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs new file mode 100644 index 0000000..6dd9f35 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs @@ -0,0 +1,133 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using System.Text; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerHonorific : IIpcCaller +{ + private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion; + private readonly ICallGateSubscriber _honorificClearCharacterTitle; + private readonly ICallGateSubscriber _honorificDisposing; + private readonly ICallGateSubscriber _honorificGetLocalCharacterTitle; + private readonly ICallGateSubscriber _honorificLocalCharacterTitleChanged; + private readonly ICallGateSubscriber _honorificReady; + private readonly ICallGateSubscriber _honorificSetCharacterTitle; + private readonly ILogger _logger; + private readonly MareMediator _mareMediator; + private readonly DalamudUtilService _dalamudUtil; + + public IpcCallerHonorific(ILogger logger, DalamudPluginInterface pi, DalamudUtilService dalamudUtil, + MareMediator mareMediator) + { + _logger = logger; + _mareMediator = mareMediator; + _dalamudUtil = dalamudUtil; + _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); + _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber("Honorific.GetLocalCharacterTitle"); + _honorificClearCharacterTitle = pi.GetIpcSubscriber("Honorific.ClearCharacterTitle"); + _honorificSetCharacterTitle = pi.GetIpcSubscriber("Honorific.SetCharacterTitle"); + _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber("Honorific.LocalCharacterTitleChanged"); + _honorificDisposing = pi.GetIpcSubscriber("Honorific.Disposing"); + _honorificReady = pi.GetIpcSubscriber("Honorific.Ready"); + + _honorificLocalCharacterTitleChanged.Subscribe(OnHonorificLocalCharacterTitleChanged); + _honorificDisposing.Subscribe(OnHonorificDisposing); + _honorificReady.Subscribe(OnHonorificReady); + + CheckAPI(); + } + + public bool APIAvailable { get; private set; } = false; + + public void CheckAPI() + { + try + { + APIAvailable = _honorificApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 0 }; + } + catch + { + APIAvailable = false; + } + } + + public void Dispose() + { + _honorificLocalCharacterTitleChanged.Unsubscribe(OnHonorificLocalCharacterTitleChanged); + _honorificDisposing.Unsubscribe(OnHonorificDisposing); + _honorificReady.Unsubscribe(OnHonorificReady); + } + + public async Task ClearTitleAsync(nint character) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is PlayerCharacter c) + { + _logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); + _honorificClearCharacterTitle!.InvokeAction(c); + } + }).ConfigureAwait(false); + } + + public string GetTitle() + { + if (!APIAvailable) return string.Empty; + string title = _honorificGetLocalCharacterTitle.InvokeFunc(); + return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title)); + } + + public async Task SetTitleAsync(IntPtr character, string honorificDataB64) + { + if (!APIAvailable) return; + _logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X")); + try + { + await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is PlayerCharacter pc) + { + string honorificData = string.IsNullOrEmpty(honorificDataB64) ? string.Empty : Encoding.UTF8.GetString(Convert.FromBase64String(honorificDataB64)); + if (string.IsNullOrEmpty(honorificData)) + { + _honorificClearCharacterTitle!.InvokeAction(pc); + } + else + { + _honorificSetCharacterTitle!.InvokeAction(pc, honorificData); + } + } + }).ConfigureAwait(false); + } + catch (Exception e) + { + _logger.LogWarning(e, "Could not apply Honorific data"); + } + } + + private void OnHonorificDisposing() + { + _mareMediator.Publish(new HonorificMessage(string.Empty)); + } + + private void OnHonorificLocalCharacterTitleChanged(string titleJson) + { + string titleData = string.IsNullOrEmpty(titleJson) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(titleJson)); + _mareMediator.Publish(new HonorificMessage(titleData)); + } + + private void OnHonorificReady() + { + CheckAPI(); + _mareMediator.Publish(new HonorificReadyMessage()); + } +} diff --git a/MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs b/MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs new file mode 100644 index 0000000..aeb7061 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs @@ -0,0 +1,328 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.Internal.Notifications; +using Dalamud.Plugin; +using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using Penumbra.Api.Enums; +using Penumbra.Api.Helpers; +using System.Collections.Concurrent; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCaller +{ + private readonly DalamudPluginInterface _pi; + private readonly DalamudUtilService _dalamudUtil; + private readonly MareMediator _mareMediator; + private readonly RedrawManager _redrawManager; + private bool _shownPenumbraUnavailable = false; + private string? _penumbraModDirectory; + public string? PenumbraModDirectory + { + get => _penumbraModDirectory; + private set + { + if (!string.Equals(_penumbraModDirectory, value, StringComparison.Ordinal)) + { + _penumbraModDirectory = value; + _mareMediator.Publish(new PenumbraDirectoryChangedMessage(_penumbraModDirectory)); + } + } + } + + private readonly FuncSubscriber, string, int, PenumbraApiEc> _penumbraAddTemporaryMod; + private readonly FuncSubscriber _penumbraAssignTemporaryCollection; + private readonly FuncSubscriber _penumbraConvertTextureFile; + private readonly FuncSubscriber _penumbraCreateNamedTemporaryCollection; + private readonly EventSubscriber _penumbraDispose; + private readonly FuncSubscriber _penumbraEnabled; + private readonly EventSubscriber _penumbraGameObjectResourcePathResolved; + private readonly FuncSubscriber _penumbraGetMetaManipulations; + private readonly EventSubscriber _penumbraInit; + private readonly EventSubscriber _penumbraModSettingChanged; + private readonly EventSubscriber _penumbraObjectIsRedrawn; + private readonly ActionSubscriber _penumbraRedraw; + private readonly ActionSubscriber _penumbraRedrawObject; + private readonly ConcurrentDictionary _penumbraRedrawRequests = new(); + private readonly FuncSubscriber _penumbraRemoveTemporaryCollection; + private readonly FuncSubscriber _penumbraRemoveTemporaryMod; + private readonly FuncSubscriber _penumbraResolveModDir; + private readonly FuncSubscriber> _penumbraResolvePaths; + private readonly ParamsFuncSubscriber?[]> _penumbraResourcePaths; + + public IpcCallerPenumbra(ILogger logger, DalamudPluginInterface pi, DalamudUtilService dalamudUtil, + MareMediator mareMediator, RedrawManager redrawManager) : base(logger, mareMediator) + { + _pi = pi; + _dalamudUtil = dalamudUtil; + _mareMediator = mareMediator; + _redrawManager = redrawManager; + _penumbraInit = Penumbra.Api.Ipc.Initialized.Subscriber(pi, PenumbraInit); + _penumbraDispose = Penumbra.Api.Ipc.Disposed.Subscriber(pi, PenumbraDispose); + _penumbraResolveModDir = Penumbra.Api.Ipc.GetModDirectory.Subscriber(pi); + _penumbraRedraw = Penumbra.Api.Ipc.RedrawObjectByName.Subscriber(pi); + _penumbraRedrawObject = Penumbra.Api.Ipc.RedrawObject.Subscriber(pi); + _penumbraObjectIsRedrawn = Penumbra.Api.Ipc.GameObjectRedrawn.Subscriber(pi, RedrawEvent); + _penumbraGetMetaManipulations = Penumbra.Api.Ipc.GetPlayerMetaManipulations.Subscriber(pi); + _penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi); + _penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi); + _penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi); + _penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi); + _penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi); + _penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPathsAsync.Subscriber(pi); + _penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi); + _penumbraModSettingChanged = Penumbra.Api.Ipc.ModSettingChanged.Subscriber(pi, (change, arg1, arg, b) => + { + if (change == ModSettingChange.EnableState) + _mareMediator.Publish(new PenumbraModSettingChangedMessage()); + }); + _penumbraConvertTextureFile = Penumbra.Api.Ipc.ConvertTextureFile.Subscriber(pi); + _penumbraResourcePaths = Penumbra.Api.Ipc.GetGameObjectResourcePaths.Subscriber(pi); + + _penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); + + CheckAPI(); + CheckModDirectory(); + + Mediator.Subscribe(this, (msg) => _penumbraRedrawObject.Invoke(msg.Character, RedrawType.AfterGPose)); + } + + public bool APIAvailable { get; private set; } = false; + + public void CheckAPI() + { + bool penumbraAvailable = false; + try + { + penumbraAvailable = (_pi.InstalledPlugins + .FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase)) + ?.Version ?? new Version(0, 0, 0, 0)) >= new Version(0, 8, 1, 6); + penumbraAvailable &= _penumbraEnabled.Invoke(); + _shownPenumbraUnavailable = _shownPenumbraUnavailable && !penumbraAvailable; + APIAvailable = penumbraAvailable; + } + catch + { + APIAvailable = penumbraAvailable; + } + finally + { + if (!penumbraAvailable && !_shownPenumbraUnavailable) + { + _shownPenumbraUnavailable = true; + _mareMediator.Publish(new NotificationMessage("Penumbra inactive", + "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare. If you just updated Penumbra, ignore this message.", + NotificationType.Error)); + } + } + } + + public void CheckModDirectory() + { + if (!APIAvailable) + { + PenumbraModDirectory = string.Empty; + } + else + { + PenumbraModDirectory = _penumbraResolveModDir!.Invoke().ToLowerInvariant(); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _redrawManager.Cancel(); + + _penumbraModSettingChanged.Dispose(); + _penumbraGameObjectResourcePathResolved.Dispose(); + _penumbraDispose.Dispose(); + _penumbraInit.Dispose(); + _penumbraObjectIsRedrawn.Dispose(); + } + + public async Task AssignTemporaryCollectionAsync(ILogger logger, string collName, int idx) + { + if (!APIAvailable) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, c: true); + logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign); + return collName; + }).ConfigureAwait(false); + } + + public async Task ConvertTextureFiles(ILogger logger, Dictionary textures, IProgress<(string, int)> progress, CancellationToken token) + { + if (!APIAvailable) return; + + _mareMediator.Publish(new HaltScanMessage(nameof(ConvertTextureFiles))); + int currentTexture = 0; + foreach (var texture in textures) + { + if (token.IsCancellationRequested) break; + + progress.Report((texture.Key, ++currentTexture)); + + logger.LogInformation("Converting Texture {path} to {type}", texture.Key, TextureType.Bc7Tex); + var convertTask = _penumbraConvertTextureFile.Invoke(texture.Key, texture.Key, TextureType.Bc7Tex, d: true); + await convertTask.ConfigureAwait(false); + if (convertTask.IsCompletedSuccessfully && texture.Value.Any()) + { + foreach (var duplicatedTexture in texture.Value) + { + logger.LogInformation("Migrating duplicate {dup}", duplicatedTexture); + try + { + File.Copy(texture.Key, duplicatedTexture, overwrite: true); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to copy duplicate {dup}", duplicatedTexture); + } + } + } + } + _mareMediator.Publish(new ResumeScanMessage(nameof(ConvertTextureFiles))); + + await _dalamudUtil.RunOnFrameworkThread(async () => + { + var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false); + _penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw); + }).ConfigureAwait(false); + } + + public async Task CreateTemporaryCollectionAsync(ILogger logger, string uid) + { + if (!APIAvailable) return string.Empty; + + return await _dalamudUtil.RunOnFrameworkThread(() => + { + var collName = "Mare_" + uid; + var retCreate = _penumbraCreateNamedTemporaryCollection.Invoke(collName); + logger.LogTrace("Creating Temp Collection {collName}, Success: {ret}", collName, retCreate); + return collName; + }).ConfigureAwait(false); + } + + public async Task?[]?> GetCharacterData(ILogger logger, GameObjectHandler handler) + { + if (!APIAvailable) return null; + + return await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths"); + var idx = handler.GetGameObject()?.ObjectIndex; + if (idx == null) return null; + return _penumbraResourcePaths.Invoke(idx.Value); + }).ConfigureAwait(false); + } + + public string GetMetaManipulations() + { + if (!APIAvailable) return string.Empty; + return _penumbraGetMetaManipulations.Invoke(); + } + + public async Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) + { + if (!APIAvailable || _dalamudUtil.IsZoning) return; + try + { + await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false); + await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => + { + logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId); + _penumbraRedrawObject!.Invoke(chara, RedrawType.Redraw); + }).ConfigureAwait(false); + } + finally + { + _redrawManager.RedrawSemaphore.Release(); + } + } + + public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, string collName) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("[{applicationId}] Removing temp collection for {collName}", applicationId, collName); + var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collName); + logger.LogTrace("[{applicationId}] RemoveTemporaryCollection: {ret2}", applicationId, ret2); + }).ConfigureAwait(false); + } + + public async Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse) + { + return await _penumbraResolvePaths.Invoke(forward, reverse).ConfigureAwait(false); + } + + public async Task SetManipulationDataAsync(ILogger logger, Guid applicationId, string collName, string manipulationData) + { + if (!APIAvailable) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); + var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collName, [], manipulationData, 0); + logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd); + }).ConfigureAwait(false); + } + + public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, string collName, Dictionary modPaths) + { + if (!APIAvailable) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + foreach (var mod in modPaths) + { + logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value); + } + var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Files", collName, 0); + logger.LogTrace("[{applicationId}] Removing temp files mod for {collName}, Success: {ret}", applicationId, collName, retRemove); + var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collName, modPaths, string.Empty, 0); + logger.LogTrace("[{applicationId}] Setting temp files mod for {collName}, Success: {ret}", applicationId, collName, retAdd); + }).ConfigureAwait(false); + } + + private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) + { + bool wasRequested = false; + if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest) && redrawRequest) + { + _penumbraRedrawRequests[objectAddress] = false; + } + else + { + _mareMediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested)); + } + } + + private void ResourceLoaded(IntPtr ptr, string arg1, string arg2) + { + if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, ignoreCase: true, System.Globalization.CultureInfo.InvariantCulture) != 0) + { + _mareMediator.Publish(new PenumbraResourceLoadMessage(ptr, arg1, arg2)); + } + } + + private void PenumbraDispose() + { + _redrawManager.Cancel(); + _mareMediator.Publish(new PenumbraDisposedMessage()); + } + + private void PenumbraInit() + { + APIAvailable = true; + PenumbraModDirectory = _penumbraResolveModDir.Invoke(); + _mareMediator.Publish(new PenumbraInitializedMessage()); + _penumbraRedraw!.Invoke("self", RedrawType.Redraw); + } +} diff --git a/MareSynchronos/Interop/Ipc/IpcManager.cs b/MareSynchronos/Interop/Ipc/IpcManager.cs new file mode 100644 index 0000000..9b92154 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcManager.cs @@ -0,0 +1,52 @@ +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Interop.Ipc; + +public sealed partial class IpcManager : DisposableMediatorSubscriberBase +{ + public IpcManager(ILogger logger, MareMediator mediator, + IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc, + IpcCallerHonorific honorificIpc) : base(logger, mediator) + { + CustomizePlus = customizeIpc; + Heels = heelsIpc; + Glamourer = glamourerIpc; + Penumbra = penumbraIpc; + Honorific = honorificIpc; + + if (Initialized) + { + Mediator.Publish(new PenumbraInitializedMessage()); + } + + Mediator.Subscribe(this, (_) => PeriodicApiStateCheck()); + + try + { + PeriodicApiStateCheck(); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to check for some IPC, plugin not installed?"); + } + } + + public bool Initialized => Penumbra.APIAvailable && Glamourer.APIAvailable; + + public IpcCallerCustomize CustomizePlus { get; init; } + public IpcCallerHonorific Honorific { get; init; } + public IpcCallerHeels Heels { get; init; } + public IpcCallerGlamourer Glamourer { get; } + public IpcCallerPenumbra Penumbra { get; } + + private void PeriodicApiStateCheck() + { + Penumbra.CheckAPI(); + Penumbra.CheckModDirectory(); + Glamourer.CheckAPI(); + Heels.CheckAPI(); + CustomizePlus.CheckAPI(); + Honorific.CheckAPI(); + } +} \ No newline at end of file diff --git a/MareSynchronos/Interop/Ipc/RedrawManager.cs b/MareSynchronos/Interop/Ipc/RedrawManager.cs new file mode 100644 index 0000000..fd3b445 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/RedrawManager.cs @@ -0,0 +1,51 @@ +using Dalamud.Game.ClientState.Objects.Types; +using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using MareSynchronos.Utils; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Interop.Ipc; + +public class RedrawManager +{ + private readonly MareMediator _mareMediator; + private readonly DalamudUtilService _dalamudUtil; + private readonly Dictionary _penumbraRedrawRequests = []; + private CancellationTokenSource _disposalCts = new(); + + public SemaphoreSlim RedrawSemaphore { get; init; } = new(2, 2); + + public RedrawManager(MareMediator mareMediator, DalamudUtilService dalamudUtil) + { + _mareMediator = mareMediator; + _dalamudUtil = dalamudUtil; + } + + public async Task PenumbraRedrawInternalAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, Action action) + { + _mareMediator.Publish(new PenumbraStartRedrawMessage(handler.Address)); + + _penumbraRedrawRequests[handler.Address] = true; + + try + { + CancellationTokenSource cancelToken = new CancellationTokenSource(); + cancelToken.CancelAfter(TimeSpan.FromSeconds(15)); + await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token).ConfigureAwait(false); + + if (!_disposalCts.Token.IsCancellationRequested) + await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false); + } + finally + { + _penumbraRedrawRequests[handler.Address] = false; + _mareMediator.Publish(new PenumbraEndRedrawMessage(handler.Address)); + } + } + + internal void Cancel() + { + _disposalCts = _disposalCts.CancelRecreate(); + } +} diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs deleted file mode 100644 index 19f5bf9..0000000 --- a/MareSynchronos/Interop/IpcManager.cs +++ /dev/null @@ -1,802 +0,0 @@ -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Interface.Internal.Notifications; -using Dalamud.Plugin; -using Dalamud.Plugin.Ipc; -using Dalamud.Utility; -using MareSynchronos.PlayerData.Handlers; -using MareSynchronos.Services; -using MareSynchronos.Services.Mediator; -using Microsoft.Extensions.Logging; -using Penumbra.Api.Enums; -using Penumbra.Api.Helpers; -using System.Collections.Concurrent; -using System.Text; - -namespace MareSynchronos.Interop; - -public sealed class IpcManager : DisposableMediatorSubscriberBase -{ - private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion; - private readonly ICallGateSubscriber _customizePlusGetBodyScale; - private readonly ICallGateSubscriber _customizePlusOnScaleUpdate; - private readonly ICallGateSubscriber _customizePlusRevertCharacter; - private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; - private readonly DalamudPluginInterface _pi; - private readonly DalamudUtilService _dalamudUtil; - private readonly ICallGateSubscriber<(int, int)> _glamourerApiVersions; - private readonly ICallGateSubscriber? _glamourerApplyAll; - private readonly ICallGateSubscriber? _glamourerGetAllCustomization; - private readonly ICallGateSubscriber _glamourerRevert; - private readonly ICallGateSubscriber _glamourerRevertByName; - private readonly ICallGateSubscriber _glamourerUnlock; - private readonly ICallGateSubscriber<(int, int)> _heelsGetApiVersion; - private readonly ICallGateSubscriber _heelsGetOffset; - private readonly ICallGateSubscriber _heelsOffsetUpdate; - private readonly ICallGateSubscriber _heelsRegisterPlayer; - private readonly ICallGateSubscriber _heelsUnregisterPlayer; - private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion; - private readonly ICallGateSubscriber _honorificClearCharacterTitle; - private readonly ICallGateSubscriber _honorificDisposing; - private readonly ICallGateSubscriber _honorificGetLocalCharacterTitle; - private readonly ICallGateSubscriber _honorificLocalCharacterTitleChanged; - private readonly ICallGateSubscriber _honorificReady; - private readonly ICallGateSubscriber _honorificSetCharacterTitle; - private readonly FuncSubscriber, string, int, PenumbraApiEc> _penumbraAddTemporaryMod; - private readonly FuncSubscriber _penumbraAssignTemporaryCollection; - private readonly FuncSubscriber _penumbraConvertTextureFile; - private readonly FuncSubscriber _penumbraCreateNamedTemporaryCollection; - private readonly EventSubscriber _penumbraDispose; - private readonly FuncSubscriber _penumbraEnabled; - private readonly EventSubscriber _penumbraGameObjectResourcePathResolved; - private readonly FuncSubscriber _penumbraGetMetaManipulations; - private readonly EventSubscriber _penumbraInit; - private readonly EventSubscriber _penumbraModSettingChanged; - private readonly EventSubscriber _penumbraObjectIsRedrawn; - private readonly ActionSubscriber _penumbraRedraw; - private readonly ActionSubscriber _penumbraRedrawObject; - private readonly ConcurrentDictionary _penumbraRedrawRequests = new(); - private readonly FuncSubscriber _penumbraRemoveTemporaryCollection; - private readonly FuncSubscriber _penumbraRemoveTemporaryMod; - private readonly FuncSubscriber _penumbraResolveModDir; - private readonly FuncSubscriber> _penumbraResolvePaths; - private readonly ParamsFuncSubscriber?[]> _penumbraResourcePaths; - private readonly SemaphoreSlim _redrawSemaphore = new(2); - private readonly uint LockCode = 0x6D617265; - private bool _customizePlusAvailable = false; - private CancellationTokenSource _disposalCts = new(); - private bool _glamourerAvailable = false; - private bool _heelsAvailable = false; - private bool _honorificAvailable = false; - private bool _penumbraAvailable = false; - private bool _shownGlamourerUnavailable = false; - private bool _shownPenumbraUnavailable = false; - - public IpcManager(ILogger logger, DalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator) - { - _pi = pi; - _dalamudUtil = dalamudUtil; - - _penumbraInit = Penumbra.Api.Ipc.Initialized.Subscriber(pi, PenumbraInit); - _penumbraDispose = Penumbra.Api.Ipc.Disposed.Subscriber(pi, PenumbraDispose); - _penumbraResolveModDir = Penumbra.Api.Ipc.GetModDirectory.Subscriber(pi); - _penumbraRedraw = Penumbra.Api.Ipc.RedrawObjectByName.Subscriber(pi); - _penumbraRedrawObject = Penumbra.Api.Ipc.RedrawObject.Subscriber(pi); - _penumbraObjectIsRedrawn = Penumbra.Api.Ipc.GameObjectRedrawn.Subscriber(pi, RedrawEvent); - _penumbraGetMetaManipulations = Penumbra.Api.Ipc.GetPlayerMetaManipulations.Subscriber(pi); - _penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi); - _penumbraAddTemporaryMod = Penumbra.Api.Ipc.AddTemporaryMod.Subscriber(pi); - _penumbraCreateNamedTemporaryCollection = Penumbra.Api.Ipc.CreateNamedTemporaryCollection.Subscriber(pi); - _penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi); - _penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi); - _penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPathsAsync.Subscriber(pi); - _penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi); - _penumbraModSettingChanged = Penumbra.Api.Ipc.ModSettingChanged.Subscriber(pi, (change, arg1, arg, b) => - { - if (change == ModSettingChange.EnableState) - Mediator.Publish(new PenumbraModSettingChangedMessage()); - }); - _penumbraConvertTextureFile = Penumbra.Api.Ipc.ConvertTextureFile.Subscriber(pi); - _penumbraResourcePaths = Penumbra.Api.Ipc.GetGameObjectResourcePaths.Subscriber(pi); - - _penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); - - _glamourerApiVersions = pi.GetIpcSubscriber<(int, int)>("Glamourer.ApiVersions"); - _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomizationFromCharacter"); - _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacterLock"); - _glamourerRevert = pi.GetIpcSubscriber("Glamourer.RevertCharacterLock"); - _glamourerRevertByName = pi.GetIpcSubscriber("Glamourer.RevertLock"); - _glamourerUnlock = pi.GetIpcSubscriber("Glamourer.UnlockName"); - - pi.GetIpcSubscriber, object?>("Glamourer.StateChanged").Subscribe((type, address, customize) => GlamourerChanged(address)); - - _heelsGetApiVersion = pi.GetIpcSubscriber<(int, int)>("SimpleHeels.ApiVersion"); - _heelsGetOffset = pi.GetIpcSubscriber("SimpleHeels.GetLocalPlayer"); - _heelsRegisterPlayer = pi.GetIpcSubscriber("SimpleHeels.RegisterPlayer"); - _heelsUnregisterPlayer = pi.GetIpcSubscriber("SimpleHeels.UnregisterPlayer"); - _heelsOffsetUpdate = pi.GetIpcSubscriber("SimpleHeels.LocalChanged"); - - _heelsOffsetUpdate.Subscribe(HeelsOffsetChange); - - _customizePlusApiVersion = pi.GetIpcSubscriber<(int, int)>("CustomizePlus.GetApiVersion"); - _customizePlusGetBodyScale = pi.GetIpcSubscriber("CustomizePlus.GetProfileFromCharacter"); - _customizePlusRevertCharacter = pi.GetIpcSubscriber("CustomizePlus.RevertCharacter"); - _customizePlusSetBodyScaleToCharacter = pi.GetIpcSubscriber("CustomizePlus.SetProfileToCharacter"); - _customizePlusOnScaleUpdate = pi.GetIpcSubscriber("CustomizePlus.OnProfileUpdate"); - - _customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange); - - _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); - _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber("Honorific.GetLocalCharacterTitle"); - _honorificClearCharacterTitle = pi.GetIpcSubscriber("Honorific.ClearCharacterTitle"); - _honorificSetCharacterTitle = pi.GetIpcSubscriber("Honorific.SetCharacterTitle"); - _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber("Honorific.LocalCharacterTitleChanged"); - _honorificDisposing = pi.GetIpcSubscriber("Honorific.Disposing"); - _honorificReady = pi.GetIpcSubscriber("Honorific.Ready"); - - _honorificLocalCharacterTitleChanged.Subscribe(OnHonorificLocalCharacterTitleChanged); - _honorificDisposing.Subscribe(OnHonorificDisposing); - _honorificReady.Subscribe(OnHonorificReady); - - if (Initialized) - { - Mediator.Publish(new PenumbraInitializedMessage()); - } - - Mediator.Subscribe(this, (_) => PeriodicApiStateCheck()); - - try - { - PeriodicApiStateCheck(); - } - catch (Exception ex) - { - logger.LogWarning(ex, "Failed to check for some IPC, plugin not installed?"); - } - } - - public bool Initialized => CheckPenumbraApiInternal() && CheckGlamourerApiInternal(); - private string? _penumbraModDirectory; - public string? PenumbraModDirectory - { - get => _penumbraModDirectory; - private set - { - if (!string.Equals(_penumbraModDirectory, value, StringComparison.Ordinal)) - { - _penumbraModDirectory = value; - Mediator.Publish(new PenumbraDirectoryChangedMessage(_penumbraModDirectory)); - } - } - } - - public bool CheckCustomizePlusApi() => _customizePlusAvailable; - - public bool CheckGlamourerApi() => _glamourerAvailable; - - public bool CheckHeelsApi() => _heelsAvailable; - - public bool CheckHonorificApi() => _honorificAvailable; - - public bool CheckPenumbraApi() => _penumbraAvailable; - - public async Task CustomizePlusRevertAsync(IntPtr character) - { - if (!CheckCustomizePlusApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X")); - _customizePlusRevertCharacter!.InvokeAction(c); - } - }).ConfigureAwait(false); - } - - public async Task CustomizePlusSetBodyScaleAsync(IntPtr character, string scale) - { - if (!CheckCustomizePlusApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale)); - Logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X")); - if (scale.IsNullOrEmpty()) - { - _customizePlusRevertCharacter!.InvokeAction(c); - } - else - { - _customizePlusSetBodyScaleToCharacter!.InvokeAction(decodedScale, c); - } - } - }).ConfigureAwait(false); - } - - public async Task GetCustomizePlusScaleAsync(IntPtr character) - { - if (!CheckCustomizePlusApi()) return null; - var scale = await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - return _customizePlusGetBodyScale.InvokeFunc(c); - } - - return string.Empty; - }).ConfigureAwait(false); - if (string.IsNullOrEmpty(scale)) return string.Empty; - return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale)); - } - - public async Task GetHeelsOffsetAsync() - { - if (!CheckHeelsApi()) return string.Empty; - return await _dalamudUtil.RunOnFrameworkThread(_heelsGetOffset.InvokeFunc).ConfigureAwait(false); - } - - public async Task GlamourerApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) - { - if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; - try - { - await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); - - await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => - { - try - { - logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); - _glamourerApplyAll!.InvokeAction(customization, chara, LockCode); - } - catch (Exception) - { - logger.LogWarning("[{appid}] Failed to apply Glamourer data", applicationId); - } - }).ConfigureAwait(false); - } - finally - { - _redrawSemaphore.Release(); - } - } - - public async Task GlamourerGetCharacterCustomizationAsync(IntPtr character) - { - if (!CheckGlamourerApi()) return string.Empty; - try - { - return await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - return _glamourerGetAllCustomization!.InvokeFunc(c); - } - return string.Empty; - }).ConfigureAwait(false); - } - catch - { - return string.Empty; - } - } - - public async Task GlamourerRevert(ILogger logger, string name, GameObjectHandler handler, Guid applicationId, CancellationToken token) - { - if ((!CheckGlamourerApi()) || _dalamudUtil.IsZoning) return; - try - { - await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); - await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => - { - try - { - logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); - _glamourerUnlock.InvokeFunc(name, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId); - _glamourerRevert.InvokeAction(chara, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: PenumbraRedraw", applicationId); - _penumbraRedrawObject.Invoke(chara, RedrawType.AfterGPose); - } - catch (Exception ex) - { - logger.LogWarning(ex, "[{appid}] Error during GlamourerRevert", applicationId); - } - }).ConfigureAwait(false); - } - finally - { - _redrawSemaphore.Release(); - } - } - - public async Task GlamourerRevertByNameAsync(ILogger logger, string name, Guid applicationId) - { - if ((!CheckGlamourerApi()) || _dalamudUtil.IsZoning) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - try - { - logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId); - _glamourerRevertByName.InvokeAction(name, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); - _glamourerUnlock.InvokeFunc(name, LockCode); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Error during Glamourer RevertByName"); - } - }).ConfigureAwait(false); - } - - public void GlamourerRevertByName(ILogger logger, string name, Guid applicationId) - { - if ((!CheckGlamourerApi()) || _dalamudUtil.IsZoning) return; - try - { - logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId); - _glamourerRevertByName.InvokeAction(name, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); - _glamourerUnlock.InvokeFunc(name, LockCode); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Error during Glamourer RevertByName"); - } - } - - public async Task HeelsRestoreOffsetForPlayerAsync(IntPtr character) - { - if (!CheckHeelsApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj != null) - { - Logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X")); - _heelsUnregisterPlayer.InvokeAction(gameObj); - } - }).ConfigureAwait(false); - } - - public async Task HeelsSetOffsetForPlayerAsync(IntPtr character, string data) - { - if (!CheckHeelsApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj != null) - { - Logger.LogTrace("Applying Heels data to {chara}", character.ToString("X")); - _heelsRegisterPlayer.InvokeAction(gameObj, data); - } - }).ConfigureAwait(false); - } - - public async Task HonorificClearTitleAsync(nint character) - { - if (!CheckHonorificApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is PlayerCharacter c) - { - Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); - _honorificClearCharacterTitle!.InvokeAction(c); - } - }).ConfigureAwait(false); - } - - public string HonorificGetTitle() - { - if (!CheckHonorificApi()) return string.Empty; - string title = _honorificGetLocalCharacterTitle.InvokeFunc(); - return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title)); - } - - public async Task HonorificSetTitleAsync(IntPtr character, string honorificDataB64) - { - if (!CheckHonorificApi()) return; - Logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X")); - try - { - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is PlayerCharacter pc) - { - string honorificData = string.IsNullOrEmpty(honorificDataB64) ? string.Empty : Encoding.UTF8.GetString(Convert.FromBase64String(honorificDataB64)); - if (string.IsNullOrEmpty(honorificData)) - { - _honorificClearCharacterTitle!.InvokeAction(pc); - } - else - { - _honorificSetCharacterTitle!.InvokeAction(pc, honorificData); - } - } - }).ConfigureAwait(false); - } - catch (Exception e) - { - Logger.LogWarning(e, "Could not apply Honorific data"); - } - } - - public async Task PenumbraAssignTemporaryCollectionAsync(ILogger logger, string collName, int idx) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, c: true); - logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign); - return collName; - }).ConfigureAwait(false); - } - - public async Task PenumbraConvertTextureFiles(ILogger logger, Dictionary textures, IProgress<(string, int)> progress, CancellationToken token) - { - if (!CheckPenumbraApi()) return; - - Mediator.Publish(new HaltScanMessage(nameof(PenumbraConvertTextureFiles))); - int currentTexture = 0; - foreach (var texture in textures) - { - if (token.IsCancellationRequested) break; - - progress.Report((texture.Key, ++currentTexture)); - - logger.LogInformation("Converting Texture {path} to {type}", texture.Key, TextureType.Bc7Tex); - var convertTask = _penumbraConvertTextureFile.Invoke(texture.Key, texture.Key, TextureType.Bc7Tex, d: true); - await convertTask.ConfigureAwait(false); - if (convertTask.IsCompletedSuccessfully && texture.Value.Any()) - { - foreach (var duplicatedTexture in texture.Value) - { - logger.LogInformation("Migrating duplicate {dup}", duplicatedTexture); - try - { - File.Copy(texture.Key, duplicatedTexture, overwrite: true); - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to copy duplicate {dup}", duplicatedTexture); - } - } - } - } - Mediator.Publish(new ResumeScanMessage(nameof(PenumbraConvertTextureFiles))); - - await _dalamudUtil.RunOnFrameworkThread(async () => - { - var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false); - _penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw); - }).ConfigureAwait(false); - } - - public async Task PenumbraCreateTemporaryCollectionAsync(ILogger logger, string uid) - { - if (!CheckPenumbraApi()) return string.Empty; - - return await _dalamudUtil.RunOnFrameworkThread(() => - { - var collName = "Mare_" + uid; - var retCreate = _penumbraCreateNamedTemporaryCollection.Invoke(collName); - logger.LogTrace("Creating Temp Collection {collName}, Success: {ret}", collName, retCreate); - return collName; - }).ConfigureAwait(false); - } - - public async Task?[]?> PenumbraGetCharacterData(ILogger logger, GameObjectHandler handler) - { - if (!CheckPenumbraApi()) return null; - - return await _dalamudUtil.RunOnFrameworkThread(() => - { - logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths"); - var idx = handler.GetGameObject()?.ObjectIndex; - if (idx == null) return null; - return _penumbraResourcePaths.Invoke(idx.Value); - }).ConfigureAwait(false); - } - - public string PenumbraGetMetaManipulations() - { - if (!CheckPenumbraApi()) return string.Empty; - return _penumbraGetMetaManipulations.Invoke(); - } - - public async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) - { - if (!CheckPenumbraApi() || _dalamudUtil.IsZoning) return; - try - { - await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); - await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => - { - logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId); - _penumbraRedrawObject!.Invoke(chara, RedrawType.Redraw); - }).ConfigureAwait(false); - } - finally - { - _redrawSemaphore.Release(); - } - } - - public async Task PenumbraRemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, string collName) - { - if (!CheckPenumbraApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - logger.LogTrace("[{applicationId}] Removing temp collection for {collName}", applicationId, collName); - var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collName); - logger.LogTrace("[{applicationId}] RemoveTemporaryCollection: {ret2}", applicationId, ret2); - }).ConfigureAwait(false); - } - - public async Task<(string[] forward, string[][] reverse)> PenumbraResolvePathsAsync(string[] forward, string[] reverse) - { - return await _penumbraResolvePaths.Invoke(forward, reverse).ConfigureAwait(false); - } - - public async Task PenumbraSetManipulationDataAsync(ILogger logger, Guid applicationId, string collName, string manipulationData) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); - var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collName, [], manipulationData, 0); - logger.LogTrace("[{applicationId}] Setting temp meta mod for {collName}, Success: {ret}", applicationId, collName, retAdd); - }).ConfigureAwait(false); - } - - public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, string collName, Dictionary modPaths) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - foreach (var mod in modPaths) - { - logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value); - } - var retRemove = _penumbraRemoveTemporaryMod.Invoke("MareChara_Files", collName, 0); - logger.LogTrace("[{applicationId}] Removing temp files mod for {collName}, Success: {ret}", applicationId, collName, retRemove); - var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collName, modPaths, string.Empty, 0); - logger.LogTrace("[{applicationId}] Setting temp files mod for {collName}, Success: {ret}", applicationId, collName, retAdd); - }).ConfigureAwait(false); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - _disposalCts.Cancel(); - - _penumbraModSettingChanged.Dispose(); - _penumbraGameObjectResourcePathResolved.Dispose(); - _penumbraDispose.Dispose(); - _penumbraInit.Dispose(); - _penumbraObjectIsRedrawn.Dispose(); - _heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange); - _customizePlusOnScaleUpdate.Unsubscribe(OnCustomizePlusScaleChange); - _honorificLocalCharacterTitleChanged.Unsubscribe(OnHonorificLocalCharacterTitleChanged); - _honorificDisposing.Unsubscribe(OnHonorificDisposing); - _honorificReady.Unsubscribe(OnHonorificReady); - } - - private bool CheckCustomizePlusApiInternal() - { - try - { - var version = _customizePlusApiVersion.InvokeFunc(); - if (version.Item1 == 3 && version.Item2 >= 0) return true; - return false; - } - catch - { - return false; - } - } - - private bool CheckGlamourerApiInternal() - { - bool apiAvailable = false; - try - { - var version = _glamourerApiVersions.InvokeFunc(); - bool versionValid = (_pi.InstalledPlugins - .FirstOrDefault(p => string.Equals(p.InternalName, "Glamourer", StringComparison.OrdinalIgnoreCase)) - ?.Version ?? new Version(0, 0, 0, 0)) >= new Version(1, 0, 6, 1); - if (version.Item1 == 0 && version.Item2 >= 1 && versionValid) - { - apiAvailable = true; - } - _shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable; - - return apiAvailable; - } - catch - { - return apiAvailable; - } - finally - { - if (!apiAvailable && !_shownGlamourerUnavailable) - { - _shownGlamourerUnavailable = true; - Mediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare. If you just updated Glamourer, ignore this message.", - NotificationType.Error)); - } - } - } - - private bool CheckHeelsApiInternal() - { - try - { - return _heelsGetApiVersion.InvokeFunc() is { Item1: 1, Item2: >= 1 }; - } - catch - { - return false; - } - } - - private bool CheckHonorificApiInternal() - { - try - { - return _honorificApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 0 }; - } - catch - { - return false; - } - } - - private bool CheckPenumbraApiInternal() - { - bool penumbraAvailable = false; - try - { - penumbraAvailable = (_pi.InstalledPlugins - .FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase)) - ?.Version ?? new Version(0, 0, 0, 0)) >= new Version(0, 8, 1, 6); - penumbraAvailable &= _penumbraEnabled.Invoke(); - _shownPenumbraUnavailable = _shownPenumbraUnavailable && !penumbraAvailable; - return penumbraAvailable; - } - catch - { - return penumbraAvailable; - } - finally - { - if (!penumbraAvailable && !_shownPenumbraUnavailable) - { - _shownPenumbraUnavailable = true; - Mediator.Publish(new NotificationMessage("Penumbra inactive", - "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare. If you just updated Penumbra, ignore this message.", - NotificationType.Error)); - } - } - } - - private string? GetPenumbraModDirectoryInternal() - { - if (!CheckPenumbraApi()) return null; - return _penumbraResolveModDir!.Invoke().ToLowerInvariant(); - } - - private void GlamourerChanged(nint address) - { - Mediator.Publish(new GlamourerChangedMessage(address)); - } - - private void HeelsOffsetChange(string offset) - { - Mediator.Publish(new HeelsOffsetMessage()); - } - - private void OnCustomizePlusScaleChange(string? profileName, string? scale) - { - Mediator.Publish(new CustomizePlusMessage(profileName ?? string.Empty)); - } - - private void OnHonorificDisposing() - { - Mediator.Publish(new HonorificMessage(string.Empty)); - } - - private void OnHonorificLocalCharacterTitleChanged(string titleJson) - { - string titleData = string.IsNullOrEmpty(titleJson) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(titleJson)); - Mediator.Publish(new HonorificMessage(titleData)); - } - - private void OnHonorificReady() - { - _honorificAvailable = CheckHonorificApiInternal(); - Mediator.Publish(new HonorificReadyMessage()); - } - - private void PenumbraDispose() - { - _disposalCts.Cancel(); - _disposalCts.Dispose(); - Mediator.Publish(new PenumbraDisposedMessage()); - _disposalCts = new(); - } - - private void PenumbraInit() - { - _penumbraAvailable = true; - PenumbraModDirectory = _penumbraResolveModDir.Invoke(); - Mediator.Publish(new PenumbraInitializedMessage()); - _penumbraRedraw!.Invoke("self", RedrawType.Redraw); - } - - private async Task PenumbraRedrawInternalAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, Action action) - { - Mediator.Publish(new PenumbraStartRedrawMessage(handler.Address)); - - _penumbraRedrawRequests[handler.Address] = true; - - try - { - CancellationTokenSource cancelToken = new CancellationTokenSource(); - cancelToken.CancelAfter(TimeSpan.FromSeconds(15)); - await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token).ConfigureAwait(false); - - if (!_disposalCts.Token.IsCancellationRequested) - await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false); - } - finally - { - _penumbraRedrawRequests[handler.Address] = false; - } - - Mediator.Publish(new PenumbraEndRedrawMessage(handler.Address)); - } - - private void PeriodicApiStateCheck() - { - _glamourerAvailable = CheckGlamourerApiInternal(); - _penumbraAvailable = CheckPenumbraApiInternal(); - _heelsAvailable = CheckHeelsApiInternal(); - _customizePlusAvailable = CheckCustomizePlusApiInternal(); - _honorificAvailable = CheckHonorificApiInternal(); - PenumbraModDirectory = GetPenumbraModDirectoryInternal(); - } - - private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) - { - bool wasRequested = false; - if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest) && redrawRequest) - { - _penumbraRedrawRequests[objectAddress] = false; - } - else - { - Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested)); - } - } - - private void ResourceLoaded(IntPtr ptr, string arg1, string arg2) - { - if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, ignoreCase: true, System.Globalization.CultureInfo.InvariantCulture) != 0) - { - Mediator.Publish(new PenumbraResourceLoadMessage(ptr, arg1, arg2)); - } - } -} \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs index 9112d39..4fb19a5 100644 --- a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs +++ b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs @@ -2,7 +2,7 @@ using LZ4; using MareSynchronos.API.Data.Enum; using MareSynchronos.FileCache; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Handlers; @@ -48,12 +48,12 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase { if ((await dalamudUtil.RunOnFrameworkThread(() => item.Value.CurrentAddress()).ConfigureAwait(false)) != nint.Zero) { - await _ipcManager.GlamourerRevert(logger, item.Value.Name, item.Value, Guid.NewGuid(), cts.Token).ConfigureAwait(false); + await _ipcManager.Glamourer.RevertAsync(logger, item.Value.Name, item.Value, Guid.NewGuid(), cts.Token).ConfigureAwait(false); } else { _logger.LogDebug("Reverting by name: {name}", item.Key); - _ipcManager.GlamourerRevertByName(logger, item.Key, Guid.NewGuid()); + _ipcManager.Glamourer.RevertByName(logger, item.Key, Guid.NewGuid()); } @@ -92,11 +92,11 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase } } var applicationId = Guid.NewGuid(); - await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false); - var coll = await _ipcManager.PenumbraCreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); - await _ipcManager.PenumbraAssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); - await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); - await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); + await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(_logger, applicationId, charaTarget.Name.TextValue).ConfigureAwait(false); + var coll = await _ipcManager.Penumbra.CreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); + await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); + await _ipcManager.Penumbra.SetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); + await _ipcManager.Penumbra.SetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetGposeCharacterFromObjectTableByName(charaTarget.Name.ToString(), _isInGpose)?.Address ?? IntPtr.Zero, isWatched: false).ConfigureAwait(false); @@ -104,17 +104,17 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase if (!_gposeGameObjects.ContainsKey(charaTarget.Name.ToString())) _gposeGameObjects[charaTarget.Name.ToString()] = tempHandler; - await _ipcManager.GlamourerApplyAllAsync(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(_logger, tempHandler, applicationId, disposeCts.Token).ConfigureAwait(false); + await _ipcManager.Glamourer.ApplyAllAsync(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(_logger, tempHandler, applicationId, disposeCts.Token).ConfigureAwait(false); _dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address, 30000); - await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, coll).ConfigureAwait(false); + await _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(_logger, applicationId, coll).ConfigureAwait(false); if (!string.IsNullOrEmpty(LoadedCharaFile.CharaFileData.CustomizePlusData)) { - await _ipcManager.CustomizePlusSetBodyScaleAsync(tempHandler.Address, LoadedCharaFile.CharaFileData.CustomizePlusData).ConfigureAwait(false); + await _ipcManager.CustomizePlus.SetBodyScaleAsync(tempHandler.Address, LoadedCharaFile.CharaFileData.CustomizePlusData).ConfigureAwait(false); } else { - await _ipcManager.CustomizePlusRevertAsync(tempHandler.Address).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertAsync(tempHandler.Address).ConfigureAwait(false); } } } diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index f9ee5f6..76df27e 100644 --- a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -1,6 +1,6 @@ using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 8c66e7b..a7be419 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -1,12 +1,11 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using MareSynchronos.API.Data.Enum; using MareSynchronos.FileCache; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.PlayerData.Data; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services; using Microsoft.Extensions.Logging; -using System.Diagnostics; using CharacterData = MareSynchronos.PlayerData.Data.CharacterData; namespace MareSynchronos.PlayerData.Factories; @@ -141,7 +140,7 @@ public class PlayerDataFactory // penumbra call, it's currently broken IReadOnlyDictionary? resolvedPaths; - resolvedPaths = (await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false))![0]; + resolvedPaths = (await _ipcManager.Penumbra.GetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false))![0]; if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data"); previousData.FileReplacements[objectKind] = @@ -192,10 +191,10 @@ public class PlayerDataFactory } // gather up data from ipc - previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations(); - Task getHeelsOffset = _ipcManager.GetHeelsOffsetAsync(); - Task getGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(playerRelatedObject.Address); - Task getCustomizeData = _ipcManager.GetCustomizePlusScaleAsync(playerRelatedObject.Address); + previousData.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations(); + Task getHeelsOffset = _ipcManager.Heels.GetOffsetAsync(); + Task getGlamourerData = _ipcManager.Glamourer.GetCharacterCustomizationAsync(playerRelatedObject.Address); + Task getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address); previousData.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false); _logger.LogDebug("Glamourer is now: {data}", previousData.GlamourerString[playerRelatedObject.ObjectKind]); var customizeScale = await getCustomizeData.ConfigureAwait(false); @@ -204,7 +203,7 @@ public class PlayerDataFactory previousData.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale; _logger.LogDebug("Customize is now: {data}", previousData.CustomizePlusScale[playerRelatedObject.ObjectKind]); } - previousData.HonorificData = _ipcManager.HonorificGetTitle(); + previousData.HonorificData = _ipcManager.Honorific.GetTitle(); _logger.LogDebug("Honorific is now: {data}", previousData.HonorificData); previousData.HeelsData = await getHeelsOffset.ConfigureAwait(false); _logger.LogDebug("Heels is now: {heels}", previousData.HeelsData); @@ -235,7 +234,7 @@ public class PlayerDataFactory var forwardPaths = forwardResolve.ToArray(); var reversePaths = reverseResolve.ToArray(); Dictionary> resolvedPaths = new(StringComparer.Ordinal); - var (forward, reverse) = await _ipcManager.PenumbraResolvePathsAsync(forwardPaths, reversePaths).ConfigureAwait(false); + var (forward, reverse) = await _ipcManager.Penumbra.ResolvePathsAsync(forwardPaths, reversePaths).ConfigureAwait(false); for (int i = 0; i < forwardPaths.Length; i++) { var filePath = forward[i].ToLowerInvariant(); diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 9514e08..64a4e1a 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -5,7 +5,6 @@ using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Logging; -using Penumbra.String; using System.Runtime.InteropServices; using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer; using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 7813bd8..5b24ee2 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -1,7 +1,7 @@ using MareSynchronos.API.Data; using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; @@ -56,7 +56,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _dalamudUtil = dalamudUtil; _lifetime = lifetime; _fileDbManager = fileDbManager; - _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); + _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); Mediator.Subscribe(this, (_) => FrameworkUpdate()); Mediator.Subscribe(this, (_) => @@ -67,7 +67,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (_) => { - _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); + _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); if (!IsVisible && _charaHandler != null) { PlayerName = string.Empty; @@ -158,7 +158,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (string.Equals(characterData.DataHash.Value, _cachedData?.DataHash.Value ?? string.Empty, StringComparison.Ordinal) && !forceApplyCustomization) return; - if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.CheckPenumbraApi() || !_ipcManager.CheckGlamourerApi()) + if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.Penumbra.APIAvailable || !_ipcManager.Glamourer.APIAvailable) { Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, "Cannot apply character data: you are in GPose, a Cutscene or Penumbra/Glamourer is not available"))); @@ -239,11 +239,11 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", applicationId, name, OnlineUser); Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser); - _ipcManager.PenumbraRemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); + _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); if (!IsVisible) { Logger.LogDebug("[{applicationId}] Restoring Glamourer for {name} ({user})", applicationId, name, OnlineUser); - _ipcManager.GlamourerRevertByNameAsync(Logger, name, applicationId).GetAwaiter().GetResult(); + _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).GetAwaiter().GetResult(); } else { @@ -311,31 +311,31 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase case PlayerChanges.Customize: if (charaData.CustomizePlusData.TryGetValue(changes.Key, out var customizePlusData)) { - await _ipcManager.CustomizePlusSetBodyScaleAsync(handler.Address, customizePlusData).ConfigureAwait(false); + await _ipcManager.CustomizePlus.SetBodyScaleAsync(handler.Address, customizePlusData).ConfigureAwait(false); } else { - await _ipcManager.CustomizePlusRevertAsync(handler.Address).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertAsync(handler.Address).ConfigureAwait(false); } break; case PlayerChanges.Heels: - await _ipcManager.HeelsSetOffsetForPlayerAsync(handler.Address, charaData.HeelsData).ConfigureAwait(false); + await _ipcManager.Heels.SetOffsetForPlayerAsync(handler.Address, charaData.HeelsData).ConfigureAwait(false); break; case PlayerChanges.Honorific: - await _ipcManager.HonorificSetTitleAsync(handler.Address, charaData.HonorificData).ConfigureAwait(false); + await _ipcManager.Honorific.SetTitleAsync(handler.Address, charaData.HonorificData).ConfigureAwait(false); break; case PlayerChanges.Glamourer: if (charaData.GlamourerData.TryGetValue(changes.Key, out var glamourerData)) { - await _ipcManager.GlamourerApplyAllAsync(Logger, handler, glamourerData, applicationId, token).ConfigureAwait(false); + await _ipcManager.Glamourer.ApplyAllAsync(Logger, handler, glamourerData, applicationId, token).ConfigureAwait(false); } break; case PlayerChanges.ForcedRedraw: - await _ipcManager.PenumbraRedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); break; default: @@ -433,7 +433,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (updateModdedPaths) { - await _ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false); + await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false); LastAppliedDataSize = -1; foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) { @@ -443,7 +443,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (updateManip) { - await _ipcManager.PenumbraSetManipulationDataAsync(Logger, _applicationId, _penumbraCollection, charaData.ManipulationData).ConfigureAwait(false); + await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, _applicationId, _penumbraCollection, charaData.ManipulationData).ConfigureAwait(false); } token.ThrowIfCancellationRequested(); @@ -527,10 +527,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { if (string.IsNullOrEmpty(_cachedData?.HonorificData)) return; Logger.LogTrace("Reapplying Honorific data for {this}", this); - await _ipcManager.HonorificSetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false); + await _ipcManager.Honorific.SetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false); }); - _ipcManager.PenumbraAssignTemporaryCollectionAsync(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult(); + _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult(); } private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId, CancellationToken cancelToken) @@ -545,26 +545,26 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, isWatched: false).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); - await _ipcManager.GlamourerRevert(Logger, name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Glamourer.RevertAsync(Logger, name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); - await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false); + await _ipcManager.Heels.RestoreOffsetForPlayerAsync(address).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); - await _ipcManager.CustomizePlusRevertAsync(address).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertAsync(address).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); - await _ipcManager.HonorificClearTitleAsync(address).ConfigureAwait(false); + await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false); } else if (objectKind == ObjectKind.MinionOrMount) { var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false); if (minionOrMount != nint.Zero) { - await _ipcManager.CustomizePlusRevertAsync(minionOrMount).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertAsync(minionOrMount).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, isWatched: false).ConfigureAwait(false); - await _ipcManager.GlamourerRevert(Logger, tempHandler.Name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Glamourer.RevertAsync(Logger, tempHandler.Name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Pet) @@ -572,10 +572,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); if (pet != nint.Zero) { - await _ipcManager.CustomizePlusRevertAsync(pet).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertAsync(pet).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, isWatched: false).ConfigureAwait(false); - await _ipcManager.GlamourerRevert(Logger, tempHandler.Name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Glamourer.RevertAsync(Logger, tempHandler.Name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Companion) @@ -583,10 +583,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); if (companion != nint.Zero) { - await _ipcManager.CustomizePlusRevertAsync(companion).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertAsync(companion).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, isWatched: false).ConfigureAwait(false); - await _ipcManager.GlamourerRevert(Logger, tempHandler.Name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Glamourer.RevertAsync(Logger, tempHandler.Name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); } } } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 0864e45..c253f63 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -6,6 +6,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using MareSynchronos.FileCache; using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Export; using MareSynchronos.PlayerData.Factories; @@ -88,8 +89,21 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService>(), dtrBar, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + + collection.AddSingleton(); + collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService>(), pluginInterface, + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService>(), pluginInterface, + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); + collection.AddSingleton((s) => new IpcCallerCustomize(s.GetRequiredService>(), pluginInterface, + s.GetRequiredService(), s.GetRequiredService())); + collection.AddSingleton((s) => new IpcCallerHeels(s.GetRequiredService>(), pluginInterface, + s.GetRequiredService(), s.GetRequiredService())); + collection.AddSingleton((s) => new IpcCallerHonorific(s.GetRequiredService>(), pluginInterface, + s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcManager(s.GetRequiredService>(), - pluginInterface, s.GetRequiredService(), s.GetRequiredService())); + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 7643747..f631bbf 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -1,4 +1,5 @@ -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.Internal.Notifications; using MareSynchronos.API.Data; using MareSynchronos.API.Dto; using MareSynchronos.API.Dto.Group; @@ -83,6 +84,6 @@ public record CombatOrPerformanceStartMessage : MessageBase; public record CombatOrPerformanceEndMessage : MessageBase; public record EventMessage(Event Event) : MessageBase; public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; - +public record PenumbraRedrawCharacterMessage(Character Character) : SameThreadMessage; #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/Services/PluginWarningNotificationService.cs b/MareSynchronos/Services/PluginWarningNotificationService.cs index c4510a0..7ce04db 100644 --- a/MareSynchronos/Services/PluginWarningNotificationService.cs +++ b/MareSynchronos/Services/PluginWarningNotificationService.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.Internal.Notifications; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Comparer; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.Services.Mediator; using System.Collections.Concurrent; @@ -35,18 +35,18 @@ public class PluginWarningNotificationService } List missingPluginsForData = []; - if (changes.Contains(PlayerChanges.Heels) && !warning.ShownHeelsWarning && !_ipcManager.CheckHeelsApi()) + if (changes.Contains(PlayerChanges.Heels) && !warning.ShownHeelsWarning && !_ipcManager.Heels.APIAvailable) { missingPluginsForData.Add("SimpleHeels"); warning.ShownHeelsWarning = true; } - if (changes.Contains(PlayerChanges.Customize) && !warning.ShownCustomizePlusWarning && !_ipcManager.CheckCustomizePlusApi()) + if (changes.Contains(PlayerChanges.Customize) && !warning.ShownCustomizePlusWarning && !_ipcManager.CustomizePlus.APIAvailable) { missingPluginsForData.Add("Customize+"); warning.ShownCustomizePlusWarning = true; } - if (changes.Contains(PlayerChanges.Honorific) && !warning.ShownHonorificWarning && !_ipcManager.CheckHonorificApi()) + if (changes.Contains(PlayerChanges.Honorific) && !warning.ShownHonorificWarning && !_ipcManager.Honorific.APIAvailable) { missingPluginsForData.Add("Honorific"); warning.ShownHonorificWarning = true; diff --git a/MareSynchronos/UI/DataAnalysisUi.cs b/MareSynchronos/UI/DataAnalysisUi.cs index 13dd2f1..301ac53 100644 --- a/MareSynchronos/UI/DataAnalysisUi.cs +++ b/MareSynchronos/UI/DataAnalysisUi.cs @@ -3,7 +3,7 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Utility.Raii; using ImGuiNET; using MareSynchronos.API.Data.Enum; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; @@ -263,7 +263,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase if (_texturesToConvert.Count > 0 && UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)")) { _conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate(); - _conversionTask = _ipcManager.PenumbraConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token); + _conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token); } } } diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index f951709..b3adfee 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -7,7 +7,7 @@ using ImGuiNET; 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; using MareSynchronos.PlayerData.Export; @@ -493,7 +493,7 @@ public class SettingsUi : WindowMediatorSubscriberBase using var id = ImRaii.PushId("penumbraMonitor"); if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.ArrowsToCircle, "Try to reinitialize Monitor")) { - _cacheMonitor.StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); + _cacheMonitor.StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); } } @@ -513,7 +513,7 @@ public class SettingsUi : WindowMediatorSubscriberBase if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Play, "Resume Monitoring")) { _cacheMonitor.StartMareWatcher(_configService.Current.CacheFolder); - _cacheMonitor.StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); + _cacheMonitor.StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); _cacheMonitor.InvokeScan(); } UiSharedService.AttachToolTip("Attempts to resume monitoring for both Penumbra and Mare Storage. " diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index 6415d55..4b4b836 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -9,7 +9,7 @@ using Dalamud.Plugin; using Dalamud.Utility; using ImGuiNET; using MareSynchronos.FileCache; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.Localization; using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration.Models; @@ -98,11 +98,11 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (_) => { - _penumbraExists = _ipcManager.CheckPenumbraApi(); - _glamourerExists = _ipcManager.CheckGlamourerApi(); - _customizePlusExists = _ipcManager.CheckCustomizePlusApi(); - _heelsExists = _ipcManager.CheckHeelsApi(); - _honorificExists = _ipcManager.CheckHonorificApi(); + _penumbraExists = _ipcManager.Penumbra.APIAvailable; + _glamourerExists = _ipcManager.Glamourer.APIAvailable; + _customizePlusExists = _ipcManager.CustomizePlus.APIAvailable; + _heelsExists = _ipcManager.Heels.APIAvailable; + _honorificExists = _ipcManager.Honorific.APIAvailable; }); } @@ -110,7 +110,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase public bool EditTrackerPosition { get; set; } - public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory); + public bool HasValidPenumbraModPath => !(_ipcManager.Penumbra.PenumbraModDirectory ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.Penumbra.PenumbraModDirectory); public bool IsInGpose => _dalamudUtil.IsInCutscene; @@ -569,7 +569,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase if (!success) return; _isOneDrive = path.Contains("onedrive", StringComparison.OrdinalIgnoreCase); - _isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory?.ToLowerInvariant(), StringComparison.Ordinal); + _isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.Penumbra.PenumbraModDirectory?.ToLowerInvariant(), StringComparison.Ordinal); _isDirectoryWritable = IsDirectoryWritable(path); _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => Path.GetFileNameWithoutExtension(f).Length != 40) || Directory.GetDirectories(path).Any(); diff --git a/MareSynchronos/Utils/Crypto.cs b/MareSynchronos/Utils/Crypto.cs index 62883ca..e4d81fb 100644 --- a/MareSynchronos/Utils/Crypto.cs +++ b/MareSynchronos/Utils/Crypto.cs @@ -1,6 +1,4 @@ -using Dalamud.Game.ClientState.Objects.SubKinds; - -using System.Security.Cryptography; +using System.Security.Cryptography; using System.Text; namespace MareSynchronos.Utils;