Partial roll-up to reduce code divergence

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
Loporrit
2025-02-15 23:07:46 +00:00
parent 324288652d
commit 7a8de7add6
41 changed files with 963 additions and 464 deletions

View File

@@ -102,10 +102,13 @@ dotnet_diagnostic.MA0075.severity = silent
dotnet_diagnostic.S3358.severity = suggestion dotnet_diagnostic.S3358.severity = suggestion
# S6678: Use PascalCase for named placeholders # S6678: Use PascalCase for named placeholders
dotnet_diagnostic.S6678.severity = suggestion dotnet_diagnostic.S6678.severity = none
# S6605: Collection-specific "Exists" method should be used instead of the "Any" extension # S6605: Collection-specific "Exists" method should be used instead of the "Any" extension
dotnet_diagnostic.S6605.severity = suggestion dotnet_diagnostic.S6605.severity = none
# S6667: Logging in a catch clause should pass the caught exception as a parameter. # S6667: Logging in a catch clause should pass the caught exception as a parameter.
dotnet_diagnostic.S6667.severity = suggestion dotnet_diagnostic.S6667.severity = suggestion
# IDE0290: Use primary constructor
csharp_style_prefer_primary_constructors = false

View File

@@ -5,7 +5,7 @@ using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Collections.Immutable;
namespace MareSynchronos.FileCache; namespace MareSynchronos.FileCache;
@@ -20,13 +20,12 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
private long _currentFileProgress = 0; private long _currentFileProgress = 0;
private CancellationTokenSource _scanCancellationTokenSource = new(); private CancellationTokenSource _scanCancellationTokenSource = new();
private readonly CancellationTokenSource _periodicCalculationTokenSource = new(); private readonly CancellationTokenSource _periodicCalculationTokenSource = new();
private readonly string[] _allowedExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk"]; public static readonly IImmutableList<string> AllowedFileExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk"];
public CacheMonitor(ILogger<CacheMonitor> logger, IpcManager ipcManager, MareConfigService configService, public CacheMonitor(ILogger<CacheMonitor> logger, IpcManager ipcManager, MareConfigService configService,
FileCacheManager fileDbManager, MareMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil, FileCacheManager fileDbManager, MareMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil,
FileCompactor fileCompactor) : base(logger, mediator) FileCompactor fileCompactor) : base(logger, mediator)
{ {
Logger.LogInformation("Creating CacheMonitor from {trace}", Environment.StackTrace);
_ipcManager = ipcManager; _ipcManager = ipcManager;
_configService = configService; _configService = configService;
_fileDbManager = fileDbManager; _fileDbManager = fileDbManager;
@@ -153,7 +152,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
{ {
Logger.LogTrace("Mare FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath); Logger.LogTrace("Mare FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
if (!_allowedExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
lock (_watcherChanges) lock (_watcherChanges)
{ {
@@ -197,7 +196,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
private void Fs_Changed(object sender, FileSystemEventArgs e) private void Fs_Changed(object sender, FileSystemEventArgs e)
{ {
if (Directory.Exists(e.FullPath)) return; if (Directory.Exists(e.FullPath)) return;
if (!_allowedExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
if (e.ChangeType is not (WatcherChangeTypes.Changed or WatcherChangeTypes.Deleted or WatcherChangeTypes.Created)) if (e.ChangeType is not (WatcherChangeTypes.Changed or WatcherChangeTypes.Deleted or WatcherChangeTypes.Created))
return; return;
@@ -221,7 +220,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
{ {
foreach (var file in directoryFiles) foreach (var file in directoryFiles)
{ {
if (!_allowedExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) continue; if (!AllowedFileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) continue;
var oldPath = file.Replace(e.FullPath, e.OldFullPath, StringComparison.OrdinalIgnoreCase); var oldPath = file.Replace(e.FullPath, e.OldFullPath, StringComparison.OrdinalIgnoreCase);
_watcherChanges.Remove(oldPath); _watcherChanges.Remove(oldPath);
@@ -233,7 +232,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
} }
else else
{ {
if (!_allowedExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
lock (_watcherChanges) lock (_watcherChanges)
{ {
@@ -499,7 +498,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
[ [
.. Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories) .. Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories)
.AsParallel() .AsParallel()
.Where(f => _allowedExtensions.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase)) .Where(f => AllowedFileExtensions.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase))
&& !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase) && !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase)
&& !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase) && !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase)
&& !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)), && !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)),

View File

@@ -139,18 +139,7 @@ public sealed class FileCacheManager : IHostedService
return Path.Combine(_configService.Current.CacheFolder, hash + "." + extension); return Path.Combine(_configService.Current.CacheFolder, hash + "." + extension);
} }
public async Task<long> GetCompressedFileLength(string fileHash, CancellationToken uploadToken) public async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{
var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath;
using var fs = File.OpenRead(fileCache);
var cs = new CountedStream(Stream.Null);
using var encstream = LZ4Stream.Encode(cs, new LZ4EncoderSettings(){CompressionLevel=K4os.Compression.LZ4.LZ4Level.L09_HC});
await fs.CopyToAsync(encstream, uploadToken).ConfigureAwait(false);
encstream.Close();
return uploadToken.IsCancellationRequested ? 0 : cs.BytesWritten;
}
public async Task<byte[]> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{ {
var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath; var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath;
using var fs = File.OpenRead(fileCache); using var fs = File.OpenRead(fileCache);
@@ -158,7 +147,7 @@ public sealed class FileCacheManager : IHostedService
using var encstream = LZ4Stream.Encode(ms, new LZ4EncoderSettings(){CompressionLevel=K4os.Compression.LZ4.LZ4Level.L09_HC}); using var encstream = LZ4Stream.Encode(ms, new LZ4EncoderSettings(){CompressionLevel=K4os.Compression.LZ4.LZ4Level.L09_HC});
await fs.CopyToAsync(encstream, uploadToken).ConfigureAwait(false); await fs.CopyToAsync(encstream, uploadToken).ConfigureAwait(false);
encstream.Close(); encstream.Close();
return ms.ToArray(); return (fileHash, ms.ToArray());
} }
public FileCacheEntity? GetFileCacheByHash(string hash) public FileCacheEntity? GetFileCacheByHash(string hash)
@@ -428,6 +417,13 @@ public sealed class FileCacheManager : IHostedService
if (File.Exists(_csvPath)) if (File.Exists(_csvPath))
{ {
if (!_ipcManager.Penumbra.APIAvailable || string.IsNullOrEmpty(_ipcManager.Penumbra.ModDirectory))
{
_mareMediator.Publish(new NotificationMessage("Penumbra not connected",
"Could not load local file cache data. Penumbra is not connected or not properly set up. Please enable and/or configure Penumbra properly to use Loporrit. After, reload Loporrit in the Plugin installer.",
MareConfiguration.Models.NotificationType.Error));
}
_logger.LogInformation("{csvPath} found, parsing", _csvPath); _logger.LogInformation("{csvPath} found, parsing", _csvPath);
bool success = false; bool success = false;

View File

@@ -11,13 +11,13 @@ namespace MareSynchronos.FileCache;
public sealed class TransientResourceManager : DisposableMediatorSubscriberBase public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
{ {
private readonly object _cacheAdditionLock = new();
private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal); private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal);
private readonly TransientConfigService _configurationService; private readonly TransientConfigService _configurationService;
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"]; private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = []; private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
private HashSet<IntPtr> _cachedFrameAddresses = []; private ConcurrentDictionary<IntPtr, ObjectKind> _cachedFrameAddresses = [];
private readonly object _cacheAdditionLock = new();
public TransientResourceManager(ILogger<TransientResourceManager> logger, TransientConfigService configurationService, public TransientResourceManager(ILogger<TransientResourceManager> logger, TransientConfigService configurationService,
DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator) DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator)
@@ -162,7 +162,13 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
{ {
if (TransientResources.TryGetValue(ptr, out var set)) if (TransientResources.TryGetValue(ptr, out var set))
{ {
set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase)); foreach (var file in set.Where(p => list.Contains(p, StringComparer.OrdinalIgnoreCase)))
{
Logger.LogTrace("Removing From Transient: {file}", file);
}
int removed = set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase));
Logger.LogInformation("Removed {removed} previously existing transient paths", removed);
} }
} }
@@ -189,7 +195,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
private void DalamudUtil_FrameworkUpdate() private void DalamudUtil_FrameworkUpdate()
{ {
_cachedFrameAddresses = _playerRelatedPointers.Select(c => c.CurrentAddress()).ToHashSet(); _cachedFrameAddresses = _cachedFrameAddresses = new ConcurrentDictionary<nint, ObjectKind>(_playerRelatedPointers.Where(k => k.Address != nint.Zero).ToDictionary(c => c.CurrentAddress(), c => c.ObjectKind));
lock (_cacheAdditionLock) lock (_cacheAdditionLock)
{ {
_cachedHandledPaths.Clear(); _cachedHandledPaths.Clear();
@@ -250,7 +256,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
} }
// ignore files not belonging to anything player related // ignore files not belonging to anything player related
if (!_cachedFrameAddresses.Contains(gameObject)) if (!_cachedFrameAddresses.TryGetValue(gameObject, out var objectKind))
{ {
lock (_cacheAdditionLock) lock (_cacheAdditionLock)
{ {
@@ -272,9 +278,18 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
} }
else else
{ {
var thing = _playerRelatedPointers.FirstOrDefault(f => f.Address == gameObject);
value.Add(replacedGamePath); value.Add(replacedGamePath);
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, gameObject.ToString("X"), filePath); Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, thing?.ToString() ?? gameObject.ToString("X"), filePath);
Mediator.Publish(new TransientResourceChangedMessage(gameObject)); _ = Task.Run(async () =>
{
_sendTransientCts?.Cancel();
_sendTransientCts?.Dispose();
_sendTransientCts = new();
var token = _sendTransientCts.Token;
await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false);
Mediator.Publish(new TransientResourceChangedMessage(gameObject));
});
} }
} }
@@ -287,4 +302,6 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
_configurationService.Save(); _configurationService.Save();
} }
} }
private CancellationTokenSource _sendTransientCts = new();
} }

View File

@@ -0,0 +1,257 @@
using Lumina.Data;
using Lumina.Extensions;
using System.Text;
using static Lumina.Data.Parsing.MdlStructs;
namespace MareSynchronos.Interop.GameModel;
#pragma warning disable S1104 // Fields should not have public accessibility
// This code is completely and shamelessly borrowed from Penumbra to load V5 and V6 model files.
// Original Source: https://github.com/Ottermandias/Penumbra.GameData/blob/main/Files/MdlFile.cs
public class MdlFile
{
public const int V5 = 0x01000005;
public const int V6 = 0x01000006;
public const uint NumVertices = 17;
public const uint FileHeaderSize = 0x44;
// Raw data to write back.
public uint Version = 0x01000005;
public float Radius;
public float ModelClipOutDistance;
public float ShadowClipOutDistance;
public byte BgChangeMaterialIndex;
public byte BgCrestChangeMaterialIndex;
public ushort CullingGridCount;
public byte Flags3;
public byte Unknown6;
public ushort Unknown8;
public ushort Unknown9;
// Offsets are stored relative to RuntimeSize instead of file start.
public uint[] VertexOffset = [0, 0, 0];
public uint[] IndexOffset = [0, 0, 0];
public uint[] VertexBufferSize = [0, 0, 0];
public uint[] IndexBufferSize = [0, 0, 0];
public byte LodCount;
public bool EnableIndexBufferStreaming;
public bool EnableEdgeGeometry;
public ModelFlags1 Flags1;
public ModelFlags2 Flags2;
public VertexDeclarationStruct[] VertexDeclarations = [];
public ElementIdStruct[] ElementIds = [];
public MeshStruct[] Meshes = [];
public BoundingBoxStruct[] BoneBoundingBoxes = [];
public LodStruct[] Lods = [];
public ExtraLodStruct[] ExtraLods = [];
public MdlFile(string filePath)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var r = new LuminaBinaryReader(stream);
var header = LoadModelFileHeader(r);
LodCount = header.LodCount;
VertexBufferSize = header.VertexBufferSize;
IndexBufferSize = header.IndexBufferSize;
VertexOffset = header.VertexOffset;
IndexOffset = header.IndexOffset;
var dataOffset = FileHeaderSize + header.RuntimeSize + header.StackSize;
for (var i = 0; i < LodCount; ++i)
{
VertexOffset[i] -= dataOffset;
IndexOffset[i] -= dataOffset;
}
VertexDeclarations = new VertexDeclarationStruct[header.VertexDeclarationCount];
for (var i = 0; i < header.VertexDeclarationCount; ++i)
VertexDeclarations[i] = VertexDeclarationStruct.Read(r);
_ = LoadStrings(r);
var modelHeader = LoadModelHeader(r);
ElementIds = new ElementIdStruct[modelHeader.ElementIdCount];
for (var i = 0; i < modelHeader.ElementIdCount; i++)
ElementIds[i] = ElementIdStruct.Read(r);
Lods = new LodStruct[3];
for (var i = 0; i < 3; i++)
{
var lod = r.ReadStructure<LodStruct>();
if (i < LodCount)
{
lod.VertexDataOffset -= dataOffset;
lod.IndexDataOffset -= dataOffset;
}
Lods[i] = lod;
}
ExtraLods = (modelHeader.Flags2 & ModelFlags2.ExtraLodEnabled) != 0
? r.ReadStructuresAsArray<ExtraLodStruct>(3)
: [];
Meshes = new MeshStruct[modelHeader.MeshCount];
for (var i = 0; i < modelHeader.MeshCount; i++)
Meshes[i] = MeshStruct.Read(r);
}
private ModelFileHeader LoadModelFileHeader(LuminaBinaryReader r)
{
var header = ModelFileHeader.Read(r);
Version = header.Version;
EnableIndexBufferStreaming = header.EnableIndexBufferStreaming;
EnableEdgeGeometry = header.EnableEdgeGeometry;
return header;
}
private ModelHeader LoadModelHeader(BinaryReader r)
{
var modelHeader = r.ReadStructure<ModelHeader>();
Radius = modelHeader.Radius;
Flags1 = modelHeader.Flags1;
Flags2 = modelHeader.Flags2;
ModelClipOutDistance = modelHeader.ModelClipOutDistance;
ShadowClipOutDistance = modelHeader.ShadowClipOutDistance;
CullingGridCount = modelHeader.CullingGridCount;
Flags3 = modelHeader.Flags3;
Unknown6 = modelHeader.Unknown6;
Unknown8 = modelHeader.Unknown8;
Unknown9 = modelHeader.Unknown9;
BgChangeMaterialIndex = modelHeader.BGChangeMaterialIndex;
BgCrestChangeMaterialIndex = modelHeader.BGCrestChangeMaterialIndex;
return modelHeader;
}
private static (uint[], string[]) LoadStrings(BinaryReader r)
{
var stringCount = r.ReadUInt16();
r.ReadUInt16();
var stringSize = (int)r.ReadUInt32();
var stringData = r.ReadBytes(stringSize);
var start = 0;
var strings = new string[stringCount];
var offsets = new uint[stringCount];
for (var i = 0; i < stringCount; ++i)
{
var span = stringData.AsSpan(start);
var idx = span.IndexOf((byte)'\0');
strings[i] = Encoding.UTF8.GetString(span[..idx]);
offsets[i] = (uint)start;
start = start + idx + 1;
}
return (offsets, strings);
}
public unsafe struct ModelHeader
{
// MeshHeader
public float Radius;
public ushort MeshCount;
public ushort AttributeCount;
public ushort SubmeshCount;
public ushort MaterialCount;
public ushort BoneCount;
public ushort BoneTableCount;
public ushort ShapeCount;
public ushort ShapeMeshCount;
public ushort ShapeValueCount;
public byte LodCount;
public ModelFlags1 Flags1;
public ushort ElementIdCount;
public byte TerrainShadowMeshCount;
public ModelFlags2 Flags2;
public float ModelClipOutDistance;
public float ShadowClipOutDistance;
public ushort CullingGridCount;
public ushort TerrainShadowSubmeshCount;
public byte Flags3;
public byte BGChangeMaterialIndex;
public byte BGCrestChangeMaterialIndex;
public byte Unknown6;
public ushort BoneTableArrayCountTotal;
public ushort Unknown8;
public ushort Unknown9;
private fixed byte _padding[6];
}
public struct ShapeStruct
{
public uint StringOffset;
public ushort[] ShapeMeshStartIndex;
public ushort[] ShapeMeshCount;
public static ShapeStruct Read(LuminaBinaryReader br)
{
ShapeStruct ret = new ShapeStruct();
ret.StringOffset = br.ReadUInt32();
ret.ShapeMeshStartIndex = br.ReadUInt16Array(3);
ret.ShapeMeshCount = br.ReadUInt16Array(3);
return ret;
}
}
[Flags]
public enum ModelFlags1 : byte
{
DustOcclusionEnabled = 0x80,
SnowOcclusionEnabled = 0x40,
RainOcclusionEnabled = 0x20,
Unknown1 = 0x10,
LightingReflectionEnabled = 0x08,
WavingAnimationDisabled = 0x04,
LightShadowDisabled = 0x02,
ShadowDisabled = 0x01,
}
[Flags]
public enum ModelFlags2 : byte
{
Unknown2 = 0x80,
BgUvScrollEnabled = 0x40,
EnableForceNonResident = 0x20,
ExtraLodEnabled = 0x10,
ShadowMaskEnabled = 0x08,
ForceLodRangeEnabled = 0x04,
EdgeGeometryEnabled = 0x02,
Unknown3 = 0x01
}
public struct VertexDeclarationStruct
{
// There are always 17, but stop when stream = -1
public VertexElement[] VertexElements;
public static VertexDeclarationStruct Read(LuminaBinaryReader br)
{
VertexDeclarationStruct ret = new VertexDeclarationStruct();
var elems = new List<VertexElement>();
// Read the vertex elements that we need
var thisElem = br.ReadStructure<VertexElement>();
do
{
elems.Add(thisElem);
thisElem = br.ReadStructure<VertexElement>();
} while (thisElem.Stream != 255);
// Skip the number of bytes that we don't need to read
// We skip elems.Count * 9 because we had to read the invalid element
int toSeek = 17 * 8 - (elems.Count + 1) * 8;
br.Seek(br.BaseStream.Position + toSeek);
ret.VertexElements = elems.ToArray();
return ret;
}
}
}
#pragma warning restore S1104 // Fields should not have public accessibility

View File

@@ -129,7 +129,7 @@ public sealed class IpcCallerCustomize : IIpcCaller
private void OnCustomizePlusScaleChange(ushort c, Guid g) private void OnCustomizePlusScaleChange(ushort c, Guid g)
{ {
var obj = _dalamudUtil.GetCharacterFromObjectTableByIndex(c); var obj = _dalamudUtil.GetCharacterFromObjectTableByIndex(c);
_mareMediator.Publish(new CustomizePlusMessage(obj?.Name.ToString() ?? string.Empty)); _mareMediator.Publish(new CustomizePlusMessage(obj?.Address ?? null));
} }
public void Dispose() public void Dispose()

View File

@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace MareSynchronos.Interop.Ipc; namespace MareSynchronos.Interop.Ipc;
public sealed class IpcCallerGlamourer : IIpcCaller public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcCaller
{ {
private readonly ILogger<IpcCallerGlamourer> _logger; private readonly ILogger<IpcCallerGlamourer> _logger;
private readonly IDalamudPluginInterface _pi; private readonly IDalamudPluginInterface _pi;
@@ -31,15 +31,15 @@ public sealed class IpcCallerGlamourer : IIpcCaller
private readonly uint LockCode = 0x626E7579; private readonly uint LockCode = 0x626E7579;
public IpcCallerGlamourer(ILogger<IpcCallerGlamourer> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator, public IpcCallerGlamourer(ILogger<IpcCallerGlamourer> logger, IDalamudPluginInterface pi, DalamudUtilService dalamudUtil, MareMediator mareMediator,
RedrawManager redrawManager) RedrawManager redrawManager) : base(logger, mareMediator)
{ {
_glamourerApiVersions = new(pi); _glamourerApiVersions = new ApiVersion(pi);
_glamourerGetAllCustomization = new(pi); _glamourerGetAllCustomization = new GetStateBase64(pi);
_glamourerApplyAll = new(pi); _glamourerApplyAll = new ApplyState(pi);
_glamourerRevert = new(pi); _glamourerRevert = new RevertState(pi);
_glamourerRevertByName = new(pi); _glamourerRevertByName = new RevertStateName(pi);
_glamourerUnlock = new(pi); _glamourerUnlock = new UnlockState(pi);
_glamourerUnlockByName = new(pi); _glamourerUnlockByName = new UnlockStateName(pi);
_logger = logger; _logger = logger;
_pi = pi; _pi = pi;
@@ -50,6 +50,16 @@ public sealed class IpcCallerGlamourer : IIpcCaller
_glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged); _glamourerStateChanged = StateChanged.Subscriber(pi, GlamourerChanged);
_glamourerStateChanged.Enable(); _glamourerStateChanged.Enable();
Mediator.Subscribe<DalamudLoginMessage>(this, s => _shownGlamourerUnavailable = false);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_redrawManager.Cancel();
_glamourerStateChanged?.Dispose();
} }
public bool APIAvailable { get; private set; } public bool APIAvailable { get; private set; }
@@ -93,11 +103,6 @@ public sealed class IpcCallerGlamourer : IIpcCaller
} }
} }
public void Dispose()
{
_glamourerStateChanged?.Dispose();
}
public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false)
{ {
if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;

View File

@@ -1,5 +1,4 @@
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Ipc;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;

View File

@@ -1,4 +1,4 @@
using Dalamud.Plugin; using Dalamud.Plugin;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services; using MareSynchronos.Services;
@@ -90,6 +90,8 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
{ {
_penumbraRedraw.Invoke(msg.Character.ObjectIndex, RedrawType.AfterGPose); _penumbraRedraw.Invoke(msg.Character.ObjectIndex, RedrawType.AfterGPose);
}); });
Mediator.Subscribe<DalamudLoginMessage>(this, (msg) => _shownPenumbraUnavailable = false);
} }
public bool APIAvailable { get; private set; } = false; public bool APIAvailable { get; private set; } = false;
@@ -103,7 +105,14 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
.FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase))
?.Version ?? new Version(0, 0, 0, 0)); ?.Version ?? new Version(0, 0, 0, 0));
penumbraAvailable = penumbraVersion >= new Version(1, 0, 1, 0); penumbraAvailable = penumbraVersion >= new Version(1, 0, 1, 0);
penumbraAvailable &= _penumbraEnabled.Invoke(); try
{
penumbraAvailable &= _penumbraEnabled.Invoke();
}
catch
{
penumbraAvailable = false;
}
_shownPenumbraUnavailable = _shownPenumbraUnavailable && !penumbraAvailable; _shownPenumbraUnavailable = _shownPenumbraUnavailable && !penumbraAvailable;
APIAvailable = penumbraAvailable; APIAvailable = penumbraAvailable;
} }
@@ -196,7 +205,7 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
await _dalamudUtil.RunOnFrameworkThread(async () => await _dalamudUtil.RunOnFrameworkThread(async () =>
{ {
var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false); var gameObject = await _dalamudUtil.CreateGameObjectAsync(await _dalamudUtil.GetPlayerPointerAsync().ConfigureAwait(false)).ConfigureAwait(false);
_penumbraRedraw.Invoke(gameObject!.ObjectIndex, RedrawType.Redraw); _penumbraRedraw.Invoke(gameObject!.ObjectIndex, setting: RedrawType.Redraw);
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
@@ -210,6 +219,7 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName); var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId); logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
return collId; return collId;
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
@@ -330,4 +340,4 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
_mareMediator.Publish(new PenumbraInitializedMessage()); _mareMediator.Publish(new PenumbraInitializedMessage());
_penumbraRedraw!.Invoke(0, setting: RedrawType.Redraw); _penumbraRedraw!.Invoke(0, setting: RedrawType.Redraw);
} }
} }

View File

@@ -1,22 +1,22 @@
using Dalamud.Plugin; using MareSynchronos.WebAPI;
using MareSynchronos.MareConfiguration.Configurations;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace MareSynchronos.MareConfiguration; namespace MareSynchronos.MareConfiguration;
public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, IDalamudPluginInterface pi, public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger,
NotesConfigService notesConfig) : IHostedService NotesConfigService notesConfig) : IHostedService
{ {
private readonly ILogger<ConfigurationMigrator> _logger = logger;
public void Migrate() public void Migrate()
{ {
var oldUri = MareSynchronos.WebAPI.ApiController.LoporritServiceUriOld; var oldUri = ApiController.LoporritServiceUriOld;
var newUri = MareSynchronos.WebAPI.ApiController.LoporritServiceUri; var newUri = ApiController.LoporritServiceUri;
if (notesConfig.Current.ServerNotes.TryGetValue(oldUri, out var old)) if (notesConfig.Current.ServerNotes.TryGetValue(oldUri, out var old))
{ {
logger.LogDebug("Migrating server notes {old} => {new}", oldUri, newUri); _logger.LogDebug("Migrating server notes {old} => {new}", oldUri, newUri);
notesConfig.Current.ServerNotes.TryAdd(newUri, new()); notesConfig.Current.ServerNotes.TryAdd(newUri, new());
var merged = notesConfig.Current.ServerNotes.GetValueOrDefault(newUri, new()); var merged = notesConfig.Current.ServerNotes.GetValueOrDefault(newUri, new());
foreach (var (k, v) in old.GidServerComments) foreach (var (k, v) in old.GidServerComments)
@@ -38,11 +38,4 @@ public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, IDalam
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
private static void SaveConfig(IMareConfiguration config, string path)
{
File.WriteAllText(path, JsonConvert.SerializeObject(config, Formatting.Indented));
}
private string ConfigurationPath(string configName) => Path.Combine(pi.ConfigDirectory.FullName, configName);
} }

View File

@@ -159,7 +159,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
{ {
Mediator.Publish(new NotificationMessage("Abnormal Log Level", Mediator.Publish(new NotificationMessage("Abnormal Log Level",
$"Your log level is set to '{_mareConfigService.Current.LogLevel}' which is not recommended for normal usage. Set it to '{LogLevel.Information}' in \"Loporrit Settings -> Debug\" unless instructed otherwise.", $"Your log level is set to '{_mareConfigService.Current.LogLevel}' which is not recommended for normal usage. Set it to '{LogLevel.Information}' in \"Loporrit Settings -> Debug\" unless instructed otherwise.",
NotificationType.Error, TimeSpan.FromSeconds(15000))); MareConfiguration.Models.NotificationType.Error, TimeSpan.FromSeconds(15000)));
} }
#endif #endif
} }

View File

@@ -64,7 +64,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
} }
foreach (var id in _gposeCustomizeObjects.Where(d => d != null)) foreach (var id in _gposeCustomizeObjects.Where(d => d != null))
{ {
await _ipcManager.CustomizePlus.RevertByIdAsync(id).ConfigureAwait(false); await _ipcManager.CustomizePlus.RevertByIdAsync(id.Value);
} }
_gposeGameObjects.Clear(); _gposeGameObjects.Clear();
}); });
@@ -100,7 +100,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
} }
var applicationId = Guid.NewGuid(); var applicationId = Guid.NewGuid();
var coll = await _ipcManager.Penumbra.CreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false); var coll = await _ipcManager.Penumbra.CreateTemporaryCollectionAsync(_logger, charaTarget.Name.TextValue).ConfigureAwait(false);
await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectTableIndex()!.Value).ConfigureAwait(false); await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(_logger, coll, charaTarget.ObjectIndex).ConfigureAwait(false);
await _ipcManager.Penumbra.SetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false); await _ipcManager.Penumbra.SetTemporaryModsAsync(_logger, applicationId, coll, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal)).ConfigureAwait(false);
await _ipcManager.Penumbra.SetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false); await _ipcManager.Penumbra.SetManipulationDataAsync(_logger, applicationId, coll, LoadedCharaFile.CharaFileData.ManipulationData).ConfigureAwait(false);

View File

@@ -1,11 +1,11 @@
using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache;
using MareSynchronos.FileCache;
using MareSynchronos.Interop.Ipc; using MareSynchronos.Interop.Ipc;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MareSynchronos.PlayerData.Factories; namespace MareSynchronos.PlayerData.Factories;
@@ -16,18 +16,19 @@ public class PairHandlerFactory
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
private readonly FileDownloadManagerFactory _fileDownloadManagerFactory; private readonly FileDownloadManagerFactory _fileDownloadManagerFactory;
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
private readonly CancellationToken _dalamudLifetimeToken; private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly MareMediator _mareMediator; private readonly MareMediator _mareMediator;
private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly PlayerPerformanceService _playerPerformanceService;
private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly ServerConfigurationManager _serverConfigurationManager;
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
PluginWarningNotificationService pluginWarningNotificationManager, ServerConfigurationManager serverConfigurationManager, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
CancellationToken dalamudLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator, XivDataAnalyzer modelAnalyzer) FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
@@ -35,17 +36,17 @@ public class PairHandlerFactory
_fileDownloadManagerFactory = fileDownloadManagerFactory; _fileDownloadManagerFactory = fileDownloadManagerFactory;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_pluginWarningNotificationManager = pluginWarningNotificationManager; _pluginWarningNotificationManager = pluginWarningNotificationManager;
_serverConfigurationManager = serverConfigurationManager; _hostApplicationLifetime = hostApplicationLifetime;
_dalamudLifetimeToken = dalamudLifetime;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_mareMediator = mareMediator; _mareMediator = mareMediator;
_xivDataAnalyzer = modelAnalyzer; _playerPerformanceService = playerPerformanceService;
_serverConfigManager = serverConfigManager;
} }
public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto) public PairHandler Create(Pair pair)
{ {
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), onlineUserIdentDto, _gameObjectHandlerFactory, return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _gameObjectHandlerFactory,
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _serverConfigurationManager, _dalamudUtilService, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
_dalamudLifetimeToken, _fileCacheManager, _mareMediator, _xivDataAnalyzer); _fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager);
} }
} }

View File

@@ -116,7 +116,6 @@ public class PlayerDataFactory
private async Task<CharacterData> CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token) private async Task<CharacterData> CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
{ {
var objectKind = playerRelatedObject.ObjectKind; var objectKind = playerRelatedObject.ObjectKind;
var charaPointer = playerRelatedObject.Address;
_logger.LogDebug("Building character data for {obj}", playerRelatedObject); _logger.LogDebug("Building character data for {obj}", playerRelatedObject);
@@ -178,10 +177,10 @@ public class PlayerDataFactory
_logger.LogDebug("Handling transient update for {obj}", playerRelatedObject); _logger.LogDebug("Handling transient update for {obj}", playerRelatedObject);
// remove all potentially gathered paths from the transient resource manager that are resolved through static resolving // remove all potentially gathered paths from the transient resource manager that are resolved through static resolving
_transientResourceManager.ClearTransientPaths(charaPointer, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList()); _transientResourceManager.ClearTransientPaths(playerRelatedObject.Address, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
// get all remaining paths and resolve them // get all remaining paths and resolve them
var transientPaths = ManageSemiTransientData(objectKind, charaPointer); var transientPaths = ManageSemiTransientData(objectKind, playerRelatedObject.Address);
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false); var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
_logger.LogDebug("== Transient Replacements =="); _logger.LogDebug("== Transient Replacements ==");
@@ -217,7 +216,7 @@ public class PlayerDataFactory
if (objectKind == ObjectKind.Player) if (objectKind == ObjectKind.Player)
{ {
previousData.PetNamesData = _ipcManager.PetNames.GetLocalNames(); previousData.PetNamesData = _ipcManager.PetNames.GetLocalNames();
_logger.LogDebug("Pet Nicknames is now: {moodles}", previousData.PetNamesData); _logger.LogDebug("Pet Nicknames is now: {petnames}", previousData.PetNamesData);
} }
if (previousData.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? fileReplacements)) if (previousData.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? fileReplacements))

View File

@@ -1,13 +1,10 @@
using Dalamud.Game.ClientState.Objects.Types; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using MareSynchronos.Services; using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Runtime.InteropServices; using System.Runtime.CompilerServices;
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer; using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
@@ -28,7 +25,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private CancellationTokenSource _zoningCts = new(); private CancellationTokenSource _zoningCts = new();
public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector, public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector,
MareMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, bool watchedObject = true) : base(logger, mediator) MareMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, bool ownedObject = true) : base(logger, mediator)
{ {
_performanceCollector = performanceCollector; _performanceCollector = performanceCollector;
ObjectKind = objectKind; ObjectKind = objectKind;
@@ -38,10 +35,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
_dalamudUtil.EnsureIsOnFramework(); _dalamudUtil.EnsureIsOnFramework();
return getAddress.Invoke(); return getAddress.Invoke();
}; };
_isOwnedObject = watchedObject; _isOwnedObject = ownedObject;
Name = string.Empty; Name = string.Empty;
if (watchedObject) if (ownedObject)
{ {
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) => Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) =>
{ {
@@ -114,13 +111,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private ushort[] MainHandData { get; set; } = new ushort[3]; private ushort[] MainHandData { get; set; } = new ushort[3];
private ushort[] OffHandData { get; set; } = new ushort[3]; private ushort[] OffHandData { get; set; } = new ushort[3];
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<ICharacter> act, CancellationToken token) public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<Dalamud.Game.ClientState.Objects.Types.ICharacter> act, CancellationToken token)
{ {
while (await _dalamudUtil.RunOnFrameworkThread(() => while (await _dalamudUtil.RunOnFrameworkThread(() =>
{ {
if (IsBeingDrawn()) return true; if (IsBeingDrawn()) return true;
var gameObj = _dalamudUtil.CreateGameObject(Address); var gameObj = _dalamudUtil.CreateGameObject(Address);
if (gameObj is ICharacter chara) if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara)
{ {
act.Invoke(chara); act.Invoke(chara);
} }
@@ -149,7 +146,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
return _getAddress.Invoke(); return _getAddress.Invoke();
} }
public IGameObject? GetGameObject() public Dalamud.Game.ClientState.Objects.Types.IGameObject? GetGameObject()
{ {
return _dalamudUtil.CreateGameObject(Address); return _dalamudUtil.CreateGameObject(Address);
} }
@@ -189,7 +186,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
if (Address != IntPtr.Zero) if (Address != IntPtr.Zero)
{ {
_ptrNullCounter = 0; _ptrNullCounter = 0;
var drawObjAddr = (IntPtr)((GameObject*)Address)->DrawObject; var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject;
DrawObjectAddress = drawObjAddr; DrawObjectAddress = drawObjAddr;
} }
else else
@@ -211,9 +208,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
_clearCts = null; _clearCts = null;
} }
var chara = (Character*)Address; var chara = (Character*)Address;
string name; var name = chara->GameObject.NameString;
fixed (byte* nameData = chara->GameObject.Name)
MemoryHelper.ReadStringNullTerminated((nint)nameData, out name);
bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal);
if (nameChange) if (nameChange)
{ {
@@ -244,8 +239,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
} }
else else
{ {
fixed (EquipmentModelId* equipmentData = chara->DrawData.EquipmentModelIds) equipDiff = CompareAndUpdateEquipByteData((byte*)Unsafe.AsPointer(ref chara->DrawData.EquipmentModelIds[0]));
equipDiff = CompareAndUpdateEquipByteData((byte*)equipmentData);
if (equipDiff) if (equipDiff)
Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff); Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff);
} }
@@ -275,15 +269,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
TribeId = tribeId; TribeId = tribeId;
} }
fixed (byte* customizeData = ((Human*)DrawObjectAddress)->Customize.Data) customizeDiff = CompareAndUpdateCustomizeData(((Human*)DrawObjectAddress)->Customize.Data);
customizeDiff = CompareAndUpdateCustomizeData(customizeData);
if (customizeDiff) if (customizeDiff)
Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff); Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff);
} }
else else
{ {
fixed (byte* customizeData = ((Human*)DrawObjectAddress)->Customize.Data) customizeDiff = CompareAndUpdateCustomizeData(chara->DrawData.CustomizeData.Data);
customizeDiff = CompareAndUpdateCustomizeData(customizeData);
if (customizeDiff) if (customizeDiff)
Logger.LogTrace("Checking [{this}] customize data from game obj, result: {diff}", this, equipDiff); Logger.LogTrace("Checking [{this}] customize data from game obj, result: {diff}", this, equipDiff);
} }
@@ -316,13 +308,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
_clearCts = null; _clearCts = null;
} }
private unsafe bool CompareAndUpdateCustomizeData(byte* customizeData) private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
{ {
bool hasChanges = false; bool hasChanges = false;
for (int i = 0; i < CustomizeData.Length; i++) for (int i = 0; i < customizeData.Length; i++)
{ {
var data = Marshal.ReadByte((IntPtr)customizeData, i); var data = customizeData[i];
if (CustomizeData[i] != data) if (CustomizeData[i] != data)
{ {
CustomizeData[i] = data; CustomizeData[i] = data;
@@ -338,7 +330,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
bool hasChanges = false; bool hasChanges = false;
for (int i = 0; i < EquipSlotData.Length; i++) for (int i = 0; i < EquipSlotData.Length; i++)
{ {
var data = Marshal.ReadByte((IntPtr)equipSlotData, i); var data = equipSlotData[i];
if (EquipSlotData[i] != data) if (EquipSlotData[i] != data)
{ {
EquipSlotData[i] = data; EquipSlotData[i] = data;
@@ -392,7 +384,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private unsafe IntPtr GetDrawObjUnsafe(nint curPtr) private unsafe IntPtr GetDrawObjUnsafe(nint curPtr)
{ {
return (IntPtr)((GameObject*)curPtr)->DrawObject; return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject;
} }
private bool IsBeingDrawn() private bool IsBeingDrawn()
@@ -433,7 +425,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
{ {
var drawObjZero = drawObj == IntPtr.Zero; var drawObjZero = drawObj == IntPtr.Zero;
if (drawObjZero) return DrawCondition.DrawObjectZero; if (drawObjZero) return DrawCondition.DrawObjectZero;
var renderFlags = ((GameObject*)curPtr)->RenderFlags != 0x0; var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags) != 0x0;
if (renderFlags) return DrawCondition.RenderFlags; if (renderFlags) return DrawCondition.RenderFlags;
if (ObjectKind == ObjectKind.Player) if (ObjectKind == ObjectKind.Player)
@@ -459,6 +451,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
{ {
_zoningCts?.CancelAfter(2500); _zoningCts?.CancelAfter(2500);
} }
catch (ObjectDisposedException)
{
// ignore
}
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Zoning CTS cancel issue"); Logger.LogWarning(ex, "Zoning CTS cancel issue");

View File

@@ -1,5 +1,4 @@
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
using MareSynchronos.Interop.Ipc; using MareSynchronos.Interop.Ipc;
using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Factories;
@@ -10,6 +9,7 @@ using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Files; using MareSynchronos.WebAPI.Files;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
@@ -24,45 +24,44 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly FileDownloadManager _downloadManager; private readonly FileDownloadManager _downloadManager;
private readonly FileCacheManager _fileDbManager; private readonly FileCacheManager _fileDbManager;
private readonly XivDataAnalyzer _xivDataAnalyzer;
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly CancellationToken _lifetime; private readonly IHostApplicationLifetime _lifetime;
private readonly PlayerPerformanceService _playerPerformanceService;
private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly ServerConfigurationManager _serverConfigurationManager;
private CancellationTokenSource? _applicationCancellationTokenSource = new(); private CancellationTokenSource? _applicationCancellationTokenSource = new();
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 CombatData? _dataReceivedInDowntime;
private CancellationTokenSource? _downloadCancellationTokenSource = new(); private CancellationTokenSource? _downloadCancellationTokenSource = new();
private bool _forceApplyMods = false; private bool _forceApplyMods = false;
private bool _isVisible; private bool _isVisible;
private Guid _penumbraCollection = Guid.Empty; private Guid _penumbraCollection = Guid.Empty;
private Dictionary<ObjectKind, Guid?> _customizeIds = [];
private bool _redrawOnNextApplication = false; private bool _redrawOnNextApplication = false;
private CombatData? _dataReceivedInCombat;
public long LastAppliedDataSize { get; private set; }
public long LastAppliedDataTris { get; private set; }
public PairHandler(ILogger<PairHandler> logger, OnlineUserIdentDto onlineUser, public PairHandler(ILogger<PairHandler> logger, Pair pair,
GameObjectHandlerFactory gameObjectHandlerFactory, GameObjectHandlerFactory gameObjectHandlerFactory,
IpcManager ipcManager, FileDownloadManager transferManager, IpcManager ipcManager, FileDownloadManager transferManager,
PluginWarningNotificationService pluginWarningNotificationManager, ServerConfigurationManager serverConfigurationManager, PluginWarningNotificationService pluginWarningNotificationManager,
DalamudUtilService dalamudUtil, CancellationToken lifetime, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
FileCacheManager fileDbManager, MareMediator mediator, FileCacheManager fileDbManager, MareMediator mediator,
XivDataAnalyzer modelAnalyzer) : base(logger, mediator) PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager) : base(logger, mediator)
{ {
OnlineUser = onlineUser; Pair = pair;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
_ipcManager = ipcManager; _ipcManager = ipcManager;
_downloadManager = transferManager; _downloadManager = transferManager;
_pluginWarningNotificationManager = pluginWarningNotificationManager; _pluginWarningNotificationManager = pluginWarningNotificationManager;
_serverConfigurationManager = serverConfigurationManager;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_lifetime = lifetime; _lifetime = lifetime;
_fileDbManager = fileDbManager; _fileDbManager = fileDbManager;
_xivDataAnalyzer = modelAnalyzer; _playerPerformanceService = playerPerformanceService;
_serverConfigManager = serverConfigManager;
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) =>
@@ -73,6 +72,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
}); });
Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) =>
{ {
_penumbraCollection = Guid.Empty;
if (!IsVisible && _charaHandler != null) if (!IsVisible && _charaHandler != null)
{ {
PlayerName = string.Empty; PlayerName = string.Empty;
@@ -87,24 +87,23 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_redrawOnNextApplication = true; _redrawOnNextApplication = true;
} }
}); });
Mediator.Subscribe<CombatEndMessage>(this, (msg) => Mediator.Subscribe<CombatOrPerformanceEndMessage>(this, (msg) =>
{ {
if (IsVisible && _dataReceivedInCombat != null) if (IsVisible && _dataReceivedInDowntime != null)
{ {
ApplyCharacterData(_dataReceivedInCombat.ApplicationId, ApplyCharacterData(_dataReceivedInDowntime.ApplicationId,
_dataReceivedInCombat.CharacterData, _dataReceivedInCombat.Forced); _dataReceivedInDowntime.CharacterData, _dataReceivedInDowntime.Forced);
_dataReceivedInCombat = null; _dataReceivedInDowntime = null;
} }
}); });
Mediator.Subscribe<CombatStartMessage>(this, _ => Mediator.Subscribe<CombatOrPerformanceStartMessage>(this, _ =>
{ {
_dataReceivedInCombat = null; _dataReceivedInDowntime = null;
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate(); _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate();
_applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate(); _applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate();
}); });
LastAppliedDataSize = -1; LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
} }
public bool IsVisible public bool IsVisible
@@ -116,34 +115,36 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
_isVisible = value; _isVisible = value;
string text = "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"); string text = "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible");
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler),
EventSeverity.Informational, text))); EventSeverity.Informational, text)));
} }
} }
} }
public OnlineUserIdentDto OnlineUser { get; private set; }
public long LastAppliedDataBytes { get; private set; }
public Pair Pair { get; private set; }
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 unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero
? uint.MaxValue ? uint.MaxValue
: ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId; : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId;
public string? PlayerName { get; private set; } public string? PlayerName { get; private set; }
public string PlayerNameHash => OnlineUser.Ident; public string PlayerNameHash => Pair.Ident;
public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false)
{ {
if (_dalamudUtil.IsInCombat) if (_dalamudUtil.IsInCombatOrPerforming)
{ {
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning,
"Cannot apply character data: you are in combat, deferring application"))); "Cannot apply character data: you are in combat or performing music, 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 or performing", applicationBase);
_dataReceivedInCombat = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(isUploading: false);
return; return;
} }
if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero))
{ {
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning,
"Cannot apply character data: Receiving Player is in an invalid state, deferring application"))); "Cannot apply character data: Receiving Player is in an invalid state, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}",
applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero);
@@ -165,13 +166,13 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.Penumbra.APIAvailable || !_ipcManager.Glamourer.APIAvailable) if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.Penumbra.APIAvailable || !_ipcManager.Glamourer.APIAvailable)
{ {
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning,
"Cannot apply character data: you are in GPose, a Cutscene or Penumbra/Glamourer is not available"))); "Cannot apply character data: you are in GPose, a Cutscene or Penumbra/Glamourer is not available")));
Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this); Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this);
return; return;
} }
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational,
"Applying Character Data"))); "Applying Character Data")));
_forceApplyMods |= forceApplyCustomization; _forceApplyMods |= forceApplyCustomization;
@@ -191,7 +192,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges))
{ {
_pluginWarningNotificationManager.NotifyForMissingPlugins(OnlineUser.User, PlayerName!, playerChanges); _pluginWarningNotificationManager.NotifyForMissingPlugins(Pair.UserData, PlayerName!, playerChanges);
} }
Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, this); Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, this);
@@ -201,9 +202,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
public override string ToString() public override string ToString()
{ {
return OnlineUser == null return Pair == null
? base.ToString() ?? string.Empty ? base.ToString() ?? string.Empty
: OnlineUser.User.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar"); : Pair.UserData.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar");
} }
internal void SetUploading(bool isUploading = true) internal void SetUploading(bool isUploading = true)
@@ -222,7 +223,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
SetUploading(isUploading: false); SetUploading(isUploading: false);
_downloadManager.Dispose(); _downloadManager.Dispose();
var name = PlayerName; var name = PlayerName;
Logger.LogDebug("Disposing {name} ({user})", name, OnlineUser); Logger.LogDebug("Disposing {name} ({user})", name, Pair);
try try
{ {
Guid applicationId = Guid.NewGuid(); Guid applicationId = Guid.NewGuid();
@@ -235,25 +236,24 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
{ {
Mediator.Publish(new EventMessage(new Event(name, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, "Disposing User"))); Mediator.Publish(new EventMessage(new Event(name, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, "Disposing User")));
} }
if (_lifetime.IsCancellationRequested) return; Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, Pair.UserPair);
Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser);
if (_penumbraCollection != Guid.Empty) if (_penumbraCollection != Guid.Empty)
{ {
_ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult();
_penumbraCollection = Guid.Empty; _penumbraCollection = Guid.Empty;
} }
if (_lifetime.ApplicationStopping.IsCancellationRequested) return;
if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name))
{ {
Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", applicationId, name, OnlineUser); Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", applicationId, name, Pair.UserPair);
if (!IsVisible) if (!IsVisible)
{ {
Logger.LogDebug("[{applicationId}] Restoring Glamourer for {name} ({user})", applicationId, name, OnlineUser); Logger.LogDebug("[{applicationId}] Restoring Glamourer for {name} ({user})", applicationId, name, Pair.UserPair);
_ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).GetAwaiter().GetResult(); _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).GetAwaiter().GetResult();
} }
else else
@@ -261,6 +261,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(60)); cts.CancelAfter(TimeSpan.FromSeconds(60));
Logger.LogInformation("[{applicationId}] CachedData is null {isNull}, contains things: {contains}", applicationId, _cachedData == null, _cachedData?.FileReplacements.Any() ?? false);
foreach (KeyValuePair<ObjectKind, List<FileReplacementData>> item in _cachedData?.FileReplacements ?? []) foreach (KeyValuePair<ObjectKind, List<FileReplacementData>> item in _cachedData?.FileReplacements ?? [])
{ {
try try
@@ -380,136 +382,151 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource(); _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource();
var downloadToken = _downloadCancellationTokenSource.Token; var downloadToken = _downloadCancellationTokenSource.Token;
_ = Task.Run(async () => _ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, downloadToken).ConfigureAwait(false);
}
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
bool updateModdedPaths, bool updateManip, CancellationToken downloadToken)
{
Dictionary<(string GamePath, string? Hash), string> moddedPaths = [];
if (updateModdedPaths)
{ {
Dictionary<(string GamePath, string? Hash), string> moddedPaths = new(); int attempts = 0;
List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)
{
_downloadManager.CancelDownload();
Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData);
Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational,
$"Starting download for {toDownloadReplacements.Count} files")));
var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false);
if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, toDownloadFiles))
{
_downloadManager.CancelDownload();
return;
}
await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false);
_downloadManager.CancelDownload();
if (downloadToken.IsCancellationRequested)
{
Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase);
_downloadManager.CancelDownload();
return;
}
toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal))))
{
break;
}
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
}
if (!await _playerPerformanceService.CheckBothThresholds(this, charaData).ConfigureAwait(false))
return;
}
downloadToken.ThrowIfCancellationRequested();
var appToken = _applicationCancellationTokenSource?.Token;
while ((!_applicationTask?.IsCompleted ?? false)
&& !downloadToken.IsCancellationRequested
&& (!appToken?.IsCancellationRequested ?? false))
{
// block until current application is done
Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName);
await Task.Delay(250).ConfigureAwait(false);
}
if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) return;
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
var token = _applicationCancellationTokenSource.Token;
_applicationTask = ApplyCharacterDataAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token);
}
private async Task ApplyCharacterDataAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData, bool updateModdedPaths, bool updateManip,
Dictionary<(string GamePath, string? Hash), string> moddedPaths, CancellationToken token)
{
try
{
_applicationId = Guid.NewGuid();
Logger.LogDebug("[BASE-{applicationId}] Starting application task for {this}: {appId}", applicationBase, this, _applicationId);
Logger.LogDebug("[{applicationId}] Waiting for initial draw for for {handler}", _applicationId, _charaHandler);
await _dalamudUtil.WaitWhileCharacterIsDrawing(Logger, _charaHandler!, _applicationId, 30000, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
if (updateModdedPaths) if (updateModdedPaths)
{ {
int attempts = 0; // ensure collection is set
List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); var objIndex = await _dalamudUtil.RunOnFrameworkThread(() => _charaHandler!.GetGameObject()!.ObjectIndex).ConfigureAwait(false);
await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, _penumbraCollection, objIndex).ConfigureAwait(false);
while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection,
moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false);
LastAppliedDataBytes = -1;
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists))
{ {
_downloadManager.CancelDownload(); if (LastAppliedDataBytes == -1) LastAppliedDataBytes = 0;
Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData);
if (toDownloadReplacements.Any())
{
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational,
$"Starting download for {toDownloadReplacements.Count} files")));
await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false);
_downloadManager.CancelDownload();
}
if (downloadToken.IsCancellationRequested) LastAppliedDataBytes += path.Length;
{
Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase);
_downloadManager.CancelDownload();
return;
}
toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal))))
{
break;
}
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
} }
} }
downloadToken.ThrowIfCancellationRequested(); if (updateManip)
var appToken = _applicationCancellationTokenSource?.Token;
while ((!_applicationTask?.IsCompleted ?? false)
&& !downloadToken.IsCancellationRequested
&& (!appToken?.IsCancellationRequested ?? false))
{ {
// block until current application is done await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, _applicationId, _penumbraCollection, charaData.ManipulationData).ConfigureAwait(false);
Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName);
await Task.Delay(250).ConfigureAwait(false);
} }
if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) return; token.ThrowIfCancellationRequested();
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); foreach (var kind in updatedData)
var token = _applicationCancellationTokenSource.Token;
_applicationTask = Task.Run(async () =>
{ {
try await ApplyCustomizationDataAsync(_applicationId, kind, charaData, token).ConfigureAwait(false);
{ token.ThrowIfCancellationRequested();
_applicationId = Guid.NewGuid(); }
Logger.LogDebug("[BASE-{applicationId}] Starting application task for {this}: {appId}", applicationBase, this, _applicationId);
Logger.LogDebug("[{applicationId}] Waiting for initial draw for for {handler}", _applicationId, _charaHandler); _cachedData = charaData;
await _dalamudUtil.WaitWhileCharacterIsDrawing(Logger, _charaHandler!, _applicationId, 30000, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested(); Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
}
if (updateModdedPaths) catch (Exception ex)
{ {
await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException))
moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false); {
LastAppliedDataSize = -1; IsVisible = false;
LastAppliedDataTris = -1; _forceApplyMods = true;
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) _cachedData = charaData;
{ Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId);
if (LastAppliedDataSize == -1) LastAppliedDataSize = 0; }
LastAppliedDataSize += path.Length; else
} {
foreach (var key in moddedPaths.Keys.Where(k => !string.IsNullOrEmpty(k.Hash))) Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId);
{ }
if (LastAppliedDataTris == -1) LastAppliedDataTris = 0; }
LastAppliedDataTris += await _xivDataAnalyzer.GetTrianglesByHash(key.Hash!).ConfigureAwait(false);
}
}
if (updateManip)
{
await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, _applicationId, _penumbraCollection, charaData.ManipulationData).ConfigureAwait(false);
}
token.ThrowIfCancellationRequested();
foreach (var kind in updatedData)
{
await ApplyCustomizationDataAsync(_applicationId, kind, charaData, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
}
_cachedData = charaData;
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
}
catch (Exception ex)
{
if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException))
{
IsVisible = false;
_forceApplyMods = true;
_cachedData = charaData;
Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId);
}
else
{
Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId);
}
}
}, token);
}, downloadToken);
} }
private void FrameworkUpdate() private void FrameworkUpdate()
{ {
if (string.IsNullOrEmpty(PlayerName)) if (string.IsNullOrEmpty(PlayerName))
{ {
var pc = _dalamudUtil.FindPlayerByNameHash(OnlineUser.Ident); var pc = _dalamudUtil.FindPlayerByNameHash(Pair.Ident);
if (pc.ObjectId == 0) return; if (pc.ObjectId == 0) return;
Logger.LogDebug("One-Time Initializing {this}", this); Logger.LogDebug("One-Time Initializing {this}", this);
Initialize(pc.Name); Initialize(pc.Name);
Logger.LogDebug("One-Time Initialized {this}", this); Logger.LogDebug("One-Time Initialized {this}", this);
Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational,
$"Initializing User For Character {pc.Name}"))); $"Initializing User For Character {pc.Name}")));
} }
@@ -545,7 +562,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private void Initialize(string name) private void Initialize(string name)
{ {
PlayerName = name; PlayerName = name;
_charaHandler = _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident), isWatched: false).GetAwaiter().GetResult(); _charaHandler = _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Pair.Ident), isWatched: false).GetAwaiter().GetResult();
Mediator.Subscribe<HonorificReadyMessage>(this, async (_) => Mediator.Subscribe<HonorificReadyMessage>(this, async (_) =>
{ {
@@ -562,17 +579,16 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
}); });
if (_penumbraCollection == Guid.Empty) if (_penumbraCollection == Guid.Empty)
_penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(Logger, OnlineUser.User.UID).GetAwaiter().GetResult(); _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(Logger, Pair.UserData.UID).GetAwaiter().GetResult();
_ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult(); _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, _penumbraCollection, _charaHandler.GetGameObject()!.ObjectIndex).GetAwaiter().GetResult();
_serverConfigurationManager.SetNameForUid(OnlineUser.User.UID, name);
} }
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(OnlineUser.Ident); nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Pair.Ident);
if (address == nint.Zero) return; if (address == nint.Zero) return;
Logger.LogDebug("[{applicationId}] Reverting all Customization for {alias}/{name} {objectKind}", applicationId, OnlineUser.User.AliasOrUID, name, objectKind); Logger.LogDebug("[{applicationId}] Reverting all Customization for {alias}/{name} {objectKind}", applicationId, Pair.UserData.AliasOrUID, name, objectKind);
if (_customizeIds.TryGetValue(objectKind, out var customizeId)) if (_customizeIds.TryGetValue(objectKind, out var customizeId))
{ {
@@ -583,18 +599,18 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, isWatched: false).ConfigureAwait(false); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, isWatched: false).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name);
await _ipcManager.Glamourer.RevertAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false); await _ipcManager.Glamourer.RevertAsync(Logger, tempHandler, applicationId, cancelToken).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name);
await _ipcManager.Heels.RestoreOffsetForPlayerAsync(address).ConfigureAwait(false); await _ipcManager.Heels.RestoreOffsetForPlayerAsync(address).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name);
await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).ConfigureAwait(false); await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); tempHandler.CompareNameAndThrow(name);
Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name);
await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false); await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false);
Logger.LogDebug("[{applicationId}] Restoring Pet Nicknames for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); Logger.LogDebug("[{applicationId}] Restoring Pet Nicknames for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name);
await _ipcManager.PetNames.ClearPlayerData(address).ConfigureAwait(false); await _ipcManager.PetNames.ClearPlayerData(address).ConfigureAwait(false);
} }
else if (objectKind == ObjectKind.MinionOrMount) else if (objectKind == ObjectKind.MinionOrMount)
@@ -636,7 +652,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
Stopwatch st = Stopwatch.StartNew(); Stopwatch st = Stopwatch.StartNew();
ConcurrentBag<FileReplacementData> missingFiles = []; ConcurrentBag<FileReplacementData> missingFiles = [];
moddedDictionary = new Dictionary<(string GamePath, string? Hash), string>(); moddedDictionary = [];
ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new(); ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new();
bool hasMigrationChanges = false; bool hasMigrationChanges = false;
@@ -692,4 +708,4 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
Logger.LogDebug("[BASE-{appBase}] ModdedPaths calculated in {time}ms, missing files: {count}, total files: {total}", applicationBase, st.ElapsedMilliseconds, missingFiles.Count, moddedDictionary.Keys.Count); Logger.LogDebug("[BASE-{appBase}] ModdedPaths calculated in {time}ms, missing files: {count}, total files: {total}", applicationBase, st.ElapsedMilliseconds, missingFiles.Count, moddedDictionary.Keys.Count);
return [.. missingFiles]; return [.. missingFiles];
} }
} }

View File

@@ -53,7 +53,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase
var newVisiblePlayers = _newVisiblePlayers.ToList(); var newVisiblePlayers = _newVisiblePlayers.ToList();
_newVisiblePlayers.Clear(); _newVisiblePlayers.Clear();
Logger.LogTrace("Has new visible players, pushing character data"); Logger.LogTrace("Has new visible players, pushing character data");
PushCharacterData(newVisiblePlayers.Select(c => c.OnlineUser.User).ToList()); PushCharacterData(newVisiblePlayers.Select(c => c.Pair.UserData).ToList());
} }
private void PlayerManagerOnPlayerHasChanged() private void PlayerManagerOnPlayerHasChanged()

View File

@@ -1,5 +1,4 @@
using Dalamud.Game.Gui.ContextMenu; using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text.SeStringHandling;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Comparer; using MareSynchronos.API.Data.Comparer;
using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Enum;
@@ -24,9 +23,8 @@ public class Pair
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
private readonly MareConfigService _mareConfig; private readonly MareConfigService _mareConfig;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private CancellationTokenSource _applicationCts = new CancellationTokenSource(); private CancellationTokenSource _applicationCts = new();
private OnlineUserIdentDto? _onlineUserIdentDto = null; private OnlineUserIdentDto? _onlineUserIdentDto = null;
private string? _playerName = null;
public Pair(ILogger<Pair> logger, PairHandlerFactory cachedPlayerFactory, public Pair(ILogger<Pair> logger, PairHandlerFactory cachedPlayerFactory,
MareMediator mediator, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager) MareMediator mediator, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager)
@@ -49,8 +47,10 @@ public class Pair
public CharacterData? LastReceivedCharacterData { get; set; } public CharacterData? LastReceivedCharacterData { get; set; }
public string? PlayerName => GetPlayerName(); public string? PlayerName => GetPlayerName();
public uint PlayerCharacterId => GetPlayerCharacterId(); public uint PlayerCharacterId => GetPlayerCharacterId();
public long LastAppliedDataSize => CachedPlayer?.LastAppliedDataSize ?? -1; public long LastAppliedDataBytes => CachedPlayer?.LastAppliedDataBytes ?? -1;
public long LastAppliedDataTris => CachedPlayer?.LastAppliedDataTris ?? -1; public long LastAppliedDataTris { get; set; } = -1;
public long LastAppliedApproximateVRAMBytes { get; set; } = -1;
public string Ident => _onlineUserIdentDto?.Ident ?? string.Empty;
public UserData UserData => UserPair?.User ?? GroupPair.First().Value.User; public UserData UserData => UserPair?.User ?? GroupPair.First().Value.User;
@@ -154,7 +154,7 @@ public class Pair
} }
CachedPlayer?.Dispose(); CachedPlayer?.Dispose();
CachedPlayer = _cachedPlayerFactory.Create(new OnlineUserIdentDto(UserData, _onlineUserIdentDto!.Ident)); CachedPlayer = _cachedPlayerFactory.Create(this);
} }
finally finally
{ {
@@ -207,11 +207,11 @@ public class Pair
{ {
if (wait) if (wait)
_creationSemaphore.Wait(); _creationSemaphore.Wait();
_onlineUserIdentDto = null;
LastReceivedCharacterData = null; LastReceivedCharacterData = null;
var player = CachedPlayer; var player = CachedPlayer;
CachedPlayer = null; CachedPlayer = null;
player?.Dispose(); player?.Dispose();
_onlineUserIdentDto = null;
} }
finally finally
{ {

View File

@@ -1,5 +1,4 @@
using Dalamud.Game.Gui.ContextMenu; using Dalamud.Plugin.Services;
using Dalamud.Plugin.Services;
using MareSynchronos.API.Data; using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Comparer; using MareSynchronos.API.Data.Comparer;
using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Data.Extensions;
@@ -37,7 +36,7 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
_directPairsInternal = DirectPairsLazy(); _directPairsInternal = DirectPairsLazy();
_groupPairsInternal = GroupPairsLazy(); _groupPairsInternal = GroupPairsLazy();
_dalamudContextMenu.OnMenuOpened += DalamudContextMenuOnMenuOpened; _dalamudContextMenu.OnMenuOpened += DalamudContextMenuOnOnOpenGameObjectContextMenu;
} }
public List<Pair> DirectPairs => _directPairsInternal.Value; public List<Pair> DirectPairs => _directPairsInternal.Value;
@@ -230,7 +229,8 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
pair.UserPair.OtherPermissions.IsDisableSounds(), pair.UserPair.OtherPermissions.IsDisableSounds(),
pair.UserPair.OtherPermissions.IsDisableVFX()); pair.UserPair.OtherPermissions.IsDisableVFX());
pair.ApplyLastReceivedData(); if (!pair.IsPaused)
pair.ApplyLastReceivedData();
RecreateLazy(); RecreateLazy();
} }
@@ -258,7 +258,8 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
pair.UserPair.OwnPermissions.IsDisableSounds(), pair.UserPair.OwnPermissions.IsDisableSounds(),
pair.UserPair.OwnPermissions.IsDisableVFX()); pair.UserPair.OwnPermissions.IsDisableVFX());
pair.ApplyLastReceivedData(); if (!pair.IsPaused)
pair.ApplyLastReceivedData();
RecreateLazy(); RecreateLazy();
} }
@@ -332,14 +333,14 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
{ {
base.Dispose(disposing); base.Dispose(disposing);
_dalamudContextMenu.OnMenuOpened -= DalamudContextMenuOnMenuOpened; _dalamudContextMenu.OnMenuOpened -= DalamudContextMenuOnOnOpenGameObjectContextMenu;
DisposePairs(); DisposePairs();
} }
private void DalamudContextMenuOnMenuOpened(IMenuOpenedArgs args) private void DalamudContextMenuOnOnOpenGameObjectContextMenu(Dalamud.Game.Gui.ContextMenu.IMenuOpenedArgs args)
{ {
/* TODO: Check empty target */ if (args.MenuType == Dalamud.Game.Gui.ContextMenu.ContextMenuType.Inventory) return;
if (!_configurationService.Current.EnableRightClickMenus) return; if (!_configurationService.Current.EnableRightClickMenus) return;
foreach (var pair in _allClientPairs.Where((p => p.Value.IsVisible))) foreach (var pair in _allClientPairs.Where((p => p.Value.IsVisible)))

View File

@@ -74,24 +74,28 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
}); });
}); });
Mediator.Subscribe<CustomizePlusMessage>(this, async (msg) => Mediator.Subscribe<CustomizePlusMessage>(this, (msg) =>
{ {
if (_isZoning) return; if (_isZoning) return;
foreach (var item in _playerRelatedObjects _ = Task.Run(async () =>
.Where(item => string.IsNullOrEmpty(msg.ProfileName)
|| string.Equals(item.Value.Name, msg.ProfileName, StringComparison.Ordinal)).Select(k => k.Key))
{ {
Logger.LogDebug("Received CustomizePlus change, updating {obj}", item);
await AddPlayerCacheToCreate(item).ConfigureAwait(false); foreach (var item in _playerRelatedObjects
} .Where(item => msg.Address == null
|| item.Value.Address == msg.Address).Select(k => k.Key))
{
Logger.LogDebug("Received CustomizePlus change, updating {obj}", item);
await AddPlayerCacheToCreate(item).ConfigureAwait(false);
}
});
}); });
Mediator.Subscribe<HeelsOffsetMessage>(this, async (_) => Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) =>
{ {
if (_isZoning) return; if (_isZoning) return;
Logger.LogDebug("Received Heels Offset change, updating player"); Logger.LogDebug("Received Heels Offset change, updating player");
await AddPlayerCacheToCreate().ConfigureAwait(false); _ = AddPlayerCacheToCreate();
}); });
Mediator.Subscribe<GlamourerChangedMessage>(this, async (msg) => Mediator.Subscribe<GlamourerChangedMessage>(this, (msg) =>
{ {
if (_isZoning) return; if (_isZoning) return;
var changedType = _playerRelatedObjects.FirstOrDefault(f => f.Value.Address == msg.Address); var changedType = _playerRelatedObjects.FirstOrDefault(f => f.Value.Address == msg.Address);
@@ -118,10 +122,10 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
PetNicknamesChanged(); PetNicknamesChanged();
} }
}); });
Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, async (msg) => Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (msg) =>
{ {
Logger.LogDebug("Received Penumbra Mod settings change, updating player"); Logger.LogDebug("Received Penumbra Mod settings change, updating player");
await AddPlayerCacheToCreate().ConfigureAwait(false); _ = AddPlayerCacheToCreate();
}); });
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation()); Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());

View File

@@ -35,20 +35,19 @@ public sealed class Plugin : IDalamudPlugin
private readonly IHost _host; private readonly IHost _host;
public static Plugin Self; public static Plugin Self;
public Action<IFramework> _realOnFrameworkUpdate = null; public Action<IFramework>? _realOnFrameworkUpdate { get; set; }
// Proxy function in the LoporritSync namespace to avoid confusion in /xlstats // Proxy function in the LoporritSync namespace to avoid confusion in /xlstats
public void OnFrameworkUpdate(IFramework framework) public void OnFrameworkUpdate(IFramework framework)
{ {
if (_realOnFrameworkUpdate != null) _realOnFrameworkUpdate?.Invoke(framework);
_realOnFrameworkUpdate(framework);
} }
public Plugin(IDalamudPluginInterface pluginInterface, ICommandManager commandManager, IDataManager gameData, public Plugin(IDalamudPluginInterface pluginInterface, ICommandManager commandManager, IDataManager gameData,
IFramework framework, IObjectTable objectTable, IClientState clientState, ICondition condition, IChatGui chatGui, IFramework framework, IObjectTable objectTable, IClientState clientState, ICondition condition, IChatGui chatGui,
IGameGui gameGui, IDtrBar dtrBar, IToastGui toastGui, IPluginLog pluginLog, ITargetManager targetManager, IGameLifecycle addonLifecycle, IGameGui gameGui, IDtrBar dtrBar, IToastGui toastGui, IPluginLog pluginLog, ITargetManager targetManager, INotificationManager notificationManager,
INotificationManager notificationManager, ITextureProvider textureProvider, IContextMenu contextMenu, IGameInteropProvider gameInteropProvider, ITextureProvider textureProvider, IContextMenu contextMenu,
INamePlateGui namePlateGui) IGameInteropProvider gameInteropProvider, INamePlateGui namePlateGui)
{ {
Plugin.Self = this; Plugin.Self = this;
_host = new HostBuilder() _host = new HostBuilder()
@@ -61,7 +60,7 @@ public sealed class Plugin : IDalamudPlugin
}) })
.ConfigureServices(collection => .ConfigureServices(collection =>
{ {
collection.AddSingleton(new WindowSystem("LoporritSync")); collection.AddSingleton(new WindowSystem("MareSynchronos"));
collection.AddSingleton<FileDialogManager>(); collection.AddSingleton<FileDialogManager>();
collection.AddSingleton(new Dalamud.Localization("MareSynchronos.Localization.", "", useEmbedded: true)); collection.AddSingleton(new Dalamud.Localization("MareSynchronos.Localization.", "", useEmbedded: true));
@@ -69,8 +68,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<MareMediator>(); collection.AddSingleton<MareMediator>();
collection.AddSingleton<FileCacheManager>(); collection.AddSingleton<FileCacheManager>();
collection.AddSingleton<ServerConfigurationManager>(); collection.AddSingleton<ServerConfigurationManager>();
collection.AddSingleton<PairManager>((s) => new PairManager(s.GetRequiredService<ILogger<PairManager>>(), s.GetRequiredService<PairFactory>(),
s.GetRequiredService<MareConfigService>(), s.GetRequiredService<MareMediator>(), contextMenu));
collection.AddSingleton<ApiController>(); collection.AddSingleton<ApiController>();
collection.AddSingleton<MareCharaFileManager>(); collection.AddSingleton<MareCharaFileManager>();
collection.AddSingleton<PerformanceCollectorService>(); collection.AddSingleton<PerformanceCollectorService>();
@@ -81,27 +78,25 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<MareProfileManager>(); collection.AddSingleton<MareProfileManager>();
collection.AddSingleton<GameObjectHandlerFactory>(); collection.AddSingleton<GameObjectHandlerFactory>();
collection.AddSingleton<FileDownloadManagerFactory>(); collection.AddSingleton<FileDownloadManagerFactory>();
collection.AddSingleton((s) => new PairHandlerFactory(s.GetRequiredService<ILoggerFactory>(), s.GetRequiredService<GameObjectHandlerFactory>(), collection.AddSingleton<PairHandlerFactory>();
s.GetRequiredService<IpcManager>(), s.GetRequiredService<FileDownloadManagerFactory>(), s.GetRequiredService<DalamudUtilService>(),
s.GetRequiredService<PluginWarningNotificationService>(), s.GetRequiredService<ServerConfigurationManager>(),
CancellationTokenSource.CreateLinkedTokenSource(addonLifecycle.GameShuttingDownToken, addonLifecycle.DalamudUnloadingToken).Token,
s.GetRequiredService<FileCacheManager>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<XivDataAnalyzer>()));
collection.AddSingleton<PairFactory>(); collection.AddSingleton<PairFactory>();
collection.AddSingleton<XivDataAnalyzer>(s => new(s.GetRequiredService<ILogger<XivDataAnalyzer>>(), s.GetRequiredService<FileCacheManager>(), collection.AddSingleton<XivDataAnalyzer>();
s.GetRequiredService<XivDataStorageService>(), gameData));
collection.AddSingleton<CharacterAnalyzer>(); collection.AddSingleton<CharacterAnalyzer>();
collection.AddSingleton<TokenProvider>(); collection.AddSingleton<TokenProvider>();
collection.AddSingleton<PluginWarningNotificationService>(); collection.AddSingleton<PluginWarningNotificationService>();
collection.AddSingleton<FileCompactor>(); collection.AddSingleton<FileCompactor>();
collection.AddSingleton<TagHandler>(); collection.AddSingleton<TagHandler>();
collection.AddSingleton<UidDisplayHandler>(); collection.AddSingleton<UidDisplayHandler>();
collection.AddSingleton<EventAggregator>(); collection.AddSingleton<PlayerPerformanceService>();
collection.AddSingleton((s) => new EventAggregator(s.GetRequiredService<MareConfigService>(),
s.GetRequiredService<ILogger<EventAggregator>>(), s.GetRequiredService<MareMediator>()));
collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(), collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService<ILogger<DalamudUtilService>>(),
clientState, objectTable, framework, gameGui, toastGui, condition, gameData, targetManager, clientState, objectTable, framework, gameGui, toastGui, condition, gameData, targetManager,
s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>())); s.GetRequiredService<MareMediator>(), s.GetRequiredService<PerformanceCollectorService>()));
collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService<ILogger<DtrEntry>>(), dtrBar, s.GetRequiredService<MareConfigService>(), collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService<ILogger<DtrEntry>>(), dtrBar, s.GetRequiredService<MareConfigService>(),
s.GetRequiredService<MareMediator>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<ApiController>())); s.GetRequiredService<MareMediator>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<ApiController>()));
collection.AddSingleton(s => new PairManager(s.GetRequiredService<ILogger<PairManager>>(), s.GetRequiredService<PairFactory>(),
s.GetRequiredService<MareConfigService>(), s.GetRequiredService<MareMediator>(), contextMenu));
collection.AddSingleton<RedrawManager>(); collection.AddSingleton<RedrawManager>();
collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService<ILogger<IpcCallerPenumbra>>(), pluginInterface, collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService<ILogger<IpcCallerPenumbra>>(), pluginInterface,
s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<RedrawManager>())); s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<RedrawManager>()));
@@ -119,7 +114,9 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<MareMediator>(), s.GetRequiredService<IpcCallerPenumbra>(), s.GetRequiredService<IpcCallerGlamourer>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<IpcCallerPenumbra>(), s.GetRequiredService<IpcCallerGlamourer>(),
s.GetRequiredService<IpcCallerCustomize>(), s.GetRequiredService<IpcCallerHeels>(), s.GetRequiredService<IpcCallerHonorific>(), s.GetRequiredService<IpcCallerCustomize>(), s.GetRequiredService<IpcCallerHeels>(), s.GetRequiredService<IpcCallerHonorific>(),
s.GetRequiredService<IpcCallerPetNames>())); s.GetRequiredService<IpcCallerPetNames>()));
collection.AddSingleton((s) => new NotificationService(s.GetRequiredService<ILogger<NotificationService>>(),
s.GetRequiredService<MareMediator>(), s.GetRequiredService<DalamudUtilService>(),
notificationManager, chatGui, s.GetRequiredService<MareConfigService>()));
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new NotesConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new NotesConfigService(pluginInterface.ConfigDirectory.FullName));
@@ -127,7 +124,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton((s) => new SyncshellConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new SyncshellConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new TransientConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new TransientConfigService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new XivDataStorageService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new XivDataStorageService(pluginInterface.ConfigDirectory.FullName));
collection.AddSingleton((s) => new ConfigurationMigrator(s.GetRequiredService<ILogger<ConfigurationMigrator>>(), pluginInterface, s.GetRequiredService<NotesConfigService>())); collection.AddSingleton((s) => new ConfigurationMigrator(s.GetRequiredService<ILogger<ConfigurationMigrator>>(), s.GetRequiredService<NotesConfigService>()));
collection.AddSingleton<HubFactory>(); collection.AddSingleton<HubFactory>();
// add scoped services // add scoped services
@@ -143,8 +140,8 @@ public sealed class Plugin : IDalamudPlugin
collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>(); collection.AddScoped<WindowMediatorSubscriberBase, EventViewerUI>();
collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>((s) => new EditProfileUi(s.GetRequiredService<ILogger<EditProfileUi>>(), collection.AddScoped<WindowMediatorSubscriberBase, EditProfileUi>((s) => new EditProfileUi(s.GetRequiredService<ILogger<EditProfileUi>>(),
s.GetRequiredService<MareMediator>(), s.GetRequiredService<ApiController>(), pluginInterface.UiBuilder, s.GetRequiredService<UiSharedService>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<FileDialogManager>(),
s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<MareProfileManager>(), s.GetRequiredService<PerformanceCollectorService>())); s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<MareProfileManager>(), s.GetRequiredService<PerformanceCollectorService>()));
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>(); collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
collection.AddScoped<IPopupHandler, ReportPopupHandler>(); collection.AddScoped<IPopupHandler, ReportPopupHandler>();
collection.AddScoped<IPopupHandler, BanUserPopupHandler>(); collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
@@ -157,10 +154,8 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<UiFactory>(), s.GetRequiredService<UiFactory>(),
s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareMediator>())); s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareMediator>()));
collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(), collection.AddScoped((s) => new CommandManagerService(commandManager, s.GetRequiredService<PerformanceCollectorService>(),
s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<ChatService>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<ChatService>(), s.GetRequiredService<ApiController>(),
s.GetRequiredService<ApiController>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<MareConfigService>())); s.GetRequiredService<MareMediator>(), s.GetRequiredService<MareConfigService>()));
collection.AddScoped((s) => new NotificationService(s.GetRequiredService<ILogger<NotificationService>>(),
s.GetRequiredService<MareMediator>(), notificationManager, chatGui, s.GetRequiredService<MareConfigService>()));
collection.AddScoped((s) => new UiSharedService(s.GetRequiredService<ILogger<UiSharedService>>(), s.GetRequiredService<IpcManager>(), s.GetRequiredService<ApiController>(), collection.AddScoped((s) => new UiSharedService(s.GetRequiredService<ILogger<UiSharedService>>(), s.GetRequiredService<IpcManager>(), s.GetRequiredService<ApiController>(),
s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareConfigService>(), s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<CacheMonitor>(), s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<MareConfigService>(), s.GetRequiredService<DalamudUtilService>(),
pluginInterface, textureProvider, s.GetRequiredService<Dalamud.Localization>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<MareMediator>())); pluginInterface, textureProvider, s.GetRequiredService<Dalamud.Localization>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<MareMediator>()));
@@ -171,14 +166,15 @@ public sealed class Plugin : IDalamudPlugin
collection.AddScoped((s) => new GuiHookService(s.GetRequiredService<ILogger<GuiHookService>>(), s.GetRequiredService<MareMediator>(), collection.AddScoped((s) => new GuiHookService(s.GetRequiredService<ILogger<GuiHookService>>(), s.GetRequiredService<MareMediator>(),
s.GetRequiredService<MareConfigService>(), namePlateGui, s.GetRequiredService<PairManager>())); s.GetRequiredService<MareConfigService>(), namePlateGui, s.GetRequiredService<PairManager>()));
collection.AddHostedService(p => p.GetRequiredService<FileCacheManager>());
collection.AddHostedService(p => p.GetRequiredService<MareMediator>()); collection.AddHostedService(p => p.GetRequiredService<MareMediator>());
collection.AddHostedService(p => p.GetRequiredService<NotificationService>());
collection.AddHostedService(p => p.GetRequiredService<FileCacheManager>());
collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>()); collection.AddHostedService(p => p.GetRequiredService<ConfigurationMigrator>());
collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>()); collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>());
collection.AddHostedService(p => p.GetRequiredService<PerformanceCollectorService>()); collection.AddHostedService(p => p.GetRequiredService<PerformanceCollectorService>());
collection.AddHostedService(p => p.GetRequiredService<DtrEntry>()); collection.AddHostedService(p => p.GetRequiredService<DtrEntry>());
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>()); collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
}) })
.Build(); .Build();
@@ -190,4 +186,4 @@ public sealed class Plugin : IDalamudPlugin
_host.StopAsync().GetAwaiter().GetResult(); _host.StopAsync().GetAwaiter().GetResult();
_host.Dispose(); _host.Dispose();
} }
} }

View File

@@ -193,16 +193,16 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0; public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token) public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token)
{ {
var compressedsize = await fileCacheManager.GetCompressedFileLength(Hash, token).ConfigureAwait(false); var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false);
var normalSize = new FileInfo(FilePaths[0]).Length; var normalSize = new FileInfo(FilePaths[0]).Length;
var entries = fileCacheManager.GetAllFileCachesByHash(Hash, ignoreCacheEntries: true, validate: false); var entries = fileCacheManager.GetAllFileCachesByHash(Hash, ignoreCacheEntries: true, validate: false);
foreach (var entry in entries) foreach (var entry in entries)
{ {
entry.Size = normalSize; entry.Size = normalSize;
entry.CompressedSize = compressedsize; entry.CompressedSize = compressedsize.Item2.LongLength;
} }
OriginalSize = normalSize; OriginalSize = normalSize;
CompressedSize = compressedsize; CompressedSize = compressedsize.Item2.LongLength;
} }
public long OriginalSize { get; private set; } = OriginalSize; public long OriginalSize { get; private set; } = OriginalSize;
public long CompressedSize { get; private set; } = CompressedSize; public long CompressedSize { get; private set; } = CompressedSize;

View File

@@ -36,6 +36,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
private readonly List<uint> _classJobIdsIgnoredForPets = [30]; private readonly List<uint> _classJobIdsIgnoredForPets = [30];
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly ICondition _condition; private readonly ICondition _condition;
private readonly IDataManager _gameData;
private readonly IFramework _framework; private readonly IFramework _framework;
private readonly IGameGui _gameGui; private readonly IGameGui _gameGui;
private readonly IToastGui _toastGui; private readonly IToastGui _toastGui;
@@ -63,6 +64,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
_gameGui = gameGui; _gameGui = gameGui;
_toastGui = toastGui; _toastGui = toastGui;
_condition = condition; _condition = condition;
_gameData = gameData;
Mediator = mediator; Mediator = mediator;
_performanceCollector = performanceCollector; _performanceCollector = performanceCollector;
WorldData = new(() => WorldData = new(() =>
@@ -98,27 +100,28 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public bool IsWine { get; init; } public bool IsWine { get; init; }
public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget; public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget;
public unsafe DalamudGameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex]; public unsafe Dalamud.Game.ClientState.Objects.Types.IGameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex];
public bool IsAnythingDrawing { get; private set; } = false; public bool IsAnythingDrawing { get; private set; } = false;
public bool IsInCutscene { get; private set; } = false; public bool IsInCutscene { get; private set; } = false;
public bool IsInGpose { get; private set; } = false; public bool IsInGpose { get; private set; } = false;
public bool IsLoggedIn { get; private set; } public bool IsLoggedIn { get; private set; }
public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread; public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread;
public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]; public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
public bool IsInCombat { get; private set; } = false; public bool IsInCombatOrPerforming { get; private set; } = false;
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; } public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
public Lazy<Dictionary<int, Lumina.Excel.Sheets.UIColor>> UiColors { get; private set; } public Lazy<Dictionary<int, Lumina.Excel.Sheets.UIColor>> UiColors { get; private set; }
public MareMediator Mediator { get; } public MareMediator Mediator { get; }
public DalamudGameObject? CreateGameObject(IntPtr reference) public Dalamud.Game.ClientState.Objects.Types.IGameObject? CreateGameObject(IntPtr reference)
{ {
EnsureIsOnFramework(); EnsureIsOnFramework();
return _objectTable.CreateObjectReference(reference); return _objectTable.CreateObjectReference(reference);
} }
public async Task<DalamudGameObject?> CreateGameObjectAsync(IntPtr reference) public async Task<Dalamud.Game.ClientState.Objects.Types.IGameObject?> CreateGameObjectAsync(IntPtr reference)
{ {
return await RunOnFrameworkThread(() => _objectTable.CreateObjectReference(reference)).ConfigureAwait(false); return await RunOnFrameworkThread(() => _objectTable.CreateObjectReference(reference)).ConfigureAwait(false);
} }
@@ -532,19 +535,19 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new GposeEndMessage()); Mediator.Publish(new GposeEndMessage());
} }
if (_condition[ConditionFlag.InCombat] && !IsInCombat) if ((_condition[ConditionFlag.Performing] || _condition[ConditionFlag.InCombat]) && !IsInCombatOrPerforming)
{ {
_logger.LogDebug("Combat start"); _logger.LogDebug("Combat/Performance start");
IsInCombat = true; IsInCombatOrPerforming = true;
Mediator.Publish(new CombatStartMessage()); Mediator.Publish(new CombatOrPerformanceStartMessage());
Mediator.Publish(new HaltScanMessage(nameof(IsInCombat))); Mediator.Publish(new HaltScanMessage(nameof(IsInCombatOrPerforming)));
} }
else if (!_condition[ConditionFlag.InCombat] && IsInCombat) else if ((!_condition[ConditionFlag.Performing] && !_condition[ConditionFlag.InCombat]) && IsInCombatOrPerforming)
{ {
_logger.LogDebug("Combat end"); _logger.LogDebug("Combat/Performance end");
IsInCombat = false; IsInCombatOrPerforming = false;
Mediator.Publish(new CombatEndMessage()); Mediator.Publish(new CombatOrPerformanceEndMessage());
Mediator.Publish(new ResumeScanMessage(nameof(IsInCombat))); Mediator.Publish(new ResumeScanMessage(nameof(IsInCombatOrPerforming)));
} }
if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene)
@@ -595,7 +598,13 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas)));
} }
if (!IsInCombat) var localPlayer = _clientState.LocalPlayer;
if (localPlayer != null)
{
_classJobId = localPlayer.ClassJob.RowId;
}
if (!IsInCombatOrPerforming)
Mediator.Publish(new FrameworkUpdateMessage()); Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new PriorityFrameworkUpdateMessage()); Mediator.Publish(new PriorityFrameworkUpdateMessage());
@@ -603,8 +612,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
if (isNormalFrameworkUpdate) if (isNormalFrameworkUpdate)
return; return;
var localPlayer = _clientState.LocalPlayer;
if (localPlayer != null && !IsLoggedIn) if (localPlayer != null && !IsLoggedIn)
{ {
_logger.LogDebug("Logged in"); _logger.LogDebug("Logged in");
@@ -619,7 +626,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new DalamudLogoutMessage()); Mediator.Publish(new DalamudLogoutMessage());
} }
if (IsInCombat) if (IsInCombatOrPerforming)
Mediator.Publish(new FrameworkUpdateMessage()); Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new DelayedFrameworkUpdateMessage()); Mediator.Publish(new DelayedFrameworkUpdateMessage());

View File

@@ -1,4 +1,4 @@
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -21,17 +21,12 @@ public class EventAggregator : MediatorSubscriberBase, IHostedService
public EventAggregator(MareConfigService configService, ILogger<EventAggregator> logger, MareMediator mareMediator) : base(logger, mareMediator) public EventAggregator(MareConfigService configService, ILogger<EventAggregator> logger, MareMediator mareMediator) : base(logger, mareMediator)
{ {
Logger.LogInformation("Starting EventAggregatorService");
Logger.LogInformation("Started EventAggregatorService");
_configDirectory = configService.ConfigurationDirectory;
_logger = logger;
_currentTime = DateTime.UnixEpoch;
Mediator.Subscribe<EventMessage>(this, (msg) => Mediator.Subscribe<EventMessage>(this, (msg) =>
{ {
_lock.Wait(); _lock.Wait();
try try
{ {
Logger.LogTrace("Received Event: {evt}", msg.Event.ToString());
_events.Add(msg.Event); _events.Add(msg.Event);
if (configService.Current.LogEvents) if (configService.Current.LogEvents)
WriteToFile(msg.Event); WriteToFile(msg.Event);
@@ -45,6 +40,9 @@ public class EventAggregator : MediatorSubscriberBase, IHostedService
}); });
EventList = CreateEventLazy(); EventList = CreateEventLazy();
_configDirectory = configService.ConfigurationDirectory;
_logger = logger;
_currentTime = DateTime.Now - TimeSpan.FromDays(1);
} }
private void RecreateLazy() private void RecreateLazy()
@@ -103,6 +101,8 @@ public class EventAggregator : MediatorSubscriberBase, IHostedService
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
Logger.LogInformation("Starting EventAggregatorService");
Logger.LogInformation("Started EventAggregatorService");
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -38,7 +38,7 @@ public record PenumbraRedrawMessage(IntPtr Address, int ObjTblIdx, bool WasReque
public record GlamourerChangedMessage(IntPtr Address) : MessageBase; public record GlamourerChangedMessage(IntPtr Address) : MessageBase;
public record HeelsOffsetMessage : MessageBase; public record HeelsOffsetMessage : MessageBase;
public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : SameThreadMessage; public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : SameThreadMessage;
public record CustomizePlusMessage(string ProfileName) : MessageBase; public record CustomizePlusMessage(nint? Address) : MessageBase;
public record HonorificMessage(string NewHonorificTitle) : MessageBase; public record HonorificMessage(string NewHonorificTitle) : MessageBase;
public record PetNamesReadyMessage : MessageBase; public record PetNamesReadyMessage : MessageBase;
public record PetNamesMessage(string PetNicknamesData) : MessageBase; public record PetNamesMessage(string PetNicknamesData) : MessageBase;
@@ -68,6 +68,7 @@ public record UiToggleMessage(Type UiType) : MessageBase;
public record PlayerUploadingMessage(GameObjectHandler Handler, bool IsUploading) : MessageBase; public record PlayerUploadingMessage(GameObjectHandler Handler, bool IsUploading) : MessageBase;
public record ClearProfileDataMessage(UserData? UserData = null) : MessageBase; public record ClearProfileDataMessage(UserData? UserData = null) : MessageBase;
public record CyclePauseMessage(UserData UserData) : MessageBase; public record CyclePauseMessage(UserData UserData) : MessageBase;
public record PauseMessage(UserData UserData) : MessageBase;
public record ProfilePopoutToggle(Pair? Pair) : MessageBase; public record ProfilePopoutToggle(Pair? Pair) : MessageBase;
public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase; public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase;
public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase; public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase;
@@ -80,8 +81,8 @@ public record OpenPermissionWindow(Pair Pair) : MessageBase;
public record DownloadLimitChangedMessage() : SameThreadMessage; public record DownloadLimitChangedMessage() : SameThreadMessage;
public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase; public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase;
public record TargetPairMessage(Pair Pair) : MessageBase; public record TargetPairMessage(Pair Pair) : MessageBase;
public record CombatStartMessage : MessageBase; public record CombatOrPerformanceStartMessage : MessageBase;
public record CombatEndMessage : MessageBase; public record CombatOrPerformanceEndMessage : MessageBase;
public record EventMessage(Event Event) : MessageBase; public record EventMessage(Event Event) : MessageBase;
public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase;
public record PenumbraRedrawCharacterMessage(ICharacter Character) : SameThreadMessage; public record PenumbraRedrawCharacterMessage(ICharacter Character) : SameThreadMessage;

View File

@@ -4,24 +4,39 @@ using Dalamud.Plugin.Services;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType; using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
namespace MareSynchronos.Services; namespace MareSynchronos.Services;
public class NotificationService : DisposableMediatorSubscriberBase public class NotificationService : DisposableMediatorSubscriberBase, IHostedService
{ {
private readonly DalamudUtilService _dalamudUtilService;
private readonly INotificationManager _notificationManager; private readonly INotificationManager _notificationManager;
private readonly IChatGui _chatGui; private readonly IChatGui _chatGui;
private readonly MareConfigService _configurationService; private readonly MareConfigService _configurationService;
public NotificationService(ILogger<NotificationService> logger, MareMediator mediator, INotificationManager notificationManager, IChatGui chatGui, MareConfigService configurationService) : base(logger, mediator) public NotificationService(ILogger<NotificationService> logger, MareMediator mediator,
DalamudUtilService dalamudUtilService,
INotificationManager notificationManager,
IChatGui chatGui, MareConfigService configurationService) : base(logger, mediator)
{ {
_dalamudUtilService = dalamudUtilService;
_notificationManager = notificationManager; _notificationManager = notificationManager;
_chatGui = chatGui; _chatGui = chatGui;
_configurationService = configurationService; _configurationService = configurationService;
}
public Task StartAsync(CancellationToken cancellationToken)
{
Mediator.Subscribe<NotificationMessage>(this, ShowNotification); Mediator.Subscribe<NotificationMessage>(this, ShowNotification);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
} }
private void PrintErrorChat(string? message) private void PrintErrorChat(string? message)
@@ -64,6 +79,8 @@ public class NotificationService : DisposableMediatorSubscriberBase
{ {
Logger.LogInformation("{msg}", msg.ToString()); Logger.LogInformation("{msg}", msg.ToString());
if (!_dalamudUtilService.IsLoggedIn) return;
switch (msg.Type) switch (msg.Type)
{ {
case NotificationType.Info: case NotificationType.Info:

View File

@@ -0,0 +1,115 @@
using MareSynchronos.API.Data;
using MareSynchronos.FileCache;
using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services.Mediator;
using MareSynchronos.WebAPI.Files.Models;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Services;
public class PlayerPerformanceService
{
private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _xivDataAnalyzer;
private readonly ILogger<PlayerPerformanceService> _logger;
private readonly MareMediator _mediator;
public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, MareMediator mediator,
FileCacheManager fileCacheManager,
XivDataAnalyzer xivDataAnalyzer)
{
_logger = logger;
_mediator = mediator;
_fileCacheManager = fileCacheManager;
_xivDataAnalyzer = xivDataAnalyzer;
}
public async Task<bool> CheckBothThresholds(PairHandler pairHandler, CharacterData charaData)
{
bool notPausedAfterVram = ComputeAndAutoPauseOnVRAMUsageThresholds(pairHandler, charaData, []);
if (!notPausedAfterVram) return false;
bool notPausedAfterTris = await CheckTriangleUsageThresholds(pairHandler, charaData).ConfigureAwait(false);
if (!notPausedAfterTris) return false;
return true;
}
public async Task<bool> CheckTriangleUsageThresholds(PairHandler pairHandler, CharacterData charaData)
{
var pair = pairHandler.Pair;
long triUsage = 0;
if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? playerReplacements))
{
pair.LastAppliedDataTris = 0;
return true;
}
var moddedModelHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith("mdl", StringComparison.OrdinalIgnoreCase)))
.Select(p => p.Hash)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
foreach (var hash in moddedModelHashes)
{
triUsage += await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false);
}
pair.LastAppliedDataTris = triUsage;
_logger.LogDebug("Calculated VRAM usage for {p}", pairHandler);
return true;
}
public bool ComputeAndAutoPauseOnVRAMUsageThresholds(PairHandler pairHandler, CharacterData charaData, List<DownloadFileTransfer> toDownloadFiles)
{
var pair = pairHandler.Pair;
long vramUsage = 0;
if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? playerReplacements))
{
pair.LastAppliedApproximateVRAMBytes = 0;
return true;
}
var moddedTextureHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)))
.Select(p => p.Hash)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
foreach (var hash in moddedTextureHashes)
{
long fileSize = 0;
var download = toDownloadFiles.Find(f => string.Equals(hash, f.Hash, StringComparison.OrdinalIgnoreCase));
if (download != null)
{
fileSize = download.TotalRaw;
}
else
{
var fileEntry = _fileCacheManager.GetFileCacheByHash(hash);
if (fileEntry == null) continue;
if (fileEntry.Size == null)
{
fileEntry.Size = new FileInfo(fileEntry.ResolvedFilepath).Length;
_fileCacheManager.UpdateHashedFile(fileEntry, computeProperties: true);
}
fileSize = fileEntry.Size.Value;
}
vramUsage += fileSize;
}
pair.LastAppliedApproximateVRAMBytes = vramUsage;
_logger.LogDebug("Calculated VRAM usage for {p}", pairHandler);
return true;
}
}

View File

@@ -147,15 +147,19 @@ public class ServerConfigurationManager
Save(); Save();
} }
internal void AddCurrentCharacterToServer(int serverSelectionIndex = -1, bool addLastSecretKey = false) internal void AddCurrentCharacterToServer(int serverSelectionIndex = -1)
{ {
if (serverSelectionIndex == -1) serverSelectionIndex = CurrentServerIndex; if (serverSelectionIndex == -1) serverSelectionIndex = CurrentServerIndex;
var server = GetServerByIndex(serverSelectionIndex); var server = GetServerByIndex(serverSelectionIndex);
if (server.Authentications.Any(c => string.Equals(c.CharacterName, _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(), StringComparison.Ordinal)
&& c.WorldId == _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult()))
return;
server.Authentications.Add(new Authentication() server.Authentications.Add(new Authentication()
{ {
CharacterName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(), CharacterName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(),
WorldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(), WorldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(),
SecretKeyIdx = addLastSecretKey ? server.SecretKeys.Last().Key : -1, SecretKeyIdx = server.SecretKeys.Last().Key,
}); });
Save(); Save();
} }
@@ -163,7 +167,10 @@ public class ServerConfigurationManager
internal void AddEmptyCharacterToServer(int serverSelectionIndex) internal void AddEmptyCharacterToServer(int serverSelectionIndex)
{ {
var server = GetServerByIndex(serverSelectionIndex); var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Add(new Authentication()); server.Authentications.Add(new Authentication()
{
SecretKeyIdx = server.SecretKeys.Any() ? server.SecretKeys.First().Key : -1,
});
Save(); Save();
} }

View File

@@ -1,12 +1,10 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.Havok.Animation; using FFXIVClientStructs.Havok.Animation;
using FFXIVClientStructs.Havok.Common.Base.Types; using FFXIVClientStructs.Havok.Common.Base.Types;
using FFXIVClientStructs.Havok.Common.Serialize.Util; using FFXIVClientStructs.Havok.Common.Serialize.Util;
using Lumina;
using Lumina.Data.Files;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
using MareSynchronos.Interop.GameModel;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -19,15 +17,14 @@ public sealed class XivDataAnalyzer
private readonly ILogger<XivDataAnalyzer> _logger; private readonly ILogger<XivDataAnalyzer> _logger;
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
private readonly XivDataStorageService _configService; private readonly XivDataStorageService _configService;
private readonly GameData _luminaGameData; private readonly List<string> _failedCalculatedTris = [];
public XivDataAnalyzer(ILogger<XivDataAnalyzer> logger, FileCacheManager fileCacheManager, public XivDataAnalyzer(ILogger<XivDataAnalyzer> logger, FileCacheManager fileCacheManager,
XivDataStorageService configService, IDataManager gameData) XivDataStorageService configService)
{ {
_logger = logger; _logger = logger;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_configService = configService; _configService = configService;
_luminaGameData = new GameData(gameData.GameData.DataPath.FullName);
} }
public unsafe Dictionary<string, List<ushort>>? GetSkeletonBoneIndices(GameObjectHandler handler) public unsafe Dictionary<string, List<ushort>>? GetSkeletonBoneIndices(GameObjectHandler handler)
@@ -153,50 +150,65 @@ public sealed class XivDataAnalyzer
return output; return output;
} }
public Task<long> GetTrianglesFromGamePath(string gamePath) public async Task<long> GetTrianglesByHash(string hash)
{ {
if (_configService.Current.TriangleDictionary.TryGetValue(gamePath, out var cachedTris)) if (_configService.Current.TriangleDictionary.TryGetValue(hash, out var cachedTris) && cachedTris > 0)
return Task.FromResult(cachedTris); return cachedTris;
_logger.LogDebug("Detected Model File {path}, calculating Tris", gamePath); if (_failedCalculatedTris.Contains(hash, StringComparer.Ordinal))
var file = _luminaGameData.GetFile<MdlFile>(gamePath); return 0;
if (file == null)
return Task.FromResult((long)0);
if (file.FileHeader.LodCount <= 0)
return Task.FromResult((long)0);
var meshIdx = file.Lods[0].MeshIndex;
var meshCnt = file.Lods[0].MeshCount;
var tris = file.Meshes.Skip(meshIdx).Take(meshCnt).Sum(p => p.IndexCount) / 3;
_logger.LogDebug("{filePath} => {tris} triangles", gamePath, tris);
_configService.Current.TriangleDictionary[gamePath] = tris;
_configService.Save();
return Task.FromResult(tris);
}
public Task<long> GetTrianglesByHash(string hash)
{
if (_configService.Current.TriangleDictionary.TryGetValue(hash, out var cachedTris))
return Task.FromResult(cachedTris);
var path = _fileCacheManager.GetFileCacheByHash(hash); var path = _fileCacheManager.GetFileCacheByHash(hash);
if (path == null || !path.ResolvedFilepath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)) if (path == null || !path.ResolvedFilepath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase))
return Task.FromResult((long)0); return 0;
var filePath = path.ResolvedFilepath; var filePath = path.ResolvedFilepath;
_logger.LogDebug("Detected Model File {path}, calculating Tris", filePath); try
var file = _luminaGameData.GetFileFromDisk<MdlFile>(filePath); {
if (file.FileHeader.LodCount <= 0) _logger.LogDebug("Detected Model File {path}, calculating Tris", filePath);
return Task.FromResult((long)0); var file = new MdlFile(filePath);
var meshIdx = file.Lods[0].MeshIndex; if (file.LodCount <= 0)
var meshCnt = file.Lods[0].MeshCount; {
var tris = file.Meshes.Skip(meshIdx).Take(meshCnt).Sum(p => p.IndexCount) / 3; _failedCalculatedTris.Add(hash);
_configService.Current.TriangleDictionary[hash] = 0;
_configService.Save();
return 0;
}
_logger.LogDebug("{filePath} => {tris} triangles", filePath, tris); long tris = 0;
_configService.Current.TriangleDictionary[hash] = tris; for (int i = 0; i < file.LodCount; i++)
_configService.Save(); {
return Task.FromResult(tris); try
{
var meshIdx = file.Lods[i].MeshIndex;
var meshCnt = file.Lods[i].MeshCount;
tris = file.Meshes.Skip(meshIdx).Take(meshCnt).Sum(p => p.IndexCount) / 3;
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Could not load lod mesh {mesh} from path {path}", i, filePath);
continue;
}
if (tris > 0)
{
_logger.LogDebug("TriAnalysis: {filePath} => {tris} triangles", filePath, tris);
_configService.Current.TriangleDictionary[hash] = tris;
_configService.Save();
break;
}
}
return tris;
}
catch (Exception e)
{
_failedCalculatedTris.Add(hash);
_configService.Current.TriangleDictionary[hash] = 0;
_configService.Save();
_logger.LogWarning(e, "Could not parse file {file}", filePath);
return 0;
}
} }
} }

View File

@@ -74,11 +74,15 @@ public class DrawGroupPair : DrawPairBase
{ {
_mediator.Publish(new TargetPairMessage(_pair)); _mediator.Publish(new TargetPairMessage(_pair));
} }
if (_pair.LastAppliedDataSize >= 0) if (_pair.LastAppliedDataBytes >= 0)
{ {
presenceText += UiSharedService.TooltipSeparator; presenceText += UiSharedService.TooltipSeparator;
presenceText += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine; presenceText += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
presenceText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true); presenceText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true);
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
{
presenceText += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true);
}
if (_pair.LastAppliedDataTris >= 0) if (_pair.LastAppliedDataTris >= 0)
{ {
presenceText += Environment.NewLine + "Triangle Count (excl. Vanilla): " presenceText += Environment.NewLine + "Triangle Count (excl. Vanilla): "

View File

@@ -72,16 +72,20 @@ public class DrawUserPair : DrawPairBase
} }
ImGui.PopFont(); ImGui.PopFont();
var visibleTooltip = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName! + Environment.NewLine + "Click to target this player"; var visibleTooltip = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName! + Environment.NewLine + "Click to target this player";
if (_pair.LastAppliedDataSize >= 0) if (_pair.LastAppliedDataBytes >= 0)
{ {
visibleTooltip += UiSharedService.TooltipSeparator; visibleTooltip += UiSharedService.TooltipSeparator;
visibleTooltip += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine; visibleTooltip += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
visibleTooltip += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true); visibleTooltip += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true);
if (_pair.LastAppliedApproximateVRAMBytes >= 0)
{
visibleTooltip += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true);
}
if (_pair.LastAppliedDataTris >= 0) if (_pair.LastAppliedDataTris >= 0)
{ {
visibleTooltip += Environment.NewLine + "Triangle Count (excl. Vanilla): " visibleTooltip += Environment.NewLine + "Triangle Count (excl. Vanilla): "
+ (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris); + (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris);
} }
} }
UiSharedService.AttachToolTip(visibleTooltip); UiSharedService.AttachToolTip(visibleTooltip);
@@ -273,16 +277,5 @@ public class DrawUserPair : DrawPairBase
_ = _apiController.UserRemovePair(new(entry.UserData)); _ = _apiController.UserRemovePair(new(entry.UserData));
} }
UiSharedService.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID); UiSharedService.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID);
ImGui.Separator();
if (!entry.IsPaused)
{
if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.ExclamationTriangle, "Report Profile"))
{
ImGui.CloseCurrentPopup();
_mediator.Publish(new OpenReportPopupMessage(_pair));
}
UiSharedService.AttachToolTip("Report this users profile to the administrative team");
}
} }
} }

View File

@@ -31,7 +31,9 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
private ObjectKind _selectedObjectTab; private ObjectKind _selectedObjectTab;
private bool _showModal = false; private bool _showModal = false;
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer, IpcManager ipcManager, PerformanceCollectorService performanceCollectorService) public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator,
CharacterAnalyzer characterAnalyzer, IpcManager ipcManager,
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Character Data Analysis", performanceCollectorService) : base(logger, mediator, "Character Data Analysis", performanceCollectorService)
{ {
_characterAnalyzer = characterAnalyzer; _characterAnalyzer = characterAnalyzer;
@@ -152,10 +154,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize)))); + ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
ImGui.SetTooltip(text); ImGui.SetTooltip(text);
} }
ImGui.TextUnformatted("Total size (uncompressed):"); ImGui.TextUnformatted("Total size (actual):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize)))); ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
ImGui.TextUnformatted("Total size (compressed):"); ImGui.TextUnformatted("Total size (compressed for up/download only):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize)))); ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize))));
ImGui.TextUnformatted($"Total modded model triangles: {_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles))}"); ImGui.TextUnformatted($"Total modded model triangles: {_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles))}");
@@ -170,7 +172,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString()); using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
if (tab.Success) if (tab.Success)
{ {
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal).OrderBy(k => k.Key, StringComparer.Ordinal).ToList(); var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
ImGui.TextUnformatted("Files for " + kvp.Key); ImGui.TextUnformatted("Files for " + kvp.Key);
ImGui.SameLine(); ImGui.SameLine();
@@ -189,12 +192,19 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize)))); + ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
ImGui.SetTooltip(text); ImGui.SetTooltip(text);
} }
ImGui.TextUnformatted($"{kvp.Key} size (uncompressed):"); ImGui.TextUnformatted($"{kvp.Key} size (actual):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
ImGui.TextUnformatted($"{kvp.Key} size (compressed):"); ImGui.TextUnformatted($"{kvp.Key} size (compressed for up/download only):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
ImGui.SameLine();
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
if (vramUsage != null)
{
ImGui.TextUnformatted(UiSharedService.ByteToString(vramUsage.Sum(f => f.OriginalSize)));
}
ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {kvp.Value.Sum(f => f.Value.Triangles)}"); ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {kvp.Value.Sum(f => f.Value.Triangles)}");
ImGui.Separator(); ImGui.Separator();
@@ -239,11 +249,11 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(fileGroup.Count().ToString()); ImGui.TextUnformatted(fileGroup.Count().ToString());
ImGui.TextUnformatted($"{fileGroup.Key} files size (uncompressed):"); ImGui.TextUnformatted($"{fileGroup.Key} files size (actual):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
ImGui.TextUnformatted($"{fileGroup.Key} files size (compressed):"); ImGui.TextUnformatted($"{fileGroup.Key} files size (compressed for up/download only):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
@@ -383,10 +393,14 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Triangles).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Triangles).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending) if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Format).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending) if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Format).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
sortSpecs.SpecsDirty = false; sortSpecs.SpecsDirty = false;
} }

View File

@@ -21,7 +21,6 @@ public class EditProfileUi : WindowMediatorSubscriberBase
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly FileDialogManager _fileDialogManager; private readonly FileDialogManager _fileDialogManager;
private readonly MareProfileManager _mareProfileManager; private readonly MareProfileManager _mareProfileManager;
private readonly IUiBuilder _uiBuilder;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private bool _adjustedForScollBarsLocalProfile = false; private bool _adjustedForScollBarsLocalProfile = false;
@@ -34,8 +33,8 @@ public class EditProfileUi : WindowMediatorSubscriberBase
private bool _wasOpen; private bool _wasOpen;
public EditProfileUi(ILogger<EditProfileUi> logger, MareMediator mediator, public EditProfileUi(ILogger<EditProfileUi> logger, MareMediator mediator,
ApiController apiController, IUiBuilder uiBuilder, UiSharedService uiSharedService, ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
FileDialogManager fileDialogManager, ServerConfigurationManager serverConfigurationManager, ServerConfigurationManager serverConfigurationManager,
MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService) MareProfileManager mareProfileManager, PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Loporrit Edit Profile###LoporritSyncEditProfileUI", performanceCollectorService) : base(logger, mediator, "Loporrit Edit Profile###LoporritSyncEditProfileUI", performanceCollectorService)
{ {
@@ -46,7 +45,6 @@ public class EditProfileUi : WindowMediatorSubscriberBase
MaximumSize = new(768, 2000) MaximumSize = new(768, 2000)
}; };
_apiController = apiController; _apiController = apiController;
_uiBuilder = uiBuilder;
_uiSharedService = uiSharedService; _uiSharedService = uiSharedService;
_fileDialogManager = fileDialogManager; _fileDialogManager = fileDialogManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;

View File

@@ -18,10 +18,11 @@ using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions;
namespace MareSynchronos.UI; namespace MareSynchronos.UI;
public class IntroUi : WindowMediatorSubscriberBase public partial class IntroUi : WindowMediatorSubscriberBase
{ {
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly MareConfigService _configService; private readonly MareConfigService _configService;
@@ -42,13 +43,12 @@ public class IntroUi : WindowMediatorSubscriberBase
private string? _registrationMessage; private string? _registrationMessage;
private RegisterReplyDto? _registrationReply; private RegisterReplyDto? _registrationReply;
public IntroUi(ILogger<IntroUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, public IntroUi(ILogger<IntroUi> logger, UiSharedService uiShared, MareConfigService configService,
CacheMonitor fileCacheManager, ServerConfigurationManager serverConfigurationManager, MareMediator mareMediator, CacheMonitor fileCacheManager, ServerConfigurationManager serverConfigurationManager, MareMediator mareMediator,
PerformanceCollectorService performanceCollectorService, DalamudUtilService dalamudUtilService) : base(logger, mareMediator, "Loporrit Setup", performanceCollectorService) PerformanceCollectorService performanceCollectorService, DalamudUtilService dalamudUtilService) : base(logger, mareMediator, "Loporrit Setup", performanceCollectorService)
{ {
_uiShared = uiShared; _uiShared = uiShared;
_configService = configService; _configService = configService;
_apiController = apiController;
_cacheMonitor = fileCacheManager; _cacheMonitor = fileCacheManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
@@ -87,11 +87,12 @@ public class IntroUi : WindowMediatorSubscriberBase
UiSharedService.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " + UiSharedService.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " +
"might look broken because of this or others players mods might not apply on your end altogether. " + "might look broken because of this or others players mods might not apply on your end altogether. " +
"If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow); "If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow);
if (!_uiShared.DrawOtherPluginState(true)) return; if (!_uiShared.DrawOtherPluginState(intro: true)) return;
ImGui.Separator(); ImGui.Separator();
if (ImGui.Button("Next##toAgreement")) if (ImGui.Button("Next##toAgreement"))
{ {
_readFirstPage = true; _readFirstPage = true;
#if !DEBUG
_timeoutTask = Task.Run(async () => _timeoutTask = Task.Run(async () =>
{ {
for (int i = 10; i > 0; i--) for (int i = 10; i > 0; i--)
@@ -100,6 +101,9 @@ public class IntroUi : WindowMediatorSubscriberBase
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
} }
}); });
#else
_timeoutTask = Task.CompletedTask;
#endif
} }
} }
else if (!_configService.Current.AcceptedAgreement && _readFirstPage) else if (!_configService.Current.AcceptedAgreement && _readFirstPage)
@@ -229,7 +233,11 @@ public class IntroUi : WindowMediatorSubscriberBase
ImGui.InputText("", ref _secretKey, 64); ImGui.InputText("", ref _secretKey, 64);
if (_secretKey.Length > 0 && _secretKey.Length != 64) if (_secretKey.Length > 0 && _secretKey.Length != 64)
{ {
UiSharedService.ColorTextWrapped("Your secret key must be exactly 64 characters long. Don't enter your Lodestone auth here.", ImGuiColors.DalamudRed); UiSharedService.ColorTextWrapped("Your secret key must be exactly 64 characters long.", ImGuiColors.DalamudRed);
}
else if (_secretKey.Length == 64 && !HexRegex().IsMatch(_secretKey))
{
UiSharedService.ColorTextWrapped("Your secret key can only contain ABCDEF and the numbers 0-9.", ImGuiColors.DalamudRed);
} }
else if (_secretKey.Length == 64) else if (_secretKey.Length == 64)
{ {
@@ -238,7 +246,7 @@ public class IntroUi : WindowMediatorSubscriberBase
{ {
string keyName; string keyName;
if (_serverConfigurationManager.CurrentServer == null) _serverConfigurationManager.SelectServer(0); if (_serverConfigurationManager.CurrentServer == null) _serverConfigurationManager.SelectServer(0);
if (_registrationReply != null && _secretKey == _registrationReply.SecretKey) if (_registrationReply != null && _secretKey.Equals(_registrationReply.SecretKey, StringComparison.Ordinal))
keyName = _registrationReply.UID + $" (registered {DateTime.Now:yyyy-MM-dd})"; keyName = _registrationReply.UID + $" (registered {DateTime.Now:yyyy-MM-dd})";
else else
keyName = $"Secret Key added on Setup ({DateTime.Now:yyyy-MM-dd})"; keyName = $"Secret Key added on Setup ({DateTime.Now:yyyy-MM-dd})";
@@ -247,13 +255,13 @@ public class IntroUi : WindowMediatorSubscriberBase
FriendlyName = keyName, FriendlyName = keyName,
Key = _secretKey, Key = _secretKey,
}); });
_serverConfigurationManager.AddCurrentCharacterToServer(addLastSecretKey: true); _serverConfigurationManager.AddCurrentCharacterToServer();
_secretKey = string.Empty; _secretKey = string.Empty;
_ = Task.Run(() => _uiShared.ApiController.CreateConnections()); _ = Task.Run(() => _uiShared.ApiController.CreateConnections());
} }
} }
if (_serverConfigurationManager.CurrentApiUrl == ApiController.LoporritServiceUri) if (_serverConfigurationManager.CurrentApiUrl.Equals(ApiController.LoporritServiceUri, StringComparison.Ordinal))
{ {
ImGui.BeginDisabled(_registrationInProgress || _registrationSuccess || _secretKey.Length > 0); ImGui.BeginDisabled(_registrationInProgress || _registrationSuccess || _secretKey.Length > 0);
ImGui.Separator(); ImGui.Separator();
@@ -270,13 +278,13 @@ public class IntroUi : WindowMediatorSubscriberBase
var postUri = MareAuth.AuthRegisterFullPath(new Uri(_serverConfigurationManager.CurrentApiUrl var postUri = MareAuth.AuthRegisterFullPath(new Uri(_serverConfigurationManager.CurrentApiUrl
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase) .Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase))); .Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
_logger.LogInformation("Registering new account: " + postUri.ToString()); _logger.LogInformation("Registering new account: {uri}", postUri.ToString());
var result = await httpClient.PostAsync(postUri, null).ConfigureAwait(false); var result = await httpClient.PostAsync(postUri, null).ConfigureAwait(false);
result.EnsureSuccessStatusCode(); result.EnsureSuccessStatusCode();
var reply = await result.Content.ReadFromJsonAsync<RegisterReplyDto>().ConfigureAwait(false) ?? new(); var reply = await result.Content.ReadFromJsonAsync<RegisterReplyDto>().ConfigureAwait(false) ?? new();
if (!reply.Success) if (!reply.Success)
{ {
_logger.LogWarning("Registration failed: " + reply.ErrorMessage); _logger.LogWarning("Registration failed: {err}", reply.ErrorMessage);
_registrationMessage = reply.ErrorMessage; _registrationMessage = reply.ErrorMessage;
if (_registrationMessage.IsNullOrEmpty()) if (_registrationMessage.IsNullOrEmpty())
_registrationMessage = "An unknown error occured. Please try again later."; _registrationMessage = "An unknown error occured. Please try again later.";
@@ -331,4 +339,7 @@ public class IntroUi : WindowMediatorSubscriberBase
_tosParagraphs = [Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6]; _tosParagraphs = [Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6];
} }
[GeneratedRegex("^([A-F0-9]{2})+")]
private static partial Regex HexRegex();
} }

View File

@@ -77,12 +77,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
MareCharaFileManager mareCharaFileManager, PairManager pairManager, ChatService chatService, GuiHookService guiHookService, MareCharaFileManager mareCharaFileManager, PairManager pairManager, ChatService chatService, GuiHookService guiHookService,
ServerConfigurationManager serverConfigurationManager, ServerConfigurationManager serverConfigurationManager,
MareMediator mediator, PerformanceCollectorService performanceCollector, MareMediator mediator, PerformanceCollectorService performanceCollector,
DalamudUtilService dalamudUtilService,
FileUploadManager fileTransferManager, FileUploadManager fileTransferManager,
FileTransferOrchestrator fileTransferOrchestrator, FileTransferOrchestrator fileTransferOrchestrator,
FileCacheManager fileCacheManager, FileCacheManager fileCacheManager,
FileCompactor fileCompactor, ApiController apiController, FileCompactor fileCompactor, ApiController apiController,
IpcManager ipcManager, CacheMonitor cacheMonitor) : base(logger, mediator, "Loporrit Settings", performanceCollector) IpcManager ipcManager, CacheMonitor cacheMonitor,
DalamudUtilService dalamudUtilService) : base(logger, mediator, "Loporrit Settings", performanceCollector)
{ {
_configService = configService; _configService = configService;
_mareCharaFileManager = mareCharaFileManager; _mareCharaFileManager = mareCharaFileManager;
@@ -91,7 +91,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
_guiHookService = guiHookService; _guiHookService = guiHookService;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_performanceCollector = performanceCollector; _performanceCollector = performanceCollector;
_dalamudUtilService = dalamudUtilService;
_fileTransferManager = fileTransferManager; _fileTransferManager = fileTransferManager;
_fileTransferOrchestrator = fileTransferOrchestrator; _fileTransferOrchestrator = fileTransferOrchestrator;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
@@ -133,6 +132,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
public override void OnClose() public override void OnClose()
{ {
_uiShared.EditTrackerPosition = false; _uiShared.EditTrackerPosition = false;
base.OnClose(); base.OnClose();
} }
@@ -191,7 +191,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
_uiShared.DrawCombo("###speed", new[] { DownloadSpeeds.Bps, DownloadSpeeds.KBps, DownloadSpeeds.MBps }, _uiShared.DrawCombo("###speed", [DownloadSpeeds.Bps, DownloadSpeeds.KBps, DownloadSpeeds.MBps],
(s) => s switch (s) => s switch
{ {
DownloadSpeeds.Bps => "Byte/s", DownloadSpeeds.Bps => "Byte/s",

View File

@@ -82,7 +82,8 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController, public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager, CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
MareConfigService configService, DalamudUtilService dalamudUtil, IDalamudPluginInterface pluginInterface, MareConfigService configService, DalamudUtilService dalamudUtil, IDalamudPluginInterface pluginInterface,
ITextureProvider textureProvider, Dalamud.Localization localization, ITextureProvider textureProvider,
Dalamud.Localization localization,
ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator) ServerConfigurationManager serverManager, MareMediator mediator) : base(logger, mediator)
{ {
_ipcManager = ipcManager; _ipcManager = ipcManager;

View File

@@ -201,7 +201,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
} }
} }
private async Task DownloadFilesInternal(GameObjectHandler gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct) public async Task<List<DownloadFileTransfer>> InitiateDownloadList(GameObjectHandler gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct)
{ {
Logger.LogDebug("Download start: {id}", gameObjectHandler.Name); Logger.LogDebug("Download start: {id}", gameObjectHandler.Name);
@@ -212,9 +212,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
Logger.LogDebug("Files with size 0 or less: {files}", string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); Logger.LogDebug("Files with size 0 or less: {files}", string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
CurrentDownloads = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred).ToList();
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
{ {
if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal)))
@@ -223,7 +220,15 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
} }
} }
var downloadGroups = CurrentDownloads.Where(f => f.CanBeTransferred).GroupBy(f => f.DownloadUri.Host + ":" + f.DownloadUri.Port, StringComparer.Ordinal); CurrentDownloads = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred).ToList();
return CurrentDownloads;
}
private async Task DownloadFilesInternal(GameObjectHandler gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct)
{
var downloadGroups = CurrentDownloads.GroupBy(f => f.DownloadUri.Host + ":" + f.DownloadUri.Port, StringComparer.Ordinal);
foreach (var downloadGroup in downloadGroups) foreach (var downloadGroup in downloadGroups)
{ {

View File

@@ -232,10 +232,10 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
{ {
Logger.LogDebug("[{hash}] Compressing", file); Logger.LogDebug("[{hash}] Compressing", file);
var data = await _fileDbManager.GetCompressedFileData(file.Hash, uploadToken).ConfigureAwait(false); var data = await _fileDbManager.GetCompressedFileData(file.Hash, uploadToken).ConfigureAwait(false);
CurrentUploads.Single(e => string.Equals(e.Hash, file.Hash, StringComparison.Ordinal)).Total = data.Length; CurrentUploads.Single(e => string.Equals(e.Hash, file.Hash, StringComparison.Ordinal)).Total = data.Item2.Length;
Logger.LogDebug("[{hash}] Starting upload for {filePath}", file.Hash, _fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath); Logger.LogDebug("[{hash}] Starting upload for {filePath}", file.Hash, _fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath);
await uploadTask.ConfigureAwait(false); await uploadTask.ConfigureAwait(false);
uploadTask = UploadFile(data, file.Hash, uploadToken); uploadTask = UploadFile(data.Item2, file.Hash, uploadToken);
uploadToken.ThrowIfCancellationRequested(); uploadToken.ThrowIfCancellationRequested();
} }

View File

@@ -18,5 +18,7 @@ public class DownloadFileTransfer : FileTransfer
} }
get => Dto.Size; get => Dto.Size;
} }
public long TotalRaw => 0; // XXX
private DownloadFileDto Dto => (DownloadFileDto)TransferDto; private DownloadFileDto Dto => (DownloadFileDto)TransferDto;
} }

View File

@@ -4,6 +4,7 @@ using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto; using MareSynchronos.API.Dto;
using MareSynchronos.API.Dto.User; using MareSynchronos.API.Dto.User;
using MareSynchronos.API.SignalR; using MareSynchronos.API.SignalR;
using MareSynchronos.MareConfiguration;
using MareSynchronos.MareConfiguration.Models; using MareSynchronos.MareConfiguration.Models;
using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services; using MareSynchronos.Services;
@@ -58,6 +59,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception)); Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception));
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePause(msg.UserData)); Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePause(msg.UserData));
Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg); Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg);
Mediator.Subscribe<PauseMessage>(this, (msg) => _ = Pause(msg.UserData));
ServerState = ServerState.Offline; ServerState = ServerState.Offline;
@@ -208,6 +210,17 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
NotificationType.Warning, TimeSpan.FromSeconds(15))); NotificationType.Warning, TimeSpan.FromSeconds(15)));
} }
if (_dalamudUtil.HasModifiedGameFiles)
{
Logger.LogError("Detected modified game files on connection");
if (false)
Mediator.Publish(new NotificationMessage("Modified Game Files detected",
"Dalamud has reported modified game files in your FFXIV installation. " +
"You will be able to connect, but the synchronization functionality might be (partially) broken. " +
"Exit the game and repair it through XIVLauncher to get rid of this message.",
NotificationType.Error, TimeSpan.FromSeconds(15)));
}
await LoadIninitialPairs().ConfigureAwait(false); await LoadIninitialPairs().ConfigureAwait(false);
await LoadOnlinePairs().ConfigureAwait(false); await LoadOnlinePairs().ConfigureAwait(false);
} }
@@ -255,7 +268,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData); var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
var perm = pair.UserPair!.OwnPermissions; var perm = pair.UserPair!.OwnPermissions;
perm.SetPaused(paused: true); perm.SetPaused(paused: true);
await UserSetPairPermissions(new API.Dto.User.UserPermissionsDto(userData, perm)).ConfigureAwait(false); await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
// wait until it's changed // wait until it's changed
while (pair.UserPair!.OwnPermissions != perm) while (pair.UserPair!.OwnPermissions != perm)
{ {
@@ -263,12 +276,20 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Logger.LogTrace("Waiting for permissions change for {data}", userData); Logger.LogTrace("Waiting for permissions change for {data}", userData);
} }
perm.SetPaused(paused: false); perm.SetPaused(paused: false);
await UserSetPairPermissions(new API.Dto.User.UserPermissionsDto(userData, perm)).ConfigureAwait(false); await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
}, cts.Token).ContinueWith((t) => cts.Dispose()); }, cts.Token).ContinueWith((t) => cts.Dispose());
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task Pause(UserData userData)
{
var pair = _pairManager.GetOnlineUserPairs().Single(p => p.UserPair != null && p.UserData == userData);
var perm = pair.UserPair!.OwnPermissions;
perm.SetPaused(paused: true);
await UserSetPairPermissions(new UserPermissionsDto(userData, perm)).ConfigureAwait(false);
}
public Task<ConnectionDto> GetConnectionDto() => GetConnectionDto(true); public Task<ConnectionDto> GetConnectionDto() => GetConnectionDto(true);
public async Task<ConnectionDto> GetConnectionDto(bool publishConnected = true) public async Task<ConnectionDto> GetConnectionDto(bool publishConnected = true)

View File

@@ -92,7 +92,6 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
throw; throw;
} }
_logger.LogTrace("GetNewToken: JWT {token}", response);
return response; return response;
} }