refactor a little bit
This commit is contained in:
		| @@ -1,4 +1,4 @@ | |||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| @@ -33,7 +33,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|         _fileCompactor = fileCompactor; |         _fileCompactor = fileCompactor; | ||||||
|         Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => |         Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => | ||||||
|         { |         { | ||||||
|             StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); |             StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); | ||||||
|             StartMareWatcher(configService.Current.CacheFolder); |             StartMareWatcher(configService.Current.CacheFolder); | ||||||
|             InvokeScan(); |             InvokeScan(); | ||||||
|         }); |         }); | ||||||
| @@ -42,7 +42,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|         Mediator.Subscribe<DalamudLoginMessage>(this, (_) => |         Mediator.Subscribe<DalamudLoginMessage>(this, (_) => | ||||||
|         { |         { | ||||||
|             StartMareWatcher(configService.Current.CacheFolder); |             StartMareWatcher(configService.Current.CacheFolder); | ||||||
|             StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); |             StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); | ||||||
|             InvokeScan(); |             InvokeScan(); | ||||||
|         }); |         }); | ||||||
|         Mediator.Subscribe<PenumbraDirectoryChangedMessage>(this, (msg) => |         Mediator.Subscribe<PenumbraDirectoryChangedMessage>(this, (msg) => | ||||||
| @@ -50,9 +50,9 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|             StartPenumbraWatcher(msg.ModDirectory); |             StartPenumbraWatcher(msg.ModDirectory); | ||||||
|             InvokeScan(); |             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()) |         if (configService.Current.HasValidSetup()) | ||||||
|         { |         { | ||||||
| @@ -466,7 +466,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|     private void FullFileScan(CancellationToken ct) |     private void FullFileScan(CancellationToken ct) | ||||||
|     { |     { | ||||||
|         TotalFiles = 1; |         TotalFiles = 1; | ||||||
|         var penumbraDir = _ipcManager.PenumbraModDirectory; |         var penumbraDir = _ipcManager.Penumbra.PenumbraModDirectory; | ||||||
|         bool penDirExists = true; |         bool penDirExists = true; | ||||||
|         bool cacheDirExists = true; |         bool cacheDirExists = true; | ||||||
|         if (string.IsNullOrEmpty(penumbraDir) || !Directory.Exists(penumbraDir)) |         if (string.IsNullOrEmpty(penumbraDir) || !Directory.Exists(penumbraDir)) | ||||||
| @@ -558,7 +558,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|                     { |                     { | ||||||
|                         if (ct.IsCancellationRequested) return; |                         if (ct.IsCancellationRequested) return; | ||||||
|  |  | ||||||
|                         if (!_ipcManager.CheckPenumbraApi()) |                         if (!_ipcManager.Penumbra.APIAvailable) | ||||||
|                         { |                         { | ||||||
|                             Logger.LogWarning("Penumbra not available"); |                             Logger.LogWarning("Penumbra not available"); | ||||||
|                             return; |                             return; | ||||||
| @@ -605,7 +605,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         Logger.LogTrace("Threads exited"); |         Logger.LogTrace("Threads exited"); | ||||||
|  |  | ||||||
|         if (!_ipcManager.CheckPenumbraApi()) |         if (!_ipcManager.Penumbra.APIAvailable) | ||||||
|         { |         { | ||||||
|             Logger.LogWarning("Penumbra not available"); |             Logger.LogWarning("Penumbra not available"); | ||||||
|             return; |             return; | ||||||
| @@ -628,7 +628,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         Logger.LogTrace("Scanner validated existing db files"); |         Logger.LogTrace("Scanner validated existing db files"); | ||||||
|  |  | ||||||
|         if (!_ipcManager.CheckPenumbraApi()) |         if (!_ipcManager.Penumbra.APIAvailable) | ||||||
|         { |         { | ||||||
|             Logger.LogWarning("Penumbra not available"); |             Logger.LogWarning("Penumbra not available"); | ||||||
|             return; |             return; | ||||||
| @@ -648,7 +648,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase | |||||||
|                 { |                 { | ||||||
|                     if (ct.IsCancellationRequested) return; |                     if (ct.IsCancellationRequested) return; | ||||||
|  |  | ||||||
|                     if (!_ipcManager.CheckPenumbraApi()) |                     if (!_ipcManager.Penumbra.APIAvailable) | ||||||
|                     { |                     { | ||||||
|                         Logger.LogWarning("Penumbra not available"); |                         Logger.LogWarning("Penumbra not available"); | ||||||
|                         return; |                         return; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| using K4os.Compression.LZ4.Streams; | using K4os.Compression.LZ4.Streams; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Utils; | using MareSynchronos.Utils; | ||||||
| @@ -53,8 +53,8 @@ public sealed class FileCacheManager : IHostedService | |||||||
|         if (!fi.Exists) return null; |         if (!fi.Exists) return null; | ||||||
|         _logger.LogTrace("Creating file entry for {path}", path); |         _logger.LogTrace("Creating file entry for {path}", path); | ||||||
|         var fullName = fi.FullName.ToLowerInvariant(); |         var fullName = fi.FullName.ToLowerInvariant(); | ||||||
|         if (!fullName.Contains(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null; |         if (!fullName.Contains(_ipcManager.Penumbra.PenumbraModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null; | ||||||
|         string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); |         string prefixedPath = fullName.Replace(_ipcManager.Penumbra.PenumbraModDirectory!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); | ||||||
|         return CreateFileCacheEntity(fi, prefixedPath); |         return CreateFileCacheEntity(fi, prefixedPath); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -173,7 +173,8 @@ public sealed class FileCacheManager : IHostedService | |||||||
|  |  | ||||||
|     private FileCacheEntity? GetFileCacheByPath(string path) |     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)); |         var entry = _fileCaches.SelectMany(v => v.Value).FirstOrDefault(f => f.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)); | ||||||
|  |  | ||||||
|         if (entry == null) |         if (entry == null) | ||||||
| @@ -195,7 +196,7 @@ public sealed class FileCacheManager : IHostedService | |||||||
|         { |         { | ||||||
|             var cleanedPaths = paths.Distinct(StringComparer.OrdinalIgnoreCase).ToDictionary(p => p, |             var cleanedPaths = paths.Distinct(StringComparer.OrdinalIgnoreCase).ToDictionary(p => p, | ||||||
|                 p => p.Replace("/", "\\", StringComparison.OrdinalIgnoreCase) |                 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(_configService.Current.CacheFolder, _configService.Current.CacheFolder.EndsWith('\\') ? CachePrefix + '\\' : CachePrefix, StringComparison.OrdinalIgnoreCase) | ||||||
|                     .Replace("\\\\", "\\", StringComparison.Ordinal), |                     .Replace("\\\\", "\\", StringComparison.Ordinal), | ||||||
|                 StringComparer.OrdinalIgnoreCase); |                 StringComparer.OrdinalIgnoreCase); | ||||||
| @@ -367,7 +368,7 @@ public sealed class FileCacheManager : IHostedService | |||||||
|     { |     { | ||||||
|         if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase)) |         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)) |         else if (fileCache.PrefixedFilePath.StartsWith(CachePrefix, StringComparison.OrdinalIgnoreCase)) | ||||||
|         { |         { | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								MareSynchronos/Interop/Ipc/IIpcCaller.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								MareSynchronos/Interop/Ipc/IIpcCaller.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | namespace MareSynchronos.Interop.Ipc; | ||||||
|  |  | ||||||
|  | public interface IIpcCaller : IDisposable | ||||||
|  | { | ||||||
|  |     bool APIAvailable { get; } | ||||||
|  |     void CheckAPI(); | ||||||
|  | } | ||||||
							
								
								
									
										139
									
								
								MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								MareSynchronos/Interop/Ipc/IpcCallerCustomize.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<ushort, (int, Guid?)> _customizePlusGetActiveProfile; | ||||||
|  |     private readonly ICallGateSubscriber<Guid, (int, string?)> _customizePlusGetProfileById; | ||||||
|  |     private readonly ICallGateSubscriber<ushort, Guid, object> _customizePlusOnScaleUpdate; | ||||||
|  |     private readonly ICallGateSubscriber<ushort, int> _customizePlusRevertCharacter; | ||||||
|  |     private readonly ICallGateSubscriber<ushort, string, (int, Guid?)> _customizePlusSetBodyScaleToCharacter; | ||||||
|  |     private readonly ICallGateSubscriber<Guid, int> _customizePlusDeleteByUniqueId; | ||||||
|  |     private readonly ILogger<IpcCallerCustomize> _logger; | ||||||
|  |     private readonly DalamudUtilService _dalamudUtil; | ||||||
|  |     private readonly MareMediator _mareMediator; | ||||||
|  |  | ||||||
|  |     public IpcCallerCustomize(ILogger<IpcCallerCustomize> logger, IDalamudPluginInterface dalamudPluginInterface, | ||||||
|  |         DalamudUtilService dalamudUtil, MareMediator mareMediator) | ||||||
|  |     { | ||||||
|  |         _customizePlusApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("CustomizePlus.General.GetApiVersion"); | ||||||
|  |         _customizePlusGetActiveProfile = dalamudPluginInterface.GetIpcSubscriber<ushort, (int, Guid?)>("CustomizePlus.Profile.GetActiveProfileIdOnCharacter"); | ||||||
|  |         _customizePlusGetProfileById = dalamudPluginInterface.GetIpcSubscriber<Guid, (int, string?)>("CustomizePlus.Profile.GetByUniqueId"); | ||||||
|  |         _customizePlusRevertCharacter = dalamudPluginInterface.GetIpcSubscriber<ushort, int>("CustomizePlus.Profile.DeleteTemporaryProfileOnCharacter"); | ||||||
|  |         _customizePlusSetBodyScaleToCharacter = dalamudPluginInterface.GetIpcSubscriber<ushort, string, (int, Guid?)>("CustomizePlus.Profile.SetTemporaryProfileOnCharacter"); | ||||||
|  |         _customizePlusOnScaleUpdate = dalamudPluginInterface.GetIpcSubscriber<ushort, Guid, object>("CustomizePlus.Profile.OnUpdate"); | ||||||
|  |         _customizePlusDeleteByUniqueId = dalamudPluginInterface.GetIpcSubscriber<Guid, int>("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<Guid?> 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<string?> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										211
									
								
								MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<IpcCallerGlamourer> _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<nint>? _glamourerStateChanged; | ||||||
|  |  | ||||||
|  |     private bool _shownGlamourerUnavailable = false; | ||||||
|  |     private readonly uint LockCode = 0x626E7579; | ||||||
|  |  | ||||||
|  |     public IpcCallerGlamourer(ILogger<IpcCallerGlamourer> 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<string> 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)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										94
									
								
								MareSynchronos/Interop/Ipc/IpcCallerHeels.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								MareSynchronos/Interop/Ipc/IpcCallerHeels.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<IpcCallerHeels> _logger; | ||||||
|  |     private readonly MareMediator _mareMediator; | ||||||
|  |     private readonly DalamudUtilService _dalamudUtil; | ||||||
|  |     private readonly ICallGateSubscriber<(int, int)> _heelsGetApiVersion; | ||||||
|  |     private readonly ICallGateSubscriber<string> _heelsGetOffset; | ||||||
|  |     private readonly ICallGateSubscriber<string, object?> _heelsOffsetUpdate; | ||||||
|  |     private readonly ICallGateSubscriber<int, string, object?> _heelsRegisterPlayer; | ||||||
|  |     private readonly ICallGateSubscriber<int, object?> _heelsUnregisterPlayer; | ||||||
|  |  | ||||||
|  |     public IpcCallerHeels(ILogger<IpcCallerHeels> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator) | ||||||
|  |     { | ||||||
|  |         _logger = logger; | ||||||
|  |         _mareMediator = mareMediator; | ||||||
|  |         _dalamudUtil = dalamudUtil; | ||||||
|  |         _heelsGetApiVersion = pi.GetIpcSubscriber<(int, int)>("SimpleHeels.ApiVersion"); | ||||||
|  |         _heelsGetOffset = pi.GetIpcSubscriber<string>("SimpleHeels.GetLocalPlayer"); | ||||||
|  |         _heelsRegisterPlayer = pi.GetIpcSubscriber<int, string, object?>("SimpleHeels.RegisterPlayer"); | ||||||
|  |         _heelsUnregisterPlayer = pi.GetIpcSubscriber<int, object?>("SimpleHeels.UnregisterPlayer"); | ||||||
|  |         _heelsOffsetUpdate = pi.GetIpcSubscriber<string, object?>("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<string> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										132
									
								
								MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								MareSynchronos/Interop/Ipc/IpcCallerHonorific.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<int, object> _honorificClearCharacterTitle; | ||||||
|  |     private readonly ICallGateSubscriber<object> _honorificDisposing; | ||||||
|  |     private readonly ICallGateSubscriber<string> _honorificGetLocalCharacterTitle; | ||||||
|  |     private readonly ICallGateSubscriber<string, object> _honorificLocalCharacterTitleChanged; | ||||||
|  |     private readonly ICallGateSubscriber<object> _honorificReady; | ||||||
|  |     private readonly ICallGateSubscriber<int, string, object> _honorificSetCharacterTitle; | ||||||
|  |     private readonly ILogger<IpcCallerHonorific> _logger; | ||||||
|  |     private readonly MareMediator _mareMediator; | ||||||
|  |     private readonly DalamudUtilService _dalamudUtil; | ||||||
|  |  | ||||||
|  |     public IpcCallerHonorific(ILogger<IpcCallerHonorific> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, | ||||||
|  |         MareMediator mareMediator) | ||||||
|  |     { | ||||||
|  |         _logger = logger; | ||||||
|  |         _mareMediator = mareMediator; | ||||||
|  |         _dalamudUtil = dalamudUtil; | ||||||
|  |         _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); | ||||||
|  |         _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber<string>("Honorific.GetLocalCharacterTitle"); | ||||||
|  |         _honorificClearCharacterTitle = pi.GetIpcSubscriber<int, object>("Honorific.ClearCharacterTitle"); | ||||||
|  |         _honorificSetCharacterTitle = pi.GetIpcSubscriber<int, string, object>("Honorific.SetCharacterTitle"); | ||||||
|  |         _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber<string, object>("Honorific.LocalCharacterTitleChanged"); | ||||||
|  |         _honorificDisposing = pi.GetIpcSubscriber<object>("Honorific.Disposing"); | ||||||
|  |         _honorificReady = pi.GetIpcSubscriber<object>("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()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										334
									
								
								MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								MareSynchronos/Interop/Ipc/IpcCallerPenumbra.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<IntPtr, bool> _penumbraRedrawRequests = new(); | ||||||
|  |  | ||||||
|  |     private readonly EventSubscriber _penumbraDispose; | ||||||
|  |     private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved; | ||||||
|  |     private readonly EventSubscriber _penumbraInit; | ||||||
|  |     private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _penumbraModSettingChanged; | ||||||
|  |     private readonly EventSubscriber<nint, int> _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<IpcCallerPenumbra> 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<PenumbraRedrawCharacterMessage>(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<string, string[]> 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<Guid> 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<Dictionary<string, HashSet<string>>?> 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<string, string> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								MareSynchronos/Interop/Ipc/IpcManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								MareSynchronos/Interop/Ipc/IpcManager.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<IpcManager> 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<DelayedFrameworkUpdateMessage>(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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								MareSynchronos/Interop/Ipc/RedrawManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								MareSynchronos/Interop/Ipc/RedrawManager.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<nint, bool> _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<ICharacter> 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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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<ushort, (int, Guid?)> _customizePlusGetActiveProfile; |  | ||||||
|     private readonly ICallGateSubscriber<Guid, (int, string?)> _customizePlusGetProfileById; |  | ||||||
|     private readonly ICallGateSubscriber<ushort, Guid, object> _customizePlusOnScaleUpdate; |  | ||||||
|     private readonly ICallGateSubscriber<ushort, int> _customizePlusRevertCharacter; |  | ||||||
|     private readonly ICallGateSubscriber<ushort, string, (int, Guid?)> _customizePlusSetBodyScaleToCharacter; |  | ||||||
|     private readonly ICallGateSubscriber<Guid, int> _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<nint> _glamourerStateChanged; |  | ||||||
|     private readonly ICallGateSubscriber<(int, int)> _heelsGetApiVersion; |  | ||||||
|     private readonly ICallGateSubscriber<string> _heelsGetOffset; |  | ||||||
|     private readonly ICallGateSubscriber<string, object?> _heelsOffsetUpdate; |  | ||||||
|     private readonly ICallGateSubscriber<int, string, object?> _heelsRegisterPlayer; |  | ||||||
|     private readonly ICallGateSubscriber<int, object?> _heelsUnregisterPlayer; |  | ||||||
|     private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion; |  | ||||||
|     private readonly ICallGateSubscriber<int, object> _honorificClearCharacterTitle; |  | ||||||
|     private readonly ICallGateSubscriber<object> _honorificDisposing; |  | ||||||
|     private readonly ICallGateSubscriber<string> _honorificGetLocalCharacterTitle; |  | ||||||
|     private readonly ICallGateSubscriber<string, object> _honorificLocalCharacterTitleChanged; |  | ||||||
|     private readonly ICallGateSubscriber<object> _honorificReady; |  | ||||||
|     private readonly ICallGateSubscriber<int, string, object> _honorificSetCharacterTitle; |  | ||||||
|     private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new(); |  | ||||||
|     private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraDispose; |  | ||||||
|     private readonly Penumbra.Api.Helpers.EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved; |  | ||||||
|     private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraInit; |  | ||||||
|     private readonly Penumbra.Api.Helpers.EventSubscriber<ModSettingChange, Guid, string, bool> _penumbraModSettingChanged; |  | ||||||
|     private readonly Penumbra.Api.Helpers.EventSubscriber<nint, int> _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<IpcManager> 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<string>("SimpleHeels.GetLocalPlayer"); |  | ||||||
|         _heelsRegisterPlayer = pi.GetIpcSubscriber<int, string, object?>("SimpleHeels.RegisterPlayer"); |  | ||||||
|         _heelsUnregisterPlayer = pi.GetIpcSubscriber<int, object?>("SimpleHeels.UnregisterPlayer"); |  | ||||||
|         _heelsOffsetUpdate = pi.GetIpcSubscriber<string, object?>("SimpleHeels.LocalChanged"); |  | ||||||
|  |  | ||||||
|         _heelsOffsetUpdate.Subscribe(HeelsOffsetChange); |  | ||||||
|  |  | ||||||
|         _customizePlusApiVersion = pi.GetIpcSubscriber<(int, int)>("CustomizePlus.General.GetApiVersion"); |  | ||||||
|         _customizePlusGetActiveProfile = pi.GetIpcSubscriber<ushort, (int, Guid?)>("CustomizePlus.Profile.GetActiveProfileIdOnCharacter"); |  | ||||||
|         _customizePlusGetProfileById = pi.GetIpcSubscriber<Guid, (int, string?)>("CustomizePlus.Profile.GetByUniqueId"); |  | ||||||
|         _customizePlusRevertCharacter = pi.GetIpcSubscriber<ushort, int>("CustomizePlus.Profile.DeleteTemporaryProfileOnCharacter"); |  | ||||||
|         _customizePlusSetBodyScaleToCharacter = pi.GetIpcSubscriber<ushort, string, (int, Guid?)>("CustomizePlus.Profile.SetTemporaryProfileOnCharacter"); |  | ||||||
|         _customizePlusOnScaleUpdate = pi.GetIpcSubscriber<ushort, Guid, object>("CustomizePlus.Profile.OnUpdate"); |  | ||||||
|         _customizePlusDeleteByUniqueId = pi.GetIpcSubscriber<Guid, int>("CustomizePlus.Profile.DeleteTemporaryProfileByUniqueId"); |  | ||||||
|  |  | ||||||
|         _customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange); |  | ||||||
|  |  | ||||||
|         _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); |  | ||||||
|         _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber<string>("Honorific.GetLocalCharacterTitle"); |  | ||||||
|         _honorificClearCharacterTitle = pi.GetIpcSubscriber<int, object>("Honorific.ClearCharacterTitle"); |  | ||||||
|         _honorificSetCharacterTitle = pi.GetIpcSubscriber<int, string, object>("Honorific.SetCharacterTitle"); |  | ||||||
|         _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber<string, object>("Honorific.LocalCharacterTitleChanged"); |  | ||||||
|         _honorificDisposing = pi.GetIpcSubscriber<object>("Honorific.Disposing"); |  | ||||||
|         _honorificReady = pi.GetIpcSubscriber<object>("Honorific.Ready"); |  | ||||||
|  |  | ||||||
|         _honorificLocalCharacterTitleChanged.Subscribe(OnHonorificLocalCharacterTitleChanged); |  | ||||||
|         _honorificDisposing.Subscribe(OnHonorificDisposing); |  | ||||||
|         _honorificReady.Subscribe(OnHonorificReady); |  | ||||||
|  |  | ||||||
|         if (Initialized) |  | ||||||
|         { |  | ||||||
|             Mediator.Publish(new PenumbraInitializedMessage()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Mediator.Subscribe<DelayedFrameworkUpdateMessage>(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<Guid?> 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<string?> 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<string> 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<string> 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<string, string[]> 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<Guid> 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<Dictionary<string, HashSet<string>>?> 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<string, string> 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<ICharacter> 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)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -2,7 +2,7 @@ | |||||||
| using K4os.Compression.LZ4.Legacy; | using K4os.Compression.LZ4.Legacy; | ||||||
| using MareSynchronos.API.Data.Enum; | using MareSynchronos.API.Data.Enum; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
| using MareSynchronos.PlayerData.Handlers; | using MareSynchronos.PlayerData.Handlers; | ||||||
| @@ -51,12 +51,12 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase | |||||||
|             { |             { | ||||||
|                 if ((await dalamudUtil.RunOnFrameworkThread(() => item.Value.CurrentAddress()).ConfigureAwait(false)) != nint.Zero) |                 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 |                 else | ||||||
|                 { |                 { | ||||||
|                     _logger.LogDebug("Reverting by name: {name}", item.Key); |                     _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)) |             foreach (var id in _gposeCustomizeObjects.Where(d => d != null)) | ||||||
|             { |             { | ||||||
|                 await _ipcManager.CustomizePlusRevertByIdAsync(id).ConfigureAwait(false); |                 await _ipcManager.CustomizePlus.RevertByIdAsync(id).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|             _gposeGameObjects.Clear(); |             _gposeGameObjects.Clear(); | ||||||
|         }); |         }); | ||||||
| @@ -99,10 +99,10 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 var applicationId = Guid.NewGuid(); |                 var applicationId = Guid.NewGuid(); | ||||||
|                 var coll = await _ipcManager.PenumbraCreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); |                 var coll = await _ipcManager.Penumbra.CreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraAssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectIndex).ConfigureAwait(false); |                 await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); |                 await _ipcManager.Penumbra.SetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); |                 await _ipcManager.Penumbra.SetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); | ||||||
|  |  | ||||||
|                 GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, |                 GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, | ||||||
|                     () => _dalamudUtil.GetGposeCharacterFromObjectTableByName(charaTarget.Name.ToString(), _isInGpose)?.Address ?? IntPtr.Zero, isWatched: false).ConfigureAwait(false); |                     () => _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())) |                 if (!_gposeGameObjects.ContainsKey(charaTarget.Name.ToString())) | ||||||
|                     _gposeGameObjects[charaTarget.Name.ToString()] = tempHandler; |                     _gposeGameObjects[charaTarget.Name.ToString()] = tempHandler; | ||||||
|  |  | ||||||
|                 await _ipcManager.GlamourerApplyAllAsync(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false); |                 await _ipcManager.Glamourer.ApplyAllAsync(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(_logger, tempHandler, applicationId, disposeCts.Token).ConfigureAwait(false); |                 await _ipcManager.Penumbra.RedrawAsync(_logger, tempHandler, applicationId, disposeCts.Token).ConfigureAwait(false); | ||||||
|                 _dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address, 30000); |                 _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)) |                 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); |                     _gposeCustomizeObjects.Add(id); | ||||||
|                 } |                 } | ||||||
|                 else |                 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); |                     _gposeCustomizeObjects.Add(id); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| using MareSynchronos.API.Dto.User; | using MareSynchronos.API.Dto.User; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.PlayerData.Handlers; | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
|   | |||||||
| @@ -1,12 +1,11 @@ | |||||||
| using FFXIVClientStructs.FFXIV.Client.Game.Character; | using FFXIVClientStructs.FFXIV.Client.Game.Character; | ||||||
| using MareSynchronos.API.Data.Enum; | using MareSynchronos.API.Data.Enum; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.PlayerData.Data; | using MareSynchronos.PlayerData.Data; | ||||||
| using MareSynchronos.PlayerData.Handlers; | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| using System.Diagnostics; |  | ||||||
| using CharacterData = MareSynchronos.PlayerData.Data.CharacterData; | using CharacterData = MareSynchronos.PlayerData.Data.CharacterData; | ||||||
|  |  | ||||||
| namespace MareSynchronos.PlayerData.Factories; | namespace MareSynchronos.PlayerData.Factories; | ||||||
| @@ -141,7 +140,7 @@ public class PlayerDataFactory | |||||||
|         // penumbra call, it's currently broken |         // penumbra call, it's currently broken | ||||||
|         Dictionary<string, HashSet<string>>? resolvedPaths; |         Dictionary<string, HashSet<string>>? 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"); |         if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data"); | ||||||
|  |  | ||||||
|         previousData.FileReplacements[objectKind] = |         previousData.FileReplacements[objectKind] = | ||||||
| @@ -192,16 +191,16 @@ public class PlayerDataFactory | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // gather up data from ipc |         // gather up data from ipc | ||||||
|         previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations(); |         previousData.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations(); | ||||||
|         Task<string> getHeelsOffset = _ipcManager.GetHeelsOffsetAsync(); |         Task<string> getHeelsOffset = _ipcManager.Heels.GetOffsetAsync(); | ||||||
|         Task<string> getGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(playerRelatedObject.Address); |         Task<string> getGlamourerData = _ipcManager.Glamourer.GetCharacterCustomizationAsync(playerRelatedObject.Address); | ||||||
|         Task<string?> getCustomizeData = _ipcManager.GetCustomizePlusScaleAsync(playerRelatedObject.Address); |         Task<string?> getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address); | ||||||
|         previousData.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false); |         previousData.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false); | ||||||
|         _logger.LogDebug("Glamourer is now: {data}", previousData.GlamourerString[playerRelatedObject.ObjectKind]); |         _logger.LogDebug("Glamourer is now: {data}", previousData.GlamourerString[playerRelatedObject.ObjectKind]); | ||||||
|         var customizeScale = await getCustomizeData.ConfigureAwait(false); |         var customizeScale = await getCustomizeData.ConfigureAwait(false); | ||||||
|         previousData.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty; |         previousData.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty; | ||||||
|         _logger.LogDebug("Customize is now: {data}", previousData.CustomizePlusScale[playerRelatedObject.ObjectKind]); |         _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); |         _logger.LogDebug("Honorific is now: {data}", previousData.HonorificData); | ||||||
|         previousData.HeelsData = await getHeelsOffset.ConfigureAwait(false); |         previousData.HeelsData = await getHeelsOffset.ConfigureAwait(false); | ||||||
|         _logger.LogDebug("Heels is now: {heels}", previousData.HeelsData); |         _logger.LogDebug("Heels is now: {heels}", previousData.HeelsData); | ||||||
| @@ -232,7 +231,7 @@ public class PlayerDataFactory | |||||||
|         var forwardPaths = forwardResolve.ToArray(); |         var forwardPaths = forwardResolve.ToArray(); | ||||||
|         var reversePaths = reverseResolve.ToArray(); |         var reversePaths = reverseResolve.ToArray(); | ||||||
|         Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal); |         Dictionary<string, List<string>> 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++) |         for (int i = 0; i < forwardPaths.Length; i++) | ||||||
|         { |         { | ||||||
|             var filePath = forward[i].ToLowerInvariant(); |             var filePath = forward[i].ToLowerInvariant(); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
| using MareSynchronos.API.Dto.User; | using MareSynchronos.API.Dto.User; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| @@ -59,7 +59,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|         _dalamudUtil = dalamudUtil; |         _dalamudUtil = dalamudUtil; | ||||||
|         _lifetime = lifetime; |         _lifetime = lifetime; | ||||||
|         _fileDbManager = fileDbManager; |         _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<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); |         Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); | ||||||
|         Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => |         Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => | ||||||
| @@ -70,7 +70,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|         }); |         }); | ||||||
|         Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => |         Mediator.Subscribe<PenumbraInitializedMessage>(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) |             if (!IsVisible && _charaHandler != null) | ||||||
|             { |             { | ||||||
|                 PlayerName = string.Empty; |                 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 (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, |             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"))); |                 "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; |             if (_lifetime.IsCancellationRequested) return; | ||||||
|  |  | ||||||
|             Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser); |             Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser); | ||||||
|             _ipcManager.PenumbraRemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); |             _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); | ||||||
|  |  | ||||||
|             if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) |             if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) | ||||||
|             { |             { | ||||||
| @@ -246,7 +246,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|                 if (!IsVisible) |                 if (!IsVisible) | ||||||
|                 { |                 { | ||||||
|                     Logger.LogDebug("[{applicationId}] Restoring Glamourer for {name} ({user})", applicationId, name, OnlineUser); |                     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 |                 else | ||||||
|                 { |                 { | ||||||
| @@ -314,32 +314,32 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|                     case PlayerChanges.Customize: |                     case PlayerChanges.Customize: | ||||||
|                         if (charaData.CustomizePlusData.TryGetValue(changes.Key, out var customizePlusData)) |                         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)) |                         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); |                             _customizeIds.Remove(changes.Key); | ||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
|  |  | ||||||
|                     case PlayerChanges.Heels: |                     case PlayerChanges.Heels: | ||||||
|                         await _ipcManager.HeelsSetOffsetForPlayerAsync(handler.Address, charaData.HeelsData).ConfigureAwait(false); |                         await _ipcManager.Heels.SetOffsetForPlayerAsync(handler.Address, charaData.HeelsData).ConfigureAwait(false); | ||||||
|                         break; |                         break; | ||||||
|  |  | ||||||
|                     case PlayerChanges.Honorific: |                     case PlayerChanges.Honorific: | ||||||
|                         await _ipcManager.HonorificSetTitleAsync(handler.Address, charaData.HonorificData).ConfigureAwait(false); |                         await _ipcManager.Honorific.SetTitleAsync(handler.Address, charaData.HonorificData).ConfigureAwait(false); | ||||||
|                         break; |                         break; | ||||||
|  |  | ||||||
|                     case PlayerChanges.Glamourer: |                     case PlayerChanges.Glamourer: | ||||||
|                         if (charaData.GlamourerData.TryGetValue(changes.Key, out var glamourerData)) |                         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; |                         break; | ||||||
|  |  | ||||||
|                     case PlayerChanges.ForcedRedraw: |                     case PlayerChanges.ForcedRedraw: | ||||||
|                         await _ipcManager.PenumbraRedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); |                         await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); | ||||||
|                         break; |                         break; | ||||||
|  |  | ||||||
|                     default: |                     default: | ||||||
| @@ -437,7 +437,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|                     if (updateModdedPaths) |                     if (updateModdedPaths) | ||||||
|                     { |                     { | ||||||
|                         await _ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false); |                         await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false); | ||||||
|                         LastAppliedDataSize = -1; |                         LastAppliedDataSize = -1; | ||||||
|                         foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) |                         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) |                     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(); |                     token.ThrowIfCancellationRequested(); | ||||||
| @@ -531,10 +531,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|         { |         { | ||||||
|             if (string.IsNullOrEmpty(_cachedData?.HonorificData)) return; |             if (string.IsNullOrEmpty(_cachedData?.HonorificData)) return; | ||||||
|             Logger.LogTrace("Reapplying Honorific data for {this}", this); |             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); |         _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); |             using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, isWatched: false).ConfigureAwait(false); | ||||||
|             tempHandler.CompareNameAndThrow(name); |             tempHandler.CompareNameAndThrow(name); | ||||||
|             Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, 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); |             tempHandler.CompareNameAndThrow(name); | ||||||
|             Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, 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); |             tempHandler.CompareNameAndThrow(name); | ||||||
|             Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, 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); |             tempHandler.CompareNameAndThrow(name); | ||||||
|             Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, 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) |         else if (objectKind == ObjectKind.MinionOrMount) | ||||||
|         { |         { | ||||||
|             var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false); |             var minionOrMount = await _dalamudUtil.GetMinionOrMountAsync(address).ConfigureAwait(false); | ||||||
|             if (minionOrMount != nint.Zero) |             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); |                 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.Glamourer.RevertAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); |                 await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else if (objectKind == ObjectKind.Pet) |         else if (objectKind == ObjectKind.Pet) | ||||||
| @@ -582,10 +582,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|             var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); |             var pet = await _dalamudUtil.GetPetAsync(address).ConfigureAwait(false); | ||||||
|             if (pet != nint.Zero) |             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); |                 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.Glamourer.RevertAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); |                 await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else if (objectKind == ObjectKind.Companion) |         else if (objectKind == ObjectKind.Companion) | ||||||
| @@ -593,10 +593,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|             var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); |             var companion = await _dalamudUtil.GetCompanionAsync(address).ConfigureAwait(false); | ||||||
|             if (companion != nint.Zero) |             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); |                 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.Glamourer.RevertAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); | ||||||
|                 await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); |                 await _ipcManager.Penumbra.RedrawAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ using Dalamud.Plugin; | |||||||
| using Dalamud.Plugin.Services; | using Dalamud.Plugin.Services; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop; | ||||||
|  | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.PlayerData.Export; | using MareSynchronos.PlayerData.Export; | ||||||
| using MareSynchronos.PlayerData.Factories; | using MareSynchronos.PlayerData.Factories; | ||||||
| @@ -98,8 +99,21 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); |                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); | ||||||
|             collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService<ILogger<DtrEntry>>(), dtrBar, s.GetRequiredService<MareConfigService>(), |             collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService<ILogger<DtrEntry>>(), dtrBar, s.GetRequiredService<MareConfigService>(), | ||||||
|                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<ApiController>())); |                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<ApiController>())); | ||||||
|  |  | ||||||
|  |             collection.AddSingleton<RedrawManager>(); | ||||||
|  |             collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService<ILogger<IpcCallerPenumbra>>(), pluginInterface, | ||||||
|  |                 s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<RedrawManager>())); | ||||||
|  |             collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService<ILogger<IpcCallerGlamourer>>(), pluginInterface, | ||||||
|  |                 s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<RedrawManager>())); | ||||||
|  |             collection.AddSingleton((s) => new IpcCallerCustomize(s.GetRequiredService<ILogger<IpcCallerCustomize>>(), pluginInterface, | ||||||
|  |                 s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>())); | ||||||
|  |             collection.AddSingleton((s) => new IpcCallerHeels(s.GetRequiredService<ILogger<IpcCallerHeels>>(), pluginInterface, | ||||||
|  |                 s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>())); | ||||||
|  |             collection.AddSingleton((s) => new IpcCallerHonorific(s.GetRequiredService<ILogger<IpcCallerHonorific>>(), pluginInterface, | ||||||
|  |                 s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>())); | ||||||
|             collection.AddSingleton((s) => new IpcManager(s.GetRequiredService<ILogger<IpcManager>>(), |             collection.AddSingleton((s) => new IpcManager(s.GetRequiredService<ILogger<IpcManager>>(), | ||||||
|                 pluginInterface, s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>())); |                 s.GetRequiredService<MareMediator>(), s.GetRequiredService<IpcCallerPenumbra>(), s.GetRequiredService<IpcCallerGlamourer>(), | ||||||
|  |                 s.GetRequiredService<IpcCallerCustomize>(), s.GetRequiredService<IpcCallerHeels>(), s.GetRequiredService<IpcCallerHonorific>())); | ||||||
|  |  | ||||||
|             collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); |             collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); | ||||||
|             collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); |             collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | using Dalamud.Game.ClientState.Objects.Types; | ||||||
| using Dalamud.Interface.ImGuiNotification; | using Dalamud.Interface.ImGuiNotification; | ||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
| using MareSynchronos.API.Dto; | using MareSynchronos.API.Dto; | ||||||
| @@ -81,7 +82,7 @@ public record CombatStartMessage : MessageBase; | |||||||
| public record CombatEndMessage : MessageBase; | public record CombatEndMessage : MessageBase; | ||||||
| public record EventMessage(Event Event) : MessageBase; | public record EventMessage(Event Event) : MessageBase; | ||||||
| public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; | public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; | ||||||
|  | public record PenumbraRedrawCharacterMessage(ICharacter Character) : SameThreadMessage; | ||||||
| public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase; | public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase; | ||||||
| public record GroupChatMsgMessage(GroupDto GroupInfo, SignedChatMessage ChatMsg) : MessageBase; | public record GroupChatMsgMessage(GroupDto GroupInfo, SignedChatMessage ChatMsg) : MessageBase; | ||||||
| #pragma warning restore S2094 | #pragma warning restore S2094 | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| using Dalamud.Interface.ImGuiNotification; | using Dalamud.Interface.ImGuiNotification; | ||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
| using MareSynchronos.API.Data.Comparer; | using MareSynchronos.API.Data.Comparer; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||||
| @@ -35,18 +35,18 @@ public class PluginWarningNotificationService | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         List<string> missingPluginsForData = []; |         List<string> missingPluginsForData = []; | ||||||
|         if (changes.Contains(PlayerChanges.Heels) && !warning.ShownHeelsWarning && !_ipcManager.CheckHeelsApi()) |         if (changes.Contains(PlayerChanges.Heels) && !warning.ShownHeelsWarning && !_ipcManager.Heels.APIAvailable) | ||||||
|         { |         { | ||||||
|             missingPluginsForData.Add("SimpleHeels"); |             missingPluginsForData.Add("SimpleHeels"); | ||||||
|             warning.ShownHeelsWarning = true; |             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+"); |             missingPluginsForData.Add("Customize+"); | ||||||
|             warning.ShownCustomizePlusWarning = true; |             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"); |             missingPluginsForData.Add("Honorific"); | ||||||
|             warning.ShownHonorificWarning = true; |             warning.ShownHonorificWarning = true; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ using Dalamud.Interface.Colors; | |||||||
| using Dalamud.Interface.Utility.Raii; | using Dalamud.Interface.Utility.Raii; | ||||||
| using ImGuiNET; | using ImGuiNET; | ||||||
| using MareSynchronos.API.Data.Enum; | using MareSynchronos.API.Data.Enum; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Utils; | 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)")) |                             if (_texturesToConvert.Count > 0 && UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.PlayCircle, "Start conversion of " + _texturesToConvert.Count + " texture(s)")) | ||||||
|                             { |                             { | ||||||
|                                 _conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate(); |                                 _conversionCancellationTokenSource = _conversionCancellationTokenSource.CancelRecreate(); | ||||||
|                                 _conversionTask = _ipcManager.PenumbraConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token); |                                 _conversionTask = _ipcManager.Penumbra.ConvertTextureFiles(_logger, _texturesToConvert, _conversionProgress, _conversionCancellationTokenSource.Token); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ using MareSynchronos.API.Data.Comparer; | |||||||
| using MareSynchronos.API.Dto.Account; | using MareSynchronos.API.Dto.Account; | ||||||
| using MareSynchronos.API.Routes; | using MareSynchronos.API.Routes; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.MareConfiguration.Models; | using MareSynchronos.MareConfiguration.Models; | ||||||
| using MareSynchronos.PlayerData.Export; | using MareSynchronos.PlayerData.Export; | ||||||
| @@ -750,7 +750,7 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|             using var id = ImRaii.PushId("penumbraMonitor"); |             using var id = ImRaii.PushId("penumbraMonitor"); | ||||||
|             if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.ArrowsToCircle, "Try to reinitialize Monitor")) |             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")) |             if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Play, "Resume Monitoring")) | ||||||
|             { |             { | ||||||
|                 _cacheMonitor.StartMareWatcher(_configService.Current.CacheFolder); |                 _cacheMonitor.StartMareWatcher(_configService.Current.CacheFolder); | ||||||
|                 _cacheMonitor.StartPenumbraWatcher(_ipcManager.PenumbraModDirectory); |                 _cacheMonitor.StartPenumbraWatcher(_ipcManager.Penumbra.PenumbraModDirectory); | ||||||
|                 _cacheMonitor.InvokeScan(); |                 _cacheMonitor.InvokeScan(); | ||||||
|             } |             } | ||||||
|             UiSharedService.AttachToolTip("Attempts to resume monitoring for both Penumbra and Loporrit Storage. " |             UiSharedService.AttachToolTip("Attempts to resume monitoring for both Penumbra and Loporrit Storage. " | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ using Dalamud.Plugin.Services; | |||||||
| using Dalamud.Utility; | using Dalamud.Utility; | ||||||
| using ImGuiNET; | using ImGuiNET; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.Localization; | using MareSynchronos.Localization; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.MareConfiguration.Models; | using MareSynchronos.MareConfiguration.Models; | ||||||
| @@ -111,11 +111,11 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => |         Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => | ||||||
|         { |         { | ||||||
|             _penumbraExists = _ipcManager.CheckPenumbraApi(); |             _penumbraExists = _ipcManager.Penumbra.APIAvailable; | ||||||
|             _glamourerExists = _ipcManager.CheckGlamourerApi(); |             _glamourerExists = _ipcManager.Glamourer.APIAvailable; | ||||||
|             _customizePlusExists = _ipcManager.CheckCustomizePlusApi(); |             _customizePlusExists = _ipcManager.CustomizePlus.APIAvailable; | ||||||
|             _heelsExists = _ipcManager.CheckHeelsApi(); |             _heelsExists = _ipcManager.Heels.APIAvailable; | ||||||
|             _honorificExists = _ipcManager.CheckHonorificApi(); |             _honorificExists = _ipcManager.Honorific.APIAvailable; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -123,7 +123,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|     public bool EditTrackerPosition { get; set; } |     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; |     public bool IsInGpose => _dalamudUtil.IsInCutscene; | ||||||
|  |  | ||||||
| @@ -601,7 +601,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase | |||||||
|                     if (!success) return; |                     if (!success) return; | ||||||
|  |  | ||||||
|                     _isOneDrive = path.Contains("onedrive", StringComparison.OrdinalIgnoreCase); |                     _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); |                     _isDirectoryWritable = IsDirectoryWritable(path); | ||||||
|                     _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => Path.GetFileNameWithoutExtension(f).Length != 40) |                     _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => Path.GetFileNameWithoutExtension(f).Length != 40) | ||||||
|                         || Directory.GetDirectories(path).Any(); |                         || Directory.GetDirectories(path).Any(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 rootdarkarchon
					rootdarkarchon