diff --git a/MareSynchronos/FileCache/CacheMonitor.cs b/MareSynchronos/FileCache/CacheMonitor.cs index 3c41353..393dc39 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 28ae2ec..5f8e590 100644 --- a/MareSynchronos/FileCache/FileCacheManager.cs +++ b/MareSynchronos/FileCache/FileCacheManager.cs @@ -1,5 +1,5 @@ using K4os.Compression.LZ4.Streams; -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); } @@ -173,7 +173,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) @@ -195,7 +196,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); @@ -367,7 +368,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..a0dea63 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs @@ -0,0 +1,139 @@ +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 _customizePlusGetActiveProfile; + private readonly ICallGateSubscriber _customizePlusGetProfileById; + private readonly ICallGateSubscriber _customizePlusOnScaleUpdate; + private readonly ICallGateSubscriber _customizePlusRevertCharacter; + private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; + private readonly ICallGateSubscriber _customizePlusDeleteByUniqueId; + private readonly ILogger _logger; + private readonly DalamudUtilService _dalamudUtil; + private readonly MareMediator _mareMediator; + + public IpcCallerCustomize(ILogger logger, IDalamudPluginInterface dalamudPluginInterface, + DalamudUtilService dalamudUtil, MareMediator mareMediator) + { + _customizePlusApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("CustomizePlus.General.GetApiVersion"); + _customizePlusGetActiveProfile = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.Profile.GetActiveProfileIdOnCharacter"); + _customizePlusGetProfileById = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.Profile.GetByUniqueId"); + _customizePlusRevertCharacter = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.Profile.DeleteTemporaryProfileOnCharacter"); + _customizePlusSetBodyScaleToCharacter = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.Profile.SetTemporaryProfileOnCharacter"); + _customizePlusOnScaleUpdate = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.Profile.OnUpdate"); + _customizePlusDeleteByUniqueId = dalamudPluginInterface.GetIpcSubscriber("CustomizePlus.Profile.DeleteTemporaryProfileByUniqueId"); + + _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 ICharacter c) + { + _logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X")); + _customizePlusRevertCharacter!.InvokeFunc(c.ObjectIndex); + } + }).ConfigureAwait(false); + } + + public async Task SetBodyScaleAsync(nint character, string scale) + { + if (!APIAvailable) return null; + return await _dalamudUtil.RunOnFrameworkThread(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is ICharacter c) + { + string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale)); + _logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X")); + if (scale.IsNullOrEmpty()) + { + _customizePlusRevertCharacter!.InvokeFunc(c.ObjectIndex); + return null; + } + else + { + var result = _customizePlusSetBodyScaleToCharacter!.InvokeFunc(c.ObjectIndex, decodedScale); + return result.Item2; + } + } + + return null; + }).ConfigureAwait(false); + } + + public async Task RevertByIdAsync(Guid? profileId) + { + if (!APIAvailable || profileId == null) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + _ = _customizePlusDeleteByUniqueId.InvokeFunc(profileId.Value); + }).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 ICharacter c) + { + var res = _customizePlusGetActiveProfile.InvokeFunc(c.ObjectIndex); + _logger.LogTrace("CustomizePlus GetActiveProfile returned {err}", res.Item1); + if (res.Item1 != 0 || res.Item2 == null) return string.Empty; + return _customizePlusGetProfileById.InvokeFunc(res.Item2.Value).Item2; + } + + 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 == 6 && version.Item2 >= 0); + } + catch + { + APIAvailable = false; + } + } + + private void OnCustomizePlusScaleChange(ushort c, Guid g) + { + var obj = _dalamudUtil.GetCharacterFromObjectTableByIndex(c); + _mareMediator.Publish(new CustomizePlusMessage(obj?.Name.ToString() ?? 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..94adb8a --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs @@ -0,0 +1,211 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Plugin; +using Glamourer.Api.Helpers; +using Glamourer.Api.IpcSubscribers; +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 ILogger _logger; + private readonly IDalamudPluginInterface _pi; + private readonly DalamudUtilService _dalamudUtil; + private readonly MareMediator _mareMediator; + private readonly RedrawManager _redrawManager; + + private readonly ApiVersion _glamourerApiVersions; + private readonly ApplyState? _glamourerApplyAll; + private readonly GetStateBase64? _glamourerGetAllCustomization; + private readonly RevertState _glamourerRevert; + private readonly RevertStateName _glamourerRevertByName; + private readonly UnlockState _glamourerUnlock; + private readonly UnlockStateName _glamourerUnlockByName; + private readonly EventSubscriber? _glamourerStateChanged; + + private bool _shownGlamourerUnavailable = false; + private readonly uint LockCode = 0x626E7579; + + public IpcCallerGlamourer(ILogger logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator, + RedrawManager redrawManager) + { + _glamourerApiVersions = new(pi); + _glamourerGetAllCustomization = new(pi); + _glamourerApplyAll = new(pi); + _glamourerRevert = new(pi); + _glamourerRevertByName = new(pi); + _glamourerUnlock = new(pi); + _glamourerUnlockByName = new(pi); + + _logger = logger; + _pi = pi; + _dalamudUtil = dalamudUtil; + _mareMediator = mareMediator; + _redrawManager = redrawManager; + CheckAPI(); + + _glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged); + _glamourerStateChanged.Enable(); + } + + public bool APIAvailable { get; private set; } + + public void CheckAPI() + { + bool apiAvailable = false; + try + { + 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); + try + { + var version = _glamourerApiVersions.Invoke(); + if (version is { Major: 1, Minor: >= 1 } && versionValid) + { + apiAvailable = true; + } + } + catch + { + // ignore + } + _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 Loporrit. If you just updated Glamourer, ignore this message.", + NotificationType.Error)); + } + } + } + + public void Dispose() + { + _glamourerStateChanged?.Dispose(); + } + + 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!.Invoke(customization, chara.ObjectIndex, LockCode); + } + catch (Exception ex) + { + logger.LogWarning(ex, "[{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 ICharacter c) + { + return _glamourerGetAllCustomization!.Invoke(c.ObjectIndex).Item2 ?? string.Empty; + } + return string.Empty; + }).ConfigureAwait(false); + } + catch + { + return string.Empty; + } + } + + public async Task RevertAsync(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) => + { + try + { + logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlock", applicationId); + _glamourerUnlock.Invoke(chara.ObjectIndex, LockCode); + logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId); + _glamourerRevert.Invoke(chara.ObjectIndex, 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(() => + { + RevertByName(logger, name, applicationId); + + }).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.Invoke(name, LockCode); + logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); + _glamourerUnlockByName.Invoke(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..0836770 --- /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, IDalamudPluginInterface 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.ObjectIndex); + } + }).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.ObjectIndex, data); + } + }).ConfigureAwait(false); + } + + public void CheckAPI() + { + try + { + APIAvailable = _heelsGetApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 0 }; + } + 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..ab161e0 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs @@ -0,0 +1,132 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +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, IDalamudPluginInterface 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: 3, 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 IPlayerCharacter c) + { + _logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); + _honorificClearCharacterTitle!.InvokeAction(c.ObjectIndex); + } + }).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 IPlayerCharacter pc) + { + string honorificData = string.IsNullOrEmpty(honorificDataB64) ? string.Empty : Encoding.UTF8.GetString(Convert.FromBase64String(honorificDataB64)); + if (string.IsNullOrEmpty(honorificData)) + { + _honorificClearCharacterTitle!.InvokeAction(pc.ObjectIndex); + } + else + { + _honorificSetCharacterTitle!.InvokeAction(pc.ObjectIndex, 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..5a9cd67 --- /dev/null +++ b/MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs @@ -0,0 +1,334 @@ +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Plugin; +using MareSynchronos.MareConfiguration.Models; +using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using Penumbra.Api.Enums; +using Penumbra.Api.Helpers; +using Penumbra.Api.IpcSubscribers; +using System.Collections.Concurrent; + +namespace MareSynchronos.Interop.Ipc; + +public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCaller +{ + private readonly IDalamudPluginInterface _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 ConcurrentDictionary _penumbraRedrawRequests = new(); + + private readonly EventSubscriber _penumbraDispose; + private readonly EventSubscriber _penumbraGameObjectResourcePathResolved; + private readonly EventSubscriber _penumbraInit; + private readonly EventSubscriber _penumbraModSettingChanged; + private readonly EventSubscriber _penumbraObjectIsRedrawn; + + private readonly AddTemporaryMod _penumbraAddTemporaryMod; + private readonly AssignTemporaryCollection _penumbraAssignTemporaryCollection; + private readonly ConvertTextureFile _penumbraConvertTextureFile; + private readonly CreateTemporaryCollection _penumbraCreateNamedTemporaryCollection; + private readonly GetEnabledState _penumbraEnabled; + private readonly GetPlayerMetaManipulations _penumbraGetMetaManipulations; + private readonly RedrawObject _penumbraRedraw; + private readonly DeleteTemporaryCollection _penumbraRemoveTemporaryCollection; + private readonly RemoveTemporaryMod _penumbraRemoveTemporaryMod; + private readonly GetModDirectory _penumbraResolveModDir; + private readonly ResolvePlayerPathsAsync _penumbraResolvePaths; + private readonly GetGameObjectResourcePaths _penumbraResourcePaths; + + public IpcCallerPenumbra(ILogger logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, + MareMediator mareMediator, RedrawManager redrawManager) : base(logger, mareMediator) + { + _pi = pi; + _dalamudUtil = dalamudUtil; + _mareMediator = mareMediator; + _redrawManager = redrawManager; + _penumbraInit = Initialized.Subscriber(pi, PenumbraInit); + _penumbraDispose = Disposed.Subscriber(pi, PenumbraDispose); + _penumbraResolveModDir = new GetModDirectory(pi); + _penumbraRedraw = new RedrawObject(pi); + _penumbraObjectIsRedrawn = GameObjectRedrawn.Subscriber(pi, RedrawEvent); + _penumbraGetMetaManipulations = new GetPlayerMetaManipulations(pi); + _penumbraRemoveTemporaryMod = new RemoveTemporaryMod(pi); + _penumbraAddTemporaryMod = new AddTemporaryMod(pi); + _penumbraCreateNamedTemporaryCollection = new CreateTemporaryCollection(pi); + _penumbraRemoveTemporaryCollection = new DeleteTemporaryCollection(pi); + _penumbraAssignTemporaryCollection = new AssignTemporaryCollection(pi); + _penumbraResolvePaths = new ResolvePlayerPathsAsync(pi); + _penumbraEnabled = new GetEnabledState(pi); + _penumbraModSettingChanged = ModSettingChanged.Subscriber(pi, (change, arg1, arg, b) => + { + if (change == ModSettingChange.EnableState) + _mareMediator.Publish(new PenumbraModSettingChangedMessage()); + }); + _penumbraConvertTextureFile = new ConvertTextureFile(pi); + _penumbraResourcePaths = new GetGameObjectResourcePaths(pi); + + _penumbraGameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); + + CheckAPI(); + CheckModDirectory(); + + Mediator.Subscribe(this, (msg) => + { + _penumbraRedraw.Invoke(msg.Character.ObjectIndex, RedrawType.AfterGPose); + }); + } + + public bool APIAvailable { get; private set; } = false; + + public void CheckAPI() + { + bool penumbraAvailable = false; + try + { + var penumbraVersion = (_pi.InstalledPlugins + .FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase)) + ?.Version ?? new Version(0, 0, 0, 0)); + penumbraAvailable = penumbraVersion >= new Version(1, 0, 1, 0); + 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 Loporrit. 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, Guid collName, int idx) + { + if (!APIAvailable) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, forceAssignment: 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, mipMaps: 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); + _penumbraRedraw.Invoke(gameObject!.ObjectIndex, RedrawType.Redraw); + }).ConfigureAwait(false); + } + + public async Task CreateTemporaryCollectionAsync(ILogger logger, string uid) + { + if (!APIAvailable) return Guid.Empty; + + return await _dalamudUtil.RunOnFrameworkThread(() => + { + var collName = "Loporrit_" + uid; + var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName); + logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId); + return collId; + }).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)[0]; + }).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); + _penumbraRedraw!.Invoke(chara.ObjectIndex, setting: RedrawType.Redraw); + + }).ConfigureAwait(false); + } + finally + { + _redrawManager.RedrawSemaphore.Release(); + } + } + + public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collId) + { + if (!APIAvailable) return; + await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("[{applicationId}] Removing temp collection for {collId}", applicationId, collId); + var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collId); + 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, Guid collId, string manipulationData) + { + if (!APIAvailable) return; + + await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); + var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collId, [], manipulationData, 0); + logger.LogTrace("[{applicationId}] Setting temp meta mod for {collId}, Success: {ret}", applicationId, collId, retAdd); + }).ConfigureAwait(false); + } + + public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collId, 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", collId, 0); + logger.LogTrace("[{applicationId}] Removing temp files mod for {collId}, Success: {ret}", applicationId, collId, retRemove); + var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collId, modPaths, string.Empty, 0); + logger.LogTrace("[{applicationId}] Setting temp files mod for {collId}, Success: {ret}", applicationId, collId, 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(0, setting: 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..153a44a --- /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 c15d4a6..0000000 --- a/MareSynchronos/Interop/IpcManager.cs +++ /dev/null @@ -1,836 +0,0 @@ -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Plugin; -using Dalamud.Plugin.Ipc; -using Glamourer.Api.Helpers; -using Glamourer.Api.IpcSubscribers; -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 Penumbra.Api.IpcSubscribers; -using System.Collections.Concurrent; -using System.Text; - -namespace MareSynchronos.Interop; - -public sealed class IpcManager : DisposableMediatorSubscriberBase -{ - private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion; - private readonly ICallGateSubscriber _customizePlusGetActiveProfile; - private readonly ICallGateSubscriber _customizePlusGetProfileById; - private readonly ICallGateSubscriber _customizePlusOnScaleUpdate; - private readonly ICallGateSubscriber _customizePlusRevertCharacter; - private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; - private readonly ICallGateSubscriber _customizePlusDeleteByUniqueId; - private readonly IDalamudPluginInterface _pi; - private readonly DalamudUtilService _dalamudUtil; - private readonly Glamourer.Api.IpcSubscribers.ApiVersion _glamourerApiVersions; - private readonly Glamourer.Api.IpcSubscribers.ApplyState? _glamourerApplyAll; - private readonly Glamourer.Api.IpcSubscribers.GetStateBase64? _glamourerGetAllCustomization; - private readonly Glamourer.Api.IpcSubscribers.RevertState _glamourerRevert; - private readonly Glamourer.Api.IpcSubscribers.RevertStateName _glamourerRevertByName; - private readonly Glamourer.Api.IpcSubscribers.UnlockState _glamourerUnlock; - private readonly Glamourer.Api.IpcSubscribers.UnlockStateName _glamourerUnlockByName; - private readonly Glamourer.Api.Helpers.EventSubscriber _glamourerStateChanged; - 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 ConcurrentDictionary _penumbraRedrawRequests = new(); - private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraDispose; - private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraGameObjectResourcePathResolved; - private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraInit; - private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraModSettingChanged; - private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraObjectIsRedrawn; - private readonly Penumbra.Api.IpcSubscribers.ApiVersion _penumbraApiVersion; - private readonly Penumbra.Api.IpcSubscribers.AddTemporaryMod _penumbraAddTemporaryMod; - private readonly Penumbra.Api.IpcSubscribers.AssignTemporaryCollection _penumbraAssignTemporaryCollection; - private readonly Penumbra.Api.IpcSubscribers.ConvertTextureFile _penumbraConvertTextureFile; - private readonly Penumbra.Api.IpcSubscribers.CreateTemporaryCollection _penumbraCreateNamedTemporaryCollection; - private readonly Penumbra.Api.IpcSubscribers.GetEnabledState _penumbraEnabled; - private readonly Penumbra.Api.IpcSubscribers.GetPlayerMetaManipulations _penumbraGetMetaManipulations; - private readonly Penumbra.Api.IpcSubscribers.RedrawObject _penumbraRedraw; - private readonly Penumbra.Api.IpcSubscribers.DeleteTemporaryCollection _penumbraRemoveTemporaryCollection; - private readonly Penumbra.Api.IpcSubscribers.RemoveTemporaryMod _penumbraRemoveTemporaryMod; - private readonly Penumbra.Api.IpcSubscribers.GetModDirectory _penumbraResolveModDir; - private readonly Penumbra.Api.IpcSubscribers.ResolvePlayerPathsAsync _penumbraResolvePaths; - private readonly Penumbra.Api.IpcSubscribers.GetGameObjectResourcePaths _penumbraResourcePaths; - private readonly SemaphoreSlim _redrawSemaphore = new(2); - private readonly uint LockCode = 0x626E7579; - 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, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator) - { - _pi = pi; - _dalamudUtil = dalamudUtil; - - _penumbraInit = Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, PenumbraInit); - _penumbraDispose = Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, PenumbraDispose); - _penumbraResolveModDir = new(pi); - _penumbraRedraw = new(pi); - _penumbraObjectIsRedrawn = Penumbra.Api.IpcSubscribers.GameObjectRedrawn.Subscriber(pi, RedrawEvent); - _penumbraGetMetaManipulations = new(pi); - _penumbraRemoveTemporaryMod = new(pi); - _penumbraAddTemporaryMod = new(pi); - _penumbraCreateNamedTemporaryCollection = new(pi); - _penumbraRemoveTemporaryCollection = new(pi); - _penumbraAssignTemporaryCollection = new(pi); - _penumbraResolvePaths = new(pi); - _penumbraEnabled = new(pi); - _penumbraModSettingChanged = Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi, (change, arg1, arg, b) => - { - if (change == ModSettingChange.EnableState) - Mediator.Publish(new PenumbraModSettingChangedMessage()); - }); - _penumbraConvertTextureFile = new(pi); - _penumbraResourcePaths = new(pi); - _penumbraApiVersion = new(pi); - - _penumbraGameObjectResourcePathResolved = Penumbra.Api.IpcSubscribers.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); - - _glamourerApiVersions = new(pi); - _glamourerGetAllCustomization = new(pi); - _glamourerApplyAll = new(pi); - _glamourerRevert = new(pi); - _glamourerRevertByName = new(pi); - _glamourerUnlock = new(pi); - _glamourerUnlockByName = new(pi); - - _glamourerStateChanged = Glamourer.Api.IpcSubscribers.StateChanged.Subscriber(pi, GlamourerChanged); - _glamourerStateChanged.Enable(); - - _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.General.GetApiVersion"); - _customizePlusGetActiveProfile = pi.GetIpcSubscriber("CustomizePlus.Profile.GetActiveProfileIdOnCharacter"); - _customizePlusGetProfileById = pi.GetIpcSubscriber("CustomizePlus.Profile.GetByUniqueId"); - _customizePlusRevertCharacter = pi.GetIpcSubscriber("CustomizePlus.Profile.DeleteTemporaryProfileOnCharacter"); - _customizePlusSetBodyScaleToCharacter = pi.GetIpcSubscriber("CustomizePlus.Profile.SetTemporaryProfileOnCharacter"); - _customizePlusOnScaleUpdate = pi.GetIpcSubscriber("CustomizePlus.Profile.OnUpdate"); - _customizePlusDeleteByUniqueId = pi.GetIpcSubscriber("CustomizePlus.Profile.DeleteTemporaryProfileByUniqueId"); - - _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?"); - } - - _glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged); - _glamourerStateChanged.Enable(); - } - - 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 ICharacter c) - { - Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X")); - _customizePlusRevertCharacter!.InvokeFunc(c.ObjectIndex); - } - }).ConfigureAwait(false); - } - - public async Task CustomizePlusRevertByIdAsync(Guid? profileId) - { - if (!CheckCustomizePlusApi() || profileId == null) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - _ = _customizePlusDeleteByUniqueId.InvokeFunc(profileId.Value); - }).ConfigureAwait(false); - } - - public async Task CustomizePlusSetBodyScaleAsync(IntPtr character, string scale) - { - if (!CheckCustomizePlusApi()) return null; - return await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is ICharacter c) - { - string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale)); - Logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X")); - if (scale.IsNullOrEmpty()) - { - _customizePlusRevertCharacter!.InvokeFunc(c.ObjectIndex); - return null; - } - else - { - var result = _customizePlusSetBodyScaleToCharacter!.InvokeFunc(c.ObjectIndex, decodedScale); - return result.Item2; - } - } - - return null; - }).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 ICharacter c) - { - var res = _customizePlusGetActiveProfile.InvokeFunc(c.ObjectIndex); - Logger.LogTrace("CustomizePlus GetActiveProfile returned {err}", res.Item1); - if (res.Item1 != 0 || res.Item2 == null) return string.Empty; - return _customizePlusGetProfileById.InvokeFunc(res.Item2.Value).Item2; - } - - 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!.Invoke(customization, chara.ObjectIndex, LockCode); - } - catch (Exception ex) - { - logger.LogWarning(ex, "[{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 ICharacter c) - { - return _glamourerGetAllCustomization!.Invoke(c.ObjectIndex).Item2 ?? string.Empty; - } - 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.Invoke(chara.ObjectIndex, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId); - _glamourerRevert.Invoke(chara.ObjectIndex, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: PenumbraRedraw", applicationId); - _penumbraRedraw.Invoke(chara.ObjectIndex, 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(() => - { - GlamourerRevertByName(logger, name, applicationId); - }).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.Invoke(name, LockCode); - logger.LogDebug("[{appid}] Calling On IPC: GlamourerUnlockName", applicationId); - _glamourerUnlockByName.Invoke(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.ObjectIndex); - } - }).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.ObjectIndex, data); - } - }).ConfigureAwait(false); - } - - public async Task HonorificClearTitleAsync(nint character) - { - if (!CheckHonorificApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is IPlayerCharacter c) - { - Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); - _honorificClearCharacterTitle!.InvokeAction(c.ObjectIndex); - } - }).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 IPlayerCharacter pc) - { - string honorificData = string.IsNullOrEmpty(honorificDataB64) ? string.Empty : Encoding.UTF8.GetString(Convert.FromBase64String(honorificDataB64)); - if (string.IsNullOrEmpty(honorificData)) - { - _honorificClearCharacterTitle!.InvokeAction(pc.ObjectIndex); - } - else - { - _honorificSetCharacterTitle!.InvokeAction(pc.ObjectIndex, honorificData); - } - } - }).ConfigureAwait(false); - } - catch (Exception e) - { - Logger.LogWarning(e, "Could not apply Honorific data"); - } - } - - public async Task PenumbraAssignTemporaryCollectionAsync(ILogger logger, Guid collName, int idx) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, forceAssignment: 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, mipMaps: 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); - _penumbraRedraw.Invoke(gameObject!.ObjectIndex, RedrawType.Redraw); - }).ConfigureAwait(false); - } - - public async Task PenumbraCreateTemporaryCollectionAsync(ILogger logger, string uid) - { - if (!CheckPenumbraApi()) return Guid.Empty; - - return await _dalamudUtil.RunOnFrameworkThread(() => - { - var collName = "Loporrit_" + uid; - var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName); - logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId); - return collId; - }).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)[0]; - }).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); - _penumbraRedraw.Invoke(chara.ObjectIndex, RedrawType.Redraw); - }).ConfigureAwait(false); - } - finally - { - _redrawSemaphore.Release(); - } - } - - public async Task PenumbraRemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collId) - { - if (!CheckPenumbraApi()) return; - await _dalamudUtil.RunOnFrameworkThread(() => - { - logger.LogTrace("[{applicationId}] Removing temp collection for {collId}", applicationId, collId); - var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collId); - 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, Guid collId, string manipulationData) - { - if (!CheckPenumbraApi()) return; - - await _dalamudUtil.RunOnFrameworkThread(() => - { - logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); - var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Meta", collId, [], manipulationData, 0); - logger.LogTrace("[{applicationId}] Setting temp meta mod for {collId}, Success: {ret}", applicationId, collId, retAdd); - }).ConfigureAwait(false); - } - - public async Task PenumbraSetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collId, 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", collId, 0); - logger.LogTrace("[{applicationId}] Removing temp files mod for {collId}, Success: {ret}", applicationId, collId, retRemove); - var retAdd = _penumbraAddTemporaryMod.Invoke("MareChara_Files", collId, modPaths, string.Empty, 0); - logger.LogTrace("[{applicationId}] Setting temp files mod for {collId}, Success: {ret}", applicationId, collId, retAdd); - }).ConfigureAwait(false); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - _disposalCts.Cancel(); - - _glamourerStateChanged?.Dispose(); - _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 == 6 && version.Item2 >= 0) return true; - return false; - } - catch - { - return false; - } - } - - private bool CheckGlamourerApiInternal() - { - bool apiAvailable = false; - try - { - 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); - try - { - var version = _glamourerApiVersions.Invoke(); - if (version is { Major: 1, Minor: >= 1 } && versionValid) - { - apiAvailable = true; - } - } - catch - { - - } - _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 Loporrit.", NotificationType.Error)); - } - } - } - - private bool CheckHeelsApiInternal() - { - try - { - return _heelsGetApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 0 }; - } - catch - { - return false; - } - } - - private bool CheckHonorificApiInternal() - { - try - { - return _honorificApiVersion.InvokeFunc() is { Item1: 3, Item2: >= 0 }; - } - catch - { - return false; - } - } - - private bool CheckPenumbraApiInternal() - { - bool penumbraAvailable = false; - try - { - bool pluginFound = (_pi.InstalledPlugins - .FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase)) - ?.Version ?? new Version(0, 0, 0, 0)) >= new Version(1, 0, 1, 0); - try - { - _ = _penumbraApiVersion.Invoke(); - } - catch - { - - } - penumbraAvailable = pluginFound && _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 Loporrit.", 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(ushort c, Guid g) - { - var obj = _dalamudUtil.GetCharacterFromObjectTableByIndex(c); - Mediator.Publish(new CustomizePlusMessage(obj?.Name.ToString() ?? 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() - { - PenumbraModDirectory = _penumbraResolveModDir.Invoke(); - _penumbraAvailable = true; - Mediator.Publish(new PenumbraInitializedMessage()); - _penumbraRedraw!.Invoke(0, 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 a05ee99..b6f470d 100644 --- a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs +++ b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs @@ -2,7 +2,7 @@ using K4os.Compression.LZ4.Legacy; 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; @@ -51,12 +51,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, 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()); } @@ -64,7 +64,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase } foreach (var id in _gposeCustomizeObjects.Where(d => d != null)) { - await _ipcManager.CustomizePlusRevertByIdAsync(id).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertByIdAsync(id).ConfigureAwait(false); } _gposeGameObjects.Clear(); }); @@ -99,10 +99,10 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase } } var applicationId = Guid.NewGuid(); - var coll = await _ipcManager.PenumbraCreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); - await _ipcManager.PenumbraAssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectIndex).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); + 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); @@ -110,18 +110,18 @@ 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)) { - var id = await _ipcManager.CustomizePlusSetBodyScaleAsync(tempHandler.Address, LoadedCharaFile.CharaFileData.CustomizePlusData).ConfigureAwait(false); + var id = await _ipcManager.CustomizePlus.SetBodyScaleAsync(tempHandler.Address, LoadedCharaFile.CharaFileData.CustomizePlusData).ConfigureAwait(false); _gposeCustomizeObjects.Add(id); } else { - var id = await _ipcManager.CustomizePlusSetBodyScaleAsync(tempHandler.Address, Convert.ToBase64String(Encoding.UTF8.GetBytes("{}"))).ConfigureAwait(false); + var id = await _ipcManager.CustomizePlus.SetBodyScaleAsync(tempHandler.Address, Convert.ToBase64String(Encoding.UTF8.GetBytes("{}"))).ConfigureAwait(false); _gposeCustomizeObjects.Add(id); } } diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index fd03dc9..f7679cf 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 c114628..82ed81b 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 Dictionary>? resolvedPaths; - resolvedPaths = await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false); + resolvedPaths = await _ipcManager.Penumbra.GetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false); if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data"); previousData.FileReplacements[objectKind] = @@ -192,16 +191,16 @@ 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); previousData.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty; _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); @@ -232,7 +231,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/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 629ec88..1a603d2 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; @@ -59,7 +59,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, (_) => @@ -70,7 +70,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; @@ -160,7 +160,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"))); @@ -238,7 +238,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (_lifetime.IsCancellationRequested) return; 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 (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) { @@ -246,7 +246,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase 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 { @@ -314,32 +314,32 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase case PlayerChanges.Customize: if (charaData.CustomizePlusData.TryGetValue(changes.Key, out var customizePlusData)) { - _customizeIds[changes.Key] = await _ipcManager.CustomizePlusSetBodyScaleAsync(handler.Address, customizePlusData).ConfigureAwait(false); + _customizeIds[changes.Key] = await _ipcManager.CustomizePlus.SetBodyScaleAsync(handler.Address, customizePlusData).ConfigureAwait(false); } else if (_customizeIds.TryGetValue(changes.Key, out var customizeId)) { - await _ipcManager.CustomizePlusRevertByIdAsync(customizeId).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).ConfigureAwait(false); _customizeIds.Remove(changes.Key); } 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: @@ -437,7 +437,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)) { @@ -447,7 +447,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(); @@ -531,10 +531,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(); _serverConfigurationManager.SetNameForUid(OnlineUser.User.UID, name); } @@ -555,26 +555,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, 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.CustomizePlusRevertByIdAsync(customizeId).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).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.CustomizePlusRevertByIdAsync(customizeId).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).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, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Pet) @@ -582,10 +582,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); if (pet != nint.Zero) { - await _ipcManager.CustomizePlusRevertByIdAsync(customizeId).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).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, applicationId, cancelToken).ConfigureAwait(false); + await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Companion) @@ -593,10 +593,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); if (companion != nint.Zero) { - await _ipcManager.CustomizePlusRevertByIdAsync(customizeId).ConfigureAwait(false); + await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).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, 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 c5189ad..05a3924 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -5,6 +5,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; @@ -98,8 +99,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 6cd83d1..8adf6b8 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -1,3 +1,4 @@ +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.ImGuiNotification; using MareSynchronos.API.Data; using MareSynchronos.API.Dto; @@ -81,7 +82,7 @@ public record CombatStartMessage : MessageBase; public record CombatEndMessage : MessageBase; public record EventMessage(Event Event) : MessageBase; public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; - +public record PenumbraRedrawCharacterMessage(ICharacter Character) : SameThreadMessage; public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase; public record GroupChatMsgMessage(GroupDto GroupInfo, SignedChatMessage ChatMsg) : MessageBase; #pragma warning restore S2094 diff --git a/MareSynchronos/Services/PluginWarningNotificationService.cs b/MareSynchronos/Services/PluginWarningNotificationService.cs index 8ec0cf6..b30a575 100644 --- a/MareSynchronos/Services/PluginWarningNotificationService.cs +++ b/MareSynchronos/Services/PluginWarningNotificationService.cs @@ -1,7 +1,7 @@ using Dalamud.Interface.ImGuiNotification; 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 0b84084..a69c277 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 9f38f42..845a21a 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -10,7 +10,7 @@ using MareSynchronos.API.Data.Comparer; using MareSynchronos.API.Dto.Account; using MareSynchronos.API.Routes; using MareSynchronos.FileCache; -using MareSynchronos.Interop; +using MareSynchronos.Interop.Ipc; using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration.Models; using MareSynchronos.PlayerData.Export; @@ -750,7 +750,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); } } @@ -770,7 +770,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 Loporrit Storage. " diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index ac32170..19b7a72 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -11,7 +11,7 @@ using Dalamud.Plugin.Services; 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; @@ -111,11 +111,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; }); } @@ -123,7 +123,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; @@ -601,7 +601,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();