fix an issue where animations get removed/added through mod changes don't reload the config
potentially fix mdl/mtrl/tex issues of recorded transients why did this even crash to begin with dunno what's wrong with pets (fuck pets, not literally)
This commit is contained in:
@@ -17,8 +17,8 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal);
|
||||
private readonly TransientConfigService _configurationService;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
|
||||
private readonly string[] _fileTypesToHandleRecording = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk", "tex", "mdl", "mtrl"];
|
||||
private readonly string[] _handledFileTypes = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
|
||||
private readonly string[] _handledRecordingFileTypes = ["tex", "mdl", "mtrl"];
|
||||
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
|
||||
private ConcurrentDictionary<IntPtr, ObjectKind> _cachedFrameAddresses = [];
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<string>>? _semiTransientResources = null;
|
||||
@@ -80,19 +80,27 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
|
||||
public void CleanUpSemiTransientResources(ObjectKind objectKind, List<FileReplacement>? fileReplacement = null)
|
||||
{
|
||||
if (SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? value))
|
||||
{
|
||||
if (!SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? value))
|
||||
return;
|
||||
|
||||
if (fileReplacement == null)
|
||||
{
|
||||
value.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
int removedPaths = 0;
|
||||
foreach (var replacement in fileReplacement.Where(p => !p.HasFileReplacement).SelectMany(p => p.GamePaths).ToList())
|
||||
{
|
||||
removedPaths++;
|
||||
PlayerConfig.RemovePath(replacement);
|
||||
value.Remove(replacement);
|
||||
}
|
||||
|
||||
if (removedPaths > 0)
|
||||
{
|
||||
Logger.LogTrace("Removed {amount} of SemiTransient paths during CleanUp, Saving from {name}", removedPaths, nameof(CleanUpSemiTransientResources));
|
||||
// force reload semi transient resources
|
||||
_configurationService.Save();
|
||||
}
|
||||
}
|
||||
@@ -124,27 +132,33 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
semiTransientResources.Add(gamePath);
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Player && newlyAddedGamePaths.Any())
|
||||
bool saveConfig = false;
|
||||
if (objectKind == ObjectKind.Player && newlyAddedGamePaths.Count != 0)
|
||||
{
|
||||
saveConfig = true;
|
||||
foreach (var item in newlyAddedGamePaths.Where(f => !string.IsNullOrEmpty(f)))
|
||||
{
|
||||
PlayerConfig.AddOrElevate(_dalamudUtil.ClassJobId, item);
|
||||
}
|
||||
|
||||
_configurationService.Save();
|
||||
}
|
||||
else if (objectKind == ObjectKind.Pet && newlyAddedGamePaths.Any())
|
||||
{
|
||||
foreach (var item in newlyAddedGamePaths.Where(f => !string.IsNullOrEmpty(f)))
|
||||
else if (objectKind == ObjectKind.Pet && newlyAddedGamePaths.Count != 0)
|
||||
{
|
||||
saveConfig = true;
|
||||
|
||||
if (!PlayerConfig.JobSpecificPetCache.TryGetValue(_dalamudUtil.ClassJobId, out var petPerma))
|
||||
{
|
||||
PlayerConfig.JobSpecificPetCache[_dalamudUtil.ClassJobId] = petPerma = [];
|
||||
}
|
||||
|
||||
foreach (var item in newlyAddedGamePaths.Where(f => !string.IsNullOrEmpty(f)))
|
||||
{
|
||||
petPerma.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (saveConfig)
|
||||
{
|
||||
Logger.LogTrace("Saving transient.json from {method}", nameof(PersistTransientResources));
|
||||
_configurationService.Save();
|
||||
}
|
||||
|
||||
@@ -159,6 +173,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
PlayerConfig.RemovePath(path);
|
||||
Logger.LogTrace("Saving transient.json from {method}", nameof(RemoveTransientResource));
|
||||
_configurationService.Save();
|
||||
}
|
||||
}
|
||||
@@ -169,18 +184,24 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
if (SemiTransientResources.TryGetValue(objectKind, out var semiTransient) && semiTransient != null && semiTransient.Contains(item))
|
||||
return false;
|
||||
|
||||
if (!TransientResources.TryGetValue(objectKind, out HashSet<string>? value))
|
||||
if (!TransientResources.TryGetValue(objectKind, out HashSet<string>? transientResource))
|
||||
{
|
||||
value = new HashSet<string>(StringComparer.Ordinal);
|
||||
TransientResources[objectKind] = value;
|
||||
transientResource = new HashSet<string>(StringComparer.Ordinal);
|
||||
TransientResources[objectKind] = transientResource;
|
||||
}
|
||||
|
||||
value.Add(item.ToLowerInvariant());
|
||||
return true;
|
||||
return transientResource.Add(item.ToLowerInvariant());
|
||||
}
|
||||
|
||||
internal void ClearTransientPaths(ObjectKind objectKind, List<string> list)
|
||||
{
|
||||
// ignore all recording only datatypes
|
||||
int recordingOnlyRemoved = list.RemoveAll(entry => _handledRecordingFileTypes.Any(ext => entry.EndsWith(ext, StringComparison.OrdinalIgnoreCase)));
|
||||
if (recordingOnlyRemoved > 0)
|
||||
{
|
||||
Logger.LogTrace("Ignored {0} game paths when clearing transients", recordingOnlyRemoved);
|
||||
}
|
||||
|
||||
if (TransientResources.TryGetValue(objectKind, out var set))
|
||||
{
|
||||
foreach (var file in set.Where(p => list.Contains(p, StringComparer.OrdinalIgnoreCase)))
|
||||
@@ -197,7 +218,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
{
|
||||
foreach (var file in semiset.Where(p => list.Contains(p, StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
Logger.LogTrace("Removing From Transient: {file}", file);
|
||||
Logger.LogTrace("Removing From SemiTransient: {file}", file);
|
||||
PlayerConfig.RemovePath(file);
|
||||
}
|
||||
|
||||
@@ -206,6 +227,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
if (removed > 0)
|
||||
{
|
||||
reloadSemiTransient = true;
|
||||
Logger.LogTrace("Saving transient.json from {method}", nameof(ClearTransientPaths));
|
||||
_configurationService.Save();
|
||||
}
|
||||
}
|
||||
@@ -295,10 +317,13 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
|
||||
// ignore files that are the same
|
||||
var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase);
|
||||
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
|
||||
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore files to not handle
|
||||
var handledTypes = IsTransientRecording ? _fileTypesToHandleRecording : _fileTypesToHandle;
|
||||
var handledTypes = IsTransientRecording ? _handledRecordingFileTypes.Concat(_handledFileTypes) : _handledFileTypes;
|
||||
if (!handledTypes.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
lock (_cacheAdditionLock)
|
||||
@@ -320,31 +345,36 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
|
||||
// ^ all of the code above is just to sanitize the data
|
||||
|
||||
if (!TransientResources.TryGetValue(objectKind, out HashSet<string>? value))
|
||||
if (!TransientResources.TryGetValue(objectKind, out HashSet<string>? transientResources))
|
||||
{
|
||||
value = new(StringComparer.OrdinalIgnoreCase);
|
||||
TransientResources[objectKind] = value;
|
||||
transientResources = new(StringComparer.OrdinalIgnoreCase);
|
||||
TransientResources[objectKind] = transientResources;
|
||||
}
|
||||
|
||||
var owner = _playerRelatedPointers.FirstOrDefault(f => f.Address == gameObjectAddress);
|
||||
bool alreadyTransient = false;
|
||||
|
||||
if (value.Contains(replacedGamePath)
|
||||
|| SemiTransientResources.SelectMany(k => k.Value).Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase)))
|
||||
bool transientContains = transientResources.Contains(replacedGamePath);
|
||||
bool semiTransientContains = SemiTransientResources.SelectMany(k => k.Value).Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase));
|
||||
if (transientContains || semiTransientContains)
|
||||
{
|
||||
if (!IsTransientRecording)
|
||||
Logger.LogTrace("Not adding {replacedPath} : {filePath}", replacedGamePath, filePath);
|
||||
Logger.LogTrace("Not adding {replacedPath} => {filePath}, Reason: Transient: {contains}, SemiTransient: {contains2}", replacedGamePath, filePath,
|
||||
transientContains, semiTransientContains);
|
||||
alreadyTransient = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsTransientRecording)
|
||||
{
|
||||
value.Add(replacedGamePath);
|
||||
bool isAdded = transientResources.Add(replacedGamePath);
|
||||
if (isAdded)
|
||||
{
|
||||
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, owner?.ToString() ?? gameObjectAddress.ToString("X"), filePath);
|
||||
SendTransients(gameObjectAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (owner != null && IsTransientRecording)
|
||||
{
|
||||
|
||||
@@ -80,11 +80,8 @@ public sealed class IpcCallerHonorific : IIpcCaller
|
||||
public async Task<string> GetTitle()
|
||||
{
|
||||
if (!APIAvailable) return string.Empty;
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
string title = _honorificGetLocalCharacterTitle.InvokeFunc();
|
||||
string title = await _dalamudUtil.RunOnFrameworkThread(() => _honorificGetLocalCharacterTitle.InvokeFunc()).ConfigureAwait(false);
|
||||
return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title));
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SetTitleAsync(IntPtr character, string honorificDataB64)
|
||||
|
||||
@@ -112,22 +112,22 @@ public class PlayerDataFactory
|
||||
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
||||
}
|
||||
|
||||
private async Task<CharacterData> CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||
private async Task<CharacterData> CreateCharacterData(CharacterData data, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||
{
|
||||
var objectKind = playerRelatedObject.ObjectKind;
|
||||
|
||||
_logger.LogDebug("Building character data for {obj}", playerRelatedObject);
|
||||
|
||||
if (!previousData.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? value))
|
||||
if (!data.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? value))
|
||||
{
|
||||
previousData.FileReplacements[objectKind] = new(FileReplacementComparer.Instance);
|
||||
data.FileReplacements[objectKind] = new(FileReplacementComparer.Instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.Clear();
|
||||
}
|
||||
|
||||
previousData.CustomizePlusScale.Remove(objectKind);
|
||||
data.CustomizePlusScale.Remove(objectKind);
|
||||
|
||||
// wait until chara is not drawing and present so nothing spontaneously explodes
|
||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: token).ConfigureAwait(false);
|
||||
@@ -151,13 +151,13 @@ public class PlayerDataFactory
|
||||
resolvedPaths = await _ipcManager.Penumbra.GetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false);
|
||||
if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data");
|
||||
|
||||
previousData.FileReplacements[objectKind] =
|
||||
data.FileReplacements[objectKind] =
|
||||
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance)
|
||||
.Where(p => p.HasFileReplacement).ToHashSet();
|
||||
previousData.FileReplacements[objectKind].RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||
data.FileReplacements[objectKind].RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||
|
||||
_logger.LogDebug("== Static Replacements ==");
|
||||
foreach (var replacement in previousData.FileReplacements[objectKind].Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase))
|
||||
foreach (var replacement in data.FileReplacements[objectKind].Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogDebug("=> {repl}", replacement);
|
||||
}
|
||||
@@ -168,7 +168,7 @@ public class PlayerDataFactory
|
||||
// or we get into redraw city for every change and nothing works properly
|
||||
if (objectKind == ObjectKind.Pet)
|
||||
{
|
||||
foreach (var item in previousData.FileReplacements[ObjectKind.Pet].Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths))
|
||||
foreach (var item in data.FileReplacements[ObjectKind.Pet].Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths))
|
||||
{
|
||||
if (_transientResourceManager.AddTransientResource(objectKind, item))
|
||||
{
|
||||
@@ -176,14 +176,14 @@ public class PlayerDataFactory
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogTrace("Clearing {count} Static Replacements for Pet", previousData.FileReplacements[ObjectKind.Pet].Count);
|
||||
previousData.FileReplacements[ObjectKind.Pet].Clear();
|
||||
_logger.LogTrace("Clearing {count} Static Replacements for Pet", data.FileReplacements[ObjectKind.Pet].Count);
|
||||
data.FileReplacements[ObjectKind.Pet].Clear();
|
||||
}
|
||||
|
||||
_logger.LogDebug("Handling transient update for {obj}", playerRelatedObject);
|
||||
|
||||
// remove all potentially gathered paths from the transient resource manager that are resolved through static resolving
|
||||
_transientResourceManager.ClearTransientPaths(objectKind, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
|
||||
_transientResourceManager.ClearTransientPaths(objectKind, data.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
|
||||
|
||||
// get all remaining paths and resolve them
|
||||
var transientPaths = ManageSemiTransientData(objectKind);
|
||||
@@ -193,41 +193,43 @@ public class PlayerDataFactory
|
||||
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal))
|
||||
{
|
||||
_logger.LogDebug("=> {repl}", replacement);
|
||||
previousData.FileReplacements[objectKind].Add(replacement);
|
||||
data.FileReplacements[objectKind].Add(replacement);
|
||||
}
|
||||
|
||||
// clean up all semi transient resources that don't have any file replacement (aka null resolve)
|
||||
_transientResourceManager.CleanUpSemiTransientResources(objectKind, [.. previousData.FileReplacements[objectKind]]);
|
||||
_transientResourceManager.CleanUpSemiTransientResources(objectKind, [.. data.FileReplacements[objectKind]]);
|
||||
|
||||
// make sure we only return data that actually has file replacements
|
||||
foreach (var item in previousData.FileReplacements)
|
||||
foreach (var item in data.FileReplacements)
|
||||
{
|
||||
previousData.FileReplacements[item.Key] = new HashSet<FileReplacement>(item.Value.Where(v => v.HasFileReplacement).OrderBy(v => v.ResolvedPath, StringComparer.Ordinal), FileReplacementComparer.Instance);
|
||||
data.FileReplacements[item.Key] = new HashSet<FileReplacement>(item.Value.Where(v => v.HasFileReplacement).OrderBy(v => v.ResolvedPath, StringComparer.Ordinal), FileReplacementComparer.Instance);
|
||||
}
|
||||
|
||||
// gather up data from ipc
|
||||
previousData.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations();
|
||||
data.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations();
|
||||
Task<string> getHeelsOffset = _ipcManager.Heels.GetOffsetAsync();
|
||||
Task<string> getGlamourerData = _ipcManager.Glamourer.GetCharacterCustomizationAsync(playerRelatedObject.Address);
|
||||
Task<string?> getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address);
|
||||
Task<string> getHonorificTitle = _ipcManager.Honorific.GetTitle();
|
||||
previousData.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false);
|
||||
_logger.LogDebug("Glamourer is now: {data}", previousData.GlamourerString[playerRelatedObject.ObjectKind]);
|
||||
data.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false);
|
||||
_logger.LogDebug("Glamourer is now: {data}", data.GlamourerString[playerRelatedObject.ObjectKind]);
|
||||
var customizeScale = await getCustomizeData.ConfigureAwait(false);
|
||||
previousData.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty;
|
||||
_logger.LogDebug("Customize is now: {data}", previousData.CustomizePlusScale[playerRelatedObject.ObjectKind]);
|
||||
previousData.HonorificData = await getHonorificTitle.ConfigureAwait(false);
|
||||
_logger.LogDebug("Honorific is now: {data}", previousData.HonorificData);
|
||||
previousData.HeelsData = await getHeelsOffset.ConfigureAwait(false);
|
||||
_logger.LogDebug("Heels is now: {heels}", previousData.HeelsData);
|
||||
data.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty;
|
||||
_logger.LogDebug("Customize is now: {data}", data.CustomizePlusScale[playerRelatedObject.ObjectKind]);
|
||||
data.HonorificData = await getHonorificTitle.ConfigureAwait(false);
|
||||
_logger.LogDebug("Honorific is now: {data}", data.HonorificData);
|
||||
data.HeelsData = await getHeelsOffset.ConfigureAwait(false);
|
||||
_logger.LogDebug("Heels is now: {heels}", data.HeelsData);
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
previousData.PetNamesData = _ipcManager.PetNames.GetLocalNames();
|
||||
_logger.LogDebug("Pet Nicknames is now: {petnames}", previousData.PetNamesData);
|
||||
previousData.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty;
|
||||
data.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty;
|
||||
_logger.LogDebug("Moodles is now: {moodles}", data.MoodlesData);
|
||||
|
||||
data.PetNamesData = _ipcManager.PetNames.GetLocalNames();
|
||||
_logger.LogDebug("Pet Nicknames is now: {petnames}", data.PetNamesData);
|
||||
}
|
||||
|
||||
if (previousData.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? fileReplacements))
|
||||
if (data.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? fileReplacements))
|
||||
{
|
||||
var toCompute = fileReplacements.Where(f => !f.IsFileSwap).ToArray();
|
||||
_logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length);
|
||||
@@ -247,7 +249,7 @@ public class PlayerDataFactory
|
||||
{
|
||||
try
|
||||
{
|
||||
await VerifyPlayerAnimationBones(boneIndices, previousData, objectKind).ConfigureAwait(false);
|
||||
await VerifyPlayerAnimationBones(boneIndices, data, objectKind).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -257,7 +259,7 @@ public class PlayerDataFactory
|
||||
|
||||
_logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start.Ticks).TotalMilliseconds);
|
||||
|
||||
return previousData;
|
||||
return data;
|
||||
}
|
||||
|
||||
private async Task VerifyPlayerAnimationBones(Dictionary<string, List<ushort>>? boneIndices, CharacterData previousData, ObjectKind objectKind)
|
||||
|
||||
Reference in New Issue
Block a user