using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; using Glamourer.Api.Helpers; using Glamourer.Api.IpcSubscribers; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.LightlessConfiguration.Models; using LightlessSync.PlayerData.Handlers; using LightlessSync.Services; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; namespace LightlessSync.Interop.Ipc; public sealed class IpcCallerGlamourer : IpcServiceBase { private static readonly IpcServiceDescriptor GlamourerDescriptor = new("Glamourer", "Glamourer", new Version(1, 3, 0, 10)); private readonly ILogger _logger; private readonly IDalamudPluginInterface _pi; private readonly DalamudUtilService _dalamudUtil; private readonly LightlessMediator _lightlessMediator; private readonly RedrawManager _redrawManager; private readonly ApiVersion _glamourerApiVersions; private readonly ApplyState? _glamourerApplyAll; private readonly GetStateBase64? _glamourerGetAllCustomization; private readonly RevertState _glamourerRevert; private readonly RevertStateName _glamourerRevertByName; private readonly UnlockState _glamourerUnlock; private readonly UnlockStateName _glamourerUnlockByName; private readonly EventSubscriber? _glamourerStateChanged; private bool _shownGlamourerUnavailable = false; private readonly uint LockCode = 0x6D617265; public IpcCallerGlamourer(ILogger logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator, RedrawManager redrawManager) : base(logger, lightlessMediator, pi, GlamourerDescriptor) { _glamourerApiVersions = new ApiVersion(pi); _glamourerGetAllCustomization = new GetStateBase64(pi); _glamourerApplyAll = new ApplyState(pi); _glamourerRevert = new RevertState(pi); _glamourerRevertByName = new RevertStateName(pi); _glamourerUnlock = new UnlockState(pi); _glamourerUnlockByName = new UnlockStateName(pi); _logger = logger; _pi = pi; _dalamudUtil = dalamudUtil; _lightlessMediator = lightlessMediator; _redrawManager = redrawManager; CheckAPI(); _glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged); _glamourerStateChanged.Enable(); Mediator.Subscribe(this, s => _shownGlamourerUnavailable = false); } protected override void Dispose(bool disposing) { base.Dispose(disposing); _redrawManager.Cancel(); _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); } }, token).ConfigureAwait(false); } finally { _redrawManager.RedrawSemaphore.Release(); } } public async Task GetCharacterCustomizationAsync(IntPtr character) { if (!APIAvailable) return string.Empty; try { return await _dalamudUtil.RunOnFrameworkThread(() => { var gameObj = _dalamudUtil.CreateGameObject(character); if (gameObj is ICharacter c) { return _glamourerGetAllCustomization!.Invoke(c.ObjectIndex).Item2 ?? string.Empty; } return string.Empty; }).ConfigureAwait(false); } catch { return string.Empty; } } public async Task RevertAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) { if ((!APIAvailable) || _dalamudUtil.IsZoning) return; try { await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false); await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => { try { logger.LogDebug("[{appid}] Calling On IPC: 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); _lightlessMediator.Publish(new PenumbraRedrawCharacterMessage(chara)); } catch (Exception ex) { logger.LogWarning(ex, "[{appid}] Error during GlamourerRevert", applicationId); } }, token).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"); } } protected override IpcConnectionState EvaluateState() { var state = base.EvaluateState(); if (state != IpcConnectionState.Available) { return state; } try { var version = _glamourerApiVersions.Invoke(); return version is { Major: 1, Minor: >= 1 } ? IpcConnectionState.Available : IpcConnectionState.VersionMismatch; } catch (Exception ex) { _logger.LogDebug(ex, "Failed to query Glamourer API version"); return IpcConnectionState.Error; } } protected override void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current) { base.OnConnectionStateChanged(previous, current); if (current == IpcConnectionState.Available) { _shownGlamourerUnavailable = false; return; } if (_shownGlamourerUnavailable || current == IpcConnectionState.Unknown) { return; } _shownGlamourerUnavailable = true; _lightlessMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Lightless. If you just updated Glamourer, ignore this message.", NotificationType.Error)); } private void GlamourerChanged(nint address) { _lightlessMediator.Publish(new GlamourerChangedMessage(address)); } }