add spaghetti
This commit is contained in:
@@ -5,16 +5,42 @@ VisualStudioVersion = 17.1.32328.378
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos", "MareSynchronos\MareSynchronos.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos", "MareSynchronos\MareSynchronos.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{44F7CA6A-898C-4901-ADB8-010BC74FF781}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "..\..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{2F26FC2D-03DF-445F-A87B-8708D621E86C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
|
||||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
|
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{44F7CA6A-898C-4901-ADB8-010BC74FF781}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{2F26FC2D-03DF-445F-A87B-8708D621E86C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
453
MareSynchronos/Hooks/DrawHooks.cs
Normal file
453
MareSynchronos/Hooks/DrawHooks.cs
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
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 MareSynchronos.Models;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
using System;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
[Signature("48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress)]
|
||||||
|
public IntPtr* DrawObjectHumanVTable;
|
||||||
|
|
||||||
|
// [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;
|
||||||
|
|
||||||
|
public DrawHooks(DalamudPluginInterface pluginInterface, ClientState clientState, ObjectTable objectTable, FileReplacementFactory factory)
|
||||||
|
{
|
||||||
|
this.pluginInterface = pluginInterface;
|
||||||
|
this.clientState = clientState;
|
||||||
|
this.objectTable = objectTable;
|
||||||
|
this.factory = factory;
|
||||||
|
SignatureHelper.Initialise(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartHooks()
|
||||||
|
{
|
||||||
|
allRequestedResources.Clear();
|
||||||
|
SetupHumanHooks();
|
||||||
|
EnableHumanHooks();
|
||||||
|
PluginLog.Debug("Hooks enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopHooks()
|
||||||
|
{
|
||||||
|
DisableHumanHooks();
|
||||||
|
DisposeHumanHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupHumanHooks()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnableHumanHooks()
|
||||||
|
{
|
||||||
|
if (ResolveDecalPathHook?.IsEnabled ?? false) return;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisableHumanHooks()
|
||||||
|
{
|
||||||
|
ResolveDecalPathHook?.Disable();
|
||||||
|
//ResolveEidPathHook?.Disable();
|
||||||
|
//ResolveImcPathHook?.Disable();
|
||||||
|
//ResolveMPapPathHook?.Disable();
|
||||||
|
ResolveMdlPathHook?.Disable();
|
||||||
|
ResolveMtrlPathHook?.Disable();
|
||||||
|
//ResolvePapPathHook?.Disable();
|
||||||
|
//ResolvePhybPathHook?.Disable();
|
||||||
|
//ResolveSklbPathHook?.Disable();
|
||||||
|
//ResolveSkpPathHook?.Disable();
|
||||||
|
//ResolveTmbPathHook?.Disable();
|
||||||
|
//ResolveVfxPathHook?.Disable();
|
||||||
|
EnableDrawHook?.Disable();
|
||||||
|
LoadMtrlTexHook?.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
EnableDrawHook!.Original.Invoke(gameObject, b, c, d);
|
||||||
|
LastGameObject = oldObject;
|
||||||
|
//PluginLog.Debug("Draw end");
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
LoadMtrlHelper(mtrlResourceHandle);
|
||||||
|
var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadMtrlHelper(IntPtr mtrlResourceHandle)
|
||||||
|
{
|
||||||
|
if (mtrlResourceHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mtrl = (MtrlResource*)mtrlResourceHandle;
|
||||||
|
var mtrlPath = Utf8String.FromSpanUnsafe(mtrl->Handle.FileNameSpan(), true, null, true);
|
||||||
|
var mtrlResource = factory.Create(mtrlPath.ToString());
|
||||||
|
var existingMat = loadedMaterials.FirstOrDefault(m => m.IsReplacedByThis(mtrlResource));
|
||||||
|
if (existingMat != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < mtrl->NumTex; i++)
|
||||||
|
{
|
||||||
|
var texPath = new Utf8String(mtrl->TexString(i));
|
||||||
|
AddRequestedResource(factory.Create(texPath.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedMaterials.Remove(existingMat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PluginLog.Error(ex, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe IntPtr ResolvePathDetour(IntPtr drawObject, IntPtr path)
|
||||||
|
{
|
||||||
|
if (path == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gamepath = new Utf8String((byte*)path);
|
||||||
|
|
||||||
|
var playerName = clientState.LocalPlayer.Name.ToString();
|
||||||
|
var gameDrawObject = (DrawObject*)drawObject;
|
||||||
|
var playerDrawObject = ((Character*)clientState.LocalPlayer.Address)->GameObject.GetDrawObject();
|
||||||
|
|
||||||
|
if (LastGameObject != null && (LastGameObject->DrawObject == null || LastGameObject->DrawObject == gameDrawObject))
|
||||||
|
{
|
||||||
|
var owner = new Utf8String(LastGameObject->Name).ToString();
|
||||||
|
if (owner != 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddRequestedResource(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<FileReplacement> loadedMaterials = new();
|
||||||
|
ConcurrentBag<FileReplacement> allRequestedResources = new();
|
||||||
|
|
||||||
|
public List<FileReplacement> PrintRequestedResources()
|
||||||
|
{
|
||||||
|
foreach (var resource in allRequestedResources)
|
||||||
|
{
|
||||||
|
PluginLog.Debug(resource.ToString());
|
||||||
|
}
|
||||||
|
//PluginLog.Debug("---");
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
MareSynchronos/Interop/Material.cs
Normal file
30
MareSynchronos/Interop/Material.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public unsafe struct Material
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x10 )]
|
||||||
|
public ResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
|
[FieldOffset( 0x28 )]
|
||||||
|
public MaterialData* MaterialData;
|
||||||
|
|
||||||
|
[FieldOffset( 0x48 )]
|
||||||
|
public Texture* Tex1;
|
||||||
|
|
||||||
|
[FieldOffset( 0x60 )]
|
||||||
|
public Texture* Tex2;
|
||||||
|
|
||||||
|
[FieldOffset( 0x78 )]
|
||||||
|
public Texture* Tex3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public unsafe struct MaterialData
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x0 )]
|
||||||
|
public byte* Data;
|
||||||
|
}
|
||||||
28
MareSynchronos/Interop/MtrlResource.cs
Normal file
28
MareSynchronos/Interop/MtrlResource.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public unsafe struct MtrlResource
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x00 )]
|
||||||
|
public ResourceHandle Handle;
|
||||||
|
|
||||||
|
[FieldOffset( 0xD0 )]
|
||||||
|
public ushort* TexSpace; // Contains the offsets for the tex files inside the string list.
|
||||||
|
|
||||||
|
[FieldOffset( 0xE0 )]
|
||||||
|
public byte* StringList;
|
||||||
|
|
||||||
|
[FieldOffset( 0xF8 )]
|
||||||
|
public ushort ShpkOffset;
|
||||||
|
|
||||||
|
[FieldOffset( 0xFA )]
|
||||||
|
public byte NumTex;
|
||||||
|
|
||||||
|
public byte* ShpkString
|
||||||
|
=> StringList + ShpkOffset;
|
||||||
|
|
||||||
|
public byte* TexString( int idx )
|
||||||
|
=> StringList + *( TexSpace + 4 + idx * 8 );
|
||||||
|
}
|
||||||
41
MareSynchronos/Interop/RenderModel.cs
Normal file
41
MareSynchronos/Interop/RenderModel.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public unsafe struct RenderModel
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x18 )]
|
||||||
|
public RenderModel* PreviousModel;
|
||||||
|
|
||||||
|
[FieldOffset( 0x20 )]
|
||||||
|
public RenderModel* NextModel;
|
||||||
|
|
||||||
|
[FieldOffset( 0x30 )]
|
||||||
|
public ResourceHandle* ResourceHandle;
|
||||||
|
|
||||||
|
[FieldOffset( 0x40 )]
|
||||||
|
public Skeleton* Skeleton;
|
||||||
|
|
||||||
|
[FieldOffset( 0x58 )]
|
||||||
|
public void** BoneList;
|
||||||
|
|
||||||
|
[FieldOffset( 0x60 )]
|
||||||
|
public int BoneListCount;
|
||||||
|
|
||||||
|
[FieldOffset( 0x68 )]
|
||||||
|
private void* UnkDXBuffer1;
|
||||||
|
|
||||||
|
[FieldOffset( 0x70 )]
|
||||||
|
private void* UnkDXBuffer2;
|
||||||
|
|
||||||
|
[FieldOffset( 0x78 )]
|
||||||
|
private void* UnkDXBuffer3;
|
||||||
|
|
||||||
|
[FieldOffset( 0x90 )]
|
||||||
|
public void** Materials;
|
||||||
|
|
||||||
|
[FieldOffset( 0x98 )]
|
||||||
|
public int MaterialCount;
|
||||||
|
}
|
||||||
96
MareSynchronos/Interop/ResourceHandle.cs
Normal file
96
MareSynchronos/Interop/ResourceHandle.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public unsafe struct ResourceHandle
|
||||||
|
{
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public struct DataIndirection
|
||||||
|
{
|
||||||
|
[FieldOffset( 0x00 )]
|
||||||
|
public void** VTable;
|
||||||
|
|
||||||
|
[FieldOffset( 0x10 )]
|
||||||
|
public byte* DataPtr;
|
||||||
|
|
||||||
|
[FieldOffset( 0x28 )]
|
||||||
|
public ulong DataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int SsoSize = 15;
|
||||||
|
|
||||||
|
public byte* FileName()
|
||||||
|
{
|
||||||
|
if( FileNameLength > SsoSize )
|
||||||
|
{
|
||||||
|
return FileNameData;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed( byte** name = &FileNameData )
|
||||||
|
{
|
||||||
|
return ( byte* )name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan< byte > FileNameSpan()
|
||||||
|
=> new(FileName(), FileNameLength);
|
||||||
|
|
||||||
|
[FieldOffset( 0x00 )]
|
||||||
|
public void** VTable;
|
||||||
|
|
||||||
|
[FieldOffset( 0x08 )]
|
||||||
|
public ResourceCategory Category;
|
||||||
|
|
||||||
|
[FieldOffset( 0x0C )]
|
||||||
|
public ResourceType FileType;
|
||||||
|
|
||||||
|
[FieldOffset( 0x10 )]
|
||||||
|
public uint Id;
|
||||||
|
|
||||||
|
[FieldOffset( 0x48 )]
|
||||||
|
public byte* FileNameData;
|
||||||
|
|
||||||
|
[FieldOffset( 0x58 )]
|
||||||
|
public int FileNameLength;
|
||||||
|
|
||||||
|
[FieldOffset( 0xAC )]
|
||||||
|
public uint RefCount;
|
||||||
|
|
||||||
|
// May return null.
|
||||||
|
public static byte* GetData( ResourceHandle* handle )
|
||||||
|
=> ( ( delegate* unmanaged< ResourceHandle*, byte* > )handle->VTable[ 23 ] )( handle );
|
||||||
|
|
||||||
|
public static ulong GetLength( ResourceHandle* handle )
|
||||||
|
=> ( ( delegate* unmanaged< ResourceHandle*, ulong > )handle->VTable[ 17 ] )( handle );
|
||||||
|
|
||||||
|
|
||||||
|
// Only use these if you know what you are doing.
|
||||||
|
// Those are actually only sure to be accessible for DefaultResourceHandles.
|
||||||
|
[FieldOffset( 0xB0 )]
|
||||||
|
public DataIndirection* Data;
|
||||||
|
|
||||||
|
[FieldOffset( 0xB8 )]
|
||||||
|
public uint DataLength;
|
||||||
|
|
||||||
|
public (IntPtr Data, int Length) GetData()
|
||||||
|
=> Data != null
|
||||||
|
? ( ( IntPtr )Data->DataPtr, ( int )Data->DataLength )
|
||||||
|
: ( IntPtr.Zero, 0 );
|
||||||
|
|
||||||
|
public bool SetData( IntPtr data, int length )
|
||||||
|
{
|
||||||
|
if( Data == null )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Data->DataPtr = length != 0 ? ( byte* )data : null;
|
||||||
|
Data->DataLength = ( ulong )length;
|
||||||
|
DataLength = ( uint )length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,11 +34,19 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="FFXIVClientStructs">
|
<Reference Include="FFXIVClientStructs">
|
||||||
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Glamourer.GameData">
|
||||||
|
<HintPath>..\..\..\..\..\AppData\Roaming\XIVLauncher\installedPlugins\Glamourer\0.0.9.0\Glamourer.GameData.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Newtonsoft.Json">
|
<Reference Include="Newtonsoft.Json">
|
||||||
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
@@ -63,6 +71,12 @@
|
|||||||
<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>
|
||||||
|
|||||||
68
MareSynchronos/Models/FileReplacement.cs
Normal file
68
MareSynchronos/Models/FileReplacement.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Models
|
||||||
|
{
|
||||||
|
public class FileReplacement
|
||||||
|
{
|
||||||
|
private readonly string penumbraDirectory;
|
||||||
|
|
||||||
|
public string GamePath { get; private set; }
|
||||||
|
public string ReplacedPath { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<FileReplacement> Associated { get; set; } = new List<FileReplacement>();
|
||||||
|
|
||||||
|
public bool HasFileReplacement => GamePath != ReplacedPath;
|
||||||
|
public FileReplacement(string gamePath, string penumbraDirectory)
|
||||||
|
{
|
||||||
|
GamePath = gamePath;
|
||||||
|
this.penumbraDirectory = penumbraDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAssociated(FileReplacement fileReplacement)
|
||||||
|
{
|
||||||
|
if (!Associated.Any(a => a.IsReplacedByThis(fileReplacement)))
|
||||||
|
{
|
||||||
|
Associated.Add(fileReplacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGamePath(string path)
|
||||||
|
{
|
||||||
|
GamePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetReplacedPath(string path)
|
||||||
|
{
|
||||||
|
ReplacedPath = path.ToLower().Replace('/', '\\').Replace(penumbraDirectory, "").Replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReplacedByThis(string path)
|
||||||
|
{
|
||||||
|
return GamePath.ToLower() == path.ToLower() || ReplacedPath.ToLower() == path.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReplacedByThis(FileReplacement replacement)
|
||||||
|
{
|
||||||
|
return IsReplacedByThis(replacement.GamePath) || IsReplacedByThis(replacement.ReplacedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ReplacedPath}");
|
||||||
|
foreach (var l1 in Associated)
|
||||||
|
{
|
||||||
|
builder.AppendLine($" + Modded: {l1.HasFileReplacement} - {l1.GamePath} => {l1.ReplacedPath}");
|
||||||
|
foreach (var l2 in l1.Associated)
|
||||||
|
{
|
||||||
|
builder.AppendLine($" + Modded: {l2.HasFileReplacement} - {l2.GamePath} => {l2.ReplacedPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
MareSynchronos/Models/FileReplacementFactory.cs
Normal file
39
MareSynchronos/Models/FileReplacementFactory.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Models
|
||||||
|
{
|
||||||
|
public class FileReplacementFactory
|
||||||
|
{
|
||||||
|
private readonly ClientState clientState;
|
||||||
|
private ICallGateSubscriber<string, string, string> resolvePath;
|
||||||
|
private string penumbraDirectory;
|
||||||
|
|
||||||
|
public FileReplacementFactory(DalamudPluginInterface pluginInterface, ClientState clientState)
|
||||||
|
{
|
||||||
|
resolvePath = pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
|
||||||
|
penumbraDirectory = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc().ToLower() + '\\';
|
||||||
|
this.clientState = clientState;
|
||||||
|
}
|
||||||
|
public FileReplacement Create(string gamePath)
|
||||||
|
{
|
||||||
|
var fileReplacement = new FileReplacement(gamePath, penumbraDirectory);
|
||||||
|
fileReplacement.SetReplacedPath(resolvePath.InvokeFunc(gamePath, clientState.LocalPlayer!.Name.ToString()));
|
||||||
|
if (!fileReplacement.HasFileReplacement)
|
||||||
|
{
|
||||||
|
// try to resolve path with -- 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.SetGamePath(newTempGamePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileReplacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,18 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MareSynchronos.Hooks;
|
||||||
|
using Penumbra.PlayerWatch;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using System.Text;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using System;
|
||||||
|
using MareSynchronos.Models;
|
||||||
|
|
||||||
namespace SamplePlugin
|
namespace SamplePlugin
|
||||||
{
|
{
|
||||||
@@ -25,12 +37,15 @@ namespace SamplePlugin
|
|||||||
private Configuration Configuration { get; init; }
|
private Configuration Configuration { get; init; }
|
||||||
private PluginUI PluginUi { get; init; }
|
private PluginUI PluginUi { get; init; }
|
||||||
private FileCacheFactory FileCacheFactory { get; init; }
|
private FileCacheFactory FileCacheFactory { get; init; }
|
||||||
|
private DrawHooks drawHooks;
|
||||||
|
|
||||||
private CancellationTokenSource cts;
|
private CancellationTokenSource cts;
|
||||||
|
private IPlayerWatcher playerWatch;
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
this.PluginInterface = pluginInterface;
|
this.PluginInterface = pluginInterface;
|
||||||
this.CommandManager = commandManager;
|
this.CommandManager = commandManager;
|
||||||
@@ -50,12 +65,18 @@ namespace SamplePlugin
|
|||||||
|
|
||||||
this.PluginInterface.UiBuilder.Draw += DrawUI;
|
this.PluginInterface.UiBuilder.Draw += DrawUI;
|
||||||
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
||||||
|
|
||||||
|
playerWatch = PlayerWatchFactory.Create(framework, clientState, objectTable);
|
||||||
|
drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new MareSynchronos.Models.FileReplacementFactory(pluginInterface, clientState));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.PluginUi.Dispose();
|
this.PluginUi.Dispose();
|
||||||
this.CommandManager.RemoveHandler(commandName);
|
this.CommandManager.RemoveHandler(commandName);
|
||||||
|
playerWatch.PlayerChanged -= PlayerWatch_PlayerChanged;
|
||||||
|
playerWatch.RemovePlayerFromWatch("Ilya Zhelmo");
|
||||||
|
drawHooks.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCommand(string command, string args)
|
private void OnCommand(string command, string args)
|
||||||
@@ -72,6 +93,139 @@ namespace SamplePlugin
|
|||||||
|
|
||||||
Task.Run(() => StartScan(), cts.Token);
|
Task.Run(() => StartScan(), cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args == "watch")
|
||||||
|
{
|
||||||
|
playerWatch.AddPlayerToWatch("Ilya Zhelmo");
|
||||||
|
playerWatch.PlayerChanged += PlayerWatch_PlayerChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args == "stopwatch")
|
||||||
|
{
|
||||||
|
playerWatch.PlayerChanged -= PlayerWatch_PlayerChanged;
|
||||||
|
playerWatch.RemovePlayerFromWatch("Ilya Zhelmo");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args == "hook")
|
||||||
|
{
|
||||||
|
drawHooks.StartHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args == "print")
|
||||||
|
{
|
||||||
|
var resources = drawHooks.PrintRequestedResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args == "copy")
|
||||||
|
{
|
||||||
|
var resources = drawHooks.PrintRequestedResources();
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
PluginLog.Debug("Copying files");
|
||||||
|
foreach (var file in Directory.GetFiles(@"G:\Penumbra\TestMod\files"))
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
File.Delete(@"G:\Penumbra\testmod\filelist.txt");
|
||||||
|
using FileCacheContext db = new FileCacheContext();
|
||||||
|
foreach (var resource in resources)
|
||||||
|
{
|
||||||
|
CopyRecursive(resource, db);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyRecursive(FileReplacement replacement, FileCacheContext db)
|
||||||
|
{
|
||||||
|
if (replacement.HasFileReplacement)
|
||||||
|
{
|
||||||
|
PluginLog.Debug("Copying file \"" + replacement.ReplacedPath + "\"");
|
||||||
|
|
||||||
|
var fileCache = db.FileCaches.Single(f => f.Filepath.Contains(replacement.ReplacedPath.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}\"," });
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var associated in replacement.Associated)
|
||||||
|
{
|
||||||
|
CopyRecursive(associated, db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerWatch_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
|
||||||
|
{
|
||||||
|
var equipment = playerWatch.UpdatePlayerWithoutEvent(actor);
|
||||||
|
var customization = new CharacterCustomization(actor);
|
||||||
|
DebugCustomization(customization);
|
||||||
|
//PluginLog.Debug(customization.Gender.ToString());
|
||||||
|
if (equipment != null)
|
||||||
|
{
|
||||||
|
PluginLog.Debug(equipment.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DebugCustomization(CharacterCustomization customization)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine("Gender: " + customization[CustomizationId.Gender].ToString() + ":" + customization.Gender.ToName());
|
||||||
|
sb.AppendLine("Race: " + customization[CustomizationId.Race].ToString() + ":" + GetBodyRaceCode(customization));
|
||||||
|
sb.AppendLine("Face: " + customization.Face.ToString());
|
||||||
|
|
||||||
|
PluginLog.Debug(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBodyRaceMdlPath(CharacterCustomization customization)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBodyRaceCode(CharacterCustomization customization)
|
||||||
|
{
|
||||||
|
return Names.CombinedRace(customization.Gender, GetBodyRace(FromSubRace(customization.Clan))).ToRaceCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelRace GetBodyRace(ModelRace modelRace)
|
||||||
|
{
|
||||||
|
return modelRace switch
|
||||||
|
{
|
||||||
|
ModelRace.AuRa => ModelRace.AuRa,
|
||||||
|
ModelRace.Miqote => ModelRace.Midlander,
|
||||||
|
ModelRace.Highlander => ModelRace.Highlander,
|
||||||
|
ModelRace.Lalafell => ModelRace.Lalafell,
|
||||||
|
ModelRace.Midlander => ModelRace.Midlander,
|
||||||
|
ModelRace.Elezen => ModelRace.Midlander,
|
||||||
|
ModelRace.Hrothgar => ModelRace.Hrothgar,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModelRace FromSubRace(SubRace race)
|
||||||
|
{
|
||||||
|
return race switch
|
||||||
|
{
|
||||||
|
SubRace.Xaela => ModelRace.AuRa,
|
||||||
|
SubRace.Raen => ModelRace.AuRa,
|
||||||
|
SubRace.Highlander => ModelRace.Highlander,
|
||||||
|
SubRace.Midlander => ModelRace.Midlander,
|
||||||
|
SubRace.Plainsfolk => ModelRace.Lalafell,
|
||||||
|
SubRace.Dunesfolk => ModelRace.Lalafell,
|
||||||
|
SubRace.SeekerOfTheSun => ModelRace.Miqote,
|
||||||
|
SubRace.KeeperOfTheMoon => ModelRace.Miqote,
|
||||||
|
SubRace.Seawolf => ModelRace.Roegadyn,
|
||||||
|
SubRace.Hellsguard => ModelRace.Roegadyn,
|
||||||
|
SubRace.Rava => ModelRace.Viera,
|
||||||
|
SubRace.Veena => ModelRace.Viera,
|
||||||
|
SubRace.Wildwood => ModelRace.Elezen,
|
||||||
|
SubRace.Duskwight => ModelRace.Elezen,
|
||||||
|
SubRace.Helion => ModelRace.Hrothgar,
|
||||||
|
SubRace.Lost => ModelRace.Hrothgar,
|
||||||
|
_ => ModelRace.Unknown
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartScan()
|
private void StartScan()
|
||||||
|
|||||||
Reference in New Issue
Block a user