various 'improvements'

This commit is contained in:
2025-12-11 12:59:32 +09:00
parent 2e14fc2f8f
commit 6cf0e3daed
26 changed files with 3706 additions and 884 deletions

3
.gitmodules vendored
View File

@@ -13,3 +13,6 @@
[submodule "OtterGui"] [submodule "OtterGui"]
path = OtterGui path = OtterGui
url = https://github.com/Ottermandias/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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtterGui", "OtterGui\OtterGui.csproj", "{C77A2833-3FE4-405B-811D-439B1FF859D9}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtterGui", "OtterGui\OtterGui.csproj", "{C77A2833-3FE4-405B-811D-439B1FF859D9}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pictomancy", "ffxiv_pictomancy\Pictomancy\Pictomancy.csproj", "{825F17D8-2704-24F6-DF8B-2542AC92C765}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|x64.Build.0 = Release|x64
{C77A2833-3FE4-405B-811D-439B1FF859D9}.Release|x86.ActiveCfg = Release|x64 {C77A2833-3FE4-405B-811D-439B1FF859D9}.Release|x86.ActiveCfg = Release|x64
{C77A2833-3FE4-405B-811D-439B1FF859D9}.Release|x86.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -426,7 +426,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
() => () =>
{ {
if (!string.IsNullOrEmpty(descriptor.HashedContentId) && if (!string.IsNullOrEmpty(descriptor.HashedContentId) &&
_actorObjectService.TryGetActorByHash(descriptor.HashedContentId, out var current) && _actorObjectService.TryGetValidatedActorByHash(descriptor.HashedContentId, out var current) &&
current.OwnedKind == kind) current.OwnedKind == kind)
{ {
return current.Address; return current.Address;

View File

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

View File

@@ -1,7 +1,6 @@
using LightlessSync.FileCache; using LightlessSync.FileCache;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Services.PairProcessing;
using LightlessSync.Services.TextureCompression; using LightlessSync.Services.TextureCompression;
using LightlessSync.WebAPI.Files; using LightlessSync.WebAPI.Files;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -15,7 +14,6 @@ public class FileDownloadManagerFactory
private readonly FileTransferOrchestrator _fileTransferOrchestrator; private readonly FileTransferOrchestrator _fileTransferOrchestrator;
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
private readonly FileCompactor _fileCompactor; private readonly FileCompactor _fileCompactor;
private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly TextureDownscaleService _textureDownscaleService; private readonly TextureDownscaleService _textureDownscaleService;
private readonly TextureMetadataHelper _textureMetadataHelper; private readonly TextureMetadataHelper _textureMetadataHelper;
@@ -26,7 +24,6 @@ public class FileDownloadManagerFactory
FileTransferOrchestrator fileTransferOrchestrator, FileTransferOrchestrator fileTransferOrchestrator,
FileCacheManager fileCacheManager, FileCacheManager fileCacheManager,
FileCompactor fileCompactor, FileCompactor fileCompactor,
PairProcessingLimiter pairProcessingLimiter,
LightlessConfigService configService, LightlessConfigService configService,
TextureDownscaleService textureDownscaleService, TextureDownscaleService textureDownscaleService,
TextureMetadataHelper textureMetadataHelper) TextureMetadataHelper textureMetadataHelper)
@@ -36,7 +33,6 @@ public class FileDownloadManagerFactory
_fileTransferOrchestrator = fileTransferOrchestrator; _fileTransferOrchestrator = fileTransferOrchestrator;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_fileCompactor = fileCompactor; _fileCompactor = fileCompactor;
_pairProcessingLimiter = pairProcessingLimiter;
_configService = configService; _configService = configService;
_textureDownscaleService = textureDownscaleService; _textureDownscaleService = textureDownscaleService;
_textureMetadataHelper = textureMetadataHelper; _textureMetadataHelper = textureMetadataHelper;
@@ -50,7 +46,6 @@ public class FileDownloadManagerFactory
_fileTransferOrchestrator, _fileTransferOrchestrator,
_fileCacheManager, _fileCacheManager,
_fileCompactor, _fileCompactor,
_pairProcessingLimiter,
_configService, _configService,
_textureDownscaleService, _textureDownscaleService,
_textureMetadataHelper); _textureMetadataHelper);

View File

@@ -94,6 +94,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None; public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None;
public byte Gender { get; private set; } public byte Gender { get; private set; }
public string Name { get; private set; } public string Name { get; private set; }
public uint EntityId { get; private set; } = uint.MaxValue;
public ObjectKind ObjectKind { get; } public ObjectKind ObjectKind { get; }
public byte RaceId { get; private set; } public byte RaceId { get; private set; }
public byte TribeId { get; private set; } public byte TribeId { get; private set; }
@@ -142,6 +143,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
{ {
Address = IntPtr.Zero; Address = IntPtr.Zero;
DrawObjectAddress = IntPtr.Zero; DrawObjectAddress = IntPtr.Zero;
EntityId = uint.MaxValue;
_haltProcessing = false; _haltProcessing = false;
} }
@@ -171,13 +173,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
Address = _getAddress(); Address = _getAddress();
if (Address != IntPtr.Zero) 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; DrawObjectAddress = drawObjAddr;
EntityId = gameObject->EntityId;
CurrentDrawCondition = DrawCondition.None; CurrentDrawCondition = DrawCondition.None;
} }
else else
{ {
DrawObjectAddress = IntPtr.Zero; DrawObjectAddress = IntPtr.Zero;
EntityId = uint.MaxValue;
CurrentDrawCondition = DrawCondition.DrawObjectZero; 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.Utils;
using LightlessSync.WebAPI.Files; using LightlessSync.WebAPI.Files;
using LightlessSync.WebAPI.Files.Models; using LightlessSync.WebAPI.Files.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind; using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
using FileReplacementDataComparer = LightlessSync.PlayerData.Data.FileReplacementDataComparer;
namespace LightlessSync.PlayerData.Pairs; namespace LightlessSync.PlayerData.Pairs;
/// <summary> /// <summary>
/// orchestrates the lifecycle of a paired character /// handles lifecycle, visibility, queued data, character data for a paired user
/// </summary> /// </summary>
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPairHandlerAdapter
{
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
{ {
private sealed record CombatData(Guid ApplicationId, CharacterData CharacterData, bool Forced); 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 PairStateCache _pairStateCache;
private readonly PairPerformanceMetricsCache _performanceMetricsCache; private readonly PairPerformanceMetricsCache _performanceMetricsCache;
private readonly PairManager _pairManager; private readonly PairManager _pairManager;
private CancellationTokenSource? _applicationCancellationTokenSource = new(); private CancellationTokenSource? _applicationCancellationTokenSource;
private Guid _applicationId; private Guid _applicationId;
private Task? _applicationTask; private Task? _applicationTask;
private CharacterData? _cachedData = null; private CharacterData? _cachedData = null;
private GameObjectHandler? _charaHandler; private GameObjectHandler? _charaHandler;
private readonly Dictionary<ObjectKind, Guid?> _customizeIds = []; private readonly Dictionary<ObjectKind, Guid?> _customizeIds = [];
private CombatData? _dataReceivedInDowntime; private CombatData? _dataReceivedInDowntime;
private CancellationTokenSource? _downloadCancellationTokenSource = new(); private CancellationTokenSource? _downloadCancellationTokenSource;
private bool _forceApplyMods = false; private bool _forceApplyMods = false;
private bool _forceFullReapply; private bool _forceFullReapply;
private Dictionary<(string GamePath, string? Hash), string>? _lastAppliedModdedPaths; private Dictionary<(string GamePath, string? Hash), string>? _lastAppliedModdedPaths;
@@ -86,6 +60,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private Guid _penumbraCollection; private Guid _penumbraCollection;
private readonly object _collectionGate = new(); private readonly object _collectionGate = new();
private bool _redrawOnNextApplication = false; private bool _redrawOnNextApplication = false;
private bool _explicitRedrawQueued;
private readonly object _initializationGate = new(); private readonly object _initializationGate = new();
private readonly object _pauseLock = new(); private readonly object _pauseLock = new();
private Task _pauseTransitionTask = Task.CompletedTask; private Task _pauseTransitionTask = Task.CompletedTask;
@@ -183,7 +158,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return; return;
} }
var user = GetPrimaryUserData();
if (LastAppliedDataBytes < 0 || LastAppliedDataTris < 0 if (LastAppliedDataBytes < 0 || LastAppliedDataTris < 0
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0) || LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
{ {
@@ -441,9 +415,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return combined; return combined;
} }
public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero; public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero;
public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero public uint PlayerCharacterId => _charaHandler?.EntityId ?? uint.MaxValue;
? uint.MaxValue
: ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId;
public string? PlayerName { get; private set; } public string? PlayerName { get; private set; }
public string PlayerNameHash => Ident; public string PlayerNameHash => Ident;
@@ -490,14 +462,14 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (shouldForce) if (shouldForce)
{ {
_forceApplyMods = true; _forceApplyMods = true;
_cachedData = null; _forceFullReapply = true;
LastAppliedDataBytes = -1; LastAppliedDataBytes = -1;
LastAppliedDataTris = -1; LastAppliedDataTris = -1;
LastAppliedApproximateVRAMBytes = -1; LastAppliedApproximateVRAMBytes = -1;
LastAppliedApproximateEffectiveVRAMBytes = -1; LastAppliedApproximateEffectiveVRAMBytes = -1;
} }
var sanitized = CloneAndSanitizeLastReceived(out var dataHash); var sanitized = CloneAndSanitizeLastReceived(out _);
if (sanitized is null) if (sanitized is null)
{ {
Logger.LogTrace("Sanitized data null for {Ident}", Ident); Logger.LogTrace("Sanitized data null for {Ident}", Ident);
@@ -746,7 +718,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (characterData is null) if (characterData is null)
{ {
Logger.LogWarning("[BASE-{appBase}] Received null character data, skipping application for {handler}", applicationBase, GetLogIdentifier()); Logger.LogWarning("[BASE-{appBase}] Received null character data, skipping application for {handler}", applicationBase, GetLogIdentifier());
SetUploading(isUploading: false); SetUploading(false);
return; return;
} }
@@ -757,7 +729,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in combat, deferring application"))); "Cannot apply character data: you are in combat, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase); Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(false);
return; return;
} }
@@ -767,7 +739,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are performing music, deferring application"))); "Cannot apply character data: you are performing music, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is performing", applicationBase); Logger.LogDebug("[BASE-{appBase}] Received data but player is performing", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(false);
return; return;
} }
@@ -777,7 +749,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in an instance, deferring application"))); "Cannot apply character data: you are in an instance, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in instance", applicationBase); Logger.LogDebug("[BASE-{appBase}] Received data but player is in instance", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(false);
return; return;
} }
@@ -787,7 +759,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in a cutscene, deferring application"))); "Cannot apply character data: you are in a cutscene, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in a cutscene", applicationBase); Logger.LogDebug("[BASE-{appBase}] Received data but player is in a cutscene", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(false);
return; return;
} }
@@ -797,7 +769,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: you are in GPose, deferring application"))); "Cannot apply character data: you are in GPose, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in GPose", applicationBase); Logger.LogDebug("[BASE-{appBase}] Received data but player is in GPose", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(false);
return; return;
} }
@@ -807,7 +779,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: Penumbra or Glamourer is not available, deferring application"))); "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()); Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while Penumbra/Glamourer unavailable, returning", applicationBase, GetLogIdentifier());
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(false);
return; 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); 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}] 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"); 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; _forceApplyMods = false;
} }
_explicitRedrawQueued = false;
if (_redrawOnNextApplication && charaDataToUpdate.TryGetValue(ObjectKind.Player, out var player)) if (_redrawOnNextApplication && charaDataToUpdate.TryGetValue(ObjectKind.Player, out var player))
{ {
player.Add(PlayerChanges.ForcedRedraw); player.Add(PlayerChanges.ForcedRedraw);
_redrawOnNextApplication = false; _redrawOnNextApplication = false;
_explicitRedrawQueued = true;
} }
if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) 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()); Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, GetPrimaryAliasOrUidSafe());
var forceFullReapply = _forceFullReapply || forceApplyCustomization var forceFullReapply = _forceFullReapply
|| LastAppliedApproximateVRAMBytes < 0 || LastAppliedDataTris < 0; || LastAppliedApproximateVRAMBytes < 0 || LastAppliedDataTris < 0;
DownloadAndApplyCharacter(applicationBase, characterData.DeepClone(), charaDataToUpdate, forceFullReapply); 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")}"; 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) 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); base.Dispose(disposing);
SetUploading(isUploading: false); SetUploading(false);
var name = PlayerName; var name = PlayerName;
var user = GetPrimaryUserDataSafe(); var user = GetPrimaryUserDataSafe();
var alias = GetPrimaryAliasOrUidSafe(); var alias = GetPrimaryAliasOrUidSafe();
@@ -1046,6 +1021,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
break; break;
case PlayerChanges.ForcedRedraw: 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); await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false);
break; 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) private static Dictionary<ObjectKind, HashSet<PlayerChanges>> BuildFullChangeSet(CharacterData characterData)
{ {
var result = new Dictionary<ObjectKind, HashSet<PlayerChanges>>(); var result = new Dictionary<ObjectKind, HashSet<PlayerChanges>>();
@@ -1126,6 +1145,39 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return result; 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) private void DownloadAndApplyCharacter(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData, bool forceFullReapply)
{ {
if (!updatedData.Any()) if (!updatedData.Any())
@@ -1165,7 +1217,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource(); _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource();
var downloadToken = _downloadCancellationTokenSource.Token; 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; private Task? _pairDownloadTask;
@@ -1173,7 +1226,9 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData, 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) bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
{ {
await using var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false); var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
try
{
bool skipDownscaleForPair = ShouldSkipDownscale(); bool skipDownscaleForPair = ShouldSkipDownscale();
var user = GetPrimaryUserData(); var user = GetPrimaryUserData();
Dictionary<(string GamePath, string? Hash), string> moddedPaths; Dictionary<(string GamePath, string? Hash), string> moddedPaths;
@@ -1275,6 +1330,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token); _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token);
} }
finally
{
await concurrencyLease.DisposeAsync().ConfigureAwait(false);
}
}
private async Task ApplyCharacterDataAsync(Guid applicationBase, GameObjectHandler handlerForApply, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData, bool updateModdedPaths, bool updateManip, private async Task ApplyCharacterDataAsync(Guid applicationBase, GameObjectHandler handlerForApply, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData, bool updateModdedPaths, bool updateManip,
Dictionary<(string GamePath, string? Hash), string> moddedPaths, CancellationToken token) Dictionary<(string GamePath, string? Hash), string> moddedPaths, CancellationToken token)
@@ -1416,6 +1476,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{ {
try try
{ {
_forceFullReapply = true;
ApplyCharacterData(appData, cachedData!, forceApplyCustomization: true); ApplyCharacterData(appData, cachedData!, forceApplyCustomization: true);
} }
catch (Exception ex) catch (Exception ex)
@@ -1432,6 +1493,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{ {
try try
{ {
_forceFullReapply = true;
ApplyLastReceivedData(forced: true); ApplyLastReceivedData(forced: true);
} }
catch (Exception ex) catch (Exception ex)
@@ -1468,21 +1530,37 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_serverConfigManager.AutoPopulateNoteForUid(user.UID, name); _serverConfigManager.AutoPopulateNoteForUid(user.UID, name);
} }
Mediator.Subscribe<HonorificReadyMessage>(this, async (_) => Mediator.Subscribe<HonorificReadyMessage>(this, _message =>
{ {
if (string.IsNullOrEmpty(_cachedData?.HonorificData)) return; var honorificData = _cachedData?.HonorificData;
Logger.LogTrace("Reapplying Honorific data for {handler}", GetLogIdentifier()); if (string.IsNullOrEmpty(honorificData))
await _ipcManager.Honorific.SetTitleAsync(PlayerCharacter, _cachedData.HonorificData).ConfigureAwait(false); return;
_ = ReapplyHonorificAsync(honorificData!);
}); });
Mediator.Subscribe<PetNamesReadyMessage>(this, async (_) => Mediator.Subscribe<PetNamesReadyMessage>(this, _message =>
{ {
if (string.IsNullOrEmpty(_cachedData?.PetNamesData)) return; var petNamesData = _cachedData?.PetNamesData;
Logger.LogTrace("Reapplying Pet Names data for {handler}", GetLogIdentifier()); if (string.IsNullOrEmpty(petNamesData))
await _ipcManager.PetNames.SetPlayerData(PlayerCharacter, _cachedData.PetNamesData).ConfigureAwait(false); 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) private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId, CancellationToken cancelToken)
{ {
nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Ident); nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Ident);
@@ -1572,15 +1650,12 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
var fileCache = _fileDbManager.GetFileCacheByHash(item.Hash); 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); 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); _fileDbManager.RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath);
fileCache = null; fileCache = null;
} }
}
if (fileCache != null) if (fileCache != null)
{ {
@@ -1701,7 +1776,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (penumbraCollection != Guid.Empty) if (penumbraCollection != Guid.Empty)
{ {
await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, penumbraCollection, character.ObjectIndex).ConfigureAwait(false); 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); 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.CharaData;
using LightlessSync.Services.Events; using LightlessSync.Services.Events;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Services.Rendering;
using LightlessSync.Services.ServerConfiguration; using LightlessSync.Services.ServerConfiguration;
using LightlessSync.Services.TextureCompression; using LightlessSync.Services.TextureCompression;
using LightlessSync.UI; using LightlessSync.UI;
@@ -33,8 +34,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NReco.Logging.File; using NReco.Logging.File;
using System;
using System.IO;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Reflection; using System.Reflection;
using OtterTex; using OtterTex;
@@ -178,6 +177,10 @@ public sealed class Plugin : IDalamudPlugin
sp.GetRequiredService<CharaDataManager>(), sp.GetRequiredService<CharaDataManager>(),
sp.GetRequiredService<LightlessMediator>())); sp.GetRequiredService<LightlessMediator>()));
services.AddSingleton(sp => new PictomancyService(
sp.GetRequiredService<ILogger<PictomancyService>>(),
pluginInterface));
// Tag (Groups) UIs // Tag (Groups) UIs
services.AddSingleton<SelectPairForTagUi>(); services.AddSingleton<SelectPairForTagUi>();
services.AddSingleton<RenamePairTagUi>(); services.AddSingleton<RenamePairTagUi>();
@@ -260,11 +263,14 @@ public sealed class Plugin : IDalamudPlugin
services.AddSingleton(sp => new LightFinderPlateHandler( services.AddSingleton(sp => new LightFinderPlateHandler(
sp.GetRequiredService<ILogger<LightFinderPlateHandler>>(), sp.GetRequiredService<ILogger<LightFinderPlateHandler>>(),
sp.GetRequiredService<LightlessMediator>(), addonLifecycle,
pluginInterface, gameGui,
sp.GetRequiredService<LightlessConfigService>(), sp.GetRequiredService<LightlessConfigService>(),
sp.GetRequiredService<LightlessMediator>(),
objectTable, objectTable,
gameGui)); sp.GetRequiredService<PairUiService>(),
pluginInterface,
sp.GetRequiredService<PictomancyService>()));
services.AddSingleton(sp => new LightFinderScannerService( services.AddSingleton(sp => new LightFinderScannerService(
sp.GetRequiredService<ILogger<LightFinderScannerService>>(), sp.GetRequiredService<ILogger<LightFinderScannerService>>(),

View File

@@ -1,27 +1,20 @@
using LightlessSync; using System.Collections.Concurrent;
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 Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.Interop;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
using FFXIVClientStructs.Interop; using LightlessObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
using System.Threading;
namespace LightlessSync.Services.ActorTracking; namespace LightlessSync.Services.ActorTracking;
public sealed unsafe class ActorObjectService : IHostedService, IDisposable public sealed class ActorObjectService : IHostedService, IDisposable
{ {
public readonly record struct ActorDescriptor( public readonly record struct ActorDescriptor(
string Name, string Name,
@@ -38,25 +31,13 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly IGameInteropProvider _interop; private readonly IGameInteropProvider _interop;
private readonly IObjectTable _objectTable; private readonly IObjectTable _objectTable;
private readonly IClientState _clientState;
private readonly LightlessMediator _mediator; private readonly LightlessMediator _mediator;
private readonly ConcurrentDictionary<nint, ActorDescriptor> _activePlayers = new(); private readonly ConcurrentDictionary<nint, ActorDescriptor> _activePlayers = new();
private readonly ConcurrentDictionary<string, ActorDescriptor> _actorsByHash = new(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, ActorDescriptor> _actorsByHash = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ConcurrentDictionary<nint, ActorDescriptor>> _actorsByName = new(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, ConcurrentDictionary<nint, ActorDescriptor>> _actorsByName = new(StringComparer.Ordinal);
private ActorDescriptor[] _playerCharacterSnapshot = Array.Empty<ActorDescriptor>(); private readonly OwnedObjectTracker _ownedTracker = new();
private nint[] _playerAddressSnapshot = Array.Empty<nint>(); private ActorSnapshot _snapshot = ActorSnapshot.Empty;
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 Hook<Character.Delegates.OnInitialize>? _onInitializeHook; private Hook<Character.Delegates.OnInitialize>? _onInitializeHook;
private Hook<Character.Delegates.Terminate>? _onTerminateHook; private Hook<Character.Delegates.Terminate>? _onTerminateHook;
@@ -80,16 +61,30 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_framework = framework; _framework = framework;
_interop = interop; _interop = interop;
_objectTable = objectTable; _objectTable = objectTable;
_clientState = clientState;
_mediator = mediator; _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 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 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) public bool TryGetPlayerByName(string name, out ActorDescriptor descriptor)
{ {
descriptor = default; descriptor = default;
@@ -100,6 +95,9 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
ActorDescriptor? best = null; ActorDescriptor? best = null;
foreach (var candidate in entries.Values) foreach (var candidate in entries.Values)
{ {
if (!ValidateDescriptorThreadSafe(candidate))
continue;
if (best is null || IsBetterNameMatch(candidate, best.Value)) if (best is null || IsBetterNameMatch(candidate, best.Value))
{ {
best = candidate; best = candidate;
@@ -115,23 +113,54 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return false; return false;
} }
public bool HooksActive => _hooksActive; public bool HooksActive => _hooksActive;
public IReadOnlyList<nint> RenderedPlayerAddresses => Volatile.Read(ref _renderedPlayerSnapshot); public IReadOnlyList<nint> RenderedPlayerAddresses => Snapshot.OwnedObjects.RenderedPlayers;
public IReadOnlyList<nint> RenderedCompanionAddresses => Volatile.Read(ref _renderedCompanionSnapshot); public IReadOnlyList<nint> RenderedCompanionAddresses => Snapshot.OwnedObjects.RenderedCompanions;
public IReadOnlyList<nint> OwnedObjectAddresses => Volatile.Read(ref _ownedObjectSnapshot); public IReadOnlyList<nint> OwnedObjectAddresses => Snapshot.OwnedObjects.OwnedAddresses;
public IReadOnlyDictionary<nint, LightlessObjectKind> OwnedObjects => Volatile.Read(ref _ownedObjectMapSnapshot); public IReadOnlyDictionary<nint, LightlessObjectKind> OwnedObjects => Snapshot.OwnedObjects.Map;
public nint LocalPlayerAddress => Volatile.Read(ref _localPlayerAddress); public nint LocalPlayerAddress => Snapshot.OwnedObjects.LocalPlayer;
public nint LocalPetAddress => Volatile.Read(ref _localPetAddress); public nint LocalPetAddress => Snapshot.OwnedObjects.LocalPet;
public nint LocalMinionOrMountAddress => Volatile.Read(ref _localMinionMountAddress); public nint LocalMinionOrMountAddress => Snapshot.OwnedObjects.LocalMinionOrMount;
public nint LocalCompanionAddress => Volatile.Read(ref _localCompanionAddress); 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) public bool TryGetOwnedObject(LightlessObjectKind kind, out nint address)
{ {
var ownedSnapshot = Snapshot.OwnedObjects;
address = kind switch address = kind switch
{ {
LightlessObjectKind.Player => Volatile.Read(ref _localPlayerAddress), LightlessObjectKind.Player => ownedSnapshot.LocalPlayer,
LightlessObjectKind.Pet => Volatile.Read(ref _localPetAddress), LightlessObjectKind.Pet => ownedSnapshot.LocalPet,
LightlessObjectKind.MinionOrMount => Volatile.Read(ref _localMinionMountAddress), LightlessObjectKind.MinionOrMount => ownedSnapshot.LocalMinionOrMount,
LightlessObjectKind.Companion => Volatile.Read(ref _localCompanionAddress), LightlessObjectKind.Companion => ownedSnapshot.LocalCompanion,
_ => nint.Zero _ => nint.Zero
}; };
@@ -158,7 +187,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
public bool TryGetPlayerAddressByHash(string hash, out nint address) 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; address = descriptor.Address;
return true; return true;
@@ -168,6 +197,50 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return false; 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) public void RefreshTrackedActors(bool force = false)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@@ -185,7 +258,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
} }
else else
{ {
_framework.RunOnFrameworkThread(RefreshTrackedActorsInternal); _ = _framework.RunOnFrameworkThread(RefreshTrackedActorsInternal);
} }
} }
@@ -211,23 +284,12 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_activePlayers.Clear(); _activePlayers.Clear();
_actorsByHash.Clear(); _actorsByHash.Clear();
_actorsByName.Clear(); _actorsByName.Clear();
Volatile.Write(ref _playerCharacterSnapshot, Array.Empty<ActorDescriptor>()); _ownedTracker.Reset();
Volatile.Write(ref _playerAddressSnapshot, Array.Empty<nint>()); Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
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();
return Task.CompletedTask; return Task.CompletedTask;
} }
private void InitializeHooks() private unsafe void InitializeHooks()
{ {
if (_hooksActive) if (_hooksActive)
return; return;
@@ -271,7 +333,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
}); });
} }
private void OnCharacterInitialized(Character* chara) private unsafe void OnCharacterInitialized(Character* chara)
{ {
try try
{ {
@@ -285,7 +347,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
QueueFrameworkUpdate(() => TrackGameObject((GameObject*)chara)); QueueFrameworkUpdate(() => TrackGameObject((GameObject*)chara));
} }
private void OnCharacterTerminated(Character* chara) private unsafe void OnCharacterTerminated(Character* chara)
{ {
var address = (nint)chara; var address = (nint)chara;
QueueFrameworkUpdate(() => UntrackGameObject(address)); 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; var address = (nint)chara;
QueueFrameworkUpdate(() => UntrackGameObject(address)); 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) if (gameObject == null)
return; return;
@@ -332,14 +394,10 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
if (_activePlayers.TryGetValue(descriptor.Address, out var existing)) if (_activePlayers.TryGetValue(descriptor.Address, out var existing))
{ {
RemoveDescriptorFromIndexes(existing); RemoveDescriptor(existing);
RemoveDescriptorFromCollections(existing);
} }
_activePlayers[descriptor.Address] = descriptor; AddDescriptor(descriptor);
IndexDescriptor(descriptor);
AddDescriptorToCollections(descriptor);
RebuildSnapshots();
if (_logger.IsEnabled(LogLevel.Debug)) if (_logger.IsEnabled(LogLevel.Debug))
{ {
@@ -355,16 +413,16 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_mediator.Publish(new ActorTrackedMessage(descriptor)); _mediator.Publish(new ActorTrackedMessage(descriptor));
} }
private ActorDescriptor? BuildDescriptor(GameObject* gameObject, DalamudObjectKind objectKind) private unsafe ActorDescriptor? BuildDescriptor(GameObject* gameObject, DalamudObjectKind objectKind)
{ {
if (gameObject == null) if (gameObject == null)
return null; return null;
var address = (nint)gameObject; var address = (nint)gameObject;
string name = string.Empty; string name = string.Empty;
ushort objectIndex = (ushort)gameObject->ObjectIndex; ushort objectIndex = gameObject->ObjectIndex;
bool isInGpose = objectIndex >= 200; bool isInGpose = objectIndex >= 200;
bool isLocal = _clientState.LocalPlayer?.Address == address; bool isLocal = _objectTable.LocalPlayer?.Address == address;
string hashedCid = string.Empty; string hashedCid = string.Empty;
if (_objectTable.CreateObjectReference(address) is IPlayerCharacter playerCharacter) if (_objectTable.CreateObjectReference(address) is IPlayerCharacter playerCharacter)
@@ -372,7 +430,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
name = playerCharacter.Name.TextValue ?? string.Empty; name = playerCharacter.Name.TextValue ?? string.Empty;
objectIndex = playerCharacter.ObjectIndex; objectIndex = playerCharacter.ObjectIndex;
isInGpose = objectIndex >= 200; isInGpose = objectIndex >= 200;
isLocal = playerCharacter.Address == _clientState.LocalPlayer?.Address; isLocal = playerCharacter.Address == _objectTable.LocalPlayer?.Address;
} }
else else
{ {
@@ -389,7 +447,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return new ActorDescriptor(name, hashedCid, address, objectIndex, isLocal, isInGpose, objectKind, ownedKind, ownerEntityId); 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) if (gameObject == null)
return (null, 0); return (null, 0);
@@ -406,7 +464,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return (LightlessObjectKind.Player, entityId); return (LightlessObjectKind.Player, entityId);
} }
if (_clientState.LocalPlayer is not { } localPlayer) if (_objectTable.LocalPlayer is not { } localPlayer)
return (null, 0); return (null, 0);
var ownerId = gameObject->OwnerId; var ownerId = gameObject->OwnerId;
@@ -453,9 +511,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
if (_activePlayers.TryRemove(address, out var descriptor)) if (_activePlayers.TryRemove(address, out var descriptor))
{ {
RemoveDescriptorFromIndexes(descriptor); RemoveDescriptor(descriptor);
RemoveDescriptorFromCollections(descriptor);
RebuildSnapshots();
if (_logger.IsEnabled(LogLevel.Debug)) if (_logger.IsEnabled(LogLevel.Debug))
{ {
_logger.LogDebug("Actor untracked: {Name} addr={Address:X} idx={Index} owned={OwnedKind}", _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(); var addresses = EnumerateActiveCharacterAddresses();
HashSet<nint> seen = new(addresses.Count); HashSet<nint> seen = new(addresses.Count);
@@ -524,7 +580,10 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return candidate.ObjectIndex < current.ObjectIndex; 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 try
{ {
@@ -538,7 +597,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
QueueFrameworkUpdate(() => TrackGameObject((GameObject*)companion)); QueueFrameworkUpdate(() => TrackGameObject((GameObject*)companion));
} }
private void OnCompanionTerminated(Companion* companion) private unsafe void OnCompanionTerminated(Companion* companion)
{ {
var address = (nint)companion; var address = (nint)companion;
QueueFrameworkUpdate(() => UntrackGameObject(address)); QueueFrameworkUpdate(() => UntrackGameObject(address));
@@ -559,9 +618,9 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
_actorsByHash.TryRemove(descriptor.HashedContentId, out _); _actorsByHash.TryRemove(descriptor.HashedContentId, out _);
} }
if (descriptor.ObjectKind == DalamudObjectKind.Player && !string.IsNullOrEmpty(descriptor.Name)) if (descriptor.ObjectKind == DalamudObjectKind.Player
{ && !string.IsNullOrEmpty(descriptor.Name)
if (_actorsByName.TryGetValue(descriptor.Name, out var bucket)) && _actorsByName.TryGetValue(descriptor.Name, out var bucket))
{ {
bucket.TryRemove(descriptor.Address, out _); bucket.TryRemove(descriptor.Address, out _);
if (bucket.IsEmpty) if (bucket.IsEmpty)
@@ -570,96 +629,35 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
} }
} }
} }
private void AddDescriptor(ActorDescriptor descriptor)
{
_activePlayers[descriptor.Address] = descriptor;
IndexDescriptor(descriptor);
_ownedTracker.OnDescriptorAdded(descriptor);
PublishSnapshot();
} }
private void AddDescriptorToCollections(ActorDescriptor descriptor) private void RemoveDescriptor(ActorDescriptor descriptor)
{ {
if (descriptor.ObjectKind == DalamudObjectKind.Player) RemoveDescriptorFromIndexes(descriptor);
{ _ownedTracker.OnDescriptorRemoved(descriptor);
_renderedPlayers.Add(descriptor.Address); PublishSnapshot();
if (descriptor.IsLocalPlayer)
{
Volatile.Write(ref _localPlayerAddress, descriptor.Address);
}
}
else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
{
_renderedCompanions.Add(descriptor.Address);
} }
if (descriptor.OwnedKind is { } ownedKind) private void PublishSnapshot()
{
_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;
}
}
}
private void RemoveDescriptorFromCollections(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;
}
}
}
private void RebuildSnapshots()
{ {
var playerDescriptors = _activePlayers.Values var playerDescriptors = _activePlayers.Values
.Where(descriptor => descriptor.ObjectKind == DalamudObjectKind.Player) .Where(descriptor => descriptor.ObjectKind == DalamudObjectKind.Player)
.ToArray(); .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); var ownedSnapshot = _ownedTracker.CreateSnapshot();
Volatile.Write(ref _playerAddressSnapshot, playerDescriptors.Select(d => d.Address).ToArray()); var nextGeneration = Snapshot.Generation + 1;
Volatile.Write(ref _renderedPlayerSnapshot, _renderedPlayers.ToArray()); var snapshot = new ActorSnapshot(playerDescriptors, playerAddresses, ownedSnapshot, nextGeneration);
Volatile.Write(ref _renderedCompanionSnapshot, _renderedCompanions.ToArray()); Volatile.Write(ref _snapshot, snapshot);
Volatile.Write(ref _ownedObjectSnapshot, _ownedObjects.Keys.ToArray());
Volatile.Write(ref _ownedObjectMapSnapshot, new Dictionary<nint, LightlessObjectKind>(_ownedObjects));
} }
private void QueueFrameworkUpdate(Action action) private void QueueFrameworkUpdate(Action action)
@@ -673,7 +671,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return; return;
} }
_framework.RunOnFrameworkThread(action); _ = _framework.RunOnFrameworkThread(action);
} }
private void DisposeHooks() private void DisposeHooks()
@@ -723,7 +721,7 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
or DalamudObjectKind.Companion or DalamudObjectKind.Companion
or DalamudObjectKind.MountType; or DalamudObjectKind.MountType;
private static List<nint> EnumerateActiveCharacterAddresses() private static unsafe List<nint> EnumerateActiveCharacterAddresses()
{ {
var results = new List<nint>(64); var results = new List<nint>(64);
var manager = GameObjectManager.Instance(); var manager = GameObjectManager.Instance();
@@ -751,4 +749,170 @@ public sealed unsafe class ActorObjectService : IHostedService, IDisposable
return results; 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.API.Dto.Chat;
using LightlessSync.Services.ActorTracking; using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
@@ -21,12 +20,11 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private const int MaxReportContextLength = 1000; private const int MaxReportContextLength = 1000;
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly ChatConfigService _chatConfigService;
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private readonly ActorObjectService _actorObjectService; private readonly ActorObjectService _actorObjectService;
private readonly PairUiService _pairUiService; 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 Dictionary<string, ChatChannelState> _channels = new(StringComparer.Ordinal);
private readonly List<string> _channelOrder = new(); private readonly List<string> _channelOrder = new();
@@ -55,7 +53,6 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
PairUiService pairUiService) PairUiService pairUiService)
: base(logger, mediator) : base(logger, mediator)
{ {
_chatConfigService = chatConfigService;
_apiController = apiController; _apiController = apiController;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_actorObjectService = actorObjectService; _actorObjectService = actorObjectService;
@@ -63,12 +60,12 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
_isLoggedIn = _dalamudUtilService.IsLoggedIn; _isLoggedIn = _dalamudUtilService.IsLoggedIn;
_isConnected = _apiController.IsConnected; _isConnected = _apiController.IsConnected;
_chatEnabled = _chatConfigService.Current.AutoEnableChatOnLogin; _chatEnabled = chatConfigService.Current.AutoEnableChatOnLogin;
} }
public IReadOnlyList<ChatChannelSnapshot> GetChannelsSnapshot() public IReadOnlyList<ChatChannelSnapshot> GetChannelsSnapshot()
{ {
lock (_sync) using (_sync.EnterScope())
{ {
var snapshots = new List<ChatChannelSnapshot>(_channelOrder.Count); var snapshots = new List<ChatChannelSnapshot>(_channelOrder.Count);
foreach (var key in _channelOrder) foreach (var key in _channelOrder)
@@ -107,7 +104,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
get get
{ {
lock (_sync) using (_sync.EnterScope())
{ {
return _chatEnabled; return _chatEnabled;
} }
@@ -118,7 +115,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
get get
{ {
lock (_sync) using (_sync.EnterScope())
{ {
return _chatEnabled && _isConnected; return _chatEnabled && _isConnected;
} }
@@ -127,7 +124,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
public void SetActiveChannel(string? key) public void SetActiveChannel(string? key)
{ {
lock (_sync) using (_sync.EnterScope())
{ {
_activeChannelKey = key; _activeChannelKey = key;
if (key is not null && _channels.TryGetValue(key, out var state)) 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() private async Task EnableChatAsync()
{ {
bool wasEnabled; bool wasEnabled;
lock (_sync) using (_sync.EnterScope())
{ {
wasEnabled = _chatEnabled; wasEnabled = _chatEnabled;
if (!wasEnabled) if (!wasEnabled)
@@ -170,7 +167,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
List<ChatChannelDescriptor> groupDescriptors; List<ChatChannelDescriptor> groupDescriptors;
ChatChannelDescriptor? zoneDescriptor; ChatChannelDescriptor? zoneDescriptor;
lock (_sync) using (_sync.EnterScope())
{ {
wasEnabled = _chatEnabled; wasEnabled = _chatEnabled;
if (!wasEnabled) 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.")); return Task.FromResult(new ChatReportResult(false, "Please describe why you are reporting this message."));
} }
lock (_sync) using (_sync.EnterScope())
{ {
if (!_chatEnabled) if (!_chatEnabled)
{ {
@@ -311,8 +308,8 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
Mediator.Subscribe<DalamudLogoutMessage>(this, _ => HandleLogout()); Mediator.Subscribe<DalamudLogoutMessage>(this, _ => HandleLogout());
Mediator.Subscribe<ZoneSwitchEndMessage>(this, _ => ScheduleZonePresenceUpdate()); Mediator.Subscribe<ZoneSwitchEndMessage>(this, _ => ScheduleZonePresenceUpdate());
Mediator.Subscribe<WorldChangedMessage>(this, _ => ScheduleZonePresenceUpdate(force: true)); Mediator.Subscribe<WorldChangedMessage>(this, _ => ScheduleZonePresenceUpdate(force: true));
Mediator.Subscribe<ConnectedMessage>(this, msg => HandleConnected(msg.Connection)); Mediator.Subscribe<ConnectedMessage>(this, _ => HandleConnected());
Mediator.Subscribe<HubReconnectedMessage>(this, _ => HandleConnected(null)); Mediator.Subscribe<HubReconnectedMessage>(this, _ => HandleConnected());
Mediator.Subscribe<HubReconnectingMessage>(this, _ => HandleReconnecting()); Mediator.Subscribe<HubReconnectingMessage>(this, _ => HandleReconnecting());
Mediator.Subscribe<DisconnectedMessage>(this, _ => HandleReconnecting()); Mediator.Subscribe<DisconnectedMessage>(this, _ => HandleReconnecting());
Mediator.Subscribe<PairUiUpdatedMessage>(this, _ => RefreshGroupsFromPairManager()); 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; _isConnected = true;
lock (_sync) using (_sync.EnterScope())
{ {
_selfTokens.Clear(); _selfTokens.Clear();
_pendingSelfMessages.Clear(); _pendingSelfMessages.Clear();
@@ -410,7 +407,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
_isConnected = false; _isConnected = false;
lock (_sync) using (_sync.EnterScope())
{ {
_selfTokens.Clear(); _selfTokens.Clear();
_pendingSelfMessages.Clear(); _pendingSelfMessages.Clear();
@@ -475,7 +472,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private void UpdateChannelsForDisabledState() private void UpdateChannelsForDisabledState()
{ {
lock (_sync) using (_sync.EnterScope())
{ {
foreach (var state in _channels.Values) foreach (var state in _channels.Values)
{ {
@@ -513,7 +510,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
string? zoneKey; string? zoneKey;
ZoneChannelDefinition? definition = null; ZoneChannelDefinition? definition = null;
lock (_sync) using (_sync.EnterScope())
{ {
_territoryToZoneKey.TryGetValue(territoryId, out zoneKey); _territoryToZoneKey.TryGetValue(territoryId, out zoneKey);
if (zoneKey is not null) if (zoneKey is not null)
@@ -538,7 +535,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
bool shouldForceSend; bool shouldForceSend;
lock (_sync) using (_sync.EnterScope())
{ {
var state = EnsureZoneStateLocked(); var state = EnsureZoneStateLocked();
state.DisplayName = definition.Value.DisplayName; state.DisplayName = definition.Value.DisplayName;
@@ -566,7 +563,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
ChatChannelDescriptor? descriptor = null; ChatChannelDescriptor? descriptor = null;
bool clearedHistory = false; bool clearedHistory = false;
lock (_sync) using (_sync.EnterScope())
{ {
descriptor = _lastZoneDescriptor; descriptor = _lastZoneDescriptor;
_lastZoneDescriptor = null; _lastZoneDescriptor = null;
@@ -590,9 +587,9 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
state.DisplayName = "Zone Chat"; 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>(); var infoList = infos ?? Array.Empty<ZoneChatChannelInfoDto>();
lock (_sync) using (_sync.EnterScope())
{ {
_zoneDefinitions.Clear(); _zoneDefinitions.Clear();
_territoryToZoneKey.Clear(); _territoryToZoneKey.Clear();
@@ -657,7 +654,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
if (def.TerritoryNames.Contains(variant)) if (def.TerritoryNames.Contains(variant))
{ {
_territoryToZoneKey[(uint)kvp.Key] = def.Key; _territoryToZoneKey[kvp.Key] = def.Key;
break; break;
} }
} }
@@ -689,7 +686,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var descriptorsToJoin = new List<ChatChannelDescriptor>(); var descriptorsToJoin = new List<ChatChannelDescriptor>();
var descriptorsToLeave = new List<ChatChannelDescriptor>(); var descriptorsToLeave = new List<ChatChannelDescriptor>();
lock (_sync) using (_sync.EnterScope())
{ {
var remainingGroups = new HashSet<string>(_groupDefinitions.Keys, StringComparer.OrdinalIgnoreCase); var remainingGroups = new HashSet<string>(_groupDefinitions.Keys, StringComparer.OrdinalIgnoreCase);
@@ -807,7 +804,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
return; return;
List<ChatChannelDescriptor> descriptors; List<ChatChannelDescriptor> descriptors;
lock (_sync) using (_sync.EnterScope())
{ {
descriptors = _channels.Values descriptors = _channels.Values
.Where(state => state.Type == ChatChannelType.Group) .Where(state => state.Type == ChatChannelType.Group)
@@ -832,7 +829,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var presenceKey = BuildPresenceKey(descriptor); var presenceKey = BuildPresenceKey(descriptor);
bool stateMatches; bool stateMatches;
lock (_sync) using (_sync.EnterScope())
{ {
stateMatches = !force stateMatches = !force
&& _lastPresenceStates.TryGetValue(presenceKey, out var lastState) && _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); await _apiController.UpdateChatPresence(new ChatPresenceUpdateDto(descriptor, territoryId, isActive)).ConfigureAwait(false);
lock (_sync) using (_sync.EnterScope())
{ {
if (isActive) if (isActive)
{ {
@@ -870,7 +867,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var key = normalized.Type == ChatChannelType.Zone ? ZoneChannelKey : BuildChannelKey(normalized); var key = normalized.Type == ChatChannelType.Zone ? ZoneChannelKey : BuildChannelKey(normalized);
var pending = new PendingSelfMessage(key, message); var pending = new PendingSelfMessage(key, message);
lock (_sync) using (_sync.EnterScope())
{ {
_pendingSelfMessages.Add(pending); _pendingSelfMessages.Add(pending);
while (_pendingSelfMessages.Count > 20) while (_pendingSelfMessages.Count > 20)
@@ -884,7 +881,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private void RemovePendingSelfMessage(PendingSelfMessage pending) private void RemovePendingSelfMessage(PendingSelfMessage pending)
{ {
lock (_sync) using (_sync.EnterScope())
{ {
var index = _pendingSelfMessages.FindIndex(p => var index = _pendingSelfMessages.FindIndex(p =>
string.Equals(p.ChannelKey, pending.ChannelKey, StringComparison.Ordinal) && string.Equals(p.ChannelKey, pending.ChannelKey, StringComparison.Ordinal) &&
@@ -905,7 +902,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
var message = BuildMessage(dto, fromSelf); var message = BuildMessage(dto, fromSelf);
bool publishChannelList = false; bool publishChannelList = false;
lock (_sync) using (_sync.EnterScope())
{ {
if (!_channels.TryGetValue(key, out var state)) if (!_channels.TryGetValue(key, out var state))
{ {
@@ -960,7 +957,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
if (publishChannelList) if (publishChannelList)
{ {
lock (_sync) using (_sync.EnterScope())
{ {
UpdateChannelOrderLocked(); 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)) if (dto.Sender.User?.UID is { } uid && string.Equals(uid, _apiController.UID, StringComparison.Ordinal))
{ {
lock (_sync) using (_sync.EnterScope())
{ {
_selfTokens[channelKey] = dto.Sender.Token; _selfTokens[channelKey] = dto.Sender.Token;
} }
@@ -981,7 +978,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
return true; return true;
} }
lock (_sync) using (_sync.EnterScope())
{ {
if (_selfTokens.TryGetValue(channelKey, out var token) && if (_selfTokens.TryGetValue(channelKey, out var token) &&
string.Equals(token, dto.Sender.Token, StringComparison.Ordinal)) 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; var isZone = dto.Channel.Type == ChatChannelType.Zone;
if (!string.IsNullOrEmpty(dto.Sender.HashedCid) && 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)) !string.IsNullOrWhiteSpace(descriptor.Name))
{ {
return descriptor.Name; return descriptor.Name;
@@ -1065,7 +1062,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
{ {
_activeChannelKey = _channelOrder[0]; _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; _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) private static bool ChannelDescriptorsMatch(ChatChannelDescriptor left, ChatChannelDescriptor right)
=> left.Type == right.Type => left.Type == right.Type
&& NormalizeKey(left.CustomKey) == NormalizeKey(right.CustomKey) && string.Equals(NormalizeKey(left.CustomKey), NormalizeKey(right.CustomKey), StringComparison.Ordinal)
&& left.WorldId == right.WorldId; && left.WorldId == right.WorldId;
private ChatChannelState EnsureZoneStateLocked() private ChatChannelState EnsureZoneStateLocked()
@@ -1180,6 +1177,3 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
private readonly record struct PendingSelfMessage(string ChannelKey, string Message); 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) public IntPtr GetPlayerCharacterFromCachedTableByIdent(string characterName)
{ {
if (_actorObjectService.TryGetActorByHash(characterName, out var actor)) if (_actorObjectService.TryGetValidatedActorByHash(characterName, out var actor))
return actor.Address; return actor.Address;
return IntPtr.Zero; return IntPtr.Zero;
} }
@@ -639,7 +639,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
internal (string Name, nint Address) FindPlayerByNameHash(string ident) 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); return (descriptor.Name, descriptor.Address);
} }

View File

@@ -1,240 +1,639 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.Text;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Plugin; using Dalamud.Plugin;
using Dalamud.Plugin.Services; 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.LightlessConfiguration;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.Services.Rendering;
using LightlessSync.UI; 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.Logging;
using Microsoft.Extensions.Hosting;
using Pictomancy;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Globalization;
using System.Numerics; 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 ILogger<LightFinderPlateHandler> _logger;
private readonly LightlessConfigService _configService; private readonly IAddonLifecycle _addonLifecycle;
private readonly IDalamudPluginInterface _pluginInterface;
private readonly IObjectTable _gameObjects;
private readonly IGameGui _gameGui; 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 const float _defaultNameplateDistance = 15.0f;
private ImmutableHashSet<string> _activeBroadcastingCids = []; private ImmutableHashSet<string> _activeBroadcastingCids = [];
private readonly Dictionary<IGameObject, Vector3> _smoothed = [];
private readonly float _defaultHeightOffset = 0f;
public LightlessMediator Mediator { get; }
public LightFinderPlateHandler( public LightFinderPlateHandler(
ILogger<LightFinderPlateHandler> logger, ILogger<LightFinderPlateHandler> logger,
LightlessMediator mediator, IAddonLifecycle addonLifecycle,
IDalamudPluginInterface dalamudPluginInterface, IGameGui gameGui,
LightlessConfigService configService, LightlessConfigService configService,
IObjectTable gameObjects, LightlessMediator mediator,
IGameGui gameGui) IObjectTable objectTable,
PairUiService pairUiService,
IDalamudPluginInterface pluginInterface,
PictomancyService pictomancyService)
{ {
_logger = logger; _logger = logger;
Mediator = mediator; _addonLifecycle = addonLifecycle;
_pluginInterface = dalamudPluginInterface;
_configService = configService;
_gameObjects = gameObjects;
_gameGui = gameGui; _gameGui = gameGui;
_configService = configService;
_mediator = mediator;
_objectTable = objectTable;
_pairUiService = pairUiService;
_uiBuilder = pluginInterface.UiBuilder ?? throw new ArgumentNullException(nameof(pluginInterface));
_ = pictomancyService ?? throw new ArgumentNullException(nameof(pictomancyService));
} }
public Task StartAsync(CancellationToken cancellationToken) internal void Init()
{ {
_logger.LogInformation("Starting LightFinderPlateHandler..."); if (!_drawSubscribed)
{
_pluginInterface.UiBuilder.Draw += OnDraw; _uiBuilder.Draw += OnUiBuilderDraw;
_drawSubscribed = true;
_logger.LogInformation("LightFinderPlateHandler started.");
return Task.CompletedTask;
} }
public Task StopAsync(CancellationToken cancellationToken) EnableNameplate();
{ _mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick);
_logger.LogInformation("Stopping LightFinderPlateHandler...");
_pluginInterface.UiBuilder.Draw -= OnDraw;
_logger.LogInformation("LightFinderPlateHandler stopped.");
return Task.CompletedTask;
} }
private unsafe void OnDraw() internal void Uninit()
{ {
if (!_configService.Current.BroadcastEnabled) DisableNameplate();
if (_drawSubscribed)
{
_uiBuilder.Draw -= OnUiBuilderDraw;
_drawSubscribed = false;
}
ClearLabelBuffer();
_mediator.Unsubscribe<PriorityFrameworkUpdateMessage>(this);
_mpNameplateAddon = null;
}
internal void EnableNameplate()
{
if (!_mEnabled)
{
try
{
_addonLifecycle.RegisterListener(AddonEvent.PostDraw, "NamePlate", NameplateDrawDetour);
_mEnabled = true;
}
catch (Exception e)
{
_logger.LogError(e, "Unknown error while trying to enable nameplate.");
DisableNameplate();
}
}
}
internal void DisableNameplate()
{
if (_mEnabled)
{
try
{
_addonLifecycle.UnregisterListener(NameplateDrawDetour);
}
catch (Exception e)
{
_logger.LogError(e, "Unknown error while unregistering nameplate listener.");
}
_mEnabled = false;
ClearNameplateCaches();
}
}
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; return;
}
if (_activeBroadcastingCids.Count == 0) var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
if (_mpNameplateAddon != pNameplateAddon)
{
ClearNameplateCaches();
_mpNameplateAddon = pNameplateAddon;
}
UpdateNameplateNodes();
}
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; return;
}
var drawList = ImGui.GetForegroundDrawList(); var currentAddon = (AddonNamePlate*)currentHandle.Address;
if (_mpNameplateAddon == null || currentAddon == null || currentAddon != _mpNameplateAddon)
foreach (var obj in _gameObjects.PlayerObjects.OfType<IPlayerCharacter>())
{ {
//Double check to be sure, should always be true due to OfType filter above if (_mpNameplateAddon != null && _logger.IsEnabled(LogLevel.Debug))
if (obj is not IPlayerCharacter player) _logger.LogDebug("Cached NamePlate addon pointer differs from current: waiting for new hook (cached {Cached}, current {Current}).", (IntPtr)_mpNameplateAddon, (IntPtr)currentAddon);
continue; return;
}
if (player.Address == IntPtr.Zero) var framework = Framework.Instance();
continue; if (framework == null)
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); if (_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug("Framework instance unavailable during nameplate update, skipping.");
return;
}
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; continue;
} }
//Possible have to rework this. Currently just a simple distance check to avoid jitter. root->Component->UldManager.UpdateDrawNodeList();
Vector3 smoothedVector3;
if (_smoothed.TryGetValue(obj, out var lastVector3)) 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 deltaVector2 = new Vector2(rawVector3.X - lastVector3.X, rawVector3.Y - lastVector3.Y); var computed = blockHeight + (int)System.Math.Round(8 * textScaleY);
if (deltaVector2.Length() < 1f) return computed <= blockHeight ? blockHeight + 1 : computed;
smoothedVector3 = lastVector3; },
else blockHeight + 1);
smoothedVector3 = rawVector3;
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 else
{ {
smoothedVector3 = rawVector3; 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;
} }
_smoothed[obj] = smoothedVector3; positionY += currentConfig.LightfinderLabelOffsetY;
alignment = (AlignmentType)System.Math.Clamp((int)alignment, 0, 8);
var screenPos = new Vector2(smoothedVector3.X, smoothedVector3.Y); 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);
var radiusWorld = Math.Max(player.HitboxRadius, 0.5f); _buffers.LabelScratch[scratchCount++] = new NameplateLabelInfo(
var radiusPx = radiusWorld * 8.0f; finalPosition,
var offsetPx = GetScreenOffset(player); labelContent,
var drawPos = new Vector2(screenPos.X, screenPos.Y - offsetPx); textColorPacked,
edgeColorPacked,
var fillColor = ImGui.GetColorU32(UiSharedService.Color(UIColors.Get("Lightfinder"))); targetFontSize,
var outlineColor = ImGui.GetColorU32(UiSharedService.Color(UIColors.Get("LightfinderEdge"))); pivot,
currentConfig.LightfinderLabelUseIcon);
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); */
}
} }
// Get screen offset based on distance to local player (to scale size appropriately) lock (_labelLock)
// I need to fine tune these values still
private float GetScreenOffset(IPlayerCharacter player)
{ {
var local = _gameObjects.LocalPlayer; if (scratchCount == 0)
if (local == null) {
return 32.1f; _labelRenderCount = 0;
}
var delta = player.Position - local.Position; else
var dist = MathF.Sqrt(delta.X * delta.X + delta.Z * delta.Z); {
Array.Copy(_buffers.LabelScratch, _buffers.LabelRender, scratchCount);
const float minDist = 2.1f; _labelRenderCount = scratchCount;
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 bool TryGetApproxNameplateScreenPos(IPlayerCharacter player, out Vector2 screenPos) private void OnUiBuilderDraw()
{ {
screenPos = default; if (!_mEnabled)
return;
var worldPos = player.Position; int copyCount;
lock (_labelLock)
{
copyCount = _labelRenderCount;
if (copyCount == 0)
return;
var visualHeight = GetVisualHeight(player); Array.Copy(_buffers.LabelRender, _buffers.LabelCopy, copyCount);
}
worldPos.Y += (visualHeight + 1.2f) + _defaultHeightOffset; using var drawList = PictoService.Draw();
if (drawList == null)
return;
if (!_gameGui.WorldToScreen(worldPos, out var raw)) for (int i = 0; i < copyCount; ++i)
return false; {
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();
}
screenPos = raw; 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; 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)
{
var local = _gameObjects.LocalPlayer;
if (local == null)
return false; 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 _);
} }
private static unsafe float GetVisualHeight(IPlayerCharacter player) internal static string NormalizeIconGlyph(string? rawInput)
{ {
var gameObject = (GameObject*)player.Address; if (string.IsNullOrWhiteSpace(rawInput))
if (gameObject == null) return DefaultIconGlyph;
return Math.Max(player.HitboxRadius * 2.0f, 1.7f); // fallback
// This should account for transformations (sitting, crouching, etc.) var trimmed = rawInput.Trim();
var radius = gameObject->GetRadius(adjustByTransformation: true);
if (radius <= 0)
radius = Math.Max(player.HitboxRadius * 2.0f, 1.7f);
return radius; 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)
{
ScreenPosition = screenPosition;
Text = text;
TextColor = textColor;
EdgeColor = edgeColor;
FontSize = fontSize;
Pivot = pivot;
UseIcon = useIcon;
}
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)
{
UpdateNameplateNodes();
_needsLabelRefresh = false;
}
} }
// Update the set of active broadcasting CIDs (Same uses as in NameplateHnadler before)
public void UpdateBroadcastingCids(IEnumerable<string> cids) public void UpdateBroadcastingCids(IEnumerable<string> cids)
{ {
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal); var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
@@ -244,6 +643,50 @@ namespace LightlessSync.Services.LightFinder
_activeBroadcastingCids = newSet; _activeBroadcastingCids = newSet;
if (_logger.IsEnabled(LogLevel.Information)) if (_logger.IsEnabled(LogLevel.Information))
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids)); _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.Collections.Concurrent;
using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using OtterTex; using OtterTex;
using OtterImage = OtterTex.Image; using OtterImage = OtterTex.Image;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
@@ -11,7 +12,6 @@ using Microsoft.Extensions.Logging;
using Lumina.Data.Files; using Lumina.Data.Files;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
/* /*
* OtterTex made by Ottermandias * 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, Task> _activeJobs = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, string> _downscaledPaths = 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 = private static readonly IReadOnlyDictionary<int, TextureCompressionTarget> BlockCompressedFormatMap =
new Dictionary<int, TextureCompressionTarget> new Dictionary<int, TextureCompressionTarget>
{ {
@@ -80,7 +81,10 @@ public sealed class TextureDownscaleService
if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return; if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return;
if (_activeJobs.ContainsKey(hash)) 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) public string GetPreferredPath(string hash, string originalPath)
@@ -108,6 +112,7 @@ public sealed class TextureDownscaleService
bool onlyDownscaleUncompressed = false; bool onlyDownscaleUncompressed = false;
bool? isIndexTexture = null; bool? isIndexTexture = null;
await _downscaleSemaphore.WaitAsync().ConfigureAwait(false);
try try
{ {
if (!File.Exists(sourcePath)) if (!File.Exists(sourcePath))
@@ -157,6 +162,15 @@ public sealed class TextureDownscaleService
return; 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)) if (onlyDownscaleUncompressed && headerInfo.HasValue && IsBlockCompressedFormat(headerInfo.Value.Format))
{ {
_downscaledPaths[hash] = sourcePath; _downscaledPaths[hash] = sourcePath;
@@ -172,21 +186,20 @@ public sealed class TextureDownscaleService
var height = rgbaInfo.Meta.Height; var height = rgbaInfo.Meta.Height;
var requiredLength = width * height * bytesPerPixel; 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); using var originalImage = SixLabors.ImageSharp.Image.LoadPixelData<Rgba32>(rgbaPixels, width, height);
var targetSize = CalculateTargetSize(originalImage.Width, originalImage.Height, targetMaxDimension); var targetSize = CalculateTargetSize(originalImage.Width, originalImage.Height, targetMaxDimension);
if (targetSize.width == originalImage.Width && targetSize.height == originalImage.Height) 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; return;
} }
using var resized = IndexDownscaler.Downscale(originalImage, targetSize.width, targetSize.height, BlockMultiple); using var resized = IndexDownscaler.Downscale(originalImage, targetSize.width, targetSize.height, BlockMultiple);
var resizedPixels = new byte[targetSize.width * targetSize.height * 4]; using var resizedScratch = CreateScratchImage(resized, targetSize.width, targetSize.height);
resized.CopyPixelDataTo(resizedPixels);
using var resizedScratch = ScratchImage.FromRGBA(resizedPixels, targetSize.width, targetSize.height, out var creationInfo).ThrowIfError(creationInfo);
using var finalScratch = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm); using var finalScratch = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm);
TexFileHelper.Save(destination, finalScratch); TexFileHelper.Save(destination, finalScratch);
@@ -209,6 +222,7 @@ public sealed class TextureDownscaleService
} }
finally finally
{ {
_downscaleSemaphore.Release();
_activeJobs.TryRemove(hash, out _); _activeJobs.TryRemove(hash, out _);
} }
} }
@@ -227,6 +241,41 @@ public sealed class TextureDownscaleService
return (resultWidth, resultHeight); 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) private static bool IsIndexMap(TextureMapKind kind)
=> kind is TextureMapKind.Mask => kind is TextureMapKind.Mask
or TextureMapKind.Index; or TextureMapKind.Index;
@@ -420,21 +469,6 @@ public sealed class TextureDownscaleService
private static int ReduceDimension(int value) private static int ReduceDimension(int value)
=> value <= 1 ? 1 : Math.Max(1, value / 2); => 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) private static bool ShouldTrim(in TexMeta meta, int targetMaxDimension)
{ {
var depth = meta.Dimension == TexDimension.Tex3D ? Math.Max(1, meta.Depth) : 1; 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) private static bool ShouldTrimDimensions(int width, int height, int depth, int targetMaxDimension)
{ {
if (width <= targetMaxDimension || height <= targetMaxDimension) if (width <= targetMaxDimension && height <= targetMaxDimension && depth <= targetMaxDimension)
{
return false;
}
if (depth > 1 && depth <= targetMaxDimension)
{ {
return false; return false;
} }

View File

@@ -51,10 +51,11 @@ public class IdDisplayHandler
(bool textIsUid, string playerText) = GetGroupText(group); (bool textIsUid, string playerText) = GetGroupText(group);
if (!string.Equals(_editEntry, group.GID, StringComparison.Ordinal)) if (!string.Equals(_editEntry, group.GID, StringComparison.Ordinal))
{ {
ImGui.AlignTextToFramePadding();
using (ImRaii.PushFont(UiBuilder.MonoFont, textIsUid)) using (ImRaii.PushFont(UiBuilder.MonoFont, textIsUid))
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(playerText); ImGui.TextUnformatted(playerText);
}
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
@@ -120,13 +121,20 @@ public class IdDisplayHandler
var compactPerformanceText = BuildCompactPerformanceUsageText(pair); var compactPerformanceText = BuildCompactPerformanceUsageText(pair);
if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal)) if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal))
{
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;
using (ImRaii.PushFont(font, textIsUid))
{ {
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
var rowStart = ImGui.GetCursorScreenPos(); var rowStart = ImGui.GetCursorScreenPos();
var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f); rowRightLimit = rowStart.X + rowWidth;
var rowRightLimit = rowStart.X + rowWidth;
var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont();
Vector4? textColor = null; Vector4? textColor = null;
Vector4? glowColor = null; Vector4? glowColor = null;
@@ -175,14 +183,9 @@ public class IdDisplayHandler
} }
} }
Vector2 itemMin;
Vector2 itemMax;
using (ImRaii.PushFont(font, textIsUid))
{
SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID); SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID);
itemMin = ImGui.GetItemRectMin(); nameRectMin = ImGui.GetItemRectMin();
itemMax = ImGui.GetItemRectMax(); nameRectMax = ImGui.GetItemRectMax();
}
if (useHighlight) if (useHighlight)
{ {
@@ -191,8 +194,8 @@ public class IdDisplayHandler
var rowTop = rowStart.Y - style.FramePadding.Y; var rowTop = rowStart.Y - style.FramePadding.Y;
var rowBottom = rowTop + frameHeight; var rowBottom = rowTop + frameHeight;
var highlightMin = new Vector2(itemMin.X - highlightPadX, rowTop - highlightPadY); var highlightMin = new Vector2(nameRectMin.X - highlightPadX, rowTop - highlightPadY);
var highlightMax = new Vector2(itemMax.X + highlightPadX, rowBottom + highlightPadY); var highlightMax = new Vector2(nameRectMax.X + highlightPadX, rowBottom + highlightPadY);
var windowPos = ImGui.GetWindowPos(); var windowPos = ImGui.GetWindowPos();
var contentMin = windowPos + ImGui.GetWindowContentRegionMin(); var contentMin = windowPos + ImGui.GetWindowContentRegionMin();
@@ -220,9 +223,12 @@ public class IdDisplayHandler
drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding); drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding);
drawList.ChannelsMerge(); drawList.ChannelsMerge();
} }
}
var nameRectMin = ImGui.GetItemRectMin(); {
var nameRectMax = ImGui.GetItemRectMax(); var style = ImGui.GetStyle();
frameHeightForStats = ImGui.GetFrameHeight();
rowTopForStats = nameRectMin.Y - style.FramePadding.Y;
}
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
if (!string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal)) if (!string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal))
@@ -292,12 +298,9 @@ public class IdDisplayHandler
const float compactFontScale = 0.85f; const float compactFontScale = 0.85f;
ImGui.SetWindowFontScale(compactFontScale); ImGui.SetWindowFontScale(compactFontScale);
var compactHeight = ImGui.GetTextLineHeight(); var compactHeight = ImGui.GetTextLineHeight();
var nameHeight = nameRectMax.Y - nameRectMin.Y;
var targetPos = ImGui.GetCursorScreenPos(); var targetPos = ImGui.GetCursorScreenPos();
var availableWidth = MathF.Max(rowRightLimit - targetPos.X, 0f); var availableWidth = MathF.Max(rowRightLimit - targetPos.X, 0f);
var centeredY = nameRectMin.Y + MathF.Max((nameHeight - compactHeight) * 0.5f, 0f); var centeredY = rowTopForStats + MathF.Max((frameHeightForStats - compactHeight) * 0.5f, 0f);
float verticalOffset = 1f * ImGuiHelpers.GlobalScale;
centeredY += verticalOffset;
ImGui.SetCursorScreenPos(new Vector2(targetPos.X, centeredY)); ImGui.SetCursorScreenPos(new Vector2(targetPos.X, centeredY));
var performanceText = string.Empty; var performanceText = string.Empty;

View File

@@ -48,7 +48,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private readonly ImGuiWindowFlags _unpinnedWindowFlags; private readonly ImGuiWindowFlags _unpinnedWindowFlags;
private float _currentWindowOpacity = DefaultWindowOpacity; private float _currentWindowOpacity = DefaultWindowOpacity;
private bool _isWindowPinned; private bool _isWindowPinned;
private bool _showRulesOverlay = true; private bool _showRulesOverlay;
private string? _selectedChannelKey; private string? _selectedChannelKey;
private bool _scrollToBottom = true; 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"; var prefix = channel.Type == ChatChannelType.Zone ? "Zone" : "Syncshell";
Vector4 color; Vector4 color;
@@ -577,12 +577,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.Separator(); ImGui.Separator();
ImGui.TextUnformatted("Reason (required)"); 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[..ReportReasonMaxLength];
{
_reportReason = _reportReason[..(int)ReportReasonMaxLength];
}
} }
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3); ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
@@ -591,12 +589,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
ImGui.Spacing(); ImGui.Spacing();
ImGui.TextUnformatted("Additional context (optional)"); 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[..ReportContextMaxLength];
{
_reportAdditionalContext = _reportAdditionalContext[..(int)ReportContextMaxLength];
}
} }
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3); 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; var text = message.Payload.Message;
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
@@ -920,7 +916,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
private void EnsureSelectedChannel(IReadOnlyList<ChatChannelSnapshot> channels) 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; return;
_selectedChannelKey = channels.Count > 0 ? channels[0].Key : null; _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 isSelected = string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal);
var showBadge = !isSelected && channel.UnreadCount > 0; var showBadge = !isSelected && channel.UnreadCount > 0;
var isZoneChannel = channel.Type == ChatChannelType.Zone; var isZoneChannel = channel.Type == ChatChannelType.Zone;
var badgeText = string.Empty; (string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null;
var badgePadding = Vector2.Zero;
var badgeTextSize = Vector2.Zero;
float badgeWidth = 0f;
float badgeHeight = 0f;
var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault"); var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault");
var hovered = isSelected var hovered = isSelected
@@ -1285,15 +1277,16 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
if (showBadge) if (showBadge)
{ {
var badgeSpacing = 4f * ImGuiHelpers.GlobalScale; var badgeSpacing = 4f * ImGuiHelpers.GlobalScale;
badgePadding = new Vector2(4f, 1.5f) * ImGuiHelpers.GlobalScale; var badgePadding = new Vector2(4f, 1.5f) * ImGuiHelpers.GlobalScale;
badgeText = channel.UnreadCount > MaxBadgeDisplay var badgeText = channel.UnreadCount > MaxBadgeDisplay
? $"{MaxBadgeDisplay}+" ? $"{MaxBadgeDisplay}+"
: channel.UnreadCount.ToString(CultureInfo.InvariantCulture); : channel.UnreadCount.ToString(CultureInfo.InvariantCulture);
badgeTextSize = ImGui.CalcTextSize(badgeText); var badgeTextSize = ImGui.CalcTextSize(badgeText);
badgeWidth = badgeTextSize.X + badgePadding.X * 2f; var badgeWidth = badgeTextSize.X + badgePadding.X * 2f;
badgeHeight = badgeTextSize.Y + badgePadding.Y * 2f; var badgeHeight = badgeTextSize.Y + badgePadding.Y * 2f;
var customPadding = new Vector2(baseFramePadding.X + badgeWidth + badgeSpacing, baseFramePadding.Y); var customPadding = new Vector2(baseFramePadding.X + badgeWidth + badgeSpacing, baseFramePadding.Y);
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, customPadding); ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, customPadding);
badgeMetrics = (badgeText, badgeTextSize, badgeWidth, badgeHeight);
} }
var clicked = ImGui.Button($"{channel.DisplayName}##chat_channel_{channel.Key}"); 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); drawList.AddRect(itemMin, itemMax, borderColorU32, style.FrameRounding, ImDrawFlags.None, borderThickness);
} }
if (showBadge) if (showBadge && badgeMetrics is { } metrics)
{ {
var buttonSizeY = itemMax.Y - itemMin.Y; var buttonSizeY = itemMax.Y - itemMin.Y;
var badgeMin = new Vector2( var badgeMin = new Vector2(
itemMin.X + baseFramePadding.X, itemMin.X + baseFramePadding.X,
itemMin.Y + (buttonSizeY - badgeHeight) * 0.5f); itemMin.Y + (buttonSizeY - metrics.Height) * 0.5f);
var badgeMax = badgeMin + new Vector2(badgeWidth, badgeHeight); var badgeMax = badgeMin + new Vector2(metrics.Width, metrics.Height);
var badgeColor = UIColors.Get("DimRed"); var badgeColor = UIColors.Get("DimRed");
var badgeColorU32 = ImGui.ColorConvertFloat4ToU32(badgeColor); 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( var textPos = new Vector2(
badgeMin.X + (badgeWidth - badgeTextSize.X) * 0.5f, badgeMin.X + (metrics.Width - metrics.TextSize.X) * 0.5f,
badgeMin.Y + (badgeHeight - badgeTextSize.Y) * 0.5f); badgeMin.Y + (metrics.Height - metrics.TextSize.Y) * 0.5f);
drawList.AddText(textPos, ImGui.ColorConvertFloat4ToU32(ImGuiColors.DalamudWhite), badgeText); drawList.AddText(textPos, ImGui.ColorConvertFloat4ToU32(ImGuiColors.DalamudWhite), metrics.Text);
} }
first = false; 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) public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null)
{ {
var drawList = ImGui.GetWindowDrawList(); var drawList = ImGui.GetWindowDrawList();
var usedFont = font ?? UiBuilder.MonoFont;
var drawParams = new SeStringDrawParams var drawParams = new SeStringDrawParams
{ {
Font = font ?? UiBuilder.MonoFont, Font = usedFont,
Color = 0xFFFFFFFF, Color = 0xFFFFFFFF,
WrapWidth = float.MaxValue, WrapWidth = float.MaxValue,
TargetDrawList = drawList TargetDrawList = drawList
}; };
ImGui.SetCursorScreenPos(position);
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
var textSize = ImGui.CalcTextSize(seString.TextValue); 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); ImGui.SetCursorScreenPos(position);
if (id is not null) if (id is not null)
@@ -591,30 +602,52 @@ public static class SeStringUtils
try try
{ {
ImGui.InvisibleButton("##hitbox", textSize); ImGui.InvisibleButton("##hitbox", new Vector2(textSize.X, hitboxHeight));
} }
finally finally
{ {
ImGui.PopID(); ImGui.PopID();
} }
return textSize; return new Vector2(textSize.X, hitboxHeight);
} }
public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null) public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null)
{ {
var drawList = ImGui.GetWindowDrawList(); 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, Color = 0xFFFFFFFF,
WrapWidth = float.MaxValue, WrapWidth = float.MaxValue
TargetDrawList = drawList
}; };
var iconMacro = $"<icon({iconId})>"; var measureResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, measureParams);
var drawResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams); 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); ImGui.SetCursorScreenPos(position);
if (id is not null) if (id is not null)
@@ -628,14 +661,14 @@ public static class SeStringUtils
try try
{ {
ImGui.InvisibleButton("##iconHitbox", drawResult.Size); ImGui.InvisibleButton("##iconHitbox", new Vector2(iconSize.X, hitboxHeight));
} }
finally finally
{ {
ImGui.PopID(); ImGui.PopID();
} }
return drawResult.Size; return new Vector2(iconSize.X, hitboxHeight);
} }
#region Internal Payloads #region Internal Payloads

View File

@@ -177,7 +177,8 @@ public static class VariousExtensions
if (objectKind != ObjectKind.Player) continue; if (objectKind != ObjectKind.Player) continue;
bool manipDataDifferent = !string.Equals(oldData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal); 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); logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff manip data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModManip);
charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip); charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip);

View File

@@ -12,7 +12,6 @@ using System.Collections.Concurrent;
using System.Net; using System.Net;
using System.Net.Http.Json; using System.Net.Http.Json;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.PairProcessing;
namespace LightlessSync.WebAPI.Files; namespace LightlessSync.WebAPI.Files;
@@ -22,12 +21,10 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private readonly FileCompactor _fileCompactor; private readonly FileCompactor _fileCompactor;
private readonly FileCacheManager _fileDbManager; private readonly FileCacheManager _fileDbManager;
private readonly FileTransferOrchestrator _orchestrator; private readonly FileTransferOrchestrator _orchestrator;
private readonly PairProcessingLimiter _pairProcessingLimiter;
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly TextureDownscaleService _textureDownscaleService; private readonly TextureDownscaleService _textureDownscaleService;
private readonly TextureMetadataHelper _textureMetadataHelper; private readonly TextureMetadataHelper _textureMetadataHelper;
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams; private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
private static readonly TimeSpan DownloadStallTimeout = TimeSpan.FromSeconds(30);
private volatile bool _disableDirectDownloads; private volatile bool _disableDirectDownloads;
private int _consecutiveDirectDownloadFailures; private int _consecutiveDirectDownloadFailures;
private bool _lastConfigDirectDownloadsState; private bool _lastConfigDirectDownloadsState;
@@ -38,7 +35,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
FileTransferOrchestrator orchestrator, FileTransferOrchestrator orchestrator,
FileCacheManager fileCacheManager, FileCacheManager fileCacheManager,
FileCompactor fileCompactor, FileCompactor fileCompactor,
PairProcessingLimiter pairProcessingLimiter,
LightlessConfigService configService, LightlessConfigService configService,
TextureDownscaleService textureDownscaleService, TextureMetadataHelper textureMetadataHelper) : base(logger, mediator) TextureDownscaleService textureDownscaleService, TextureMetadataHelper textureMetadataHelper) : base(logger, mediator)
{ {
@@ -46,7 +42,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
_orchestrator = orchestrator; _orchestrator = orchestrator;
_fileDbManager = fileCacheManager; _fileDbManager = fileCacheManager;
_fileCompactor = fileCompactor; _fileCompactor = fileCompactor;
_pairProcessingLimiter = pairProcessingLimiter;
_configService = configService; _configService = configService;
_textureDownscaleService = textureDownscaleService; _textureDownscaleService = textureDownscaleService;
_textureMetadataHelper = textureMetadataHelper; _textureMetadataHelper = textureMetadataHelper;
@@ -282,42 +277,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
int bytesRead; int bytesRead;
try try
{ {
using var readCancellation = CancellationTokenSource.CreateLinkedTokenSource(ct); bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), ct).ConfigureAwait(false);
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);
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {
@@ -340,11 +300,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
Logger.LogDebug("{requestUrl} downloaded to {destination}", requestUrl, destinationFilename); 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) catch (OperationCanceledException)
{ {
throw; throw;

View File

@@ -521,26 +521,979 @@
"resolved": "17.6.3", "resolved": "17.6.3",
"contentHash": "N0ZIanl1QCgvUumEL1laasU0a7sOE5ZwLZVTn0pAePnfhq8P7SvTjF8Axq+CnavuQkmdQpGNXQ1efZtu5kDFbA==" "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": { "System.Diagnostics.EventLog": {
"type": "Transitive", "type": "Transitive",
"resolved": "9.0.3", "resolved": "9.0.3",
"contentHash": "0nDJBZ06DVdTG2vvCZ4XjazLVaFawdT0pnji23ISX8I8fEOlRJyzH2I0kWiAbCtFwry2Zir4qE4l/GStLATfFw==" "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": { "System.Memory": {
"type": "Transitive", "type": "Transitive",
"resolved": "4.5.5", "resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" "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": { "System.Net.ServerSentEvents": {
"type": "Transitive", "type": "Transitive",
"resolved": "9.0.3", "resolved": "9.0.3",
"contentHash": "Vs/C2V27bjtwLqYag9ATzHilcUn8VQTICre4jSBMGFUeSTxEZffTjb+xZwjcmPsVAjmSZmBI5N7Ezq8UFvqQQg==" "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": { "System.Threading.Channels": {
"type": "Transitive", "type": "Transitive",
"resolved": "9.0.3", "resolved": "9.0.3",
"contentHash": "Ao0iegVONKYVw0eWxJv0ArtMVfkFjgyyYKtUXru6xX5H95flSZWW3QCavD4PAgwpc0ETP38kGHaYbPzSE7sw2w==" "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": { "lightlesssync.api": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
@@ -569,6 +1522,15 @@
}, },
"penumbra.string": { "penumbra.string": {
"type": "Project" "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