use penumbra ipc for getting character data

This commit is contained in:
rootdarkarchon
2023-10-05 01:13:04 +02:00
parent f371df7aa1
commit 61a2fe9168
9 changed files with 42 additions and 295 deletions

View File

@@ -8,7 +8,7 @@ public partial class FileReplacement
{
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);
ResolvedPath = filePath.Replace('\\', '/');

View File

@@ -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<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)
{
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<string, List<string>> 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<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();
_logger.LogDebug("== Static Replacements ==");
@@ -364,7 +175,7 @@ public class PlayerDataFactory
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(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);

View File

@@ -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);