fix some shit, add triangle count
check for invalid animations I hate animations ignore broken bones from god knows what fix more idiotic mod things fully ignore garbage skeletons that fail to process properly fix my own mistakes fix more bullshit check for filename length and continue idk some cleanup fix spoopy skellingtons change loglevel of tris
This commit is contained in:
@@ -100,3 +100,12 @@ dotnet_diagnostic.MA0075.severity = silent
|
||||
|
||||
# S3358: Ternary operators should not be nested
|
||||
dotnet_diagnostic.S3358.severity = suggestion
|
||||
|
||||
# S6678: Use PascalCase for named placeholders
|
||||
dotnet_diagnostic.S6678.severity = suggestion
|
||||
|
||||
# S6605: Collection-specific "Exists" method should be used instead of the "Any" extension
|
||||
dotnet_diagnostic.S6605.severity = suggestion
|
||||
|
||||
# S6667: Logging in a catch clause should pass the caught exception as a parameter.
|
||||
dotnet_diagnostic.S6667.severity = suggestion
|
||||
|
||||
@@ -5,6 +5,7 @@ using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MareSynchronos.FileCache;
|
||||
|
||||
@@ -25,6 +26,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
||||
FileCacheManager fileDbManager, MareMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil,
|
||||
FileCompactor fileCompactor) : base(logger, mediator)
|
||||
{
|
||||
Logger.LogInformation("Creating CacheMonitor from {trace}", Environment.StackTrace);
|
||||
_ipcManager = ipcManager;
|
||||
_configService = configService;
|
||||
_fileDbManager = fileDbManager;
|
||||
|
||||
@@ -208,7 +208,7 @@ public sealed class FileCacheManager : IHostedService
|
||||
|
||||
foreach (var entry in cleanedPaths)
|
||||
{
|
||||
_logger.LogDebug("Checking {path}", entry.Value);
|
||||
//_logger.LogDebug("Checking {path}", entry.Value);
|
||||
|
||||
if (dict.TryGetValue(entry.Value, out var entity))
|
||||
{
|
||||
@@ -336,7 +336,7 @@ public sealed class FileCacheManager : IHostedService
|
||||
|
||||
if (!entries.Exists(u => string.Equals(u.PrefixedFilePath, fileCache.PrefixedFilePath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_logger.LogTrace("Adding to DB: {hash} => {path}", fileCache.Hash, fileCache.PrefixedFilePath);
|
||||
//_logger.LogTrace("Adding to DB: {hash} => {path}", fileCache.Hash, fileCache.PrefixedFilePath);
|
||||
entries.Add(fileCache);
|
||||
}
|
||||
}
|
||||
@@ -359,7 +359,7 @@ public sealed class FileCacheManager : IHostedService
|
||||
private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache)
|
||||
{
|
||||
var resultingFileCache = ReplacePathPrefixes(fileCache);
|
||||
_logger.LogTrace("Validating {path}", fileCache.PrefixedFilePath);
|
||||
//_logger.LogTrace("Validating {path}", fileCache.PrefixedFilePath);
|
||||
resultingFileCache = Validate(resultingFileCache);
|
||||
return resultingFileCache;
|
||||
}
|
||||
|
||||
@@ -277,4 +277,14 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
Mediator.Publish(new TransientResourceChangedMessage(gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveTransientResource(ObjectKind objectKind, string path)
|
||||
{
|
||||
if (SemiTransientResources.TryGetValue(objectKind, out var resources))
|
||||
{
|
||||
resources.RemoveWhere(f => string.Equals(path, f, StringComparison.OrdinalIgnoreCase));
|
||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = resources;
|
||||
_configurationService.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ public abstract class ConfigurationServiceBase<T> : IDisposable where T : IMareC
|
||||
{
|
||||
_periodicCheckCts.Cancel();
|
||||
_periodicCheckCts.Dispose();
|
||||
if (_configIsDirty) SaveDirtyConfig();
|
||||
}
|
||||
|
||||
protected T LoadConfig()
|
||||
@@ -94,10 +95,12 @@ public abstract class ConfigurationServiceBase<T> : IDisposable where T : IMareC
|
||||
// ignore if file cannot be backupped once
|
||||
}
|
||||
|
||||
File.WriteAllText(ConfigurationPath, JsonSerializer.Serialize(Current, new JsonSerializerOptions()
|
||||
var temp = ConfigurationPath + ".tmp";
|
||||
File.WriteAllText(temp, JsonSerializer.Serialize(Current, new JsonSerializerOptions()
|
||||
{
|
||||
WriteIndented = true
|
||||
}));
|
||||
File.Move(temp, ConfigurationPath, true);
|
||||
_configLastWriteTime = new FileInfo(ConfigurationPath).LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@ public class TransientConfig : IMareConfiguration
|
||||
{
|
||||
public Dictionary<string, HashSet<string>> PlayerPersistentTransientCache { get; set; } = new(StringComparer.Ordinal);
|
||||
public int Version { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronos.MareConfiguration.Configurations;
|
||||
|
||||
public class XivDataStorageConfig : IMareConfiguration
|
||||
{
|
||||
public ConcurrentDictionary<string, long> TriangleDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
public ConcurrentDictionary<string, Dictionary<string, List<ushort>>> BonesDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
public int Version { get; set; } = 0;
|
||||
}
|
||||
@@ -11,4 +11,4 @@ public class TransientConfigService : ConfigurationServiceBase<TransientConfig>
|
||||
}
|
||||
|
||||
protected override string ConfigurationName => ConfigName;
|
||||
}
|
||||
}
|
||||
|
||||
12
MareSynchronos/MareConfiguration/XivDataStorageService.cs
Normal file
12
MareSynchronos/MareConfiguration/XivDataStorageService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using MareSynchronos.MareConfiguration.Configurations;
|
||||
|
||||
namespace MareSynchronos.MareConfiguration;
|
||||
|
||||
public class XivDataStorageService : ConfigurationServiceBase<XivDataStorageConfig>
|
||||
{
|
||||
public const string ConfigName = "xivdatastorage.json";
|
||||
|
||||
public XivDataStorageService(string configDir) : base(configDir) { }
|
||||
|
||||
protected override string ConfigurationName => ConfigName;
|
||||
}
|
||||
@@ -74,6 +74,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private IServiceScope? _runtimeServiceScope;
|
||||
private Task? _launchTask = null;
|
||||
|
||||
public MarePlugin(ILogger<MarePlugin> logger, MareConfigService mareConfigService,
|
||||
ServerConfigurationManager serverConfigurationManager,
|
||||
@@ -93,7 +94,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
|
||||
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(MarePlugin), Services.Events.EventSeverity.Informational,
|
||||
$"Starting Loporrit Sync {version.Major}.{version.Minor}.{version.Build}-lop{version.Revision}")));
|
||||
|
||||
Mediator.Subscribe<SwitchToMainUiMessage>(this, (msg) => _ = Task.Run(WaitForPlayerAndLaunchCharacterManager));
|
||||
Mediator.Subscribe<SwitchToMainUiMessage>(this, (msg) => { if (_launchTask == null || _launchTask.IsCompleted) _launchTask = Task.Run(WaitForPlayerAndLaunchCharacterManager); });
|
||||
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
||||
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
|
||||
|
||||
@@ -116,8 +117,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
Logger?.LogDebug("Client login");
|
||||
|
||||
_ = Task.Run(WaitForPlayerAndLaunchCharacterManager);
|
||||
if (_launchTask == null || _launchTask.IsCompleted) _launchTask = Task.Run(WaitForPlayerAndLaunchCharacterManager);
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogOut()
|
||||
|
||||
@@ -20,13 +20,14 @@ public class PairHandlerFactory
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly XivDataAnalyzer _xivDataAnalyzer;
|
||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
|
||||
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
||||
PluginWarningNotificationService pluginWarningNotificationManager, ServerConfigurationManager serverConfigurationManager,
|
||||
CancellationToken dalamudLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator)
|
||||
CancellationToken dalamudLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator, XivDataAnalyzer modelAnalyzer)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||
@@ -38,12 +39,13 @@ public class PairHandlerFactory
|
||||
_dalamudLifetimeToken = dalamudLifetime;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_mareMediator = mareMediator;
|
||||
_xivDataAnalyzer = modelAnalyzer;
|
||||
}
|
||||
|
||||
public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto)
|
||||
{
|
||||
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), onlineUserIdentDto, _gameObjectHandlerFactory,
|
||||
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _serverConfigurationManager, _dalamudUtilService,
|
||||
_dalamudLifetimeToken, _fileCacheManager, _mareMediator);
|
||||
_dalamudLifetimeToken, _fileCacheManager, _mareMediator, _xivDataAnalyzer);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Interop.Ipc;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.PlayerData.Data;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using CharacterData = MareSynchronos.PlayerData.Data.CharacterData;
|
||||
|
||||
@@ -18,11 +20,13 @@ public class PlayerDataFactory
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly ILogger<PlayerDataFactory> _logger;
|
||||
private readonly PerformanceCollectorService _performanceCollector;
|
||||
private readonly XivDataAnalyzer _modelAnalyzer;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly TransientResourceManager _transientResourceManager;
|
||||
|
||||
public PlayerDataFactory(ILogger<PlayerDataFactory> logger, DalamudUtilService dalamudUtil, IpcManager ipcManager,
|
||||
TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory,
|
||||
PerformanceCollectorService performanceCollector)
|
||||
PerformanceCollectorService performanceCollector, XivDataAnalyzer modelAnalyzer, MareMediator mareMediator)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
@@ -30,7 +34,9 @@ public class PlayerDataFactory
|
||||
_transientResourceManager = transientResourceManager;
|
||||
_fileCacheManager = fileReplacementFactory;
|
||||
_performanceCollector = performanceCollector;
|
||||
_logger.LogTrace("Creating " + nameof(PlayerDataFactory));
|
||||
_modelAnalyzer = modelAnalyzer;
|
||||
_mareMediator = mareMediator;
|
||||
_logger.LogTrace("Creating {this}", nameof(PlayerDataFactory));
|
||||
}
|
||||
|
||||
public async Task BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||
@@ -128,12 +134,16 @@ public class PlayerDataFactory
|
||||
// wait until chara is not drawing and present so nothing spontaneously explodes
|
||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: token).ConfigureAwait(false);
|
||||
int totalWaitTime = 10000;
|
||||
while (!await _dalamudUtil.IsObjectPresentAsync(await _dalamudUtil.CreateGameObjectAsync(charaPointer).ConfigureAwait(false)).ConfigureAwait(false) && totalWaitTime > 0)
|
||||
while (!await _dalamudUtil.IsObjectPresentAsync(await _dalamudUtil.CreateGameObjectAsync(playerRelatedObject.Address).ConfigureAwait(false)).ConfigureAwait(false) && totalWaitTime > 0)
|
||||
{
|
||||
_logger.LogTrace("Character is null but it shouldn't be, waiting");
|
||||
await Task.Delay(50, token).ConfigureAwait(false);
|
||||
totalWaitTime -= 50;
|
||||
}
|
||||
Dictionary<string, List<ushort>>? boneIndices =
|
||||
objectKind != ObjectKind.Player
|
||||
? null
|
||||
: await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetSkeletonBoneIndices(playerRelatedObject)).ConfigureAwait(false);
|
||||
|
||||
DateTime start = DateTime.UtcNow;
|
||||
|
||||
@@ -226,11 +236,84 @@ public class PlayerDataFactory
|
||||
}
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
try
|
||||
{
|
||||
await VerifyPlayerAnimationBones(boneIndices, previousData, objectKind).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning(e, "Failed to verify player animations, continuing without further verification");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start.Ticks).TotalMilliseconds);
|
||||
|
||||
return previousData;
|
||||
}
|
||||
|
||||
private async Task VerifyPlayerAnimationBones(Dictionary<string, List<ushort>>? boneIndices, CharacterData previousData, ObjectKind objectKind)
|
||||
{
|
||||
if (boneIndices == null) return;
|
||||
|
||||
foreach (var kvp in boneIndices)
|
||||
{
|
||||
_logger.LogDebug("Found {skellyname} ({idx} bone indices) on player: {bones}", kvp.Key, kvp.Value.Any() ? kvp.Value.Max() : 0, string.Join(',', kvp.Value));
|
||||
}
|
||||
|
||||
if (boneIndices.All(u => u.Value.Count == 0)) return;
|
||||
|
||||
int noValidationFailed = 0;
|
||||
foreach (var file in previousData.FileReplacements[objectKind].Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList())
|
||||
{
|
||||
var skeletonIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(file.Hash)).ConfigureAwait(false);
|
||||
bool validationFailed = false;
|
||||
if (skeletonIndices != null)
|
||||
{
|
||||
// 105 is the maximum vanilla skellington spoopy bone index
|
||||
if (skeletonIndices.All(k => k.Value.Max() <= 105))
|
||||
{
|
||||
_logger.LogTrace("All indices of {path} are <= 105, ignoring", file.ResolvedPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Verifying bone indices for {path}, found {x} skeletons", file.ResolvedPath, skeletonIndices.Count);
|
||||
|
||||
foreach (var boneCount in skeletonIndices.Select(k => k).ToList())
|
||||
{
|
||||
if (boneCount.Value.Max() > boneIndices.SelectMany(b => b.Value).Max())
|
||||
{
|
||||
_logger.LogWarning("Found more bone indices on the animation {path} skeleton {skl} (max indice {idx}) than on any player related skeleton (max indice {idx2})",
|
||||
file.ResolvedPath, boneCount.Key, boneCount.Value.Max(), boneIndices.SelectMany(b => b.Value).Max());
|
||||
validationFailed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validationFailed)
|
||||
{
|
||||
noValidationFailed++;
|
||||
_logger.LogDebug("Removing {file} from sent file replacements and transient data", file.ResolvedPath);
|
||||
previousData.FileReplacements[objectKind].Remove(file);
|
||||
foreach (var gamePath in file.GamePaths)
|
||||
{
|
||||
_transientResourceManager.RemoveTransientResource(objectKind, gamePath);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (noValidationFailed > 0)
|
||||
{
|
||||
_mareMediator.Publish(new NotificationMessage("Invalid Skeleton Setup",
|
||||
$"Your client is attempting to send {noValidationFailed} animation files with invalid bone data. Those animation files have been removed from your sent data. " +
|
||||
$"Verify that you are using the correct skeleton for those animation files (Check /xllog for more information).",
|
||||
NotificationType.Warning, TimeSpan.FromSeconds(10)));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyDictionary<string, string[]>> GetFileReplacementsFromPaths(HashSet<string> forwardResolve, HashSet<string> reverseResolve)
|
||||
{
|
||||
var forwardPaths = forwardResolve.ToArray();
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly FileDownloadManager _downloadManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly XivDataAnalyzer _xivDataAnalyzer;
|
||||
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly CancellationToken _lifetime;
|
||||
@@ -42,13 +43,15 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
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,
|
||||
GameObjectHandlerFactory gameObjectHandlerFactory,
|
||||
IpcManager ipcManager, FileDownloadManager transferManager,
|
||||
PluginWarningNotificationService pluginWarningNotificationManager, ServerConfigurationManager serverConfigurationManager,
|
||||
DalamudUtilService dalamudUtil, CancellationToken lifetime,
|
||||
FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator)
|
||||
FileCacheManager fileDbManager, MareMediator mediator,
|
||||
XivDataAnalyzer modelAnalyzer) : base(logger, mediator)
|
||||
{
|
||||
OnlineUser = onlineUser;
|
||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||
@@ -59,6 +62,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_lifetime = lifetime;
|
||||
_fileDbManager = fileDbManager;
|
||||
_xivDataAnalyzer = modelAnalyzer;
|
||||
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
|
||||
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) =>
|
||||
@@ -100,6 +104,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
});
|
||||
|
||||
LastAppliedDataSize = -1;
|
||||
LastAppliedDataTris = -1;
|
||||
}
|
||||
|
||||
public bool IsVisible
|
||||
@@ -377,7 +382,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
Dictionary<string, string> moddedPaths = new(StringComparer.Ordinal);
|
||||
Dictionary<(string GamePath, string? Hash), string> moddedPaths = new();
|
||||
|
||||
if (updateModdedPaths)
|
||||
{
|
||||
@@ -444,12 +449,20 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
|
||||
if (updateModdedPaths)
|
||||
{
|
||||
await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false);
|
||||
await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection,
|
||||
moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false);
|
||||
LastAppliedDataSize = -1;
|
||||
LastAppliedDataTris = -1;
|
||||
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists))
|
||||
{
|
||||
if (LastAppliedDataSize == -1) LastAppliedDataSize = 0;
|
||||
LastAppliedDataSize += path.Length;
|
||||
}
|
||||
foreach (var key in moddedPaths.Keys.Where(k => !string.IsNullOrEmpty(k.Hash)))
|
||||
{
|
||||
if (LastAppliedDataTris == -1) LastAppliedDataTris = 0;
|
||||
LastAppliedDataTris += await _xivDataAnalyzer.GetTrianglesByHash(key.Hash!).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (updateManip)
|
||||
@@ -619,12 +632,12 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private List<FileReplacementData> TryCalculateModdedDictionary(Guid applicationBase, CharacterData charaData, out Dictionary<string, string> moddedDictionary, CancellationToken token)
|
||||
private List<FileReplacementData> TryCalculateModdedDictionary(Guid applicationBase, CharacterData charaData, out Dictionary<(string GamePath, string? Hash), string> moddedDictionary, CancellationToken token)
|
||||
{
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
ConcurrentBag<FileReplacementData> missingFiles = [];
|
||||
moddedDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
ConcurrentDictionary<string, string> outputDict = new(StringComparer.Ordinal);
|
||||
moddedDictionary = new Dictionary<(string GamePath, string? Hash), string>();
|
||||
ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new();
|
||||
bool hasMigrationChanges = false;
|
||||
|
||||
try
|
||||
@@ -649,7 +662,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
|
||||
foreach (var gamePath in item.GamePaths)
|
||||
{
|
||||
outputDict[gamePath] = fileCache.ResolvedFilepath;
|
||||
outputDict[(gamePath, item.Hash)] = fileCache.ResolvedFilepath;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -659,14 +672,14 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
}
|
||||
});
|
||||
|
||||
moddedDictionary = outputDict.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal);
|
||||
moddedDictionary = outputDict.ToDictionary(k => k.Key, k => k.Value);
|
||||
|
||||
foreach (var item in charaData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList())
|
||||
{
|
||||
foreach (var gamePath in item.GamePaths)
|
||||
{
|
||||
Logger.LogTrace("[BASE-{appBase}] Adding file swap for {path}: {fileSwap}", applicationBase, gamePath, item.FileSwapPath);
|
||||
moddedDictionary[gamePath] = item.FileSwapPath;
|
||||
moddedDictionary[(gamePath, null)] = item.FileSwapPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class Pair
|
||||
public string? PlayerName => GetPlayerName();
|
||||
public uint PlayerCharacterId => GetPlayerCharacterId();
|
||||
public long LastAppliedDataSize => CachedPlayer?.LastAppliedDataSize ?? -1;
|
||||
public long LastAppliedDataTris => CachedPlayer?.LastAppliedDataTris ?? -1;
|
||||
|
||||
public UserData UserData => UserPair?.User ?? GroupPair.First().Value.User;
|
||||
|
||||
|
||||
@@ -85,8 +85,10 @@ public sealed class Plugin : IDalamudPlugin
|
||||
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<FileCacheManager>(), s.GetRequiredService<MareMediator>(), s.GetRequiredService<XivDataAnalyzer>()));
|
||||
collection.AddSingleton<PairFactory>();
|
||||
collection.AddSingleton<XivDataAnalyzer>(s => new(s.GetRequiredService<ILogger<XivDataAnalyzer>>(), s.GetRequiredService<FileCacheManager>(),
|
||||
s.GetRequiredService<XivDataStorageService>(), gameData));
|
||||
collection.AddSingleton<CharacterAnalyzer>();
|
||||
collection.AddSingleton<TokenProvider>();
|
||||
collection.AddSingleton<PluginWarningNotificationService>();
|
||||
@@ -124,6 +126,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton((s) => new ServerTagConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new SyncshellConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new TransientConfigService(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<HubFactory>();
|
||||
|
||||
|
||||
@@ -12,16 +12,22 @@ namespace MareSynchronos.Services;
|
||||
public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
private readonly XivDataAnalyzer _xivDataAnalyzer;
|
||||
private CancellationTokenSource? _analysisCts;
|
||||
private CancellationTokenSource _baseAnalysisCts = new();
|
||||
private string _lastDataHash = string.Empty;
|
||||
|
||||
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager) : base(logger, mediator)
|
||||
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
|
||||
: base(logger, mediator)
|
||||
{
|
||||
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
|
||||
{
|
||||
_ = Task.Run(() => BaseAnalysis(msg.CharacterData.DeepClone()));
|
||||
_baseAnalysisCts = _baseAnalysisCts.CancelRecreate();
|
||||
var token = _baseAnalysisCts.Token;
|
||||
_ = BaseAnalysis(msg.CharacterData, token);
|
||||
});
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_xivDataAnalyzer = modelAnalyzer;
|
||||
}
|
||||
|
||||
public int CurrentFile { get; internal set; }
|
||||
@@ -87,7 +93,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
_analysisCts.CancelDispose();
|
||||
}
|
||||
|
||||
private void BaseAnalysis(CharacterData charaData)
|
||||
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
|
||||
{
|
||||
if (string.Equals(charaData.DataHash.Value, _lastDataHash, StringComparison.Ordinal)) return;
|
||||
|
||||
@@ -98,6 +104,8 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
Dictionary<string, FileDataEntry> data = new(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var fileEntry in obj.Value)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var fileCacheEntries = _fileCacheManager.GetAllFileCachesByHash(fileEntry.Hash, ignoreCacheEntries: true, validate: false).ToList();
|
||||
if (fileCacheEntries.Count == 0) continue;
|
||||
|
||||
@@ -113,12 +121,16 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
Logger.LogWarning(ex, "Could not identify extension for {path}", filePath);
|
||||
}
|
||||
|
||||
var tris = await _xivDataAnalyzer.GetTrianglesByHash(fileEntry.Hash).ConfigureAwait(false);
|
||||
|
||||
foreach (var entry in fileCacheEntries)
|
||||
{
|
||||
data[fileEntry.Hash] = new FileDataEntry(fileEntry.Hash, ext,
|
||||
[.. fileEntry.GamePaths],
|
||||
fileCacheEntries.Select(c => c.ResolvedFilepath).Distinct().ToList(),
|
||||
entry.Size > 0 ? entry.Size.Value : 0, entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0);
|
||||
entry.Size > 0 ? entry.Size.Value : 0,
|
||||
entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0,
|
||||
tris);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +188,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
Logger.LogInformation("IMPORTANT NOTES:\n\r- For uploads and downloads only the compressed size is relevant.\n\r- An unusually high total files count beyond 200 and up will also increase your download time to others significantly.");
|
||||
}
|
||||
|
||||
internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize)
|
||||
internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize, long Triangles)
|
||||
{
|
||||
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
|
||||
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token)
|
||||
@@ -194,6 +206,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
}
|
||||
public long OriginalSize { get; private set; } = OriginalSize;
|
||||
public long CompressedSize { get; private set; } = CompressedSize;
|
||||
public long Triangles { get; private set; } = Triangles;
|
||||
|
||||
public Lazy<string> Format = new(() =>
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class CommandManagerService : IDisposable
|
||||
{
|
||||
var splitArgs = args.ToLowerInvariant().Trim().Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (splitArgs == null || splitArgs.Length == 0)
|
||||
if (splitArgs.Length == 0)
|
||||
{
|
||||
// Interpret this as toggling the UI
|
||||
if (_mareConfigService.Current.HasValidSetup())
|
||||
|
||||
202
MareSynchronos/Services/XivDataAnalyzer.cs
Normal file
202
MareSynchronos/Services/XivDataAnalyzer.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.Havok.Animation;
|
||||
using FFXIVClientStructs.Havok.Common.Base.Types;
|
||||
using FFXIVClientStructs.Havok.Common.Serialize.Util;
|
||||
using Lumina;
|
||||
using Lumina.Data.Files;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public sealed class XivDataAnalyzer
|
||||
{
|
||||
private readonly ILogger<XivDataAnalyzer> _logger;
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
private readonly XivDataStorageService _configService;
|
||||
private readonly GameData _luminaGameData;
|
||||
|
||||
public XivDataAnalyzer(ILogger<XivDataAnalyzer> logger, FileCacheManager fileCacheManager,
|
||||
XivDataStorageService configService, IDataManager gameData)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_configService = configService;
|
||||
_luminaGameData = new GameData(gameData.GameData.DataPath.FullName);
|
||||
}
|
||||
|
||||
public unsafe Dictionary<string, List<ushort>>? GetSkeletonBoneIndices(GameObjectHandler handler)
|
||||
{
|
||||
if (handler.Address == nint.Zero) return null;
|
||||
var chara = (CharacterBase*)(((Character*)handler.Address)->GameObject.DrawObject);
|
||||
if (chara->GetModelType() != CharacterBase.ModelType.Human) return null;
|
||||
var resHandles = chara->Skeleton->SkeletonResourceHandles;
|
||||
Dictionary<string, List<ushort>> outputIndices = [];
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < chara->Skeleton->PartialSkeletonCount; i++)
|
||||
{
|
||||
var handle = *(resHandles + i);
|
||||
_logger.LogTrace("Iterating over SkeletonResourceHandle #{i}:{x}", i, ((nint)handle).ToString("X"));
|
||||
if ((nint)handle == nint.Zero) continue;
|
||||
var curBones = handle->BoneCount;
|
||||
// this is unrealistic, the filename shouldn't ever be that long
|
||||
if (handle->ResourceHandle.FileName.Length > 1024) continue;
|
||||
var skeletonName = handle->ResourceHandle.FileName.ToString();
|
||||
if (string.IsNullOrEmpty(skeletonName)) continue;
|
||||
outputIndices[skeletonName] = new();
|
||||
for (ushort boneIdx = 0; boneIdx < curBones; boneIdx++)
|
||||
{
|
||||
var boneName = handle->HavokSkeleton->Bones[boneIdx].Name.String;
|
||||
if (boneName == null) continue;
|
||||
outputIndices[skeletonName].Add((ushort)(boneIdx + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not process skeleton data");
|
||||
}
|
||||
|
||||
return (outputIndices.Count != 0 && outputIndices.Values.All(u => u.Count > 0)) ? outputIndices : null;
|
||||
}
|
||||
|
||||
public unsafe Dictionary<string, List<ushort>>? GetBoneIndicesFromPap(string hash)
|
||||
{
|
||||
if (_configService.Current.BonesDictionary.TryGetValue(hash, out var bones)) return bones;
|
||||
|
||||
var cacheEntity = _fileCacheManager.GetFileCacheByHash(hash);
|
||||
if (cacheEntity == null) return null;
|
||||
|
||||
using BinaryReader reader = new BinaryReader(File.Open(cacheEntity.ResolvedFilepath, FileMode.Open, FileAccess.Read, FileShare.Read));
|
||||
|
||||
// most of this shit is from vfxeditor, surely nothing will change in the pap format :copium:
|
||||
reader.ReadInt32(); // ignore
|
||||
reader.ReadInt32(); // ignore
|
||||
reader.ReadInt16(); // read 2 (num animations)
|
||||
reader.ReadInt16(); // read 2 (modelid)
|
||||
var type = reader.ReadByte();// read 1 (type)
|
||||
if (type != 0) return null; // it's not human, just ignore it, whatever
|
||||
|
||||
reader.ReadByte(); // read 1 (variant)
|
||||
reader.ReadInt32(); // ignore
|
||||
var havokPosition = reader.ReadInt32();
|
||||
var footerPosition = reader.ReadInt32();
|
||||
var havokDataSize = footerPosition - havokPosition;
|
||||
reader.BaseStream.Position = havokPosition;
|
||||
var havokData = reader.ReadBytes(havokDataSize);
|
||||
if (havokData.Length <= 8) return null; // no havok data
|
||||
|
||||
var output = new Dictionary<string, List<ushort>>(StringComparer.OrdinalIgnoreCase);
|
||||
var tempHavokDataPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + ".hkx";
|
||||
var tempHavokDataPathAnsi = Marshal.StringToHGlobalAnsi(tempHavokDataPath);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(tempHavokDataPath, havokData);
|
||||
|
||||
var loadoptions = stackalloc hkSerializeUtil.LoadOptions[1];
|
||||
loadoptions->TypeInfoRegistry = hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry();
|
||||
loadoptions->ClassNameRegistry = hkBuiltinTypeRegistry.Instance()->GetClassNameRegistry();
|
||||
loadoptions->Flags = new hkFlags<hkSerializeUtil.LoadOptionBits, int>
|
||||
{
|
||||
Storage = (int)(hkSerializeUtil.LoadOptionBits.Default)
|
||||
};
|
||||
|
||||
var resource = hkSerializeUtil.LoadFromFile((byte*)tempHavokDataPathAnsi, null, loadoptions);
|
||||
if (resource == null)
|
||||
{
|
||||
throw new InvalidOperationException("Resource was null after loading");
|
||||
}
|
||||
|
||||
var rootLevelName = @"hkRootLevelContainer"u8;
|
||||
fixed (byte* n1 = rootLevelName)
|
||||
{
|
||||
var container = (hkRootLevelContainer*)resource->GetContentsPointer(n1, hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry());
|
||||
var animationName = @"hkaAnimationContainer"u8;
|
||||
fixed (byte* n2 = animationName)
|
||||
{
|
||||
var animContainer = (hkaAnimationContainer*)container->findObjectByName(n2, null);
|
||||
for (int i = 0; i < animContainer->Bindings.Length; i++)
|
||||
{
|
||||
var binding = animContainer->Bindings[i].ptr;
|
||||
var boneTransform = binding->TransformTrackToBoneIndices;
|
||||
string name = binding->OriginalSkeletonName.String! + "_" + i;
|
||||
output[name] = [];
|
||||
for (int boneIdx = 0; boneIdx < boneTransform.Length; boneIdx++)
|
||||
{
|
||||
output[name].Add((ushort)boneTransform[boneIdx]);
|
||||
}
|
||||
output[name].Sort();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Could not load havok file in {path}", tempHavokDataPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(tempHavokDataPathAnsi);
|
||||
File.Delete(tempHavokDataPath);
|
||||
}
|
||||
|
||||
_configService.Current.BonesDictionary[hash] = output;
|
||||
_configService.Save();
|
||||
return output;
|
||||
}
|
||||
|
||||
public Task<long> GetTrianglesFromGamePath(string gamePath)
|
||||
{
|
||||
if (_configService.Current.TriangleDictionary.TryGetValue(gamePath, out var cachedTris))
|
||||
return Task.FromResult(cachedTris);
|
||||
|
||||
_logger.LogDebug("Detected Model File {path}, calculating Tris", gamePath);
|
||||
var file = _luminaGameData.GetFile<MdlFile>(gamePath);
|
||||
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);
|
||||
if (path == null || !path.ResolvedFilepath.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase))
|
||||
return Task.FromResult((long)0);
|
||||
|
||||
var filePath = path.ResolvedFilepath;
|
||||
|
||||
_logger.LogDebug("Detected Model File {path}, calculating Tris", filePath);
|
||||
var file = _luminaGameData.GetFileFromDisk<MdlFile>(filePath);
|
||||
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", filePath, tris);
|
||||
_configService.Current.TriangleDictionary[hash] = tris;
|
||||
_configService.Save();
|
||||
return Task.FromResult(tris);
|
||||
}
|
||||
}
|
||||
@@ -76,8 +76,14 @@ public class DrawGroupPair : DrawPairBase
|
||||
}
|
||||
if (_pair.LastAppliedDataSize >= 0)
|
||||
{
|
||||
presenceText += UiSharedService.TooltipSeparator +
|
||||
"Loaded Mods Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true);
|
||||
presenceText += UiSharedService.TooltipSeparator;
|
||||
presenceText += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
|
||||
presenceText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true);
|
||||
if (_pair.LastAppliedDataTris >= 0)
|
||||
{
|
||||
presenceText += Environment.NewLine + "Triangle Count (excl. Vanilla): "
|
||||
+ (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris);
|
||||
}
|
||||
}
|
||||
}
|
||||
UiSharedService.AttachToolTip(presenceText);
|
||||
|
||||
@@ -74,8 +74,14 @@ public class DrawUserPair : DrawPairBase
|
||||
var visibleTooltip = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName! + Environment.NewLine + "Click to target this player";
|
||||
if (_pair.LastAppliedDataSize >= 0)
|
||||
{
|
||||
visibleTooltip += UiSharedService.TooltipSeparator +
|
||||
"Loaded Mods Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true);
|
||||
visibleTooltip += UiSharedService.TooltipSeparator;
|
||||
visibleTooltip += ((!_pair.IsVisible) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine;
|
||||
visibleTooltip += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true);
|
||||
if (_pair.LastAppliedDataTris >= 0)
|
||||
{
|
||||
visibleTooltip += Environment.NewLine + "Triangle Count (excl. Vanilla): "
|
||||
+ (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris);
|
||||
}
|
||||
}
|
||||
|
||||
UiSharedService.AttachToolTip(visibleTooltip);
|
||||
|
||||
@@ -15,7 +15,8 @@ public class PopupHandler : WindowMediatorSubscriberBase
|
||||
private readonly HashSet<IPopupHandler> _handlers;
|
||||
private IPopupHandler? _currentHandler = null;
|
||||
|
||||
public PopupHandler(ILogger<PopupHandler> logger, MareMediator mediator, IEnumerable<IPopupHandler> popupHandlers, PerformanceCollectorService performanceCollectorService)
|
||||
public PopupHandler(ILogger<PopupHandler> logger, MareMediator mediator, IEnumerable<IPopupHandler> popupHandlers,
|
||||
PerformanceCollectorService performanceCollectorService)
|
||||
: base(logger, mediator, "MarePopupHandler", performanceCollectorService)
|
||||
{
|
||||
Flags = ImGuiWindowFlags.NoBringToFrontOnFocus
|
||||
|
||||
@@ -158,7 +158,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TextUnformatted("Total size (compressed):");
|
||||
ImGui.SameLine();
|
||||
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.Separator();
|
||||
|
||||
using var tabbar = ImRaii.TabBar("objectSelection");
|
||||
@@ -195,6 +195,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (compressed):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
||||
ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {kvp.Value.Sum(f => f.Value.Triangles)}");
|
||||
|
||||
ImGui.Separator();
|
||||
if (_selectedObjectTab != kvp.Key)
|
||||
@@ -334,8 +335,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
|
||||
private void DrawTable(IGrouping<string, CharacterAnalyzer.FileDataEntry> fileGroup)
|
||||
{
|
||||
using var table = ImRaii.Table("Analysis", string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) ?
|
||||
(_enableBc7ConversionMode ? 7 : 6) : 5, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
||||
var tableColumns = string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal)
|
||||
? (_enableBc7ConversionMode ? 7 : 6)
|
||||
: (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) ? 6 : 5);
|
||||
using var table = ImRaii.Table("Analysis", tableColumns, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
||||
new Vector2(0, 300));
|
||||
if (!table.Success) return;
|
||||
ImGui.TableSetupColumn("Hash");
|
||||
@@ -348,6 +351,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
ImGui.TableSetupColumn("Format");
|
||||
if (_enableBc7ConversionMode) ImGui.TableSetupColumn("Convert to BC7");
|
||||
}
|
||||
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.TableSetupColumn("Triangles");
|
||||
}
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
@@ -441,6 +448,12 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
}
|
||||
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(item.Triangles.ToString());
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ public class GposeUi : WindowMediatorSubscriberBase
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_fileDialogManager = fileDialogManager;
|
||||
_configService = configService;
|
||||
|
||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => StartGpose());
|
||||
Mediator.Subscribe<GposeEndMessage>(this, (_) => EndGpose());
|
||||
IsOpen = _dalamudUtil.IsInGpose;
|
||||
|
||||
@@ -83,7 +83,11 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
// do nothing
|
||||
//
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user