2.0.0 #92

Merged
defnotken merged 171 commits from 2.0.0 into master 2025-12-21 17:19:36 +00:00
26 changed files with 3706 additions and 884 deletions
Showing only changes of commit 6cf0e3daed - Show all commits

3
.gitmodules vendored
View File

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

View File

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

View File

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

View File

@@ -79,6 +79,7 @@
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
<ProjectReference Include="..\Penumbra.String\Penumbra.String.csproj" />
<ProjectReference Include="..\ffxiv_pictomancy\Pictomancy\Pictomancy.csproj" />
</ItemGroup>
<ItemGroup>

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
using LightlessSync.API.Data;
namespace LightlessSync.PlayerData.Pairs;
/// <summary>
/// orchestrates the lifecycle of a paired character
/// </summary>
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);
}

View File

@@ -0,0 +1,6 @@
namespace LightlessSync.PlayerData.Pairs;
public interface IPairHandlerAdapterFactory
{
IPairHandlerAdapter Create(string ident);
}

View File

@@ -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;
/// <summary>
/// orchestrates the lifecycle of a paired character
/// handles lifecycle, visibility, queued data, character data for a paired user
/// </summary>
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<ObjectKind, Guid?> _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<PlayerChanges> 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<ObjectKind, HashSet<PlayerChanges>> BuildFullChangeSet(CharacterData characterData)
{
var result = new Dictionary<ObjectKind, HashSet<PlayerChanges>>();
@@ -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<FileReplacementData>? 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<FileReplacementData>? left, IReadOnlyCollection<FileReplacementData>? 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<ObjectKind, HashSet<PlayerChanges>> 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<ObjectKind, HashSet<PlayerChanges>> 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<FileReplacementData> 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<FileReplacementData> 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<ObjectKind, HashSet<PlayerChanges>> 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<HonorificReadyMessage>(this, async (_) =>
Mediator.Subscribe<HonorificReadyMessage>(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<PetNamesReadyMessage>(this, async (_) =>
Mediator.Subscribe<PetNamesReadyMessage>(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<string, string>()).ConfigureAwait(false);
await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, applicationId, penumbraCollection, new Dictionary<string, string>(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<DalamudUtilService>();
return new PairHandlerAdapter(
_loggerFactory.CreateLogger<PairHandlerAdapter>(),
_mediator,
_pairManager,
ident,
_gameObjectHandlerFactory,
_ipcManager,
downloadManager,
_pluginWarningNotificationManager,
dalamudUtilService,
_lifetime,
_fileCacheManager,
_playerPerformanceService,
_pairProcessingLimiter,
_serverConfigManager,
_textureDownscaleService,
_pairStateCache,
_pairPerformanceMetricsCache);
}
}

View File

@@ -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<DalamudUtilService>();
return new PairHandlerAdapter(
_loggerFactory.CreateLogger<PairHandlerAdapter>(),
_mediator,
_pairManager,
ident,
_gameObjectHandlerFactory,
_ipcManager,
downloadManager,
_pluginWarningNotificationManager,
dalamudUtilService,
_lifetime,
_fileCacheManager,
_playerPerformanceService,
_pairProcessingLimiter,
_serverConfigManager,
_textureDownscaleService,
_pairStateCache,
_pairPerformanceMetricsCache);
}
}

View File

@@ -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<CharaDataManager>(),
sp.GetRequiredService<LightlessMediator>()));
services.AddSingleton(sp => new PictomancyService(
sp.GetRequiredService<ILogger<PictomancyService>>(),
pluginInterface));
// Tag (Groups) UIs
services.AddSingleton<SelectPairForTagUi>();
services.AddSingleton<RenamePairTagUi>();
@@ -260,11 +263,14 @@ public sealed class Plugin : IDalamudPlugin
services.AddSingleton(sp => new LightFinderPlateHandler(
sp.GetRequiredService<ILogger<LightFinderPlateHandler>>(),
sp.GetRequiredService<LightlessMediator>(),
pluginInterface,
addonLifecycle,
gameGui,
sp.GetRequiredService<LightlessConfigService>(),
sp.GetRequiredService<LightlessMediator>(),
objectTable,
gameGui));
sp.GetRequiredService<PairUiService>(),
pluginInterface,
sp.GetRequiredService<PictomancyService>()));
services.AddSingleton(sp => new LightFinderScannerService(
sp.GetRequiredService<ILogger<LightFinderScannerService>>(),

View File

@@ -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<nint, ActorDescriptor> _activePlayers = new();
private readonly ConcurrentDictionary<string, ActorDescriptor> _actorsByHash = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ConcurrentDictionary<nint, ActorDescriptor>> _actorsByName = new(StringComparer.Ordinal);
private ActorDescriptor[] _playerCharacterSnapshot = Array.Empty<ActorDescriptor>();
private nint[] _playerAddressSnapshot = Array.Empty<nint>();
private readonly HashSet<nint> _renderedPlayers = new();
private readonly HashSet<nint> _renderedCompanions = new();
private readonly Dictionary<nint, LightlessObjectKind> _ownedObjects = new();
private nint[] _renderedPlayerSnapshot = Array.Empty<nint>();
private nint[] _renderedCompanionSnapshot = Array.Empty<nint>();
private nint[] _ownedObjectSnapshot = Array.Empty<nint>();
private IReadOnlyDictionary<nint, LightlessObjectKind> _ownedObjectMapSnapshot = new Dictionary<nint, LightlessObjectKind>();
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<Character.Delegates.OnInitialize>? _onInitializeHook;
private Hook<Character.Delegates.Terminate>? _onTerminateHook;
@@ -80,16 +61,30 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_framework = framework;
_interop = interop;
_objectTable = objectTable;
_clientState = clientState;
_mediator = mediator;
}
public IReadOnlyList<nint> PlayerAddresses => Volatile.Read(ref _playerAddressSnapshot);
private ActorSnapshot Snapshot => Volatile.Read(ref _snapshot);
public IReadOnlyList<nint> PlayerAddresses => Snapshot.PlayerAddresses;
public IEnumerable<ActorDescriptor> PlayerDescriptors => _activePlayers.Values;
public IReadOnlyList<ActorDescriptor> PlayerCharacterDescriptors => Volatile.Read(ref _playerCharacterSnapshot);
public IReadOnlyList<ActorDescriptor> 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<nint> RenderedPlayerAddresses => Volatile.Read(ref _renderedPlayerSnapshot);
public IReadOnlyList<nint> RenderedCompanionAddresses => Volatile.Read(ref _renderedCompanionSnapshot);
public IReadOnlyList<nint> OwnedObjectAddresses => Volatile.Read(ref _ownedObjectSnapshot);
public IReadOnlyDictionary<nint, LightlessObjectKind> 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<nint> RenderedPlayerAddresses => Snapshot.OwnedObjects.RenderedPlayers;
public IReadOnlyList<nint> RenderedCompanionAddresses => Snapshot.OwnedObjects.RenderedCompanions;
public IReadOnlyList<nint> OwnedObjectAddresses => Snapshot.OwnedObjects.OwnedAddresses;
public IReadOnlyDictionary<nint, LightlessObjectKind> 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<ActorDescriptor>());
Volatile.Write(ref _playerAddressSnapshot, Array.Empty<nint>());
Volatile.Write(ref _renderedPlayerSnapshot, Array.Empty<nint>());
Volatile.Write(ref _renderedCompanionSnapshot, Array.Empty<nint>());
Volatile.Write(ref _ownedObjectSnapshot, Array.Empty<nint>());
Volatile.Write(ref _ownedObjectMapSnapshot, new Dictionary<nint, LightlessObjectKind>());
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<nint> 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<nint, LightlessObjectKind>(_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<nint> EnumerateActiveCharacterAddresses()
private static unsafe List<nint> EnumerateActiveCharacterAddresses()
{
var results = new List<nint>(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<nint> _renderedPlayers = new();
private readonly HashSet<nint> _renderedCompanions = new();
private readonly Dictionary<nint, LightlessObjectKind> _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<nint, LightlessObjectKind>(_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<nint> RenderedPlayers,
IReadOnlyList<nint> RenderedCompanions,
IReadOnlyList<nint> OwnedAddresses,
IReadOnlyDictionary<nint, LightlessObjectKind> Map,
nint LocalPlayer,
nint LocalPet,
nint LocalMinionOrMount,
nint LocalCompanion)
{
public static OwnedObjectSnapshot Empty { get; } = new(
Array.Empty<nint>(),
Array.Empty<nint>(),
Array.Empty<nint>(),
new Dictionary<nint, LightlessObjectKind>(),
nint.Zero,
nint.Zero,
nint.Zero,
nint.Zero);
}
private sealed record ActorSnapshot(
IReadOnlyList<ActorDescriptor> PlayerDescriptors,
IReadOnlyList<nint> PlayerAddresses,
OwnedObjectSnapshot OwnedObjects,
int Generation)
{
public static ActorSnapshot Empty { get; } = new(
Array.Empty<ActorDescriptor>(),
Array.Empty<nint>(),
OwnedObjectSnapshot.Empty,
0);
}
}

View File

@@ -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<string, ChatChannelState> _channels = new(StringComparer.Ordinal);
private readonly List<string> _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<ChatChannelSnapshot> GetChannelsSnapshot()
{
lock (_sync)
using (_sync.EnterScope())
{
var snapshots = new List<ChatChannelSnapshot>(_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<ChatChannelDescriptor> 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<DalamudLogoutMessage>(this, _ => HandleLogout());
Mediator.Subscribe<ZoneSwitchEndMessage>(this, _ => ScheduleZonePresenceUpdate());
Mediator.Subscribe<WorldChangedMessage>(this, _ => ScheduleZonePresenceUpdate(force: true));
Mediator.Subscribe<ConnectedMessage>(this, msg => HandleConnected(msg.Connection));
Mediator.Subscribe<HubReconnectedMessage>(this, _ => HandleConnected(null));
Mediator.Subscribe<ConnectedMessage>(this, _ => HandleConnected());
Mediator.Subscribe<HubReconnectedMessage>(this, _ => HandleConnected());
Mediator.Subscribe<HubReconnectingMessage>(this, _ => HandleReconnecting());
Mediator.Subscribe<DisconnectedMessage>(this, _ => HandleReconnecting());
Mediator.Subscribe<PairUiUpdatedMessage>(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<ZoneChatChannelInfoDto>();
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<ChatChannelDescriptor>();
var descriptorsToLeave = new List<ChatChannelDescriptor>();
lock (_sync)
using (_sync.EnterScope())
{
var remainingGroups = new HashSet<string>(_groupDefinitions.Keys, StringComparer.OrdinalIgnoreCase);
@@ -807,7 +804,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
return;
List<ChatChannelDescriptor> 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);
}

View File

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

View File

@@ -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<LightFinderPlateHandler> _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<string> _activeBroadcastingCids = [];
public LightFinderPlateHandler(
ILogger<LightFinderPlateHandler> logger,
IAddonLifecycle addonLifecycle,
IGameGui gameGui,
LightlessConfigService configService,
LightlessMediator mediator,
IObjectTable objectTable,
PairUiService pairUiService,
IDalamudPluginInterface pluginInterface,
PictomancyService pictomancyService)
{
private readonly ILogger<LightFinderPlateHandler> _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<string> _activeBroadcastingCids = [];
private readonly Dictionary<IGameObject, Vector3> _smoothed = [];
private readonly float _defaultHeightOffset = 0f;
}
public LightlessMediator Mediator { get; }
public LightFinderPlateHandler(
ILogger<LightFinderPlateHandler> 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<PriorityFrameworkUpdateMessage>(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<PriorityFrameworkUpdateMessage>(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<IPlayerCharacter>())
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<int> 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<SeIconChar>(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<ulong> 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<string> 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<string> 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;
}
}

View File

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

View File

@@ -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<PictomancyService> _logger;
private bool _initialized;
public PictomancyService(ILogger<PictomancyService> 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;
}
}
}

View File

@@ -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<string, Task> _activeJobs = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, string> _downscaledPaths = new(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _downscaleSemaphore = new(4);
private static readonly IReadOnlyDictionary<int, TextureCompressionTarget> BlockCompressedFormatMap =
new Dictionary<int, TextureCompressionTarget>
{
@@ -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<Rgba32>(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<Rgba32> image, int width, int height)
{
const int BytesPerPixel = 4;
var requiredLength = width * height * BytesPerPixel;
static ScratchImage Create(ReadOnlySpan<byte> 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<byte>.Shared.Rent(requiredLength);
try
{
var rentedSpan = rented.AsSpan(0, requiredLength);
image.CopyPixelDataTo(rentedSpan);
return Create(rentedSpan, width, height);
}
finally
{
ArrayPool<byte>.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<Rgba32> ReduceLinearTexture(Image<Rgba32> 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;
}

View File

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

View File

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

View File

@@ -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 = $"<icon({iconId})>";
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 = $"<icon({iconId})>";
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

View File

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

View File

@@ -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<ThrottledStream, byte> _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;

View File

@@ -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, )"
}
}
}
}

View File

@@ -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"
}
}
}
}
}

1
ffxiv_pictomancy Submodule

Submodule ffxiv_pictomancy added at 788bc339a6