merge 0.4.0 into main
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 _);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
175
MareSynchronos/Managers/TransientResourceManager.cs
Normal file
175
MareSynchronos/Managers/TransientResourceManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -44,6 +44,8 @@ public class DownloadUi : Window, IDisposable
|
||||
Flags |= ImGuiWindowFlags.NoTitleBar;
|
||||
Flags |= ImGuiWindowFlags.NoDecoration;
|
||||
|
||||
ForceMainWindow = true;
|
||||
|
||||
windowSystem.AddWindow(this);
|
||||
IsOpen = true;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user