use penumbra ipc for getting character data
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Interop.FFXIV;
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
|
||||||
public unsafe struct WeaponDrawObject
|
|
||||||
{
|
|
||||||
[FieldOffset(0x00)] public RenderModel* RenderModel;
|
|
||||||
}
|
|
||||||
@@ -67,6 +67,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly FuncSubscriber<string, string, int, PenumbraApiEc> _penumbraRemoveTemporaryMod;
|
private readonly FuncSubscriber<string, string, int, PenumbraApiEc> _penumbraRemoveTemporaryMod;
|
||||||
private readonly FuncSubscriber<string> _penumbraResolveModDir;
|
private readonly FuncSubscriber<string> _penumbraResolveModDir;
|
||||||
private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths;
|
private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths;
|
||||||
|
private readonly ParamsFuncSubscriber<ushort, IReadOnlyDictionary<string, string[]>?[]> _penumbraResourcePaths;
|
||||||
private readonly SemaphoreSlim _redrawSemaphore = new(2);
|
private readonly SemaphoreSlim _redrawSemaphore = new(2);
|
||||||
private bool _customizePlusAvailable = false;
|
private bool _customizePlusAvailable = false;
|
||||||
private CancellationTokenSource _disposalCts = new();
|
private CancellationTokenSource _disposalCts = new();
|
||||||
@@ -103,6 +104,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Mediator.Publish(new PenumbraModSettingChangedMessage());
|
Mediator.Publish(new PenumbraModSettingChangedMessage());
|
||||||
});
|
});
|
||||||
_penumbraConvertTextureFile = Penumbra.Api.Ipc.ConvertTextureFile.Subscriber(pi);
|
_penumbraConvertTextureFile = Penumbra.Api.Ipc.ConvertTextureFile.Subscriber(pi);
|
||||||
|
_penumbraResourcePaths = Penumbra.Api.Ipc.GetGameObjectResourcePaths.Subscriber(pi);
|
||||||
|
|
||||||
_penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded);
|
_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);
|
logger.LogDebug("[{appid}] Calling On IPC: GlamourerRevertByName", applicationId);
|
||||||
_glamourerRevertByName.InvokeAction(name, LockCode);
|
_glamourerRevertByName.InvokeAction(name, LockCode);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Logger.LogWarning(ex, "Error during Glamourer RevertByName");
|
Logger.LogWarning(ex, "Error during Glamourer RevertByName");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,8 +576,24 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
Mediator.Publish(new ResumeScanMessage("TextureConversion"));
|
Mediator.Publish(new ResumeScanMessage("TextureConversion"));
|
||||||
|
|
||||||
var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync());
|
await _dalamudUtil.RunOnFrameworkThread(async () =>
|
||||||
_penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw);
|
{
|
||||||
|
var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||||
|
_penumbraRedrawObject.Invoke(gameObject!, RedrawType.Redraw);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<string, 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);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public partial class FileReplacement
|
|||||||
{
|
{
|
||||||
private readonly Lazy<string> _hashLazy;
|
private readonly Lazy<string> _hashLazy;
|
||||||
|
|
||||||
public FileReplacement(List<string> gamePaths, string filePath, FileCacheManager fileDbManager)
|
public FileReplacement(string[] gamePaths, string filePath, FileCacheManager fileDbManager)
|
||||||
{
|
{
|
||||||
GamePaths = gamePaths.Select(g => g.Replace('\\', '/')).ToHashSet(StringComparer.Ordinal);
|
GamePaths = gamePaths.Select(g => g.Replace('\\', '/')).ToHashSet(StringComparer.Ordinal);
|
||||||
ResolvedPath = filePath.Replace('\\', '/');
|
ResolvedPath = filePath.Replace('\\', '/');
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
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.API.Data.Enum;
|
||||||
using MareSynchronos.Interop;
|
using MareSynchronos.Interop;
|
||||||
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
|
||||||
using Weapon = MareSynchronos.Interop.FFXIV.Weapon;
|
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Globalization;
|
|
||||||
using MareSynchronos.PlayerData.Data;
|
using MareSynchronos.PlayerData.Data;
|
||||||
using MareSynchronos.PlayerData.Handlers;
|
using MareSynchronos.PlayerData.Handlers;
|
||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
|
||||||
using CharacterData = MareSynchronos.PlayerData.Data.CharacterData;
|
using CharacterData = MareSynchronos.PlayerData.Data.CharacterData;
|
||||||
|
|
||||||
namespace MareSynchronos.PlayerData.Factories;
|
namespace MareSynchronos.PlayerData.Factories;
|
||||||
@@ -104,190 +98,6 @@ public class PlayerDataFactory
|
|||||||
previousData.CustomizePlusScale = previousCustomize;
|
previousData.CustomizePlusScale = previousCustomize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void AddPlayerSpecificReplacements(Human* human, HashSet<string> forwardResolve, HashSet<string> 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<string> forwardResolve, HashSet<string> 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<string> forwardResolve, HashSet<string> 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<string> forwardResolve, HashSet<string> 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<string> 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<string> forwardResolve, HashSet<string> reverseResolve) BuildDataFromModel(ObjectKind objectKind, nint charaPointer, CancellationToken token)
|
|
||||||
{
|
|
||||||
HashSet<string> forwardResolve = new(StringComparer.Ordinal);
|
|
||||||
HashSet<string> 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<bool> CheckForNullDrawObject(IntPtr playerPointer)
|
private async Task<bool> CheckForNullDrawObject(IntPtr playerPointer)
|
||||||
{
|
{
|
||||||
return await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)).ConfigureAwait(false);
|
return await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)).ConfigureAwait(false);
|
||||||
@@ -332,10 +142,11 @@ public class PlayerDataFactory
|
|||||||
Stopwatch st = Stopwatch.StartNew();
|
Stopwatch st = Stopwatch.StartNew();
|
||||||
|
|
||||||
// gather static replacements from render model
|
// gather static replacements from render model
|
||||||
var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false);
|
var data = await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false);
|
||||||
Dictionary<string, List<string>> resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false);
|
if (data == null) throw new InvalidOperationException("Penumbra returned null data");
|
||||||
|
|
||||||
previousData.FileReplacements[objectKind] =
|
previousData.FileReplacements[objectKind] =
|
||||||
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)), FileReplacementComparer.Instance)
|
new HashSet<FileReplacement>(data[0]!.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)), FileReplacementComparer.Instance)
|
||||||
.Where(p => p.HasFileReplacement).ToHashSet();
|
.Where(p => p.HasFileReplacement).ToHashSet();
|
||||||
|
|
||||||
_logger.LogDebug("== Static Replacements ==");
|
_logger.LogDebug("== Static Replacements ==");
|
||||||
@@ -364,7 +175,7 @@ public class PlayerDataFactory
|
|||||||
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogDebug("== Transient Replacements ==");
|
_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);
|
_logger.LogDebug("=> {repl}", replacement);
|
||||||
previousData.FileReplacements[objectKind].Add(replacement);
|
previousData.FileReplacements[objectKind].Add(replacement);
|
||||||
|
|||||||
@@ -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;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
||||||
using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
|
using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
|
||||||
|
|
||||||
namespace MareSynchronos.PlayerData.Handlers;
|
namespace MareSynchronos.PlayerData.Handlers;
|
||||||
@@ -201,7 +203,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
_clearCts?.CancelDispose();
|
_clearCts?.CancelDispose();
|
||||||
_clearCts = null;
|
_clearCts = null;
|
||||||
}
|
}
|
||||||
var chara = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Address;
|
var chara = (Character*)Address;
|
||||||
var name = new ByteString(chara->GameObject.Name).ToString();
|
var name = new ByteString(chara->GameObject.Name).ToString();
|
||||||
bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal);
|
bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal);
|
||||||
if (nameChange)
|
if (nameChange)
|
||||||
@@ -214,8 +216,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
&& ((CharacterBase*)DrawObjectAddress)->GetModelType() == CharacterBase.ModelType.Human)
|
&& ((CharacterBase*)DrawObjectAddress)->GetModelType() == CharacterBase.ModelType.Human)
|
||||||
{
|
{
|
||||||
equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)DrawObjectAddress)->Head);
|
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)
|
if (equipDiff)
|
||||||
Logger.LogTrace("Checking [{this}] equip data as human from draw obj, result: {diff}", this, 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);
|
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
|
if (equipDiff && !_isOwnedObject && !_ignoreSendAfterRedraw) // send the message out immediately and cancel out, no reason to continue if not self
|
||||||
{
|
{
|
||||||
Logger.LogTrace("[{this}] Changed", this);
|
Logger.LogTrace("[{this}] Changed", this);
|
||||||
|
|||||||
@@ -632,7 +632,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text("Glamourer");
|
ImGui.Text("Glamourer");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
FontText(_glamourerExists ? check : cross, UiBuilder.IconFont, penumbraColor);
|
FontText(_glamourerExists ? check : cross, UiBuilder.IconFont, glamourerColor);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Glamourer is " + (_glamourerExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip($"Glamourer is " + (_glamourerExists ? "available and up to date." : "unavailable or not up to date."));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
@@ -641,7 +641,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text("SimpleHeels");
|
ImGui.Text("SimpleHeels");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
FontText(_heelsExists ? check : cross, UiBuilder.IconFont, penumbraColor);
|
FontText(_heelsExists ? check : cross, UiBuilder.IconFont, heelsColor);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"SimpleHeels is " + (_heelsExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip($"SimpleHeels is " + (_heelsExists ? "available and up to date." : "unavailable or not up to date."));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
@@ -649,7 +649,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text("Customize+");
|
ImGui.Text("Customize+");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
FontText(_customizePlusExists ? check : cross, UiBuilder.IconFont, penumbraColor);
|
FontText(_customizePlusExists ? check : cross, UiBuilder.IconFont, customizeColor);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Customize+ is " + (_customizePlusExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip($"Customize+ is " + (_customizePlusExists ? "available and up to date." : "unavailable or not up to date."));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
@@ -657,7 +657,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text("Palette+");
|
ImGui.Text("Palette+");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
FontText(_palettePlusExists ? check : cross, UiBuilder.IconFont, penumbraColor);
|
FontText(_palettePlusExists ? check : cross, UiBuilder.IconFont, paletteColor);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Palette+ is " + (_palettePlusExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip($"Palette+ is " + (_palettePlusExists ? "available and up to date." : "unavailable or not up to date."));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
@@ -665,7 +665,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text("Honorific");
|
ImGui.Text("Honorific");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
FontText(_honorificExists ? check : cross, UiBuilder.IconFont, penumbraColor);
|
FontText(_honorificExists ? check : cross, UiBuilder.IconFont, honorificColor);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
AttachToolTip($"Honorific is " + (_honorificExists ? "available and up to date." : "unavailable or not up to date."));
|
AttachToolTip($"Honorific is " + (_honorificExists ? "available and up to date." : "unavailable or not up to date."));
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|||||||
Reference in New Issue
Block a user