remove DrawHooks, use new penumbra IPC calls (otter pls)
This commit is contained in:
@@ -7,45 +7,20 @@ namespace MareSynchronos.Factories
|
||||
public class FileReplacementFactory
|
||||
{
|
||||
private readonly IpcManager ipcManager;
|
||||
private readonly ClientState clientState;
|
||||
private string playerName;
|
||||
|
||||
public FileReplacementFactory(IpcManager ipcManager, ClientState clientState)
|
||||
public FileReplacementFactory(IpcManager ipcManager)
|
||||
{
|
||||
this.ipcManager = ipcManager;
|
||||
this.clientState = clientState;
|
||||
playerName = null!;
|
||||
}
|
||||
|
||||
public FileReplacement Create(string gamePath, bool resolve = true)
|
||||
public FileReplacement Create()
|
||||
{
|
||||
if (!ipcManager.CheckPenumbraAPI())
|
||||
{
|
||||
throw new System.Exception();
|
||||
}
|
||||
|
||||
var fileReplacement = new FileReplacement(gamePath, ipcManager.PenumbraModDirectory()!);
|
||||
if (!resolve) return fileReplacement;
|
||||
|
||||
if (clientState.LocalPlayer != null)
|
||||
{
|
||||
playerName = clientState.LocalPlayer.Name.ToString();
|
||||
}
|
||||
fileReplacement.SetResolvedPath(ipcManager.PenumbraResolvePath(gamePath, playerName)!);
|
||||
if (!fileReplacement.HasFileReplacement)
|
||||
{
|
||||
// try to resolve path with --filename instead?
|
||||
string[] tempGamePath = gamePath.Split('/');
|
||||
tempGamePath[^1] = "--" + tempGamePath[^1];
|
||||
string newTempGamePath = string.Join('/', tempGamePath);
|
||||
var resolvedPath = ipcManager.PenumbraResolvePath(newTempGamePath, playerName)!;
|
||||
if (resolvedPath != newTempGamePath)
|
||||
{
|
||||
fileReplacement.SetResolvedPath(resolvedPath);
|
||||
fileReplacement.SetGamePath(newTempGamePath);
|
||||
}
|
||||
}
|
||||
return fileReplacement;
|
||||
return new FileReplacement(ipcManager.PenumbraModDirectory()!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,433 +0,0 @@
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using MareSynchronos.Factories;
|
||||
using MareSynchronos.Managers;
|
||||
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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Hooks
|
||||
{
|
||||
public unsafe class DrawHooks : IDisposable
|
||||
{
|
||||
public const int ResolveMdlIdx = 73;
|
||||
public const int ResolveMtrlIdx = 82;
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40", DetourName = "CharacterBaseCreateDetour")]
|
||||
public Hook<CharacterBaseCreateDelegate>? CharacterBaseCreateHook;
|
||||
[Signature("E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30",
|
||||
DetourName = "CharacterBaseDestructorDetour")]
|
||||
public Hook<CharacterBaseDestructorDelegate>? CharacterBaseDestructorHook;
|
||||
[Signature("48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress)]
|
||||
public IntPtr* DrawObjectHumanVTable;
|
||||
[Signature("E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0")]
|
||||
public Hook<EnableDrawDelegate>? EnableDrawHook;
|
||||
[Signature("4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour")]
|
||||
public Hook<LoadMtrlFilesDelegate>? LoadMtrlTexHook;
|
||||
|
||||
public event EventHandler? PlayerLoadEvent;
|
||||
|
||||
public Hook<GeneralResolveDelegate>? ResolveMdlPathHook;
|
||||
public Hook<MaterialResolveDetour>? ResolveMtrlPathHook;
|
||||
private readonly ClientState clientState;
|
||||
private readonly Dictionary<IntPtr, ushort> DrawObjectToObject = new();
|
||||
private readonly FileReplacementFactory factory;
|
||||
private readonly GameGui gameGui;
|
||||
private readonly ObjectTable objectTable;
|
||||
private readonly DalamudPluginInterface pluginInterface;
|
||||
private ConcurrentDictionary<string, FileReplacement> cachedResources = new();
|
||||
private GameObject* lastGameObject = null;
|
||||
private ConcurrentBag<FileReplacement> loadedMaterials = new();
|
||||
|
||||
public DrawHooks(DalamudPluginInterface pluginInterface, ClientState clientState, ObjectTable objectTable, FileReplacementFactory factory, GameGui gameGui)
|
||||
{
|
||||
this.pluginInterface = pluginInterface;
|
||||
this.clientState = clientState;
|
||||
this.objectTable = objectTable;
|
||||
this.factory = factory;
|
||||
this.gameGui = gameGui;
|
||||
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.Value.IsInUse = false;
|
||||
resource.Value.ImcData = string.Empty;
|
||||
resource.Value.Associated.Clear();
|
||||
}
|
||||
|
||||
PluginLog.Verbose("Invaldated resource cache");
|
||||
|
||||
var cache = new CharacterCache();
|
||||
|
||||
try
|
||||
{
|
||||
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.Value.IsReplacedByThis(mdlResource)).Value;
|
||||
|
||||
var imc = (ResourceHandle*)model->IMCArray[idx];
|
||||
if (imc != null)
|
||||
{
|
||||
byte[] imcData = new byte[imc->Data->DataLength / sizeof(long)];
|
||||
Marshal.Copy((IntPtr)imc->Data->DataPtr, imcData, 0, (int)imc->Data->DataLength / sizeof(long));
|
||||
string imcDataStr = BitConverter.ToString(imcData).Replace("-", "");
|
||||
cachedMdlResource.ImcData = imcDataStr;
|
||||
}
|
||||
cache.AddAssociatedResource(cachedMdlResource, null!, null!);
|
||||
|
||||
for (int mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
|
||||
{
|
||||
var mtrl = (Material*)mdl->Materials[mtrlIdx];
|
||||
if (mtrl == null) continue;
|
||||
|
||||
var mtrlFileResource = factory.Create(new Utf8String(mtrl->ResourceHandle->FileName()).ToString().Split("|")[2]);
|
||||
var cachedMtrlResource = cachedResources.First(r => r.Value.IsReplacedByThis(mtrlFileResource)).Value;
|
||||
cache.AddAssociatedResource(cachedMtrlResource, cachedMdlResource, null!);
|
||||
|
||||
var mtrlResource = (MtrlResource*)mtrl->ResourceHandle;
|
||||
for (int resIdx = 0; resIdx < mtrlResource->NumTex; resIdx++)
|
||||
{
|
||||
var texPath = new Utf8String(mtrlResource->TexString(resIdx));
|
||||
|
||||
if (string.IsNullOrEmpty(texPath.ToString())) continue;
|
||||
|
||||
var texResource = factory.Create(texPath.ToString());
|
||||
var cachedTexResource = cachedResources.First(r => r.Value.IsReplacedByThis(texResource)).Value;
|
||||
cache.AddAssociatedResource(cachedTexResource, cachedMdlResource, cachedMtrlResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, ex.Message);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void PrintRequestedResources()
|
||||
{
|
||||
var cache = BuildCharacterCache();
|
||||
|
||||
PluginLog.Verbose("--- CURRENTLY LOADED FILES ---");
|
||||
|
||||
PluginLog.Verbose(cache.ToString());
|
||||
|
||||
PluginLog.Verbose("--- LOOSE FILES ---");
|
||||
|
||||
foreach (var resource in cachedResources.Where(r => !r.Value.IsInUse).OrderBy(a => a.Value.GamePath))
|
||||
{
|
||||
PluginLog.Verbose(resource.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public void StartHooks()
|
||||
{
|
||||
cachedResources.Clear();
|
||||
SetupHumanHooks();
|
||||
EnableHumanHooks();
|
||||
PluginLog.Verbose("Hooks enabled");
|
||||
}
|
||||
|
||||
public void StopHooks()
|
||||
{
|
||||
DisableHumanHooks();
|
||||
DisposeHumanHooks();
|
||||
}
|
||||
|
||||
private void AddRequestedResource(FileReplacement replacement)
|
||||
{
|
||||
if (!cachedResources.Any(a => a.Value.IsReplacedByThis(replacement)) || cachedResources.Any(c => c.Key == replacement.GamePath))
|
||||
{
|
||||
cachedResources[replacement.GamePath] = replacement;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr CharacterBaseCreateDetour(uint a, IntPtr b, IntPtr c, byte d)
|
||||
{
|
||||
var ret = CharacterBaseCreateHook!.Original(a, b, c, d);
|
||||
if (lastGameObject != null)
|
||||
{
|
||||
DrawObjectToObject[ret] = (lastGameObject->ObjectIndex);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void CharacterBaseDestructorDetour(IntPtr drawBase)
|
||||
{
|
||||
if (DrawObjectToObject.TryGetValue(drawBase, out ushort idx))
|
||||
{
|
||||
var gameObj = GetGameObjectFromDrawObject(drawBase, idx);
|
||||
if (clientState.LocalPlayer != null && gameObj == (GameObject*)clientState.LocalPlayer!.Address)
|
||||
{
|
||||
//PluginLog.Verbose("Clearing resources");
|
||||
//cachedResources.Clear();
|
||||
DrawObjectToObject.Clear();
|
||||
}
|
||||
}
|
||||
CharacterBaseDestructorHook!.Original.Invoke(drawBase);
|
||||
}
|
||||
|
||||
private void DisableHumanHooks()
|
||||
{
|
||||
ResolveMdlPathHook?.Disable();
|
||||
ResolveMdlPathHook?.Disable();
|
||||
ResolveMtrlPathHook?.Disable();
|
||||
EnableDrawHook?.Disable();
|
||||
LoadMtrlTexHook?.Disable();
|
||||
CharacterBaseCreateHook?.Disable();
|
||||
CharacterBaseDestructorHook?.Disable();
|
||||
}
|
||||
|
||||
private void DisposeHumanHooks()
|
||||
{
|
||||
ResolveMdlPathHook?.Dispose();
|
||||
ResolveMtrlPathHook?.Dispose();
|
||||
EnableDrawHook?.Dispose();
|
||||
LoadMtrlTexHook?.Dispose();
|
||||
CharacterBaseCreateHook?.Dispose();
|
||||
CharacterBaseDestructorHook?.Dispose();
|
||||
}
|
||||
|
||||
private void EnableDrawDetour(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d)
|
||||
{
|
||||
var oldObject = lastGameObject;
|
||||
lastGameObject = (GameObject*)gameObject;
|
||||
EnableDrawHook!.Original.Invoke(gameObject, b, c, d);
|
||||
lastGameObject = oldObject;
|
||||
}
|
||||
|
||||
private void EnableHumanHooks()
|
||||
{
|
||||
if (ResolveMdlPathHook?.IsEnabled ?? false) return;
|
||||
|
||||
ResolveMdlPathHook?.Enable();
|
||||
ResolveMtrlPathHook?.Enable();
|
||||
EnableDrawHook?.Enable();
|
||||
LoadMtrlTexHook?.Enable();
|
||||
CharacterBaseCreateHook?.Enable();
|
||||
CharacterBaseDestructorHook?.Enable();
|
||||
}
|
||||
|
||||
private string? GetCardName()
|
||||
{
|
||||
var uiModule = (UIModule*)gameGui.GetUIModule();
|
||||
var agentModule = uiModule->GetAgentModule();
|
||||
var agent = (byte*)agentModule->GetAgentByInternalID(393);
|
||||
if (agent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = *(byte**)(agent + 0x28);
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var block = data + 0x7A;
|
||||
return new Utf8String(block).ToString();
|
||||
}
|
||||
|
||||
private GameObject* GetGameObjectFromDrawObject(IntPtr drawObject, int gameObjectIdx)
|
||||
{
|
||||
var tmp = objectTable[gameObjectIdx];
|
||||
GameObject* gameObject;
|
||||
if (tmp != null)
|
||||
{
|
||||
gameObject = (GameObject*)tmp.Address;
|
||||
if (gameObject->DrawObject == (DrawObject*)drawObject)
|
||||
{
|
||||
return gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
DrawObjectToObject.Remove(drawObject);
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? GetGlamourName()
|
||||
{
|
||||
var addon = gameGui.GetAddonByName("MiragePrismMiragePlate", 1);
|
||||
return addon == IntPtr.Zero ? null : GetPlayerName();
|
||||
}
|
||||
|
||||
private string? GetInspectName()
|
||||
{
|
||||
var addon = gameGui.GetAddonByName("CharacterInspect", 1);
|
||||
if (addon == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ui = (AtkUnitBase*)addon;
|
||||
if (ui->UldManager.NodeListCount < 60)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var text = (AtkTextNode*)ui->UldManager.NodeList[59];
|
||||
if (text == null || !text->AtkResNode.IsVisible)
|
||||
{
|
||||
text = (AtkTextNode*)ui->UldManager.NodeList[60];
|
||||
}
|
||||
|
||||
return text != null ? text->NodeText.ToString() : null;
|
||||
}
|
||||
|
||||
private string GetPlayerName()
|
||||
{
|
||||
return clientState.LocalPlayer!.Name.ToString();
|
||||
}
|
||||
|
||||
private void LoadMtrlHelper(IntPtr mtrlResourceHandle)
|
||||
{
|
||||
if (mtrlResourceHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var mtrl = (MtrlResource*)mtrlResourceHandle;
|
||||
var mtrlPath = Utf8String.FromSpanUnsafe(mtrl->Handle.FileNameSpan(), true, null, true);
|
||||
PluginLog.Verbose("Attempting to resolve: " + mtrlPath.ToString());
|
||||
var mtrlResource = factory.Create(mtrlPath.ToString());
|
||||
var existingMat = loadedMaterials.FirstOrDefault(m => m.IsReplacedByThis(mtrlResource));
|
||||
if (existingMat != null)
|
||||
{
|
||||
PluginLog.Verbose("Resolving material: " + existingMat.GamePath);
|
||||
for (int i = 0; i < mtrl->NumTex; i++)
|
||||
{
|
||||
var texPath = new Utf8String(mtrl->TexString(i));
|
||||
PluginLog.Verbose("Resolving tex: " + texPath.ToString());
|
||||
|
||||
AddRequestedResource(factory.Create(texPath.ToString()));
|
||||
}
|
||||
|
||||
loadedMaterials = new(loadedMaterials.Except(new[] { existingMat }));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, "error");
|
||||
}
|
||||
}
|
||||
|
||||
private byte LoadMtrlTexDetour(IntPtr mtrlResourceHandle)
|
||||
{
|
||||
LoadMtrlHelper(mtrlResourceHandle);
|
||||
var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private IntPtr ResolveMdlDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint modelType)
|
||||
=> ResolvePathDetour(drawObject, ResolveMdlPathHook!.Original(drawObject, path, unk3, modelType));
|
||||
|
||||
private IntPtr ResolveMtrlDetour(IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5)
|
||||
=> ResolvePathDetour(drawObject, ResolveMtrlPathHook!.Original(drawObject, path, unk3, unk4, unk5));
|
||||
|
||||
private unsafe IntPtr ResolvePathDetour(IntPtr drawObject, IntPtr path)
|
||||
{
|
||||
if (path == IntPtr.Zero || clientState.LocalPlayer == null)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
var gamepath = new Utf8String((byte*)path);
|
||||
|
||||
var playerName = GetPlayerName();
|
||||
var gameDrawObject = (DrawObject*)drawObject;
|
||||
GameObject* gameObject = lastGameObject;
|
||||
|
||||
if (DrawObjectToObject.TryGetValue(drawObject, out ushort idx))
|
||||
{
|
||||
gameObject = GetGameObjectFromDrawObject(drawObject, DrawObjectToObject[drawObject]);
|
||||
}
|
||||
|
||||
if (gameObject != null && (gameObject->DrawObject == null || gameObject->DrawObject == gameDrawObject))
|
||||
{
|
||||
// 240, 241, 242 and 243 might need Penumbra config readout
|
||||
var actualName = gameObject->ObjectIndex switch
|
||||
{
|
||||
240 => GetPlayerName(), // character window
|
||||
241 => GetInspectName() ?? GetCardName() ?? GetGlamourName(), // inspect, character card, glamour plate editor.
|
||||
242 => GetPlayerName(), // try-on
|
||||
243 => GetPlayerName(), // dye preview
|
||||
_ => null,
|
||||
} ?? new Utf8String(gameObject->Name).ToString();
|
||||
|
||||
if (actualName != playerName)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
PluginLog.Verbose("Resolving resource: " + gamepath.ToString());
|
||||
PlayerLoadEvent?.Invoke((IntPtr)gameObject, new EventArgs());
|
||||
|
||||
var resource = factory.Create(gamepath.ToString());
|
||||
|
||||
if (gamepath.ToString().EndsWith("mtrl"))
|
||||
{
|
||||
loadedMaterials.Add(resource);
|
||||
}
|
||||
|
||||
AddRequestedResource(resource);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private void SetupHumanHooks()
|
||||
{
|
||||
if (ResolveMdlPathHook != null) return;
|
||||
|
||||
ResolveMdlPathHook = new Hook<GeneralResolveDelegate>(DrawObjectHumanVTable[ResolveMdlIdx], ResolveMdlDetour);
|
||||
ResolveMtrlPathHook = new Hook<MaterialResolveDetour>(DrawObjectHumanVTable[ResolveMtrlIdx], ResolveMtrlDetour);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,21 @@ using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using MareSynchronos.Hooks;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using MareSynchronos.Factories;
|
||||
using MareSynchronos.Models;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.PlayerWatch;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -20,26 +26,37 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
public class CharacterManager : IDisposable
|
||||
{
|
||||
private readonly DrawHooks drawHooks;
|
||||
private readonly ClientState clientState;
|
||||
private readonly Framework framework;
|
||||
private readonly ApiController apiController;
|
||||
private readonly ObjectTable objectTable;
|
||||
private readonly IpcManager ipcManager;
|
||||
private Task? drawHookTask = null;
|
||||
private readonly FileReplacementFactory factory;
|
||||
private readonly IPlayerWatcher watcher;
|
||||
private Task? playerChangedTask = null;
|
||||
|
||||
public CharacterManager(DrawHooks drawhooks, ClientState clientState, Framework framework, ApiController apiController, ObjectTable objectTable, IpcManager ipcManager)
|
||||
public CharacterManager(ClientState clientState, Framework framework, ApiController apiController, ObjectTable objectTable, IpcManager ipcManager, FileReplacementFactory factory)
|
||||
{
|
||||
this.drawHooks = drawhooks;
|
||||
this.clientState = clientState;
|
||||
this.framework = framework;
|
||||
this.apiController = apiController;
|
||||
this.objectTable = objectTable;
|
||||
this.ipcManager = ipcManager;
|
||||
drawHooks.StartHooks();
|
||||
this.factory = factory;
|
||||
watcher = PlayerWatchFactory.Create(framework, clientState, objectTable);
|
||||
clientState.TerritoryChanged += ClientState_TerritoryChanged;
|
||||
framework.Update += Framework_Update;
|
||||
drawhooks.PlayerLoadEvent += Drawhooks_PlayerLoadEvent;
|
||||
ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
|
||||
}
|
||||
|
||||
private void IpcManager_PenumbraRedrawEvent(object? sender, EventArgs e)
|
||||
{
|
||||
var actorName = ((string)sender!);
|
||||
PluginLog.Debug("Penumbra redraw " + actorName);
|
||||
if (actorName == GetPlayerName())
|
||||
{
|
||||
PlayerChanged(actorName);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, string> localPlayers = new();
|
||||
@@ -81,6 +98,118 @@ namespace MareSynchronos.Managers
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPlayerName()
|
||||
{
|
||||
return clientState.LocalPlayer!.Name.ToString();
|
||||
}
|
||||
|
||||
private void Watcher_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
|
||||
{
|
||||
if (actor.Name.ToString() == clientState.LocalPlayer!.Name.ToString())
|
||||
{
|
||||
PlayerChanged(actor.Name.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug("PlayerChanged: " + actor.Name.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void PlayerChanged(string name)
|
||||
{
|
||||
//if (sender == null) return;
|
||||
PluginLog.Debug("Player changed: " + name);
|
||||
if (playerChangedTask is { IsCompleted: false }) return;
|
||||
|
||||
playerChangedTask = Task.Run(() =>
|
||||
{
|
||||
var obj = (GameObject*)clientState.LocalPlayer!.Address;
|
||||
|
||||
PluginLog.Debug("Waiting for charater to be drawn");
|
||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000) // 0b100000000000 is "still rendering" or something
|
||||
{
|
||||
PluginLog.Debug("Waiting for character to finish drawing");
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
PluginLog.Debug("Character finished drawing");
|
||||
|
||||
// wait half a second just in case
|
||||
Thread.Sleep(500);
|
||||
|
||||
var cache = CreateFullCharacterCache();
|
||||
while (!cache.IsCompleted)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
_ = apiController.SendCharacterData(cache.Result);
|
||||
});
|
||||
}
|
||||
|
||||
public unsafe CharacterCache BuildCharacterCache()
|
||||
{
|
||||
var cache = new CharacterCache();
|
||||
|
||||
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 mdlPath = new Utf8String(mdl->ResourceHandle->FileName()).ToString();
|
||||
|
||||
FileReplacement cachedMdlResource = factory.Create();
|
||||
cachedMdlResource.GamePaths = ipcManager.PenumbraReverseResolvePath(mdlPath, GetPlayerName());
|
||||
cachedMdlResource.SetResolvedPath(mdlPath);
|
||||
PluginLog.Verbose("Resolving for model " + mdlPath);
|
||||
|
||||
cache.AddAssociatedResource(cachedMdlResource, null!, null!);
|
||||
|
||||
var imc = (ResourceHandle*)model->IMCArray[idx];
|
||||
if (imc != null)
|
||||
{
|
||||
byte[] imcData = new byte[imc->Data->DataLength / sizeof(long)];
|
||||
Marshal.Copy((IntPtr)imc->Data->DataPtr, imcData, 0, (int)imc->Data->DataLength / sizeof(long));
|
||||
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();
|
||||
var mtrlPath = new Utf8String(mtrl->ResourceHandle->FileName()).ToString().Split("|")[2];
|
||||
PluginLog.Verbose("Resolving for material " + mtrlPath);
|
||||
var cachedMtrlResource = factory.Create();
|
||||
cachedMtrlResource.GamePaths = ipcManager.PenumbraReverseResolvePath(mtrlPath, GetPlayerName());
|
||||
cachedMtrlResource.SetResolvedPath(mtrlPath);
|
||||
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)).ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(texPath.ToString())) continue;
|
||||
PluginLog.Verbose("Resolving for texture " + texPath);
|
||||
|
||||
var cachedTexResource = factory.Create();
|
||||
cachedTexResource.GamePaths = new[] { texPath };
|
||||
cachedTexResource.SetResolvedPath(ipcManager.PenumbraResolvePath(texPath, GetPlayerName())!);
|
||||
cache.AddAssociatedResource(cachedTexResource, cachedMdlResource, cachedMtrlResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void ClientState_TerritoryChanged(object? sender, ushort e)
|
||||
{
|
||||
localPlayers.Clear();
|
||||
@@ -89,46 +218,31 @@ namespace MareSynchronos.Managers
|
||||
public void Dispose()
|
||||
{
|
||||
framework.Update -= Framework_Update;
|
||||
drawHooks.PlayerLoadEvent -= Drawhooks_PlayerLoadEvent;
|
||||
clientState.TerritoryChanged -= ClientState_TerritoryChanged;
|
||||
drawHooks?.Dispose();
|
||||
watcher.PlayerChanged -= Watcher_PlayerChanged;
|
||||
watcher?.Dispose();
|
||||
}
|
||||
|
||||
private unsafe void Drawhooks_PlayerLoadEvent(object? sender, EventArgs e)
|
||||
internal void StartWatchingPlayer()
|
||||
{
|
||||
if (sender == null) return;
|
||||
if (drawHookTask != null && !drawHookTask.IsCompleted) return;
|
||||
|
||||
var obj = (GameObject*)(IntPtr)sender;
|
||||
drawHookTask = Task.Run(() =>
|
||||
{
|
||||
PluginLog.Debug("Waiting for charater to be drawn");
|
||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000) // 0b100000000000 is "still rendering" or something
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
PluginLog.Debug("Character finished drawing");
|
||||
|
||||
// wait one more second just in case
|
||||
Thread.Sleep(1000);
|
||||
|
||||
var cache = CreateFullCharacterCache();
|
||||
while (!cache.IsCompleted)
|
||||
{
|
||||
Task.Delay(50);
|
||||
}
|
||||
|
||||
_ = apiController.SendCharacterData(cache.Result);
|
||||
});
|
||||
watcher.AddPlayerToWatch(clientState.LocalPlayer!.Name.ToString());
|
||||
watcher.PlayerChanged += Watcher_PlayerChanged;
|
||||
watcher.Enable();
|
||||
}
|
||||
|
||||
public CharacterCache GetCharacterCache() => drawHooks.BuildCharacterCache();
|
||||
public void StopWatchPlayer(string name)
|
||||
{
|
||||
watcher.RemovePlayerFromWatch(name);
|
||||
}
|
||||
|
||||
public void PrintRequestedResources() => drawHooks.PrintRequestedResources();
|
||||
public void WatchPlayer(string name)
|
||||
{
|
||||
watcher.AddPlayerToWatch(name);
|
||||
}
|
||||
|
||||
private async Task<CharacterCache> CreateFullCharacterCache()
|
||||
{
|
||||
var cache = drawHooks.BuildCharacterCache();
|
||||
var cache = BuildCharacterCache();
|
||||
cache.SetGlamourerData(ipcManager.GlamourerGetCharacterCustomization()!);
|
||||
cache.JobId = clientState.LocalPlayer!.ClassJob.Id;
|
||||
await Task.Run(async () =>
|
||||
@@ -137,6 +251,7 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
|
||||
|
||||
cache.CacheHash = Crypto.GetHash(json);
|
||||
@@ -145,12 +260,12 @@ namespace MareSynchronos.Managers
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void DebugJson()
|
||||
public async Task DebugJson()
|
||||
{
|
||||
var cache = CreateFullCharacterCache();
|
||||
while (!cache.IsCompleted)
|
||||
{
|
||||
Task.Delay(50);
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
PluginLog.Debug(JsonConvert.SerializeObject(cache.Result, Formatting.Indented));
|
||||
|
||||
@@ -15,10 +15,14 @@ namespace MareSynchronos.Managers
|
||||
private ICallGateSubscriber<string, string, object>? glamourerApplyCharacterCustomization;
|
||||
private ICallGateSubscriber<int> penumbraApiVersion;
|
||||
private ICallGateSubscriber<int> glamourerApiVersion;
|
||||
private ICallGateSubscriber<string, string> penumbraObjectIsRedrawn;
|
||||
private ICallGateSubscriber<string, int, object>? penumbraRedraw;
|
||||
private ICallGateSubscriber<string, string, string[]>? penumbraReverseResolvePath;
|
||||
|
||||
public bool Initialized { get; private set; } = false;
|
||||
|
||||
public event EventHandler? PenumbraRedrawEvent;
|
||||
|
||||
public IpcManager(DalamudPluginInterface pi)
|
||||
{
|
||||
pluginInterface = pi;
|
||||
@@ -29,9 +33,12 @@ namespace MareSynchronos.Managers
|
||||
penumbraRedraw = pluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||
glamourerGetCharacterCustomization = pluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization");
|
||||
glamourerApplyCharacterCustomization = pluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization");
|
||||
penumbraReverseResolvePath = pluginInterface.GetIpcSubscriber<string, string, string[]>("Penumbra.ReverseResolvePath");
|
||||
penumbraApiVersion = pluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion");
|
||||
glamourerApiVersion = pluginInterface.GetIpcSubscriber<int>("Glamourer.ApiVersion");
|
||||
penumbraInit.Subscribe(() => penumbraRedraw!.InvokeAction("self", 0));
|
||||
penumbraObjectIsRedrawn = pluginInterface.GetIpcSubscriber<string, string>("Penumbra.ObjectIsRedrawn");
|
||||
penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
|
||||
penumbraInit.Subscribe(RedrawSelf);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
@@ -60,16 +67,35 @@ namespace MareSynchronos.Managers
|
||||
}
|
||||
}
|
||||
|
||||
private void RedrawEvent(string actorName)
|
||||
{
|
||||
PenumbraRedrawEvent?.Invoke(actorName, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void RedrawSelf()
|
||||
{
|
||||
penumbraRedraw!.InvokeAction("self", 0);
|
||||
}
|
||||
|
||||
private void Uninitialize()
|
||||
{
|
||||
penumbraInit.Unsubscribe(RedrawSelf);
|
||||
penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
|
||||
penumbraResolvePath = null;
|
||||
penumbraResolveModDir = null;
|
||||
glamourerGetCharacterCustomization = null;
|
||||
glamourerApplyCharacterCustomization = null;
|
||||
penumbraReverseResolvePath = null;
|
||||
Initialized = false;
|
||||
PluginLog.Debug("IPC Manager disposed");
|
||||
}
|
||||
|
||||
public string[] PenumbraReverseResolvePath(string path, string characterName)
|
||||
{
|
||||
if (!CheckPenumbraAPI()) return new[] { path };
|
||||
return penumbraReverseResolvePath!.InvokeFunc(path, characterName);
|
||||
}
|
||||
|
||||
public string? PenumbraResolvePath(string path, string characterName)
|
||||
{
|
||||
if (!CheckPenumbraAPI()) return null;
|
||||
|
||||
@@ -13,10 +13,10 @@ namespace MareSynchronos.Models
|
||||
{
|
||||
[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))
|
||||
.Distinct().OrderBy(f => f.GamePath)
|
||||
FileReplacements.Where(f => f.HasFileReplacement)
|
||||
.Concat(FileReplacements.SelectMany(f => f.Associated)).Where(f => f.HasFileReplacement)
|
||||
.Concat(FileReplacements.SelectMany(f => f.Associated).SelectMany(f => f.Associated)).Where(f => f.HasFileReplacement)
|
||||
.Distinct().OrderBy(f => f.GamePaths[0])
|
||||
.ToList();
|
||||
|
||||
public List<FileReplacement> FileReplacements { get; set; } = new List<FileReplacement>();
|
||||
@@ -31,11 +31,10 @@ namespace MareSynchronos.Models
|
||||
|
||||
[JsonProperty]
|
||||
public uint JobId { get; set; } = 0;
|
||||
public void AddAssociatedResource(FileReplacement resource, FileReplacement mdlParent, FileReplacement mtrlParent)
|
||||
public void AddAssociatedResource(FileReplacement resource, FileReplacement? mdlParent, FileReplacement? mtrlParent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (resource == null) return;
|
||||
if (mdlParent == null)
|
||||
{
|
||||
resource.IsInUse = true;
|
||||
@@ -43,16 +42,16 @@ namespace MareSynchronos.Models
|
||||
return;
|
||||
}
|
||||
|
||||
FileReplacement replacement;
|
||||
|
||||
if (mtrlParent == null && (replacement = FileReplacements.SingleOrDefault(f => f == mdlParent)!) != null)
|
||||
var mdlReplacements = FileReplacements.Where(f => f == mdlParent && mtrlParent == null);
|
||||
foreach (var mdlReplacement in mdlReplacements)
|
||||
{
|
||||
replacement.AddAssociated(resource);
|
||||
mdlReplacement.AddAssociated(resource);
|
||||
}
|
||||
|
||||
if ((replacement = FileReplacements.SingleOrDefault(f => f == mdlParent)?.Associated.SingleOrDefault(f => f == mtrlParent)!) != null)
|
||||
var mtrlReplacements = FileReplacements.Where(f => f == mdlParent).SelectMany(a => a.Associated).Where(f => f == mtrlParent);
|
||||
foreach (var mtrlReplacement in mtrlReplacements)
|
||||
{
|
||||
replacement.AddAssociated(resource);
|
||||
mtrlReplacement.AddAssociated(resource);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -89,7 +88,7 @@ namespace MareSynchronos.Models
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (var fileReplacement in FileReplacements.OrderBy(a => a.GamePath))
|
||||
foreach (var fileReplacement in FileReplacements.OrderBy(a => a.GamePaths[0]))
|
||||
{
|
||||
stringBuilder.AppendLine(fileReplacement.ToString());
|
||||
}
|
||||
|
||||
@@ -17,21 +17,21 @@ namespace MareSynchronos.Models
|
||||
private readonly string penumbraDirectory;
|
||||
|
||||
[JsonProperty]
|
||||
public string GamePath { get; private set; }
|
||||
public string ResolvedPath { get; private set; } = string.Empty;
|
||||
public string[] GamePaths { get; set; } = Array.Empty<string>();
|
||||
[JsonProperty]
|
||||
public string ResolvedPath { get; 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>();
|
||||
[JsonProperty]
|
||||
public string ImcData { get; set; } = string.Empty;
|
||||
public bool HasFileReplacement => GamePath != ResolvedPath;
|
||||
public bool HasFileReplacement => GamePaths.Length >= 1 && GamePaths[0] != ResolvedPath;
|
||||
|
||||
public bool Computed => (computationTask == null || (computationTask?.IsCompleted ?? true)) && Associated.All(f => f.Computed);
|
||||
private Task? computationTask = null;
|
||||
public FileReplacement(string gamePath, string penumbraDirectory)
|
||||
public FileReplacement(string penumbraDirectory)
|
||||
{
|
||||
GamePath = gamePath;
|
||||
this.penumbraDirectory = penumbraDirectory;
|
||||
}
|
||||
|
||||
@@ -39,15 +39,7 @@ namespace MareSynchronos.Models
|
||||
{
|
||||
fileReplacement.IsInUse = true;
|
||||
|
||||
if (!Associated.Any(a => a.IsReplacedByThis(fileReplacement)))
|
||||
{
|
||||
Associated.Add(fileReplacement);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetGamePath(string path)
|
||||
{
|
||||
GamePath = path;
|
||||
Associated.Add(fileReplacement);
|
||||
}
|
||||
|
||||
public void SetResolvedPath(string path)
|
||||
@@ -107,26 +99,16 @@ namespace MareSynchronos.Models
|
||||
return hash;
|
||||
}
|
||||
|
||||
public bool IsReplacedByThis(string path)
|
||||
{
|
||||
return GamePath.ToLower() == path.ToLower() || ResolvedPath.ToLower() == path.ToLower();
|
||||
}
|
||||
|
||||
public bool IsReplacedByThis(FileReplacement replacement)
|
||||
{
|
||||
return IsReplacedByThis(replacement.GamePath) || IsReplacedByThis(replacement.ResolvedPath);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ResolvedPath}");
|
||||
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
|
||||
foreach (var l1 in Associated)
|
||||
{
|
||||
builder.AppendLine($" + Modded: {l1.HasFileReplacement} - {l1.GamePath} => {l1.ResolvedPath}");
|
||||
builder.AppendLine($" + Modded: {l1.HasFileReplacement} - {string.Join(",", l1.GamePaths)} => {l1.ResolvedPath}");
|
||||
foreach (var l2 in l1.Associated)
|
||||
{
|
||||
builder.AppendLine($" + Modded: {l2.HasFileReplacement} - {l2.GamePath} => {l2.ResolvedPath}");
|
||||
builder.AppendLine($" + Modded: {l2.HasFileReplacement} - {string.Join(",", l2.GamePaths)} => {l2.ResolvedPath}");
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
@@ -137,7 +119,7 @@ namespace MareSynchronos.Models
|
||||
if (obj == null) return true;
|
||||
if (obj.GetType() == typeof(FileReplacement))
|
||||
{
|
||||
return Hash == ((FileReplacement)obj).Hash && GamePath == ((FileReplacement)obj).GamePath;
|
||||
return Hash == ((FileReplacement)obj).Hash;
|
||||
}
|
||||
|
||||
return base.Equals(obj);
|
||||
@@ -148,8 +130,7 @@ namespace MareSynchronos.Models
|
||||
int result = 13;
|
||||
result *= 397;
|
||||
result += Hash.GetHashCode();
|
||||
result += GamePath.GetHashCode();
|
||||
result += ImcData.GetHashCode();
|
||||
result += ResolvedPath.GetHashCode();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.Hooks;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState;
|
||||
@@ -29,21 +28,19 @@ namespace MareSynchronos
|
||||
private const string commandName = "/mare";
|
||||
private readonly ClientState clientState;
|
||||
private readonly Framework framework;
|
||||
private readonly GameGui gameGui;
|
||||
private readonly ObjectTable objectTable;
|
||||
private readonly WindowSystem windowSystem;
|
||||
private readonly ApiController apiController;
|
||||
private CharacterManager? characterManager;
|
||||
private IpcManager ipcManager;
|
||||
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
|
||||
Framework framework, ObjectTable objectTable, ClientState clientState, GameGui gameGui)
|
||||
Framework framework, ObjectTable objectTable, ClientState clientState)
|
||||
{
|
||||
this.PluginInterface = pluginInterface;
|
||||
this.CommandManager = commandManager;
|
||||
this.framework = framework;
|
||||
this.objectTable = objectTable;
|
||||
this.clientState = clientState;
|
||||
this.gameGui = gameGui;
|
||||
Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
Configuration.Initialize(this.PluginInterface);
|
||||
|
||||
@@ -94,8 +91,8 @@ namespace MareSynchronos
|
||||
}
|
||||
|
||||
characterManager = new CharacterManager(
|
||||
new DrawHooks(PluginInterface, clientState, objectTable, new FileReplacementFactory(ipcManager, clientState), gameGui),
|
||||
clientState, framework, apiController, objectTable, ipcManager);
|
||||
clientState, framework, apiController, objectTable, ipcManager, new FileReplacementFactory(ipcManager));
|
||||
characterManager.StartWatchingPlayer();
|
||||
ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString());
|
||||
});
|
||||
|
||||
@@ -140,11 +137,14 @@ namespace MareSynchronos
|
||||
File.Copy(fileCache.Filepath, newFilePath);
|
||||
if (resourceDict != null)
|
||||
{
|
||||
resourceDict[replacement.GamePath] = $"files\\{fileCache.Hash.ToLower() + ext}";
|
||||
foreach(var path in replacement.GamePaths)
|
||||
{
|
||||
resourceDict[path] = $"files\\{fileCache.Hash.ToLower() + ext}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File.AppendAllLines(Path.Combine(targetDirectory, "filelist.txt"), new[] { $"\"{replacement.GamePath}\": \"files\\\\{fileCache.Hash.ToLower() + ext}\"," });
|
||||
//File.AppendAllLines(Path.Combine(targetDirectory, "filelist.txt"), new[] { $"\"{replacement.GamePath}\": \"files\\\\{fileCache.Hash.ToLower() + ext}\"," });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,14 +167,21 @@ namespace MareSynchronos
|
||||
|
||||
private void OnCommand(string command, string args)
|
||||
{
|
||||
if (args == "print")
|
||||
{
|
||||
characterManager?.PrintRequestedResources();
|
||||
}
|
||||
|
||||
if (args == "printjson")
|
||||
{
|
||||
characterManager?.DebugJson();
|
||||
_ = characterManager?.DebugJson();
|
||||
}
|
||||
|
||||
if (args.StartsWith("watch"))
|
||||
{
|
||||
var playerName = args.Replace("watch", "").Trim();
|
||||
characterManager!.WatchPlayer(playerName);
|
||||
}
|
||||
|
||||
if (args.StartsWith("stop"))
|
||||
{
|
||||
var playerName = args.Replace("watch", "").Trim();
|
||||
characterManager!.StopWatchPlayer(playerName);
|
||||
}
|
||||
|
||||
if (args == "createtestmod")
|
||||
@@ -199,7 +206,7 @@ namespace MareSynchronos
|
||||
Description = "Mare Synchronous Test Mod Export",
|
||||
};
|
||||
|
||||
var resources = characterManager!.GetCharacterCache();
|
||||
var resources = characterManager!.BuildCharacterCache();
|
||||
var metaJson = JsonConvert.SerializeObject(meta);
|
||||
File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user