diff --git a/.gitmodules b/.gitmodules
index 7879cd2..7ae9eb6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,6 @@
[submodule "OtterGui"]
path = OtterGui
url = https://github.com/Ottermandias/OtterGui
+[submodule "ffxiv_pictomancy"]
+ path = ffxiv_pictomancy
+ url = https://github.com/sourpuh/ffxiv_pictomancy
diff --git a/LightlessSync.sln b/LightlessSync.sln
index 8d92b53..55bddfd 100644
--- a/LightlessSync.sln
+++ b/LightlessSync.sln
@@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Penumbra.GameData", "Penumb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtterGui", "OtterGui\OtterGui.csproj", "{C77A2833-3FE4-405B-811D-439B1FF859D9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pictomancy", "ffxiv_pictomancy\Pictomancy\Pictomancy.csproj", "{825F17D8-2704-24F6-DF8B-2542AC92C765}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -102,6 +104,18 @@ Global
{C77A2833-3FE4-405B-811D-439B1FF859D9}.Release|x64.Build.0 = Release|x64
{C77A2833-3FE4-405B-811D-439B1FF859D9}.Release|x86.ActiveCfg = Release|x64
{C77A2833-3FE4-405B-811D-439B1FF859D9}.Release|x86.Build.0 = Release|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Debug|Any CPU.Build.0 = Debug|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Debug|x64.ActiveCfg = Debug|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Debug|x64.Build.0 = Debug|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Debug|x86.ActiveCfg = Debug|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Debug|x86.Build.0 = Debug|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Release|Any CPU.ActiveCfg = Release|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Release|Any CPU.Build.0 = Release|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Release|x64.ActiveCfg = Release|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Release|x64.Build.0 = Release|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Release|x86.ActiveCfg = Release|x64
+ {825F17D8-2704-24F6-DF8B-2542AC92C765}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/LightlessSync/FileCache/TransientResourceManager.cs b/LightlessSync/FileCache/TransientResourceManager.cs
index 7f982a3..6d77a97 100644
--- a/LightlessSync/FileCache/TransientResourceManager.cs
+++ b/LightlessSync/FileCache/TransientResourceManager.cs
@@ -426,7 +426,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
() =>
{
if (!string.IsNullOrEmpty(descriptor.HashedContentId) &&
- _actorObjectService.TryGetActorByHash(descriptor.HashedContentId, out var current) &&
+ _actorObjectService.TryGetValidatedActorByHash(descriptor.HashedContentId, out var current) &&
current.OwnedKind == kind)
{
return current.Address;
diff --git a/LightlessSync/LightlessSync.csproj b/LightlessSync/LightlessSync.csproj
index 975e935..8930cb6 100644
--- a/LightlessSync/LightlessSync.csproj
+++ b/LightlessSync/LightlessSync.csproj
@@ -79,6 +79,7 @@
+
diff --git a/LightlessSync/PlayerData/Factories/FileDownloadManagerFactory.cs b/LightlessSync/PlayerData/Factories/FileDownloadManagerFactory.cs
index f9b522a..e3697cf 100644
--- a/LightlessSync/PlayerData/Factories/FileDownloadManagerFactory.cs
+++ b/LightlessSync/PlayerData/Factories/FileDownloadManagerFactory.cs
@@ -1,7 +1,6 @@
using LightlessSync.FileCache;
using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator;
-using LightlessSync.Services.PairProcessing;
using LightlessSync.Services.TextureCompression;
using LightlessSync.WebAPI.Files;
using Microsoft.Extensions.Logging;
@@ -15,7 +14,6 @@ public class FileDownloadManagerFactory
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
private readonly FileCacheManager _fileCacheManager;
private readonly FileCompactor _fileCompactor;
- private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly LightlessConfigService _configService;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly TextureMetadataHelper _textureMetadataHelper;
@@ -26,7 +24,6 @@ public class FileDownloadManagerFactory
FileTransferOrchestrator fileTransferOrchestrator,
FileCacheManager fileCacheManager,
FileCompactor fileCompactor,
- PairProcessingLimiter pairProcessingLimiter,
LightlessConfigService configService,
TextureDownscaleService textureDownscaleService,
TextureMetadataHelper textureMetadataHelper)
@@ -36,7 +33,6 @@ public class FileDownloadManagerFactory
_fileTransferOrchestrator = fileTransferOrchestrator;
_fileCacheManager = fileCacheManager;
_fileCompactor = fileCompactor;
- _pairProcessingLimiter = pairProcessingLimiter;
_configService = configService;
_textureDownscaleService = textureDownscaleService;
_textureMetadataHelper = textureMetadataHelper;
@@ -50,7 +46,6 @@ public class FileDownloadManagerFactory
_fileTransferOrchestrator,
_fileCacheManager,
_fileCompactor,
- _pairProcessingLimiter,
_configService,
_textureDownscaleService,
_textureMetadataHelper);
diff --git a/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs b/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs
index 8d56b4f..829c737 100644
--- a/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs
+++ b/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs
@@ -94,6 +94,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None;
public byte Gender { get; private set; }
public string Name { get; private set; }
+ public uint EntityId { get; private set; } = uint.MaxValue;
public ObjectKind ObjectKind { get; }
public byte RaceId { get; private set; }
public byte TribeId { get; private set; }
@@ -142,6 +143,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
{
Address = IntPtr.Zero;
DrawObjectAddress = IntPtr.Zero;
+ EntityId = uint.MaxValue;
_haltProcessing = false;
}
@@ -171,13 +173,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
Address = _getAddress();
if (Address != IntPtr.Zero)
{
- var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject;
+ var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address;
+ var drawObjAddr = (IntPtr)gameObject->DrawObject;
DrawObjectAddress = drawObjAddr;
+ EntityId = gameObject->EntityId;
CurrentDrawCondition = DrawCondition.None;
}
else
{
DrawObjectAddress = IntPtr.Zero;
+ EntityId = uint.MaxValue;
CurrentDrawCondition = DrawCondition.DrawObjectZero;
}
diff --git a/LightlessSync/PlayerData/Pairs/IPairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/IPairHandlerAdapter.cs
new file mode 100644
index 0000000..c89d311
--- /dev/null
+++ b/LightlessSync/PlayerData/Pairs/IPairHandlerAdapter.cs
@@ -0,0 +1,27 @@
+using LightlessSync.API.Data;
+
+namespace LightlessSync.PlayerData.Pairs;
+
+///
+/// orchestrates the lifecycle of a paired character
+///
+public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
+{
+ new string Ident { get; }
+ bool Initialized { get; }
+ bool IsVisible { get; }
+ bool ScheduledForDeletion { get; set; }
+ CharacterData? LastReceivedCharacterData { get; }
+ long LastAppliedDataBytes { get; }
+ new string? PlayerName { get; }
+ string PlayerNameHash { get; }
+ uint PlayerCharacterId { get; }
+
+ void Initialize();
+ void ApplyData(CharacterData data);
+ void ApplyLastReceivedData(bool forced = false);
+ bool FetchPerformanceMetricsFromCache();
+ void LoadCachedCharacterData(CharacterData data);
+ void SetUploading(bool uploading);
+ void SetPaused(bool paused);
+}
diff --git a/LightlessSync/PlayerData/Pairs/IPairHandlerAdapterFactory.cs b/LightlessSync/PlayerData/Pairs/IPairHandlerAdapterFactory.cs
new file mode 100644
index 0000000..167b5bc
--- /dev/null
+++ b/LightlessSync/PlayerData/Pairs/IPairHandlerAdapterFactory.cs
@@ -0,0 +1,6 @@
+namespace LightlessSync.PlayerData.Pairs;
+
+public interface IPairHandlerAdapterFactory
+{
+ IPairHandlerAdapter Create(string ident);
+}
diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs
index 70f4f0b..925c42c 100644
--- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs
+++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs
@@ -16,43 +16,17 @@ using LightlessSync.Services.TextureCompression;
using LightlessSync.Utils;
using LightlessSync.WebAPI.Files;
using LightlessSync.WebAPI.Files.Models;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
+using FileReplacementDataComparer = LightlessSync.PlayerData.Data.FileReplacementDataComparer;
namespace LightlessSync.PlayerData.Pairs;
///
-/// orchestrates the lifecycle of a paired character
+/// handles lifecycle, visibility, queued data, character data for a paired user
///
-public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
-{
- string Ident { get; }
- bool Initialized { get; }
- bool IsVisible { get; }
- bool ScheduledForDeletion { get; set; }
- CharacterData? LastReceivedCharacterData { get; }
- long LastAppliedDataBytes { get; }
- string? PlayerName { get; }
- string PlayerNameHash { get; }
- uint PlayerCharacterId { get; }
-
- void Initialize();
- void ApplyData(CharacterData data);
- void ApplyLastReceivedData(bool forced = false);
- bool FetchPerformanceMetricsFromCache();
- void LoadCachedCharacterData(CharacterData data);
- void SetUploading(bool uploading);
- void SetPaused(bool paused);
-}
-
-public interface IPairHandlerAdapterFactory
-{
- IPairHandlerAdapter Create(string ident);
-}
-
-internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPairHandlerAdapter, IPairPerformanceSubject
+internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPairHandlerAdapter
{
private sealed record CombatData(Guid ApplicationId, CharacterData CharacterData, bool Forced);
@@ -70,14 +44,14 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private readonly PairStateCache _pairStateCache;
private readonly PairPerformanceMetricsCache _performanceMetricsCache;
private readonly PairManager _pairManager;
- private CancellationTokenSource? _applicationCancellationTokenSource = new();
+ private CancellationTokenSource? _applicationCancellationTokenSource;
private Guid _applicationId;
private Task? _applicationTask;
private CharacterData? _cachedData = null;
private GameObjectHandler? _charaHandler;
private readonly Dictionary _customizeIds = [];
private CombatData? _dataReceivedInDowntime;
- private CancellationTokenSource? _downloadCancellationTokenSource = new();
+ private CancellationTokenSource? _downloadCancellationTokenSource;
private bool _forceApplyMods = false;
private bool _forceFullReapply;
private Dictionary<(string GamePath, string? Hash), string>? _lastAppliedModdedPaths;
@@ -86,6 +60,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private Guid _penumbraCollection;
private readonly object _collectionGate = new();
private bool _redrawOnNextApplication = false;
+ private bool _explicitRedrawQueued;
private readonly object _initializationGate = new();
private readonly object _pauseLock = new();
private Task _pauseTransitionTask = Task.CompletedTask;
@@ -183,7 +158,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return;
}
- var user = GetPrimaryUserData();
if (LastAppliedDataBytes < 0 || LastAppliedDataTris < 0
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
{
@@ -441,9 +415,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return combined;
}
public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero;
- public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero
- ? uint.MaxValue
- : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId;
+ public uint PlayerCharacterId => _charaHandler?.EntityId ?? uint.MaxValue;
public string? PlayerName { get; private set; }
public string PlayerNameHash => Ident;
@@ -490,14 +462,14 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (shouldForce)
{
_forceApplyMods = true;
- _cachedData = null;
+ _forceFullReapply = true;
LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
LastAppliedApproximateVRAMBytes = -1;
LastAppliedApproximateEffectiveVRAMBytes = -1;
}
- var sanitized = CloneAndSanitizeLastReceived(out var dataHash);
+ var sanitized = CloneAndSanitizeLastReceived(out _);
if (sanitized is null)
{
Logger.LogTrace("Sanitized data null for {Ident}", Ident);
@@ -746,7 +718,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (characterData is null)
{
Logger.LogWarning("[BASE-{appBase}] Received null character data, skipping application for {handler}", applicationBase, GetLogIdentifier());
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -757,7 +729,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in combat, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -767,7 +739,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are performing music, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is performing", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -777,7 +749,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in an instance, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in instance", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -787,7 +759,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in a cutscene, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in a cutscene", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -797,7 +769,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in GPose, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in GPose", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -807,7 +779,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: Penumbra or Glamourer is not available, deferring application")));
Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while Penumbra/Glamourer unavailable, returning", applicationBase, GetLogIdentifier());
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
- SetUploading(isUploading: false);
+ SetUploading(false);
return;
}
@@ -828,7 +800,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
}
- SetUploading(isUploading: false);
+ SetUploading(false);
Logger.LogDebug("[BASE-{appbase}] Applying data for {player}, forceApplyCustomization: {forced}, forceApplyMods: {forceMods}", applicationBase, GetLogIdentifier(), forceApplyCustomization, _forceApplyMods);
Logger.LogDebug("[BASE-{appbase}] Hash for data is {newHash}, current cache hash is {oldHash}", applicationBase, characterData.DataHash.Value, _cachedData?.DataHash.Value ?? "NODATA");
@@ -850,10 +822,13 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_forceApplyMods = false;
}
+ _explicitRedrawQueued = false;
+
if (_redrawOnNextApplication && charaDataToUpdate.TryGetValue(ObjectKind.Player, out var player))
{
player.Add(PlayerChanges.ForcedRedraw);
_redrawOnNextApplication = false;
+ _explicitRedrawQueued = true;
}
if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges))
@@ -863,7 +838,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, GetPrimaryAliasOrUidSafe());
- var forceFullReapply = _forceFullReapply || forceApplyCustomization
+ var forceFullReapply = _forceFullReapply
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedDataTris < 0;
DownloadAndApplyCharacter(applicationBase, characterData.DeepClone(), charaDataToUpdate, forceFullReapply);
@@ -875,12 +850,12 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return $"{alias}:{PlayerName ?? string.Empty}:{(PlayerCharacter != nint.Zero ? "HasChar" : "NoChar")}";
}
- public void SetUploading(bool isUploading = true)
+ public void SetUploading(bool uploading)
{
- Logger.LogTrace("Setting {name} uploading {uploading}", GetPrimaryAliasOrUidSafe(), isUploading);
+ Logger.LogTrace("Setting {name} uploading {uploading}", GetPrimaryAliasOrUidSafe(), uploading);
if (_charaHandler != null)
{
- Mediator.Publish(new PlayerUploadingMessage(_charaHandler, isUploading));
+ Mediator.Publish(new PlayerUploadingMessage(_charaHandler, uploading));
}
}
@@ -904,7 +879,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
base.Dispose(disposing);
- SetUploading(isUploading: false);
+ SetUploading(false);
var name = PlayerName;
var user = GetPrimaryUserDataSafe();
var alias = GetPrimaryAliasOrUidSafe();
@@ -1046,6 +1021,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
break;
case PlayerChanges.ForcedRedraw:
+ if (!ShouldPerformForcedRedraw(changes.Key, changes.Value, charaData))
+ {
+ Logger.LogTrace("[{applicationId}] Skipping forced redraw for {handler}", applicationId, handler);
+ break;
+ }
await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false);
break;
@@ -1061,6 +1041,45 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
}
}
+ private bool ShouldPerformForcedRedraw(ObjectKind objectKind, ICollection changeSet, CharacterData newData)
+ {
+ if (objectKind != ObjectKind.Player)
+ {
+ return true;
+ }
+
+ var hasModFiles = changeSet.Contains(PlayerChanges.ModFiles);
+ var hasManip = changeSet.Contains(PlayerChanges.ModManip);
+ var modsChanged = hasModFiles && PlayerModFilesChanged(newData, _cachedData);
+ var manipChanged = hasManip && !string.Equals(_cachedData?.ManipulationData, newData.ManipulationData, StringComparison.Ordinal);
+
+ if (modsChanged)
+ {
+ _explicitRedrawQueued = false;
+ return true;
+ }
+
+ if (manipChanged)
+ {
+ _explicitRedrawQueued = false;
+ return true;
+ }
+
+ if (_explicitRedrawQueued)
+ {
+ _explicitRedrawQueued = false;
+ return true;
+ }
+
+ if ((hasModFiles || hasManip) && (_forceFullReapply || _needsCollectionRebuild))
+ {
+ _explicitRedrawQueued = false;
+ return true;
+ }
+
+ return false;
+ }
+
private static Dictionary> BuildFullChangeSet(CharacterData characterData)
{
var result = new Dictionary>();
@@ -1126,6 +1145,39 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return result;
}
+ private static bool PlayerModFilesChanged(CharacterData newData, CharacterData? previousData)
+ {
+ return !FileReplacementListsEqual(
+ TryGetFileReplacementList(newData, ObjectKind.Player),
+ TryGetFileReplacementList(previousData, ObjectKind.Player));
+ }
+
+ private static IReadOnlyCollection? TryGetFileReplacementList(CharacterData? data, ObjectKind objectKind)
+ {
+ if (data is null)
+ {
+ return null;
+ }
+
+ return data.FileReplacements.TryGetValue(objectKind, out var list) ? list : null;
+ }
+
+ private static bool FileReplacementListsEqual(IReadOnlyCollection? left, IReadOnlyCollection? right)
+ {
+ if (left is null || left.Count == 0)
+ {
+ return right is null || right.Count == 0;
+ }
+
+ if (right is null || right.Count == 0)
+ {
+ return false;
+ }
+
+ var comparer = FileReplacementDataComparer.Instance;
+ return !left.Except(right, comparer).Any() && !right.Except(left, comparer).Any();
+ }
+
private void DownloadAndApplyCharacter(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, bool forceFullReapply)
{
if (!updatedData.Any())
@@ -1165,7 +1217,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource();
var downloadToken = _downloadCancellationTokenSource.Token;
- _ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, cachedModdedPaths, downloadToken).ConfigureAwait(false);
+ _ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, cachedModdedPaths, downloadToken)
+ .ConfigureAwait(false);
}
private Task? _pairDownloadTask;
@@ -1173,107 +1226,114 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary> updatedData,
bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
{
- await using var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
- bool skipDownscaleForPair = ShouldSkipDownscale();
- var user = GetPrimaryUserData();
- Dictionary<(string GamePath, string? Hash), string> moddedPaths;
-
- if (updateModdedPaths)
+ var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
+ try
{
- if (cachedModdedPaths is not null)
+ bool skipDownscaleForPair = ShouldSkipDownscale();
+ var user = GetPrimaryUserData();
+ Dictionary<(string GamePath, string? Hash), string> moddedPaths;
+
+ if (updateModdedPaths)
{
- moddedPaths = new Dictionary<(string GamePath, string? Hash), string>(cachedModdedPaths, cachedModdedPaths.Comparer);
+ if (cachedModdedPaths is not null)
+ {
+ moddedPaths = new Dictionary<(string GamePath, string? Hash), string>(cachedModdedPaths, cachedModdedPaths.Comparer);
+ }
+ else
+ {
+ int attempts = 0;
+ List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
+
+ while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)
+ {
+ if (_pairDownloadTask != null && !_pairDownloadTask.IsCompleted)
+ {
+ Logger.LogDebug("[BASE-{appBase}] Finishing prior running download task for player {name}, {kind}", applicationBase, PlayerName, updatedData);
+ await _pairDownloadTask.ConfigureAwait(false);
+ }
+
+ Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData);
+
+ Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter), EventSeverity.Informational,
+ $"Starting download for {toDownloadReplacements.Count} files")));
+ var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false);
+
+ if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, toDownloadFiles))
+ {
+ _downloadManager.ClearDownload();
+ return;
+ }
+
+ var handlerForDownload = _charaHandler;
+ _pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair).ConfigureAwait(false));
+
+ await _pairDownloadTask.ConfigureAwait(false);
+
+ if (downloadToken.IsCancellationRequested)
+ {
+ Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase);
+ return;
+ }
+
+ toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
+
+ if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal))))
+ {
+ break;
+ }
+
+ await Task.Delay(TimeSpan.FromSeconds(2), downloadToken).ConfigureAwait(false);
+ }
+
+ if (!await _playerPerformanceService.CheckBothThresholds(this, charaData).ConfigureAwait(false))
+ {
+ return;
+ }
+ }
}
else
{
- int attempts = 0;
- List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
-
- while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)
- {
- if (_pairDownloadTask != null && !_pairDownloadTask.IsCompleted)
- {
- Logger.LogDebug("[BASE-{appBase}] Finishing prior running download task for player {name}, {kind}", applicationBase, PlayerName, updatedData);
- await _pairDownloadTask.ConfigureAwait(false);
- }
-
- Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData);
-
- Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter), EventSeverity.Informational,
- $"Starting download for {toDownloadReplacements.Count} files")));
- var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false);
-
- if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, toDownloadFiles))
- {
- _downloadManager.ClearDownload();
- return;
- }
-
- var handlerForDownload = _charaHandler;
- _pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair).ConfigureAwait(false));
-
- await _pairDownloadTask.ConfigureAwait(false);
-
- if (downloadToken.IsCancellationRequested)
- {
- Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase);
- return;
- }
-
- toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
-
- if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal))))
- {
- break;
- }
-
- await Task.Delay(TimeSpan.FromSeconds(2), downloadToken).ConfigureAwait(false);
- }
-
- if (!await _playerPerformanceService.CheckBothThresholds(this, charaData).ConfigureAwait(false))
- {
- return;
- }
+ moddedPaths = cachedModdedPaths is not null
+ ? new Dictionary<(string GamePath, string? Hash), string>(cachedModdedPaths, cachedModdedPaths.Comparer)
+ : [];
}
+
+ downloadToken.ThrowIfCancellationRequested();
+
+ var handlerForApply = _charaHandler;
+ if (handlerForApply is null || handlerForApply.Address == nint.Zero)
+ {
+ Logger.LogDebug("[BASE-{appBase}] Handler not available for {player}, cached data for later application", applicationBase, GetLogIdentifier());
+ _cachedData = charaData;
+ _pairStateCache.Store(Ident, charaData);
+ _forceFullReapply = true;
+ return;
+ }
+
+ var appToken = _applicationCancellationTokenSource?.Token;
+ while ((!_applicationTask?.IsCompleted ?? false)
+ && !downloadToken.IsCancellationRequested
+ && (!appToken?.IsCancellationRequested ?? false))
+ {
+ Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName);
+ await Task.Delay(250).ConfigureAwait(false);
+ }
+
+ if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false))
+ {
+ _forceFullReapply = true;
+ return;
+ }
+
+ _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
+ var token = _applicationCancellationTokenSource.Token;
+
+ _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token);
}
- else
+ finally
{
- moddedPaths = cachedModdedPaths is not null
- ? new Dictionary<(string GamePath, string? Hash), string>(cachedModdedPaths, cachedModdedPaths.Comparer)
- : [];
+ await concurrencyLease.DisposeAsync().ConfigureAwait(false);
}
-
- downloadToken.ThrowIfCancellationRequested();
-
- var handlerForApply = _charaHandler;
- if (handlerForApply is null || handlerForApply.Address == nint.Zero)
- {
- Logger.LogDebug("[BASE-{appBase}] Handler not available for {player}, cached data for later application", applicationBase, GetLogIdentifier());
- _cachedData = charaData;
- _pairStateCache.Store(Ident, charaData);
- _forceFullReapply = true;
- return;
- }
-
- var appToken = _applicationCancellationTokenSource?.Token;
- while ((!_applicationTask?.IsCompleted ?? false)
- && !downloadToken.IsCancellationRequested
- && (!appToken?.IsCancellationRequested ?? false))
- {
- Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName);
- await Task.Delay(250).ConfigureAwait(false);
- }
-
- if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false))
- {
- _forceFullReapply = true;
- return;
- }
-
- _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
- var token = _applicationCancellationTokenSource.Token;
-
- _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token);
}
private async Task ApplyCharacterDataAsync(Guid applicationBase, GameObjectHandler handlerForApply, CharacterData charaData, Dictionary> updatedData, bool updateModdedPaths, bool updateManip,
@@ -1416,6 +1476,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
try
{
+ _forceFullReapply = true;
ApplyCharacterData(appData, cachedData!, forceApplyCustomization: true);
}
catch (Exception ex)
@@ -1432,6 +1493,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
try
{
+ _forceFullReapply = true;
ApplyLastReceivedData(forced: true);
}
catch (Exception ex)
@@ -1468,21 +1530,37 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_serverConfigManager.AutoPopulateNoteForUid(user.UID, name);
}
- Mediator.Subscribe(this, async (_) =>
+ Mediator.Subscribe(this, _message =>
{
- if (string.IsNullOrEmpty(_cachedData?.HonorificData)) return;
- Logger.LogTrace("Reapplying Honorific data for {handler}", GetLogIdentifier());
- await _ipcManager.Honorific.SetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false);
+ var honorificData = _cachedData?.HonorificData;
+ if (string.IsNullOrEmpty(honorificData))
+ return;
+
+ _ = ReapplyHonorificAsync(honorificData!);
});
- Mediator.Subscribe(this, async (_) =>
+ Mediator.Subscribe(this, _message =>
{
- if (string.IsNullOrEmpty(_cachedData?.PetNamesData)) return;
- Logger.LogTrace("Reapplying Pet Names data for {handler}", GetLogIdentifier());
- await _ipcManager.PetNames.SetPlayerData(PlayerCharacter, _cachedData.PetNamesData).ConfigureAwait(false);
+ var petNamesData = _cachedData?.PetNamesData;
+ if (string.IsNullOrEmpty(petNamesData))
+ return;
+
+ _ = ReapplyPetNamesAsync(petNamesData!);
});
}
+ private async Task ReapplyHonorificAsync(string honorificData)
+ {
+ Logger.LogTrace("Reapplying Honorific data for {handler}", GetLogIdentifier());
+ await _ipcManager.Honorific.SetTitleAsync(PlayerCharacter, honorificData).ConfigureAwait(false);
+ }
+
+ private async Task ReapplyPetNamesAsync(string petNamesData)
+ {
+ Logger.LogTrace("Reapplying Pet Names data for {handler}", GetLogIdentifier());
+ await _ipcManager.PetNames.SetPlayerData(PlayerCharacter, petNamesData).ConfigureAwait(false);
+ }
+
private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId, CancellationToken cancelToken)
{
nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Ident);
@@ -1572,14 +1650,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
token.ThrowIfCancellationRequested();
var fileCache = _fileDbManager.GetFileCacheByHash(item.Hash);
- if (fileCache != null)
+ if (fileCache is not null && !File.Exists(fileCache.ResolvedFilepath))
{
- if (!File.Exists(fileCache.ResolvedFilepath))
- {
- Logger.LogTrace("[BASE-{appBase}] Cached path {Path} missing on disk for hash {Hash}, removing cache entry", applicationBase, fileCache.ResolvedFilepath, item.Hash);
- _fileDbManager.RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath);
- fileCache = null;
- }
+ Logger.LogTrace("[BASE-{appBase}] Cached path {Path} missing on disk for hash {Hash}, removing cache entry", applicationBase, fileCache.ResolvedFilepath, item.Hash);
+ _fileDbManager.RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath);
+ fileCache = null;
}
if (fileCache != null)
@@ -1701,7 +1776,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (penumbraCollection != Guid.Empty)
{
await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, penumbraCollection, character.ObjectIndex).ConfigureAwait(false);
- await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, applicationId, penumbraCollection, new Dictionary()).ConfigureAwait(false);
+ await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, applicationId, penumbraCollection, new Dictionary(StringComparer.Ordinal)).ConfigureAwait(false);
await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, applicationId, penumbraCollection, string.Empty).ConfigureAwait(false);
}
}
@@ -1775,83 +1850,3 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
}
}
-
-internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
-{
- private readonly ILoggerFactory _loggerFactory;
- private readonly LightlessMediator _mediator;
- private readonly PairManager _pairManager;
- private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
- private readonly IpcManager _ipcManager;
- private readonly FileDownloadManagerFactory _fileDownloadManagerFactory;
- private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
- private readonly IServiceProvider _serviceProvider;
- private readonly IHostApplicationLifetime _lifetime;
- private readonly FileCacheManager _fileCacheManager;
- private readonly PlayerPerformanceService _playerPerformanceService;
- private readonly PairProcessingLimiter _pairProcessingLimiter;
- private readonly ServerConfigurationManager _serverConfigManager;
- private readonly TextureDownscaleService _textureDownscaleService;
- private readonly PairStateCache _pairStateCache;
- private readonly PairPerformanceMetricsCache _pairPerformanceMetricsCache;
-
- public PairHandlerAdapterFactory(
- ILoggerFactory loggerFactory,
- LightlessMediator mediator,
- PairManager pairManager,
- GameObjectHandlerFactory gameObjectHandlerFactory,
- IpcManager ipcManager,
- FileDownloadManagerFactory fileDownloadManagerFactory,
- PluginWarningNotificationService pluginWarningNotificationManager,
- IServiceProvider serviceProvider,
- IHostApplicationLifetime lifetime,
- FileCacheManager fileCacheManager,
- PlayerPerformanceService playerPerformanceService,
- PairProcessingLimiter pairProcessingLimiter,
- ServerConfigurationManager serverConfigManager,
- TextureDownscaleService textureDownscaleService,
- PairStateCache pairStateCache,
- PairPerformanceMetricsCache pairPerformanceMetricsCache)
- {
- _loggerFactory = loggerFactory;
- _mediator = mediator;
- _pairManager = pairManager;
- _gameObjectHandlerFactory = gameObjectHandlerFactory;
- _ipcManager = ipcManager;
- _fileDownloadManagerFactory = fileDownloadManagerFactory;
- _pluginWarningNotificationManager = pluginWarningNotificationManager;
- _serviceProvider = serviceProvider;
- _lifetime = lifetime;
- _fileCacheManager = fileCacheManager;
- _playerPerformanceService = playerPerformanceService;
- _pairProcessingLimiter = pairProcessingLimiter;
- _serverConfigManager = serverConfigManager;
- _textureDownscaleService = textureDownscaleService;
- _pairStateCache = pairStateCache;
- _pairPerformanceMetricsCache = pairPerformanceMetricsCache;
- }
-
- public IPairHandlerAdapter Create(string ident)
- {
- var downloadManager = _fileDownloadManagerFactory.Create();
- var dalamudUtilService = _serviceProvider.GetRequiredService();
- return new PairHandlerAdapter(
- _loggerFactory.CreateLogger(),
- _mediator,
- _pairManager,
- ident,
- _gameObjectHandlerFactory,
- _ipcManager,
- downloadManager,
- _pluginWarningNotificationManager,
- dalamudUtilService,
- _lifetime,
- _fileCacheManager,
- _playerPerformanceService,
- _pairProcessingLimiter,
- _serverConfigManager,
- _textureDownscaleService,
- _pairStateCache,
- _pairPerformanceMetricsCache);
- }
-}
diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs
new file mode 100644
index 0000000..1fe2703
--- /dev/null
+++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs
@@ -0,0 +1,93 @@
+using LightlessSync.FileCache;
+using LightlessSync.Interop.Ipc;
+using LightlessSync.PlayerData.Factories;
+using LightlessSync.Services;
+using LightlessSync.Services.Mediator;
+using LightlessSync.Services.PairProcessing;
+using LightlessSync.Services.ServerConfiguration;
+using LightlessSync.Services.TextureCompression;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace LightlessSync.PlayerData.Pairs;
+
+internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
+{
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly LightlessMediator _mediator;
+ private readonly PairManager _pairManager;
+ private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
+ private readonly IpcManager _ipcManager;
+ private readonly FileDownloadManagerFactory _fileDownloadManagerFactory;
+ private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IHostApplicationLifetime _lifetime;
+ private readonly FileCacheManager _fileCacheManager;
+ private readonly PlayerPerformanceService _playerPerformanceService;
+ private readonly PairProcessingLimiter _pairProcessingLimiter;
+ private readonly ServerConfigurationManager _serverConfigManager;
+ private readonly TextureDownscaleService _textureDownscaleService;
+ private readonly PairStateCache _pairStateCache;
+ private readonly PairPerformanceMetricsCache _pairPerformanceMetricsCache;
+
+ public PairHandlerAdapterFactory(
+ ILoggerFactory loggerFactory,
+ LightlessMediator mediator,
+ PairManager pairManager,
+ GameObjectHandlerFactory gameObjectHandlerFactory,
+ IpcManager ipcManager,
+ FileDownloadManagerFactory fileDownloadManagerFactory,
+ PluginWarningNotificationService pluginWarningNotificationManager,
+ IServiceProvider serviceProvider,
+ IHostApplicationLifetime lifetime,
+ FileCacheManager fileCacheManager,
+ PlayerPerformanceService playerPerformanceService,
+ PairProcessingLimiter pairProcessingLimiter,
+ ServerConfigurationManager serverConfigManager,
+ TextureDownscaleService textureDownscaleService,
+ PairStateCache pairStateCache,
+ PairPerformanceMetricsCache pairPerformanceMetricsCache)
+ {
+ _loggerFactory = loggerFactory;
+ _mediator = mediator;
+ _pairManager = pairManager;
+ _gameObjectHandlerFactory = gameObjectHandlerFactory;
+ _ipcManager = ipcManager;
+ _fileDownloadManagerFactory = fileDownloadManagerFactory;
+ _pluginWarningNotificationManager = pluginWarningNotificationManager;
+ _serviceProvider = serviceProvider;
+ _lifetime = lifetime;
+ _fileCacheManager = fileCacheManager;
+ _playerPerformanceService = playerPerformanceService;
+ _pairProcessingLimiter = pairProcessingLimiter;
+ _serverConfigManager = serverConfigManager;
+ _textureDownscaleService = textureDownscaleService;
+ _pairStateCache = pairStateCache;
+ _pairPerformanceMetricsCache = pairPerformanceMetricsCache;
+ }
+
+ public IPairHandlerAdapter Create(string ident)
+ {
+ var downloadManager = _fileDownloadManagerFactory.Create();
+ var dalamudUtilService = _serviceProvider.GetRequiredService();
+ return new PairHandlerAdapter(
+ _loggerFactory.CreateLogger(),
+ _mediator,
+ _pairManager,
+ ident,
+ _gameObjectHandlerFactory,
+ _ipcManager,
+ downloadManager,
+ _pluginWarningNotificationManager,
+ dalamudUtilService,
+ _lifetime,
+ _fileCacheManager,
+ _playerPerformanceService,
+ _pairProcessingLimiter,
+ _serverConfigManager,
+ _textureDownscaleService,
+ _pairStateCache,
+ _pairPerformanceMetricsCache);
+ }
+}
diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs
index d1f3b6d..2cf8bdf 100644
--- a/LightlessSync/Plugin.cs
+++ b/LightlessSync/Plugin.cs
@@ -18,6 +18,7 @@ using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.CharaData;
using LightlessSync.Services.Events;
using LightlessSync.Services.Mediator;
+using LightlessSync.Services.Rendering;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.Services.TextureCompression;
using LightlessSync.UI;
@@ -33,8 +34,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NReco.Logging.File;
-using System;
-using System.IO;
using System.Net.Http.Headers;
using System.Reflection;
using OtterTex;
@@ -178,6 +177,10 @@ public sealed class Plugin : IDalamudPlugin
sp.GetRequiredService(),
sp.GetRequiredService()));
+ services.AddSingleton(sp => new PictomancyService(
+ sp.GetRequiredService>(),
+ pluginInterface));
+
// Tag (Groups) UIs
services.AddSingleton();
services.AddSingleton();
@@ -260,11 +263,14 @@ public sealed class Plugin : IDalamudPlugin
services.AddSingleton(sp => new LightFinderPlateHandler(
sp.GetRequiredService>(),
- sp.GetRequiredService(),
- pluginInterface,
+ addonLifecycle,
+ gameGui,
sp.GetRequiredService(),
+ sp.GetRequiredService(),
objectTable,
- gameGui));
+ sp.GetRequiredService(),
+ pluginInterface,
+ sp.GetRequiredService()));
services.AddSingleton(sp => new LightFinderScannerService(
sp.GetRequiredService>(),
diff --git a/LightlessSync/Services/ActorTracking/ActorObjectService.cs b/LightlessSync/Services/ActorTracking/ActorObjectService.cs
index 2305c2a..c2650ad 100644
--- a/LightlessSync/Services/ActorTracking/ActorObjectService.cs
+++ b/LightlessSync/Services/ActorTracking/ActorObjectService.cs
@@ -1,27 +1,20 @@
-using LightlessSync;
-using LightlessObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using Dalamud.Game;
-using Dalamud.Game.ClientState;
+using System.Collections.Concurrent;
using Dalamud.Game.ClientState.Objects.SubKinds;
-using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
+using FFXIVClientStructs.Interop;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
+using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
-using FFXIVClientStructs.Interop;
-using System.Threading;
+using LightlessObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
namespace LightlessSync.Services.ActorTracking;
-public sealed unsafe class ActorObjectService : IHostedService, IDisposable
+public sealed class ActorObjectService : IHostedService, IDisposable
{
public readonly record struct ActorDescriptor(
string Name,
@@ -38,25 +31,13 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
private readonly IFramework _framework;
private readonly IGameInteropProvider _interop;
private readonly IObjectTable _objectTable;
- private readonly IClientState _clientState;
private readonly LightlessMediator _mediator;
private readonly ConcurrentDictionary _activePlayers = new();
private readonly ConcurrentDictionary _actorsByHash = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary> _actorsByName = new(StringComparer.Ordinal);
- private ActorDescriptor[] _playerCharacterSnapshot = Array.Empty();
- private nint[] _playerAddressSnapshot = Array.Empty();
- private readonly HashSet _renderedPlayers = new();
- private readonly HashSet _renderedCompanions = new();
- private readonly Dictionary _ownedObjects = new();
- private nint[] _renderedPlayerSnapshot = Array.Empty();
- private nint[] _renderedCompanionSnapshot = Array.Empty();
- private nint[] _ownedObjectSnapshot = Array.Empty();
- private IReadOnlyDictionary _ownedObjectMapSnapshot = new Dictionary();
- private nint _localPlayerAddress = nint.Zero;
- private nint _localPetAddress = nint.Zero;
- private nint _localMinionMountAddress = nint.Zero;
- private nint _localCompanionAddress = nint.Zero;
+ private readonly OwnedObjectTracker _ownedTracker = new();
+ private ActorSnapshot _snapshot = ActorSnapshot.Empty;
private Hook? _onInitializeHook;
private Hook? _onTerminateHook;
@@ -80,16 +61,30 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_framework = framework;
_interop = interop;
_objectTable = objectTable;
- _clientState = clientState;
_mediator = mediator;
}
- public IReadOnlyList PlayerAddresses => Volatile.Read(ref _playerAddressSnapshot);
+ private ActorSnapshot Snapshot => Volatile.Read(ref _snapshot);
+
+ public IReadOnlyList PlayerAddresses => Snapshot.PlayerAddresses;
public IEnumerable PlayerDescriptors => _activePlayers.Values;
- public IReadOnlyList PlayerCharacterDescriptors => Volatile.Read(ref _playerCharacterSnapshot);
+ public IReadOnlyList PlayerCharacterDescriptors => Snapshot.PlayerDescriptors;
public bool TryGetActorByHash(string hash, out ActorDescriptor descriptor) => _actorsByHash.TryGetValue(hash, out descriptor);
+ public bool TryGetValidatedActorByHash(string hash, out ActorDescriptor descriptor)
+ {
+ descriptor = default;
+ if (!_actorsByHash.TryGetValue(hash, out var candidate))
+ return false;
+
+ if (!ValidateDescriptorThreadSafe(candidate))
+ return false;
+
+ descriptor = candidate;
+ return true;
+ }
+
public bool TryGetPlayerByName(string name, out ActorDescriptor descriptor)
{
descriptor = default;
@@ -100,6 +95,9 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
ActorDescriptor? best = null;
foreach (var candidate in entries.Values)
{
+ if (!ValidateDescriptorThreadSafe(candidate))
+ continue;
+
if (best is null || IsBetterNameMatch(candidate, best.Value))
{
best = candidate;
@@ -115,23 +113,54 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return false;
}
public bool HooksActive => _hooksActive;
- public IReadOnlyList RenderedPlayerAddresses => Volatile.Read(ref _renderedPlayerSnapshot);
- public IReadOnlyList RenderedCompanionAddresses => Volatile.Read(ref _renderedCompanionSnapshot);
- public IReadOnlyList OwnedObjectAddresses => Volatile.Read(ref _ownedObjectSnapshot);
- public IReadOnlyDictionary OwnedObjects => Volatile.Read(ref _ownedObjectMapSnapshot);
- public nint LocalPlayerAddress => Volatile.Read(ref _localPlayerAddress);
- public nint LocalPetAddress => Volatile.Read(ref _localPetAddress);
- public nint LocalMinionOrMountAddress => Volatile.Read(ref _localMinionMountAddress);
- public nint LocalCompanionAddress => Volatile.Read(ref _localCompanionAddress);
+ public IReadOnlyList RenderedPlayerAddresses => Snapshot.OwnedObjects.RenderedPlayers;
+ public IReadOnlyList RenderedCompanionAddresses => Snapshot.OwnedObjects.RenderedCompanions;
+ public IReadOnlyList OwnedObjectAddresses => Snapshot.OwnedObjects.OwnedAddresses;
+ public IReadOnlyDictionary OwnedObjects => Snapshot.OwnedObjects.Map;
+ public nint LocalPlayerAddress => Snapshot.OwnedObjects.LocalPlayer;
+ public nint LocalPetAddress => Snapshot.OwnedObjects.LocalPet;
+ public nint LocalMinionOrMountAddress => Snapshot.OwnedObjects.LocalMinionOrMount;
+ public nint LocalCompanionAddress => Snapshot.OwnedObjects.LocalCompanion;
+
+ public bool TryGetOwnedKind(nint address, out LightlessObjectKind kind)
+ => OwnedObjects.TryGetValue(address, out kind);
+
+ public bool TryGetOwnedActor(LightlessObjectKind kind, out ActorDescriptor descriptor)
+ {
+ descriptor = default;
+ if (!TryGetOwnedObject(kind, out var address))
+ return false;
+ return TryGetDescriptor(address, out descriptor);
+ }
+
+ public bool TryGetOwnedObjectByIndex(ushort objectIndex, out LightlessObjectKind ownedKind)
+ {
+ ownedKind = default;
+ var ownedSnapshot = OwnedObjects;
+ foreach (var (address, kind) in ownedSnapshot)
+ {
+ if (!TryGetDescriptor(address, out var descriptor))
+ continue;
+
+ if (descriptor.ObjectIndex == objectIndex)
+ {
+ ownedKind = kind;
+ return true;
+ }
+ }
+
+ return false;
+ }
public bool TryGetOwnedObject(LightlessObjectKind kind, out nint address)
{
+ var ownedSnapshot = Snapshot.OwnedObjects;
address = kind switch
{
- LightlessObjectKind.Player => Volatile.Read(ref _localPlayerAddress),
- LightlessObjectKind.Pet => Volatile.Read(ref _localPetAddress),
- LightlessObjectKind.MinionOrMount => Volatile.Read(ref _localMinionMountAddress),
- LightlessObjectKind.Companion => Volatile.Read(ref _localCompanionAddress),
+ LightlessObjectKind.Player => ownedSnapshot.LocalPlayer,
+ LightlessObjectKind.Pet => ownedSnapshot.LocalPet,
+ LightlessObjectKind.MinionOrMount => ownedSnapshot.LocalMinionOrMount,
+ LightlessObjectKind.Companion => ownedSnapshot.LocalCompanion,
_ => nint.Zero
};
@@ -158,7 +187,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
public bool TryGetPlayerAddressByHash(string hash, out nint address)
{
- if (TryGetActorByHash(hash, out var descriptor) && descriptor.Address != nint.Zero)
+ if (TryGetValidatedActorByHash(hash, out var descriptor) && descriptor.Address != nint.Zero)
{
address = descriptor.Address;
return true;
@@ -168,6 +197,50 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return false;
}
+ public async Task WaitForFullyLoadedAsync(nint address, CancellationToken cancellationToken = default)
+ {
+ if (address == nint.Zero)
+ throw new ArgumentException("Address cannot be zero.", nameof(address));
+
+ while (true)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var isLoaded = await _framework.RunOnFrameworkThread(() => IsObjectFullyLoaded(address)).ConfigureAwait(false);
+ if (isLoaded)
+ return;
+
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private bool ValidateDescriptorThreadSafe(ActorDescriptor descriptor)
+ {
+ if (_framework.IsInFrameworkUpdateThread)
+ return ValidateDescriptorInternal(descriptor);
+
+ return _framework.RunOnFrameworkThread(() => ValidateDescriptorInternal(descriptor)).GetAwaiter().GetResult();
+ }
+
+ private bool ValidateDescriptorInternal(ActorDescriptor descriptor)
+ {
+ if (descriptor.Address == nint.Zero)
+ return false;
+
+ if (descriptor.ObjectKind == DalamudObjectKind.Player &&
+ !string.IsNullOrEmpty(descriptor.HashedContentId))
+ {
+ var liveHash = DalamudUtilService.GetHashedCIDFromPlayerPointer(descriptor.Address);
+ if (!string.Equals(liveHash, descriptor.HashedContentId, StringComparison.Ordinal))
+ {
+ UntrackGameObject(descriptor.Address);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
public void RefreshTrackedActors(bool force = false)
{
var now = DateTime.UtcNow;
@@ -185,7 +258,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
}
else
{
- _framework.RunOnFrameworkThread(RefreshTrackedActorsInternal);
+ _ = _framework.RunOnFrameworkThread(RefreshTrackedActorsInternal);
}
}
@@ -211,23 +284,12 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_activePlayers.Clear();
_actorsByHash.Clear();
_actorsByName.Clear();
- Volatile.Write(ref _playerCharacterSnapshot, Array.Empty());
- Volatile.Write(ref _playerAddressSnapshot, Array.Empty());
- Volatile.Write(ref _renderedPlayerSnapshot, Array.Empty());
- Volatile.Write(ref _renderedCompanionSnapshot, Array.Empty());
- Volatile.Write(ref _ownedObjectSnapshot, Array.Empty());
- Volatile.Write(ref _ownedObjectMapSnapshot, new Dictionary());
- Volatile.Write(ref _localPlayerAddress, nint.Zero);
- Volatile.Write(ref _localPetAddress, nint.Zero);
- Volatile.Write(ref _localMinionMountAddress, nint.Zero);
- Volatile.Write(ref _localCompanionAddress, nint.Zero);
- _renderedPlayers.Clear();
- _renderedCompanions.Clear();
- _ownedObjects.Clear();
+ _ownedTracker.Reset();
+ Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
return Task.CompletedTask;
}
- private void InitializeHooks()
+ private unsafe void InitializeHooks()
{
if (_hooksActive)
return;
@@ -271,7 +333,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
});
}
- private void OnCharacterInitialized(Character* chara)
+ private unsafe void OnCharacterInitialized(Character* chara)
{
try
{
@@ -285,7 +347,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
QueueFrameworkUpdate(() => TrackGameObject((GameObject*)chara));
}
- private void OnCharacterTerminated(Character* chara)
+ private unsafe void OnCharacterTerminated(Character* chara)
{
var address = (nint)chara;
QueueFrameworkUpdate(() => UntrackGameObject(address));
@@ -299,7 +361,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
}
}
- private GameObject* OnCharacterDisposed(Character* chara, byte freeMemory)
+ private unsafe GameObject* OnCharacterDisposed(Character* chara, byte freeMemory)
{
var address = (nint)chara;
QueueFrameworkUpdate(() => UntrackGameObject(address));
@@ -314,7 +376,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
}
}
- private void TrackGameObject(GameObject* gameObject)
+ private unsafe void TrackGameObject(GameObject* gameObject)
{
if (gameObject == null)
return;
@@ -332,14 +394,10 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
if (_activePlayers.TryGetValue(descriptor.Address, out var existing))
{
- RemoveDescriptorFromIndexes(existing);
- RemoveDescriptorFromCollections(existing);
+ RemoveDescriptor(existing);
}
- _activePlayers[descriptor.Address] = descriptor;
- IndexDescriptor(descriptor);
- AddDescriptorToCollections(descriptor);
- RebuildSnapshots();
+ AddDescriptor(descriptor);
if (_logger.IsEnabled(LogLevel.Debug))
{
@@ -355,16 +413,16 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_mediator.Publish(new ActorTrackedMessage(descriptor));
}
- private ActorDescriptor? BuildDescriptor(GameObject* gameObject, DalamudObjectKind objectKind)
+ private unsafe ActorDescriptor? BuildDescriptor(GameObject* gameObject, DalamudObjectKind objectKind)
{
if (gameObject == null)
return null;
var address = (nint)gameObject;
string name = string.Empty;
- ushort objectIndex = (ushort)gameObject->ObjectIndex;
+ ushort objectIndex = gameObject->ObjectIndex;
bool isInGpose = objectIndex >= 200;
- bool isLocal = _clientState.LocalPlayer?.Address == address;
+ bool isLocal = _objectTable.LocalPlayer?.Address == address;
string hashedCid = string.Empty;
if (_objectTable.CreateObjectReference(address) is IPlayerCharacter playerCharacter)
@@ -372,7 +430,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
name = playerCharacter.Name.TextValue ?? string.Empty;
objectIndex = playerCharacter.ObjectIndex;
isInGpose = objectIndex >= 200;
- isLocal = playerCharacter.Address == _clientState.LocalPlayer?.Address;
+ isLocal = playerCharacter.Address == _objectTable.LocalPlayer?.Address;
}
else
{
@@ -389,7 +447,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return new ActorDescriptor(name, hashedCid, address, objectIndex, isLocal, isInGpose, objectKind, ownedKind, ownerEntityId);
}
- private (LightlessObjectKind? OwnedKind, uint OwnerEntityId) DetermineOwnedKind(GameObject* gameObject, DalamudObjectKind objectKind, bool isLocalPlayer)
+ private unsafe (LightlessObjectKind? OwnedKind, uint OwnerEntityId) DetermineOwnedKind(GameObject* gameObject, DalamudObjectKind objectKind, bool isLocalPlayer)
{
if (gameObject == null)
return (null, 0);
@@ -406,7 +464,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return (LightlessObjectKind.Player, entityId);
}
- if (_clientState.LocalPlayer is not { } localPlayer)
+ if (_objectTable.LocalPlayer is not { } localPlayer)
return (null, 0);
var ownerId = gameObject->OwnerId;
@@ -453,9 +511,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
if (_activePlayers.TryRemove(address, out var descriptor))
{
- RemoveDescriptorFromIndexes(descriptor);
- RemoveDescriptorFromCollections(descriptor);
- RebuildSnapshots();
+ RemoveDescriptor(descriptor);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Actor untracked: {Name} addr={Address:X} idx={Index} owned={OwnedKind}",
@@ -469,7 +525,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
}
}
- private void RefreshTrackedActorsInternal()
+ private unsafe void RefreshTrackedActorsInternal()
{
var addresses = EnumerateActiveCharacterAddresses();
HashSet seen = new(addresses.Count);
@@ -524,7 +580,10 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return candidate.ObjectIndex < current.ObjectIndex;
}
- private void OnCompanionInitialized(Companion* companion)
+ private bool TryGetDescriptor(nint address, out ActorDescriptor descriptor)
+ => _activePlayers.TryGetValue(address, out descriptor);
+
+ private unsafe void OnCompanionInitialized(Companion* companion)
{
try
{
@@ -538,7 +597,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
QueueFrameworkUpdate(() => TrackGameObject((GameObject*)companion));
}
- private void OnCompanionTerminated(Companion* companion)
+ private unsafe void OnCompanionTerminated(Companion* companion)
{
var address = (nint)companion;
QueueFrameworkUpdate(() => UntrackGameObject(address));
@@ -559,107 +618,46 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_actorsByHash.TryRemove(descriptor.HashedContentId, out _);
}
- if (descriptor.ObjectKind == DalamudObjectKind.Player && !string.IsNullOrEmpty(descriptor.Name))
+ if (descriptor.ObjectKind == DalamudObjectKind.Player
+ && !string.IsNullOrEmpty(descriptor.Name)
+ && _actorsByName.TryGetValue(descriptor.Name, out var bucket))
{
- if (_actorsByName.TryGetValue(descriptor.Name, out var bucket))
+ bucket.TryRemove(descriptor.Address, out _);
+ if (bucket.IsEmpty)
{
- bucket.TryRemove(descriptor.Address, out _);
- if (bucket.IsEmpty)
- {
- _actorsByName.TryRemove(descriptor.Name, out _);
- }
+ _actorsByName.TryRemove(descriptor.Name, out _);
}
}
}
- private void AddDescriptorToCollections(ActorDescriptor descriptor)
+ private void AddDescriptor(ActorDescriptor descriptor)
{
- if (descriptor.ObjectKind == DalamudObjectKind.Player)
- {
- _renderedPlayers.Add(descriptor.Address);
- if (descriptor.IsLocalPlayer)
- {
- Volatile.Write(ref _localPlayerAddress, descriptor.Address);
- }
- }
- else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
- {
- _renderedCompanions.Add(descriptor.Address);
- }
-
- if (descriptor.OwnedKind is { } ownedKind)
- {
- _ownedObjects[descriptor.Address] = ownedKind;
- switch (ownedKind)
- {
- case LightlessObjectKind.Player:
- Volatile.Write(ref _localPlayerAddress, descriptor.Address);
- break;
- case LightlessObjectKind.Pet:
- Volatile.Write(ref _localPetAddress, descriptor.Address);
- break;
- case LightlessObjectKind.MinionOrMount:
- Volatile.Write(ref _localMinionMountAddress, descriptor.Address);
- break;
- case LightlessObjectKind.Companion:
- Volatile.Write(ref _localCompanionAddress, descriptor.Address);
- break;
- }
- }
+ _activePlayers[descriptor.Address] = descriptor;
+ IndexDescriptor(descriptor);
+ _ownedTracker.OnDescriptorAdded(descriptor);
+ PublishSnapshot();
}
- private void RemoveDescriptorFromCollections(ActorDescriptor descriptor)
+ private void RemoveDescriptor(ActorDescriptor descriptor)
{
- if (descriptor.ObjectKind == DalamudObjectKind.Player)
- {
- _renderedPlayers.Remove(descriptor.Address);
- if (descriptor.IsLocalPlayer && Volatile.Read(ref _localPlayerAddress) == descriptor.Address)
- {
- Volatile.Write(ref _localPlayerAddress, nint.Zero);
- }
- }
- else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
- {
- _renderedCompanions.Remove(descriptor.Address);
- if (Volatile.Read(ref _localCompanionAddress) == descriptor.Address)
- {
- Volatile.Write(ref _localCompanionAddress, nint.Zero);
- }
- }
-
- if (descriptor.OwnedKind is { } ownedKind)
- {
- _ownedObjects.Remove(descriptor.Address);
- switch (ownedKind)
- {
- case LightlessObjectKind.Player when Volatile.Read(ref _localPlayerAddress) == descriptor.Address:
- Volatile.Write(ref _localPlayerAddress, nint.Zero);
- break;
- case LightlessObjectKind.Pet when Volatile.Read(ref _localPetAddress) == descriptor.Address:
- Volatile.Write(ref _localPetAddress, nint.Zero);
- break;
- case LightlessObjectKind.MinionOrMount when Volatile.Read(ref _localMinionMountAddress) == descriptor.Address:
- Volatile.Write(ref _localMinionMountAddress, nint.Zero);
- break;
- case LightlessObjectKind.Companion when Volatile.Read(ref _localCompanionAddress) == descriptor.Address:
- Volatile.Write(ref _localCompanionAddress, nint.Zero);
- break;
- }
- }
+ RemoveDescriptorFromIndexes(descriptor);
+ _ownedTracker.OnDescriptorRemoved(descriptor);
+ PublishSnapshot();
}
- private void RebuildSnapshots()
+ private void PublishSnapshot()
{
var playerDescriptors = _activePlayers.Values
.Where(descriptor => descriptor.ObjectKind == DalamudObjectKind.Player)
.ToArray();
+ var playerAddresses = new nint[playerDescriptors.Length];
+ for (var i = 0; i < playerDescriptors.Length; i++)
+ playerAddresses[i] = playerDescriptors[i].Address;
- Volatile.Write(ref _playerCharacterSnapshot, playerDescriptors);
- Volatile.Write(ref _playerAddressSnapshot, playerDescriptors.Select(d => d.Address).ToArray());
- Volatile.Write(ref _renderedPlayerSnapshot, _renderedPlayers.ToArray());
- Volatile.Write(ref _renderedCompanionSnapshot, _renderedCompanions.ToArray());
- Volatile.Write(ref _ownedObjectSnapshot, _ownedObjects.Keys.ToArray());
- Volatile.Write(ref _ownedObjectMapSnapshot, new Dictionary(_ownedObjects));
+ var ownedSnapshot = _ownedTracker.CreateSnapshot();
+ var nextGeneration = Snapshot.Generation + 1;
+ var snapshot = new ActorSnapshot(playerDescriptors, playerAddresses, ownedSnapshot, nextGeneration);
+ Volatile.Write(ref _snapshot, snapshot);
}
private void QueueFrameworkUpdate(Action action)
@@ -673,7 +671,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return;
}
- _framework.RunOnFrameworkThread(action);
+ _ = _framework.RunOnFrameworkThread(action);
}
private void DisposeHooks()
@@ -723,7 +721,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
or DalamudObjectKind.Companion
or DalamudObjectKind.MountType;
- private static List EnumerateActiveCharacterAddresses()
+ private static unsafe List EnumerateActiveCharacterAddresses()
{
var results = new List(64);
var manager = GameObjectManager.Instance();
@@ -751,4 +749,170 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return results;
}
+
+ private static unsafe bool IsObjectFullyLoaded(nint address)
+ {
+ if (address == nint.Zero)
+ return false;
+
+ var gameObject = (GameObject*)address;
+ if (gameObject == null)
+ return false;
+
+ var drawObject = gameObject->DrawObject;
+ if (drawObject == null)
+ return false;
+
+ if (gameObject->RenderFlags == 2048)
+ return false;
+
+ var characterBase = (CharacterBase*)drawObject;
+ if (characterBase == null)
+ return false;
+
+ if (characterBase->HasModelInSlotLoaded != 0)
+ return false;
+
+ if (characterBase->HasModelFilesInSlotLoaded != 0)
+ return false;
+
+ return true;
+ }
+
+ private sealed class OwnedObjectTracker
+ {
+ private readonly HashSet _renderedPlayers = new();
+ private readonly HashSet _renderedCompanions = new();
+ private readonly Dictionary _ownedObjects = new();
+ private nint _localPlayerAddress = nint.Zero;
+ private nint _localPetAddress = nint.Zero;
+ private nint _localMinionMountAddress = nint.Zero;
+ private nint _localCompanionAddress = nint.Zero;
+
+ public void OnDescriptorAdded(ActorDescriptor descriptor)
+ {
+ if (descriptor.ObjectKind == DalamudObjectKind.Player)
+ {
+ _renderedPlayers.Add(descriptor.Address);
+ if (descriptor.IsLocalPlayer)
+ _localPlayerAddress = descriptor.Address;
+ }
+ else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
+ {
+ _renderedCompanions.Add(descriptor.Address);
+ }
+
+ if (descriptor.OwnedKind is { } ownedKind)
+ {
+ _ownedObjects[descriptor.Address] = ownedKind;
+ switch (ownedKind)
+ {
+ case LightlessObjectKind.Player:
+ _localPlayerAddress = descriptor.Address;
+ break;
+ case LightlessObjectKind.Pet:
+ _localPetAddress = descriptor.Address;
+ break;
+ case LightlessObjectKind.MinionOrMount:
+ _localMinionMountAddress = descriptor.Address;
+ break;
+ case LightlessObjectKind.Companion:
+ _localCompanionAddress = descriptor.Address;
+ break;
+ }
+ }
+ }
+
+ public void OnDescriptorRemoved(ActorDescriptor descriptor)
+ {
+ if (descriptor.ObjectKind == DalamudObjectKind.Player)
+ {
+ _renderedPlayers.Remove(descriptor.Address);
+ if (descriptor.IsLocalPlayer && _localPlayerAddress == descriptor.Address)
+ _localPlayerAddress = nint.Zero;
+ }
+ else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
+ {
+ _renderedCompanions.Remove(descriptor.Address);
+ if (_localCompanionAddress == descriptor.Address)
+ _localCompanionAddress = nint.Zero;
+ }
+
+ if (descriptor.OwnedKind is { } ownedKind)
+ {
+ _ownedObjects.Remove(descriptor.Address);
+ switch (ownedKind)
+ {
+ case LightlessObjectKind.Player when _localPlayerAddress == descriptor.Address:
+ _localPlayerAddress = nint.Zero;
+ break;
+ case LightlessObjectKind.Pet when _localPetAddress == descriptor.Address:
+ _localPetAddress = nint.Zero;
+ break;
+ case LightlessObjectKind.MinionOrMount when _localMinionMountAddress == descriptor.Address:
+ _localMinionMountAddress = nint.Zero;
+ break;
+ case LightlessObjectKind.Companion when _localCompanionAddress == descriptor.Address:
+ _localCompanionAddress = nint.Zero;
+ break;
+ }
+ }
+ }
+
+ public OwnedObjectSnapshot CreateSnapshot()
+ => new(
+ _renderedPlayers.ToArray(),
+ _renderedCompanions.ToArray(),
+ _ownedObjects.Keys.ToArray(),
+ new Dictionary(_ownedObjects),
+ _localPlayerAddress,
+ _localPetAddress,
+ _localMinionMountAddress,
+ _localCompanionAddress);
+
+ public void Reset()
+ {
+ _renderedPlayers.Clear();
+ _renderedCompanions.Clear();
+ _ownedObjects.Clear();
+ _localPlayerAddress = nint.Zero;
+ _localPetAddress = nint.Zero;
+ _localMinionMountAddress = nint.Zero;
+ _localCompanionAddress = nint.Zero;
+ }
+ }
+
+ private sealed record OwnedObjectSnapshot(
+ IReadOnlyList RenderedPlayers,
+ IReadOnlyList RenderedCompanions,
+ IReadOnlyList OwnedAddresses,
+ IReadOnlyDictionary Map,
+ nint LocalPlayer,
+ nint LocalPet,
+ nint LocalMinionOrMount,
+ nint LocalCompanion)
+ {
+ public static OwnedObjectSnapshot Empty { get; } = new(
+ Array.Empty(),
+ Array.Empty(),
+ Array.Empty(),
+ new Dictionary(),
+ nint.Zero,
+ nint.Zero,
+ nint.Zero,
+ nint.Zero);
+ }
+
+ private sealed record ActorSnapshot(
+ IReadOnlyList PlayerDescriptors,
+ IReadOnlyList PlayerAddresses,
+ OwnedObjectSnapshot OwnedObjects,
+ int Generation)
+ {
+ public static ActorSnapshot Empty { get; } = new(
+ Array.Empty(),
+ Array.Empty(),
+ OwnedObjectSnapshot.Empty,
+ 0);
+ }
}
diff --git a/LightlessSync/Services/Chat/ZoneChatService.cs b/LightlessSync/Services/Chat/ZoneChatService.cs
index 9126436..edb3a86 100644
--- a/LightlessSync/Services/Chat/ZoneChatService.cs
+++ b/LightlessSync/Services/Chat/ZoneChatService.cs
@@ -1,4 +1,3 @@
-using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Chat;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Mediator;
@@ -21,12 +20,11 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private const int MaxReportContextLength = 1000;
private readonly ApiController _apiController;
- private readonly ChatConfigService _chatConfigService;
private readonly DalamudUtilService _dalamudUtilService;
private readonly ActorObjectService _actorObjectService;
private readonly PairUiService _pairUiService;
- private readonly object _sync = new();
+ private readonly Lock _sync = new();
private readonly Dictionary _channels = new(StringComparer.Ordinal);
private readonly List _channelOrder = new();
@@ -55,7 +53,6 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
PairUiService pairUiService)
: base(logger, mediator)
{
- _chatConfigService = chatConfigService;
_apiController = apiController;
_dalamudUtilService = dalamudUtilService;
_actorObjectService = actorObjectService;
@@ -63,12 +60,12 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
_isLoggedIn = _dalamudUtilService.IsLoggedIn;
_isConnected = _apiController.IsConnected;
- _chatEnabled = _chatConfigService.Current.AutoEnableChatOnLogin;
+ _chatEnabled = chatConfigService.Current.AutoEnableChatOnLogin;
}
public IReadOnlyList GetChannelsSnapshot()
{
- lock (_sync)
+ using (_sync.EnterScope())
{
var snapshots = new List(_channelOrder.Count);
foreach (var key in _channelOrder)
@@ -107,7 +104,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
get
{
- lock (_sync)
+ using (_sync.EnterScope())
{
return _chatEnabled;
}
@@ -118,7 +115,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
get
{
- lock (_sync)
+ using (_sync.EnterScope())
{
return _chatEnabled && _isConnected;
}
@@ -127,7 +124,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
public void SetActiveChannel(string? key)
{
- lock (_sync)
+ using (_sync.EnterScope())
{
_activeChannelKey = key;
if (key is not null && _channels.TryGetValue(key, out var state))
@@ -145,7 +142,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private async Task EnableChatAsync()
{
bool wasEnabled;
- lock (_sync)
+ using (_sync.EnterScope())
{
wasEnabled = _chatEnabled;
if (!wasEnabled)
@@ -170,7 +167,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
List groupDescriptors;
ChatChannelDescriptor? zoneDescriptor;
- lock (_sync)
+ using (_sync.EnterScope())
{
wasEnabled = _chatEnabled;
if (!wasEnabled)
@@ -259,7 +256,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
return Task.FromResult(new ChatReportResult(false, "Please describe why you are reporting this message."));
}
- lock (_sync)
+ using (_sync.EnterScope())
{
if (!_chatEnabled)
{
@@ -311,8 +308,8 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
Mediator.Subscribe(this, _ => HandleLogout());
Mediator.Subscribe(this, _ => ScheduleZonePresenceUpdate());
Mediator.Subscribe(this, _ => ScheduleZonePresenceUpdate(force: true));
- Mediator.Subscribe(this, msg => HandleConnected(msg.Connection));
- Mediator.Subscribe(this, _ => HandleConnected(null));
+ Mediator.Subscribe(this, _ => HandleConnected());
+ Mediator.Subscribe(this, _ => HandleConnected());
Mediator.Subscribe(this, _ => HandleReconnecting());
Mediator.Subscribe(this, _ => HandleReconnecting());
Mediator.Subscribe(this, _ => RefreshGroupsFromPairManager());
@@ -371,11 +368,11 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
}
}
- private void HandleConnected(ConnectionDto? connection)
+ private void HandleConnected()
{
_isConnected = true;
- lock (_sync)
+ using (_sync.EnterScope())
{
_selfTokens.Clear();
_pendingSelfMessages.Clear();
@@ -410,7 +407,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
_isConnected = false;
- lock (_sync)
+ using (_sync.EnterScope())
{
_selfTokens.Clear();
_pendingSelfMessages.Clear();
@@ -475,7 +472,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private void UpdateChannelsForDisabledState()
{
- lock (_sync)
+ using (_sync.EnterScope())
{
foreach (var state in _channels.Values)
{
@@ -513,7 +510,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
string? zoneKey;
ZoneChannelDefinition? definition = null;
- lock (_sync)
+ using (_sync.EnterScope())
{
_territoryToZoneKey.TryGetValue(territoryId, out zoneKey);
if (zoneKey is not null)
@@ -538,7 +535,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
bool shouldForceSend;
- lock (_sync)
+ using (_sync.EnterScope())
{
var state = EnsureZoneStateLocked();
state.DisplayName = definition.Value.DisplayName;
@@ -566,7 +563,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
ChatChannelDescriptor? descriptor = null;
bool clearedHistory = false;
- lock (_sync)
+ using (_sync.EnterScope())
{
descriptor = _lastZoneDescriptor;
_lastZoneDescriptor = null;
@@ -590,9 +587,9 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
state.DisplayName = "Zone Chat";
}
- if (_activeChannelKey == ZoneChannelKey)
+ if (string.Equals(_activeChannelKey, ZoneChannelKey, StringComparison.Ordinal))
{
- _activeChannelKey = _channelOrder.FirstOrDefault(key => key != ZoneChannelKey);
+ _activeChannelKey = _channelOrder.FirstOrDefault(key => !string.Equals(key, ZoneChannelKey, StringComparison.Ordinal));
}
}
@@ -627,7 +624,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
var infoList = infos ?? Array.Empty();
- lock (_sync)
+ using (_sync.EnterScope())
{
_zoneDefinitions.Clear();
_territoryToZoneKey.Clear();
@@ -657,7 +654,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
if (def.TerritoryNames.Contains(variant))
{
- _territoryToZoneKey[(uint)kvp.Key] = def.Key;
+ _territoryToZoneKey[kvp.Key] = def.Key;
break;
}
}
@@ -689,7 +686,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var descriptorsToJoin = new List();
var descriptorsToLeave = new List();
- lock (_sync)
+ using (_sync.EnterScope())
{
var remainingGroups = new HashSet(_groupDefinitions.Keys, StringComparer.OrdinalIgnoreCase);
@@ -807,7 +804,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
return;
List descriptors;
- lock (_sync)
+ using (_sync.EnterScope())
{
descriptors = _channels.Values
.Where(state => state.Type == ChatChannelType.Group)
@@ -832,7 +829,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var presenceKey = BuildPresenceKey(descriptor);
bool stateMatches;
- lock (_sync)
+ using (_sync.EnterScope())
{
stateMatches = !force
&& _lastPresenceStates.TryGetValue(presenceKey, out var lastState)
@@ -846,7 +843,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
await _apiController.UpdateChatPresence(new ChatPresenceUpdateDto(descriptor, territoryId, isActive)).ConfigureAwait(false);
- lock (_sync)
+ using (_sync.EnterScope())
{
if (isActive)
{
@@ -870,7 +867,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var key = normalized.Type == ChatChannelType.Zone ? ZoneChannelKey : BuildChannelKey(normalized);
var pending = new PendingSelfMessage(key, message);
- lock (_sync)
+ using (_sync.EnterScope())
{
_pendingSelfMessages.Add(pending);
while (_pendingSelfMessages.Count > 20)
@@ -884,7 +881,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private void RemovePendingSelfMessage(PendingSelfMessage pending)
{
- lock (_sync)
+ using (_sync.EnterScope())
{
var index = _pendingSelfMessages.FindIndex(p =>
string.Equals(p.ChannelKey, pending.ChannelKey, StringComparison.Ordinal) &&
@@ -905,7 +902,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var message = BuildMessage(dto, fromSelf);
bool publishChannelList = false;
- lock (_sync)
+ using (_sync.EnterScope())
{
if (!_channels.TryGetValue(key, out var state))
{
@@ -960,7 +957,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
if (publishChannelList)
{
- lock (_sync)
+ using (_sync.EnterScope())
{
UpdateChannelOrderLocked();
}
@@ -973,7 +970,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
if (dto.Sender.User?.UID is { } uid && string.Equals(uid, _apiController.UID, StringComparison.Ordinal))
{
- lock (_sync)
+ using (_sync.EnterScope())
{
_selfTokens[channelKey] = dto.Sender.Token;
}
@@ -981,7 +978,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
return true;
}
- lock (_sync)
+ using (_sync.EnterScope())
{
if (_selfTokens.TryGetValue(channelKey, out var token) &&
string.Equals(token, dto.Sender.Token, StringComparison.Ordinal))
@@ -1014,7 +1011,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
var isZone = dto.Channel.Type == ChatChannelType.Zone;
if (!string.IsNullOrEmpty(dto.Sender.HashedCid) &&
- _actorObjectService.TryGetActorByHash(dto.Sender.HashedCid, out var descriptor) &&
+ _actorObjectService.TryGetValidatedActorByHash(dto.Sender.HashedCid, out var descriptor) &&
!string.IsNullOrWhiteSpace(descriptor.Name))
{
return descriptor.Name;
@@ -1065,7 +1062,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{
_activeChannelKey = _channelOrder[0];
}
- else if (_activeChannelKey is not null && !_channelOrder.Contains(_activeChannelKey))
+ else if (_activeChannelKey is not null && !_channelOrder.Contains(_activeChannelKey, StringComparer.Ordinal))
{
_activeChannelKey = _channelOrder.Count > 0 ? _channelOrder[0] : null;
}
@@ -1108,7 +1105,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private static bool ChannelDescriptorsMatch(ChatChannelDescriptor left, ChatChannelDescriptor right)
=> left.Type == right.Type
- && NormalizeKey(left.CustomKey) == NormalizeKey(right.CustomKey)
+ && string.Equals(NormalizeKey(left.CustomKey), NormalizeKey(right.CustomKey), StringComparison.Ordinal)
&& left.WorldId == right.WorldId;
private ChatChannelState EnsureZoneStateLocked()
@@ -1180,6 +1177,3 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private readonly record struct PendingSelfMessage(string ChannelKey, string Message);
}
-
-
-
diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs
index 1bbef90..fdf2ec3 100644
--- a/LightlessSync/Services/DalamudUtilService.cs
+++ b/LightlessSync/Services/DalamudUtilService.cs
@@ -360,7 +360,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public IntPtr GetPlayerCharacterFromCachedTableByIdent(string characterName)
{
- if (_actorObjectService.TryGetActorByHash(characterName, out var actor))
+ if (_actorObjectService.TryGetValidatedActorByHash(characterName, out var actor))
return actor.Address;
return IntPtr.Zero;
}
@@ -639,7 +639,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
internal (string Name, nint Address) FindPlayerByNameHash(string ident)
{
- if (_actorObjectService.TryGetActorByHash(ident, out var descriptor))
+ if (_actorObjectService.TryGetValidatedActorByHash(ident, out var descriptor))
{
return (descriptor.Name, descriptor.Address);
}
diff --git a/LightlessSync/Services/LightFinder/LightFinderPlateHandler.cs b/LightlessSync/Services/LightFinder/LightFinderPlateHandler.cs
index 8002beb..544ada1 100644
--- a/LightlessSync/Services/LightFinder/LightFinderPlateHandler.cs
+++ b/LightlessSync/Services/LightFinder/LightFinderPlateHandler.cs
@@ -1,249 +1,692 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Game.ClientState.Objects.SubKinds;
-using Dalamud.Game.ClientState.Objects.Types;
+using Dalamud.Bindings.ImGui;
+using Dalamud.Game.Addon.Lifecycle;
+using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
+using Dalamud.Game.ClientState.Objects.Enums;
+using Dalamud.Game.Text;
using Dalamud.Interface;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game.Object;
+using FFXIVClientStructs.FFXIV.Client.System.Framework;
+using FFXIVClientStructs.FFXIV.Client.UI;
+using FFXIVClientStructs.FFXIV.Component.GUI;
using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator;
+using LightlessSync.Services.Rendering;
using LightlessSync.UI;
-using Microsoft.Extensions.Hosting;
+using LightlessSync.UI.Services;
+using LightlessSync.Utils;
+using LightlessSync.UtilsEnum.Enum;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Hosting;
+using Pictomancy;
using System.Collections.Immutable;
+using System.Globalization;
using System.Numerics;
+using Task = System.Threading.Tasks.Task;
-namespace LightlessSync.Services.LightFinder
+namespace LightlessSync.Services.LightFinder;
+
+public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscriber
{
- public class LightFinderPlateHandler : IHostedService, IMediatorSubscriber
+ private readonly ILogger _logger;
+ private readonly IAddonLifecycle _addonLifecycle;
+ private readonly IGameGui _gameGui;
+ private readonly IObjectTable _objectTable;
+ private readonly LightlessConfigService _configService;
+ private readonly PairUiService _pairUiService;
+ private readonly LightlessMediator _mediator;
+ public LightlessMediator Mediator => _mediator;
+
+ private readonly IUiBuilder _uiBuilder;
+ private bool _mEnabled;
+ private bool _needsLabelRefresh;
+ private bool _drawSubscribed;
+ private AddonNamePlate* _mpNameplateAddon;
+ private readonly object _labelLock = new();
+ private readonly NameplateBuffers _buffers = new();
+ private int _labelRenderCount;
+
+ private const string DefaultLabelText = "LightFinder";
+ private const SeIconChar DefaultIcon = SeIconChar.Hyadelyn;
+ private static readonly string DefaultIconGlyph = SeIconCharExtensions.ToIconString(DefaultIcon);
+ private static readonly Vector2 DefaultPivot = new(0.5f, 1f);
+
+ private ImmutableHashSet _activeBroadcastingCids = [];
+
+ public LightFinderPlateHandler(
+ ILogger logger,
+ IAddonLifecycle addonLifecycle,
+ IGameGui gameGui,
+ LightlessConfigService configService,
+ LightlessMediator mediator,
+ IObjectTable objectTable,
+ PairUiService pairUiService,
+ IDalamudPluginInterface pluginInterface,
+ PictomancyService pictomancyService)
{
- private readonly ILogger _logger;
- private readonly LightlessConfigService _configService;
- private readonly IDalamudPluginInterface _pluginInterface;
- private readonly IObjectTable _gameObjects;
- private readonly IGameGui _gameGui;
+ _logger = logger;
+ _addonLifecycle = addonLifecycle;
+ _gameGui = gameGui;
+ _configService = configService;
+ _mediator = mediator;
+ _objectTable = objectTable;
+ _pairUiService = pairUiService;
+ _uiBuilder = pluginInterface.UiBuilder ?? throw new ArgumentNullException(nameof(pluginInterface));
+ _ = pictomancyService ?? throw new ArgumentNullException(nameof(pictomancyService));
- private const float _defaultNameplateDistance = 15.0f;
- private ImmutableHashSet _activeBroadcastingCids = [];
- private readonly Dictionary _smoothed = [];
- private readonly float _defaultHeightOffset = 0f;
+ }
- public LightlessMediator Mediator { get; }
-
- public LightFinderPlateHandler(
- ILogger logger,
- LightlessMediator mediator,
- IDalamudPluginInterface dalamudPluginInterface,
- LightlessConfigService configService,
- IObjectTable gameObjects,
- IGameGui gameGui)
+ internal void Init()
+ {
+ if (!_drawSubscribed)
{
- _logger = logger;
- Mediator = mediator;
- _pluginInterface = dalamudPluginInterface;
- _configService = configService;
- _gameObjects = gameObjects;
- _gameGui = gameGui;
+ _uiBuilder.Draw += OnUiBuilderDraw;
+ _drawSubscribed = true;
}
- public Task StartAsync(CancellationToken cancellationToken)
+ EnableNameplate();
+ _mediator.Subscribe(this, OnTick);
+ }
+
+ internal void Uninit()
+ {
+ DisableNameplate();
+ if (_drawSubscribed)
{
- _logger.LogInformation("Starting LightFinderPlateHandler...");
-
- _pluginInterface.UiBuilder.Draw += OnDraw;
-
- _logger.LogInformation("LightFinderPlateHandler started.");
- return Task.CompletedTask;
+ _uiBuilder.Draw -= OnUiBuilderDraw;
+ _drawSubscribed = false;
}
+ ClearLabelBuffer();
+ _mediator.Unsubscribe(this);
+ _mpNameplateAddon = null;
+ }
- public Task StopAsync(CancellationToken cancellationToken)
+ internal void EnableNameplate()
+ {
+ if (!_mEnabled)
{
- _logger.LogInformation("Stopping LightFinderPlateHandler...");
-
- _pluginInterface.UiBuilder.Draw -= OnDraw;
-
- _logger.LogInformation("LightFinderPlateHandler stopped.");
- return Task.CompletedTask;
- }
-
- private unsafe void OnDraw()
- {
- if (!_configService.Current.BroadcastEnabled)
- return;
-
- if (_activeBroadcastingCids.Count == 0)
- return;
-
- var drawList = ImGui.GetForegroundDrawList();
-
- foreach (var obj in _gameObjects.PlayerObjects.OfType())
+ try
{
- //Double check to be sure, should always be true due to OfType filter above
- if (obj is not IPlayerCharacter player)
- continue;
-
- if (player.Address == IntPtr.Zero)
- continue;
-
- var hashedCID = DalamudUtilService.GetHashedCIDFromPlayerPointer(player.Address);
- if (!_activeBroadcastingCids.Contains(hashedCID))
- continue;
-
- //Approximate check if nameplate should be visible (at short distances)
- if (!ShouldApproximateNameplateVisible(player))
- continue;
-
- if (!TryGetApproxNameplateScreenPos(player, out var rawScreenPos))
- continue;
-
- var rawVector3 = new Vector3(rawScreenPos.X, rawScreenPos.Y, 0f);
-
- if (rawVector3 == Vector3.Zero)
- {
- _smoothed.Remove(obj);
- continue;
- }
-
- //Possible have to rework this. Currently just a simple distance check to avoid jitter.
- Vector3 smoothedVector3;
-
- if (_smoothed.TryGetValue(obj, out var lastVector3))
- {
- var deltaVector2 = new Vector2(rawVector3.X - lastVector3.X, rawVector3.Y - lastVector3.Y);
- if (deltaVector2.Length() < 1f)
- smoothedVector3 = lastVector3;
- else
- smoothedVector3 = rawVector3;
- }
- else
- {
- smoothedVector3 = rawVector3;
- }
-
- _smoothed[obj] = smoothedVector3;
-
- var screenPos = new Vector2(smoothedVector3.X, smoothedVector3.Y);
-
- var radiusWorld = Math.Max(player.HitboxRadius, 0.5f);
- var radiusPx = radiusWorld * 8.0f;
- var offsetPx = GetScreenOffset(player);
- var drawPos = new Vector2(screenPos.X, screenPos.Y - offsetPx);
-
- var fillColor = ImGui.GetColorU32(UiSharedService.Color(UIColors.Get("Lightfinder")));
- var outlineColor = ImGui.GetColorU32(UiSharedService.Color(UIColors.Get("LightfinderEdge")));
-
- drawList.AddCircleFilled(drawPos, radiusPx, fillColor);
- drawList.AddCircle(drawPos, radiusPx, outlineColor, 0, 2.0f);
-
- var label = "LightFinder";
- var icon = FontAwesomeIcon.Bullseye.ToIconString();
-
- ImGui.PushFont(UiBuilder.IconFont);
- var iconSize = ImGui.CalcTextSize(icon);
- var iconPos = new Vector2(drawPos.X - iconSize.X / 2f, drawPos.Y - radiusPx - iconSize.Y - 2f);
- drawList.AddText(iconPos, fillColor, icon);
- ImGui.PopFont();
-
- /* var scale = 1.4f;
- var font = ImGui.GetFont();
- var baseFontSize = ImGui.GetFontSize();
- var fontSize = baseFontSize * scale;
-
- var baseTextSize = ImGui.CalcTextSize(label);
- var textSize = baseTextSize * scale;
-
- var textPos = new Vector2(
- drawPos.X - textSize.X / 2f,
- drawPos.Y - radiusPx - textSize.Y - 2f
- );
-
- drawList.AddText(font, fontSize, textPos, fillColor, label); */
+ _addonLifecycle.RegisterListener(AddonEvent.PostDraw, "NamePlate", NameplateDrawDetour);
+ _mEnabled = true;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Unknown error while trying to enable nameplate.");
+ DisableNameplate();
}
}
+ }
- // Get screen offset based on distance to local player (to scale size appropriately)
- // I need to fine tune these values still
- private float GetScreenOffset(IPlayerCharacter player)
+ internal void DisableNameplate()
+ {
+ if (_mEnabled)
{
- var local = _gameObjects.LocalPlayer;
- if (local == null)
- return 32.1f;
+ try
+ {
+ _addonLifecycle.UnregisterListener(NameplateDrawDetour);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Unknown error while unregistering nameplate listener.");
+ }
- var delta = player.Position - local.Position;
- var dist = MathF.Sqrt(delta.X * delta.X + delta.Z * delta.Z);
+ _mEnabled = false;
+ ClearNameplateCaches();
+ }
+ }
- const float minDist = 2.1f;
- const float maxDist = 30.4f;
- dist = Math.Clamp(dist, minDist, maxDist);
-
- var t = 1f - (dist - minDist) / (maxDist - minDist);
-
- const float minOffset = 24.4f;
- const float maxOffset = 56.4f;
- return minOffset + (maxOffset - minOffset) * t;
+ private void NameplateDrawDetour(AddonEvent type, AddonArgs args)
+ {
+ if (args.Addon.Address == nint.Zero)
+ {
+ if (_logger.IsEnabled(LogLevel.Warning))
+ _logger.LogWarning("Nameplate draw detour received a null addon address, skipping update.");
+ return;
}
- private bool TryGetApproxNameplateScreenPos(IPlayerCharacter player, out Vector2 screenPos)
+ var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
+
+ if (_mpNameplateAddon != pNameplateAddon)
{
- screenPos = default;
+ ClearNameplateCaches();
+ _mpNameplateAddon = pNameplateAddon;
+ }
- var worldPos = player.Position;
+ UpdateNameplateNodes();
+ }
- var visualHeight = GetVisualHeight(player);
+ private void UpdateNameplateNodes()
+ {
+ var currentHandle = _gameGui.GetAddonByName("NamePlate");
+ if (currentHandle.Address == nint.Zero)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("NamePlate addon unavailable during update, skipping label refresh.");
+ ClearLabelBuffer();
+ return;
+ }
- worldPos.Y += (visualHeight + 1.2f) + _defaultHeightOffset;
+ var currentAddon = (AddonNamePlate*)currentHandle.Address;
+ if (_mpNameplateAddon == null || currentAddon == null || currentAddon != _mpNameplateAddon)
+ {
+ if (_mpNameplateAddon != null && _logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("Cached NamePlate addon pointer differs from current: waiting for new hook (cached {Cached}, current {Current}).", (IntPtr)_mpNameplateAddon, (IntPtr)currentAddon);
+ return;
+ }
- if (!_gameGui.WorldToScreen(worldPos, out var raw))
- return false;
+ var framework = Framework.Instance();
+ if (framework == null)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("Framework instance unavailable during nameplate update, skipping.");
+ return;
+ }
- screenPos = raw;
+ var uiModule = framework->GetUIModule();
+ if (uiModule == null)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("UI module unavailable during nameplate update, skipping.");
+ return;
+ }
+
+ var ui3DModule = uiModule->GetUI3DModule();
+ if (ui3DModule == null)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("UI3D module unavailable during nameplate update, skipping.");
+ return;
+ }
+
+ var vec = ui3DModule->NamePlateObjectInfoPointers;
+ if (vec.IsEmpty)
+ {
+ ClearLabelBuffer();
+ return;
+ }
+
+ var visibleUserIdsSnapshot = VisibleUserIds;
+ var safeCount = System.Math.Min(ui3DModule->NamePlateObjectInfoCount, vec.Length);
+ var currentConfig = _configService.Current;
+ var labelColor = UIColors.Get("Lightfinder");
+ var edgeColor = UIColors.Get("LightfinderEdge");
+ var scratchCount = 0;
+
+ for (int i = 0; i < safeCount; ++i)
+ {
+ var objectInfoPtr = vec[i];
+ if (objectInfoPtr == null)
+ continue;
+
+ var objectInfo = objectInfoPtr.Value;
+ if (objectInfo == null || objectInfo->GameObject == null)
+ continue;
+
+ var nameplateIndex = objectInfo->NamePlateIndex;
+ if (nameplateIndex < 0 || nameplateIndex >= AddonNamePlate.NumNamePlateObjects)
+ continue;
+
+ var gameObject = objectInfo->GameObject;
+ if ((ObjectKind)gameObject->ObjectKind != ObjectKind.Player)
+ continue;
+
+ // CID gating
+ var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)gameObject);
+ if (cid == null || !_activeBroadcastingCids.Contains(cid))
+ continue;
+
+ var local = _objectTable.LocalPlayer;
+ if (!currentConfig.LightfinderLabelShowOwn && local != null &&
+ objectInfo->GameObject->GetGameObjectId() == local.GameObjectId)
+ continue;
+
+ var hidePaired = !currentConfig.LightfinderLabelShowPaired;
+ var goId = gameObject->GetGameObjectId();
+ if (hidePaired && visibleUserIdsSnapshot.Contains(goId))
+ continue;
+
+ var nameplateObject = _mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
+ var root = nameplateObject.RootComponentNode;
+ var nameContainer = nameplateObject.NameContainer;
+ var nameText = nameplateObject.NameText;
+ var marker = nameplateObject.MarkerIcon;
+
+ if (root == null || root->Component == null || nameContainer == null || nameText == null)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("Nameplate {Index} missing required nodes during update, skipping.", nameplateIndex);
+ continue;
+ }
+
+ root->Component->UldManager.UpdateDrawNodeList();
+
+ bool isVisible =
+ (marker != null && marker->AtkResNode.IsVisible()) ||
+ (nameContainer->IsVisible() && nameText->AtkResNode.IsVisible()) ||
+ currentConfig.LightfinderLabelShowHidden;
+
+ if (!isVisible)
+ continue;
+
+ var scaleMultiplier = System.Math.Clamp(currentConfig.LightfinderLabelScale, 0.5f, 2.0f);
+ var baseScale = currentConfig.LightfinderLabelUseIcon ? 1.0f : 0.5f;
+ var effectiveScale = baseScale * scaleMultiplier;
+ var baseFontSize = currentConfig.LightfinderLabelUseIcon ? 36f : 24f;
+ var targetFontSize = (int)System.Math.Round(baseFontSize * scaleMultiplier);
+ var labelContent = currentConfig.LightfinderLabelUseIcon
+ ? NormalizeIconGlyph(currentConfig.LightfinderLabelIconGlyph)
+ : DefaultLabelText;
+
+ if (!currentConfig.LightfinderLabelUseIcon && (string.IsNullOrWhiteSpace(labelContent) || string.Equals(labelContent, "-", StringComparison.Ordinal)))
+ labelContent = DefaultLabelText;
+
+ var nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
+ var nodeHeight = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale);
+ AlignmentType alignment;
+
+ var textScaleY = nameText->AtkResNode.ScaleY;
+ if (textScaleY <= 0f)
+ textScaleY = 1f;
+
+ var blockHeight = ResolveCache(
+ _buffers.TextHeights,
+ nameplateIndex,
+ System.Math.Abs((int)nameplateObject.TextH),
+ () => GetScaledTextHeight(nameText),
+ nodeHeight);
+
+ var containerHeight = ResolveCache(
+ _buffers.ContainerHeights,
+ nameplateIndex,
+ (int)nameContainer->Height,
+ () =>
+ {
+ var computed = blockHeight + (int)System.Math.Round(8 * textScaleY);
+ return computed <= blockHeight ? blockHeight + 1 : computed;
+ },
+ blockHeight + 1);
+
+ var blockTop = containerHeight - blockHeight;
+ if (blockTop < 0)
+ blockTop = 0;
+ var verticalPadding = (int)System.Math.Round(4 * effectiveScale);
+
+ var positionY = blockTop - verticalPadding;
+
+ var rawTextWidth = (int)nameplateObject.TextW;
+ var textWidth = ResolveCache(
+ _buffers.TextWidths,
+ nameplateIndex,
+ System.Math.Abs(rawTextWidth),
+ () => GetScaledTextWidth(nameText),
+ nodeWidth);
+
+ var textOffset = (int)System.Math.Round(nameText->AtkResNode.X);
+ var hasValidOffset = TryCacheTextOffset(nameplateIndex, rawTextWidth, textOffset);
+
+ if (nameContainer == null)
+ {
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("Nameplate {Index} container became unavailable during update, skipping.", nameplateIndex);
+ continue;
+ }
+
+ float finalX;
+ if (currentConfig.LightfinderAutoAlign)
+ {
+ var measuredWidth = System.Math.Max(1, textWidth > 0 ? textWidth : nodeWidth);
+ var measuredWidthF = (float)measuredWidth;
+ var alignmentType = currentConfig.LabelAlignment;
+
+ var containerScale = nameContainer->ScaleX;
+ if (containerScale <= 0f)
+ containerScale = 1f;
+ var containerWidthRaw = (float)nameContainer->Width;
+ if (containerWidthRaw <= 0f)
+ containerWidthRaw = measuredWidthF;
+ var containerWidth = containerWidthRaw * containerScale;
+ if (containerWidth <= 0f)
+ containerWidth = measuredWidthF;
+
+ var containerLeft = nameContainer->ScreenX;
+ var containerRight = containerLeft + containerWidth;
+ var containerCenter = containerLeft + (containerWidth * 0.5f);
+
+ var iconMargin = currentConfig.LightfinderLabelUseIcon
+ ? System.Math.Min(containerWidth * 0.1f, 14f * containerScale)
+ : 0f;
+
+ switch (alignmentType)
+ {
+ case LabelAlignment.Left:
+ finalX = containerLeft + iconMargin;
+ alignment = AlignmentType.BottomLeft;
+ break;
+ case LabelAlignment.Right:
+ finalX = containerRight - iconMargin;
+ alignment = AlignmentType.BottomRight;
+ break;
+ default:
+ finalX = containerCenter;
+ alignment = AlignmentType.Bottom;
+ break;
+ }
+
+ finalX += currentConfig.LightfinderLabelOffsetX;
+ }
+ else
+ {
+ var cachedTextOffset = _buffers.TextOffsets[nameplateIndex];
+ var hasCachedOffset = cachedTextOffset != int.MinValue;
+ var baseOffsetX = (!currentConfig.LightfinderLabelUseIcon && hasValidOffset && hasCachedOffset) ? cachedTextOffset : 0;
+ finalX = nameContainer->ScreenX + baseOffsetX + 58 + currentConfig.LightfinderLabelOffsetX;
+ alignment = AlignmentType.Bottom;
+ }
+
+ positionY += currentConfig.LightfinderLabelOffsetY;
+ alignment = (AlignmentType)System.Math.Clamp((int)alignment, 0, 8);
+
+ var finalPosition = new Vector2(finalX, nameContainer->ScreenY + positionY);
+ var pivot = (currentConfig.LightfinderAutoAlign || currentConfig.LightfinderLabelUseIcon)
+ ? AlignmentToPivot(alignment)
+ : DefaultPivot;
+ var textColorPacked = PackColor(labelColor);
+ var edgeColorPacked = PackColor(edgeColor);
+
+ _buffers.LabelScratch[scratchCount++] = new NameplateLabelInfo(
+ finalPosition,
+ labelContent,
+ textColorPacked,
+ edgeColorPacked,
+ targetFontSize,
+ pivot,
+ currentConfig.LightfinderLabelUseIcon);
+ }
+
+ lock (_labelLock)
+ {
+ if (scratchCount == 0)
+ {
+ _labelRenderCount = 0;
+ }
+ else
+ {
+ Array.Copy(_buffers.LabelScratch, _buffers.LabelRender, scratchCount);
+ _labelRenderCount = scratchCount;
+ }
+ }
+ }
+
+ private void OnUiBuilderDraw()
+ {
+ if (!_mEnabled)
+ return;
+
+ int copyCount;
+ lock (_labelLock)
+ {
+ copyCount = _labelRenderCount;
+ if (copyCount == 0)
+ return;
+
+ Array.Copy(_buffers.LabelRender, _buffers.LabelCopy, copyCount);
+ }
+
+ using var drawList = PictoService.Draw();
+ if (drawList == null)
+ return;
+
+ for (int i = 0; i < copyCount; ++i)
+ {
+ ref var info = ref _buffers.LabelCopy[i];
+ var font = default(ImFontPtr);
+ if (info.UseIcon)
+ {
+ var ioFonts = ImGui.GetIO().Fonts;
+ font = ioFonts.Fonts.Size > 1 ? new ImFontPtr(ioFonts.Fonts[1]) : ImGui.GetFont();
+ }
+
+ drawList.AddScreenText(info.ScreenPosition, info.Text, info.TextColor, info.FontSize, info.Pivot, info.EdgeColor, font);
+ }
+ }
+
+ private static Vector2 AlignmentToPivot(AlignmentType alignment) => alignment switch
+ {
+ AlignmentType.BottomLeft => new Vector2(0f, 1f),
+ AlignmentType.BottomRight => new Vector2(1f, 1f),
+ AlignmentType.TopLeft => new Vector2(0f, 0f),
+ AlignmentType.TopRight => new Vector2(1f, 0f),
+ AlignmentType.Top => new Vector2(0.5f, 0f),
+ AlignmentType.Left => new Vector2(0f, 0.5f),
+ AlignmentType.Right => new Vector2(1f, 0.5f),
+ _ => DefaultPivot
+ };
+
+ private static uint PackColor(Vector4 color)
+ {
+ var r = (byte)System.Math.Clamp(color.X * 255f, 0f, 255f);
+ var g = (byte)System.Math.Clamp(color.Y * 255f, 0f, 255f);
+ var b = (byte)System.Math.Clamp(color.Z * 255f, 0f, 255f);
+ var a = (byte)System.Math.Clamp(color.W * 255f, 0f, 255f);
+ return (uint)((a << 24) | (b << 16) | (g << 8) | r);
+ }
+
+ private void ClearLabelBuffer()
+ {
+ lock (_labelLock)
+ {
+ _labelRenderCount = 0;
+ }
+ }
+
+ private static unsafe int GetScaledTextHeight(AtkTextNode* node)
+ {
+ if (node == null)
+ return 0;
+
+ var resNode = &node->AtkResNode;
+ var rawHeight = (int)resNode->GetHeight();
+ if (rawHeight <= 0 && node->LineSpacing > 0)
+ rawHeight = node->LineSpacing;
+ if (rawHeight <= 0)
+ rawHeight = AtkNodeHelpers.DefaultTextNodeHeight;
+
+ var scale = resNode->ScaleY;
+ if (scale <= 0f)
+ scale = 1f;
+
+ var computed = (int)System.Math.Round(rawHeight * scale);
+ return System.Math.Max(1, computed);
+ }
+
+ private static unsafe int GetScaledTextWidth(AtkTextNode* node)
+ {
+ if (node == null)
+ return 0;
+
+ var resNode = &node->AtkResNode;
+ var rawWidth = (int)resNode->GetWidth();
+ if (rawWidth <= 0)
+ rawWidth = AtkNodeHelpers.DefaultTextNodeWidth;
+
+ var scale = resNode->ScaleX;
+ if (scale <= 0f)
+ scale = 1f;
+
+ var computed = (int)System.Math.Round(rawWidth * scale);
+ return System.Math.Max(1, computed);
+ }
+
+ private static int ResolveCache(
+ int[] cache,
+ int index,
+ int rawValue,
+ Func fallback,
+ int fallbackWhenZero)
+ {
+ if (rawValue > 0)
+ {
+ cache[index] = rawValue;
+ return rawValue;
+ }
+
+ var cachedValue = cache[index];
+ if (cachedValue > 0)
+ return cachedValue;
+
+ var computed = fallback();
+ if (computed <= 0)
+ computed = fallbackWhenZero;
+
+ cache[index] = computed;
+ return computed;
+ }
+
+ private bool TryCacheTextOffset(int nameplateIndex, int measuredTextWidth, int textOffset)
+ {
+ if (System.Math.Abs(measuredTextWidth) > 0 || textOffset != 0)
+ {
+ _buffers.TextOffsets[nameplateIndex] = textOffset;
return true;
}
- // Approximate check to see if nameplate would be visible based on distance and screen position
- // Also has to be fine tuned still
- private bool ShouldApproximateNameplateVisible(IPlayerCharacter player)
+ return false;
+ }
+
+ internal static string NormalizeIconGlyph(string? rawInput)
+ {
+ if (string.IsNullOrWhiteSpace(rawInput))
+ return DefaultIconGlyph;
+
+ var trimmed = rawInput.Trim();
+
+ if (Enum.TryParse(trimmed, true, out var iconEnum))
+ return SeIconCharExtensions.ToIconString(iconEnum);
+
+ var hexCandidate = trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
+ ? trimmed[2..]
+ : trimmed;
+
+ if (ushort.TryParse(hexCandidate, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hexValue))
+ return char.ConvertFromUtf32(hexValue);
+
+ var enumerator = trimmed.EnumerateRunes();
+ if (enumerator.MoveNext())
+ return enumerator.Current.ToString();
+
+ return DefaultIconGlyph;
+ }
+
+ internal static string ToIconEditorString(string? rawInput)
+ {
+ var normalized = NormalizeIconGlyph(rawInput);
+ var runeEnumerator = normalized.EnumerateRunes();
+ return runeEnumerator.MoveNext()
+ ? runeEnumerator.Current.Value.ToString("X4", CultureInfo.InvariantCulture)
+ : DefaultIconGlyph;
+ }
+ private readonly struct NameplateLabelInfo
+ {
+ public NameplateLabelInfo(
+ Vector2 screenPosition,
+ string text,
+ uint textColor,
+ uint edgeColor,
+ float fontSize,
+ Vector2 pivot,
+ bool useIcon)
{
- var local = _gameObjects.LocalPlayer;
- if (local == null)
- return false;
-
- var delta = player.Position - local.Position;
- var distance2D = MathF.Sqrt(delta.X * delta.X + delta.Z * delta.Z);
- if (distance2D > _defaultNameplateDistance)
- return false;
-
- var verticalDelta = MathF.Abs(delta.Y);
- if (verticalDelta > 3.4f)
- return false;
-
- return TryGetApproxNameplateScreenPos(player, out _);
+ ScreenPosition = screenPosition;
+ Text = text;
+ TextColor = textColor;
+ EdgeColor = edgeColor;
+ FontSize = fontSize;
+ Pivot = pivot;
+ UseIcon = useIcon;
}
- private static unsafe float GetVisualHeight(IPlayerCharacter player)
+ public Vector2 ScreenPosition { get; }
+ public string Text { get; }
+ public uint TextColor { get; }
+ public uint EdgeColor { get; }
+ public float FontSize { get; }
+ public Vector2 Pivot { get; }
+ public bool UseIcon { get; }
+ }
+
+ private HashSet VisibleUserIds
+ => [.. _pairUiService.GetSnapshot().PairsByUid.Values
+ .Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
+ .Select(u => (ulong)u.PlayerCharacterId)];
+
+ public void FlagRefresh()
+ {
+ _needsLabelRefresh = true;
+ }
+
+ public void OnTick(PriorityFrameworkUpdateMessage _)
+ {
+ if (_needsLabelRefresh)
{
- var gameObject = (GameObject*)player.Address;
- if (gameObject == null)
- return Math.Max(player.HitboxRadius * 2.0f, 1.7f); // fallback
-
- // This should account for transformations (sitting, crouching, etc.)
- var radius = gameObject->GetRadius(adjustByTransformation: true);
- if (radius <= 0)
- radius = Math.Max(player.HitboxRadius * 2.0f, 1.7f);
-
- return radius;
- }
-
- // Update the set of active broadcasting CIDs (Same uses as in NameplateHnadler before)
- public void UpdateBroadcastingCids(IEnumerable cids)
- {
- var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
- if (ReferenceEquals(_activeBroadcastingCids, newSet) || _activeBroadcastingCids.SetEquals(newSet))
- return;
-
- _activeBroadcastingCids = newSet;
- if (_logger.IsEnabled(LogLevel.Information))
- _logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids));
+ UpdateNameplateNodes();
+ _needsLabelRefresh = false;
}
}
+
+ public void UpdateBroadcastingCids(IEnumerable cids)
+ {
+ var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
+ if (ReferenceEquals(_activeBroadcastingCids, newSet) || _activeBroadcastingCids.SetEquals(newSet))
+ return;
+
+ _activeBroadcastingCids = newSet;
+ if (_logger.IsEnabled(LogLevel.Information))
+ _logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids));
+ FlagRefresh();
+ }
+
+ public void ClearNameplateCaches()
+ {
+ _buffers.Clear();
+ ClearLabelBuffer();
+ }
+
+ private sealed class NameplateBuffers
+ {
+ public NameplateBuffers()
+ {
+ TextOffsets = new int[AddonNamePlate.NumNamePlateObjects];
+ System.Array.Fill(TextOffsets, int.MinValue);
+ }
+
+ public int[] TextWidths { get; } = new int[AddonNamePlate.NumNamePlateObjects];
+ public int[] TextHeights { get; } = new int[AddonNamePlate.NumNamePlateObjects];
+ public int[] ContainerHeights { get; } = new int[AddonNamePlate.NumNamePlateObjects];
+ public int[] TextOffsets { get; }
+ public NameplateLabelInfo[] LabelScratch { get; } = new NameplateLabelInfo[AddonNamePlate.NumNamePlateObjects];
+ public NameplateLabelInfo[] LabelRender { get; } = new NameplateLabelInfo[AddonNamePlate.NumNamePlateObjects];
+ public NameplateLabelInfo[] LabelCopy { get; } = new NameplateLabelInfo[AddonNamePlate.NumNamePlateObjects];
+
+ public void Clear()
+ {
+ System.Array.Clear(TextWidths, 0, TextWidths.Length);
+ System.Array.Clear(TextHeights, 0, TextHeights.Length);
+ System.Array.Clear(ContainerHeights, 0, ContainerHeights.Length);
+ System.Array.Fill(TextOffsets, int.MinValue);
+ }
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ Init();
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ Uninit();
+ return Task.CompletedTask;
+ }
+
}
\ No newline at end of file
diff --git a/LightlessSync/Services/Rendering/PctDrawListExtensions.cs b/LightlessSync/Services/Rendering/PctDrawListExtensions.cs
new file mode 100644
index 0000000..20bcb45
--- /dev/null
+++ b/LightlessSync/Services/Rendering/PctDrawListExtensions.cs
@@ -0,0 +1,82 @@
+using System.Numerics;
+using System.Reflection;
+using Dalamud.Bindings.ImGui;
+using Pictomancy;
+
+namespace LightlessSync.Services.Rendering;
+
+internal static class PctDrawListExtensions
+{
+ private static readonly FieldInfo? DrawListField = typeof(PctDrawList).GetField("_drawList", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ private static bool TryGetImDrawList(PctDrawList drawList, out ImDrawListPtr ptr)
+ {
+ ptr = default;
+ if (DrawListField == null)
+ return false;
+
+ if (DrawListField.GetValue(drawList) is ImDrawListPtr list)
+ {
+ ptr = list;
+ return true;
+ }
+
+ return false;
+ }
+
+ public static void AddScreenText(this PctDrawList drawList, Vector2 screenPosition, string text, uint color, float fontSize, Vector2? pivot = null, uint? outlineColor = null, ImFontPtr fontOverride = default)
+ {
+ if (drawList == null || string.IsNullOrEmpty(text))
+ return;
+
+ if (!TryGetImDrawList(drawList, out var imDrawList))
+ return;
+
+ var font = fontOverride.IsNull ? ImGui.GetFont() : fontOverride;
+ if (font.IsNull)
+ return;
+
+ var size = MathF.Max(1f, fontSize);
+ var pivotValue = pivot ?? new Vector2(0.5f, 0.5f);
+
+ Vector2 measured;
+ float calcFontSize;
+ if (!fontOverride.IsNull)
+ {
+ ImGui.PushFont(font);
+ measured = ImGui.CalcTextSize(text);
+ calcFontSize = ImGui.GetFontSize();
+ ImGui.PopFont();
+ }
+ else
+ {
+ measured = ImGui.CalcTextSize(text);
+ calcFontSize = ImGui.GetFontSize();
+ }
+
+ if (calcFontSize > 0f && MathF.Abs(size - calcFontSize) > 0.001f)
+ {
+ measured *= size / calcFontSize;
+ }
+
+ var drawPos = screenPosition - measured * pivotValue;
+ if (outlineColor.HasValue)
+ {
+ var thickness = MathF.Max(1f, size / 24f);
+ Span offsets = stackalloc Vector2[4]
+ {
+ new Vector2(1f, 0f),
+ new Vector2(-1f, 0f),
+ new Vector2(0f, 1f),
+ new Vector2(0f, -1f)
+ };
+
+ foreach (var offset in offsets)
+ {
+ imDrawList.AddText(font, size, drawPos + offset * thickness, outlineColor.Value, text);
+ }
+ }
+
+ imDrawList.AddText(font, size, drawPos, color, text);
+ }
+}
diff --git a/LightlessSync/Services/Rendering/PictomancyService.cs b/LightlessSync/Services/Rendering/PictomancyService.cs
new file mode 100644
index 0000000..7d12b4c
--- /dev/null
+++ b/LightlessSync/Services/Rendering/PictomancyService.cs
@@ -0,0 +1,47 @@
+using Dalamud.Plugin;
+using Microsoft.Extensions.Logging;
+using Pictomancy;
+
+namespace LightlessSync.Services.Rendering;
+
+public sealed class PictomancyService : IDisposable
+{
+ private readonly ILogger _logger;
+ private bool _initialized;
+
+ public PictomancyService(ILogger logger, IDalamudPluginInterface pluginInterface)
+ {
+ _logger = logger;
+
+ try
+ {
+ PictoService.Initialize(pluginInterface);
+ _initialized = true;
+ if (_logger.IsEnabled(LogLevel.Debug))
+ _logger.LogDebug("Pictomancy initialized");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to initialize Pictomancy");
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_initialized)
+ return;
+
+ try
+ {
+ PictoService.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to dispose Pictomancy");
+ }
+ finally
+ {
+ _initialized = false;
+ }
+ }
+}
diff --git a/LightlessSync/Services/TextureCompression/TextureDownscaleService.cs b/LightlessSync/Services/TextureCompression/TextureDownscaleService.cs
index b43b1b5..a7b42f5 100644
--- a/LightlessSync/Services/TextureCompression/TextureDownscaleService.cs
+++ b/LightlessSync/Services/TextureCompression/TextureDownscaleService.cs
@@ -1,8 +1,9 @@
-using System;
using System.Collections.Concurrent;
+using System.Buffers;
using System.Buffers.Binary;
using System.Globalization;
using System.IO;
+using System.Runtime.InteropServices;
using OtterTex;
using OtterImage = OtterTex.Image;
using LightlessSync.LightlessConfiguration;
@@ -11,7 +12,6 @@ using Microsoft.Extensions.Logging;
using Lumina.Data.Files;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
/*
* OtterTex made by Ottermandias
@@ -33,6 +33,7 @@ public sealed class TextureDownscaleService
private readonly ConcurrentDictionary _activeJobs = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary _downscaledPaths = new(StringComparer.OrdinalIgnoreCase);
+ private readonly SemaphoreSlim _downscaleSemaphore = new(4);
private static readonly IReadOnlyDictionary BlockCompressedFormatMap =
new Dictionary
{
@@ -80,7 +81,10 @@ public sealed class TextureDownscaleService
if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return;
if (_activeJobs.ContainsKey(hash)) return;
- _activeJobs[hash] = Task.Run(() => DownscaleInternalAsync(hash, filePath, mapKind), CancellationToken.None);
+ _activeJobs[hash] = Task.Run(async () =>
+ {
+ await DownscaleInternalAsync(hash, filePath, mapKind).ConfigureAwait(false);
+ }, CancellationToken.None);
}
public string GetPreferredPath(string hash, string originalPath)
@@ -108,6 +112,7 @@ public sealed class TextureDownscaleService
bool onlyDownscaleUncompressed = false;
bool? isIndexTexture = null;
+ await _downscaleSemaphore.WaitAsync().ConfigureAwait(false);
try
{
if (!File.Exists(sourcePath))
@@ -157,6 +162,15 @@ public sealed class TextureDownscaleService
return;
}
+ if (headerInfo is { } headerValue &&
+ headerValue.Width <= targetMaxDimension &&
+ headerValue.Height <= targetMaxDimension)
+ {
+ _downscaledPaths[hash] = sourcePath;
+ _logger.LogTrace("Skipping downscale for index texture {Hash}; header dimensions {Width}x{Height} within target.", hash, headerValue.Width, headerValue.Height);
+ return;
+ }
+
if (onlyDownscaleUncompressed && headerInfo.HasValue && IsBlockCompressedFormat(headerInfo.Value.Format))
{
_downscaledPaths[hash] = sourcePath;
@@ -172,21 +186,20 @@ public sealed class TextureDownscaleService
var height = rgbaInfo.Meta.Height;
var requiredLength = width * height * bytesPerPixel;
- var rgbaPixels = rgbaScratch.Pixels[..requiredLength].ToArray();
+ var rgbaPixels = rgbaScratch.Pixels.Slice(0, requiredLength);
using var originalImage = SixLabors.ImageSharp.Image.LoadPixelData(rgbaPixels, width, height);
var targetSize = CalculateTargetSize(originalImage.Width, originalImage.Height, targetMaxDimension);
if (targetSize.width == originalImage.Width && targetSize.height == originalImage.Height)
{
+ _downscaledPaths[hash] = sourcePath;
+ _logger.LogTrace("Skipping downscale for index texture {Hash}; already within bounds.", hash);
return;
}
using var resized = IndexDownscaler.Downscale(originalImage, targetSize.width, targetSize.height, BlockMultiple);
- var resizedPixels = new byte[targetSize.width * targetSize.height * 4];
- resized.CopyPixelDataTo(resizedPixels);
-
- using var resizedScratch = ScratchImage.FromRGBA(resizedPixels, targetSize.width, targetSize.height, out var creationInfo).ThrowIfError(creationInfo);
+ using var resizedScratch = CreateScratchImage(resized, targetSize.width, targetSize.height);
using var finalScratch = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm);
TexFileHelper.Save(destination, finalScratch);
@@ -209,6 +222,7 @@ public sealed class TextureDownscaleService
}
finally
{
+ _downscaleSemaphore.Release();
_activeJobs.TryRemove(hash, out _);
}
}
@@ -227,6 +241,41 @@ public sealed class TextureDownscaleService
return (resultWidth, resultHeight);
}
+ private static ScratchImage CreateScratchImage(Image image, int width, int height)
+ {
+ const int BytesPerPixel = 4;
+ var requiredLength = width * height * BytesPerPixel;
+
+ static ScratchImage Create(ReadOnlySpan pixels, int width, int height)
+ {
+ var scratchResult = ScratchImage.FromRGBA(pixels, width, height, out var creationInfo);
+ return scratchResult.ThrowIfError(creationInfo);
+ }
+
+ if (image.DangerousTryGetSinglePixelMemory(out var pixelMemory))
+ {
+ var byteSpan = MemoryMarshal.AsBytes(pixelMemory.Span);
+ if (byteSpan.Length < requiredLength)
+ {
+ throw new InvalidOperationException($"Image buffer shorter than expected ({byteSpan.Length} < {requiredLength}).");
+ }
+
+ return Create(byteSpan.Slice(0, requiredLength), width, height);
+ }
+
+ var rented = ArrayPool.Shared.Rent(requiredLength);
+ try
+ {
+ var rentedSpan = rented.AsSpan(0, requiredLength);
+ image.CopyPixelDataTo(rentedSpan);
+ return Create(rentedSpan, width, height);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(rented);
+ }
+ }
+
private static bool IsIndexMap(TextureMapKind kind)
=> kind is TextureMapKind.Mask
or TextureMapKind.Index;
@@ -420,21 +469,6 @@ public sealed class TextureDownscaleService
private static int ReduceDimension(int value)
=> value <= 1 ? 1 : Math.Max(1, value / 2);
- private static Image ReduceLinearTexture(Image source, int targetWidth, int targetHeight)
- {
- var clone = source.Clone();
-
- while (clone.Width > targetWidth || clone.Height > targetHeight)
- {
- var nextWidth = Math.Max(targetWidth, Math.Max(BlockMultiple, clone.Width / 2));
- var nextHeight = Math.Max(targetHeight, Math.Max(BlockMultiple, clone.Height / 2));
- clone.Mutate(ctx => ctx.Resize(nextWidth, nextHeight, KnownResamplers.Lanczos3));
- }
-
- return clone;
- }
-
-
private static bool ShouldTrim(in TexMeta meta, int targetMaxDimension)
{
var depth = meta.Dimension == TexDimension.Tex3D ? Math.Max(1, meta.Depth) : 1;
@@ -443,12 +477,7 @@ public sealed class TextureDownscaleService
private static bool ShouldTrimDimensions(int width, int height, int depth, int targetMaxDimension)
{
- if (width <= targetMaxDimension || height <= targetMaxDimension)
- {
- return false;
- }
-
- if (depth > 1 && depth <= targetMaxDimension)
+ if (width <= targetMaxDimension && height <= targetMaxDimension && depth <= targetMaxDimension)
{
return false;
}
diff --git a/LightlessSync/UI/Handlers/IdDisplayHandler.cs b/LightlessSync/UI/Handlers/IdDisplayHandler.cs
index 28f3053..b31b145 100644
--- a/LightlessSync/UI/Handlers/IdDisplayHandler.cs
+++ b/LightlessSync/UI/Handlers/IdDisplayHandler.cs
@@ -51,10 +51,11 @@ public class IdDisplayHandler
(bool textIsUid, string playerText) = GetGroupText(group);
if (!string.Equals(_editEntry, group.GID, StringComparison.Ordinal))
{
- ImGui.AlignTextToFramePadding();
-
using (ImRaii.PushFont(UiBuilder.MonoFont, textIsUid))
+ {
+ ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(playerText);
+ }
if (ImGui.IsItemHovered())
{
@@ -121,108 +122,113 @@ public class IdDisplayHandler
if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal))
{
- ImGui.AlignTextToFramePadding();
- var rowStart = ImGui.GetCursorScreenPos();
- var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f);
- var rowRightLimit = rowStart.X + rowWidth;
-
var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont();
+ var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f);
+ float rowRightLimit = 0f;
+ Vector2 nameRectMin = Vector2.Zero;
+ Vector2 nameRectMax = Vector2.Zero;
+ float rowTopForStats = 0f;
+ float frameHeightForStats = 0f;
- Vector4? textColor = null;
- Vector4? glowColor = null;
-
- if (pair.UserData.HasVanity)
- {
- if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex))
- {
- textColor = UIColors.HexToRgba(pair.UserData.TextColorHex);
- }
-
- if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex))
- {
- glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex);
- }
- }
-
- var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null);
- var seString = useVanityColors
- ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor)
- : SeStringUtils.BuildPlain(playerText);
-
- var drawList = ImGui.GetWindowDrawList();
- bool useHighlight = false;
- float highlightPadX = 0f;
- float highlightPadY = 0f;
-
- if (useVanityColors)
- {
- float boost = Luminance.ComputeHighlight(textColor, glowColor);
-
- if (boost > 0f)
- {
- var style = ImGui.GetStyle();
- useHighlight = true;
- highlightPadX = MathF.Max(style.FramePadding.X * 0.6f, 2f * ImGuiHelpers.GlobalScale);
- highlightPadY = MathF.Max(style.FramePadding.Y * 0.55f, 1.25f * ImGuiHelpers.GlobalScale);
- drawList.ChannelsSplit(2);
- drawList.ChannelsSetCurrent(1);
-
- _highlightBoost = boost;
- }
- else
- {
- _highlightBoost = 0f;
- }
- }
-
- Vector2 itemMin;
- Vector2 itemMax;
using (ImRaii.PushFont(font, textIsUid))
{
- SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID);
- itemMin = ImGui.GetItemRectMin();
- itemMax = ImGui.GetItemRectMax();
- }
+ ImGui.AlignTextToFramePadding();
+ var rowStart = ImGui.GetCursorScreenPos();
+ rowRightLimit = rowStart.X + rowWidth;
- if (useHighlight)
+ Vector4? textColor = null;
+ Vector4? glowColor = null;
+
+ if (pair.UserData.HasVanity)
+ {
+ if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex))
+ {
+ textColor = UIColors.HexToRgba(pair.UserData.TextColorHex);
+ }
+
+ if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex))
+ {
+ glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex);
+ }
+ }
+
+ var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null);
+ var seString = useVanityColors
+ ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor)
+ : SeStringUtils.BuildPlain(playerText);
+
+ var drawList = ImGui.GetWindowDrawList();
+ bool useHighlight = false;
+ float highlightPadX = 0f;
+ float highlightPadY = 0f;
+
+ if (useVanityColors)
+ {
+ float boost = Luminance.ComputeHighlight(textColor, glowColor);
+
+ if (boost > 0f)
+ {
+ var style = ImGui.GetStyle();
+ useHighlight = true;
+ highlightPadX = MathF.Max(style.FramePadding.X * 0.6f, 2f * ImGuiHelpers.GlobalScale);
+ highlightPadY = MathF.Max(style.FramePadding.Y * 0.55f, 1.25f * ImGuiHelpers.GlobalScale);
+ drawList.ChannelsSplit(2);
+ drawList.ChannelsSetCurrent(1);
+
+ _highlightBoost = boost;
+ }
+ else
+ {
+ _highlightBoost = 0f;
+ }
+ }
+
+ SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID);
+ nameRectMin = ImGui.GetItemRectMin();
+ nameRectMax = ImGui.GetItemRectMax();
+
+ if (useHighlight)
+ {
+ var style = ImGui.GetStyle();
+ var frameHeight = ImGui.GetFrameHeight();
+ var rowTop = rowStart.Y - style.FramePadding.Y;
+ var rowBottom = rowTop + frameHeight;
+
+ var highlightMin = new Vector2(nameRectMin.X - highlightPadX, rowTop - highlightPadY);
+ var highlightMax = new Vector2(nameRectMax.X + highlightPadX, rowBottom + highlightPadY);
+
+ var windowPos = ImGui.GetWindowPos();
+ var contentMin = windowPos + ImGui.GetWindowContentRegionMin();
+ var contentMax = windowPos + ImGui.GetWindowContentRegionMax();
+ highlightMin.X = MathF.Max(highlightMin.X, contentMin.X);
+ highlightMax.X = MathF.Min(highlightMax.X, contentMax.X);
+ highlightMin.Y = MathF.Max(highlightMin.Y, contentMin.Y);
+ highlightMax.Y = MathF.Min(highlightMax.Y, contentMax.Y);
+
+ var highlightColor = new Vector4(
+ 0.25f + _highlightBoost,
+ 0.25f + _highlightBoost,
+ 0.25f + _highlightBoost,
+ 1f
+ );
+
+ highlightColor = Luminance.BackgroundContrast(textColor, glowColor, highlightColor, ref _currentBg);
+
+ float rounding = style.FrameRounding > 0f ? style.FrameRounding : 5f * ImGuiHelpers.GlobalScale;
+ drawList.ChannelsSetCurrent(0);
+ drawList.AddRectFilled(highlightMin, highlightMax, ImGui.GetColorU32(highlightColor), rounding);
+
+ var borderColor = style.Colors[(int)ImGuiCol.Border];
+ borderColor.W *= 0.25f;
+ drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding);
+ drawList.ChannelsMerge();
+ }
+ }
{
var style = ImGui.GetStyle();
- var frameHeight = ImGui.GetFrameHeight();
- var rowTop = rowStart.Y - style.FramePadding.Y;
- var rowBottom = rowTop + frameHeight;
-
- var highlightMin = new Vector2(itemMin.X - highlightPadX, rowTop - highlightPadY);
- var highlightMax = new Vector2(itemMax.X + highlightPadX, rowBottom + highlightPadY);
-
- var windowPos = ImGui.GetWindowPos();
- var contentMin = windowPos + ImGui.GetWindowContentRegionMin();
- var contentMax = windowPos + ImGui.GetWindowContentRegionMax();
- highlightMin.X = MathF.Max(highlightMin.X, contentMin.X);
- highlightMax.X = MathF.Min(highlightMax.X, contentMax.X);
- highlightMin.Y = MathF.Max(highlightMin.Y, contentMin.Y);
- highlightMax.Y = MathF.Min(highlightMax.Y, contentMax.Y);
-
- var highlightColor = new Vector4(
- 0.25f + _highlightBoost,
- 0.25f + _highlightBoost,
- 0.25f + _highlightBoost,
- 1f
- );
-
- highlightColor = Luminance.BackgroundContrast(textColor, glowColor, highlightColor, ref _currentBg);
-
- float rounding = style.FrameRounding > 0f ? style.FrameRounding : 5f * ImGuiHelpers.GlobalScale;
- drawList.ChannelsSetCurrent(0);
- drawList.AddRectFilled(highlightMin, highlightMax, ImGui.GetColorU32(highlightColor), rounding);
-
- var borderColor = style.Colors[(int)ImGuiCol.Border];
- borderColor.W *= 0.25f;
- drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding);
- drawList.ChannelsMerge();
+ frameHeightForStats = ImGui.GetFrameHeight();
+ rowTopForStats = nameRectMin.Y - style.FramePadding.Y;
}
-
- var nameRectMin = ImGui.GetItemRectMin();
- var nameRectMax = ImGui.GetItemRectMax();
if (ImGui.IsItemHovered())
{
if (!string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal))
@@ -292,12 +298,9 @@ public class IdDisplayHandler
const float compactFontScale = 0.85f;
ImGui.SetWindowFontScale(compactFontScale);
var compactHeight = ImGui.GetTextLineHeight();
- var nameHeight = nameRectMax.Y - nameRectMin.Y;
var targetPos = ImGui.GetCursorScreenPos();
var availableWidth = MathF.Max(rowRightLimit - targetPos.X, 0f);
- var centeredY = nameRectMin.Y + MathF.Max((nameHeight - compactHeight) * 0.5f, 0f);
- float verticalOffset = 1f * ImGuiHelpers.GlobalScale;
- centeredY += verticalOffset;
+ var centeredY = rowTopForStats + MathF.Max((frameHeightForStats - compactHeight) * 0.5f, 0f);
ImGui.SetCursorScreenPos(new Vector2(targetPos.X, centeredY));
var performanceText = string.Empty;
diff --git a/LightlessSync/UI/ZoneChatUi.cs b/LightlessSync/UI/ZoneChatUi.cs
index 697f100..084b55b 100644
--- a/LightlessSync/UI/ZoneChatUi.cs
+++ b/LightlessSync/UI/ZoneChatUi.cs
@@ -48,7 +48,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private readonly ImGuiWindowFlags _unpinnedWindowFlags;
private float _currentWindowOpacity = DefaultWindowOpacity;
private bool _isWindowPinned;
- private bool _showRulesOverlay = true;
+ private bool _showRulesOverlay;
private string? _selectedChannelKey;
private bool _scrollToBottom = true;
@@ -165,7 +165,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
}
}
- private void DrawHeader(ChatChannelSnapshot channel)
+ private static void DrawHeader(ChatChannelSnapshot channel)
{
var prefix = channel.Type == ChatChannelType.Zone ? "Zone" : "Syncshell";
Vector4 color;
@@ -577,12 +577,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.Separator();
ImGui.TextUnformatted("Reason (required)");
- if (ImGui.InputTextMultiline("##chat_report_reason", ref _reportReason, ReportReasonMaxLength, new Vector2(-1, 80f * ImGuiHelpers.GlobalScale)))
+ if (ImGui.InputTextMultiline("##chat_report_reason", ref _reportReason, ReportReasonMaxLength, new Vector2(-1, 80f * ImGuiHelpers.GlobalScale))
+ && _reportReason.Length > ReportReasonMaxLength)
{
- if (_reportReason.Length > ReportReasonMaxLength)
- {
- _reportReason = _reportReason[..(int)ReportReasonMaxLength];
- }
+ _reportReason = _reportReason[..ReportReasonMaxLength];
}
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
@@ -591,12 +589,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.Spacing();
ImGui.TextUnformatted("Additional context (optional)");
- if (ImGui.InputTextMultiline("##chat_report_context", ref _reportAdditionalContext, ReportContextMaxLength, new Vector2(-1, 120f * ImGuiHelpers.GlobalScale)))
+ if (ImGui.InputTextMultiline("##chat_report_context", ref _reportAdditionalContext, ReportContextMaxLength, new Vector2(-1, 120f * ImGuiHelpers.GlobalScale))
+ && _reportAdditionalContext.Length > ReportContextMaxLength)
{
- if (_reportAdditionalContext.Length > ReportContextMaxLength)
- {
- _reportAdditionalContext = _reportAdditionalContext[..(int)ReportContextMaxLength];
- }
+ _reportAdditionalContext = _reportAdditionalContext[..ReportContextMaxLength];
}
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
@@ -768,7 +764,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
}
}
- private bool TryCreateCopyMessageAction(ChatMessageEntry message, out ChatMessageContextAction action)
+ private static bool TryCreateCopyMessageAction(ChatMessageEntry message, out ChatMessageContextAction action)
{
var text = message.Payload.Message;
if (string.IsNullOrEmpty(text))
@@ -920,7 +916,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private void EnsureSelectedChannel(IReadOnlyList channels)
{
- if (_selectedChannelKey is not null && channels.Any(channel => channel.Key == _selectedChannelKey))
+ if (_selectedChannelKey is not null && channels.Any(channel => string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal)))
return;
_selectedChannelKey = channels.Count > 0 ? channels[0].Key : null;
@@ -1264,11 +1260,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
var isSelected = string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal);
var showBadge = !isSelected && channel.UnreadCount > 0;
var isZoneChannel = channel.Type == ChatChannelType.Zone;
- var badgeText = string.Empty;
- var badgePadding = Vector2.Zero;
- var badgeTextSize = Vector2.Zero;
- float badgeWidth = 0f;
- float badgeHeight = 0f;
+ (string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null;
var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault");
var hovered = isSelected
@@ -1285,15 +1277,16 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
if (showBadge)
{
var badgeSpacing = 4f * ImGuiHelpers.GlobalScale;
- badgePadding = new Vector2(4f, 1.5f) * ImGuiHelpers.GlobalScale;
- badgeText = channel.UnreadCount > MaxBadgeDisplay
+ var badgePadding = new Vector2(4f, 1.5f) * ImGuiHelpers.GlobalScale;
+ var badgeText = channel.UnreadCount > MaxBadgeDisplay
? $"{MaxBadgeDisplay}+"
: channel.UnreadCount.ToString(CultureInfo.InvariantCulture);
- badgeTextSize = ImGui.CalcTextSize(badgeText);
- badgeWidth = badgeTextSize.X + badgePadding.X * 2f;
- badgeHeight = badgeTextSize.Y + badgePadding.Y * 2f;
+ var badgeTextSize = ImGui.CalcTextSize(badgeText);
+ var badgeWidth = badgeTextSize.X + badgePadding.X * 2f;
+ var badgeHeight = badgeTextSize.Y + badgePadding.Y * 2f;
var customPadding = new Vector2(baseFramePadding.X + badgeWidth + badgeSpacing, baseFramePadding.Y);
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, customPadding);
+ badgeMetrics = (badgeText, badgeTextSize, badgeWidth, badgeHeight);
}
var clicked = ImGui.Button($"{channel.DisplayName}##chat_channel_{channel.Key}");
@@ -1324,20 +1317,20 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
drawList.AddRect(itemMin, itemMax, borderColorU32, style.FrameRounding, ImDrawFlags.None, borderThickness);
}
- if (showBadge)
+ if (showBadge && badgeMetrics is { } metrics)
{
var buttonSizeY = itemMax.Y - itemMin.Y;
var badgeMin = new Vector2(
itemMin.X + baseFramePadding.X,
- itemMin.Y + (buttonSizeY - badgeHeight) * 0.5f);
- var badgeMax = badgeMin + new Vector2(badgeWidth, badgeHeight);
+ itemMin.Y + (buttonSizeY - metrics.Height) * 0.5f);
+ var badgeMax = badgeMin + new Vector2(metrics.Width, metrics.Height);
var badgeColor = UIColors.Get("DimRed");
var badgeColorU32 = ImGui.ColorConvertFloat4ToU32(badgeColor);
- drawList.AddRectFilled(badgeMin, badgeMax, badgeColorU32, badgeHeight * 0.5f);
+ drawList.AddRectFilled(badgeMin, badgeMax, badgeColorU32, metrics.Height * 0.5f);
var textPos = new Vector2(
- badgeMin.X + (badgeWidth - badgeTextSize.X) * 0.5f,
- badgeMin.Y + (badgeHeight - badgeTextSize.Y) * 0.5f);
- drawList.AddText(textPos, ImGui.ColorConvertFloat4ToU32(ImGuiColors.DalamudWhite), badgeText);
+ badgeMin.X + (metrics.Width - metrics.TextSize.X) * 0.5f,
+ badgeMin.Y + (metrics.Height - metrics.TextSize.Y) * 0.5f);
+ drawList.AddText(textPos, ImGui.ColorConvertFloat4ToU32(ImGuiColors.DalamudWhite), metrics.Text);
}
first = false;
diff --git a/LightlessSync/Utils/SeStringUtils.cs b/LightlessSync/Utils/SeStringUtils.cs
index c8b9a7b..c3e50fd 100644
--- a/LightlessSync/Utils/SeStringUtils.cs
+++ b/LightlessSync/Utils/SeStringUtils.cs
@@ -565,19 +565,30 @@ public static class SeStringUtils
public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null)
{
var drawList = ImGui.GetWindowDrawList();
-
+ var usedFont = font ?? UiBuilder.MonoFont;
var drawParams = new SeStringDrawParams
{
- Font = font ?? UiBuilder.MonoFont,
+ Font = usedFont,
Color = 0xFFFFFFFF,
WrapWidth = float.MaxValue,
TargetDrawList = drawList
};
- ImGui.SetCursorScreenPos(position);
- ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
-
var textSize = ImGui.CalcTextSize(seString.TextValue);
+ if (textSize.Y <= 0f)
+ {
+ textSize.Y = usedFont.FontSize;
+ }
+
+ var style = ImGui.GetStyle();
+ var fontHeight = usedFont.FontSize > 0f ? usedFont.FontSize : ImGui.GetFontSize();
+ var frameHeight = fontHeight + style.FramePadding.Y * 2f;
+ var hitboxHeight = MathF.Max(frameHeight, textSize.Y);
+ var verticalOffset = MathF.Max((hitboxHeight - textSize.Y) * 0.5f, 0f);
+
+ var drawPos = new Vector2(position.X, position.Y + verticalOffset);
+ ImGui.SetCursorScreenPos(drawPos);
+ ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
ImGui.SetCursorScreenPos(position);
if (id is not null)
@@ -591,30 +602,52 @@ public static class SeStringUtils
try
{
- ImGui.InvisibleButton("##hitbox", textSize);
+ ImGui.InvisibleButton("##hitbox", new Vector2(textSize.X, hitboxHeight));
}
finally
{
ImGui.PopID();
}
- return textSize;
+ return new Vector2(textSize.X, hitboxHeight);
}
public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null)
{
var drawList = ImGui.GetWindowDrawList();
+ var usedFont = font ?? UiBuilder.MonoFont;
+ var iconMacro = $"";
- var drawParams = new SeStringDrawParams
+ var measureParams = new SeStringDrawParams
{
- Font = font ?? UiBuilder.MonoFont,
+ Font = usedFont,
Color = 0xFFFFFFFF,
- WrapWidth = float.MaxValue,
- TargetDrawList = drawList
+ WrapWidth = float.MaxValue
};
- var iconMacro = $"";
- var drawResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams);
+ var measureResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, measureParams);
+ var iconSize = measureResult.Size;
+ if (iconSize.Y <= 0f)
+ {
+ iconSize.Y = usedFont.FontSize > 0f ? usedFont.FontSize : ImGui.GetFontSize();
+ }
+
+ var style = ImGui.GetStyle();
+ var fontHeight = usedFont.FontSize > 0f ? usedFont.FontSize : ImGui.GetFontSize();
+ var frameHeight = fontHeight + style.FramePadding.Y * 2f;
+ var hitboxHeight = MathF.Max(frameHeight, iconSize.Y);
+ var verticalOffset = MathF.Max((hitboxHeight - iconSize.Y) * 0.5f, 0f);
+
+ var drawPos = new Vector2(position.X, position.Y + verticalOffset);
+ var drawParams = new SeStringDrawParams
+ {
+ Font = usedFont,
+ Color = 0xFFFFFFFF,
+ WrapWidth = float.MaxValue,
+ TargetDrawList = drawList,
+ ScreenOffset = drawPos
+ };
+ ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams);
ImGui.SetCursorScreenPos(position);
if (id is not null)
@@ -628,14 +661,14 @@ public static class SeStringUtils
try
{
- ImGui.InvisibleButton("##iconHitbox", drawResult.Size);
+ ImGui.InvisibleButton("##iconHitbox", new Vector2(iconSize.X, hitboxHeight));
}
finally
{
ImGui.PopID();
}
- return drawResult.Size;
+ return new Vector2(iconSize.X, hitboxHeight);
}
#region Internal Payloads
diff --git a/LightlessSync/Utils/VariousExtensions.cs b/LightlessSync/Utils/VariousExtensions.cs
index e0fd466..3f47d98 100644
--- a/LightlessSync/Utils/VariousExtensions.cs
+++ b/LightlessSync/Utils/VariousExtensions.cs
@@ -177,7 +177,8 @@ public static class VariousExtensions
if (objectKind != ObjectKind.Player) continue;
bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal);
- if (manipDataDifferent || forceApplyMods)
+ var hasManipulationData = !string.IsNullOrEmpty(newData.ManipulationData);
+ if (manipDataDifferent || (forceApplyMods && hasManipulationData))
{
logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff manip data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModManip);
charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip);
diff --git a/LightlessSync/WebAPI/Files/FileDownloadManager.cs b/LightlessSync/WebAPI/Files/FileDownloadManager.cs
index 181b02a..49dd868 100644
--- a/LightlessSync/WebAPI/Files/FileDownloadManager.cs
+++ b/LightlessSync/WebAPI/Files/FileDownloadManager.cs
@@ -12,7 +12,6 @@ using System.Collections.Concurrent;
using System.Net;
using System.Net.Http.Json;
using LightlessSync.LightlessConfiguration;
-using LightlessSync.Services.PairProcessing;
namespace LightlessSync.WebAPI.Files;
@@ -22,12 +21,10 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private readonly FileCompactor _fileCompactor;
private readonly FileCacheManager _fileDbManager;
private readonly FileTransferOrchestrator _orchestrator;
- private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly LightlessConfigService _configService;
private readonly TextureDownscaleService _textureDownscaleService;
private readonly TextureMetadataHelper _textureMetadataHelper;
private readonly ConcurrentDictionary _activeDownloadStreams;
- private static readonly TimeSpan DownloadStallTimeout = TimeSpan.FromSeconds(30);
private volatile bool _disableDirectDownloads;
private int _consecutiveDirectDownloadFailures;
private bool _lastConfigDirectDownloadsState;
@@ -38,7 +35,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
FileTransferOrchestrator orchestrator,
FileCacheManager fileCacheManager,
FileCompactor fileCompactor,
- PairProcessingLimiter pairProcessingLimiter,
LightlessConfigService configService,
TextureDownscaleService textureDownscaleService, TextureMetadataHelper textureMetadataHelper) : base(logger, mediator)
{
@@ -46,7 +42,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
_orchestrator = orchestrator;
_fileDbManager = fileCacheManager;
_fileCompactor = fileCompactor;
- _pairProcessingLimiter = pairProcessingLimiter;
_configService = configService;
_textureDownscaleService = textureDownscaleService;
_textureMetadataHelper = textureMetadataHelper;
@@ -282,42 +277,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
int bytesRead;
try
{
- using var readCancellation = CancellationTokenSource.CreateLinkedTokenSource(ct);
- var readTask = stream.ReadAsync(buffer.AsMemory(0, buffer.Length), readCancellation.Token).AsTask();
- while (!readTask.IsCompleted)
- {
- var completedTask = await Task.WhenAny(readTask, Task.Delay(DownloadStallTimeout)).ConfigureAwait(false);
- if (completedTask == readTask)
- {
- break;
- }
-
- ct.ThrowIfCancellationRequested();
-
- var snapshot = _pairProcessingLimiter.GetSnapshot();
- if (snapshot.Waiting > 0)
- {
- readCancellation.Cancel();
- try
- {
- await readTask.ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- // expected when cancelling the read due to timeout
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex, "Error finishing read task after stall detection for {requestUrl}", requestUrl);
- }
-
- throw new TimeoutException($"No data received for {DownloadStallTimeout.TotalSeconds} seconds while downloading {requestUrl} (waiting: {snapshot.Waiting})");
- }
-
- Logger.LogTrace("Download stalled for {requestUrl} but no queued pairs, continuing to wait", requestUrl);
- }
-
- bytesRead = await readTask.ConfigureAwait(false);
+ bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), ct).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
@@ -340,11 +300,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
Logger.LogDebug("{requestUrl} downloaded to {destination}", requestUrl, destinationFilename);
}
}
- catch (TimeoutException ex)
- {
- Logger.LogWarning(ex, "Detected stalled download for {requestUrl}, aborting transfer", requestUrl);
- throw;
- }
catch (OperationCanceledException)
{
throw;
diff --git a/LightlessSync/packages.lock.json b/LightlessSync/packages.lock.json
index a109393..daa1008 100644
--- a/LightlessSync/packages.lock.json
+++ b/LightlessSync/packages.lock.json
@@ -521,26 +521,979 @@
"resolved": "17.6.3",
"contentHash": "N0ZIanl1QCgvUumEL1laasU0a7sOE5ZwLZVTn0pAePnfhq8P7SvTjF8Axq+CnavuQkmdQpGNXQ1efZtu5kDFbA=="
},
+ "Microsoft.NETCore.Platforms": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
+ },
+ "Microsoft.NETCore.Targets": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
+ },
+ "Microsoft.Win32.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "NETStandard.Library": {
+ "type": "Transitive",
+ "resolved": "1.6.1",
+ "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.Win32.Primitives": "4.3.0",
+ "System.AppContext": "4.3.0",
+ "System.Collections": "4.3.0",
+ "System.Collections.Concurrent": "4.3.0",
+ "System.Console": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tools": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Calendars": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.Compression": "4.3.0",
+ "System.IO.Compression.ZipFile": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.Linq.Expressions": "4.3.0",
+ "System.Net.Http": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Net.Sockets": "4.3.0",
+ "System.ObjectModel": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Extensions": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.InteropServices.RuntimeInformation": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Security.Cryptography.X509Certificates": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Text.Encoding.Extensions": "4.3.0",
+ "System.Text.RegularExpressions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "System.Threading.Timer": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0",
+ "System.Xml.XDocument": "4.3.0"
+ }
+ },
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q=="
+ },
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA=="
+ },
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw=="
+ },
+ "runtime.native.System": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.IO.Compression": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==",
+ "dependencies": {
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0"
+ }
+ },
+ "runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==",
+ "dependencies": {
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A=="
+ },
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g=="
+ },
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg=="
+ },
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ=="
+ },
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A=="
+ },
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg=="
+ },
+ "SharpDX": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "3pv0LFMvfK/dv1qISJnn8xBeeT6R/FRvr0EV4KI2DGsL84Qlv6P7isWqxGyU0LCwlSVCJN3jgHJ4Bl0KI2PJww==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1"
+ }
+ },
+ "SharpDX.D3DCompiler": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "Rnsd6Ilp127xbXqhTit8WKFQUrXwWxqVGpglyWDNkIBCk0tWXNQEjrJpsl0KAObzyZaa33+EXAikLVt5fnd3GA==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0"
+ }
+ },
+ "SharpDX.Direct2D1": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "Qs8LzDMaQf1u3KB8ArHu9pDv6itZ++QXs99a/bVAG+nKr0Hx5NG4mcN5vsfE0mVR2TkeHfeUm4PksRah6VUPtA==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0",
+ "SharpDX.DXGI": "4.2.0"
+ }
+ },
+ "SharpDX.Direct3D11": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "oTm/iT5X/IIuJ8kNYP+DTC/MhBhqtRF5dbgPPFgLBdQv0BKzNTzXQQXd7SveBFjQg6hXEAJ2jGCAzNYvGFc9LA==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0",
+ "SharpDX.DXGI": "4.2.0"
+ }
+ },
+ "SharpDX.DXGI": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "UjKqkgWc8U+SP+j3LBzFP6OB6Ntapjih7Xo+g1rLcsGbIb5KwewBrBChaUu7sil8rWoeVU/k0EJd3SMN4VqNZw==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0"
+ }
+ },
+ "SharpDX.Mathematics": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "R2pcKLgdsP9p5WyTjHmGOZ0ka0zASAZYc6P4L6rSvjYhf6klGYbent7MiVwbkwkt9dD44p5brjy5IwAnVONWGw==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0"
+ }
+ },
+ "System.AppContext": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==",
+ "dependencies": {
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Collections": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Collections.Concurrent": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Console": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Debug": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Diagnostics.DiagnosticSource": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
"System.Diagnostics.EventLog": {
"type": "Transitive",
"resolved": "9.0.3",
"contentHash": "0nDJBZ06DVdTG2vvCZ4XjazLVaFawdT0pnji23ISX8I8fEOlRJyzH2I0kWiAbCtFwry2Zir4qE4l/GStLATfFw=="
},
+ "System.Diagnostics.Tools": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Tracing": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization.Calendars": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0"
+ }
+ },
+ "System.IO": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.IO.Compression": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Buffers": "4.3.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.IO.Compression": "4.3.0"
+ }
+ },
+ "System.IO.Compression.ZipFile": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==",
+ "dependencies": {
+ "System.Buffers": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.Compression": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Linq": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0"
+ }
+ },
+ "System.Linq.Expressions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.ObjectModel": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Emit": "4.3.0",
+ "System.Reflection.Emit.ILGeneration": "4.3.0",
+ "System.Reflection.Emit.Lightweight": "4.3.0",
+ "System.Reflection.Extensions": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Reflection.TypeExtensions": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
+ "System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.DiagnosticSource": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Extensions": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Security.Cryptography.X509Certificates": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Net.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
"System.Net.ServerSentEvents": {
"type": "Transitive",
"resolved": "9.0.3",
"contentHash": "Vs/C2V27bjtwLqYag9ATzHilcUn8VQTICre4jSBMGFUeSTxEZffTjb+xZwjcmPsVAjmSZmBI5N7Ezq8UFvqQQg=="
},
+ "System.Net.Sockets": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Reflection": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Emit": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==",
+ "dependencies": {
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Emit.ILGeneration": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Emit.ILGeneration": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Emit.Lightweight": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Emit.ILGeneration": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.TypeExtensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Resources.ResourceManager": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "System.Runtime.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.Handles": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.InteropServices": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Runtime.InteropServices.RuntimeInformation": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Extensions": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Threading": "4.3.0",
+ "runtime.native.System": "4.3.0"
+ }
+ },
+ "System.Runtime.Numerics": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==",
+ "dependencies": {
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Algorithms": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.Apple": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Cng": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Csp": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Collections.Concurrent": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==",
+ "dependencies": {
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.X509Certificates": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Calendars": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Cng": "4.3.0",
+ "System.Security.Cryptography.Csp": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Text.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Text.Encoding.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Text.RegularExpressions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Threading": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
"System.Threading.Channels": {
"type": "Transitive",
"resolved": "9.0.3",
"contentHash": "Ao0iegVONKYVw0eWxJv0ArtMVfkFjgyyYKtUXru6xX5H95flSZWW3QCavD4PAgwpc0ETP38kGHaYbPzSE7sw2w=="
},
+ "System.Threading.Tasks": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Threading.Tasks.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Threading.Timer": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Xml.ReaderWriter": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Text.Encoding.Extensions": "4.3.0",
+ "System.Text.RegularExpressions": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "System.Threading.Tasks.Extensions": "4.3.0"
+ }
+ },
+ "System.Xml.XDocument": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tools": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0"
+ }
+ },
"lightlesssync.api": {
"type": "Project",
"dependencies": {
@@ -569,6 +1522,15 @@
},
"penumbra.string": {
"type": "Project"
+ },
+ "pictomancy": {
+ "type": "Project",
+ "dependencies": {
+ "SharpDX.D3DCompiler": "[4.2.0, )",
+ "SharpDX.Direct2D1": "[4.2.0, )",
+ "SharpDX.Direct3D11": "[4.2.0, )",
+ "SharpDX.Mathematics": "[4.2.0, )"
+ }
}
}
}
diff --git a/Pictomancy/Pictomancy/packages.lock.json b/Pictomancy/Pictomancy/packages.lock.json
new file mode 100644
index 0000000..95a3cd4
--- /dev/null
+++ b/Pictomancy/Pictomancy/packages.lock.json
@@ -0,0 +1,970 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net9.0-windows7.0": {
+ "DotNet.ReproducibleBuilds": {
+ "type": "Direct",
+ "requested": "[1.2.25, )",
+ "resolved": "1.2.25",
+ "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg=="
+ },
+ "SharpDX.D3DCompiler": {
+ "type": "Direct",
+ "requested": "[4.2.0, )",
+ "resolved": "4.2.0",
+ "contentHash": "Rnsd6Ilp127xbXqhTit8WKFQUrXwWxqVGpglyWDNkIBCk0tWXNQEjrJpsl0KAObzyZaa33+EXAikLVt5fnd3GA==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0"
+ }
+ },
+ "SharpDX.Direct2D1": {
+ "type": "Direct",
+ "requested": "[4.2.0, )",
+ "resolved": "4.2.0",
+ "contentHash": "Qs8LzDMaQf1u3KB8ArHu9pDv6itZ++QXs99a/bVAG+nKr0Hx5NG4mcN5vsfE0mVR2TkeHfeUm4PksRah6VUPtA==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0",
+ "SharpDX.DXGI": "4.2.0"
+ }
+ },
+ "SharpDX.Direct3D11": {
+ "type": "Direct",
+ "requested": "[4.2.0, )",
+ "resolved": "4.2.0",
+ "contentHash": "oTm/iT5X/IIuJ8kNYP+DTC/MhBhqtRF5dbgPPFgLBdQv0BKzNTzXQQXd7SveBFjQg6hXEAJ2jGCAzNYvGFc9LA==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0",
+ "SharpDX.DXGI": "4.2.0"
+ }
+ },
+ "SharpDX.Mathematics": {
+ "type": "Direct",
+ "requested": "[4.2.0, )",
+ "resolved": "4.2.0",
+ "contentHash": "R2pcKLgdsP9p5WyTjHmGOZ0ka0zASAZYc6P4L6rSvjYhf6klGYbent7MiVwbkwkt9dD44p5brjy5IwAnVONWGw==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0"
+ }
+ },
+ "Microsoft.NETCore.Platforms": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
+ },
+ "Microsoft.NETCore.Targets": {
+ "type": "Transitive",
+ "resolved": "1.1.0",
+ "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
+ },
+ "Microsoft.Win32.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "NETStandard.Library": {
+ "type": "Transitive",
+ "resolved": "1.6.1",
+ "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.Win32.Primitives": "4.3.0",
+ "System.AppContext": "4.3.0",
+ "System.Collections": "4.3.0",
+ "System.Collections.Concurrent": "4.3.0",
+ "System.Console": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tools": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Calendars": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.Compression": "4.3.0",
+ "System.IO.Compression.ZipFile": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.Linq.Expressions": "4.3.0",
+ "System.Net.Http": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Net.Sockets": "4.3.0",
+ "System.ObjectModel": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Extensions": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.InteropServices.RuntimeInformation": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Security.Cryptography.X509Certificates": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Text.Encoding.Extensions": "4.3.0",
+ "System.Text.RegularExpressions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "System.Threading.Timer": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0",
+ "System.Xml.XDocument": "4.3.0"
+ }
+ },
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q=="
+ },
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA=="
+ },
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw=="
+ },
+ "runtime.native.System": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.IO.Compression": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==",
+ "dependencies": {
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0"
+ }
+ },
+ "runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==",
+ "dependencies": {
+ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A=="
+ },
+ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ=="
+ },
+ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g=="
+ },
+ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg=="
+ },
+ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ=="
+ },
+ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A=="
+ },
+ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg=="
+ },
+ "SharpDX": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "3pv0LFMvfK/dv1qISJnn8xBeeT6R/FRvr0EV4KI2DGsL84Qlv6P7isWqxGyU0LCwlSVCJN3jgHJ4Bl0KI2PJww==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1"
+ }
+ },
+ "SharpDX.DXGI": {
+ "type": "Transitive",
+ "resolved": "4.2.0",
+ "contentHash": "UjKqkgWc8U+SP+j3LBzFP6OB6Ntapjih7Xo+g1rLcsGbIb5KwewBrBChaUu7sil8rWoeVU/k0EJd3SMN4VqNZw==",
+ "dependencies": {
+ "NETStandard.Library": "1.6.1",
+ "SharpDX": "4.2.0"
+ }
+ },
+ "System.AppContext": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==",
+ "dependencies": {
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Collections": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Collections.Concurrent": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Console": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Debug": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Diagnostics.DiagnosticSource": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Tools": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Diagnostics.Tracing": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization.Calendars": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Globalization.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0"
+ }
+ },
+ "System.IO": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.IO.Compression": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Buffers": "4.3.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.IO.Compression": "4.3.0"
+ }
+ },
+ "System.IO.Compression.ZipFile": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==",
+ "dependencies": {
+ "System.Buffers": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.Compression": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.IO.FileSystem.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Linq": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0"
+ }
+ },
+ "System.Linq.Expressions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.ObjectModel": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Emit": "4.3.0",
+ "System.Reflection.Emit.ILGeneration": "4.3.0",
+ "System.Reflection.Emit.Lightweight": "4.3.0",
+ "System.Reflection.Extensions": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Reflection.TypeExtensions": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Net.Http": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.DiagnosticSource": "4.3.0",
+ "System.Diagnostics.Tracing": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Extensions": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Security.Cryptography.X509Certificates": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Net.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Net.Sockets": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Net.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Reflection": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Emit": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==",
+ "dependencies": {
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Emit.ILGeneration": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Emit.ILGeneration": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Emit.Lightweight": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Emit.ILGeneration": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Reflection.TypeExtensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Resources.ResourceManager": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Globalization": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0"
+ }
+ },
+ "System.Runtime.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.Handles": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Runtime.InteropServices": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Primitives": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Handles": "4.3.0"
+ }
+ },
+ "System.Runtime.InteropServices.RuntimeInformation": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==",
+ "dependencies": {
+ "System.Reflection": "4.3.0",
+ "System.Reflection.Extensions": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Threading": "4.3.0",
+ "runtime.native.System": "4.3.0"
+ }
+ },
+ "System.Runtime.Numerics": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==",
+ "dependencies": {
+ "System.Globalization": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Algorithms": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.Apple": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Cng": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Csp": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Collections.Concurrent": "4.3.0",
+ "System.Linq": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.OpenSsl": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.Primitives": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==",
+ "dependencies": {
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Security.Cryptography.X509Certificates": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.Globalization.Calendars": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.Handles": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Runtime.Numerics": "4.3.0",
+ "System.Security.Cryptography.Algorithms": "4.3.0",
+ "System.Security.Cryptography.Cng": "4.3.0",
+ "System.Security.Cryptography.Csp": "4.3.0",
+ "System.Security.Cryptography.Encoding": "4.3.0",
+ "System.Security.Cryptography.OpenSsl": "4.3.0",
+ "System.Security.Cryptography.Primitives": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "runtime.native.System": "4.3.0",
+ "runtime.native.System.Net.Http": "4.3.0",
+ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
+ }
+ },
+ "System.Text.Encoding": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Text.Encoding.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0",
+ "System.Text.Encoding": "4.3.0"
+ }
+ },
+ "System.Text.RegularExpressions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==",
+ "dependencies": {
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Threading": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
+ "dependencies": {
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Threading.Tasks": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Threading.Tasks.Extensions": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Threading.Tasks": "4.3.0"
+ }
+ },
+ "System.Threading.Timer": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==",
+ "dependencies": {
+ "Microsoft.NETCore.Platforms": "1.1.0",
+ "Microsoft.NETCore.Targets": "1.1.0",
+ "System.Runtime": "4.3.0"
+ }
+ },
+ "System.Xml.ReaderWriter": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.IO.FileSystem": "4.3.0",
+ "System.IO.FileSystem.Primitives": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Runtime.InteropServices": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Text.Encoding.Extensions": "4.3.0",
+ "System.Text.RegularExpressions": "4.3.0",
+ "System.Threading.Tasks": "4.3.0",
+ "System.Threading.Tasks.Extensions": "4.3.0"
+ }
+ },
+ "System.Xml.XDocument": {
+ "type": "Transitive",
+ "resolved": "4.3.0",
+ "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==",
+ "dependencies": {
+ "System.Collections": "4.3.0",
+ "System.Diagnostics.Debug": "4.3.0",
+ "System.Diagnostics.Tools": "4.3.0",
+ "System.Globalization": "4.3.0",
+ "System.IO": "4.3.0",
+ "System.Reflection": "4.3.0",
+ "System.Resources.ResourceManager": "4.3.0",
+ "System.Runtime": "4.3.0",
+ "System.Runtime.Extensions": "4.3.0",
+ "System.Text.Encoding": "4.3.0",
+ "System.Threading": "4.3.0",
+ "System.Xml.ReaderWriter": "4.3.0"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ffxiv_pictomancy b/ffxiv_pictomancy
new file mode 160000
index 0000000..788bc33
--- /dev/null
+++ b/ffxiv_pictomancy
@@ -0,0 +1 @@
+Subproject commit 788bc339a67e7a3db01a47a954034a83b9c3b61b