diff --git a/MareSynchronos/Factories/FileReplacementFactory.cs b/MareSynchronos/Factories/FileReplacementFactory.cs index 8218bd6..7f56dab 100644 --- a/MareSynchronos/Factories/FileReplacementFactory.cs +++ b/MareSynchronos/Factories/FileReplacementFactory.cs @@ -8,11 +8,13 @@ namespace MareSynchronos.Factories { private readonly IpcManager ipcManager; private readonly ClientState clientState; + private string playerName; public FileReplacementFactory(IpcManager ipcManager, ClientState clientState) { this.ipcManager = ipcManager; this.clientState = clientState; + playerName = null!; } public FileReplacement Create(string gamePath, bool resolve = true) @@ -20,7 +22,10 @@ namespace MareSynchronos.Factories var fileReplacement = new FileReplacement(gamePath, ipcManager.PenumbraModDirectory()!); if (!resolve) return fileReplacement; - string playerName = clientState.LocalPlayer!.Name.ToString(); + if(clientState.LocalPlayer != null) + { + playerName = clientState.LocalPlayer.Name.ToString(); + } fileReplacement.SetResolvedPath(ipcManager.PenumbraResolvePath(gamePath, playerName)!); if (!fileReplacement.HasFileReplacement) { diff --git a/MareSynchronos/Hooks/DrawHooks.cs b/MareSynchronos/Hooks/DrawHooks.cs index 597fc33..212a33a 100644 --- a/MareSynchronos/Hooks/DrawHooks.cs +++ b/MareSynchronos/Hooks/DrawHooks.cs @@ -50,7 +50,7 @@ namespace MareSynchronos.Hooks private readonly GameGui gameGui; private readonly ObjectTable objectTable; private readonly DalamudPluginInterface pluginInterface; - private ConcurrentBag cachedResources = new(); + private ConcurrentDictionary cachedResources = new(); private GameObject* lastGameObject = null; private ConcurrentBag loadedMaterials = new(); @@ -81,57 +81,63 @@ namespace MareSynchronos.Hooks { foreach (var resource in cachedResources) { - resource.IsInUse = false; - resource.ImcData = string.Empty; + resource.Value.IsInUse = false; + resource.Value.ImcData = string.Empty; } - PluginLog.Debug("Invaldated resource cache"); + PluginLog.Verbose("Invaldated resource cache"); var cache = new CharacterCache(); - var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject(); - for (var idx = 0; idx < model->SlotCount; ++idx) + try { - var mdl = (RenderModel*)model->ModelArray[idx]; - if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara) + var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject(); + for (var idx = 0; idx < model->SlotCount; ++idx) { - continue; - } - - var mdlResource = factory.Create(new Utf8String(mdl->ResourceHandle->FileName()).ToString()); - var cachedMdlResource = cachedResources.First(r => r.IsReplacedByThis(mdlResource)); - - var imc = (ResourceHandle*)model->IMCArray[idx]; - if (imc != null) - { - byte[] imcData = new byte[imc->Data->DataLength]; - Marshal.Copy((IntPtr)imc->Data->DataPtr, imcData, 0, (int)imc->Data->DataLength); - string imcDataStr = BitConverter.ToString(imcData).Replace("-", ""); - cachedMdlResource.ImcData = imcDataStr; - } - cache.AddAssociatedResource(cachedMdlResource, null!, null!); - - for (int mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++) - { - var mtrl = (Material*)mdl->Materials[mtrlIdx]; - if (mtrl == null) continue; - - var mtrlFileResource = factory.Create(new Utf8String(mtrl->ResourceHandle->FileName()).ToString().Split("|")[2]); - var cachedMtrlResource = cachedResources.First(r => r.IsReplacedByThis(mtrlFileResource)); - cache.AddAssociatedResource(cachedMtrlResource, cachedMdlResource, null!); - - var mtrlResource = (MtrlResource*)mtrl->ResourceHandle; - for (int resIdx = 0; resIdx < mtrlResource->NumTex; resIdx++) + var mdl = (RenderModel*)model->ModelArray[idx]; + if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara) { - var texPath = new Utf8String(mtrlResource->TexString(resIdx)); + continue; + } - if (string.IsNullOrEmpty(texPath.ToString())) continue; + var mdlResource = factory.Create(new Utf8String(mdl->ResourceHandle->FileName()).ToString()); + var cachedMdlResource = cachedResources.First(r => r.Value.IsReplacedByThis(mdlResource)).Value; - var texResource = factory.Create(texPath.ToString()); - var cachedTexResource = cachedResources.First(r => r.IsReplacedByThis(texResource)); - cache.AddAssociatedResource(cachedTexResource, cachedMdlResource, cachedMtrlResource); + var imc = (ResourceHandle*)model->IMCArray[idx]; + if (imc != null) + { + byte[] imcData = new byte[imc->Data->DataLength]; + Marshal.Copy((IntPtr)imc->Data->DataPtr, imcData, 0, (int)imc->Data->DataLength); + string imcDataStr = BitConverter.ToString(imcData).Replace("-", ""); + cachedMdlResource.ImcData = imcDataStr; + } + cache.AddAssociatedResource(cachedMdlResource, null!, null!); + + for (int mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++) + { + var mtrl = (Material*)mdl->Materials[mtrlIdx]; + if (mtrl == null) continue; + + var mtrlFileResource = factory.Create(new Utf8String(mtrl->ResourceHandle->FileName()).ToString().Split("|")[2]); + var cachedMtrlResource = cachedResources.First(r => r.Value.IsReplacedByThis(mtrlFileResource)).Value; + cache.AddAssociatedResource(cachedMtrlResource, cachedMdlResource, null!); + + var mtrlResource = (MtrlResource*)mtrl->ResourceHandle; + for (int resIdx = 0; resIdx < mtrlResource->NumTex; resIdx++) + { + var texPath = new Utf8String(mtrlResource->TexString(resIdx)); + + if (string.IsNullOrEmpty(texPath.ToString())) continue; + + var texResource = factory.Create(texPath.ToString()); + var cachedTexResource = cachedResources.First(r => r.Value.IsReplacedByThis(texResource)).Value; + cache.AddAssociatedResource(cachedTexResource, cachedMdlResource, cachedMtrlResource); + } } } + } catch (Exception ex) + { + PluginLog.Error(ex, ex.Message); } return cache; @@ -141,15 +147,15 @@ namespace MareSynchronos.Hooks { var cache = BuildCharacterCache(); - PluginLog.Debug("--- CURRENTLY LOADED FILES ---"); + PluginLog.Verbose("--- CURRENTLY LOADED FILES ---"); - PluginLog.Debug(cache.ToString()); + PluginLog.Verbose(cache.ToString()); - PluginLog.Debug("--- LOOSE FILES ---"); + PluginLog.Verbose("--- LOOSE FILES ---"); - foreach (var resource in cachedResources.Where(r => !r.IsInUse).OrderBy(a => a.GamePath)) + foreach (var resource in cachedResources.Where(r => !r.Value.IsInUse).OrderBy(a => a.Value.GamePath)) { - PluginLog.Debug(resource.ToString()); + PluginLog.Verbose(resource.ToString()); } return cache.FileReplacements; @@ -160,7 +166,7 @@ namespace MareSynchronos.Hooks cachedResources.Clear(); SetupHumanHooks(); EnableHumanHooks(); - PluginLog.Debug("Hooks enabled"); + PluginLog.Verbose("Hooks enabled"); } public void StopHooks() @@ -171,9 +177,9 @@ namespace MareSynchronos.Hooks private void AddRequestedResource(FileReplacement replacement) { - if (!cachedResources.Any(a => a.IsReplacedByThis(replacement))) + if (!cachedResources.Any(a => a.Value.IsReplacedByThis(replacement)) || cachedResources.Any(c => c.Key == replacement.GamePath)) { - cachedResources.Add(replacement); + cachedResources[replacement.GamePath] = replacement; } } @@ -195,8 +201,8 @@ namespace MareSynchronos.Hooks var gameObj = GetGameObjectFromDrawObject(drawBase, idx); if (clientState.LocalPlayer != null && gameObj == (GameObject*)clientState.LocalPlayer!.Address) { - PluginLog.Debug("Clearing resources"); - cachedResources.Clear(); + PluginLog.Verbose("Clearing resources"); + //cachedResources.Clear(); DrawObjectToObject.Clear(); } } @@ -326,13 +332,17 @@ namespace MareSynchronos.Hooks { var mtrl = (MtrlResource*)mtrlResourceHandle; var mtrlPath = Utf8String.FromSpanUnsafe(mtrl->Handle.FileNameSpan(), true, null, true); + PluginLog.Verbose("Attempting to resolve: " + mtrlPath.ToString()); var mtrlResource = factory.Create(mtrlPath.ToString()); var existingMat = loadedMaterials.FirstOrDefault(m => m.IsReplacedByThis(mtrlResource)); if (existingMat != null) { + PluginLog.Verbose("Resolving material: " + existingMat.GamePath); for (int i = 0; i < mtrl->NumTex; i++) { var texPath = new Utf8String(mtrl->TexString(i)); + PluginLog.Verbose("Resolving tex: " + texPath.ToString()); + AddRequestedResource(factory.Create(texPath.ToString())); } @@ -347,8 +357,8 @@ namespace MareSynchronos.Hooks private byte LoadMtrlTexDetour(IntPtr mtrlResourceHandle) { - if (clientState.LocalPlayer != null) - LoadMtrlHelper(mtrlResourceHandle); + //if (clientState.LocalPlayer != null) + LoadMtrlHelper(mtrlResourceHandle); var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle); return ret; } @@ -394,6 +404,7 @@ namespace MareSynchronos.Hooks return path; } + PluginLog.Verbose("Resolving resource: " + gamepath.ToString()); PlayerLoadEvent?.Invoke((IntPtr)gameObject, new EventArgs()); var resource = factory.Create(gamepath.ToString()); diff --git a/MareSynchronos/MareSynchronos.json b/MareSynchronos/MareSynchronos.json index 84ddd63..c721c6d 100644 --- a/MareSynchronos/MareSynchronos.json +++ b/MareSynchronos/MareSynchronos.json @@ -1,5 +1,5 @@ { - "Author": "your name here", + "Author": "darkarchon", "Name": "Mare Synchronos", "Punchline": "", "Description": "", @@ -9,5 +9,7 @@ "sample", "plugin", "goats" - ] + ], + "IconUrl": "https://raw.githubusercontent.com/Penumbra-Sync/client/main/MareSynchronos/images/logo.png", + "RepoUrl": "https://github.com/Penumbra-Sync/client" } diff --git a/MareSynchronos/Models/CharacterCache.cs b/MareSynchronos/Models/CharacterCache.cs index 71a1c81..b67f796 100644 --- a/MareSynchronos/Models/CharacterCache.cs +++ b/MareSynchronos/Models/CharacterCache.cs @@ -16,7 +16,7 @@ namespace MareSynchronos.Models FileReplacements.Where(x => x.HasFileReplacement) .Concat(FileReplacements.SelectMany(f => f.Associated).Where(f => f.HasFileReplacement)) .Concat(FileReplacements.SelectMany(f => f.Associated).SelectMany(f => f.Associated).Where(f => f.HasFileReplacement)) - .Distinct() + .Distinct().OrderBy(f => f.GamePath) .ToList(); public List FileReplacements { get; set; } = new List(); @@ -26,6 +26,9 @@ namespace MareSynchronos.Models public bool IsReady => FileReplacements.All(f => f.Computed); + [JsonProperty] + public string CacheHash { get; set; } = string.Empty; + [JsonProperty] public uint JobId { get; set; } = 0; public void AddAssociatedResource(FileReplacement resource, FileReplacement mdlParent, FileReplacement mtrlParent) diff --git a/MareSynchronos/Models/FileReplacement.cs b/MareSynchronos/Models/FileReplacement.cs index c6cf015..dfc31f2 100644 --- a/MareSynchronos/Models/FileReplacement.cs +++ b/MareSynchronos/Models/FileReplacement.cs @@ -142,5 +142,16 @@ namespace MareSynchronos.Models return base.Equals(obj); } + + public override int GetHashCode() + { + int result = 13; + result *= 397; + result += Hash.GetHashCode(); + result += GamePath.GetHashCode(); + result += ImcData.GetHashCode(); + + return result; + } } } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index f98c1ae..12a4f9c 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -32,6 +32,7 @@ using Newtonsoft.Json.Serialization; using System.Reflection; using MareSynchronos.Managers; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using MareSynchronos.Utils; namespace MareSynchronos { @@ -132,10 +133,12 @@ namespace MareSynchronos var obj = (GameObject*)(IntPtr)sender; drawHookTask = Task.Run(() => { + PluginLog.Debug("Waiting for charater to be drawn"); while ((obj->RenderFlags & 0b100000000000) == 0b100000000000) // 0b100000000000 is "still rendering" or something { Thread.Sleep(10); } + PluginLog.Debug("Character finished drawing"); // we should recalculate cache here // probably needs a different method @@ -174,6 +177,10 @@ namespace MareSynchronos await Task.Delay(50); } var json = JsonConvert.SerializeObject(cache, Formatting.Indented); + + cache.CacheHash = Crypto.GetHash(json); + + json = JsonConvert.SerializeObject(cache, Formatting.Indented); PluginLog.Debug(json); }); } diff --git a/MareSynchronos/images/logo.png b/MareSynchronos/images/logo.png index e5b9e28..550e79b 100644 Binary files a/MareSynchronos/images/logo.png and b/MareSynchronos/images/logo.png differ