From 535554e6e59ade22a0b492284c96e332f4ebf5b3 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sat, 30 Sep 2023 17:20:13 +0200 Subject: [PATCH] adapt to new glamourer --- MareSynchronos/Interop/IpcManager.cs | 86 +++++-------------- MareSynchronos/MareSynchronos.csproj | 12 +-- .../PlayerData/Export/MareCharaFileManager.cs | 33 ++++++- .../PlayerData/Handlers/GameObjectHandler.cs | 33 +++++++ .../PlayerData/Handlers/PairHandler.cs | 49 +---------- MareSynchronos/Services/DalamudUtilService.cs | 7 ++ 6 files changed, 99 insertions(+), 121 deletions(-) diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index 6e2d6ff..a58d387 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -17,19 +17,19 @@ namespace MareSynchronos.Interop; public sealed class IpcManager : DisposableMediatorSubscriberBase { + private readonly uint LockCode = 0x6D617265; + private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion; private readonly ICallGateSubscriber _customizePlusGetBodyScale; private readonly ICallGateSubscriber _customizePlusOnScaleUpdate; private readonly ICallGateSubscriber _customizePlusRevertCharacter; private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; private readonly DalamudUtilService _dalamudUtil; - private readonly ICallGateSubscriber _glamourerApiVersion; private readonly ICallGateSubscriber<(int, int)> _glamourerApiVersions; - private readonly ICallGateSubscriber? _glamourerApplyAll; - private readonly ICallGateSubscriber? _glamourerApplyOnlyCustomization; - private readonly ICallGateSubscriber? _glamourerApplyOnlyEquipment; + private readonly ICallGateSubscriber? _glamourerApplyAll; private readonly ICallGateSubscriber? _glamourerGetAllCustomization; - private readonly ICallGateSubscriber _glamourerRevert; + private readonly ICallGateSubscriber _glamourerRevert; + private readonly ICallGateSubscriber _glamourerUnlock; private readonly ICallGateSubscriber<(int, int)> _heelsGetApiVersion; private readonly ICallGateSubscriber _heelsGetOffset; private readonly ICallGateSubscriber _heelsOffsetUpdate; @@ -70,7 +70,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase private bool _customizePlusAvailable = false; private CancellationTokenSource _disposalCts = new(); private bool _glamourerAvailable = false; - private bool _glamourerTestingAvailable = false; private bool _heelsAvailable = false; private bool _honorificAvailable = false; private bool _palettePlusAvailable = false; @@ -106,13 +105,11 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase _penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); - _glamourerApiVersion = pi.GetIpcSubscriber("Glamourer.ApiVersion"); _glamourerApiVersions = pi.GetIpcSubscriber<(int, int)>("Glamourer.ApiVersions"); _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomizationFromCharacter"); - _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacter"); - _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); - _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); - _glamourerRevert = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); + _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacterLock"); + _glamourerRevert = pi.GetIpcSubscriber("Glamourer.RevertCharacterLock"); + _glamourerUnlock = pi.GetIpcSubscriber("Glamourer.Unlock"); _heelsGetApiVersion = pi.GetIpcSubscriber<(int, int)>("SimpleHeels.ApiVersion"); _heelsGetOffset = pi.GetIpcSubscriber("SimpleHeels.GetLocalPlayer"); @@ -167,8 +164,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public bool CheckGlamourerApi() => _glamourerAvailable; - public bool CheckGlamourerTestingApi() => _glamourerTestingAvailable; - public bool CheckHeelsApi() => _heelsAvailable; public bool CheckHonorificApi() => _honorificAvailable; @@ -248,18 +243,15 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase try { logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); - _glamourerApplyAll!.InvokeAction(customization, chara); + _glamourerApplyAll!.InvokeAction(customization, chara, LockCode); + logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId); + _penumbraRedrawObject.Invoke(chara, RedrawType.Redraw); } catch (Exception) { logger.LogWarning("[{appid}] Failed to apply Glamourer data", applicationId); } - if (_glamourerTestingAvailable) - { - logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId); - _penumbraRedrawObject.Invoke(chara, RedrawType.Redraw); - } }).ConfigureAwait(false); } finally @@ -270,16 +262,17 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public async Task GlamourerRevert(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) { - if ((!CheckGlamourerApi() && !CheckGlamourerTestingApi()) || _dalamudUtil.IsZoning) return; + if ((!CheckGlamourerApi()) || _dalamudUtil.IsZoning) return; try { await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => { logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevert", applicationId); - _glamourerRevert.InvokeAction(chara); + _glamourerRevert.InvokeAction(chara, LockCode); logger.LogDebug("[{appid}] Calling On IPC: PenumbraRedraw", applicationId); - _penumbraRedrawObject.Invoke(chara, RedrawType.Redraw); + _penumbraRedrawObject.Invoke(chara, RedrawType.AfterGPose); + _glamourerUnlock.InvokeFunc(chara, LockCode); }).ConfigureAwait(false); } @@ -289,29 +282,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase } } - public async Task GlamourerApplyCustomizationAndEquipmentAsync(ILogger logger, GameObjectHandler handler, string customization, string equipment, 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) => - { - logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyCustomization", applicationid); - _glamourerApplyOnlyCustomization!.InvokeAction(customization, chara); - }).ConfigureAwait(false); - await PenumbraRedrawInternalAsync(logger, handler, applicationid, (chara) => - { - logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyEquipment", applicationid); - _glamourerApplyOnlyEquipment!.InvokeAction(equipment, chara); - }).ConfigureAwait(false); - } - finally - { - _redrawSemaphore.Release(); - } - } - public async Task GlamourerGetCharacterCustomizationAsync(IntPtr character) { if (!CheckGlamourerApi()) return string.Empty; @@ -630,8 +600,13 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase bool apiAvailable = false; try { - apiAvailable = _glamourerApiVersion.InvokeFunc() >= 0; + var version = _glamourerApiVersions.InvokeFunc(); + if (version.Item1 == 0 && version.Item2 >= 1) + { + apiAvailable = true; + } _shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable; + return apiAvailable; } catch @@ -648,24 +623,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase } } - private bool CheckGlamourerTestingApiInternal() - { - bool apiAvailable = false; - try - { - var version = _glamourerApiVersions.InvokeFunc(); - if (version.Item1 == 0 && version.Item2 >= 1) - { - apiAvailable = true; - } - return apiAvailable; - } - catch - { - return apiAvailable; - } - } - private bool CheckHeelsApiInternal() { try @@ -805,7 +762,6 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase private void PeriodicApiStateCheck() { _glamourerAvailable = CheckGlamourerApiInternal(); - _glamourerTestingAvailable = CheckGlamourerTestingApiInternal(); _penumbraAvailable = CheckPenumbraApiInternal(); _heelsAvailable = CheckHeelsApiInternal(); _customizePlusAvailable = CheckCustomizePlusApiInternal(); diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index b026276..5e0ecb8 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -29,17 +29,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs index 2a3d175..86edfee 100644 --- a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs +++ b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs @@ -10,10 +10,11 @@ using MareSynchronos.Interop; using MareSynchronos.Services; using MareSynchronos.Utils; using MareSynchronos.PlayerData.Factories; +using MareSynchronos.Services.Mediator; namespace MareSynchronos.PlayerData.Export; -public class MareCharaFileManager +public class MareCharaFileManager : DisposableMediatorSubscriberBase { private readonly MareConfigService _configService; private readonly DalamudUtilService _dalamudUtil; @@ -23,9 +24,12 @@ public class MareCharaFileManager private readonly ILogger _logger; private readonly FileCacheManager _manager; private int _globalFileCounter = 0; + private readonly List _gposeGameObjects; + private bool _isInGpose = false; public MareCharaFileManager(ILogger logger, GameObjectHandlerFactory gameObjectHandlerFactory, - FileCacheManager manager, IpcManager ipcManager, MareConfigService configService, DalamudUtilService dalamudUtil) + FileCacheManager manager, IpcManager ipcManager, MareConfigService configService, DalamudUtilService dalamudUtil, + MareMediator mediator) : base(logger, mediator) { _factory = new(manager); _logger = logger; @@ -34,6 +38,19 @@ public class MareCharaFileManager _ipcManager = ipcManager; _configService = configService; _dalamudUtil = dalamudUtil; + _gposeGameObjects = new(); + Mediator.Subscribe(this, _ => _isInGpose = true); + Mediator.Subscribe(this, async _ => + { + _isInGpose = false; + CancellationTokenSource cts = new(); + foreach (var item in _gposeGameObjects) + { + await _ipcManager.GlamourerRevert(logger, item, Guid.NewGuid(), cts.Token); + item.Dispose(); + } + _gposeGameObjects.Clear(); + }); } public bool CurrentlyWorking { get; private set; } = false; @@ -41,11 +58,12 @@ public class MareCharaFileManager public async Task ApplyMareCharaFile(GameObject? charaTarget) { + if (charaTarget == null) return; Dictionary extractedFiles = new(StringComparer.Ordinal); CurrentlyWorking = true; try { - if (LoadedCharaFile == null || charaTarget == null || !File.Exists(LoadedCharaFile.FilePath)) return; + if (LoadedCharaFile == null || !File.Exists(LoadedCharaFile.FilePath)) return; var unwrapped = File.OpenRead(LoadedCharaFile.FilePath); await using (unwrapped.ConfigureAwait(false)) { @@ -69,7 +87,14 @@ public class MareCharaFileManager await _ipcManager.PenumbraAssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); await _ipcManager.PenumbraSetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); await _ipcManager.PenumbraSetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); - using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => charaTarget.Address, false).ConfigureAwait(false); + + + GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, + () => _dalamudUtil.GetGposeCharacterFromObjectTableByName(charaTarget.Name.ToString(), _isInGpose)?.Address ?? IntPtr.Zero, false).ConfigureAwait(false); + + if (!_gposeGameObjects.Exists(o => o.Address == tempHandler.Address)) + _gposeGameObjects.Add(tempHandler); + await _ipcManager.GlamourerApplyAllAsync(_logger, tempHandler, LoadedCharaFile.CharaFileData.GlamourerData, applicationId, disposeCts.Token).ConfigureAwait(false); _dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address, 30000); await _ipcManager.PenumbraRemoveTemporaryCollectionAsync(_logger, applicationId, coll).ConfigureAwait(false); diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 4adf9c6..42fc181 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -102,6 +102,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private byte[] CustomizeData { get; set; } = new byte[26]; private IntPtr DrawObjectAddress { get; set; } private byte[] EquipSlotData { get; set; } = new byte[40]; + private ushort[] MainHandData { get; set; } = new ushort[3]; + private ushort[] OffHandData { get; set; } = new ushort[3]; public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action act, CancellationToken token) { @@ -212,6 +214,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase && ((CharacterBase*)DrawObjectAddress)->GetModelType() == CharacterBase.ModelType.Human) { equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)DrawObjectAddress)->Head); + equipDiff |= CompareAndUpdateMainHand(*((Weapon**)&chara->DrawData.MainHand + 1)); + equipDiff |= CompareAndUpdateOffHand(*((Weapon**)&chara->DrawData.OffHand + 1)); + if (equipDiff) Logger.LogTrace("Checking [{this}] equip data as human from draw obj, result: {diff}", this, equipDiff); } @@ -222,6 +227,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff); } + + if (equipDiff && !_isOwnedObject && !_ignoreSendAfterRedraw) // send the message out immediately and cancel out, no reason to continue if not self { Logger.LogTrace("[{this}] Changed", this); @@ -264,6 +271,32 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase } } + private unsafe bool CompareAndUpdateMainHand(Weapon* weapon) + { + if ((nint)weapon == nint.Zero) return false; + bool hasChanges = false; + hasChanges |= weapon->ModelSetId != MainHandData[0]; + MainHandData[0] = weapon->ModelSetId; + hasChanges |= weapon->Variant != MainHandData[1]; + MainHandData[1] = weapon->Variant; + hasChanges |= weapon->SecondaryId != MainHandData[2]; + MainHandData[2] = weapon->SecondaryId; + return hasChanges; + } + + private unsafe bool CompareAndUpdateOffHand(Weapon* weapon) + { + if ((nint)weapon == nint.Zero) return false; + bool hasChanges = false; + hasChanges |= weapon->ModelSetId != OffHandData[0]; + OffHandData[0] = weapon->ModelSetId; + hasChanges |= weapon->Variant != OffHandData[1]; + OffHandData[1] = weapon->Variant; + hasChanges |= weapon->SecondaryId != OffHandData[2]; + OffHandData[2] = weapon->SecondaryId; + return hasChanges; + } + private async Task ClearAsync(CancellationToken token) { Logger.LogDebug("[{this}] Running Clear Task", this); diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 548aa6c..98a5ad4 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -33,8 +33,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private GameObjectHandler? _charaHandler; private CancellationTokenSource? _downloadCancellationTokenSource = new(); private bool _forceApplyMods = false; - private string _lastGlamourerData = string.Empty; - private string _originalGlamourerData = string.Empty; private string _penumbraCollection; private CancellationTokenSource _redrawCts = new(); @@ -60,7 +58,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (_) => { _downloadCancellationTokenSource?.CancelDispose(); - MediatorUnsubscribeFromCharacterChanged(); _charaHandler?.Invalidate(); IsVisible = false; }); @@ -413,9 +410,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { Logger.LogTrace("[BASE-{appBase}] {this} visibility changed, now: {visi}, cached data exists", appData, this, IsVisible); - _ = Task.Run(async () => + _ = Task.Run(() => { - _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false); ApplyCharacterData(appData, _cachedData!, true); }); } @@ -423,8 +419,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { Logger.LogTrace("{this} visibility changed, now: {visi}, no cached data exists", this, IsVisible); } - - MediatorSubscribeToCharacterChanged(); } else if (_charaHandler?.Address == nint.Zero && IsVisible) { @@ -432,7 +426,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _charaHandler?.Invalidate(); _downloadCancellationTokenSource?.CancelDispose(); _downloadCancellationTokenSource = null; - MediatorUnsubscribeFromCharacterChanged(); Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); } } @@ -442,8 +435,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase PlayerName = name; _charaHandler = _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident), false).GetAwaiter().GetResult(); - _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false).GetAwaiter().GetResult(); - _lastGlamourerData = _originalGlamourerData; Mediator.Subscribe(this, IpcManagerOnPenumbraRedrawEvent); Mediator.Subscribe(this, async (_) => { @@ -477,33 +468,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }, token); } - private void MediatorSubscribeToCharacterChanged() - { - Mediator.Subscribe(this, (msg) => - { - if (msg.GameObjectHandler == _charaHandler && (_applicationTask?.IsCompleted ?? true)) - { - Guid appBase = Guid.NewGuid(); - var newGlamData = _ipcManager.GlamourerGetCharacterCustomizationAsync(PlayerCharacter).ConfigureAwait(false).GetAwaiter().GetResult(); - if (!string.Equals(_lastGlamourerData, newGlamData, StringComparison.OrdinalIgnoreCase)) - { - Logger.LogTrace("[BASE-{appBase}] Saving new Glamourer Data for {this}", appBase, this); - - _lastGlamourerData = newGlamData; - if (_cachedData != null) - { - ApplyCharacterData(appBase, _cachedData!, true); - } - } - } - }); - } - - private void MediatorUnsubscribeFromCharacterChanged() - { - Mediator.Unsubscribe(this); - } - private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId) { nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident); @@ -518,15 +482,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, false).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); - Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData); - if (!_ipcManager.CheckGlamourerTestingApi()) - { - await _ipcManager.GlamourerApplyCustomizationAndEquipmentAsync(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); - } - else - { - await _ipcManager.GlamourerRevert(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); - } + Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); + await _ipcManager.GlamourerRevert(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.HeelsRestoreOffsetForPlayerAsync(address).ConfigureAwait(false); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 42be824..5f7ea99 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -92,6 +92,13 @@ public class DalamudUtilService : IHostedService return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj; } + public Dalamud.Game.ClientState.Objects.Types.Character? GetGposeCharacterFromObjectTableByName(string name, bool onlyGposeCharacters = false) + { + EnsureIsOnFramework(); + return (Dalamud.Game.ClientState.Objects.Types.Character?)_objectTable.Where(i => !onlyGposeCharacters || i.ObjectIndex >= 200) + .FirstOrDefault(f => f.Name.ToString() == name); + } + public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null) { EnsureIsOnFramework();