I should actually be working
This commit is contained in:
		| @@ -1,8 +1,10 @@ | ||||
| using Dalamud.Game.ClientState; | ||||
| using Dalamud.Plugin; | ||||
| using Dalamud.Plugin.Ipc; | ||||
| using MareSynchronos.FileCacheDB; | ||||
| using MareSynchronos.Models; | ||||
| 
 | ||||
| namespace MareSynchronos.Models | ||||
| namespace MareSynchronos.Factories | ||||
| { | ||||
|     public class FileReplacementFactory | ||||
|     { | ||||
| @@ -16,20 +18,22 @@ namespace MareSynchronos.Models | ||||
|             penumbraDirectory = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc().ToLower() + '\\'; | ||||
|             this.clientState = clientState; | ||||
|         } | ||||
|         public FileReplacement Create(string gamePath) | ||||
|         public FileReplacement Create(string gamePath, bool resolve = true) | ||||
|         { | ||||
|             var fileReplacement = new FileReplacement(gamePath, penumbraDirectory); | ||||
|             fileReplacement.SetReplacedPath(resolvePath.InvokeFunc(gamePath, clientState.LocalPlayer!.Name.ToString())); | ||||
|             if (!resolve) return fileReplacement; | ||||
| 
 | ||||
|             fileReplacement.SetResolvedPath(resolvePath.InvokeFunc(gamePath, clientState.LocalPlayer!.Name.ToString())); | ||||
|             if (!fileReplacement.HasFileReplacement) | ||||
|             { | ||||
|                 // try to resolve path with -- instead? | ||||
|                 // try to resolve path with --filename instead? | ||||
|                 string[] tempGamePath = gamePath.Split('/'); | ||||
|                 tempGamePath[tempGamePath.Length - 1] = "--" + tempGamePath[tempGamePath.Length - 1]; | ||||
|                 string newTempGamePath = string.Join('/', tempGamePath); | ||||
|                 var resolvedPath = resolvePath.InvokeFunc(newTempGamePath, clientState.LocalPlayer!.Name.ToString()); | ||||
|                 if (resolvedPath != newTempGamePath) | ||||
|                 { | ||||
|                     fileReplacement.SetReplacedPath(resolvedPath); | ||||
|                     fileReplacement.SetResolvedPath(resolvedPath); | ||||
|                     fileReplacement.SetGamePath(newTempGamePath); | ||||
|                 } | ||||
|             } | ||||
| @@ -1,14 +1,17 @@ | ||||
| using Dalamud.Game.ClientState; | ||||
| using Dalamud.Game.ClientState.Objects; | ||||
| using Dalamud.Game.Gui; | ||||
| using Dalamud.Hooking; | ||||
| using Dalamud.Logging; | ||||
| using Dalamud.Plugin; | ||||
| using Dalamud.Plugin.Ipc; | ||||
| using Dalamud.Utility.Signatures; | ||||
| using FFXIVClientStructs.FFXIV.Client.Game.Character; | ||||
| using FFXIVClientStructs.FFXIV.Client.Game.Object; | ||||
| using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; | ||||
| using FFXIVClientStructs.FFXIV.Client.System.Resource; | ||||
| using FFXIVClientStructs.FFXIV.Client.UI; | ||||
| using FFXIVClientStructs.FFXIV.Component.GUI; | ||||
| using MareSynchronos.Factories; | ||||
| using MareSynchronos.Models; | ||||
| using Penumbra.GameData.ByteString; | ||||
| using Penumbra.Interop.Structs; | ||||
| @@ -17,76 +20,143 @@ using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace MareSynchronos.Hooks | ||||
| { | ||||
|     public unsafe class DrawHooks : IDisposable | ||||
|     { | ||||
|         public const int ResolveMdlIdx = 73; | ||||
|         public const int ResolveMtrlIdx = 82; | ||||
|  | ||||
|         [Signature("E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40", DetourName = "CharacterBaseCreateDetour")] | ||||
|         public Hook<CharacterBaseCreateDelegate>? CharacterBaseCreateHook; | ||||
|         [Signature("E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30", | ||||
|             DetourName = "CharacterBaseDestructorDetour")] | ||||
|         public Hook<CharacterBaseDestructorDelegate>? CharacterBaseDestructorHook; | ||||
|         [Signature("48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress)] | ||||
|         public IntPtr* DrawObjectHumanVTable; | ||||
|         [Signature("E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0")] | ||||
|         public Hook<EnableDrawDelegate>? EnableDrawHook; | ||||
|         [Signature("4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour")] | ||||
|         public Hook<LoadMtrlFilesDelegate>? LoadMtrlTexHook; | ||||
|  | ||||
|         // [Signature( "48 8D 1D ?? ?? ?? ?? 48 C7 41", ScanType = ScanType.StaticAddress )] | ||||
|         // public IntPtr* DrawObjectVTable; | ||||
|         //  | ||||
|         // [Signature( "48 8D 05 ?? ?? ?? ?? 45 33 C0 48 89 03 BA", ScanType = ScanType.StaticAddress )] | ||||
|         // public IntPtr* DrawObjectDemihumanVTable; | ||||
|         //  | ||||
|         // [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0 48 89 83 ?? ?? ?? ?? 48 89 83 ?? ?? ?? ?? C7 83", ScanType = ScanType.StaticAddress )] | ||||
|         // public IntPtr* DrawObjectMonsterVTable; | ||||
|         //  | ||||
|         // public const int ResolveRootIdx  = 71; | ||||
|  | ||||
|         public const int ResolveSklbIdx = 72; | ||||
|         public const int ResolveMdlIdx = 73; | ||||
|         public const int ResolveSkpIdx = 74; | ||||
|         public const int ResolvePhybIdx = 75; | ||||
|         public const int ResolvePapIdx = 76; | ||||
|         public const int ResolveTmbIdx = 77; | ||||
|         public const int ResolveMPapIdx = 79; | ||||
|         public const int ResolveImcIdx = 81; | ||||
|         public const int ResolveMtrlIdx = 82; | ||||
|         public const int ResolveDecalIdx = 83; | ||||
|         public const int ResolveVfxIdx = 84; | ||||
|         public const int ResolveEidIdx = 85; | ||||
|         private readonly DalamudPluginInterface pluginInterface; | ||||
|         private readonly ClientState clientState; | ||||
|         private readonly ObjectTable objectTable; | ||||
|         private readonly FileReplacementFactory factory; | ||||
|  | ||||
|         public delegate IntPtr GeneralResolveDelegate(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4); | ||||
|         public delegate IntPtr MPapResolveDelegate(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5); | ||||
|         public delegate IntPtr MaterialResolveDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5); | ||||
|         public delegate IntPtr EidResolveDelegate(IntPtr drawObject, IntPtr path, IntPtr unk3); | ||||
|  | ||||
|         public delegate void OnModelLoadCompleteDelegate(IntPtr drawObject); | ||||
|  | ||||
|         public Hook<GeneralResolveDelegate>? ResolveDecalPathHook; | ||||
|         public Hook<EidResolveDelegate>? ResolveEidPathHook; | ||||
|         public Hook<GeneralResolveDelegate>? ResolveImcPathHook; | ||||
|         public Hook<MPapResolveDelegate>? ResolveMPapPathHook; | ||||
|         public Hook<GeneralResolveDelegate>? ResolveMdlPathHook; | ||||
|         public Hook<MaterialResolveDetour>? ResolveMtrlPathHook; | ||||
|         public Hook<MaterialResolveDetour>? ResolvePapPathHook; | ||||
|         public Hook<GeneralResolveDelegate>? ResolvePhybPathHook; | ||||
|         public Hook<GeneralResolveDelegate>? ResolveSklbPathHook; | ||||
|         public Hook<GeneralResolveDelegate>? ResolveSkpPathHook; | ||||
|         public Hook<EidResolveDelegate>? ResolveTmbPathHook; | ||||
|         public Hook<MaterialResolveDetour>? ResolveVfxPathHook; | ||||
|         private readonly ClientState clientState; | ||||
|         private readonly Dictionary<IntPtr, ushort> DrawObjectToObject = new(); | ||||
|         private readonly FileReplacementFactory factory; | ||||
|         private readonly GameGui gameGui; | ||||
|         private readonly ObjectTable objectTable; | ||||
|         private readonly DalamudPluginInterface pluginInterface; | ||||
|         private ConcurrentBag<FileReplacement> cachedResources = new(); | ||||
|         private GameObject* lastGameObject = null; | ||||
|         private ConcurrentBag<FileReplacement> loadedMaterials = new(); | ||||
|         private CharacterCache characterCache; | ||||
|  | ||||
|         public DrawHooks(DalamudPluginInterface pluginInterface, ClientState clientState, ObjectTable objectTable, FileReplacementFactory factory) | ||||
|         public DrawHooks(DalamudPluginInterface pluginInterface, ClientState clientState, ObjectTable objectTable, FileReplacementFactory factory, GameGui gameGui) | ||||
|         { | ||||
|             this.pluginInterface = pluginInterface; | ||||
|             this.clientState = clientState; | ||||
|             this.objectTable = objectTable; | ||||
|             this.factory = factory; | ||||
|             this.gameGui = gameGui; | ||||
|             characterCache = new CharacterCache(); | ||||
|             SignatureHelper.Initialise(this); | ||||
|         } | ||||
|  | ||||
|         public delegate IntPtr CharacterBaseCreateDelegate(uint a, IntPtr b, IntPtr c, byte d); | ||||
|         public delegate void CharacterBaseDestructorDelegate(IntPtr drawBase); | ||||
|         public delegate void EnableDrawDelegate(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d); | ||||
|         public delegate IntPtr GeneralResolveDelegate(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4); | ||||
|         public delegate byte LoadMtrlFilesDelegate(IntPtr mtrlResourceHandle); | ||||
|         public delegate IntPtr MaterialResolveDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5); | ||||
|         public delegate void OnModelLoadCompleteDelegate(IntPtr drawObject); | ||||
|         public void Dispose() | ||||
|         { | ||||
|             DisableHumanHooks(); | ||||
|             DisposeHumanHooks(); | ||||
|         } | ||||
|  | ||||
|         public CharacterCache BuildCharacterCache() | ||||
|         { | ||||
|             foreach (var resource in cachedResources) | ||||
|             { | ||||
|                 resource.IsInUse = false; | ||||
|                 resource.ImcData = string.Empty; | ||||
|             } | ||||
|  | ||||
|             var cache = new CharacterCache(); | ||||
|  | ||||
|             PluginLog.Debug("Invaldated character cache"); | ||||
|  | ||||
|             var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject(); | ||||
|             for (var idx = 0; idx < model->SlotCount; ++idx) | ||||
|             { | ||||
|                 var mdl = (RenderModel*)model->ModelArray[idx]; | ||||
|                 if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara) | ||||
|                 { | ||||
|                     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 texPath = new Utf8String(mtrlResource->TexString(resIdx)); | ||||
|  | ||||
|                         if (string.IsNullOrEmpty(texPath.ToString())) continue; | ||||
|  | ||||
|                         var texResource = factory.Create(texPath.ToString()); | ||||
|                         var cachedTexResource = cachedResources.First(r => r.IsReplacedByThis(texResource)); | ||||
|                         cache.AddAssociatedResource(cachedTexResource, cachedMdlResource, cachedMtrlResource); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return cache; | ||||
|         } | ||||
|  | ||||
|         public List<FileReplacement> PrintRequestedResources() | ||||
|         { | ||||
|             var cache = BuildCharacterCache(); | ||||
|  | ||||
|             PluginLog.Debug("--- CURRENTLY LOADED FILES ---"); | ||||
|  | ||||
|             PluginLog.Debug(cache.ToString()); | ||||
|  | ||||
|             PluginLog.Debug("--- LOOSE FILES ---"); | ||||
|  | ||||
|             foreach (var resource in cachedResources.Where(r => !r.IsInUse).OrderBy(a => a.GamePath)) | ||||
|             { | ||||
|                 PluginLog.Debug(resource.ToString()); | ||||
|             } | ||||
|  | ||||
|             return cache.FileReplacements; | ||||
|         } | ||||
|  | ||||
|         public void StartHooks() | ||||
|         { | ||||
|             allRequestedResources.Clear(); | ||||
|             cachedResources.Clear(); | ||||
|             SetupHumanHooks(); | ||||
|             EnableHumanHooks(); | ||||
|             PluginLog.Debug("Hooks enabled"); | ||||
| @@ -98,153 +168,151 @@ namespace MareSynchronos.Hooks | ||||
|             DisposeHumanHooks(); | ||||
|         } | ||||
|  | ||||
|         private void SetupHumanHooks() | ||||
|         private void AddRequestedResource(FileReplacement replacement) | ||||
|         { | ||||
|             if (ResolveDecalPathHook != null) return; | ||||
|  | ||||
|             ResolveDecalPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveDecalIdx], ResolveDecalDetour); | ||||
|             ResolveEidPathHook = new Hook<EidResolveDelegate>(DrawObjectHumanVTable[ResolveEidIdx], ResolveEidDetour); | ||||
|             ResolveImcPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveImcIdx], ResolveImcDetour); | ||||
|             ResolveMPapPathHook = new Hook<MPapResolveDelegate>(DrawObjectHumanVTable[ResolveMPapIdx], ResolveMPapDetour); | ||||
|             ResolveMdlPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveMdlIdx], ResolveMdlDetour); | ||||
|             ResolveMtrlPathHook = new Hook<MaterialResolveDetour>(DrawObjectHumanVTable[ResolveMtrlIdx], ResolveMtrlDetour); | ||||
|             ResolvePapPathHook = new Hook<MaterialResolveDetour>(DrawObjectHumanVTable[ResolvePapIdx], ResolvePapDetour); | ||||
|             ResolvePhybPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolvePhybIdx], ResolvePhybDetour); | ||||
|             ResolveSklbPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveSklbIdx], ResolveSklbDetour); | ||||
|             ResolveSkpPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveSkpIdx], ResolveSkpDetour); | ||||
|             ResolveTmbPathHook = new Hook<EidResolveDelegate>(DrawObjectHumanVTable[ResolveTmbIdx], ResolveTmbDetour); | ||||
|             ResolveVfxPathHook = new Hook<MaterialResolveDetour>(DrawObjectHumanVTable[ResolveVfxIdx], ResolveVfxDetour); | ||||
|             if (!cachedResources.Any(a => a.IsReplacedByThis(replacement))) | ||||
|             { | ||||
|                 cachedResources.Add(replacement); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void EnableHumanHooks() | ||||
|         private IntPtr CharacterBaseCreateDetour(uint a, IntPtr b, IntPtr c, byte d) | ||||
|         { | ||||
|             if (ResolveDecalPathHook?.IsEnabled ?? false) return; | ||||
|             PluginLog.Debug("Character base detour"); | ||||
|             var ret = CharacterBaseCreateHook!.Original(a, b, c, d); | ||||
|             if (lastGameObject != null) | ||||
|             { | ||||
|                 DrawObjectToObject[ret] = (lastGameObject->ObjectIndex); | ||||
|             } | ||||
|  | ||||
|             ResolveDecalPathHook?.Enable(); | ||||
|             //ResolveEidPathHook?.Enable(); | ||||
|             //ResolveImcPathHook?.Enable(); | ||||
|             //ResolveMPapPathHook?.Enable(); | ||||
|             ResolveMdlPathHook?.Enable(); | ||||
|             ResolveMtrlPathHook?.Enable(); | ||||
|             //ResolvePapPathHook?.Enable(); | ||||
|             //ResolvePhybPathHook?.Enable(); | ||||
|             //ResolveSklbPathHook?.Enable(); | ||||
|             //ResolveSkpPathHook?.Enable(); | ||||
|             //ResolveTmbPathHook?.Enable(); | ||||
|             //ResolveVfxPathHook?.Enable(); | ||||
|             EnableDrawHook?.Enable(); | ||||
|             LoadMtrlTexHook?.Enable(); | ||||
|             return ret; | ||||
|         } | ||||
|  | ||||
|         private void CharacterBaseDestructorDetour(IntPtr drawBase) | ||||
|         { | ||||
|             if (DrawObjectToObject.TryGetValue(drawBase, out ushort idx)) | ||||
|             { | ||||
|                 var gameObj = GetGameObjectFromDrawObject(drawBase, idx); | ||||
|                 if (gameObj == (GameObject*)clientState.LocalPlayer!.Address) | ||||
|                 { | ||||
|                     PluginLog.Debug("Clearing resources"); | ||||
|                     cachedResources.Clear(); | ||||
|                     DrawObjectToObject.Clear(); | ||||
|                 } | ||||
|             } | ||||
|             CharacterBaseDestructorHook!.Original.Invoke(drawBase); | ||||
|         } | ||||
|  | ||||
|         private void DisableHumanHooks() | ||||
|         { | ||||
|             ResolveDecalPathHook?.Disable(); | ||||
|             //ResolveEidPathHook?.Disable(); | ||||
|             //ResolveImcPathHook?.Disable(); | ||||
|             //ResolveMPapPathHook?.Disable(); | ||||
|             ResolveMdlPathHook?.Disable(); | ||||
|             ResolveMdlPathHook?.Disable(); | ||||
|             ResolveMtrlPathHook?.Disable(); | ||||
|             //ResolvePapPathHook?.Disable(); | ||||
|             //ResolvePhybPathHook?.Disable(); | ||||
|             //ResolveSklbPathHook?.Disable(); | ||||
|             //ResolveSkpPathHook?.Disable(); | ||||
|             //ResolveTmbPathHook?.Disable(); | ||||
|             //ResolveVfxPathHook?.Disable(); | ||||
|             EnableDrawHook?.Disable(); | ||||
|             LoadMtrlTexHook?.Disable(); | ||||
|             CharacterBaseCreateHook?.Disable(); | ||||
|             CharacterBaseDestructorHook?.Disable(); | ||||
|         } | ||||
|  | ||||
|         private void DisposeHumanHooks() | ||||
|         { | ||||
|             ResolveDecalPathHook?.Dispose(); | ||||
|             //ResolveEidPathHook?.Dispose(); | ||||
|             //ResolveImcPathHook?.Dispose(); | ||||
|             //ResolveMPapPathHook?.Dispose(); | ||||
|             ResolveMdlPathHook?.Dispose(); | ||||
|             ResolveMtrlPathHook?.Dispose(); | ||||
|             //ResolvePapPathHook?.Dispose(); | ||||
|             //ResolvePhybPathHook?.Dispose(); | ||||
|             //ResolveSklbPathHook?.Dispose(); | ||||
|             //ResolveSkpPathHook?.Dispose(); | ||||
|             //ResolveTmbPathHook?.Dispose(); | ||||
|             //ResolveVfxPathHook?.Dispose(); | ||||
|             EnableDrawHook?.Dispose(); | ||||
|             LoadMtrlTexHook?.Dispose(); | ||||
|             CharacterBaseCreateHook?.Dispose(); | ||||
|             CharacterBaseDestructorHook?.Dispose(); | ||||
|         } | ||||
|  | ||||
|         // Humans | ||||
|         private IntPtr ResolveDecalDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4) | ||||
|             => ResolvePathDetour(drawObject, ResolveDecalPathHook!.Original(drawObject, path, unk3, unk4)); | ||||
|  | ||||
|         private IntPtr ResolveEidDetour(IntPtr drawObject, IntPtr path, IntPtr unk3) | ||||
|             => ResolvePathDetour(drawObject, ResolveEidPathHook!.Original(drawObject, path, unk3)); | ||||
|  | ||||
|         private IntPtr ResolveImcDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4) | ||||
|             => ResolvePathDetour(drawObject, ResolveImcPathHook!.Original(drawObject, path, unk3, unk4)); | ||||
|  | ||||
|         private IntPtr ResolveMPapDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5) | ||||
|             => ResolvePathDetour(drawObject, ResolveMPapPathHook!.Original(drawObject, path, unk3, unk4, unk5)); | ||||
|  | ||||
|         private IntPtr ResolveMdlDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType) | ||||
|         { | ||||
|             return ResolvePathDetour(drawObject, ResolveMdlPathHook!.Original(drawObject, path, unk3, modelType)); | ||||
|         } | ||||
|  | ||||
|         private IntPtr ResolveMtrlDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5) | ||||
|             => ResolvePathDetour(drawObject, ResolveMtrlPathHook!.Original(drawObject, path, unk3, unk4, unk5)); | ||||
|  | ||||
|         private IntPtr ResolvePapDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5) | ||||
|         { | ||||
|             return ResolvePathDetour(drawObject, ResolvePapPathHook!.Original(drawObject, path, unk3, unk4, unk5)); | ||||
|         } | ||||
|  | ||||
|         private IntPtr ResolvePhybDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4) | ||||
|         { | ||||
|             return ResolvePathDetour(drawObject, ResolvePhybPathHook!.Original(drawObject, path, unk3, unk4)); | ||||
|         } | ||||
|  | ||||
|         private IntPtr ResolveSklbDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4) | ||||
|         { | ||||
|             return ResolvePathDetour(drawObject, ResolveSklbPathHook!.Original(drawObject, path, unk3, unk4)); | ||||
|         } | ||||
|  | ||||
|         private IntPtr ResolveSkpDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4) | ||||
|         { | ||||
|             return ResolvePathDetour(drawObject, ResolveSkpPathHook!.Original(drawObject, path, unk3, unk4)); | ||||
|         } | ||||
|  | ||||
|         private IntPtr ResolveTmbDetour(IntPtr drawObject, IntPtr path, IntPtr unk3) | ||||
|             => ResolvePathDetour(drawObject, ResolveTmbPathHook!.Original(drawObject, path, unk3)); | ||||
|  | ||||
|         private IntPtr ResolveVfxDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5) | ||||
|             => ResolvePathDetour(drawObject, ResolveVfxPathHook!.Original(drawObject, path, unk3, unk4, unk5)); | ||||
|  | ||||
|         public delegate void EnableDrawDelegate(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d); | ||||
|  | ||||
|         [Signature("E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0")] | ||||
|         public Hook<EnableDrawDelegate>? EnableDrawHook; | ||||
|  | ||||
|         public GameObject* LastGameObject { get; private set; } | ||||
|  | ||||
|         private void EnableDrawDetour(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d) | ||||
|         { | ||||
|             //PluginLog.Debug("Draw start"); | ||||
|             var oldObject = LastGameObject; | ||||
|             LastGameObject = (GameObject*)gameObject; | ||||
|             var oldObject = lastGameObject; | ||||
|             lastGameObject = (GameObject*)gameObject; | ||||
|             EnableDrawHook!.Original.Invoke(gameObject, b, c, d); | ||||
|             LastGameObject = oldObject; | ||||
|             //PluginLog.Debug("Draw end"); | ||||
|             lastGameObject = oldObject; | ||||
|         } | ||||
|  | ||||
|         public delegate byte LoadMtrlFilesDelegate(IntPtr mtrlResourceHandle); | ||||
|         [Signature("4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour")] | ||||
|         public Hook<LoadMtrlFilesDelegate>? LoadMtrlTexHook; | ||||
|  | ||||
|         private byte LoadMtrlTexDetour(IntPtr mtrlResourceHandle) | ||||
|         private void EnableHumanHooks() | ||||
|         { | ||||
|             LoadMtrlHelper(mtrlResourceHandle); | ||||
|             var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle); | ||||
|             return ret; | ||||
|             if (ResolveMdlPathHook?.IsEnabled ?? false) return; | ||||
|  | ||||
|             ResolveMdlPathHook?.Enable(); | ||||
|             ResolveMtrlPathHook?.Enable(); | ||||
|             EnableDrawHook?.Enable(); | ||||
|             LoadMtrlTexHook?.Enable(); | ||||
|             CharacterBaseCreateHook?.Enable(); | ||||
|             CharacterBaseDestructorHook?.Enable(); | ||||
|         } | ||||
|  | ||||
|         private string? GetCardName() | ||||
|         { | ||||
|             var uiModule = (UIModule*)gameGui.GetUIModule(); | ||||
|             var agentModule = uiModule->GetAgentModule(); | ||||
|             var agent = (byte*)agentModule->GetAgentByInternalID(393); | ||||
|             if (agent == null) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             var data = *(byte**)(agent + 0x28); | ||||
|             if (data == null) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             var block = data + 0x7A; | ||||
|             return new Utf8String(block).ToString(); | ||||
|         } | ||||
|  | ||||
|         private GameObject* GetGameObjectFromDrawObject(IntPtr drawObject, int gameObjectIdx) | ||||
|         { | ||||
|             var tmp = objectTable[gameObjectIdx]; | ||||
|             GameObject* gameObject; | ||||
|             if (tmp != null) | ||||
|             { | ||||
|                 gameObject = (GameObject*)tmp.Address; | ||||
|                 if (gameObject->DrawObject == (DrawObject*)drawObject) | ||||
|                 { | ||||
|                     return gameObject; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             DrawObjectToObject.Remove(drawObject); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         private string? GetGlamourName() | ||||
|         { | ||||
|             var addon = gameGui.GetAddonByName("MiragePrismMiragePlate", 1); | ||||
|             return addon == IntPtr.Zero ? null : GetPlayerName(); | ||||
|         } | ||||
|  | ||||
|         private string? GetInspectName() | ||||
|         { | ||||
|             var addon = gameGui.GetAddonByName("CharacterInspect", 1); | ||||
|             if (addon == IntPtr.Zero) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             var ui = (AtkUnitBase*)addon; | ||||
|             if (ui->UldManager.NodeListCount < 60) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             var text = (AtkTextNode*)ui->UldManager.NodeList[59]; | ||||
|             if (text == null || !text->AtkResNode.IsVisible) | ||||
|             { | ||||
|                 text = (AtkTextNode*)ui->UldManager.NodeList[60]; | ||||
|             } | ||||
|  | ||||
|             return text != null ? text->NodeText.ToString() : null; | ||||
|         } | ||||
|  | ||||
|         private string GetPlayerName() | ||||
|         { | ||||
|             return clientState.LocalPlayer!.Name.ToString(); | ||||
|         } | ||||
|  | ||||
|         private void LoadMtrlHelper(IntPtr mtrlResourceHandle) | ||||
| @@ -268,7 +336,7 @@ namespace MareSynchronos.Hooks | ||||
|                         AddRequestedResource(factory.Create(texPath.ToString())); | ||||
|                     } | ||||
|  | ||||
|                     loadedMaterials.Remove(existingMat); | ||||
|                     loadedMaterials = new(loadedMaterials.Except(new[] { existingMat })); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
| @@ -277,6 +345,19 @@ namespace MareSynchronos.Hooks | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private byte LoadMtrlTexDetour(IntPtr mtrlResourceHandle) | ||||
|         { | ||||
|             LoadMtrlHelper(mtrlResourceHandle); | ||||
|             var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle); | ||||
|             return ret; | ||||
|         } | ||||
|  | ||||
|         private IntPtr ResolveMdlDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType) | ||||
|         => ResolvePathDetour(drawObject, ResolveMdlPathHook!.Original(drawObject, path, unk3, modelType)); | ||||
|  | ||||
|         private IntPtr ResolveMtrlDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5) | ||||
|             => ResolvePathDetour(drawObject, ResolveMtrlPathHook!.Original(drawObject, path, unk3, unk4, unk5)); | ||||
|  | ||||
|         private unsafe IntPtr ResolvePathDetour(IntPtr drawObject, IntPtr path) | ||||
|         { | ||||
|             if (path == IntPtr.Zero) | ||||
| @@ -286,23 +367,34 @@ namespace MareSynchronos.Hooks | ||||
|  | ||||
|             var gamepath = new Utf8String((byte*)path); | ||||
|  | ||||
|             var playerName = clientState.LocalPlayer.Name.ToString(); | ||||
|             var playerName = GetPlayerName(); | ||||
|             var gameDrawObject = (DrawObject*)drawObject; | ||||
|             var playerDrawObject = ((Character*)clientState.LocalPlayer.Address)->GameObject.GetDrawObject(); | ||||
|             GameObject* gameObject = lastGameObject; | ||||
|  | ||||
|             if (LastGameObject != null && (LastGameObject->DrawObject == null || LastGameObject->DrawObject == gameDrawObject)) | ||||
|             if (DrawObjectToObject.TryGetValue(drawObject, out ushort idx)) | ||||
|             { | ||||
|                 var owner = new Utf8String(LastGameObject->Name).ToString(); | ||||
|                 if (owner != playerName) | ||||
|                 gameObject = GetGameObjectFromDrawObject(drawObject, DrawObjectToObject[drawObject]); | ||||
|             } | ||||
|  | ||||
|             if (gameObject != null && (gameObject->DrawObject == null || gameObject->DrawObject == gameDrawObject)) | ||||
|             { | ||||
|                 // 240, 241, 242 and 243 might need Penumbra config readout | ||||
|                 var actualName = gameObject->ObjectIndex switch | ||||
|                 { | ||||
|                     240 => GetPlayerName(), // character window | ||||
|                     241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor. | ||||
|                     242 => GetPlayerName(), // try-on | ||||
|                     243 => GetPlayerName(), // dye preview | ||||
|                     _ => null, | ||||
|                 } ?? new Utf8String(gameObject->Name).ToString(); | ||||
|  | ||||
|                 if (actualName != playerName) | ||||
|                 { | ||||
|                     return path; | ||||
|                 } | ||||
|  | ||||
|                 AddRequestedResource(factory.Create(gamepath.ToString())); | ||||
|             } | ||||
|             else if (playerDrawObject == gameDrawObject) | ||||
|             { | ||||
|                 var resource = factory.Create(gamepath.ToString()); | ||||
|  | ||||
|                 if (gamepath.ToString().EndsWith("mtrl")) | ||||
|                 { | ||||
|                     loadedMaterials.Add(resource); | ||||
| @@ -314,140 +406,12 @@ namespace MareSynchronos.Hooks | ||||
|             return path; | ||||
|         } | ||||
|  | ||||
|         List<FileReplacement> loadedMaterials = new(); | ||||
|         ConcurrentBag<FileReplacement> allRequestedResources = new(); | ||||
|  | ||||
|         public List<FileReplacement> PrintRequestedResources() | ||||
|         private void SetupHumanHooks() | ||||
|         { | ||||
|             foreach (var resource in allRequestedResources) | ||||
|             { | ||||
|                 PluginLog.Debug(resource.ToString()); | ||||
|             } | ||||
|             //PluginLog.Debug("---"); | ||||
|             if (ResolveMdlPathHook != null) return; | ||||
|  | ||||
|             var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject(); | ||||
|  | ||||
|             List<FileReplacement> fluctuatingResources = new(); | ||||
|  | ||||
|             for (var i = 0; i < model->SlotCount; ++i) | ||||
|             { | ||||
|                 var mdl = (RenderModel*)model->ModelArray[i]; | ||||
|  | ||||
|                 if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var resource = factory.Create(new Utf8String(mdl->ResourceHandle->FileName()).ToString()); | ||||
|  | ||||
|                 PluginLog.Debug("Checking model: " + resource); | ||||
|  | ||||
|                 var mdlResourceRepl = allRequestedResources.FirstOrDefault(r => r.IsReplacedByThis(resource)); | ||||
|  | ||||
|                 if (mdlResourceRepl != null) | ||||
|                 { | ||||
|                     //PluginLog.Debug("Fluctuating resource detected: " + mdlResourceRepl); | ||||
|                     //allRequestedResources.Remove(mdlResourceRepl); | ||||
|                     fluctuatingResources.Add(mdlResourceRepl); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     //var resolvedPath = ResolvePath(mdlFile); | ||||
|                     //if (resolvedPath != mdlFile) | ||||
|                     //{ | ||||
|                     //fluctuatingResources[mdlFile] = resolvedPath; | ||||
|                     //} | ||||
|                 } | ||||
|  | ||||
|                 for (int mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++) | ||||
|                 { | ||||
|                     var mtrl = (Material*)mdl->Materials[mtrlIdx]; | ||||
|  | ||||
|                     if (mtrl == null) continue; | ||||
|  | ||||
|                     var mtrlresource = factory.Create(new Utf8String(mtrl->ResourceHandle->FileName()).ToString().Split("|")[2]); | ||||
|  | ||||
|                     var mtrlResourceRepl = allRequestedResources.FirstOrDefault(r => r.IsReplacedByThis(mtrlresource)); | ||||
|                     if (mtrlResourceRepl != null) | ||||
|                     { | ||||
|                         mdlResourceRepl.AddAssociated(mtrlResourceRepl); | ||||
|                         //PluginLog.Debug("Fluctuating resource detected: " + mtrlResourceRepl); | ||||
|                         //allRequestedResources.Remove(mtrlResourceRepl); | ||||
|                         //fluctuatingResources.Add(mtrlResourceRepl); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         //var resolvedPath = ResolvePath(mtrlPath); | ||||
|                         //if (resolvedPath != mtrlPath) | ||||
|                         //{ | ||||
|                         //    fluctuatingResources[mtrlPath] = resolvedPath; | ||||
|                         //} | ||||
|                     } | ||||
|  | ||||
|                     var mtrlResource = (MtrlResource*)mtrl->ResourceHandle; | ||||
|  | ||||
|                     for (int resIdx = 0; resIdx < mtrlResource->NumTex; resIdx++) | ||||
|                     { | ||||
|                         var path = new Utf8String(mtrlResource->TexString(resIdx)); | ||||
|                         var gamePath = Utf8GamePath.FromString(path.ToString(), out var p, true) ? p : Utf8GamePath.Empty; | ||||
|  | ||||
|                         var texResource = factory.Create(path.ToString()); | ||||
|  | ||||
|                         var texResourceRepl = allRequestedResources.FirstOrDefault(r => r.IsReplacedByThis(texResource)); | ||||
|                         if (texResourceRepl != null) | ||||
|                         { | ||||
|                             //PluginLog.Debug("Fluctuating resource detected: " + texResourceRepl); | ||||
|                             //allRequestedResources.Remove(texResourceRepl); | ||||
|                             //fluctuatingResources.Add(texResourceRepl); | ||||
|                             mtrlResourceRepl.AddAssociated(texResourceRepl); | ||||
|                             //fluctuatingResources[existingResource.Key] = existingResource.Value; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             //var resolvedPath = ResolvePath(path.ToString()); | ||||
|                             //if (resolvedPath != path.ToString()) | ||||
|                             //{ | ||||
|                             //    fluctuatingResources[path.ToString()] = resolvedPath; | ||||
|                             //} | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             PluginLog.Debug("---"); | ||||
|  | ||||
|             foreach (var resource in fluctuatingResources.OrderBy(a => a.GamePath)) | ||||
|             { | ||||
|                 PluginLog.Debug(Environment.NewLine + resource.ToString()); | ||||
|             } | ||||
|  | ||||
|             PluginLog.Debug("---"); | ||||
|  | ||||
|             /*foreach (var resource in allRequestedResources.Where(r => r.HasFileReplacement && r.Associated.Count == 0).OrderBy(a => a.GamePath)) | ||||
|             { | ||||
|                 PluginLog.Debug(resource.ToString()); | ||||
|             }*/ | ||||
|  | ||||
|             return fluctuatingResources; | ||||
|         } | ||||
|  | ||||
|         private void AddRequestedResource(FileReplacement replacement) | ||||
|         { | ||||
|             if (allRequestedResources.Any(a => a.IsReplacedByThis(replacement))) | ||||
|             { | ||||
|                 PluginLog.Debug("Already added: " + replacement); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             PluginLog.Debug("Adding: " + replacement.GamePath); | ||||
|  | ||||
|             allRequestedResources.Add(replacement); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             DisableHumanHooks(); | ||||
|             DisposeHumanHooks(); | ||||
|             ResolveMdlPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveMdlIdx], ResolveMdlDetour); | ||||
|             ResolveMtrlPathHook = new Hook<MaterialResolveDetour>(DrawObjectHumanVTable[ResolveMtrlIdx], ResolveMtrlDetour); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,6 +32,7 @@ | ||||
|     </PackageReference> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.17" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
| @@ -71,12 +72,6 @@ | ||||
|       <HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath> | ||||
|       <Private>false</Private> | ||||
|     </Reference> | ||||
|     <Reference Include="Penumbra.GameData"> | ||||
|       <HintPath>..\..\..\..\..\AppData\Roaming\XIVLauncher\installedPlugins\Penumbra\0.5.0.5\Penumbra.GameData.dll</HintPath> | ||||
|     </Reference> | ||||
|     <Reference Include="Penumbra.PlayerWatch"> | ||||
|       <HintPath>..\..\..\..\..\AppData\Roaming\XIVLauncher\installedPlugins\Penumbra\0.5.0.5\Penumbra.PlayerWatch.dll</HintPath> | ||||
|     </Reference> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
							
								
								
									
										89
									
								
								MareSynchronos/Models/CharacterCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								MareSynchronos/Models/CharacterCache.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| using Dalamud.Logging; | ||||
| using Newtonsoft.Json; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace MareSynchronos.Models | ||||
| { | ||||
|     [JsonObject(MemberSerialization.OptIn)] | ||||
|     public class CharacterCache | ||||
|     { | ||||
|         public List<FileReplacement> FileReplacements { get; set; } = new List<FileReplacement>(); | ||||
|  | ||||
|         [JsonProperty] | ||||
|         public List<FileReplacement> AllReplacements => | ||||
|             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)) | ||||
|             .ToList(); | ||||
|  | ||||
|         public CharacterCache() | ||||
|         { | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public void Invalidate(List<FileReplacement>? fileReplacements = null) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 var fileReplacement = fileReplacements ?? FileReplacements.ToList(); | ||||
|                 foreach (var item in fileReplacement) | ||||
|                 { | ||||
|                     item.IsInUse = false; | ||||
|                     Invalidate(item.Associated); | ||||
|                     if (FileReplacements.Contains(item)) | ||||
|                     { | ||||
|                         FileReplacements.Remove(item); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 PluginLog.Debug(ex.Message); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public void AddAssociatedResource(FileReplacement resource, FileReplacement mdlParent, FileReplacement mtrlParent) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (resource == null) return; | ||||
|                 if (mdlParent == null) | ||||
|                 { | ||||
|                     resource.IsInUse = true; | ||||
|                     FileReplacements.Add(resource); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 FileReplacement replacement; | ||||
|  | ||||
|                 if (mtrlParent == null && (replacement = FileReplacements.SingleOrDefault(f => f == mdlParent)!) != null) | ||||
|                 { | ||||
|                     replacement.AddAssociated(resource); | ||||
|                 } | ||||
|  | ||||
|                 if ((replacement = FileReplacements.SingleOrDefault(f => f == mdlParent)?.Associated.SingleOrDefault(f => f == mtrlParent)!) != null) | ||||
|                 { | ||||
|                     replacement.AddAssociated(resource); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 PluginLog.Debug(ex.Message); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override string ToString() | ||||
|         { | ||||
|             StringBuilder stringBuilder = new StringBuilder(); | ||||
|             foreach (var fileReplacement in FileReplacements.OrderBy(a => a.GamePath)) | ||||
|             { | ||||
|                 stringBuilder.AppendLine(fileReplacement.ToString()); | ||||
|             } | ||||
|             return stringBuilder.ToString(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,21 +1,29 @@ | ||||
| using System; | ||||
| using Dalamud.Logging; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using Newtonsoft.Json; | ||||
| using MareSynchronos.FileCacheDB; | ||||
|  | ||||
| namespace MareSynchronos.Models | ||||
| { | ||||
|     [JsonObject(MemberSerialization.OptIn)] | ||||
|     public class FileReplacement | ||||
|     { | ||||
|         private readonly string penumbraDirectory; | ||||
|  | ||||
|         [JsonProperty] | ||||
|         public string GamePath { get; private set; } | ||||
|         public string ReplacedPath { get; private set; } = string.Empty; | ||||
|  | ||||
|         public string ResolvedPath { get; private set; } = string.Empty; | ||||
|         [JsonProperty] | ||||
|         public string Hash { get; set; } = string.Empty; | ||||
|         public bool IsInUse { get; set; } = false; | ||||
|         public List<FileReplacement> Associated { get; set; } = new List<FileReplacement>(); | ||||
|  | ||||
|         public bool HasFileReplacement => GamePath != ReplacedPath; | ||||
|         [JsonProperty] | ||||
|         public string ImcData { get; set; } = string.Empty; | ||||
|         public bool HasFileReplacement => GamePath != ResolvedPath; | ||||
|         public FileReplacement(string gamePath, string penumbraDirectory) | ||||
|         { | ||||
|             GamePath = gamePath; | ||||
| @@ -24,6 +32,8 @@ namespace MareSynchronos.Models | ||||
|  | ||||
|         public void AddAssociated(FileReplacement fileReplacement) | ||||
|         { | ||||
|             fileReplacement.IsInUse = true; | ||||
|  | ||||
|             if (!Associated.Any(a => a.IsReplacedByThis(fileReplacement))) | ||||
|             { | ||||
|                 Associated.Add(fileReplacement); | ||||
| @@ -35,31 +45,40 @@ namespace MareSynchronos.Models | ||||
|             GamePath = path; | ||||
|         } | ||||
|  | ||||
|         public void SetReplacedPath(string path) | ||||
|         public void SetResolvedPath(string path) | ||||
|         { | ||||
|             ReplacedPath = path.ToLower().Replace('/', '\\').Replace(penumbraDirectory, "").Replace('\\', '/'); | ||||
|             ResolvedPath = path.ToLower().Replace('/', '\\').Replace(penumbraDirectory, "").Replace('\\', '/'); | ||||
|             if (!HasFileReplacement) return; | ||||
|  | ||||
|             Task.Run(() => | ||||
|             { | ||||
|                 using FileCacheContext db = new FileCacheContext(); | ||||
|                 var fileCache = db.FileCaches.SingleOrDefault(f => f.Filepath == path.ToLower()); | ||||
|                 if (fileCache != null) | ||||
|                     Hash = fileCache.Hash; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         public bool IsReplacedByThis(string path) | ||||
|         { | ||||
|             return GamePath.ToLower() == path.ToLower() || ReplacedPath.ToLower() == path.ToLower(); | ||||
|             return GamePath.ToLower() == path.ToLower() || ResolvedPath.ToLower() == path.ToLower(); | ||||
|         } | ||||
|  | ||||
|         public bool IsReplacedByThis(FileReplacement replacement) | ||||
|         { | ||||
|             return IsReplacedByThis(replacement.GamePath) || IsReplacedByThis(replacement.ReplacedPath); | ||||
|             return IsReplacedByThis(replacement.GamePath) || IsReplacedByThis(replacement.ResolvedPath); | ||||
|         } | ||||
|  | ||||
|         public override string ToString() | ||||
|         { | ||||
|             StringBuilder builder = new StringBuilder(); | ||||
|             builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ReplacedPath}"); | ||||
|             builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ResolvedPath}"); | ||||
|             foreach (var l1 in Associated) | ||||
|             { | ||||
|                 builder.AppendLine($"  + Modded: {l1.HasFileReplacement} - {l1.GamePath} => {l1.ReplacedPath}"); | ||||
|                 builder.AppendLine($"  + Modded: {l1.HasFileReplacement} - {l1.GamePath} => {l1.ResolvedPath}"); | ||||
|                 foreach (var l2 in l1.Associated) | ||||
|                 { | ||||
|                     builder.AppendLine($"    + Modded: {l2.HasFileReplacement} - {l2.GamePath} => {l2.ReplacedPath}"); | ||||
|                     builder.AppendLine($"    + Modded: {l2.HasFileReplacement} - {l2.GamePath} => {l2.ResolvedPath}"); | ||||
|                 } | ||||
|             } | ||||
|             return builder.ToString(); | ||||
|   | ||||
							
								
								
									
										19
									
								
								MareSynchronos/PenumbraMod/DefaultMod.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								MareSynchronos/PenumbraMod/DefaultMod.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| using Newtonsoft.Json; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace MareSynchronos.PenumbraMod | ||||
| { | ||||
|     [JsonObject(MemberSerialization.OptOut)] | ||||
|     internal class DefaultMod | ||||
|     { | ||||
|         public string Name { get; set; } = "Default"; | ||||
|         public int Priority { get; set; } = 0; | ||||
|         public Dictionary<string, string> Files { get; set; } = new(); | ||||
|         public Dictionary<string, string> FileSwaps { get; set; } = new(); | ||||
|         public List<string> Manipulations { get; set; } = new(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								MareSynchronos/PenumbraMod/Meta.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								MareSynchronos/PenumbraMod/Meta.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| using Newtonsoft.Json; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace MareSynchronos.PenumbraMod | ||||
| { | ||||
|     [JsonObject(MemberSerialization.OptOut)] | ||||
|     internal class Meta | ||||
|     { | ||||
|         public int FileVersion { get; set; } = 1; | ||||
|         public string Name { get; set; } = string.Empty; | ||||
|         public string Author { get; set; } = string.Empty; | ||||
|         public string Description { get; set; } = string.Empty; | ||||
|         public string Version { get; set; } = "0"; | ||||
|         public string Website { get; set; } = string.Empty; | ||||
|         public long ImportDate { get; set; } = DateTime.Now.Ticks; | ||||
|     } | ||||
| } | ||||
| @@ -23,6 +23,14 @@ using System.Text; | ||||
| using Penumbra.GameData.Enums; | ||||
| using System; | ||||
| using MareSynchronos.Models; | ||||
| using Dalamud.Game.Gui; | ||||
| using MareSynchronos.PenumbraMod; | ||||
| using System.Text.Json; | ||||
| using System.Text.Encodings.Web; | ||||
| using System.Text.Unicode; | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Serialization; | ||||
| using System.Reflection; | ||||
|  | ||||
| namespace SamplePlugin | ||||
| { | ||||
| @@ -30,7 +38,8 @@ namespace SamplePlugin | ||||
|     { | ||||
|         public string Name => "Mare Synchronos"; | ||||
|  | ||||
|         private const string commandName = "/pscan"; | ||||
|         private const string commandName = "/mare"; | ||||
|         private readonly ClientState clientState; | ||||
|  | ||||
|         private DalamudPluginInterface PluginInterface { get; init; } | ||||
|         private CommandManager CommandManager { get; init; } | ||||
| @@ -45,11 +54,11 @@ namespace SamplePlugin | ||||
|         public Plugin( | ||||
|             [RequiredVersion("1.0")] DalamudPluginInterface pluginInterface, | ||||
|             [RequiredVersion("1.0")] CommandManager commandManager, | ||||
|             Framework framework, ObjectTable objectTable, ClientState clientState, DataManager dataManager) | ||||
|             Framework framework, ObjectTable objectTable, ClientState clientState, DataManager dataManager, GameGui gameGui) | ||||
|         { | ||||
|             this.PluginInterface = pluginInterface; | ||||
|             this.CommandManager = commandManager; | ||||
|  | ||||
|             this.clientState = clientState; | ||||
|             this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); | ||||
|             this.Configuration.Initialize(this.PluginInterface); | ||||
|  | ||||
| @@ -67,7 +76,7 @@ namespace SamplePlugin | ||||
|             this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI; | ||||
|  | ||||
|             playerWatch = PlayerWatchFactory.Create(framework, clientState, objectTable); | ||||
|             drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new MareSynchronos.Models.FileReplacementFactory(pluginInterface, clientState)); | ||||
|             drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new FileReplacementFactory(pluginInterface, clientState), gameGui); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
| @@ -87,6 +96,17 @@ namespace SamplePlugin | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if(args == "playerdata") | ||||
|             { | ||||
|                 PluginLog.Debug(PluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization").InvokeFunc()); | ||||
|             } | ||||
|  | ||||
|             if(args == "applyglam") | ||||
|             { | ||||
|                 PluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization") | ||||
|                     .InvokeAction("Ah3/DwQBAR4IBHOABIOceTIIApkDAgADQmQBZJqepQZlAAEAAAAAAAAAAACcEwEAyxcBbrAXAUnKFwJIuBcBBkYAAQBIAAEANQABADUAAQACAAQAAQAAAIA/Eg==", "Ilya Zhelmo"); | ||||
|             } | ||||
|  | ||||
|             if (args == "scan") | ||||
|             { | ||||
|                 cts = new CancellationTokenSource(); | ||||
| @@ -116,45 +136,81 @@ namespace SamplePlugin | ||||
|                 var resources = drawHooks.PrintRequestedResources(); | ||||
|             } | ||||
|  | ||||
|             if (args == "copy") | ||||
|             if (args == "printjson") | ||||
|             { | ||||
|                 var cache = drawHooks.BuildCharacterCache(); | ||||
|                 var json = JsonConvert.SerializeObject(cache, Formatting.Indented); | ||||
|                 PluginLog.Debug(json); | ||||
|             } | ||||
|  | ||||
|             if (args == "createtestmod") | ||||
|             { | ||||
|                 var resources = drawHooks.PrintRequestedResources(); | ||||
|                 Task.Run(() => | ||||
|                 { | ||||
|                     PluginLog.Debug("Copying files"); | ||||
|                     foreach (var file in Directory.GetFiles(@"G:\Penumbra\TestMod\files")) | ||||
|                     var playerName = clientState.LocalPlayer!.Name.ToString(); | ||||
|                     var modName = $"Mare Synchronos Test Mod {playerName}"; | ||||
|                     var modDirectory = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc(); | ||||
|                     string modDirectoryPath = Path.Combine(modDirectory, modName); | ||||
|                     if (Directory.Exists(modDirectoryPath)) | ||||
|                     { | ||||
|                         File.Delete(file); | ||||
|                         Directory.Delete(modDirectoryPath, true); | ||||
|                     } | ||||
|                     File.Delete(@"G:\Penumbra\testmod\filelist.txt"); | ||||
|                     using FileCacheContext db = new FileCacheContext(); | ||||
|  | ||||
|                     Directory.CreateDirectory(modDirectoryPath); | ||||
|                     Directory.CreateDirectory(Path.Combine(modDirectoryPath, "files")); | ||||
|                     Meta meta = new Meta() | ||||
|                     { | ||||
|                         Name = modName, | ||||
|                         Author = playerName, | ||||
|                         Description = "Mare Synchronous Test Mod Export", | ||||
|                     }; | ||||
|  | ||||
|                     var resources = drawHooks.PrintRequestedResources(); | ||||
|                     var metaJson = JsonConvert.SerializeObject(meta); | ||||
|                     File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson); | ||||
|  | ||||
|                     DefaultMod defaultMod = new DefaultMod(); | ||||
|  | ||||
|                     using var db = new FileCacheContext(); | ||||
|                     foreach (var resource in resources) | ||||
|                     { | ||||
|                         CopyRecursive(resource, db); | ||||
|                         CopyRecursive(resource, modDirectoryPath, db, defaultMod.Files); | ||||
|                     } | ||||
|  | ||||
|                     var defaultModJson = JsonConvert.SerializeObject(defaultMod); | ||||
|                     File.WriteAllText(Path.Combine(modDirectoryPath, "default_mod.json"), defaultModJson); | ||||
|  | ||||
|                     PluginLog.Debug("Mod created to " + modDirectoryPath); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void CopyRecursive(FileReplacement replacement, FileCacheContext db) | ||||
|         private void CopyRecursive(FileReplacement replacement, string targetDirectory, FileCacheContext db, Dictionary<string, string>? resourceDict = null) | ||||
|         { | ||||
|             if (replacement.HasFileReplacement) | ||||
|             { | ||||
|                 PluginLog.Debug("Copying file \"" + replacement.ReplacedPath + "\""); | ||||
|                 PluginLog.Debug("Copying file \"" + replacement.ResolvedPath + "\""); | ||||
|  | ||||
|                 var fileCache = db.FileCaches.Single(f => f.Filepath.Contains(replacement.ReplacedPath.Replace('/', '\\'))); | ||||
|                 var fileCache = db.FileCaches.Single(f => f.Filepath.Contains(replacement.ResolvedPath.Replace('/', '\\'))); | ||||
|                 try | ||||
|                 { | ||||
|                     var ext = new FileInfo(fileCache.Filepath).Extension; | ||||
|                     File.Copy(fileCache.Filepath, Path.Combine(@"G:\Penumbra\TestMod\files", fileCache.Hash.ToLower() + ext)); | ||||
|                     File.AppendAllLines(Path.Combine(@"G:\Penumbra\TestMod", "filelist.txt"), new[] { $"\"{replacement.GamePath}\": \"files\\\\{fileCache.Hash.ToLower() + ext}\"," }); | ||||
|                     File.Copy(fileCache.Filepath, Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext)); | ||||
|                     if (resourceDict != null) | ||||
|                     { | ||||
|                         resourceDict[replacement.GamePath] = $"files\\{fileCache.Hash.ToLower() + ext}"; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         File.AppendAllLines(Path.Combine(targetDirectory, "filelist.txt"), new[] { $"\"{replacement.GamePath}\": \"files\\\\{fileCache.Hash.ToLower() + ext}\"," }); | ||||
|                     } | ||||
|                 } | ||||
|                 catch { } | ||||
|             } | ||||
|  | ||||
|             foreach (var associated in replacement.Associated) | ||||
|             { | ||||
|                 CopyRecursive(associated, db); | ||||
|                 CopyRecursive(associated, targetDirectory, db, resourceDict); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -174,7 +230,7 @@ namespace SamplePlugin | ||||
|         { | ||||
|             Stopwatch st = Stopwatch.StartNew(); | ||||
|  | ||||
|             string penumbraDir = Configuration.PenumbraFolder; | ||||
|             string penumbraDir = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc(); | ||||
|             PluginLog.Debug("Getting files from " + penumbraDir); | ||||
|             ConcurrentDictionary<string, bool> charaFiles = new ConcurrentDictionary<string, bool>( | ||||
|                 Directory.GetFiles(penumbraDir, "*.*", SearchOption.AllDirectories) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stanley Dimant
					Stanley Dimant