I should actually be working
This commit is contained in:
		| @@ -1,8 +1,10 @@ | |||||||
| using Dalamud.Game.ClientState; | using Dalamud.Game.ClientState; | ||||||
| using Dalamud.Plugin; | using Dalamud.Plugin; | ||||||
| using Dalamud.Plugin.Ipc; | using Dalamud.Plugin.Ipc; | ||||||
|  | using MareSynchronos.FileCacheDB; | ||||||
|  | using MareSynchronos.Models; | ||||||
| 
 | 
 | ||||||
| namespace MareSynchronos.Models | namespace MareSynchronos.Factories | ||||||
| { | { | ||||||
|     public class FileReplacementFactory |     public class FileReplacementFactory | ||||||
|     { |     { | ||||||
| @@ -16,20 +18,22 @@ namespace MareSynchronos.Models | |||||||
|             penumbraDirectory = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc().ToLower() + '\\'; |             penumbraDirectory = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc().ToLower() + '\\'; | ||||||
|             this.clientState = clientState; |             this.clientState = clientState; | ||||||
|         } |         } | ||||||
|         public FileReplacement Create(string gamePath) |         public FileReplacement Create(string gamePath, bool resolve = true) | ||||||
|         { |         { | ||||||
|             var fileReplacement = new FileReplacement(gamePath, penumbraDirectory); |             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) |             if (!fileReplacement.HasFileReplacement) | ||||||
|             { |             { | ||||||
|                 // try to resolve path with -- instead? |                 // try to resolve path with --filename instead? | ||||||
|                 string[] tempGamePath = gamePath.Split('/'); |                 string[] tempGamePath = gamePath.Split('/'); | ||||||
|                 tempGamePath[tempGamePath.Length - 1] = "--" + tempGamePath[tempGamePath.Length - 1]; |                 tempGamePath[tempGamePath.Length - 1] = "--" + tempGamePath[tempGamePath.Length - 1]; | ||||||
|                 string newTempGamePath = string.Join('/', tempGamePath); |                 string newTempGamePath = string.Join('/', tempGamePath); | ||||||
|                 var resolvedPath = resolvePath.InvokeFunc(newTempGamePath, clientState.LocalPlayer!.Name.ToString()); |                 var resolvedPath = resolvePath.InvokeFunc(newTempGamePath, clientState.LocalPlayer!.Name.ToString()); | ||||||
|                 if (resolvedPath != newTempGamePath) |                 if (resolvedPath != newTempGamePath) | ||||||
|                 { |                 { | ||||||
|                     fileReplacement.SetReplacedPath(resolvedPath); |                     fileReplacement.SetResolvedPath(resolvedPath); | ||||||
|                     fileReplacement.SetGamePath(newTempGamePath); |                     fileReplacement.SetGamePath(newTempGamePath); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -1,14 +1,17 @@ | |||||||
| using Dalamud.Game.ClientState; | using Dalamud.Game.ClientState; | ||||||
| using Dalamud.Game.ClientState.Objects; | using Dalamud.Game.ClientState.Objects; | ||||||
|  | using Dalamud.Game.Gui; | ||||||
| using Dalamud.Hooking; | using Dalamud.Hooking; | ||||||
| using Dalamud.Logging; | using Dalamud.Logging; | ||||||
| using Dalamud.Plugin; | using Dalamud.Plugin; | ||||||
| using Dalamud.Plugin.Ipc; |  | ||||||
| using Dalamud.Utility.Signatures; | using Dalamud.Utility.Signatures; | ||||||
| using FFXIVClientStructs.FFXIV.Client.Game.Character; | using FFXIVClientStructs.FFXIV.Client.Game.Character; | ||||||
| using FFXIVClientStructs.FFXIV.Client.Game.Object; | using FFXIVClientStructs.FFXIV.Client.Game.Object; | ||||||
| using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; | using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; | ||||||
| using FFXIVClientStructs.FFXIV.Client.System.Resource; | using FFXIVClientStructs.FFXIV.Client.System.Resource; | ||||||
|  | using FFXIVClientStructs.FFXIV.Client.UI; | ||||||
|  | using FFXIVClientStructs.FFXIV.Component.GUI; | ||||||
|  | using MareSynchronos.Factories; | ||||||
| using MareSynchronos.Models; | using MareSynchronos.Models; | ||||||
| using Penumbra.GameData.ByteString; | using Penumbra.GameData.ByteString; | ||||||
| using Penumbra.Interop.Structs; | using Penumbra.Interop.Structs; | ||||||
| @@ -17,76 +20,143 @@ using System.Collections.Concurrent; | |||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| using System.Text; |  | ||||||
| using System.Threading.Tasks; |  | ||||||
|  |  | ||||||
| namespace MareSynchronos.Hooks | namespace MareSynchronos.Hooks | ||||||
| { | { | ||||||
|     public unsafe class DrawHooks : IDisposable |     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)] |         [Signature("48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress)] | ||||||
|         public IntPtr* DrawObjectHumanVTable; |         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<GeneralResolveDelegate>? ResolveMdlPathHook; | ||||||
|         public Hook<MaterialResolveDetour>? ResolveMtrlPathHook; |         public Hook<MaterialResolveDetour>? ResolveMtrlPathHook; | ||||||
|         public Hook<MaterialResolveDetour>? ResolvePapPathHook; |         private readonly ClientState clientState; | ||||||
|         public Hook<GeneralResolveDelegate>? ResolvePhybPathHook; |         private readonly Dictionary<IntPtr, ushort> DrawObjectToObject = new(); | ||||||
|         public Hook<GeneralResolveDelegate>? ResolveSklbPathHook; |         private readonly FileReplacementFactory factory; | ||||||
|         public Hook<GeneralResolveDelegate>? ResolveSkpPathHook; |         private readonly GameGui gameGui; | ||||||
|         public Hook<EidResolveDelegate>? ResolveTmbPathHook; |         private readonly ObjectTable objectTable; | ||||||
|         public Hook<MaterialResolveDetour>? ResolveVfxPathHook; |         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.pluginInterface = pluginInterface; | ||||||
|             this.clientState = clientState; |             this.clientState = clientState; | ||||||
|             this.objectTable = objectTable; |             this.objectTable = objectTable; | ||||||
|             this.factory = factory; |             this.factory = factory; | ||||||
|  |             this.gameGui = gameGui; | ||||||
|  |             characterCache = new CharacterCache(); | ||||||
|             SignatureHelper.Initialise(this); |             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() |         public void StartHooks() | ||||||
|         { |         { | ||||||
|             allRequestedResources.Clear(); |             cachedResources.Clear(); | ||||||
|             SetupHumanHooks(); |             SetupHumanHooks(); | ||||||
|             EnableHumanHooks(); |             EnableHumanHooks(); | ||||||
|             PluginLog.Debug("Hooks enabled"); |             PluginLog.Debug("Hooks enabled"); | ||||||
| @@ -98,153 +168,151 @@ namespace MareSynchronos.Hooks | |||||||
|             DisposeHumanHooks(); |             DisposeHumanHooks(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void SetupHumanHooks() |         private void AddRequestedResource(FileReplacement replacement) | ||||||
|         { |         { | ||||||
|             if (ResolveDecalPathHook != null) return; |             if (!cachedResources.Any(a => a.IsReplacedByThis(replacement))) | ||||||
|  |             { | ||||||
|             ResolveDecalPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveDecalIdx], ResolveDecalDetour); |                 cachedResources.Add(replacement); | ||||||
|             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); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         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(); |             return ret; | ||||||
|             //ResolveEidPathHook?.Enable(); |         } | ||||||
|             //ResolveImcPathHook?.Enable(); |  | ||||||
|             //ResolveMPapPathHook?.Enable(); |         private void CharacterBaseDestructorDetour(IntPtr drawBase) | ||||||
|             ResolveMdlPathHook?.Enable(); |         { | ||||||
|             ResolveMtrlPathHook?.Enable(); |             if (DrawObjectToObject.TryGetValue(drawBase, out ushort idx)) | ||||||
|             //ResolvePapPathHook?.Enable(); |             { | ||||||
|             //ResolvePhybPathHook?.Enable(); |                 var gameObj = GetGameObjectFromDrawObject(drawBase, idx); | ||||||
|             //ResolveSklbPathHook?.Enable(); |                 if (gameObj == (GameObject*)clientState.LocalPlayer!.Address) | ||||||
|             //ResolveSkpPathHook?.Enable(); |                 { | ||||||
|             //ResolveTmbPathHook?.Enable(); |                     PluginLog.Debug("Clearing resources"); | ||||||
|             //ResolveVfxPathHook?.Enable(); |                     cachedResources.Clear(); | ||||||
|             EnableDrawHook?.Enable(); |                     DrawObjectToObject.Clear(); | ||||||
|             LoadMtrlTexHook?.Enable(); |                 } | ||||||
|  |             } | ||||||
|  |             CharacterBaseDestructorHook!.Original.Invoke(drawBase); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void DisableHumanHooks() |         private void DisableHumanHooks() | ||||||
|         { |         { | ||||||
|             ResolveDecalPathHook?.Disable(); |             ResolveMdlPathHook?.Disable(); | ||||||
|             //ResolveEidPathHook?.Disable(); |  | ||||||
|             //ResolveImcPathHook?.Disable(); |  | ||||||
|             //ResolveMPapPathHook?.Disable(); |  | ||||||
|             ResolveMdlPathHook?.Disable(); |             ResolveMdlPathHook?.Disable(); | ||||||
|             ResolveMtrlPathHook?.Disable(); |             ResolveMtrlPathHook?.Disable(); | ||||||
|             //ResolvePapPathHook?.Disable(); |  | ||||||
|             //ResolvePhybPathHook?.Disable(); |  | ||||||
|             //ResolveSklbPathHook?.Disable(); |  | ||||||
|             //ResolveSkpPathHook?.Disable(); |  | ||||||
|             //ResolveTmbPathHook?.Disable(); |  | ||||||
|             //ResolveVfxPathHook?.Disable(); |  | ||||||
|             EnableDrawHook?.Disable(); |             EnableDrawHook?.Disable(); | ||||||
|             LoadMtrlTexHook?.Disable(); |             LoadMtrlTexHook?.Disable(); | ||||||
|  |             CharacterBaseCreateHook?.Disable(); | ||||||
|  |             CharacterBaseDestructorHook?.Disable(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void DisposeHumanHooks() |         private void DisposeHumanHooks() | ||||||
|         { |         { | ||||||
|             ResolveDecalPathHook?.Dispose(); |  | ||||||
|             //ResolveEidPathHook?.Dispose(); |  | ||||||
|             //ResolveImcPathHook?.Dispose(); |  | ||||||
|             //ResolveMPapPathHook?.Dispose(); |  | ||||||
|             ResolveMdlPathHook?.Dispose(); |             ResolveMdlPathHook?.Dispose(); | ||||||
|             ResolveMtrlPathHook?.Dispose(); |             ResolveMtrlPathHook?.Dispose(); | ||||||
|             //ResolvePapPathHook?.Dispose(); |  | ||||||
|             //ResolvePhybPathHook?.Dispose(); |  | ||||||
|             //ResolveSklbPathHook?.Dispose(); |  | ||||||
|             //ResolveSkpPathHook?.Dispose(); |  | ||||||
|             //ResolveTmbPathHook?.Dispose(); |  | ||||||
|             //ResolveVfxPathHook?.Dispose(); |  | ||||||
|             EnableDrawHook?.Dispose(); |             EnableDrawHook?.Dispose(); | ||||||
|             LoadMtrlTexHook?.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) |         private void EnableDrawDetour(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d) | ||||||
|         { |         { | ||||||
|             //PluginLog.Debug("Draw start"); |             var oldObject = lastGameObject; | ||||||
|             var oldObject = LastGameObject; |             lastGameObject = (GameObject*)gameObject; | ||||||
|             LastGameObject = (GameObject*)gameObject; |  | ||||||
|             EnableDrawHook!.Original.Invoke(gameObject, b, c, d); |             EnableDrawHook!.Original.Invoke(gameObject, b, c, d); | ||||||
|             LastGameObject = oldObject; |             lastGameObject = oldObject; | ||||||
|             //PluginLog.Debug("Draw end"); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public delegate byte LoadMtrlFilesDelegate(IntPtr mtrlResourceHandle); |         private void EnableHumanHooks() | ||||||
|         [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) |  | ||||||
|         { |         { | ||||||
|             LoadMtrlHelper(mtrlResourceHandle); |             if (ResolveMdlPathHook?.IsEnabled ?? false) return; | ||||||
|             var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle); |  | ||||||
|             return ret; |             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) |         private void LoadMtrlHelper(IntPtr mtrlResourceHandle) | ||||||
| @@ -268,7 +336,7 @@ namespace MareSynchronos.Hooks | |||||||
|                         AddRequestedResource(factory.Create(texPath.ToString())); |                         AddRequestedResource(factory.Create(texPath.ToString())); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     loadedMaterials.Remove(existingMat); |                     loadedMaterials = new(loadedMaterials.Except(new[] { existingMat })); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             catch (Exception ex) |             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) |         private unsafe IntPtr ResolvePathDetour(IntPtr drawObject, IntPtr path) | ||||||
|         { |         { | ||||||
|             if (path == IntPtr.Zero) |             if (path == IntPtr.Zero) | ||||||
| @@ -286,23 +367,34 @@ namespace MareSynchronos.Hooks | |||||||
|  |  | ||||||
|             var gamepath = new Utf8String((byte*)path); |             var gamepath = new Utf8String((byte*)path); | ||||||
|  |  | ||||||
|             var playerName = clientState.LocalPlayer.Name.ToString(); |             var playerName = GetPlayerName(); | ||||||
|             var gameDrawObject = (DrawObject*)drawObject; |             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(); |                 gameObject = GetGameObjectFromDrawObject(drawObject, DrawObjectToObject[drawObject]); | ||||||
|                 if (owner != playerName) |             } | ||||||
|  |  | ||||||
|  |             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; |                     return path; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 AddRequestedResource(factory.Create(gamepath.ToString())); |  | ||||||
|             } |  | ||||||
|             else if (playerDrawObject == gameDrawObject) |  | ||||||
|             { |  | ||||||
|                 var resource = factory.Create(gamepath.ToString()); |                 var resource = factory.Create(gamepath.ToString()); | ||||||
|  |  | ||||||
|                 if (gamepath.ToString().EndsWith("mtrl")) |                 if (gamepath.ToString().EndsWith("mtrl")) | ||||||
|                 { |                 { | ||||||
|                     loadedMaterials.Add(resource); |                     loadedMaterials.Add(resource); | ||||||
| @@ -314,140 +406,12 @@ namespace MareSynchronos.Hooks | |||||||
|             return path; |             return path; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         List<FileReplacement> loadedMaterials = new(); |         private void SetupHumanHooks() | ||||||
|         ConcurrentBag<FileReplacement> allRequestedResources = new(); |  | ||||||
|  |  | ||||||
|         public List<FileReplacement> PrintRequestedResources() |  | ||||||
|         { |         { | ||||||
|             foreach (var resource in allRequestedResources) |             if (ResolveMdlPathHook != null) return; | ||||||
|             { |  | ||||||
|                 PluginLog.Debug(resource.ToString()); |  | ||||||
|             } |  | ||||||
|             //PluginLog.Debug("---"); |  | ||||||
|  |  | ||||||
|             var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject(); |             ResolveMdlPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveMdlIdx], ResolveMdlDetour); | ||||||
|  |             ResolveMtrlPathHook = new Hook<MaterialResolveDetour>(DrawObjectHumanVTable[ResolveMtrlIdx], ResolveMtrlDetour); | ||||||
|             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(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ | |||||||
|     </PackageReference> |     </PackageReference> | ||||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.17" /> |     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.17" /> | ||||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" /> |     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" /> | ||||||
|  |     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
| @@ -71,12 +72,6 @@ | |||||||
|       <HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath> |       <HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath> | ||||||
|       <Private>false</Private> |       <Private>false</Private> | ||||||
|     </Reference> |     </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> | ||||||
|  |  | ||||||
|   <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.Collections.Generic; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
|  | using Newtonsoft.Json; | ||||||
|  | using MareSynchronos.FileCacheDB; | ||||||
|  |  | ||||||
| namespace MareSynchronos.Models | namespace MareSynchronos.Models | ||||||
| { | { | ||||||
|  |     [JsonObject(MemberSerialization.OptIn)] | ||||||
|     public class FileReplacement |     public class FileReplacement | ||||||
|     { |     { | ||||||
|         private readonly string penumbraDirectory; |         private readonly string penumbraDirectory; | ||||||
|  |  | ||||||
|  |         [JsonProperty] | ||||||
|         public string GamePath { get; private set; } |         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 List<FileReplacement> Associated { get; set; } = new List<FileReplacement>(); | ||||||
|  |         [JsonProperty] | ||||||
|         public bool HasFileReplacement => GamePath != ReplacedPath; |         public string ImcData { get; set; } = string.Empty; | ||||||
|  |         public bool HasFileReplacement => GamePath != ResolvedPath; | ||||||
|         public FileReplacement(string gamePath, string penumbraDirectory) |         public FileReplacement(string gamePath, string penumbraDirectory) | ||||||
|         { |         { | ||||||
|             GamePath = gamePath; |             GamePath = gamePath; | ||||||
| @@ -24,6 +32,8 @@ namespace MareSynchronos.Models | |||||||
|  |  | ||||||
|         public void AddAssociated(FileReplacement fileReplacement) |         public void AddAssociated(FileReplacement fileReplacement) | ||||||
|         { |         { | ||||||
|  |             fileReplacement.IsInUse = true; | ||||||
|  |  | ||||||
|             if (!Associated.Any(a => a.IsReplacedByThis(fileReplacement))) |             if (!Associated.Any(a => a.IsReplacedByThis(fileReplacement))) | ||||||
|             { |             { | ||||||
|                 Associated.Add(fileReplacement); |                 Associated.Add(fileReplacement); | ||||||
| @@ -35,31 +45,40 @@ namespace MareSynchronos.Models | |||||||
|             GamePath = path; |             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) |         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) |         public bool IsReplacedByThis(FileReplacement replacement) | ||||||
|         { |         { | ||||||
|             return IsReplacedByThis(replacement.GamePath) || IsReplacedByThis(replacement.ReplacedPath); |             return IsReplacedByThis(replacement.GamePath) || IsReplacedByThis(replacement.ResolvedPath); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override string ToString() |         public override string ToString() | ||||||
|         { |         { | ||||||
|             StringBuilder builder = new StringBuilder(); |             StringBuilder builder = new StringBuilder(); | ||||||
|             builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ReplacedPath}"); |             builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ResolvedPath}"); | ||||||
|             foreach (var l1 in Associated) |             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) |                 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(); |             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 Penumbra.GameData.Enums; | ||||||
| using System; | using System; | ||||||
| using MareSynchronos.Models; | 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 | namespace SamplePlugin | ||||||
| { | { | ||||||
| @@ -30,7 +38,8 @@ namespace SamplePlugin | |||||||
|     { |     { | ||||||
|         public string Name => "Mare Synchronos"; |         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 DalamudPluginInterface PluginInterface { get; init; } | ||||||
|         private CommandManager CommandManager { get; init; } |         private CommandManager CommandManager { get; init; } | ||||||
| @@ -45,11 +54,11 @@ namespace SamplePlugin | |||||||
|         public Plugin( |         public Plugin( | ||||||
|             [RequiredVersion("1.0")] DalamudPluginInterface pluginInterface, |             [RequiredVersion("1.0")] DalamudPluginInterface pluginInterface, | ||||||
|             [RequiredVersion("1.0")] CommandManager commandManager, |             [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.PluginInterface = pluginInterface; | ||||||
|             this.CommandManager = commandManager; |             this.CommandManager = commandManager; | ||||||
|  |             this.clientState = clientState; | ||||||
|             this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); |             this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); | ||||||
|             this.Configuration.Initialize(this.PluginInterface); |             this.Configuration.Initialize(this.PluginInterface); | ||||||
|  |  | ||||||
| @@ -67,7 +76,7 @@ namespace SamplePlugin | |||||||
|             this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI; |             this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI; | ||||||
|  |  | ||||||
|             playerWatch = PlayerWatchFactory.Create(framework, clientState, objectTable); |             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() |         public void Dispose() | ||||||
| @@ -87,6 +96,17 @@ namespace SamplePlugin | |||||||
|                 return; |                 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") |             if (args == "scan") | ||||||
|             { |             { | ||||||
|                 cts = new CancellationTokenSource(); |                 cts = new CancellationTokenSource(); | ||||||
| @@ -116,45 +136,81 @@ namespace SamplePlugin | |||||||
|                 var resources = drawHooks.PrintRequestedResources(); |                 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(() => |                 Task.Run(() => | ||||||
|                 { |                 { | ||||||
|                     PluginLog.Debug("Copying files"); |                     var playerName = clientState.LocalPlayer!.Name.ToString(); | ||||||
|                     foreach (var file in Directory.GetFiles(@"G:\Penumbra\TestMod\files")) |                     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) |                     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) |             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 |                 try | ||||||
|                 { |                 { | ||||||
|                     var ext = new FileInfo(fileCache.Filepath).Extension; |                     var ext = new FileInfo(fileCache.Filepath).Extension; | ||||||
|                     File.Copy(fileCache.Filepath, Path.Combine(@"G:\Penumbra\TestMod\files", fileCache.Hash.ToLower() + ext)); |                     File.Copy(fileCache.Filepath, Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext)); | ||||||
|                     File.AppendAllLines(Path.Combine(@"G:\Penumbra\TestMod", "filelist.txt"), new[] { $"\"{replacement.GamePath}\": \"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 { } |                 catch { } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             foreach (var associated in replacement.Associated) |             foreach (var associated in replacement.Associated) | ||||||
|             { |             { | ||||||
|                 CopyRecursive(associated, db); |                 CopyRecursive(associated, targetDirectory, db, resourceDict); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -174,7 +230,7 @@ namespace SamplePlugin | |||||||
|         { |         { | ||||||
|             Stopwatch st = Stopwatch.StartNew(); |             Stopwatch st = Stopwatch.StartNew(); | ||||||
|  |  | ||||||
|             string penumbraDir = Configuration.PenumbraFolder; |             string penumbraDir = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc(); | ||||||
|             PluginLog.Debug("Getting files from " + penumbraDir); |             PluginLog.Debug("Getting files from " + penumbraDir); | ||||||
|             ConcurrentDictionary<string, bool> charaFiles = new ConcurrentDictionary<string, bool>( |             ConcurrentDictionary<string, bool> charaFiles = new ConcurrentDictionary<string, bool>( | ||||||
|                 Directory.GetFiles(penumbraDir, "*.*", SearchOption.AllDirectories) |                 Directory.GetFiles(penumbraDir, "*.*", SearchOption.AllDirectories) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Stanley Dimant
					Stanley Dimant