merge 0.4.0 into main

This commit is contained in:
Stanley Dimant
2022-09-10 15:31:03 +02:00
20 changed files with 709 additions and 166 deletions

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
@@ -22,16 +24,23 @@ public class CharacterDataFactory
{
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly TransientResourceManager transientResourceManager;
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager)
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager)
{
Logger.Verbose("Creating " + nameof(CharacterDataFactory));
_dalamudUtil = dalamudUtil;
_ipcManager = ipcManager;
this.transientResourceManager = transientResourceManager;
}
public unsafe CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token)
private unsafe bool CheckForPointer(IntPtr playerPointer)
{
return playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null;
}
public CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token)
{
if (!_ipcManager.Initialized)
{
@@ -41,7 +50,7 @@ public class CharacterDataFactory
bool pointerIsZero = true;
try
{
pointerIsZero = playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null;
pointerIsZero = CheckForPointer(playerPointer);
}
catch (Exception ex)
{
@@ -144,7 +153,6 @@ public class CharacterDataFactory
return;
}
//Logger.Verbose("Adding File Replacement for Material " + fileName);
var mtrlPath = fileName.Split("|")[2];
if (cache.FileReplacements.ContainsKey(objectKind))
@@ -179,12 +187,28 @@ public class CharacterDataFactory
}
}
private void AddReplacement(string varPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = false)
{
if (varPath.IsNullOrEmpty()) return;
if (cache.FileReplacements.ContainsKey(objectKind))
{
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath)))
{
return;
}
}
var variousReplacement = CreateFileReplacement(varPath, doNotReverseResolve);
DebugPrint(variousReplacement, objectKind, "Various", inheritanceLevel);
cache.AddFileReplacement(objectKind, variousReplacement);
}
private void AddReplacementsFromTexture(string texPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = true)
{
if (string.IsNullOrEmpty(texPath)) return;
//Logger.Verbose("Adding File Replacement for Texture " + texPath);
if (cache.FileReplacements.ContainsKey(objectKind))
{
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath)))
@@ -215,14 +239,16 @@ public class CharacterDataFactory
previousData.FileReplacements[objectKind].Clear();
}
Stopwatch st = Stopwatch.StartNew();
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
while (!_dalamudUtil.IsObjectPresent(chara))
{
Logger.Verbose("Character is null but it shouldn't be, waiting");
Thread.Sleep(50);
}
_dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer);
_dalamudUtil.WaitWhileCharacterIsDrawing(objectKind.ToString(), charaPointer);
Stopwatch st = Stopwatch.StartNew();
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
@@ -242,57 +268,131 @@ public class CharacterDataFactory
AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0);
}
foreach (var item in previousData.FileReplacements[objectKind])
{
transientResourceManager.RemoveTransientResource(charaPointer, item);
}
if (objectKind == ObjectKind.Player)
{
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
if ((IntPtr)weaponObject != IntPtr.Zero)
{
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0);
if (weaponObject->NextSibling != (IntPtr)weaponObject)
{
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1);
}
}
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData);
try
{
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, false);
}
catch
{
Logger.Warn("Could not get Decal data");
}
try
{
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, false);
}
catch
{
Logger.Warn("Could not get Legacy Body Decal Data");
}
AddPlayerSpecificReplacements(previousData, objectKind, charaPointer, human);
}
if (objectKind == ObjectKind.Pet)
{
foreach (var item in previousData.FileReplacements[objectKind])
{
transientResourceManager.AddSemiTransientResource(objectKind, item);
}
previousData.FileReplacements[objectKind].Clear();
}
ManageSemiTransientData(previousData, objectKind, charaPointer);
st.Stop();
Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed);
return previousData;
}
private unsafe void ManageSemiTransientData(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer)
{
transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement);
foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind))
{
if (!previousData.FileReplacements.ContainsKey(objectKind))
{
previousData.FileReplacements.Add(objectKind, new());
}
if (!previousData.FileReplacements[objectKind].Any(k => k.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant()))
{
if (_ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant() == item.GamePaths.First().ToLowerInvariant())
{
transientResourceManager.RemoveTransientResource(charaPointer, item);
}
else
{
Logger.Verbose("Found semi transient resource: " + item);
previousData.FileReplacements[objectKind].Add(item);
}
}
}
}
private unsafe void AddPlayerSpecificReplacements(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer, Human* human)
{
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
if ((IntPtr)weaponObject != IntPtr.Zero)
{
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0);
foreach (var item in previousData.FileReplacements[objectKind])
{
transientResourceManager.RemoveTransientResource(charaPointer, item);
}
foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject))
{
Logger.Verbose("Found transient weapon resource: " + item);
AddReplacement(item, objectKind, previousData, 1, true);
}
if (weaponObject->NextSibling != (IntPtr)weaponObject)
{
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1);
foreach (var item in previousData.FileReplacements[objectKind])
{
transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item);
}
foreach (var item in transientResourceManager.GetTransientResources((IntPtr)offHandWeapon))
{
Logger.Verbose("Found transient offhand weapon resource: " + item);
AddReplacement(item, objectKind, previousData, 1, true);
}
}
}
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData);
try
{
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, false);
}
catch
{
Logger.Warn("Could not get Decal data");
}
try
{
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, false);
}
catch
{
Logger.Warn("Could not get Legacy Body Decal Data");
}
foreach (var item in previousData.FileReplacements[objectKind])
{
transientResourceManager.RemoveTransientResource(charaPointer, item);
}
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
}
private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache)
{
string raceSexIdString = raceSexId.ToString("0000");
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
//Logger.Verbose("Adding File Replacement for Skeleton " + skeletonPath);
var replacement = CreateFileReplacement(skeletonPath, true);
cache.AddFileReplacement(objectKind, replacement);

View File

@@ -12,6 +12,7 @@ using MareSynchronos.Interop;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using Newtonsoft.Json;
namespace MareSynchronos.Managers;
@@ -40,7 +41,7 @@ public class CachedPlayer
}
}
private bool _isDisposed = false;
private bool _isDisposed = true;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private string _lastGlamourerData = string.Empty;
@@ -142,6 +143,7 @@ public class CachedPlayer
{
Dictionary<string, string> moddedPaths;
int attempts = 0;
//Logger.Verbose(JsonConvert.SerializeObject(_cachedData, Formatting.Indented));
while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10)
{
Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind);
@@ -162,19 +164,9 @@ public class CachedPlayer
ApplyBaseData(moddedPaths);
}
if (_dalamudUtil.IsInGpose)
{
Logger.Verbose("Player is in GPose, waiting");
while (_dalamudUtil.IsInGpose)
{
await Task.Delay(TimeSpan.FromSeconds(0.5));
downloadToken.ThrowIfCancellationRequested();
}
}
foreach (var kind in objectKind)
{
ApplyCustomizationData(kind);
ApplyCustomizationData(kind, downloadToken);
}
}, downloadToken).ContinueWith(task =>
{
@@ -194,7 +186,7 @@ public class CachedPlayer
try
{
using var db = new FileCacheContext();
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value).ToList())
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var gamePath in item.GamePaths)
{
@@ -205,10 +197,20 @@ public class CachedPlayer
}
else
{
Logger.Verbose("Missing file: " + item.Hash);
missingFiles.Add(item);
}
}
}
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var gamePath in item.GamePaths)
{
Logger.Verbose("Adding file swap for " + gamePath + ":" + item.FileSwapPath);
moddedDictionary[gamePath] = item.FileSwapPath;
}
}
}
catch (Exception ex)
{
@@ -224,14 +226,16 @@ public class CachedPlayer
_ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData);
}
private unsafe void ApplyCustomizationData(ObjectKind objectKind)
private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct)
{
if (PlayerCharacter == IntPtr.Zero) return;
_cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData);
if (objectKind == ObjectKind.Player)
{
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter);
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, ct);
ct.ThrowIfCancellationRequested();
_ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter);
RequestedPenumbraRedraw = true;
Logger.Debug(
$"Request Redraw for {PlayerName}");
@@ -250,6 +254,8 @@ public class CachedPlayer
if (minionOrMount != null)
{
Logger.Debug($"Request Redraw for Minion/Mount");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, obj: (IntPtr)minionOrMount);
@@ -262,17 +268,29 @@ public class CachedPlayer
}
else if (objectKind == ObjectKind.Pet)
{
int tick = 16;
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Pet");
var totalWait = 0;
var newPet = IntPtr.Zero;
const int maxWait = 3000;
Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms");
do
{
Thread.Sleep(tick);
totalWait += tick;
newPet = _dalamudUtil.GetPet(PlayerCharacter);
} while (newPet == pet && totalWait < maxWait);
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, pet);
_ipcManager.GlamourerApplyAll(glamourerData, newPet);
}
else
{
_ipcManager.PenumbraRedraw(pet);
_ipcManager.PenumbraRedraw(newPet);
}
}
}
@@ -282,6 +300,8 @@ public class CachedPlayer
if (companion != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Companion");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, companion);
@@ -348,6 +368,8 @@ public class CachedPlayer
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
if (PlayerCharacter != IntPtr.Zero)
{
foreach (var item in _cachedData.FileReplacements)
@@ -355,9 +377,6 @@ public class CachedPlayer
RevertCustomizationData(item.Key);
}
}
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
}
catch (Exception ex)
{
@@ -374,6 +393,7 @@ public class CachedPlayer
public void InitializePlayer(IntPtr character, string name, CharacterCacheDto? cache)
{
if (!_isDisposed) return;
Logger.Debug("Initializing Player " + this + " has cache: " + (cache != null));
IsVisible = true;
PlayerName = name;
@@ -426,14 +446,16 @@ public class CachedPlayer
_penumbraRedrawEventTask = Task.Run(() =>
{
PlayerCharacter = address;
using var cts = new CancellationTokenSource();
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, cts.Token);
cts.Dispose();
cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter, cts.Token);
if (RequestedPenumbraRedraw == false)
{
Logger.Debug("Unauthorized character change detected");
ApplyCustomizationData(ObjectKind.Player);
ApplyCustomizationData(ObjectKind.Player, cts.Token);
}
else
{
@@ -441,6 +463,7 @@ public class CachedPlayer
Logger.Debug(
$"Penumbra Redraw done for {PlayerName}");
}
cts.Dispose();
});
}

View File

@@ -23,6 +23,7 @@ namespace MareSynchronos.Managers
private readonly CancellationTokenSource _rescanTaskCancellationTokenSource = new();
private CancellationTokenSource _rescanTaskRunCancellationTokenSource = new();
private CancellationTokenSource? _scanCancellationTokenSource;
private object modifiedFilesLock = new object();
public FileCacheManager(IpcManager ipcManager, Configuration pluginConfiguration)
{
Logger.Verbose("Creating " + nameof(FileCacheManager));
@@ -48,7 +49,7 @@ namespace MareSynchronos.Managers
public string WatchedPenumbraDirectory => (_penumbraDirWatcher?.EnableRaisingEvents ?? false) ? _penumbraDirWatcher!.Path : "Not watched";
public FileCache? Create(string file, CancellationToken token)
public FileCache? Create(string file, CancellationToken? token)
{
FileInfo fileInfo = new(file);
int attempt = 0;
@@ -56,7 +57,7 @@ namespace MareSynchronos.Managers
{
Thread.Sleep(1000);
Logger.Debug("Waiting for file release " + fileInfo.FullName + " attempt " + attempt);
token.ThrowIfCancellationRequested();
token?.ThrowIfCancellationRequested();
}
if (attempt >= 10) return null;
@@ -64,7 +65,7 @@ namespace MareSynchronos.Managers
var sha1Hash = Crypto.GetFileHash(fileInfo.FullName);
return new FileCache()
{
Filepath = fileInfo.FullName.ToLower(),
Filepath = fileInfo.FullName.ToLowerInvariant(),
Hash = sha1Hash,
LastModifiedDate = fileInfo.LastWriteTimeUtc.Ticks.ToString(),
};
@@ -142,7 +143,10 @@ namespace MareSynchronos.Managers
private void OnModified(object sender, FileSystemEventArgs e)
{
_modifiedFiles.Add(e.FullPath);
lock (modifiedFilesLock)
{
_modifiedFiles.Add(e.FullPath);
}
_ = StartRescan();
}
@@ -189,21 +193,28 @@ namespace MareSynchronos.Managers
Logger.Debug("File changes detected");
if (!_modifiedFiles.Any()) return;
lock (modifiedFilesLock)
{
if (!_modifiedFiles.Any()) return;
}
_rescanTask = Task.Run(async () =>
{
var listCopy = _modifiedFiles.ToList();
_modifiedFiles.Clear();
List<string> modifiedFilesCopy = new List<string>();
lock (modifiedFilesLock)
{
modifiedFilesCopy = _modifiedFiles.ToList();
_modifiedFiles.Clear();
}
await using var db = new FileCacheContext();
foreach (var item in listCopy.Distinct())
foreach (var item in modifiedFilesCopy.Distinct())
{
var fi = new FileInfo(item);
if (!fi.Exists)
{
PluginLog.Verbose("Removed: " + item);
db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == item.ToLower()));
db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == item.ToLowerInvariant()));
}
else
{
@@ -211,7 +222,7 @@ namespace MareSynchronos.Managers
var fileCache = Create(item, _rescanTaskCancellationTokenSource.Token);
if (fileCache != null)
{
db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == fileCache.Filepath.ToLower()));
db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == fileCache.Filepath.ToLowerInvariant()));
await db.AddAsync(fileCache, _rescanTaskCancellationTokenSource.Token);
}
}

View File

@@ -11,6 +11,8 @@ using System.Collections.Concurrent;
namespace MareSynchronos.Managers
{
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
public class IpcManager : IDisposable
{
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
@@ -33,6 +35,14 @@ namespace MareSynchronos.Managers
private readonly ICallGateSubscriber<string, string[]>? _reverseResolvePlayer;
private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int>
_penumbraSetTemporaryMod;
private readonly ICallGateSubscriber<IntPtr, string, string, object?> _penumbraGameObjectResourcePathResolved;
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
private readonly ICallGateSubscriber<float> _heelsGetOffset;
private readonly ICallGateSubscriber<float, object?> _heelsOffsetUpdate;
private readonly ICallGateSubscriber<GameObject, float, object?> _heelsRegisterPlayer;
private readonly ICallGateSubscriber<GameObject, object?> _heelsUnregisterPlayer;
private readonly DalamudUtil _dalamudUtil;
private readonly ConcurrentQueue<Action> actionQueue = new();
@@ -58,7 +68,9 @@ namespace MareSynchronos.Managers
_glamourerApplyOnlyCustomization = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyCustomizationToCharacter");
_glamourerApplyOnlyEquipment = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyEquipmentToCharacter");
_glamourerRevertCustomization = pi.GetIpcSubscriber<GameObject?, object>("Glamourer.RevertCharacter");
_penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber<IntPtr, string, string, object?>("Penumbra.GameObjectResourcePathResolved");
_penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded);
_penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
_penumbraInit.Subscribe(PenumbraInit);
_penumbraDispose.Subscribe(PenumbraDispose);
@@ -82,6 +94,15 @@ namespace MareSynchronos.Managers
_dalamudUtil.FrameworkUpdate += HandleActionQueue;
}
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
{
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0)
{
PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2);
//Logger.Debug($"Resolved {ptr:X}: {arg1} => {arg2}");
}
}
private void HandleActionQueue()
{
if (actionQueue.TryDequeue(out var action))
@@ -95,6 +116,8 @@ namespace MareSynchronos.Managers
public event VoidDelegate? PenumbraInitialized;
public event VoidDelegate? PenumbraDisposed;
public event PenumbraRedrawEvent? PenumbraRedrawEvent;
public event HeelsOffsetChange? HeelsOffsetChangeEvent;
public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
public bool Initialized => CheckPenumbraApi();
public bool CheckGlamourerApi()
@@ -113,7 +136,7 @@ namespace MareSynchronos.Managers
{
try
{
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 11 };
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 };
}
catch
{
@@ -125,12 +148,43 @@ namespace MareSynchronos.Managers
{
Logger.Verbose("Disposing " + nameof(IpcManager));
int totalSleepTime = 0;
while (actionQueue.Count > 0 && totalSleepTime < 2000)
{
Logger.Verbose("Waiting for actionqueue to clear...");
System.Threading.Thread.Sleep(16);
totalSleepTime += 16;
}
Logger.Verbose("Action queue clear or not, disposing");
_dalamudUtil.FrameworkUpdate -= HandleActionQueue;
actionQueue.Clear();
_penumbraDispose.Unsubscribe(PenumbraDispose);
_penumbraInit.Unsubscribe(PenumbraInit);
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
_penumbraGameObjectResourcePathResolved.Unsubscribe(ResourceLoaded);
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
}
public float GetHeelsOffset()
{
if (!CheckHeelsApi()) return 0.0f;
return _heelsGetOffset.InvokeFunc();
}
public void HeelsSetOffsetForPlayer(float offset, IntPtr character)
{
if(!CheckHeelsApi()) return;
actionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
{
Logger.Verbose("Applying Heels data to " + character.ToString("X"));
_heelsRegisterPlayer.InvokeAction(gameObj, offset);
}
});
}
public void GlamourerApplyAll(string? customization, IntPtr obj)
@@ -246,11 +300,11 @@ namespace MareSynchronos.Managers
});
}
public string? PenumbraResolvePath(string path)
public string PenumbraResolvePath(string path)
{
if (!CheckPenumbraApi()) return null;
if (!CheckPenumbraApi()) return path;
var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path);
return resolvedPath;
return resolvedPath ?? path;
}
public string[] PenumbraReverseResolvePlayer(string path)

View File

@@ -9,7 +9,6 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using MareSynchronos.WebAPI.Utils;
using Newtonsoft.Json;
namespace MareSynchronos.Managers;
@@ -199,30 +198,6 @@ public class OnlinePlayerManager : IDisposable
return;
}
if (_dalamudUtil.IsInGpose)
{
_playerTokenDisposal.TryGetValue(cachedPlayer, out var cancellationTokenSource);
cancellationTokenSource?.Cancel();
cachedPlayer.IsVisible = false;
_playerTokenDisposal[cachedPlayer] = new CancellationTokenSource();
cancellationTokenSource = _playerTokenDisposal[cachedPlayer];
var token = cancellationTokenSource.Token;
Task.Run(async () =>
{
Logger.Verbose("Cannot dispose Player, in GPose");
while (_dalamudUtil.IsInGpose)
{
await Task.Delay(TimeSpan.FromSeconds(0.5));
if (token.IsCancellationRequested) return;
}
cachedPlayer.DisposePlayer();
_onlineCachedPlayers.TryRemove(characterHash, out _);
}, token);
return;
}
cachedPlayer.DisposePlayer();
_onlineCachedPlayers.TryRemove(characterHash, out _);
}

View File

@@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using System.Collections.Generic;
using System.Linq;
using MareSynchronos.Models;
using Newtonsoft.Json;
namespace MareSynchronos.Managers
{
@@ -19,6 +20,7 @@ namespace MareSynchronos.Managers
private readonly ApiController _apiController;
private readonly CharacterDataFactory _characterDataFactory;
private readonly DalamudUtil _dalamudUtil;
private readonly TransientResourceManager _transientResourceManager;
private readonly IpcManager _ipcManager;
public event PlayerHasChanged? PlayerHasChanged;
public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
@@ -30,7 +32,7 @@ namespace MareSynchronos.Managers
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil)
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager)
{
Logger.Verbose("Creating " + nameof(PlayerManager));
@@ -38,9 +40,10 @@ namespace MareSynchronos.Managers
_ipcManager = ipcManager;
_characterDataFactory = characterDataFactory;
_dalamudUtil = dalamudUtil;
_transientResourceManager = transientResourceManager;
_apiController.Connected += ApiControllerOnConnected;
_apiController.Disconnected += ApiController_Disconnected;
_transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad;
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
@@ -58,6 +61,29 @@ namespace MareSynchronos.Managers
};
}
public void HandleTransientResourceLoad(IntPtr gameObj)
{
foreach (var obj in playerRelatedObjects)
{
if (obj.Address == gameObj && !obj.HasUnprocessedUpdate)
{
obj.HasUnprocessedUpdate = true;
OnPlayerOrAttachedObjectsChanged();
return;
}
}
}
private void HeelsOffsetChanged(float change)
{
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing)
{
Logger.Debug("Heels offset changed to " + change);
playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasUnprocessedUpdate = true;
}
}
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(PlayerManager));
@@ -67,6 +93,11 @@ namespace MareSynchronos.Managers
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad;
_playerChangedCts?.Cancel();
_ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged;
}
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
@@ -110,6 +141,7 @@ namespace MareSynchronos.Managers
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested)
{
Logger.Verbose("Waiting until cache is ready");
await Task.Delay(50, token);
}
@@ -117,7 +149,9 @@ namespace MareSynchronos.Managers
Logger.Verbose("Cache creation complete");
return PermanentDataCache.ToCharacterCacheDto();
var cache = PermanentDataCache.ToCharacterCacheDto();
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
return cache;
}
private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
@@ -129,6 +163,7 @@ namespace MareSynchronos.Managers
if (address == item.Address)
{
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
//_transientResourceManager.CleanSemiTransientResources(item.ObjectKind);
item.HasUnprocessedUpdate = true;
}
}
@@ -141,8 +176,6 @@ namespace MareSynchronos.Managers
private void OnPlayerOrAttachedObjectsChanged()
{
if (_dalamudUtil.IsInGpose) return;
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList();
foreach (var unprocessedObject in unprocessedObjects)
{
@@ -176,7 +209,10 @@ namespace MareSynchronos.Managers
Task.Run(async () =>
{
_dalamudUtil.WaitWhileSelfIsDrawing(token);
foreach(var item in unprocessedObjects)
{
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, token);
}
CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token));
if (cacheDto == null || token.IsCancellationRequested) return;

View File

@@ -0,0 +1,175 @@
using MareSynchronos.API;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MareSynchronos.Managers
{
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
public class TransientResourceManager : IDisposable
{
private readonly IpcManager manager;
private readonly DalamudUtil dalamudUtil;
public event TransientResourceLoadedEvent? TransientResourceLoaded;
private Dictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
private Dictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil)
{
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
this.manager = manager;
this.dalamudUtil = dalamudUtil;
dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate;
dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged;
}
private void DalamudUtil_ClassJobChanged()
{
if (SemiTransientResources.ContainsKey(ObjectKind.Pet))
{
SemiTransientResources[ObjectKind.Pet].Clear();
}
}
private void DalamudUtil_FrameworkUpdate()
{
foreach (var item in TransientResources.ToList())
{
if (!dalamudUtil.IsGameObjectPresent(item.Key))
{
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
TransientResources.Remove(item.Key);
}
}
}
public void CleanSemiTransientResources(ObjectKind objectKind)
{
if (SemiTransientResources.ContainsKey(objectKind))
{
SemiTransientResources[objectKind].Clear();
}
}
public List<string> GetTransientResources(IntPtr gameObject)
{
if (TransientResources.TryGetValue(gameObject, out var result))
{
return result.ToList();
}
return new List<string>();
}
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
{
if (SemiTransientResources.TryGetValue(objectKind, out var result))
{
return result.ToList();
}
return new List<FileReplacement>();
}
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath)
{
if (!TransientResources.ContainsKey(gameObject))
{
TransientResources[gameObject] = new();
}
if (filePath.StartsWith("|"))
{
filePath = filePath.Split("|")[2];
}
filePath = filePath.ToLowerInvariant();
var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/");
if (TransientResources[gameObject].Contains(replacedGamePath) ||
SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
&& f.ResolvedPath.ToLowerInvariant() == filePath)))
{
Logger.Debug("Not adding " + replacedGamePath + ":" + filePath);
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
&& f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
}
else
{
TransientResources[gameObject].Add(replacedGamePath);
Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})");
TransientResourceLoaded?.Invoke(gameObject);
}
}
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
{
if (TransientResources.ContainsKey(gameObject))
{
TransientResources[gameObject].RemoveWhere(f => fileReplacement.ResolvedPath.ToLowerInvariant() == f.ToLowerInvariant());
}
}
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func<string, bool, FileReplacement> createFileReplacement)
{
if (!SemiTransientResources.ContainsKey(objectKind))
{
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
}
if (!TransientResources.TryGetValue(gameObject, out var resources))
{
return;
}
var transientResources = resources.ToList();
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
foreach (var item in transientResources)
{
var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant());
if (existingResource)
{
Logger.Debug("Semi Transient resource replaced: " + item);
SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant());
}
if (!SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant()))
{
Logger.Debug("Persisting " + item.ToLowerInvariant());
var fileReplacement = createFileReplacement(item.ToLowerInvariant(), true);
if (!fileReplacement.HasFileReplacement)
fileReplacement = createFileReplacement(item.ToLowerInvariant(), false);
SemiTransientResources[objectKind].Add(fileReplacement);
}
}
TransientResources[gameObject].Clear();
}
public void Dispose()
{
dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
TransientResources.Clear();
}
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
{
if (!SemiTransientResources.ContainsKey(objectKind))
{
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
}
if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant()))
{
SemiTransientResources[objectKind].Add(item);
}
}
}
}

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Authors></Authors>
<Company></Company>
<Version>0.3.15</Version>
<Version>0.4.0</Version>
<Description></Description>
<Copyright></Copyright>
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using MareSynchronos.API;
using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets;
namespace MareSynchronos.Models
{
@@ -39,16 +41,31 @@ namespace MareSynchronos.Models
public CharacterCacheDto ToCharacterCacheDto()
{
var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g =>
{
return new FileReplacementDto()
{
GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(),
Hash = g.First().Hash,
};
}).ToList());
Logger.Debug("Adding fileSwaps");
foreach (var item in FileReplacements)
{
Logger.Debug("Checking fileSwaps for " + item.Key);
var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto());
Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps");
foreach (var swap in fileSwapsToAdd)
{
Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath);
}
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
}
return new CharacterCacheDto()
{
FileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement).GroupBy(f => f.Hash).Select(g =>
{
return new FileReplacementDto()
{
GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(),
Hash = g.First().Hash
};
}).ToList()),
FileReplacements = fileReplacements,
GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value),
ManipulationData = ManipulationString
};

View File

@@ -8,6 +8,7 @@ using MareSynchronos.FileCacheDB;
using System.IO;
using MareSynchronos.API;
using MareSynchronos.Utils;
using System.Text.RegularExpressions;
namespace MareSynchronos.Models
{
@@ -20,27 +21,29 @@ namespace MareSynchronos.Models
_penumbraDirectory = penumbraDirectory;
}
public bool Computed => !HasFileReplacement || !string.IsNullOrEmpty(Hash);
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
public List<string> GamePaths { get; set; } = new();
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath);
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath;
public string Hash { get; set; } = string.Empty;
public string ResolvedPath { get; set; } = string.Empty;
public void SetResolvedPath(string path)
{
ResolvedPath = path.ToLower().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/');
if (!HasFileReplacement) return;
ResolvedPath = path.ToLowerInvariant().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/');
if (!HasFileReplacement || IsFileSwap) return;
_ = Task.Run(() =>
{
FileCache? fileCache;
using (FileCacheContext db = new())
{
fileCache = db.FileCaches.FirstOrDefault(f => f.Filepath == path.ToLower());
fileCache = db.FileCaches.FirstOrDefault(f => f.Filepath == path.ToLowerInvariant());
}
if (fileCache != null)
@@ -54,7 +57,7 @@ namespace MareSynchronos.Models
{
Hash = ComputeHash(fi);
using var db = new FileCacheContext();
var newTempCache = db.FileCaches.Single(f => f.Filepath == path.ToLower());
var newTempCache = db.FileCaches.Single(f => f.Filepath == path.ToLowerInvariant());
newTempCache.Hash = Hash;
db.Update(newTempCache);
db.SaveChanges();
@@ -73,6 +76,7 @@ namespace MareSynchronos.Models
{
GamePaths = GamePaths.ToArray(),
Hash = Hash,
FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty
};
}
public override string ToString()
@@ -88,15 +92,16 @@ namespace MareSynchronos.Models
string hash = Crypto.GetFileHash(fi.FullName);
using FileCacheContext db = new();
var fileAddedDuringCompute = db.FileCaches.FirstOrDefault(f => f.Filepath == fi.FullName.ToLower());
var fileAddedDuringCompute = db.FileCaches.FirstOrDefault(f => f.Filepath == fi.FullName.ToLowerInvariant());
if (fileAddedDuringCompute != null) return fileAddedDuringCompute.Hash;
try
{
Logger.Debug("Adding new file to DB: " + fi.FullName + ", " + hash);
db.Add(new FileCache()
{
Hash = hash,
Filepath = fi.FullName.ToLower(),
Filepath = fi.FullName.ToLowerInvariant(),
LastModifiedDate = fi.LastWriteTimeUtc.Ticks.ToString()
});
db.SaveChanges();

View File

@@ -19,7 +19,18 @@ namespace MareSynchronos.Models
public IntPtr Address { get; set; }
public IntPtr DrawObjectAddress { get; set; }
private IntPtr CurrentAddress => getAddress.Invoke();
private IntPtr CurrentAddress
{
get
{
try
{
return getAddress.Invoke();
}
catch
{ return IntPtr.Zero; }
}
}
public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
{
@@ -53,7 +64,7 @@ namespace MareSynchronos.Models
if (addr || equip || drawObj || nameChange)
{
_name = name;
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + curPtr + ", " + (IntPtr)chara->GameObject.DrawObject);
Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}");
Address = curPtr;
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;

View File

@@ -14,6 +14,7 @@ using Dalamud.Interface.Windowing;
using MareSynchronos.UI;
using MareSynchronos.Utils;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
namespace MareSynchronos
{
@@ -31,6 +32,7 @@ namespace MareSynchronos
private readonly SettingsUi _settingsUi;
private readonly WindowSystem _windowSystem;
private PlayerManager? _playerManager;
private TransientResourceManager? _transientResourceManager;
private readonly DalamudUtil _dalamudUtil;
private OnlinePlayerManager? _characterCacheManager;
private readonly DownloadUi _downloadUi;
@@ -41,7 +43,7 @@ namespace MareSynchronos
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
Framework framework, ObjectTable objectTable, ClientState clientState)
Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition)
{
Logger.Debug("Launching " + Name);
PluginInterface = pluginInterface;
@@ -59,7 +61,7 @@ namespace MareSynchronos
new FileCacheContext().Dispose(); // make sure db is initialized I guess
// those can be initialized outside of game login
_dalamudUtil = new DalamudUtil(clientState, objectTable, framework);
_dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition);
_apiController = new ApiController(_configuration, _dalamudUtil);
_ipcManager = new IpcManager(PluginInterface, _dalamudUtil);
@@ -123,7 +125,7 @@ namespace MareSynchronos
_commandManager.RemoveHandler(CommandName);
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
_uiSharedComponent.Dispose();
_settingsUi?.Dispose();
_introUi?.Dispose();
@@ -134,6 +136,8 @@ namespace MareSynchronos
_ipcManager?.Dispose();
_playerManager?.Dispose();
_characterCacheManager?.Dispose();
_transientResourceManager?.Dispose();
_dalamudUtil.Dispose();
Logger.Debug("Shut down");
}
@@ -165,6 +169,7 @@ namespace MareSynchronos
Logger.Debug("Client logout");
_characterCacheManager?.Dispose();
_playerManager?.Dispose();
_transientResourceManager?.Dispose();
PluginInterface.UiBuilder.Draw -= Draw;
PluginInterface.UiBuilder.OpenConfigUi -= OpenUi;
_commandManager.RemoveHandler(CommandName);
@@ -174,6 +179,7 @@ namespace MareSynchronos
{
_characterCacheManager?.Dispose();
_playerManager?.Dispose();
_transientResourceManager?.Dispose();
Task.Run(WaitForPlayerAndLaunchCharacterManager);
}
@@ -187,10 +193,11 @@ namespace MareSynchronos
try
{
_transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil);
var characterCacheFactory =
new CharacterDataFactory(_dalamudUtil, _ipcManager);
new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager);
_playerManager = new PlayerManager(_apiController, _ipcManager,
characterCacheFactory, _dalamudUtil);
characterCacheFactory, _dalamudUtil, _transientResourceManager);
_characterCacheManager = new OnlinePlayerManager(_framework,
_apiController, _dalamudUtil, _ipcManager, _playerManager);
}

View File

@@ -292,8 +292,8 @@ namespace MareSynchronos.UI
{
if (_characterOrCommentFilter.IsNullOrEmpty()) return true;
_configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment);
return p.OtherUID.ToLower().Contains(_characterOrCommentFilter.ToLower()) ||
(comment?.ToLower().Contains(_characterOrCommentFilter.ToLower()) ?? false);
return p.OtherUID.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ||
(comment?.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ?? false);
});
if (_configuration.ReverseUserSort) users = users.Reverse();

View File

@@ -44,6 +44,8 @@ public class DownloadUi : Window, IDisposable
Flags |= ImGuiWindowFlags.NoTitleBar;
Flags |= ImGuiWindowFlags.NoDecoration;
ForceMainWindow = true;
windowSystem.AddWindow(this);
IsOpen = true;
}

View File

@@ -40,9 +40,10 @@ namespace MareSynchronos.UI
private Dictionary<string, string> _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
private int _currentLanguage;
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3;
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim()
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim()
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim();
public void Dispose()
{

View File

@@ -423,7 +423,7 @@ namespace MareSynchronos.UI
{
if (!success) return;
_isPenumbraDirectory = path.ToLower() == _ipcManager.PenumbraModDirectory()?.ToLower();
_isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant();
_isDirectoryWritable = IsDirectoryWritable(path);
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40);
_cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript);

View File

@@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
@@ -15,6 +17,7 @@ namespace MareSynchronos.Utils
public delegate void LogIn();
public delegate void LogOut();
public delegate void ClassJobChanged();
public delegate void FrameworkUpdate();
@@ -23,33 +26,57 @@ namespace MareSynchronos.Utils
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
private readonly Condition _condition;
public event LogIn? LogIn;
public event LogOut? LogOut;
public event FrameworkUpdate? FrameworkUpdate;
public event ClassJobChanged? ClassJobChanged;
private uint? classJobId = 0;
public event FrameworkUpdate? DelayedFrameworkUpdate;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework)
public unsafe bool IsGameObjectPresent(IntPtr key)
{
foreach (var obj in _objectTable)
{
if (obj.Address == key)
{
return true;
}
}
return false;
}
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition)
{
_clientState = clientState;
_objectTable = objectTable;
_framework = framework;
_condition = condition;
_clientState.Login += ClientStateOnLogin;
_clientState.Logout += ClientStateOnLogout;
_framework.Update += FrameworkOnUpdate;
if (IsLoggedIn)
{
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(null, EventArgs.Empty);
}
}
private void FrameworkOnUpdate(Framework framework)
{
foreach (FrameworkUpdate frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
{
return;
}
foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
{
try
{
frameworkInvocation.Invoke();
frameworkInvocation?.Invoke();
}
catch (Exception ex)
{
@@ -58,12 +85,23 @@ namespace MareSynchronos.Utils
}
}
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(0.25)) return;
foreach (FrameworkUpdate frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
{
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
if (classJobId != newclassJobId)
{
classJobId = newclassJobId;
ClassJobChanged?.Invoke();
}
}
foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
{
try
{
frameworkInvocation.Invoke();
frameworkInvocation?.Invoke();
}
catch (Exception ex)
{
@@ -119,7 +157,7 @@ namespace MareSynchronos.Utils
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
public IntPtr PlayerPointer => _clientState.LocalPlayer!.Address;
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
@@ -152,25 +190,33 @@ namespace MareSynchronos.Utils
return null;
}
public unsafe void WaitWhileCharacterIsDrawing(IntPtr characterAddress, CancellationToken? ct = null)
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
{
if (!_clientState.IsLoggedIn) return;
return await _framework.RunOnFrameworkThread(func);
}
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, CancellationToken? ct = null)
{
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
var obj = (GameObject*)characterAddress;
const int maxWaitTime = 5000;
const int tick = 250;
int curWaitTime = 0;
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true)) // 0b100000000000 is "still rendering" or something
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < maxWaitTime) // 0b100000000000 is "still rendering" or something
{
Logger.Verbose("Waiting for character to finish drawing");
Thread.Sleep(250);
Logger.Verbose($"Waiting for {name} to finish drawing");
curWaitTime += tick;
Thread.Sleep(tick);
}
if (ct?.IsCancellationRequested ?? false) return;
// wait quarter a second just in case
Thread.Sleep(250);
Thread.Sleep(tick);
}
public void WaitWhileSelfIsDrawing(CancellationToken? token) => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? new IntPtr(), token);
public void WaitWhileSelfIsDrawing(CancellationToken? token) => WaitWhileCharacterIsDrawing("self", _clientState.LocalPlayer?.Address ?? IntPtr.Zero, token);
public void Dispose()
{

View File

@@ -1,11 +1,37 @@
using System.Diagnostics;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using Dalamud.Logging;
using Dalamud.Utility;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils
{
internal class Logger
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
{
private readonly ConcurrentDictionary<string, Logger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
public DalamudLoggingProvider()
{
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName));
}
public void Dispose()
{
_loggers.Clear();
}
}
internal class Logger : ILogger
{
private readonly string name;
public static void Info(string info)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
@@ -40,5 +66,47 @@ namespace MareSynchronos.Utils
PluginLog.Verbose($"[{caller}] {verbose}");
#endif
}
public Logger(string name)
{
this.name = name;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel)) return;
switch (logLevel)
{
case LogLevel.Debug:
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Error:
case LogLevel.Critical:
PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Information:
PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Warning:
PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Trace:
default:
#if DEBUG
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
#else
PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
#endif
break;
}
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state) => default!;
}
}

View File

@@ -76,6 +76,8 @@ namespace MareSynchronos.WebAPI
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred).ToList();
@@ -123,9 +125,13 @@ namespace MareSynchronos.WebAPI
{
await using (var db = new FileCacheContext())
{
allFilesInDb = CurrentDownloads[currentDownloadId]
var fileCount = CurrentDownloads[currentDownloadId]
.Where(c => c.CanBeTransferred)
.All(h => db.FileCaches.Any(f => f.Hash == h.Hash));
.Count(h => db.FileCaches.Any(f => f.Hash == h.Hash));
var totalFiles = CurrentDownloads[currentDownloadId].Count(c => c.CanBeTransferred);
Logger.Debug("Waiting for files to be in the DB, added " + fileCount + " of " + totalFiles);
allFilesInDb = fileCount == totalFiles;
}
await Task.Delay(250, ct);
@@ -146,7 +152,7 @@ namespace MareSynchronos.WebAPI
Logger.Verbose("New Token Created");
List<string> unverifiedUploadHashes = new();
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Select(v => v.Hash).Distinct()).Distinct().ToList())
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList())
{
if (!_verifiedUploadedHashes.Contains(item))
{

View File

@@ -11,6 +11,7 @@ using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.WebAPI
{
@@ -304,6 +305,10 @@ namespace MareSynchronos.WebAPI
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
})
.WithAutomaticReconnect(new ForeverRetryPolicy())
.ConfigureLogging(a => {
a.ClearProviders().AddProvider(new DalamudLoggingProvider());
a.SetMinimumLevel(LogLevel.Trace);
})
.Build();
}
@@ -334,9 +339,10 @@ namespace MareSynchronos.WebAPI
{
if (_mareHub is not null)
{
_uploadCancellationTokenSource?.Cancel();
Logger.Info("Stopping existing connection");
_mareHub.Closed -= MareHubOnClosed;
_mareHub.Reconnecting += MareHubOnReconnecting;
_mareHub.Reconnecting -= MareHubOnReconnecting;
await _mareHub.StopAsync(token);
await _mareHub.DisposeAsync();
CurrentUploads.Clear();