work done on the ipc
This commit is contained in:
193
LightlessSync/Interop/Ipc/Framework/IpcFramework.cs
Normal file
193
LightlessSync/Interop/Ipc/Framework/IpcFramework.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc.Framework;
|
||||||
|
|
||||||
|
public enum IpcConnectionState
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
MissingPlugin = 1,
|
||||||
|
VersionMismatch = 2,
|
||||||
|
PluginDisabled = 3,
|
||||||
|
NotReady = 4,
|
||||||
|
Available = 5,
|
||||||
|
Error = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record IpcServiceDescriptor(string InternalName, string DisplayName, Version MinimumVersion)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
=> $"{DisplayName} (>= {MinimumVersion})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IIpcService : IDisposable
|
||||||
|
{
|
||||||
|
IpcServiceDescriptor Descriptor { get; }
|
||||||
|
IpcConnectionState State { get; }
|
||||||
|
IDalamudPluginInterface PluginInterface { get; }
|
||||||
|
bool APIAvailable { get; }
|
||||||
|
void CheckAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IIpcInterop : IDisposable
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
void OnConnectionStateChanged(IpcConnectionState state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IpcInteropBase : IIpcInterop
|
||||||
|
{
|
||||||
|
protected IpcInteropBase(ILogger logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
|
protected IpcConnectionState State { get; private set; } = IpcConnectionState.Unknown;
|
||||||
|
|
||||||
|
protected bool IsAvailable => State == IpcConnectionState.Available;
|
||||||
|
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
public void OnConnectionStateChanged(IpcConnectionState state)
|
||||||
|
{
|
||||||
|
if (State == state)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previous = State;
|
||||||
|
State = state;
|
||||||
|
HandleStateChange(previous, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void HandleStateChange(IpcConnectionState previous, IpcConnectionState current);
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IpcServiceBase : DisposableMediatorSubscriberBase, IIpcService
|
||||||
|
{
|
||||||
|
private readonly List<IIpcInterop> _interops = new();
|
||||||
|
|
||||||
|
protected IpcServiceBase(
|
||||||
|
ILogger logger,
|
||||||
|
LightlessMediator mediator,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
IpcServiceDescriptor descriptor) : base(logger, mediator)
|
||||||
|
{
|
||||||
|
PluginInterface = pluginInterface;
|
||||||
|
Descriptor = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IDalamudPluginInterface PluginInterface { get; }
|
||||||
|
|
||||||
|
IDalamudPluginInterface IIpcService.PluginInterface => PluginInterface;
|
||||||
|
|
||||||
|
protected IpcServiceDescriptor Descriptor { get; }
|
||||||
|
|
||||||
|
IpcServiceDescriptor IIpcService.Descriptor => Descriptor;
|
||||||
|
|
||||||
|
public IpcConnectionState State { get; private set; } = IpcConnectionState.Unknown;
|
||||||
|
|
||||||
|
public bool APIAvailable => State == IpcConnectionState.Available;
|
||||||
|
|
||||||
|
public virtual void CheckAPI()
|
||||||
|
{
|
||||||
|
var newState = EvaluateState();
|
||||||
|
UpdateState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual IpcConnectionState EvaluateState()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var plugin = PluginInterface.InstalledPlugins
|
||||||
|
.FirstOrDefault(p => string.Equals(p.InternalName, Descriptor.InternalName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (plugin == null)
|
||||||
|
{
|
||||||
|
return IpcConnectionState.MissingPlugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.Version < Descriptor.MinimumVersion)
|
||||||
|
{
|
||||||
|
return IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPluginEnabled())
|
||||||
|
{
|
||||||
|
return IpcConnectionState.PluginDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPluginReady())
|
||||||
|
{
|
||||||
|
return IpcConnectionState.NotReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IpcConnectionState.Available;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(ex, "Failed to evaluate IPC state for {Service}", Descriptor.DisplayName);
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool IsPluginEnabled()
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
protected virtual bool IsPluginReady()
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
protected TInterop RegisterInterop<TInterop>(TInterop interop)
|
||||||
|
where TInterop : IIpcInterop
|
||||||
|
{
|
||||||
|
_interops.Add(interop);
|
||||||
|
interop.OnConnectionStateChanged(State);
|
||||||
|
return interop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateState(IpcConnectionState newState)
|
||||||
|
{
|
||||||
|
if (State == newState)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previous = State;
|
||||||
|
State = newState;
|
||||||
|
OnConnectionStateChanged(previous, newState);
|
||||||
|
|
||||||
|
foreach (var interop in _interops)
|
||||||
|
{
|
||||||
|
interop.OnConnectionStateChanged(newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
Logger.LogTrace("{Service} IPC state transitioned from {Previous} to {Current}", Descriptor.DisplayName, previous, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = _interops.Count - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
_interops[i].Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_interops.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace LightlessSync.Interop.Ipc;
|
|
||||||
|
|
||||||
public interface IIpcCaller : IDisposable
|
|
||||||
{
|
|
||||||
bool APIAvailable { get; }
|
|
||||||
void CheckAPI();
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,19 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using LightlessSync.API.Dto.CharaData;
|
using LightlessSync.API.Dto.CharaData;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerBrio : IIpcCaller
|
public sealed class IpcCallerBrio : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor BrioDescriptor = new("Brio", "Brio", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ILogger<IpcCallerBrio> _logger;
|
private readonly ILogger<IpcCallerBrio> _logger;
|
||||||
private readonly DalamudUtilService _dalamudUtilService;
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
private readonly ICallGateSubscriber<(int, int)> _brioApiVersion;
|
private readonly ICallGateSubscriber<(int, int)> _brioApiVersion;
|
||||||
@@ -25,10 +29,8 @@ public sealed class IpcCallerBrio : IIpcCaller
|
|||||||
private readonly ICallGateSubscriber<bool> _brioFreezePhysics;
|
private readonly ICallGateSubscriber<bool> _brioFreezePhysics;
|
||||||
|
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; }
|
|
||||||
|
|
||||||
public IpcCallerBrio(ILogger<IpcCallerBrio> logger, IDalamudPluginInterface dalamudPluginInterface,
|
public IpcCallerBrio(ILogger<IpcCallerBrio> logger, IDalamudPluginInterface dalamudPluginInterface,
|
||||||
DalamudUtilService dalamudUtilService)
|
DalamudUtilService dalamudUtilService, LightlessMediator mediator) : base(logger, mediator, dalamudPluginInterface, BrioDescriptor)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dalamudUtilService = dalamudUtilService;
|
_dalamudUtilService = dalamudUtilService;
|
||||||
@@ -46,19 +48,6 @@ public sealed class IpcCallerBrio : IIpcCaller
|
|||||||
CheckAPI();
|
CheckAPI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckAPI()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var version = _brioApiVersion.InvokeFunc();
|
|
||||||
APIAvailable = (version.Item1 == 2 && version.Item2 >= 0);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IGameObject?> SpawnActorAsync()
|
public async Task<IGameObject?> SpawnActorAsync()
|
||||||
{
|
{
|
||||||
if (!APIAvailable) return null;
|
if (!APIAvailable) return null;
|
||||||
@@ -140,7 +129,30 @@ public sealed class IpcCallerBrio : IIpcCaller
|
|||||||
return await _dalamudUtilService.RunOnFrameworkThread(() => _brioSetPoseFromJson.InvokeFunc(gameObject, applicablePose.ToJsonString(), false)).ConfigureAwait(false);
|
return await _dalamudUtilService.RunOnFrameworkThread(() => _brioSetPoseFromJson.InvokeFunc(gameObject, applicablePose.ToJsonString(), false)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
protected override IpcConnectionState EvaluateState()
|
||||||
{
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var version = _brioApiVersion.InvokeFunc();
|
||||||
|
return version.Item1 == 2 && version.Item2 >= 0
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to query Brio IPC version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -9,8 +10,10 @@ using System.Text;
|
|||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerCustomize : IIpcCaller
|
public sealed class IpcCallerCustomize : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor CustomizeDescriptor = new("CustomizePlus", "Customize+", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion;
|
private readonly ICallGateSubscriber<(int, int)> _customizePlusApiVersion;
|
||||||
private readonly ICallGateSubscriber<ushort, (int, Guid?)> _customizePlusGetActiveProfile;
|
private readonly ICallGateSubscriber<ushort, (int, Guid?)> _customizePlusGetActiveProfile;
|
||||||
private readonly ICallGateSubscriber<Guid, (int, string?)> _customizePlusGetProfileById;
|
private readonly ICallGateSubscriber<Guid, (int, string?)> _customizePlusGetProfileById;
|
||||||
@@ -23,7 +26,7 @@ public sealed class IpcCallerCustomize : IIpcCaller
|
|||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
|
|
||||||
public IpcCallerCustomize(ILogger<IpcCallerCustomize> logger, IDalamudPluginInterface dalamudPluginInterface,
|
public IpcCallerCustomize(ILogger<IpcCallerCustomize> logger, IDalamudPluginInterface dalamudPluginInterface,
|
||||||
DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator)
|
DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator) : base(logger, lightlessMediator, dalamudPluginInterface, CustomizeDescriptor)
|
||||||
{
|
{
|
||||||
_customizePlusApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("CustomizePlus.General.GetApiVersion");
|
_customizePlusApiVersion = dalamudPluginInterface.GetIpcSubscriber<(int, int)>("CustomizePlus.General.GetApiVersion");
|
||||||
_customizePlusGetActiveProfile = dalamudPluginInterface.GetIpcSubscriber<ushort, (int, Guid?)>("CustomizePlus.Profile.GetActiveProfileIdOnCharacter");
|
_customizePlusGetActiveProfile = dalamudPluginInterface.GetIpcSubscriber<ushort, (int, Guid?)>("CustomizePlus.Profile.GetActiveProfileIdOnCharacter");
|
||||||
@@ -41,8 +44,6 @@ public sealed class IpcCallerCustomize : IIpcCaller
|
|||||||
CheckAPI();
|
CheckAPI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; } = false;
|
|
||||||
|
|
||||||
public async Task RevertAsync(nint character)
|
public async Task RevertAsync(nint character)
|
||||||
{
|
{
|
||||||
if (!APIAvailable) return;
|
if (!APIAvailable) return;
|
||||||
@@ -113,16 +114,25 @@ public sealed class IpcCallerCustomize : IIpcCaller
|
|||||||
return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale));
|
return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckAPI()
|
protected override IpcConnectionState EvaluateState()
|
||||||
{
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var version = _customizePlusApiVersion.InvokeFunc();
|
var version = _customizePlusApiVersion.InvokeFunc();
|
||||||
APIAvailable = (version.Item1 == 6 && version.Item2 >= 0);
|
return version.Item1 == 6 && version.Item2 >= 0
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
APIAvailable = false;
|
Logger.LogDebug(ex, "Failed to query Customize+ API version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +142,14 @@ public sealed class IpcCallerCustomize : IIpcCaller
|
|||||||
_lightlessMediator.Publish(new CustomizePlusMessage(obj?.Address ?? null));
|
_lightlessMediator.Publish(new CustomizePlusMessage(obj?.Address ?? null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_customizePlusOnScaleUpdate.Unsubscribe(OnCustomizePlusScaleChange);
|
_customizePlusOnScaleUpdate.Unsubscribe(OnCustomizePlusScaleChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Api.Helpers;
|
using Glamourer.Api.Helpers;
|
||||||
using Glamourer.Api.IpcSubscribers;
|
using Glamourer.Api.IpcSubscribers;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.LightlessConfiguration.Models;
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
@@ -10,8 +11,9 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcCaller
|
public sealed class IpcCallerGlamourer : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor GlamourerDescriptor = new("Glamourer", "Glamourer", new Version(1, 3, 0, 10));
|
||||||
private readonly ILogger<IpcCallerGlamourer> _logger;
|
private readonly ILogger<IpcCallerGlamourer> _logger;
|
||||||
private readonly IDalamudPluginInterface _pi;
|
private readonly IDalamudPluginInterface _pi;
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
@@ -31,7 +33,7 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC
|
|||||||
private readonly uint LockCode = 0x6D617265;
|
private readonly uint LockCode = 0x6D617265;
|
||||||
|
|
||||||
public IpcCallerGlamourer(ILogger<IpcCallerGlamourer> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator,
|
public IpcCallerGlamourer(ILogger<IpcCallerGlamourer> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator,
|
||||||
RedrawManager redrawManager) : base(logger, lightlessMediator)
|
RedrawManager redrawManager) : base(logger, lightlessMediator, pi, GlamourerDescriptor)
|
||||||
{
|
{
|
||||||
_glamourerApiVersions = new ApiVersion(pi);
|
_glamourerApiVersions = new ApiVersion(pi);
|
||||||
_glamourerGetAllCustomization = new GetStateBase64(pi);
|
_glamourerGetAllCustomization = new GetStateBase64(pi);
|
||||||
@@ -62,47 +64,6 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC
|
|||||||
_glamourerStateChanged?.Dispose();
|
_glamourerStateChanged?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; }
|
|
||||||
|
|
||||||
public void CheckAPI()
|
|
||||||
{
|
|
||||||
bool apiAvailable = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool versionValid = (_pi.InstalledPlugins
|
|
||||||
.FirstOrDefault(p => string.Equals(p.InternalName, "Glamourer", StringComparison.OrdinalIgnoreCase))
|
|
||||||
?.Version ?? new Version(0, 0, 0, 0)) >= new Version(1, 3, 0, 10);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var version = _glamourerApiVersions.Invoke();
|
|
||||||
if (version is { Major: 1, Minor: >= 1 } && versionValid)
|
|
||||||
{
|
|
||||||
apiAvailable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
_shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable;
|
|
||||||
|
|
||||||
APIAvailable = apiAvailable;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = apiAvailable;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!apiAvailable && !_shownGlamourerUnavailable)
|
|
||||||
{
|
|
||||||
_shownGlamourerUnavailable = true;
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Lightless. If you just updated Glamourer, ignore this message.",
|
|
||||||
NotificationType.Error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false)
|
public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false)
|
||||||
{
|
{
|
||||||
if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;
|
if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;
|
||||||
@@ -210,6 +171,49 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IpcConnectionState EvaluateState()
|
||||||
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var version = _glamourerApiVersions.Invoke();
|
||||||
|
return version is { Major: 1, Minor: >= 1 }
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to query Glamourer API version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
base.OnConnectionStateChanged(previous, current);
|
||||||
|
|
||||||
|
if (current == IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
_shownGlamourerUnavailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_shownGlamourerUnavailable || current == IpcConnectionState.Unknown)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_shownGlamourerUnavailable = true;
|
||||||
|
_lightlessMediator.Publish(new NotificationMessage("Glamourer inactive",
|
||||||
|
"Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Lightless. If you just updated Glamourer, ignore this message.",
|
||||||
|
NotificationType.Error));
|
||||||
|
}
|
||||||
|
|
||||||
private void GlamourerChanged(nint address)
|
private void GlamourerChanged(nint address)
|
||||||
{
|
{
|
||||||
_lightlessMediator.Publish(new GlamourerChangedMessage(address));
|
_lightlessMediator.Publish(new GlamourerChangedMessage(address));
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerHeels : IIpcCaller
|
public sealed class IpcCallerHeels : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor HeelsDescriptor = new("SimpleHeels", "Simple Heels", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ILogger<IpcCallerHeels> _logger;
|
private readonly ILogger<IpcCallerHeels> _logger;
|
||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
@@ -18,6 +21,7 @@ public sealed class IpcCallerHeels : IIpcCaller
|
|||||||
private readonly ICallGateSubscriber<int, object?> _heelsUnregisterPlayer;
|
private readonly ICallGateSubscriber<int, object?> _heelsUnregisterPlayer;
|
||||||
|
|
||||||
public IpcCallerHeels(ILogger<IpcCallerHeels> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator)
|
public IpcCallerHeels(ILogger<IpcCallerHeels> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, LightlessMediator lightlessMediator)
|
||||||
|
: base(logger, lightlessMediator, pi, HeelsDescriptor)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_lightlessMediator = lightlessMediator;
|
_lightlessMediator = lightlessMediator;
|
||||||
@@ -32,8 +36,26 @@ public sealed class IpcCallerHeels : IIpcCaller
|
|||||||
|
|
||||||
CheckAPI();
|
CheckAPI();
|
||||||
}
|
}
|
||||||
|
protected override IpcConnectionState EvaluateState()
|
||||||
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; } = false;
|
try
|
||||||
|
{
|
||||||
|
return _heelsGetApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 1 }
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to query SimpleHeels API version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void HeelsOffsetChange(string offset)
|
private void HeelsOffsetChange(string offset)
|
||||||
{
|
{
|
||||||
@@ -74,20 +96,14 @@ public sealed class IpcCallerHeels : IIpcCaller
|
|||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckAPI()
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
try
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
{
|
{
|
||||||
APIAvailable = _heelsGetApiVersion.InvokeFunc() is { Item1: 2, Item2: >= 1 };
|
return;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -8,8 +9,10 @@ using System.Text;
|
|||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerHonorific : IIpcCaller
|
public sealed class IpcCallerHonorific : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor HonorificDescriptor = new("Honorific", "Honorific", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion;
|
private readonly ICallGateSubscriber<(uint major, uint minor)> _honorificApiVersion;
|
||||||
private readonly ICallGateSubscriber<int, object> _honorificClearCharacterTitle;
|
private readonly ICallGateSubscriber<int, object> _honorificClearCharacterTitle;
|
||||||
private readonly ICallGateSubscriber<object> _honorificDisposing;
|
private readonly ICallGateSubscriber<object> _honorificDisposing;
|
||||||
@@ -22,7 +25,7 @@ public sealed class IpcCallerHonorific : IIpcCaller
|
|||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
|
|
||||||
public IpcCallerHonorific(ILogger<IpcCallerHonorific> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
public IpcCallerHonorific(ILogger<IpcCallerHonorific> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||||
LightlessMediator lightlessMediator)
|
LightlessMediator lightlessMediator) : base(logger, lightlessMediator, pi, HonorificDescriptor)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_lightlessMediator = lightlessMediator;
|
_lightlessMediator = lightlessMediator;
|
||||||
@@ -41,23 +44,14 @@ public sealed class IpcCallerHonorific : IIpcCaller
|
|||||||
|
|
||||||
CheckAPI();
|
CheckAPI();
|
||||||
}
|
}
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
public bool APIAvailable { get; private set; } = false;
|
|
||||||
|
|
||||||
public void CheckAPI()
|
|
||||||
{
|
{
|
||||||
try
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
{
|
{
|
||||||
APIAvailable = _honorificApiVersion.InvokeFunc() is { Item1: 3, Item2: >= 1 };
|
return;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_honorificLocalCharacterTitleChanged.Unsubscribe(OnHonorificLocalCharacterTitleChanged);
|
_honorificLocalCharacterTitleChanged.Unsubscribe(OnHonorificLocalCharacterTitleChanged);
|
||||||
_honorificDisposing.Unsubscribe(OnHonorificDisposing);
|
_honorificDisposing.Unsubscribe(OnHonorificDisposing);
|
||||||
_honorificReady.Unsubscribe(OnHonorificReady);
|
_honorificReady.Unsubscribe(OnHonorificReady);
|
||||||
@@ -113,6 +107,27 @@ public sealed class IpcCallerHonorific : IIpcCaller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IpcConnectionState EvaluateState()
|
||||||
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _honorificApiVersion.InvokeFunc() is { Item1: 3, Item2: >= 1 }
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to query Honorific API version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnHonorificDisposing()
|
private void OnHonorificDisposing()
|
||||||
{
|
{
|
||||||
_lightlessMediator.Publish(new HonorificMessage(string.Empty));
|
_lightlessMediator.Publish(new HonorificMessage(string.Empty));
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerMoodles : IIpcCaller
|
public sealed class IpcCallerMoodles : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor MoodlesDescriptor = new("Moodles", "Moodles", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<int> _moodlesApiVersion;
|
private readonly ICallGateSubscriber<int> _moodlesApiVersion;
|
||||||
private readonly ICallGateSubscriber<IPlayerCharacter, object> _moodlesOnChange;
|
private readonly ICallGateSubscriber<IPlayerCharacter, object> _moodlesOnChange;
|
||||||
private readonly ICallGateSubscriber<nint, string> _moodlesGetStatus;
|
private readonly ICallGateSubscriber<nint, string> _moodlesGetStatus;
|
||||||
@@ -19,7 +22,7 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
|||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
|
|
||||||
public IpcCallerMoodles(ILogger<IpcCallerMoodles> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
public IpcCallerMoodles(ILogger<IpcCallerMoodles> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||||
LightlessMediator lightlessMediator)
|
LightlessMediator lightlessMediator) : base(logger, lightlessMediator, pi, MoodlesDescriptor)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
@@ -41,22 +44,14 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
|||||||
_lightlessMediator.Publish(new MoodlesMessage(character.Address));
|
_lightlessMediator.Publish(new MoodlesMessage(character.Address));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; } = false;
|
protected override void Dispose(bool disposing)
|
||||||
|
|
||||||
public void CheckAPI()
|
|
||||||
{
|
{
|
||||||
try
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
{
|
{
|
||||||
APIAvailable = _moodlesApiVersion.InvokeFunc() == 3;
|
return;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_moodlesOnChange.Unsubscribe(OnMoodlesChange);
|
_moodlesOnChange.Unsubscribe(OnMoodlesChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +96,25 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
|||||||
_logger.LogWarning(e, "Could not Set Moodles Status");
|
_logger.LogWarning(e, "Could not Set Moodles Status");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IpcConnectionState EvaluateState()
|
||||||
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _moodlesApiVersion.InvokeFunc() == 3
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to query Moodles API version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.Interop.Ipc.Penumbra;
|
||||||
using LightlessSync.LightlessConfiguration.Models;
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
@@ -8,520 +10,210 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
using Penumbra.Api.IpcSubscribers;
|
using Penumbra.Api.IpcSubscribers;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCaller
|
public sealed class IpcCallerPenumbra : IpcServiceBase
|
||||||
{
|
{
|
||||||
private readonly IDalamudPluginInterface _pi;
|
private static readonly IpcServiceDescriptor PenumbraDescriptor = new("Penumbra", "Penumbra", new Version(1, 2, 0, 22));
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
|
||||||
private readonly LightlessMediator _lightlessMediator;
|
|
||||||
private readonly RedrawManager _redrawManager;
|
|
||||||
private readonly ActorObjectService _actorObjectService;
|
|
||||||
private bool _shownPenumbraUnavailable = false;
|
|
||||||
private string? _penumbraModDirectory;
|
|
||||||
public string? ModDirectory
|
|
||||||
{
|
|
||||||
get => _penumbraModDirectory;
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
if (!string.Equals(_penumbraModDirectory, value, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_penumbraModDirectory = value;
|
|
||||||
_lightlessMediator.Publish(new PenumbraDirectoryChangedMessage(_penumbraModDirectory));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<IntPtr, bool> _penumbraRedrawRequests = new();
|
private readonly PenumbraCollections _collections;
|
||||||
private readonly ConcurrentDictionary<IntPtr, byte> _trackedActors = new();
|
private readonly PenumbraResource _resources;
|
||||||
|
private readonly PenumbraRedraw _redraw;
|
||||||
|
private readonly PenumbraTexture _textures;
|
||||||
|
|
||||||
private readonly EventSubscriber _penumbraDispose;
|
|
||||||
private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved;
|
|
||||||
private readonly EventSubscriber _penumbraInit;
|
|
||||||
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _penumbraModSettingChanged;
|
|
||||||
private readonly EventSubscriber<nint, int> _penumbraObjectIsRedrawn;
|
|
||||||
|
|
||||||
private readonly AddTemporaryMod _penumbraAddTemporaryMod;
|
|
||||||
private readonly AssignTemporaryCollection _penumbraAssignTemporaryCollection;
|
|
||||||
private readonly ConvertTextureFile _penumbraConvertTextureFile;
|
|
||||||
private readonly CreateTemporaryCollection _penumbraCreateNamedTemporaryCollection;
|
|
||||||
private readonly GetEnabledState _penumbraEnabled;
|
private readonly GetEnabledState _penumbraEnabled;
|
||||||
private readonly GetPlayerMetaManipulations _penumbraGetMetaManipulations;
|
private readonly GetModDirectory _penumbraGetModDirectory;
|
||||||
private readonly RedrawObject _penumbraRedraw;
|
private readonly EventSubscriber _penumbraInit;
|
||||||
private readonly DeleteTemporaryCollection _penumbraRemoveTemporaryCollection;
|
private readonly EventSubscriber _penumbraDispose;
|
||||||
private readonly RemoveTemporaryMod _penumbraRemoveTemporaryMod;
|
private readonly EventSubscriber<ModSettingChange, Guid, string, bool> _penumbraModSettingChanged;
|
||||||
private readonly GetModDirectory _penumbraResolveModDir;
|
|
||||||
private readonly ResolvePlayerPathsAsync _penumbraResolvePaths;
|
|
||||||
private readonly GetGameObjectResourcePaths _penumbraResourcePaths;
|
|
||||||
//private readonly GetPlayerResourcePaths _penumbraPlayerResourcePaths;
|
|
||||||
private readonly GetCollections _penumbraGetCollections;
|
|
||||||
private readonly ConcurrentDictionary<Guid, string> _activeTemporaryCollections = new();
|
|
||||||
private int _performedInitialCleanup;
|
|
||||||
|
|
||||||
public IpcCallerPenumbra(ILogger<IpcCallerPenumbra> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
private bool _shownPenumbraUnavailable;
|
||||||
LightlessMediator lightlessMediator, RedrawManager redrawManager, ActorObjectService actorObjectService) : base(logger, lightlessMediator)
|
private string? _modDirectory;
|
||||||
|
|
||||||
|
public IpcCallerPenumbra(
|
||||||
|
ILogger<IpcCallerPenumbra> logger,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
LightlessMediator mediator,
|
||||||
|
RedrawManager redrawManager,
|
||||||
|
ActorObjectService actorObjectService) : base(logger, mediator, pluginInterface, PenumbraDescriptor)
|
||||||
{
|
{
|
||||||
_pi = pi;
|
_penumbraEnabled = new GetEnabledState(pluginInterface);
|
||||||
_dalamudUtil = dalamudUtil;
|
_penumbraGetModDirectory = new GetModDirectory(pluginInterface);
|
||||||
_lightlessMediator = lightlessMediator;
|
_penumbraInit = Initialized.Subscriber(pluginInterface, HandlePenumbraInitialized);
|
||||||
_redrawManager = redrawManager;
|
_penumbraDispose = Disposed.Subscriber(pluginInterface, HandlePenumbraDisposed);
|
||||||
_actorObjectService = actorObjectService;
|
_penumbraModSettingChanged = ModSettingChanged.Subscriber(pluginInterface, HandlePenumbraModSettingChanged);
|
||||||
_penumbraInit = Initialized.Subscriber(pi, PenumbraInit);
|
|
||||||
_penumbraDispose = Disposed.Subscriber(pi, PenumbraDispose);
|
|
||||||
_penumbraResolveModDir = new GetModDirectory(pi);
|
|
||||||
_penumbraRedraw = new RedrawObject(pi);
|
|
||||||
_penumbraObjectIsRedrawn = GameObjectRedrawn.Subscriber(pi, RedrawEvent);
|
|
||||||
_penumbraGetMetaManipulations = new GetPlayerMetaManipulations(pi);
|
|
||||||
_penumbraRemoveTemporaryMod = new RemoveTemporaryMod(pi);
|
|
||||||
_penumbraAddTemporaryMod = new AddTemporaryMod(pi);
|
|
||||||
_penumbraCreateNamedTemporaryCollection = new CreateTemporaryCollection(pi);
|
|
||||||
_penumbraRemoveTemporaryCollection = new DeleteTemporaryCollection(pi);
|
|
||||||
_penumbraAssignTemporaryCollection = new AssignTemporaryCollection(pi);
|
|
||||||
_penumbraGetCollections = new GetCollections(pi);
|
|
||||||
_penumbraResolvePaths = new ResolvePlayerPathsAsync(pi);
|
|
||||||
_penumbraEnabled = new GetEnabledState(pi);
|
|
||||||
_penumbraModSettingChanged = ModSettingChanged.Subscriber(pi, (change, arg1, arg, b) =>
|
|
||||||
{
|
|
||||||
if (change == ModSettingChange.EnableState)
|
|
||||||
_lightlessMediator.Publish(new PenumbraModSettingChangedMessage());
|
|
||||||
});
|
|
||||||
_penumbraConvertTextureFile = new ConvertTextureFile(pi);
|
|
||||||
_penumbraResourcePaths = new GetGameObjectResourcePaths(pi);
|
|
||||||
//_penumbraPlayerResourcePaths = new GetPlayerResourcePaths(pi);
|
|
||||||
|
|
||||||
_penumbraGameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pi, ResourceLoaded);
|
_collections = RegisterInterop(new PenumbraCollections(logger, pluginInterface, dalamudUtil, mediator));
|
||||||
|
_resources = RegisterInterop(new PenumbraResource(logger, pluginInterface, dalamudUtil, mediator, actorObjectService));
|
||||||
|
_redraw = RegisterInterop(new PenumbraRedraw(logger, pluginInterface, dalamudUtil, mediator, redrawManager));
|
||||||
|
_textures = RegisterInterop(new PenumbraTexture(logger, pluginInterface, dalamudUtil, mediator, _redraw));
|
||||||
|
|
||||||
|
SubscribeMediatorEvents();
|
||||||
|
|
||||||
CheckAPI();
|
CheckAPI();
|
||||||
CheckModDirectory();
|
CheckModDirectory();
|
||||||
|
|
||||||
Mediator.Subscribe<PenumbraRedrawCharacterMessage>(this, (msg) =>
|
|
||||||
{
|
|
||||||
_penumbraRedraw.Invoke(msg.Character.ObjectIndex, RedrawType.AfterGPose);
|
|
||||||
});
|
|
||||||
|
|
||||||
Mediator.Subscribe<DalamudLoginMessage>(this, (msg) => _shownPenumbraUnavailable = false);
|
|
||||||
|
|
||||||
Mediator.Subscribe<ActorTrackedMessage>(this, msg =>
|
|
||||||
{
|
|
||||||
if (msg.Descriptor.Address != nint.Zero)
|
|
||||||
{
|
|
||||||
_trackedActors[(IntPtr)msg.Descriptor.Address] = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Mediator.Subscribe<ActorUntrackedMessage>(this, msg =>
|
|
||||||
{
|
|
||||||
if (msg.Descriptor.Address != nint.Zero)
|
|
||||||
{
|
|
||||||
_trackedActors.TryRemove((IntPtr)msg.Descriptor.Address, out _);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, msg =>
|
|
||||||
{
|
|
||||||
if (msg.GameObjectHandler.Address != nint.Zero)
|
|
||||||
{
|
|
||||||
_trackedActors[(IntPtr)msg.GameObjectHandler.Address] = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Mediator.Subscribe<GameObjectHandlerDestroyedMessage>(this, msg =>
|
|
||||||
{
|
|
||||||
if (msg.GameObjectHandler.Address != nint.Zero)
|
|
||||||
{
|
|
||||||
_trackedActors.TryRemove((IntPtr)msg.GameObjectHandler.Address, out _);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var descriptor in _actorObjectService.PlayerDescriptors)
|
|
||||||
{
|
|
||||||
if (descriptor.Address != nint.Zero)
|
|
||||||
{
|
|
||||||
_trackedActors[(IntPtr)descriptor.Address] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; } = false;
|
public string? ModDirectory
|
||||||
|
|
||||||
public void CheckAPI()
|
|
||||||
{
|
{
|
||||||
bool penumbraAvailable = false;
|
get => _modDirectory;
|
||||||
try
|
private set
|
||||||
{
|
{
|
||||||
var penumbraVersion = (_pi.InstalledPlugins
|
if (string.Equals(_modDirectory, value, StringComparison.Ordinal))
|
||||||
.FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase))
|
|
||||||
?.Version ?? new Version(0, 0, 0, 0));
|
|
||||||
penumbraAvailable = penumbraVersion >= new Version(1, 2, 0, 22);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
penumbraAvailable &= _penumbraEnabled.Invoke();
|
return;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
penumbraAvailable = false;
|
|
||||||
}
|
|
||||||
_shownPenumbraUnavailable = _shownPenumbraUnavailable && !penumbraAvailable;
|
|
||||||
APIAvailable = penumbraAvailable;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = penumbraAvailable;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!penumbraAvailable && !_shownPenumbraUnavailable)
|
|
||||||
{
|
|
||||||
_shownPenumbraUnavailable = true;
|
|
||||||
_lightlessMediator.Publish(new NotificationMessage("Penumbra inactive",
|
|
||||||
"Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Lightless. If you just updated Penumbra, ignore this message.",
|
|
||||||
NotificationType.Error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (APIAvailable)
|
_modDirectory = value;
|
||||||
{
|
Mediator.Publish(new PenumbraDirectoryChangedMessage(_modDirectory));
|
||||||
ScheduleTemporaryCollectionCleanup();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task AssignTemporaryCollectionAsync(ILogger logger, Guid collectionId, int objectIndex)
|
||||||
|
=> _collections.AssignTemporaryCollectionAsync(logger, collectionId, objectIndex);
|
||||||
|
|
||||||
|
public Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
|
||||||
|
=> _collections.CreateTemporaryCollectionAsync(logger, uid);
|
||||||
|
|
||||||
|
public Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collectionId)
|
||||||
|
=> _collections.RemoveTemporaryCollectionAsync(logger, applicationId, collectionId);
|
||||||
|
|
||||||
|
public Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collectionId, Dictionary<string, string> modPaths)
|
||||||
|
=> _collections.SetTemporaryModsAsync(logger, applicationId, collectionId, modPaths);
|
||||||
|
|
||||||
|
public Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collectionId, string manipulationData)
|
||||||
|
=> _collections.SetManipulationDataAsync(logger, applicationId, collectionId, manipulationData);
|
||||||
|
|
||||||
|
public Task<Dictionary<string, HashSet<string>>?> GetCharacterData(ILogger logger, GameObjectHandler handler)
|
||||||
|
=> _resources.GetCharacterDataAsync(logger, handler);
|
||||||
|
|
||||||
|
public string GetMetaManipulations()
|
||||||
|
=> _resources.GetMetaManipulations();
|
||||||
|
|
||||||
|
public Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse)
|
||||||
|
=> _resources.ResolvePathsAsync(forward, reverse);
|
||||||
|
|
||||||
|
public Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
||||||
|
=> _redraw.RedrawAsync(logger, handler, applicationId, token);
|
||||||
|
|
||||||
|
public Task ConvertTextureFiles(ILogger logger, IReadOnlyList<TextureConversionJob> jobs, IProgress<TextureConversionProgress>? progress, CancellationToken token)
|
||||||
|
=> _textures.ConvertTextureFilesAsync(logger, jobs, progress, token);
|
||||||
|
|
||||||
|
public Task ConvertTextureFileDirectAsync(TextureConversionJob job, CancellationToken token)
|
||||||
|
=> _textures.ConvertTextureFileDirectAsync(job, token);
|
||||||
|
|
||||||
public void CheckModDirectory()
|
public void CheckModDirectory()
|
||||||
{
|
{
|
||||||
if (!APIAvailable)
|
if (!APIAvailable)
|
||||||
{
|
{
|
||||||
ModDirectory = string.Empty;
|
ModDirectory = string.Empty;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ModDirectory = _penumbraResolveModDir!.Invoke().ToLowerInvariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ScheduleTemporaryCollectionCleanup()
|
|
||||||
{
|
|
||||||
if (Interlocked.Exchange(ref _performedInitialCleanup, 1) != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_ = Task.Run(CleanupTemporaryCollectionsAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CleanupTemporaryCollectionsAsync()
|
|
||||||
{
|
|
||||||
if (!APIAvailable)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var collections = await _dalamudUtil.RunOnFrameworkThread(() => _penumbraGetCollections.Invoke()).ConfigureAwait(false);
|
ModDirectory = _penumbraGetModDirectory.Invoke().ToLowerInvariant();
|
||||||
foreach (var (collectionId, name) in collections)
|
|
||||||
{
|
|
||||||
if (!IsLightlessCollectionName(name))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (_activeTemporaryCollections.ContainsKey(collectionId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Logger.LogDebug("Cleaning up stale temporary collection {CollectionName} ({CollectionId})", name, collectionId);
|
|
||||||
var deleteResult = await _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
var result = (PenumbraApiEc)_penumbraRemoveTemporaryCollection.Invoke(collectionId);
|
|
||||||
Logger.LogTrace("Cleanup RemoveTemporaryCollection result for {CollectionName} ({CollectionId}): {Result}", name, collectionId, result);
|
|
||||||
return result;
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
if (deleteResult == PenumbraApiEc.Success)
|
|
||||||
{
|
|
||||||
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogDebug("Skipped removing temporary collection {CollectionName} ({CollectionId}). Result: {Result}", name, collectionId, deleteResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "Failed to clean up Penumbra temporary collections");
|
Logger.LogWarning(ex, "Failed to resolve Penumbra mod directory");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsLightlessCollectionName(string? name)
|
protected override bool IsPluginEnabled()
|
||||||
=> !string.IsNullOrEmpty(name) && name.StartsWith("Lightless_", StringComparison.Ordinal);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _penumbraEnabled.Invoke();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnConnectionStateChanged(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
base.OnConnectionStateChanged(previous, current);
|
||||||
|
|
||||||
|
if (current == IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
_shownPenumbraUnavailable = false;
|
||||||
|
if (string.IsNullOrEmpty(ModDirectory))
|
||||||
|
{
|
||||||
|
CheckModDirectory();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModDirectory = string.Empty;
|
||||||
|
_redraw.CancelPendingRedraws();
|
||||||
|
|
||||||
|
if (_shownPenumbraUnavailable || current == IpcConnectionState.Unknown)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_shownPenumbraUnavailable = true;
|
||||||
|
Mediator.Publish(new NotificationMessage(
|
||||||
|
"Penumbra inactive",
|
||||||
|
"Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Lightless. If you just updated Penumbra, ignore this message.",
|
||||||
|
NotificationType.Error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeMediatorEvents()
|
||||||
|
{
|
||||||
|
Mediator.Subscribe<PenumbraRedrawCharacterMessage>(this, msg =>
|
||||||
|
{
|
||||||
|
_redraw.RequestImmediateRedraw(msg.Character.ObjectIndex, RedrawType.AfterGPose);
|
||||||
|
});
|
||||||
|
|
||||||
|
Mediator.Subscribe<DalamudLoginMessage>(this, _ => _shownPenumbraUnavailable = false);
|
||||||
|
|
||||||
|
Mediator.Subscribe<ActorTrackedMessage>(this, msg => _resources.TrackActor(msg.Descriptor.Address));
|
||||||
|
Mediator.Subscribe<ActorUntrackedMessage>(this, msg => _resources.UntrackActor(msg.Descriptor.Address));
|
||||||
|
Mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, msg => _resources.TrackActor(msg.GameObjectHandler.Address));
|
||||||
|
Mediator.Subscribe<GameObjectHandlerDestroyedMessage>(this, msg => _resources.UntrackActor(msg.GameObjectHandler.Address));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePenumbraInitialized()
|
||||||
|
{
|
||||||
|
Mediator.Publish(new PenumbraInitializedMessage());
|
||||||
|
CheckModDirectory();
|
||||||
|
_redraw.RequestImmediateRedraw(0, RedrawType.Redraw);
|
||||||
|
CheckAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePenumbraDisposed()
|
||||||
|
{
|
||||||
|
_redraw.CancelPendingRedraws();
|
||||||
|
ModDirectory = string.Empty;
|
||||||
|
Mediator.Publish(new PenumbraDisposedMessage());
|
||||||
|
CheckAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePenumbraModSettingChanged(ModSettingChange change, Guid _, string __, bool ___)
|
||||||
|
{
|
||||||
|
if (change == ModSettingChange.EnableState)
|
||||||
|
{
|
||||||
|
Mediator.Publish(new PenumbraModSettingChangedMessage());
|
||||||
|
CheckAPI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
||||||
_redrawManager.Cancel();
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_penumbraModSettingChanged.Dispose();
|
_penumbraModSettingChanged.Dispose();
|
||||||
_penumbraGameObjectResourcePathResolved.Dispose();
|
|
||||||
_penumbraDispose.Dispose();
|
_penumbraDispose.Dispose();
|
||||||
_penumbraInit.Dispose();
|
_penumbraInit.Dispose();
|
||||||
_penumbraObjectIsRedrawn.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AssignTemporaryCollectionAsync(ILogger logger, Guid collName, int idx)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return;
|
|
||||||
|
|
||||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx, forceAssignment: true);
|
|
||||||
logger.LogTrace("Assigning Temp Collection {collName} to index {idx}, Success: {ret}", collName, idx, retAssign);
|
|
||||||
return collName;
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ConvertTextureFiles(ILogger logger, IReadOnlyList<TextureConversionJob> jobs, IProgress<TextureConversionProgress>? progress, CancellationToken token)
|
|
||||||
{
|
|
||||||
if (!APIAvailable || jobs.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lightlessMediator.Publish(new HaltScanMessage(nameof(ConvertTextureFiles)));
|
|
||||||
|
|
||||||
var totalJobs = jobs.Count;
|
|
||||||
var completedJobs = 0;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var job in jobs)
|
|
||||||
{
|
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress?.Report(new TextureConversionProgress(completedJobs, totalJobs, job));
|
|
||||||
|
|
||||||
logger.LogInformation("Converting texture {Input} -> {Output} ({Target})", job.InputFile, job.OutputFile, job.TargetType);
|
|
||||||
var convertTask = _penumbraConvertTextureFile.Invoke(job.InputFile, job.OutputFile, job.TargetType, job.IncludeMipMaps);
|
|
||||||
await convertTask.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (convertTask.IsCompletedSuccessfully && job.DuplicateTargets is { Count: > 0 })
|
|
||||||
{
|
|
||||||
foreach (var duplicate in job.DuplicateTargets)
|
|
||||||
{
|
|
||||||
logger.LogInformation("Synchronizing duplicate {Duplicate}", duplicate);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Copy(job.OutputFile, duplicate, overwrite: true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, "Failed to copy duplicate {Duplicate}", duplicate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
completedJobs++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new ResumeScanMessage(nameof(ConvertTextureFiles)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (completedJobs > 0 && !token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await _dalamudUtil.RunOnFrameworkThread(async () =>
|
|
||||||
{
|
|
||||||
var player = await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false);
|
|
||||||
if (player == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gameObject = await _dalamudUtil.CreateGameObjectAsync(player).ConfigureAwait(false);
|
|
||||||
_penumbraRedraw.Invoke(gameObject!.ObjectIndex, setting: RedrawType.Redraw);
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return Guid.Empty;
|
|
||||||
|
|
||||||
var (collectionId, collectionName) = await _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
var collName = "Lightless_" + uid;
|
|
||||||
_penumbraCreateNamedTemporaryCollection.Invoke(collName, collName, out var collId);
|
|
||||||
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
|
|
||||||
return (collId, collName);
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
if (collectionId != Guid.Empty)
|
|
||||||
{
|
|
||||||
_activeTemporaryCollections[collectionId] = collectionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return collectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Dictionary<string, HashSet<string>>?> GetCharacterData(ILogger logger, GameObjectHandler handler)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) 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)[0];
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetMetaManipulations()
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return string.Empty;
|
|
||||||
return _penumbraGetMetaManipulations.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
|
||||||
{
|
|
||||||
if (!APIAvailable || _dalamudUtil.IsZoning) return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
|
||||||
await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) =>
|
|
||||||
{
|
|
||||||
logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId);
|
|
||||||
_penumbraRedraw!.Invoke(chara.ObjectIndex, setting: RedrawType.Redraw);
|
|
||||||
|
|
||||||
}, token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_redrawManager.RedrawSemaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collId)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return;
|
|
||||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
logger.LogTrace("[{applicationId}] Removing temp collection for {collId}", applicationId, collId);
|
|
||||||
var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collId);
|
|
||||||
logger.LogTrace("[{applicationId}] RemoveTemporaryCollection: {ret2}", applicationId, ret2);
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
if (collId != Guid.Empty)
|
|
||||||
{
|
|
||||||
_activeTemporaryCollections.TryRemove(collId, out _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse)
|
|
||||||
{
|
|
||||||
return await _penumbraResolvePaths.Invoke(forward, reverse).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ConvertTextureFileDirectAsync(TextureConversionJob job, CancellationToken token)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return;
|
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
await _penumbraConvertTextureFile.Invoke(job.InputFile, job.OutputFile, job.TargetType, job.IncludeMipMaps)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (job.DuplicateTargets is { Count: > 0 })
|
|
||||||
{
|
|
||||||
foreach (var duplicate in job.DuplicateTargets)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Copy(job.OutputFile, duplicate, overwrite: true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogDebug(ex, "Failed to copy duplicate {Duplicate} for texture conversion", duplicate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collId, string manipulationData)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return;
|
|
||||||
|
|
||||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData);
|
|
||||||
var retAdd = _penumbraAddTemporaryMod.Invoke("LightlessChara_Meta", collId, [], manipulationData, 0);
|
|
||||||
logger.LogTrace("[{applicationId}] Setting temp meta mod for {collId}, Success: {ret}", applicationId, collId, retAdd);
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collId, Dictionary<string, string> modPaths)
|
|
||||||
{
|
|
||||||
if (!APIAvailable) return;
|
|
||||||
|
|
||||||
await _dalamudUtil.RunOnFrameworkThread(() =>
|
|
||||||
{
|
|
||||||
foreach (var mod in modPaths)
|
|
||||||
{
|
|
||||||
logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value);
|
|
||||||
}
|
|
||||||
var retRemove = _penumbraRemoveTemporaryMod.Invoke("LightlessChara_Files", collId, 0);
|
|
||||||
logger.LogTrace("[{applicationId}] Removing temp files mod for {collId}, Success: {ret}", applicationId, collId, retRemove);
|
|
||||||
var retAdd = _penumbraAddTemporaryMod.Invoke("LightlessChara_Files", collId, modPaths, string.Empty, 0);
|
|
||||||
logger.LogTrace("[{applicationId}] Setting temp files mod for {collId}, Success: {ret}", applicationId, collId, retAdd);
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
|
||||||
{
|
|
||||||
bool wasRequested = false;
|
|
||||||
if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest) && redrawRequest)
|
|
||||||
{
|
|
||||||
_penumbraRedrawRequests[objectAddress] = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lightlessMediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
|
|
||||||
{
|
|
||||||
if (ptr == IntPtr.Zero)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_trackedActors.ContainsKey(ptr))
|
|
||||||
{
|
|
||||||
var descriptor = _actorObjectService.PlayerDescriptors.FirstOrDefault(d => d.Address == ptr);
|
|
||||||
if (descriptor.Address != nint.Zero)
|
|
||||||
{
|
|
||||||
_trackedActors[ptr] = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Compare(arg1, arg2, ignoreCase: true, System.Globalization.CultureInfo.InvariantCulture) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_lightlessMediator.Publish(new PenumbraResourceLoadMessage(ptr, arg1, arg2));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PenumbraDispose()
|
|
||||||
{
|
|
||||||
_redrawManager.Cancel();
|
|
||||||
_lightlessMediator.Publish(new PenumbraDisposedMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PenumbraInit()
|
|
||||||
{
|
|
||||||
APIAvailable = true;
|
|
||||||
ModDirectory = _penumbraResolveModDir.Invoke();
|
|
||||||
_lightlessMediator.Publish(new PenumbraInitializedMessage());
|
|
||||||
ScheduleTemporaryCollectionCleanup();
|
|
||||||
_penumbraRedraw!.Invoke(0, setting: RedrawType.Redraw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace LightlessSync.Interop.Ipc;
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
public sealed class IpcCallerPetNames : IIpcCaller
|
public sealed class IpcCallerPetNames : IpcServiceBase
|
||||||
{
|
{
|
||||||
|
private static readonly IpcServiceDescriptor PetRenamerDescriptor = new("PetRenamer", "Pet Renamer", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ILogger<IpcCallerPetNames> _logger;
|
private readonly ILogger<IpcCallerPetNames> _logger;
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
@@ -24,7 +27,7 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
|||||||
private readonly ICallGateSubscriber<ushort, object> _clearPlayerData;
|
private readonly ICallGateSubscriber<ushort, object> _clearPlayerData;
|
||||||
|
|
||||||
public IpcCallerPetNames(ILogger<IpcCallerPetNames> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
public IpcCallerPetNames(ILogger<IpcCallerPetNames> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil,
|
||||||
LightlessMediator lightlessMediator)
|
LightlessMediator lightlessMediator) : base(logger, lightlessMediator, pi, PetRenamerDescriptor)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
@@ -46,25 +49,6 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
|||||||
|
|
||||||
CheckAPI();
|
CheckAPI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool APIAvailable { get; private set; } = false;
|
|
||||||
|
|
||||||
public void CheckAPI()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
APIAvailable = _enabled?.InvokeFunc() ?? false;
|
|
||||||
if (APIAvailable)
|
|
||||||
{
|
|
||||||
APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 4, Item2: >= 0 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
APIAvailable = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPetNicknamesReady()
|
private void OnPetNicknamesReady()
|
||||||
{
|
{
|
||||||
CheckAPI();
|
CheckAPI();
|
||||||
@@ -76,6 +60,34 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
|||||||
_lightlessMediator.Publish(new PetNamesMessage(string.Empty));
|
_lightlessMediator.Publish(new PetNamesMessage(string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IpcConnectionState EvaluateState()
|
||||||
|
{
|
||||||
|
var state = base.EvaluateState();
|
||||||
|
if (state != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var enabled = _enabled?.InvokeFunc() ?? false;
|
||||||
|
if (!enabled)
|
||||||
|
{
|
||||||
|
return IpcConnectionState.PluginDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
var version = _apiVersion?.InvokeFunc() ?? (0u, 0u);
|
||||||
|
return version.Item1 == 4 && version.Item2 >= 0
|
||||||
|
? IpcConnectionState.Available
|
||||||
|
: IpcConnectionState.VersionMismatch;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogDebug(ex, "Failed to query Pet Renamer API version");
|
||||||
|
return IpcConnectionState.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string GetLocalNames()
|
public string GetLocalNames()
|
||||||
{
|
{
|
||||||
if (!APIAvailable) return string.Empty;
|
if (!APIAvailable) return string.Empty;
|
||||||
@@ -149,8 +161,14 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
|||||||
_lightlessMediator.Publish(new PetNamesMessage(data));
|
_lightlessMediator.Publish(new PetNamesMessage(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_petnamesReady.Unsubscribe(OnPetNicknamesReady);
|
_petnamesReady.Unsubscribe(OnPetNicknamesReady);
|
||||||
_petnamesDisposing.Unsubscribe(OnPetNicknamesDispose);
|
_petnamesDisposing.Unsubscribe(OnPetNicknamesDispose);
|
||||||
_playerDataChanged.Unsubscribe(OnLocalPetNicknamesDataChange);
|
_playerDataChanged.Unsubscribe(OnLocalPetNicknamesDataChange);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using System;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
@@ -14,9 +15,7 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
|||||||
private readonly ILogger<IpcProvider> _logger;
|
private readonly ILogger<IpcProvider> _logger;
|
||||||
private readonly IDalamudPluginInterface _pi;
|
private readonly IDalamudPluginInterface _pi;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
private ICallGateProvider<string, IGameObject, bool>? _loadFileProvider;
|
private readonly List<IpcRegister> _ipcRegisters = [];
|
||||||
private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProvider;
|
|
||||||
private ICallGateProvider<List<nint>>? _handledGameAddresses;
|
|
||||||
private readonly List<GameObjectHandler> _activeGameObjectHandlers = [];
|
private readonly List<GameObjectHandler> _activeGameObjectHandlers = [];
|
||||||
|
|
||||||
public LightlessMediator Mediator { get; init; }
|
public LightlessMediator Mediator { get; init; }
|
||||||
@@ -44,12 +43,9 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
|||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Starting IpcProviderService");
|
_logger.LogInformation("Starting IpcProviderService");
|
||||||
_loadFileProvider = _pi.GetIpcProvider<string, IGameObject, bool>("LightlessSync.LoadMcdf");
|
_ipcRegisters.Add(RegisterFunc<string, IGameObject, bool>("LightlessSync.LoadMcdf", LoadMcdf));
|
||||||
_loadFileProvider.RegisterFunc(LoadMcdf);
|
_ipcRegisters.Add(RegisterFunc<string, IGameObject, Task<bool>>("LightlessSync.LoadMcdfAsync", LoadMcdfAsync));
|
||||||
_loadFileAsyncProvider = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("LightlessSync.LoadMcdfAsync");
|
_ipcRegisters.Add(RegisterFunc("LightlessSync.GetHandledAddresses", GetHandledAddresses));
|
||||||
_loadFileAsyncProvider.RegisterFunc(LoadMcdfAsync);
|
|
||||||
_handledGameAddresses = _pi.GetIpcProvider<List<nint>>("LightlessSync.GetHandledAddresses");
|
|
||||||
_handledGameAddresses.RegisterFunc(GetHandledAddresses);
|
|
||||||
_logger.LogInformation("Started IpcProviderService");
|
_logger.LogInformation("Started IpcProviderService");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -57,9 +53,11 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
|||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Stopping IpcProvider Service");
|
_logger.LogDebug("Stopping IpcProvider Service");
|
||||||
_loadFileProvider?.UnregisterFunc();
|
foreach (var register in _ipcRegisters)
|
||||||
_loadFileAsyncProvider?.UnregisterFunc();
|
{
|
||||||
_handledGameAddresses?.UnregisterFunc();
|
register.Dispose();
|
||||||
|
}
|
||||||
|
_ipcRegisters.Clear();
|
||||||
Mediator.UnsubscribeAll(this);
|
Mediator.UnsubscribeAll(this);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -89,4 +87,40 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
return _activeGameObjectHandlers.Where(g => g.Address != nint.Zero).Select(g => g.Address).Distinct().ToList();
|
return _activeGameObjectHandlers.Where(g => g.Address != nint.Zero).Select(g => g.Address).Distinct().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IpcRegister RegisterFunc(string label, Func<List<nint>> handler)
|
||||||
|
{
|
||||||
|
var provider = _pi.GetIpcProvider<List<nint>>(label);
|
||||||
|
provider.RegisterFunc(handler);
|
||||||
|
return new IpcRegister(provider.UnregisterFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IpcRegister RegisterFunc<T1, T2, TRet>(string label, Func<T1, T2, TRet> handler)
|
||||||
|
{
|
||||||
|
var provider = _pi.GetIpcProvider<T1, T2, TRet>(label);
|
||||||
|
provider.RegisterFunc(handler);
|
||||||
|
return new IpcRegister(provider.UnregisterFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class IpcRegister : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Action _unregister;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public IpcRegister(Action unregister)
|
||||||
|
{
|
||||||
|
_unregister = unregister;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_unregister();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
LightlessSync/Interop/Ipc/Penumbra/PenumbraBase.cs
Normal file
27
LightlessSync/Interop/Ipc/Penumbra/PenumbraBase.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.Services;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
||||||
|
|
||||||
|
public abstract class PenumbraBase : IpcInteropBase
|
||||||
|
{
|
||||||
|
protected PenumbraBase(
|
||||||
|
ILogger logger,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
LightlessMediator mediator) : base(logger)
|
||||||
|
{
|
||||||
|
PluginInterface = pluginInterface;
|
||||||
|
DalamudUtil = dalamudUtil;
|
||||||
|
Mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IDalamudPluginInterface PluginInterface { get; }
|
||||||
|
|
||||||
|
protected DalamudUtilService DalamudUtil { get; }
|
||||||
|
|
||||||
|
protected LightlessMediator Mediator { get; }
|
||||||
|
}
|
||||||
197
LightlessSync/Interop/Ipc/Penumbra/PenumbraCollections.cs
Normal file
197
LightlessSync/Interop/Ipc/Penumbra/PenumbraCollections.cs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.Services;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Api.IpcSubscribers;
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
||||||
|
|
||||||
|
public sealed class PenumbraCollections : PenumbraBase
|
||||||
|
{
|
||||||
|
private readonly CreateTemporaryCollection _createNamedTemporaryCollection;
|
||||||
|
private readonly AssignTemporaryCollection _assignTemporaryCollection;
|
||||||
|
private readonly DeleteTemporaryCollection _removeTemporaryCollection;
|
||||||
|
private readonly AddTemporaryMod _addTemporaryMod;
|
||||||
|
private readonly RemoveTemporaryMod _removeTemporaryMod;
|
||||||
|
private readonly GetCollections _getCollections;
|
||||||
|
private readonly ConcurrentDictionary<Guid, string> _activeTemporaryCollections = new();
|
||||||
|
|
||||||
|
private int _cleanupScheduled;
|
||||||
|
|
||||||
|
public PenumbraCollections(
|
||||||
|
ILogger logger,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
LightlessMediator mediator) : base(logger, pluginInterface, dalamudUtil, mediator)
|
||||||
|
{
|
||||||
|
_createNamedTemporaryCollection = new CreateTemporaryCollection(pluginInterface);
|
||||||
|
_assignTemporaryCollection = new AssignTemporaryCollection(pluginInterface);
|
||||||
|
_removeTemporaryCollection = new DeleteTemporaryCollection(pluginInterface);
|
||||||
|
_addTemporaryMod = new AddTemporaryMod(pluginInterface);
|
||||||
|
_removeTemporaryMod = new RemoveTemporaryMod(pluginInterface);
|
||||||
|
_getCollections = new GetCollections(pluginInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Penumbra.Collections";
|
||||||
|
|
||||||
|
public async Task AssignTemporaryCollectionAsync(ILogger logger, Guid collectionId, int objectIndex)
|
||||||
|
{
|
||||||
|
if (!IsAvailable || collectionId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
var result = _assignTemporaryCollection.Invoke(collectionId, objectIndex, forceAssignment: true);
|
||||||
|
logger.LogTrace("Assigning Temp Collection {CollectionId} to index {ObjectIndex}, Success: {Result}", collectionId, objectIndex, result);
|
||||||
|
return result;
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Guid> CreateTemporaryCollectionAsync(ILogger logger, string uid)
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
return Guid.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (collectionId, collectionName) = await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
var name = $"Lightless_{uid}";
|
||||||
|
_createNamedTemporaryCollection.Invoke(name, name, out var tempCollectionId);
|
||||||
|
logger.LogTrace("Creating Temp Collection {CollectionName}, GUID: {CollectionId}", name, tempCollectionId);
|
||||||
|
return (tempCollectionId, name);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (collectionId != Guid.Empty)
|
||||||
|
{
|
||||||
|
_activeTemporaryCollections[collectionId] = collectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveTemporaryCollectionAsync(ILogger logger, Guid applicationId, Guid collectionId)
|
||||||
|
{
|
||||||
|
if (!IsAvailable || collectionId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
logger.LogTrace("[{ApplicationId}] Removing temp collection for {CollectionId}", applicationId, collectionId);
|
||||||
|
var result = _removeTemporaryCollection.Invoke(collectionId);
|
||||||
|
logger.LogTrace("[{ApplicationId}] RemoveTemporaryCollection: {Result}", applicationId, result);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetTemporaryModsAsync(ILogger logger, Guid applicationId, Guid collectionId, IReadOnlyDictionary<string, string> modPaths)
|
||||||
|
{
|
||||||
|
if (!IsAvailable || collectionId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
foreach (var mod in modPaths)
|
||||||
|
{
|
||||||
|
logger.LogTrace("[{ApplicationId}] Change: {From} => {To}", applicationId, mod.Key, mod.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeResult = _removeTemporaryMod.Invoke("LightlessChara_Files", collectionId, 0);
|
||||||
|
logger.LogTrace("[{ApplicationId}] Removing temp files mod for {CollectionId}, Success: {Result}", applicationId, collectionId, removeResult);
|
||||||
|
|
||||||
|
var addResult = _addTemporaryMod.Invoke("LightlessChara_Files", collectionId, new Dictionary<string, string>(modPaths), string.Empty, 0);
|
||||||
|
logger.LogTrace("[{ApplicationId}] Setting temp files mod for {CollectionId}, Success: {Result}", applicationId, collectionId, addResult);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetManipulationDataAsync(ILogger logger, Guid applicationId, Guid collectionId, string manipulationData)
|
||||||
|
{
|
||||||
|
if (!IsAvailable || collectionId == Guid.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
logger.LogTrace("[{ApplicationId}] Manip: {Data}", applicationId, manipulationData);
|
||||||
|
var result = _addTemporaryMod.Invoke("LightlessChara_Meta", collectionId, [], manipulationData, 0);
|
||||||
|
logger.LogTrace("[{ApplicationId}] Setting temp meta mod for {CollectionId}, Success: {Result}", applicationId, collectionId, result);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
if (current == IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
ScheduleCleanup();
|
||||||
|
}
|
||||||
|
else if (previous == IpcConnectionState.Available && current != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _cleanupScheduled, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScheduleCleanup()
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref _cleanupScheduled, 1) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(CleanupTemporaryCollectionsAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CleanupTemporaryCollectionsAsync()
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var collections = await DalamudUtil.RunOnFrameworkThread(() => _getCollections.Invoke()).ConfigureAwait(false);
|
||||||
|
foreach (var (collectionId, name) in collections)
|
||||||
|
{
|
||||||
|
if (!IsLightlessCollectionName(name) || _activeTemporaryCollections.ContainsKey(collectionId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug("Cleaning up stale temporary collection {CollectionName} ({CollectionId})", name, collectionId);
|
||||||
|
var deleteResult = await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
var result = (PenumbraApiEc)_removeTemporaryCollection.Invoke(collectionId);
|
||||||
|
Logger.LogTrace("Cleanup RemoveTemporaryCollection result for {CollectionName} ({CollectionId}): {Result}", name, collectionId, result);
|
||||||
|
return result;
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (deleteResult == PenumbraApiEc.Success)
|
||||||
|
{
|
||||||
|
_activeTemporaryCollections.TryRemove(collectionId, out _);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Skipped removing temporary collection {CollectionName} ({CollectionId}). Result: {Result}", name, collectionId, deleteResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Failed to clean up Penumbra temporary collections");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsLightlessCollectionName(string? name)
|
||||||
|
=> !string.IsNullOrEmpty(name) && name.StartsWith("Lightless_", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
81
LightlessSync/Interop/Ipc/Penumbra/PenumbraRedraw.cs
Normal file
81
LightlessSync/Interop/Ipc/Penumbra/PenumbraRedraw.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.PlayerData.Handlers;
|
||||||
|
using LightlessSync.Services;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Api.Helpers;
|
||||||
|
using Penumbra.Api.IpcSubscribers;
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
||||||
|
|
||||||
|
public sealed class PenumbraRedraw : PenumbraBase
|
||||||
|
{
|
||||||
|
private readonly RedrawManager _redrawManager;
|
||||||
|
private readonly RedrawObject _penumbraRedraw;
|
||||||
|
private readonly EventSubscriber<nint, int> _penumbraObjectIsRedrawn;
|
||||||
|
|
||||||
|
public PenumbraRedraw(
|
||||||
|
ILogger logger,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
LightlessMediator mediator,
|
||||||
|
RedrawManager redrawManager) : base(logger, pluginInterface, dalamudUtil, mediator)
|
||||||
|
{
|
||||||
|
_redrawManager = redrawManager;
|
||||||
|
|
||||||
|
_penumbraRedraw = new RedrawObject(pluginInterface);
|
||||||
|
_penumbraObjectIsRedrawn = GameObjectRedrawn.Subscriber(pluginInterface, HandlePenumbraRedrawEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Penumbra.Redraw";
|
||||||
|
|
||||||
|
public void CancelPendingRedraws()
|
||||||
|
=> _redrawManager.Cancel();
|
||||||
|
|
||||||
|
public void RequestImmediateRedraw(int objectIndex, RedrawType redrawType)
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_penumbraRedraw.Invoke(objectIndex, redrawType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (!IsAvailable || DalamudUtil.IsZoning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _redrawManager.RedrawSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||||
|
await _redrawManager.PenumbraRedrawInternalAsync(logger, handler, applicationId, chara =>
|
||||||
|
{
|
||||||
|
logger.LogDebug("[{ApplicationId}] Calling on IPC: PenumbraRedraw", applicationId);
|
||||||
|
_penumbraRedraw.Invoke(chara.ObjectIndex, RedrawType.Redraw);
|
||||||
|
}, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_redrawManager.RedrawSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePenumbraRedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||||
|
=> Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, false));
|
||||||
|
|
||||||
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_penumbraObjectIsRedrawn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
141
LightlessSync/Interop/Ipc/Penumbra/PenumbraResource.cs
Normal file
141
LightlessSync/Interop/Ipc/Penumbra/PenumbraResource.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.PlayerData.Handlers;
|
||||||
|
using LightlessSync.Services;
|
||||||
|
using LightlessSync.Services.ActorTracking;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Penumbra.Api.Helpers;
|
||||||
|
using Penumbra.Api.IpcSubscribers;
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
||||||
|
|
||||||
|
public sealed class PenumbraResource : PenumbraBase
|
||||||
|
{
|
||||||
|
private readonly ActorObjectService _actorObjectService;
|
||||||
|
private readonly GetGameObjectResourcePaths _gameObjectResourcePaths;
|
||||||
|
private readonly ResolvePlayerPathsAsync _resolvePlayerPaths;
|
||||||
|
private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations;
|
||||||
|
private readonly EventSubscriber<nint, string, string> _gameObjectResourcePathResolved;
|
||||||
|
private readonly ConcurrentDictionary<IntPtr, byte> _trackedActors = new();
|
||||||
|
|
||||||
|
public PenumbraResource(
|
||||||
|
ILogger logger,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
LightlessMediator mediator,
|
||||||
|
ActorObjectService actorObjectService) : base(logger, pluginInterface, dalamudUtil, mediator)
|
||||||
|
{
|
||||||
|
_actorObjectService = actorObjectService;
|
||||||
|
_gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface);
|
||||||
|
_resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface);
|
||||||
|
_getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface);
|
||||||
|
_gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded);
|
||||||
|
|
||||||
|
foreach (var descriptor in _actorObjectService.PlayerDescriptors)
|
||||||
|
{
|
||||||
|
TrackActor(descriptor.Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Penumbra.Resources";
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, HashSet<string>>?> GetCharacterDataAsync(ILogger logger, GameObjectHandler handler)
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await DalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
logger.LogTrace("Calling On IPC: Penumbra.GetGameObjectResourcePaths");
|
||||||
|
var idx = handler.GetGameObject()?.ObjectIndex;
|
||||||
|
if (idx == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _gameObjectResourcePaths.Invoke(idx.Value)[0];
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string>(), Array.Empty<string[]>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _resolvePlayerPaths.Invoke(forwardPaths, reversePaths).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TrackActor(nint address)
|
||||||
|
{
|
||||||
|
if (address != nint.Zero)
|
||||||
|
{
|
||||||
|
_trackedActors[(IntPtr)address] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UntrackActor(nint address)
|
||||||
|
{
|
||||||
|
if (address != nint.Zero)
|
||||||
|
{
|
||||||
|
_trackedActors.TryRemove((IntPtr)address, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleResourceLoaded(nint ptr, string resolvedPath, string gamePath)
|
||||||
|
{
|
||||||
|
if (ptr == nint.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_trackedActors.ContainsKey(ptr))
|
||||||
|
{
|
||||||
|
var descriptor = _actorObjectService.PlayerDescriptors.FirstOrDefault(d => d.Address == ptr);
|
||||||
|
if (descriptor.Address != nint.Zero)
|
||||||
|
{
|
||||||
|
_trackedActors[ptr] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Compare(resolvedPath, gamePath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mediator.Publish(new PenumbraResourceLoadMessage(ptr, resolvedPath, gamePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
if (current != IpcConnectionState.Available)
|
||||||
|
{
|
||||||
|
_trackedActors.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var descriptor in _actorObjectService.PlayerDescriptors)
|
||||||
|
{
|
||||||
|
TrackActor(descriptor.Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_gameObjectResourcePathResolved.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
121
LightlessSync/Interop/Ipc/Penumbra/PenumbraTexture.cs
Normal file
121
LightlessSync/Interop/Ipc/Penumbra/PenumbraTexture.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
using Dalamud.Plugin;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.Services;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Api.IpcSubscribers;
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc.Penumbra;
|
||||||
|
|
||||||
|
public sealed class PenumbraTexture : PenumbraBase
|
||||||
|
{
|
||||||
|
private readonly PenumbraRedraw _redrawFeature;
|
||||||
|
private readonly ConvertTextureFile _convertTextureFile;
|
||||||
|
|
||||||
|
public PenumbraTexture(
|
||||||
|
ILogger logger,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
LightlessMediator mediator,
|
||||||
|
PenumbraRedraw redrawFeature) : base(logger, pluginInterface, dalamudUtil, mediator)
|
||||||
|
{
|
||||||
|
_redrawFeature = redrawFeature;
|
||||||
|
_convertTextureFile = new ConvertTextureFile(pluginInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Penumbra.Textures";
|
||||||
|
|
||||||
|
public async Task ConvertTextureFilesAsync(ILogger logger, IReadOnlyList<TextureConversionJob> jobs, IProgress<TextureConversionProgress>? progress, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (!IsAvailable || jobs.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mediator.Publish(new HaltScanMessage(nameof(ConvertTextureFilesAsync)));
|
||||||
|
|
||||||
|
var totalJobs = jobs.Count;
|
||||||
|
var completedJobs = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var job in jobs)
|
||||||
|
{
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress?.Report(new TextureConversionProgress(completedJobs, totalJobs, job));
|
||||||
|
await ConvertSingleJobAsync(logger, job, token).ConfigureAwait(false);
|
||||||
|
completedJobs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Mediator.Publish(new ResumeScanMessage(nameof(ConvertTextureFilesAsync)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completedJobs > 0 && !token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await DalamudUtil.RunOnFrameworkThread(async () =>
|
||||||
|
{
|
||||||
|
var player = await DalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false);
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameObject = await DalamudUtil.CreateGameObjectAsync(player).ConfigureAwait(false);
|
||||||
|
if (gameObject == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_redrawFeature.RequestImmediateRedraw(gameObject.ObjectIndex, RedrawType.Redraw);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ConvertTextureFileDirectAsync(TextureConversionJob job, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ConvertSingleJobAsync(Logger, job, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ConvertSingleJobAsync(ILogger logger, TextureConversionJob job, CancellationToken token)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
logger.LogInformation("Converting texture {Input} -> {Output} ({Target})", job.InputFile, job.OutputFile, job.TargetType);
|
||||||
|
var convertTask = _convertTextureFile.Invoke(job.InputFile, job.OutputFile, job.TargetType, job.IncludeMipMaps);
|
||||||
|
await convertTask.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!convertTask.IsCompletedSuccessfully || job.DuplicateTargets is not { Count: > 0 })
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var duplicate in job.DuplicateTargets)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.LogInformation("Synchronizing duplicate {Duplicate}", duplicate);
|
||||||
|
File.Copy(job.OutputFile, duplicate, overwrite: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Failed to copy duplicate {Duplicate}", duplicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -228,7 +228,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton((s) => new IpcCallerPetNames(s.GetRequiredService<ILogger<IpcCallerPetNames>>(), pluginInterface,
|
collection.AddSingleton((s) => new IpcCallerPetNames(s.GetRequiredService<ILogger<IpcCallerPetNames>>(), pluginInterface,
|
||||||
s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessMediator>()));
|
s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessMediator>()));
|
||||||
collection.AddSingleton((s) => new IpcCallerBrio(s.GetRequiredService<ILogger<IpcCallerBrio>>(), pluginInterface,
|
collection.AddSingleton((s) => new IpcCallerBrio(s.GetRequiredService<ILogger<IpcCallerBrio>>(), pluginInterface,
|
||||||
s.GetRequiredService<DalamudUtilService>()));
|
s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessMediator>()));
|
||||||
collection.AddSingleton((s) => new IpcManager(s.GetRequiredService<ILogger<IpcManager>>(),
|
collection.AddSingleton((s) => new IpcManager(s.GetRequiredService<ILogger<IpcManager>>(),
|
||||||
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<IpcCallerPenumbra>(), s.GetRequiredService<IpcCallerGlamourer>(),
|
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<IpcCallerPenumbra>(), s.GetRequiredService<IpcCallerGlamourer>(),
|
||||||
s.GetRequiredService<IpcCallerCustomize>(), s.GetRequiredService<IpcCallerHeels>(), s.GetRequiredService<IpcCallerHonorific>(),
|
s.GetRequiredService<IpcCallerCustomize>(), s.GetRequiredService<IpcCallerHeels>(), s.GetRequiredService<IpcCallerHonorific>(),
|
||||||
|
|||||||
Reference in New Issue
Block a user