using Dalamud.Plugin; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.PlayerData.Handlers; using LightlessSync.Services; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; using System.Diagnostics; using System.Globalization; using Penumbra.Api.Helpers; using Penumbra.Api.IpcSubscribers; namespace LightlessSync.Interop.Ipc.Penumbra; public sealed class PenumbraResource : PenumbraBase { private readonly GetGameObjectResourcePaths _gameObjectResourcePaths; private readonly ResolveGameObjectPath _resolveGameObjectPath; private readonly ReverseResolveGameObjectPath _reverseResolveGameObjectPath; private readonly ResolvePlayerPathsAsync _resolvePlayerPaths; private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations; private readonly EventSubscriber _gameObjectResourcePathResolved; public PenumbraResource( ILogger logger, IDalamudPluginInterface pluginInterface, DalamudUtilService dalamudUtil, LightlessMediator mediator) : base(logger, pluginInterface, dalamudUtil, mediator) { _gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface); _resolveGameObjectPath = new ResolveGameObjectPath(pluginInterface); _reverseResolveGameObjectPath = new ReverseResolveGameObjectPath(pluginInterface); _resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface); _getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface); _gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded); } public override string Name => "Penumbra.Resources"; public async Task>?> GetCharacterDataAsync(ILogger logger, GameObjectHandler handler) { if (!IsAvailable) { return null; } var requestId = Guid.NewGuid(); var totalTimer = Stopwatch.StartNew(); logger.LogTrace("[{requestId}] Requesting Penumbra.GetGameObjectResourcePaths for {handler}", requestId, handler); var result = await DalamudUtil.RunOnFrameworkThread(() => { var idx = handler.GetGameObject()?.ObjectIndex; if (idx == null) { logger.LogTrace("[{requestId}] GetGameObjectResourcePaths aborted (missing object index) for {handler}", requestId, handler); return null; } logger.LogTrace("[{requestId}] Invoking Penumbra.GetGameObjectResourcePaths for index {index}", requestId, idx.Value); var invokeTimer = Stopwatch.StartNew(); var data = _gameObjectResourcePaths.Invoke(idx.Value)[0]; invokeTimer.Stop(); logger.LogTrace("[{requestId}] Penumbra.GetGameObjectResourcePaths returned {count} entries in {elapsedMs}ms", requestId, data?.Count ?? 0, invokeTimer.ElapsedMilliseconds); return data; }).ConfigureAwait(false); totalTimer.Stop(); logger.LogTrace("[{requestId}] Penumbra.GetGameObjectResourcePaths finished in {elapsedMs}ms (null: {isNull})", requestId, totalTimer.ElapsedMilliseconds, result is null); return result; } public string GetMetaManipulations() => IsAvailable ? _getPlayerMetaManipulations.Invoke() : string.Empty; public async Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forwardPaths, string[] reversePaths) { if (!IsAvailable) { return (Array.Empty(), Array.Empty()); } return await _resolvePlayerPaths.Invoke(forwardPaths, reversePaths).ConfigureAwait(false); } public string ResolveGameObjectPath(string gamePath, int gameObjectIndex) => IsAvailable ? _resolveGameObjectPath.Invoke(gamePath, gameObjectIndex) : gamePath; public string[] ReverseResolveGameObjectPath(string moddedPath, int gameObjectIndex) => IsAvailable ? _reverseResolveGameObjectPath.Invoke(moddedPath, gameObjectIndex) : Array.Empty(); private void HandleResourceLoaded(nint ptr, string gamePath, string resolvedPath) { if (ptr != nint.Zero && string.Compare(gamePath, resolvedPath, ignoreCase: true, CultureInfo.InvariantCulture) != 0) { Mediator.Publish(new PenumbraResourceLoadMessage(ptr, gamePath, resolvedPath)); } } protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current) { } public override void Dispose() { base.Dispose(); _gameObjectResourcePathResolved.Dispose(); } }