merge 0.4.0 into main
This commit is contained in:
		| @@ -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); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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 _); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
							
								
								
									
										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> |   <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> | ||||||
|   | |||||||
| @@ -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 | ||||||
|             }; |             }; | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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); | ||||||
| @@ -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); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
| @@ -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; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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() | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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() | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -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!; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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)) | ||||||
|                 { |                 { | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Stanley Dimant
					Stanley Dimant