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.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
@@ -22,16 +24,23 @@ public class CharacterDataFactory
{ {
private readonly DalamudUtil _dalamudUtil; private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager; 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)); Logger.Verbose("Creating " + nameof(CharacterDataFactory));
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_ipcManager = ipcManager; _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) if (!_ipcManager.Initialized)
{ {
@@ -41,7 +50,7 @@ public class CharacterDataFactory
bool pointerIsZero = true; bool pointerIsZero = true;
try try
{ {
pointerIsZero = playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null; pointerIsZero = CheckForPointer(playerPointer);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -144,7 +153,6 @@ public class CharacterDataFactory
return; return;
} }
//Logger.Verbose("Adding File Replacement for Material " + fileName);
var mtrlPath = fileName.Split("|")[2]; var mtrlPath = fileName.Split("|")[2];
if (cache.FileReplacements.ContainsKey(objectKind)) 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) private void AddReplacementsFromTexture(string texPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = true)
{ {
if (string.IsNullOrEmpty(texPath)) return; if (string.IsNullOrEmpty(texPath)) return;
//Logger.Verbose("Adding File Replacement for Texture " + texPath);
if (cache.FileReplacements.ContainsKey(objectKind)) if (cache.FileReplacements.ContainsKey(objectKind))
{ {
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath))) if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath)))
@@ -215,14 +239,16 @@ public class CharacterDataFactory
previousData.FileReplacements[objectKind].Clear(); previousData.FileReplacements[objectKind].Clear();
} }
Stopwatch st = Stopwatch.StartNew();
var chara = _dalamudUtil.CreateGameObject(charaPointer)!; var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
while (!_dalamudUtil.IsObjectPresent(chara)) while (!_dalamudUtil.IsObjectPresent(chara))
{ {
Logger.Verbose("Character is null but it shouldn't be, waiting"); Logger.Verbose("Character is null but it shouldn't be, waiting");
Thread.Sleep(50); Thread.Sleep(50);
} }
_dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer);
_dalamudUtil.WaitWhileCharacterIsDrawing(objectKind.ToString(), charaPointer);
Stopwatch st = Stopwatch.StartNew();
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations(); previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
@@ -242,57 +268,131 @@ public class CharacterDataFactory
AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0); AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0);
} }
foreach (var item in previousData.FileReplacements[objectKind])
{
transientResourceManager.RemoveTransientResource(charaPointer, item);
}
if (objectKind == ObjectKind.Player) if (objectKind == ObjectKind.Player)
{ {
var weaponObject = (Weapon*)((Object*)human)->ChildObject; AddPlayerSpecificReplacements(previousData, objectKind, charaPointer, human);
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");
}
} }
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(); st.Stop();
Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed); Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed);
return previousData; 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) private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache)
{ {
string raceSexIdString = raceSexId.ToString("0000"); string raceSexIdString = raceSexId.ToString("0000");
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb"; 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); var replacement = CreateFileReplacement(skeletonPath, true);
cache.AddFileReplacement(objectKind, replacement); cache.AddFileReplacement(objectKind, replacement);

View File

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

View File

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

View File

@@ -11,6 +11,8 @@ using System.Collections.Concurrent;
namespace MareSynchronos.Managers namespace MareSynchronos.Managers
{ {
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx); 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 public class IpcManager : IDisposable
{ {
private readonly ICallGateSubscriber<int> _glamourerApiVersion; private readonly ICallGateSubscriber<int> _glamourerApiVersion;
@@ -33,6 +35,14 @@ namespace MareSynchronos.Managers
private readonly ICallGateSubscriber<string, string[]>? _reverseResolvePlayer; private readonly ICallGateSubscriber<string, string[]>? _reverseResolvePlayer;
private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int> private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int>
_penumbraSetTemporaryMod; _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 DalamudUtil _dalamudUtil;
private readonly ConcurrentQueue<Action> actionQueue = new(); private readonly ConcurrentQueue<Action> actionQueue = new();
@@ -58,7 +68,9 @@ namespace MareSynchronos.Managers
_glamourerApplyOnlyCustomization = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyCustomizationToCharacter"); _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyCustomizationToCharacter");
_glamourerApplyOnlyEquipment = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyEquipmentToCharacter"); _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyEquipmentToCharacter");
_glamourerRevertCustomization = pi.GetIpcSubscriber<GameObject?, object>("Glamourer.RevertCharacter"); _glamourerRevertCustomization = pi.GetIpcSubscriber<GameObject?, object>("Glamourer.RevertCharacter");
_penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber<IntPtr, string, string, object?>("Penumbra.GameObjectResourcePathResolved");
_penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded);
_penumbraObjectIsRedrawn.Subscribe(RedrawEvent); _penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
_penumbraInit.Subscribe(PenumbraInit); _penumbraInit.Subscribe(PenumbraInit);
_penumbraDispose.Subscribe(PenumbraDispose); _penumbraDispose.Subscribe(PenumbraDispose);
@@ -82,6 +94,15 @@ namespace MareSynchronos.Managers
_dalamudUtil.FrameworkUpdate += HandleActionQueue; _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() private void HandleActionQueue()
{ {
if (actionQueue.TryDequeue(out var action)) if (actionQueue.TryDequeue(out var action))
@@ -95,6 +116,8 @@ namespace MareSynchronos.Managers
public event VoidDelegate? PenumbraInitialized; public event VoidDelegate? PenumbraInitialized;
public event VoidDelegate? PenumbraDisposed; public event VoidDelegate? PenumbraDisposed;
public event PenumbraRedrawEvent? PenumbraRedrawEvent; public event PenumbraRedrawEvent? PenumbraRedrawEvent;
public event HeelsOffsetChange? HeelsOffsetChangeEvent;
public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
public bool Initialized => CheckPenumbraApi(); public bool Initialized => CheckPenumbraApi();
public bool CheckGlamourerApi() public bool CheckGlamourerApi()
@@ -113,7 +136,7 @@ namespace MareSynchronos.Managers
{ {
try try
{ {
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 11 }; return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 };
} }
catch catch
{ {
@@ -125,12 +148,43 @@ namespace MareSynchronos.Managers
{ {
Logger.Verbose("Disposing " + nameof(IpcManager)); 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; _dalamudUtil.FrameworkUpdate -= HandleActionQueue;
actionQueue.Clear(); actionQueue.Clear();
_penumbraDispose.Unsubscribe(PenumbraDispose); _penumbraDispose.Unsubscribe(PenumbraDispose);
_penumbraInit.Unsubscribe(PenumbraInit); _penumbraInit.Unsubscribe(PenumbraInit);
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); _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) 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); var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path);
return resolvedPath; return resolvedPath ?? path;
} }
public string[] PenumbraReverseResolvePlayer(string path) public string[] PenumbraReverseResolvePlayer(string path)

View File

@@ -9,7 +9,6 @@ using MareSynchronos.API;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
using MareSynchronos.WebAPI.Utils; using MareSynchronos.WebAPI.Utils;
using Newtonsoft.Json;
namespace MareSynchronos.Managers; namespace MareSynchronos.Managers;
@@ -199,30 +198,6 @@ public class OnlinePlayerManager : IDisposable
return; 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(); cachedPlayer.DisposePlayer();
_onlineCachedPlayers.TryRemove(characterHash, out _); _onlineCachedPlayers.TryRemove(characterHash, out _);
} }

View File

@@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using MareSynchronos.Models; using MareSynchronos.Models;
using Newtonsoft.Json;
namespace MareSynchronos.Managers namespace MareSynchronos.Managers
{ {
@@ -19,6 +20,7 @@ namespace MareSynchronos.Managers
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly CharacterDataFactory _characterDataFactory; private readonly CharacterDataFactory _characterDataFactory;
private readonly DalamudUtil _dalamudUtil; private readonly DalamudUtil _dalamudUtil;
private readonly TransientResourceManager _transientResourceManager;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
public event PlayerHasChanged? PlayerHasChanged; public event PlayerHasChanged? PlayerHasChanged;
public CharacterCacheDto? LastCreatedCharacterData { get; private set; } public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
@@ -30,7 +32,7 @@ namespace MareSynchronos.Managers
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>(); private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager, public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil) CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager)
{ {
Logger.Verbose("Creating " + nameof(PlayerManager)); Logger.Verbose("Creating " + nameof(PlayerManager));
@@ -38,9 +40,10 @@ namespace MareSynchronos.Managers
_ipcManager = ipcManager; _ipcManager = ipcManager;
_characterDataFactory = characterDataFactory; _characterDataFactory = characterDataFactory;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_transientResourceManager = transientResourceManager;
_apiController.Connected += ApiControllerOnConnected; _apiController.Connected += ApiControllerOnConnected;
_apiController.Disconnected += ApiController_Disconnected; _apiController.Disconnected += ApiController_Disconnected;
_transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad;
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate; _dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected); 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() public void Dispose()
{ {
Logger.Verbose("Disposing " + nameof(PlayerManager)); Logger.Verbose("Disposing " + nameof(PlayerManager));
@@ -67,6 +93,11 @@ namespace MareSynchronos.Managers
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate; _dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad;
_playerChangedCts?.Cancel();
_ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged;
} }
private unsafe void DalamudUtilOnDelayedFrameworkUpdate() private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
@@ -110,6 +141,7 @@ namespace MareSynchronos.Managers
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested) while (!PermanentDataCache.IsReady && !token.IsCancellationRequested)
{ {
Logger.Verbose("Waiting until cache is ready");
await Task.Delay(50, token); await Task.Delay(50, token);
} }
@@ -117,7 +149,9 @@ namespace MareSynchronos.Managers
Logger.Verbose("Cache creation complete"); 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) private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
@@ -129,6 +163,7 @@ namespace MareSynchronos.Managers
if (address == item.Address) if (address == item.Address)
{ {
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind); Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
//_transientResourceManager.CleanSemiTransientResources(item.ObjectKind);
item.HasUnprocessedUpdate = true; item.HasUnprocessedUpdate = true;
} }
} }
@@ -141,8 +176,6 @@ namespace MareSynchronos.Managers
private void OnPlayerOrAttachedObjectsChanged() private void OnPlayerOrAttachedObjectsChanged()
{ {
if (_dalamudUtil.IsInGpose) return;
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList(); var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList();
foreach (var unprocessedObject in unprocessedObjects) foreach (var unprocessedObject in unprocessedObjects)
{ {
@@ -176,7 +209,10 @@ namespace MareSynchronos.Managers
Task.Run(async () => 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)); CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token));
if (cacheDto == null || token.IsCancellationRequested) return; 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> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>0.3.15</Version> <Version>0.4.0</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl> <PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using MareSynchronos.API; using MareSynchronos.API;
using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets;
namespace MareSynchronos.Models namespace MareSynchronos.Models
{ {
@@ -39,16 +41,31 @@ namespace MareSynchronos.Models
public CharacterCacheDto ToCharacterCacheDto() 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() return new CharacterCacheDto()
{ {
FileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement).GroupBy(f => f.Hash).Select(g => FileReplacements = fileReplacements,
{
return new FileReplacementDto()
{
GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(),
Hash = g.First().Hash
};
}).ToList()),
GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value), GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value),
ManipulationData = ManipulationString ManipulationData = ManipulationString
}; };

View File

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

View File

@@ -19,7 +19,18 @@ namespace MareSynchronos.Models
public IntPtr Address { get; set; } public IntPtr Address { get; set; }
public IntPtr DrawObjectAddress { 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) public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
{ {
@@ -53,7 +64,7 @@ namespace MareSynchronos.Models
if (addr || equip || drawObj || nameChange) if (addr || equip || drawObj || nameChange)
{ {
_name = name; _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; Address = curPtr;
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject; DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;

View File

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

View File

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

View File

@@ -44,6 +44,8 @@ public class DownloadUi : Window, IDisposable
Flags |= ImGuiWindowFlags.NoTitleBar; Flags |= ImGuiWindowFlags.NoTitleBar;
Flags |= ImGuiWindowFlags.NoDecoration; Flags |= ImGuiWindowFlags.NoDecoration;
ForceMainWindow = true;
windowSystem.AddWindow(this); windowSystem.AddWindow(this);
IsOpen = true; 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 Dictionary<string, string> _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
private int _currentLanguage; private int _currentLanguage;
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1 private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim()
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2 && _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim()
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3; && _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim();
public void Dispose() public void Dispose()
{ {

View File

@@ -423,7 +423,7 @@ namespace MareSynchronos.UI
{ {
if (!success) return; if (!success) return;
_isPenumbraDirectory = path.ToLower() == _ipcManager.PenumbraModDirectory()?.ToLower(); _isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant();
_isDirectoryWritable = IsDirectoryWritable(path); _isDirectoryWritable = IsDirectoryWritable(path);
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40); _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); _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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState; using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
@@ -15,6 +17,7 @@ namespace MareSynchronos.Utils
public delegate void LogIn(); public delegate void LogIn();
public delegate void LogOut(); public delegate void LogOut();
public delegate void ClassJobChanged();
public delegate void FrameworkUpdate(); public delegate void FrameworkUpdate();
@@ -23,33 +26,57 @@ namespace MareSynchronos.Utils
private readonly ClientState _clientState; private readonly ClientState _clientState;
private readonly ObjectTable _objectTable; private readonly ObjectTable _objectTable;
private readonly Framework _framework; private readonly Framework _framework;
private readonly Condition _condition;
public event LogIn? LogIn; public event LogIn? LogIn;
public event LogOut? LogOut; public event LogOut? LogOut;
public event FrameworkUpdate? FrameworkUpdate; public event FrameworkUpdate? FrameworkUpdate;
public event ClassJobChanged? ClassJobChanged;
private uint? classJobId = 0;
public event FrameworkUpdate? DelayedFrameworkUpdate; public event FrameworkUpdate? DelayedFrameworkUpdate;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now; 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; _clientState = clientState;
_objectTable = objectTable; _objectTable = objectTable;
_framework = framework; _framework = framework;
_condition = condition;
_clientState.Login += ClientStateOnLogin; _clientState.Login += ClientStateOnLogin;
_clientState.Logout += ClientStateOnLogout; _clientState.Logout += ClientStateOnLogout;
_framework.Update += FrameworkOnUpdate; _framework.Update += FrameworkOnUpdate;
if (IsLoggedIn) if (IsLoggedIn)
{ {
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(null, EventArgs.Empty); ClientStateOnLogin(null, EventArgs.Empty);
} }
} }
private void FrameworkOnUpdate(Framework framework) 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 try
{ {
frameworkInvocation.Invoke(); frameworkInvocation?.Invoke();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -58,12 +85,23 @@ namespace MareSynchronos.Utils
} }
} }
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(0.25)) return; if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
foreach (FrameworkUpdate frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>()) 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 try
{ {
frameworkInvocation.Invoke(); frameworkInvocation?.Invoke();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -119,7 +157,7 @@ namespace MareSynchronos.Utils
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--"; 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!; public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
@@ -152,25 +190,33 @@ namespace MareSynchronos.Utils
return null; 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; var obj = (GameObject*)characterAddress;
const int maxWaitTime = 5000;
const int tick = 250;
int curWaitTime = 0;
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop // 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"); Logger.Verbose($"Waiting for {name} to finish drawing");
Thread.Sleep(250); curWaitTime += tick;
Thread.Sleep(tick);
} }
if (ct?.IsCancellationRequested ?? false) return; if (ct?.IsCancellationRequested ?? false) return;
// wait quarter a second just in case // 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() 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.Logging;
using Dalamud.Utility; using Dalamud.Utility;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils 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) public static void Info(string info)
{ {
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
@@ -40,5 +66,47 @@ namespace MareSynchronos.Utils
PluginLog.Verbose($"[{caller}] {verbose}"); PluginLog.Verbose($"[{caller}] {verbose}");
#endif #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>(); List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct)); 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)) CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred).ToList(); .Where(d => d.CanBeTransferred).ToList();
@@ -123,9 +125,13 @@ namespace MareSynchronos.WebAPI
{ {
await using (var db = new FileCacheContext()) await using (var db = new FileCacheContext())
{ {
allFilesInDb = CurrentDownloads[currentDownloadId] var fileCount = CurrentDownloads[currentDownloadId]
.Where(c => c.CanBeTransferred) .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); await Task.Delay(250, ct);
@@ -146,7 +152,7 @@ namespace MareSynchronos.WebAPI
Logger.Verbose("New Token Created"); Logger.Verbose("New Token Created");
List<string> unverifiedUploadHashes = new(); 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)) if (!_verifiedUploadedHashes.Contains(item))
{ {

View File

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