Merge tag '0.9.21' into mare-classic
* tag '0.9.21': fix combat situations not redrawing every time after combat ends add more resilience to MCDF export and loading disable data application and scanner in combat fix bug add lock around adding to cached handled paths disable target in pvp add click to target in ui change tooltip for penumbra version to 0.8.2.1 add file storage validation add experimental resolving of data through penumbra adjust initial dialog to opt in/out into census with buttons
This commit is contained in:
@@ -65,7 +65,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
|
||||
public bool CurrentlyWorking { get; private set; } = false;
|
||||
public MareCharaFileHeader? LoadedCharaFile { get; private set; }
|
||||
|
||||
public async Task ApplyMareCharaFile(GameObject? charaTarget)
|
||||
public async Task ApplyMareCharaFile(GameObject? charaTarget, long expectedLength)
|
||||
{
|
||||
if (charaTarget == null) return;
|
||||
Dictionary<string, string> extractedFiles = new(StringComparer.Ordinal);
|
||||
@@ -80,8 +80,8 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
|
||||
using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression);
|
||||
using var reader = new BinaryReader(lz4Stream);
|
||||
MareCharaFileHeader.AdvanceReaderToData(reader);
|
||||
_logger.LogDebug("Applying to {chara}", charaTarget.Name.TextValue);
|
||||
extractedFiles = ExtractFilesFromCharaFile(LoadedCharaFile, reader);
|
||||
_logger.LogDebug("Applying to {chara}, expected length of contents: {exp}", charaTarget.Name.TextValue, expectedLength);
|
||||
extractedFiles = ExtractFilesFromCharaFile(LoadedCharaFile, reader, expectedLength);
|
||||
Dictionary<string, string> fileSwaps = new(StringComparer.Ordinal);
|
||||
foreach (var fileSwap in LoadedCharaFile.CharaFileData.FileSwaps)
|
||||
{
|
||||
@@ -125,14 +125,19 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failure to read MCDF");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CurrentlyWorking = false;
|
||||
|
||||
_logger.LogDebug("Clearing local files");
|
||||
foreach (var file in extractedFiles)
|
||||
foreach (var file in Directory.EnumerateFiles(_configService.Current.CacheFolder, "*.tmp"))
|
||||
{
|
||||
File.Delete(file.Value);
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +147,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
|
||||
LoadedCharaFile = null;
|
||||
}
|
||||
|
||||
public void LoadMareCharaFile(string filePath)
|
||||
public long LoadMareCharaFile(string filePath)
|
||||
{
|
||||
CurrentlyWorking = true;
|
||||
try
|
||||
@@ -179,6 +184,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
|
||||
|
||||
_logger.LogInformation("Expected length: {expected}", expectedLength);
|
||||
}
|
||||
return expectedLength;
|
||||
}
|
||||
finally { CurrentlyWorking = false; }
|
||||
}
|
||||
@@ -214,34 +220,38 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failure Saving Mare Chara File, deleting output");
|
||||
File.Delete(filePath);
|
||||
}
|
||||
finally { CurrentlyWorking = false; }
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ExtractFilesFromCharaFile(MareCharaFileHeader charaFileHeader, BinaryReader reader)
|
||||
private Dictionary<string, string> ExtractFilesFromCharaFile(MareCharaFileHeader charaFileHeader, BinaryReader reader, long expectedLength)
|
||||
{
|
||||
long totalRead = 0;
|
||||
Dictionary<string, string> gamePathToFilePath = new(StringComparer.Ordinal);
|
||||
foreach (var fileData in charaFileHeader.CharaFileData.Files)
|
||||
{
|
||||
var fileName = Path.Combine(_configService.Current.CacheFolder, "mare_" + _globalFileCounter++ + ".tmp");
|
||||
var length = fileData.Length;
|
||||
var bufferSize = 4 * 1024 * 1024;
|
||||
var length = (int)fileData.Length;
|
||||
var bufferSize = length;
|
||||
using var fs = File.OpenWrite(fileName);
|
||||
using var wr = new BinaryWriter(fs);
|
||||
int chunk = 0;
|
||||
while (length > 0)
|
||||
{
|
||||
if (length < bufferSize) bufferSize = (int)length;
|
||||
_logger.LogTrace("Reading chunk {chunk} {bufferSize}/{length} of {fileName}", chunk++, bufferSize, length, fileName);
|
||||
var buffer = reader.ReadBytes(bufferSize);
|
||||
wr.Write(length > bufferSize ? buffer : buffer.Take((int)length).ToArray());
|
||||
length -= bufferSize;
|
||||
}
|
||||
_logger.LogTrace("Reading {length} of {fileName}", length, fileName);
|
||||
var buffer = reader.ReadBytes(bufferSize);
|
||||
wr.Write(buffer);
|
||||
wr.Flush();
|
||||
wr.Close();
|
||||
if (buffer.Length == 0) throw new EndOfStreamException("Unexpected EOF");
|
||||
foreach (var path in fileData.GamePaths)
|
||||
{
|
||||
gamePathToFilePath[path] = fileName;
|
||||
_logger.LogTrace("{path} => {fileName}", path, fileName);
|
||||
}
|
||||
totalRead += length;
|
||||
_logger.LogTrace("Read {read}/{expected} bytes", totalRead, expectedLength);
|
||||
}
|
||||
|
||||
return gamePathToFilePath;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using MareSynchronos.API.Dto.User;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services;
|
||||
@@ -21,13 +20,12 @@ public class PairHandlerFactory
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly MareMediator _mareMediator;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||
|
||||
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
|
||||
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
||||
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
||||
FileCacheManager fileCacheManager, MareMediator mareMediator, MareConfigService mareConfigService)
|
||||
FileCacheManager fileCacheManager, MareMediator mareMediator)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||
@@ -38,13 +36,12 @@ public class PairHandlerFactory
|
||||
_hostApplicationLifetime = hostApplicationLifetime;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_mareMediator = mareMediator;
|
||||
_mareConfigService = mareConfigService;
|
||||
}
|
||||
|
||||
public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto)
|
||||
{
|
||||
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), onlineUserIdentDto, _gameObjectHandlerFactory,
|
||||
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
||||
_fileCacheManager, _mareMediator, _mareConfigService);
|
||||
_fileCacheManager, _mareMediator);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Data;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
@@ -25,11 +26,12 @@ public class PlayerDataFactory
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly ILogger<PlayerDataFactory> _logger;
|
||||
private readonly PerformanceCollectorService _performanceCollector;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly TransientResourceManager _transientResourceManager;
|
||||
|
||||
public PlayerDataFactory(ILogger<PlayerDataFactory> logger, DalamudUtilService dalamudUtil, IpcManager ipcManager,
|
||||
TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory,
|
||||
PerformanceCollectorService performanceCollector)
|
||||
PerformanceCollectorService performanceCollector, MareConfigService mareConfigService)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
@@ -37,7 +39,7 @@ public class PlayerDataFactory
|
||||
_transientResourceManager = transientResourceManager;
|
||||
_fileCacheManager = fileReplacementFactory;
|
||||
_performanceCollector = performanceCollector;
|
||||
|
||||
_mareConfigService = mareConfigService;
|
||||
_logger.LogTrace("Creating " + nameof(PlayerDataFactory));
|
||||
}
|
||||
|
||||
@@ -281,7 +283,7 @@ public class PlayerDataFactory
|
||||
AddReplacementsFromRenderModel(mdl, forwardResolve, reverseResolve);
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Player)
|
||||
if (objectKind == ObjectKind.Player && human->CharacterBase.GetModelType() == CharacterBase.ModelType.Human)
|
||||
{
|
||||
AddPlayerSpecificReplacements(human, forwardResolve, reverseResolve);
|
||||
}
|
||||
@@ -330,12 +332,18 @@ public class PlayerDataFactory
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
|
||||
// penumbra call, it's currently broken
|
||||
// var data = (await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false))![0];
|
||||
// if (data == null) throw new InvalidOperationException("Penumbra returned null data");
|
||||
|
||||
// gather static replacements from render model
|
||||
var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false);
|
||||
Dictionary<string, List<string>> resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false);
|
||||
IReadOnlyDictionary<string, string[]>? resolvedPaths;
|
||||
if (_mareConfigService.Current.ExperimentalUsePenumbraResourceTree)
|
||||
{
|
||||
resolvedPaths = (await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false))![0];
|
||||
if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data");
|
||||
}
|
||||
else
|
||||
{
|
||||
// gather static replacements from render model
|
||||
var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false);
|
||||
resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false);
|
||||
}
|
||||
previousData.FileReplacements[objectKind] =
|
||||
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance)
|
||||
.Where(p => p.HasFileReplacement).ToHashSet();
|
||||
@@ -353,6 +361,7 @@ public class PlayerDataFactory
|
||||
{
|
||||
foreach (var item in previousData.FileReplacements[objectKind].Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths))
|
||||
{
|
||||
_logger.LogDebug("Persisting {item}", item);
|
||||
_transientResourceManager.AddSemiTransientResource(objectKind, item);
|
||||
}
|
||||
}
|
||||
@@ -425,7 +434,7 @@ public class PlayerDataFactory
|
||||
return previousData;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, List<string>>> GetFileReplacementsFromPaths(HashSet<string> forwardResolve, HashSet<string> reverseResolve)
|
||||
private async Task<IReadOnlyDictionary<string, string[]>> GetFileReplacementsFromPaths(HashSet<string> forwardResolve, HashSet<string> reverseResolve)
|
||||
{
|
||||
var forwardPaths = forwardResolve.ToArray();
|
||||
var reversePaths = reverseResolve.ToArray();
|
||||
@@ -457,7 +466,7 @@ public class PlayerDataFactory
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedPaths;
|
||||
return resolvedPaths.ToDictionary(k => k.Key, k => k.Value.ToArray(), StringComparer.OrdinalIgnoreCase).AsReadOnly();
|
||||
}
|
||||
|
||||
private HashSet<string> ManageSemiTransientData(ObjectKind objectKind, IntPtr charaPointer)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using MareSynchronos.API.Dto.User;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Factories;
|
||||
using MareSynchronos.PlayerData.Pairs;
|
||||
using MareSynchronos.Services;
|
||||
@@ -19,10 +18,11 @@ namespace MareSynchronos.PlayerData.Handlers;
|
||||
|
||||
public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private sealed record CombatData(Guid ApplicationId, CharacterData CharacterData, bool Forced);
|
||||
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly FileDownloadManager _downloadManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly IHostApplicationLifetime _lifetime;
|
||||
@@ -37,6 +37,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
private bool _isVisible;
|
||||
private string _penumbraCollection;
|
||||
private bool _redrawOnNextApplication = false;
|
||||
private CombatData? _dataReceivedInCombat;
|
||||
public long LastAppliedDataSize { get; private set; }
|
||||
|
||||
public PairHandler(ILogger<PairHandler> logger, OnlineUserIdentDto onlineUser,
|
||||
@@ -44,8 +45,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
IpcManager ipcManager, FileDownloadManager transferManager,
|
||||
PluginWarningNotificationService pluginWarningNotificationManager,
|
||||
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
|
||||
FileCacheManager fileDbManager, MareMediator mediator,
|
||||
MareConfigService mareConfigService) : base(logger, mediator)
|
||||
FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator)
|
||||
{
|
||||
OnlineUser = onlineUser;
|
||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||
@@ -55,7 +55,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_lifetime = lifetime;
|
||||
_fileDbManager = fileDbManager;
|
||||
_mareConfigService = mareConfigService;
|
||||
_penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
|
||||
@@ -77,11 +76,26 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
});
|
||||
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) =>
|
||||
{
|
||||
if (msg.gameObjectHandler == _charaHandler)
|
||||
if (msg.GameObjectHandler == _charaHandler)
|
||||
{
|
||||
_redrawOnNextApplication = true;
|
||||
}
|
||||
});
|
||||
Mediator.Subscribe<CombatEndMessage>(this, (msg) =>
|
||||
{
|
||||
if (IsVisible && _dataReceivedInCombat != null)
|
||||
{
|
||||
ApplyCharacterData(_dataReceivedInCombat.ApplicationId,
|
||||
_dataReceivedInCombat.CharacterData, _dataReceivedInCombat.Forced);
|
||||
_dataReceivedInCombat = null;
|
||||
}
|
||||
});
|
||||
Mediator.Subscribe<CombatStartMessage>(this, _ =>
|
||||
{
|
||||
_dataReceivedInCombat = null;
|
||||
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate();
|
||||
_applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate();
|
||||
});
|
||||
|
||||
LastAppliedDataSize = -1;
|
||||
}
|
||||
@@ -107,12 +121,21 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
|
||||
public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false)
|
||||
{
|
||||
if (_dalamudUtil.IsInCombat)
|
||||
{
|
||||
Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase);
|
||||
_dataReceivedInCombat = new(applicationBase, characterData, forceApplyCustomization);
|
||||
SetUploading(isUploading: false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero))
|
||||
{
|
||||
Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}",
|
||||
applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero);
|
||||
var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger,
|
||||
this, forceApplyCustomization, forceApplyMods: false).Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles));
|
||||
this, forceApplyCustomization, forceApplyMods: false)
|
||||
.Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles));
|
||||
_forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null);
|
||||
_cachedData = characterData;
|
||||
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
|
||||
@@ -395,7 +418,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
{
|
||||
await _ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false);
|
||||
LastAppliedDataSize = -1;
|
||||
foreach (var path in moddedPaths.Select(v => new FileInfo(v.Value)).Where(p => p.Exists))
|
||||
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists))
|
||||
{
|
||||
LastAppliedDataSize += path.Length;
|
||||
}
|
||||
|
||||
@@ -48,8 +48,21 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
||||
_playerRelatedObjects[ObjectKind.Companion] = gameObjectHandlerFactory.Create(ObjectKind.Companion, () => dalamudUtil.GetCompanion(), isWatched: true)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) =>
|
||||
{
|
||||
if (msg.GameObjectHandler != _playerRelatedObjects[ObjectKind.Player]) return;
|
||||
|
||||
Logger.LogTrace("Removing pet data for {obj}", msg.GameObjectHandler);
|
||||
_playerData.FileReplacements.Remove(ObjectKind.Pet);
|
||||
_playerData.GlamourerString.Remove(ObjectKind.Pet);
|
||||
_playerData.CustomizePlusScale.Remove(ObjectKind.Pet);
|
||||
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||
});
|
||||
|
||||
Mediator.Subscribe<ClearCacheForObjectMessage>(this, (msg) =>
|
||||
{
|
||||
// ignore pets
|
||||
if (msg.ObjectToCreateFor == _playerRelatedObjects[ObjectKind.Pet]) return;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor);
|
||||
|
||||
Reference in New Issue
Block a user