2025-08-22 02:19:48 +01:00
using Dalamud.Game.ClientState.Objects.Types ;
using Dalamud.Plugin ;
using Glamourer.Api.Helpers ;
using Glamourer.Api.IpcSubscribers ;
using MareSynchronos.MareConfiguration.Models ;
using MareSynchronos.PlayerData.Handlers ;
using MareSynchronos.Services ;
using MareSynchronos.Services.Mediator ;
using Microsoft.Extensions.Logging ;
namespace MareSynchronos.Interop.Ipc ;
public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase , IIpcCaller
{
private readonly ILogger < IpcCallerGlamourer > _logger ;
private readonly DalamudUtilService _dalamudUtil ;
private readonly MareMediator _mareMediator ;
private readonly RedrawManager _redrawManager ;
private readonly ApiVersion _glamourerApiVersions ;
private readonly ApplyState ? _glamourerApplyAll ;
private readonly GetStateBase64 ? _glamourerGetAllCustomization ;
private readonly RevertState _glamourerRevert ;
private readonly RevertStateName _glamourerRevertByName ;
private readonly UnlockState _glamourerUnlock ;
private readonly UnlockStateName _glamourerUnlockByName ;
private readonly EventSubscriber < nint > ? _glamourerStateChanged ;
private bool _pluginLoaded ;
private Version _pluginVersion ;
private bool _shownGlamourerUnavailable = false ;
private readonly uint LockCode = 0x626E7579 ;
public IpcCallerGlamourer ( ILogger < IpcCallerGlamourer > logger , IDalamudPluginInterface pi , DalamudUtilService dalamudUtil , MareMediator mareMediator ,
RedrawManager redrawManager ) : base ( logger , mareMediator )
{
_glamourerApiVersions = new ApiVersion ( pi ) ;
_glamourerGetAllCustomization = new GetStateBase64 ( pi ) ;
_glamourerApplyAll = new ApplyState ( pi ) ;
_glamourerRevert = new RevertState ( pi ) ;
_glamourerRevertByName = new RevertStateName ( pi ) ;
_glamourerUnlock = new UnlockState ( pi ) ;
_glamourerUnlockByName = new UnlockStateName ( pi ) ;
_logger = logger ;
_dalamudUtil = dalamudUtil ;
_mareMediator = mareMediator ;
_redrawManager = redrawManager ;
var plugin = PluginWatcherService . GetInitialPluginState ( pi , "Glamourer" ) ;
_pluginLoaded = plugin ? . IsLoaded ? ? false ;
_pluginVersion = plugin ? . Version ? ? new ( 0 , 0 , 0 , 0 ) ;
Mediator . SubscribeKeyed < PluginChangeMessage > ( this , "Glamourer" , ( msg ) = >
{
_pluginLoaded = msg . IsLoaded ;
_pluginVersion = msg . Version ;
CheckAPI ( ) ;
} ) ;
CheckAPI ( ) ;
_glamourerStateChanged = StateChanged . Subscriber ( pi , GlamourerChanged ) ;
_glamourerStateChanged . Enable ( ) ;
Mediator . Subscribe < DalamudLoginMessage > ( this , s = > _shownGlamourerUnavailable = false ) ;
}
protected override void Dispose ( bool disposing )
{
base . Dispose ( disposing ) ;
_redrawManager . Cancel ( ) ;
_glamourerStateChanged ? . Dispose ( ) ;
}
public bool APIAvailable { get ; private set ; }
public void CheckAPI ( )
{
bool apiAvailable = false ;
try
{
bool versionValid = _pluginLoaded & & _pluginVersion > = new Version ( 1 , 0 , 6 , 1 ) ;
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 ;
2025-08-22 21:26:51 +01:00
_mareMediator . Publish ( new NotificationMessage ( "Glamourer inactive" , "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Snowcloak. If you just updated Glamourer, ignore this message." ,
2025-08-22 02:19:48 +01:00
NotificationType . Error ) ) ;
}
}
}
public async Task ApplyAllAsync ( ILogger logger , GameObjectHandler handler , string? customization , Guid applicationId , CancellationToken token , bool allowImmediate = false )
{
if ( ! APIAvailable | | string . IsNullOrEmpty ( customization ) | | _dalamudUtil . IsZoning ) return ;
// Call immediately if possible
if ( allowImmediate & & _dalamudUtil . IsOnFrameworkThread & & ! await handler . IsBeingDrawnRunOnFrameworkAsync ( ) . ConfigureAwait ( false ) )
{
var gameObj = await _dalamudUtil . CreateGameObjectAsync ( handler . Address ) . ConfigureAwait ( false ) ;
if ( gameObj is ICharacter chara )
{
logger . LogDebug ( "[{appid}] Calling on IPC: GlamourerApplyAll" , applicationId ) ;
_glamourerApplyAll ! . Invoke ( customization , chara . ObjectIndex , LockCode ) ;
return ;
}
}
await _redrawManager . RedrawSemaphore . WaitAsync ( token ) . ConfigureAwait ( false ) ;
try
{
await _redrawManager . PenumbraRedrawInternalAsync ( logger , handler , applicationId , ( chara ) = >
{
try
{
logger . LogDebug ( "[{appid}] Calling on IPC: GlamourerApplyAll" , applicationId ) ;
_glamourerApplyAll ! . Invoke ( customization , chara . ObjectIndex , LockCode ) ;
}
catch ( Exception ex )
{
logger . LogWarning ( ex , "[{appid}] Failed to apply Glamourer data" , applicationId ) ;
}
} , token ) . ConfigureAwait ( false ) ;
}
finally
{
_redrawManager . RedrawSemaphore . Release ( ) ;
}
}
public async Task < string > GetCharacterCustomizationAsync ( IntPtr character )
{
if ( ! APIAvailable ) return string . Empty ;
try
{
return await _dalamudUtil . RunOnFrameworkThread ( ( ) = >
{
var gameObj = _dalamudUtil . CreateGameObject ( character ) ;
if ( gameObj is ICharacter c )
{
return _glamourerGetAllCustomization ! . Invoke ( c . ObjectIndex ) . Item2 ? ? string . Empty ;
}
return string . Empty ;
} ) . ConfigureAwait ( false ) ;
}
catch
{
return string . Empty ;
}
}
public async Task RevertAsync ( 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 ) = >
{
try
{
logger . LogDebug ( "[{appid}] Calling On IPC: GlamourerUnlock" , applicationId ) ;
_glamourerUnlock . Invoke ( chara . ObjectIndex , LockCode ) ;
logger . LogDebug ( "[{appid}] Calling On IPC: GlamourerRevert" , applicationId ) ;
_glamourerRevert . Invoke ( chara . ObjectIndex , LockCode ) ;
logger . LogDebug ( "[{appid}] Calling On IPC: PenumbraRedraw" , applicationId ) ;
_mareMediator . Publish ( new PenumbraRedrawCharacterMessage ( chara ) ) ;
}
catch ( Exception ex )
{
logger . LogWarning ( ex , "[{appid}] Error during GlamourerRevert" , applicationId ) ;
}
} , token ) . ConfigureAwait ( false ) ;
}
finally
{
_redrawManager . RedrawSemaphore . Release ( ) ;
}
}
public void RevertNow ( ILogger logger , Guid applicationId , int objectIndex )
{
if ( ( ! APIAvailable ) | | _dalamudUtil . IsZoning ) return ;
logger . LogTrace ( "[{applicationId}] Immediately reverting object index {objId}" , applicationId , objectIndex ) ;
_glamourerRevert . Invoke ( objectIndex , LockCode ) ;
}
public void RevertByNameNow ( ILogger logger , Guid applicationId , string name )
{
if ( ( ! APIAvailable ) | | _dalamudUtil . IsZoning ) return ;
logger . LogTrace ( "[{applicationId}] Immediately reverting {name}" , applicationId , name ) ;
_glamourerRevertByName . Invoke ( name , LockCode ) ;
}
public async Task RevertByNameAsync ( ILogger logger , string name , Guid applicationId )
{
if ( ( ! APIAvailable ) | | _dalamudUtil . IsZoning ) return ;
await _dalamudUtil . RunOnFrameworkThread ( ( ) = >
{
RevertByName ( logger , name , applicationId ) ;
} ) . ConfigureAwait ( false ) ;
}
public void RevertByName ( ILogger logger , string name , Guid applicationId )
{
if ( ( ! APIAvailable ) | | _dalamudUtil . IsZoning ) return ;
try
{
logger . LogDebug ( "[{appid}] Calling On IPC: GlamourerRevertByName" , applicationId ) ;
_glamourerRevertByName . Invoke ( name , LockCode ) ;
logger . LogDebug ( "[{appid}] Calling On IPC: GlamourerUnlockName" , applicationId ) ;
_glamourerUnlockByName . Invoke ( name , LockCode ) ;
}
catch ( Exception ex )
{
_logger . LogWarning ( ex , "Error during Glamourer RevertByName" ) ;
}
}
private void GlamourerChanged ( nint address )
{
_mareMediator . Publish ( new GlamourerChangedMessage ( address ) ) ;
}
}