diff --git a/MareSynchronos/Interop/FFXIV/RenderModel.cs b/MareSynchronos/Interop/FFXIV/RenderModel.cs deleted file mode 100644 index 17ce6ba..0000000 --- a/MareSynchronos/Interop/FFXIV/RenderModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.InteropServices; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; - -namespace MareSynchronos.Interop.FFXIV; - -[StructLayout(LayoutKind.Explicit)] -public unsafe struct RenderModel -{ - [FieldOffset(0x18)] - public RenderModel* PreviousModel; - - [FieldOffset(0x20)] - public RenderModel* NextModel; - - [FieldOffset(0x30)] - public ResourceHandle* ResourceHandle; - - [FieldOffset(0x40)] - public Skeleton* Skeleton; - - [FieldOffset(0x58)] - public void** BoneList; - - [FieldOffset(0x60)] - public int BoneListCount; - - - [FieldOffset(0x98)] - public void** Materials; - - [FieldOffset(0xA0)] - public int MaterialCount; -} \ No newline at end of file diff --git a/MareSynchronos/Interop/FFXIV/ResourceHandle.cs b/MareSynchronos/Interop/FFXIV/ResourceHandle.cs deleted file mode 100644 index 973d633..0000000 --- a/MareSynchronos/Interop/FFXIV/ResourceHandle.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Runtime.InteropServices; -using FFXIVClientStructs.FFXIV.Client.System.Resource; - -namespace MareSynchronos.Interop.FFXIV; - -[StructLayout(LayoutKind.Explicit)] -public unsafe struct ResourceHandle -{ - public const int SsoSize = 15; - - public byte* FileName() - { - if (FileNameLength > SsoSize) - { - return FileNameData; - } - - fixed (byte** name = &FileNameData) - { - return (byte*)name; - } - } - - [FieldOffset(0x08)] - public ResourceCategory Category; - - [FieldOffset(0x48)] - public byte* FileNameData; - - [FieldOffset(0x58)] - public int FileNameLength; -} \ No newline at end of file diff --git a/MareSynchronos/Interop/FFXIV/Weapon.cs b/MareSynchronos/Interop/FFXIV/Weapon.cs deleted file mode 100644 index b740875..0000000 --- a/MareSynchronos/Interop/FFXIV/Weapon.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace MareSynchronos.Interop.FFXIV; - -[StructLayout(LayoutKind.Explicit)] -public unsafe struct Weapon -{ - [FieldOffset(0x18)] public IntPtr Parent; - [FieldOffset(0x20)] public IntPtr NextSibling; - [FieldOffset(0x28)] public IntPtr PreviousSibling; - [FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel; -} diff --git a/MareSynchronos/Interop/FFXIV/WeaponDrawObject.cs b/MareSynchronos/Interop/FFXIV/WeaponDrawObject.cs deleted file mode 100644 index d4fc435..0000000 --- a/MareSynchronos/Interop/FFXIV/WeaponDrawObject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Runtime.InteropServices; - -namespace MareSynchronos.Interop.FFXIV; - -[StructLayout(LayoutKind.Explicit)] -public unsafe struct WeaponDrawObject -{ - [FieldOffset(0x00)] public RenderModel* RenderModel; -} diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index f08ea5d..5d534dc 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -67,6 +67,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase private readonly FuncSubscriber _penumbraRemoveTemporaryMod; private readonly FuncSubscriber _penumbraResolveModDir; private readonly FuncSubscriber _penumbraResolvePaths; + private readonly ParamsFuncSubscriber?[]> _penumbraResourcePaths; private readonly SemaphoreSlim _redrawSemaphore = new(2); private bool _customizePlusAvailable = false; private CancellationTokenSource _disposalCts = new(); @@ -103,6 +104,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase Mediator.Publish(new PenumbraModSettingChangedMessage()); }); _penumbraConvertTextureFile = Penumbra.Api.Ipc.ConvertTextureFile.Subscriber(pi); + _penumbraResourcePaths = Penumbra.Api.Ipc.GetGameObjectResourcePaths.Subscriber(pi); _penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded); @@ -292,7 +294,8 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId); _glamourerRevertByName.InvokeAction(name, LockCode); } - catch (Exception ex) { + catch (Exception ex) + { Logger.LogWarning(ex, "Error during Glamourer RevertByName"); } } @@ -573,8 +576,24 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase } Mediator.Publish(new ResumeScanMessage("TextureConversion")); - var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync()); - _penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw); + await _dalamudUtil.RunOnFrameworkThread(async () => + { + var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false); + _penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw); + }).ConfigureAwait(false); + } + + public async Task?[]?> PenumbraGetCharacterData(ILogger logger, GameObjectHandler handler) + { + if (!CheckPenumbraApi()) return null; + + return await _dalamudUtil.RunOnFrameworkThread(() => + { + logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths"); + var idx = handler.GetGameObject()?.ObjectIndex; + if (idx == null) return null; + return _penumbraResourcePaths.Invoke(idx.Value); + }).ConfigureAwait(false); } protected override void Dispose(bool disposing) diff --git a/MareSynchronos/PlayerData/Data/FileReplacement.cs b/MareSynchronos/PlayerData/Data/FileReplacement.cs index 151d447..ae8b8df 100644 --- a/MareSynchronos/PlayerData/Data/FileReplacement.cs +++ b/MareSynchronos/PlayerData/Data/FileReplacement.cs @@ -8,7 +8,7 @@ public partial class FileReplacement { private readonly Lazy _hashLazy; - public FileReplacement(List gamePaths, string filePath, FileCacheManager fileDbManager) + public FileReplacement(string[] gamePaths, string filePath, FileCacheManager fileDbManager) { GamePaths = gamePaths.Select(g => g.Replace('\\', '/')).ToHashSet(StringComparer.Ordinal); ResolvedPath = filePath.Replace('\\', '/'); diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 79b8af2..b215fb5 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -1,18 +1,12 @@ using System.Diagnostics; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.FFXIV.Client.System.Resource; using MareSynchronos.API.Data.Enum; using MareSynchronos.Interop; -using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object; -using Weapon = MareSynchronos.Interop.FFXIV.Weapon; using MareSynchronos.FileCache; using Microsoft.Extensions.Logging; -using System.Globalization; using MareSynchronos.PlayerData.Data; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using CharacterData = MareSynchronos.PlayerData.Data.CharacterData; namespace MareSynchronos.PlayerData.Factories; @@ -104,190 +98,6 @@ public class PlayerDataFactory previousData.CustomizePlusScale = previousCustomize; } - private unsafe void AddPlayerSpecificReplacements(Human* human, HashSet forwardResolve, HashSet reverseResolve) - { - var weaponObject = (Weapon*)((Object*)human)->ChildObject; - - if ((IntPtr)weaponObject != IntPtr.Zero) - { - var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel; - - AddReplacementsFromRenderModel((Model*)mainHandWeapon, forwardResolve, reverseResolve); - - foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)weaponObject)) - { - _logger.LogTrace("Found transient weapon resource: {item}", item); - forwardResolve.Add(item); - } - - if (weaponObject->NextSibling != (IntPtr)weaponObject) - { - var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel; - - AddReplacementsFromRenderModel((Model*)offHandWeapon, forwardResolve, reverseResolve); - - foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)offHandWeapon)) - { - _logger.LogTrace("Found transient offhand weapon resource: {item}", item); - forwardResolve.Add(item); - } - } - } - - AddReplacementSkeleton((human)->RaceSexId, forwardResolve); - try - { - AddReplacementsFromTexture((human)->Decal->ResourceHandle.FileName.ToString(), forwardResolve, reverseResolve, doNotReverseResolve: false); - } - catch - { - _logger.LogWarning("Could not get Decal data"); - } - try - { - AddReplacementsFromTexture((human)->LegacyBodyDecal->ResourceHandle.FileName.ToString(), forwardResolve, reverseResolve, doNotReverseResolve: false); - } - catch - { - _logger.LogWarning("Could not get Legacy Body Decal Data"); - } - } - - private unsafe void AddReplacementsFromMaterial(Material* mtrl, HashSet forwardResolve, HashSet reverseResolve) - { - string fileName; - try - { - fileName = mtrl->MaterialResourceHandle->ResourceHandle.FileName.ToString(); - } - catch - { - _logger.LogWarning("Could not get material data"); - return; - } - - _logger.LogTrace("Checking File Replacement for Material {file}", fileName); - var mtrlPath = fileName.Split("|")[2]; - - reverseResolve.Add(mtrlPath); - - var mtrlResourceHandle = mtrl->MaterialResourceHandle; - for (var resIdx = 0; resIdx < mtrlResourceHandle->TextureCount; resIdx++) - { - string? texPath = null; - try - { - texPath = mtrlResourceHandle->TexturePathString(resIdx); - } - catch (Exception e) - { - _logger.LogWarning(e, "Could not get Texture data for Material {file}", fileName); - } - - if (string.IsNullOrEmpty(texPath)) continue; - - AddReplacementsFromTexture(texPath, forwardResolve, reverseResolve); - } - - try - { - var shpkPath = "shader/sm5/shpk/" + mtrlResourceHandle->ShpkNameString; - _logger.LogTrace("Checking File Replacement for Shader {path}", shpkPath); - forwardResolve.Add(shpkPath); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Could not find shpk for Material {path}", fileName); - } - } - - private unsafe void AddReplacementsFromRenderModel(Model* mdl, HashSet forwardResolve, HashSet reverseResolve) - { - if (mdl == null || mdl->ModelResourceHandle == null || mdl->ModelResourceHandle->ResourceHandle.Category != ResourceCategory.Chara) - { - return; - } - - string mdlPath; - try - { - mdlPath = mdl->ModelResourceHandle->ResourceHandle.FileName.ToString(); - } - catch - { - _logger.LogWarning("Could not get model data"); - return; - } - _logger.LogTrace("Checking File Replacement for Model {path}", mdlPath); - - reverseResolve.Add(mdlPath); - - for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++) - { - var mtrl = mdl->Materials[mtrlIdx]; - if (mtrl == null) continue; - - AddReplacementsFromMaterial(mtrl, forwardResolve, reverseResolve); - } - } - - private void AddReplacementsFromTexture(string texPath, HashSet forwardResolve, HashSet reverseResolve, bool doNotReverseResolve = true) - { - if (string.IsNullOrEmpty(texPath)) return; - - _logger.LogTrace("Checking File Replacement for Texture {path}", texPath); - - if (doNotReverseResolve) - forwardResolve.Add(texPath); - else - reverseResolve.Add(texPath); - - if (texPath.Contains("/--", StringComparison.Ordinal)) return; - - var dx11Path = texPath.Insert(texPath.LastIndexOf('/') + 1, "--"); - if (doNotReverseResolve) - forwardResolve.Add(dx11Path); - else - reverseResolve.Add(dx11Path); - } - - private void AddReplacementSkeleton(ushort raceSexId, HashSet forwardResolve) - { - string raceSexIdString = raceSexId.ToString("0000", CultureInfo.InvariantCulture); - - string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb"; - - _logger.LogTrace("Checking skeleton {path}", skeletonPath); - - forwardResolve.Add(skeletonPath); - } - - private unsafe (HashSet forwardResolve, HashSet reverseResolve) BuildDataFromModel(ObjectKind objectKind, nint charaPointer, CancellationToken token) - { - HashSet forwardResolve = new(StringComparer.Ordinal); - HashSet reverseResolve = new(StringComparer.Ordinal); - var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject(); - for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx) - { - var mdl = human->CharacterBase.Models[mdlIdx]; - if (mdl == null || mdl->ModelResourceHandle == null || mdl->ModelResourceHandle->ResourceHandle.Category != ResourceCategory.Chara) - { - continue; - } - - token.ThrowIfCancellationRequested(); - - AddReplacementsFromRenderModel(mdl, forwardResolve, reverseResolve); - } - - if (objectKind == ObjectKind.Player) - { - AddPlayerSpecificReplacements(human, forwardResolve, reverseResolve); - } - - return (forwardResolve, reverseResolve); - } - private async Task CheckForNullDrawObject(IntPtr playerPointer) { return await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)).ConfigureAwait(false); @@ -332,10 +142,11 @@ public class PlayerDataFactory Stopwatch st = Stopwatch.StartNew(); // gather static replacements from render model - var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false); - Dictionary> resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false); + var data = await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false); + if (data == null) throw new InvalidOperationException("Penumbra returned null data"); + previousData.FileReplacements[objectKind] = - new HashSet(resolvedPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)), FileReplacementComparer.Instance) + new HashSet(data[0]!.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)), FileReplacementComparer.Instance) .Where(p => p.HasFileReplacement).ToHashSet(); _logger.LogDebug("== Static Replacements =="); @@ -364,7 +175,7 @@ public class PlayerDataFactory var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet(StringComparer.Ordinal)).ConfigureAwait(false); _logger.LogDebug("== Transient Replacements =="); - foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal)) + foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement(c.Value.ToArray(), c.Key, _fileCacheManager)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal)) { _logger.LogDebug("=> {repl}", replacement); previousData.FileReplacements[objectKind].Add(replacement); diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 42fc181..d58f328 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -1,10 +1,12 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Logging; using Penumbra.String; using System.Runtime.InteropServices; +using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer; using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; namespace MareSynchronos.PlayerData.Handlers; @@ -201,7 +203,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase _clearCts?.CancelDispose(); _clearCts = null; } - var chara = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Address; + var chara = (Character*)Address; var name = new ByteString(chara->GameObject.Name).ToString(); bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); if (nameChange) @@ -214,8 +216,11 @@ 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)); + + ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand); + ref var oh = ref chara->DrawData.Weapon(WeaponSlot.OffHand); + equipDiff |= CompareAndUpdateMainHand((Weapon*)mh.DrawObject); + equipDiff |= CompareAndUpdateOffHand((Weapon*)oh.DrawObject); if (equipDiff) Logger.LogTrace("Checking [{this}] equip data as human from draw obj, result: {diff}", this, equipDiff); @@ -227,8 +232,6 @@ 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); diff --git a/MareSynchronos/UI/UISharedService.cs b/MareSynchronos/UI/UISharedService.cs index 461a8f5..61594c4 100644 --- a/MareSynchronos/UI/UISharedService.cs +++ b/MareSynchronos/UI/UISharedService.cs @@ -632,7 +632,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.Text("Glamourer"); ImGui.SameLine(); - FontText(_glamourerExists ? check : cross, UiBuilder.IconFont, penumbraColor); + FontText(_glamourerExists ? check : cross, UiBuilder.IconFont, glamourerColor); ImGui.SameLine(); AttachToolTip($"Glamourer is " + (_glamourerExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -641,7 +641,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.Text("SimpleHeels"); ImGui.SameLine(); - FontText(_heelsExists ? check : cross, UiBuilder.IconFont, penumbraColor); + FontText(_heelsExists ? check : cross, UiBuilder.IconFont, heelsColor); ImGui.SameLine(); AttachToolTip($"SimpleHeels is " + (_heelsExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -649,7 +649,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.Text("Customize+"); ImGui.SameLine(); - FontText(_customizePlusExists ? check : cross, UiBuilder.IconFont, penumbraColor); + FontText(_customizePlusExists ? check : cross, UiBuilder.IconFont, customizeColor); ImGui.SameLine(); AttachToolTip($"Customize+ is " + (_customizePlusExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -657,7 +657,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.Text("Palette+"); ImGui.SameLine(); - FontText(_palettePlusExists ? check : cross, UiBuilder.IconFont, penumbraColor); + FontText(_palettePlusExists ? check : cross, UiBuilder.IconFont, paletteColor); ImGui.SameLine(); AttachToolTip($"Palette+ is " + (_palettePlusExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing(); @@ -665,7 +665,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ImGui.SameLine(); ImGui.Text("Honorific"); ImGui.SameLine(); - FontText(_honorificExists ? check : cross, UiBuilder.IconFont, penumbraColor); + FontText(_honorificExists ? check : cross, UiBuilder.IconFont, honorificColor); ImGui.SameLine(); AttachToolTip($"Honorific is " + (_honorificExists ? "available and up to date." : "unavailable or not up to date.")); ImGui.Spacing();