actually start to bring structure into the project
make it resilent against restarts/reloads remove all user interaction for resource gathering compute hashes on first time file resolving and on updates of said file on resolving
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SamplePlugin
|
namespace MareSynchronos
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
using MareSynchronos.FileCacheDB;
|
using MareSynchronos.FileCacheDB;
|
||||||
using System.Security.Cryptography;
|
using MareSynchronos.Utils;
|
||||||
|
|
||||||
|
|
||||||
namespace MareSynchronos.Factories
|
namespace MareSynchronos.Factories
|
||||||
{
|
{
|
||||||
public class FileCacheFactory
|
public class FileCacheFactory
|
||||||
{
|
{
|
||||||
public FileCacheFactory()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileCache Create(string file)
|
public FileCache Create(string file)
|
||||||
{
|
{
|
||||||
FileInfo fileInfo = new(file);
|
FileInfo fileInfo = new(file);
|
||||||
string sha1Hash = GetHash(fileInfo.FullName);
|
string sha1Hash = Crypto.GetFileHash(fileInfo.FullName);
|
||||||
return new FileCache()
|
return new FileCache()
|
||||||
{
|
{
|
||||||
Filepath = fileInfo.FullName,
|
Filepath = fileInfo.FullName,
|
||||||
@@ -28,14 +21,8 @@ namespace MareSynchronos.Factories
|
|||||||
public void UpdateFileCache(FileCache cache)
|
public void UpdateFileCache(FileCache cache)
|
||||||
{
|
{
|
||||||
FileInfo fileInfo = new(cache.Filepath);
|
FileInfo fileInfo = new(cache.Filepath);
|
||||||
cache.Hash = GetHash(cache.Filepath);
|
cache.Hash = Crypto.GetFileHash(cache.Filepath);
|
||||||
cache.LastModifiedDate = fileInfo.LastWriteTimeUtc.Ticks.ToString();
|
cache.LastModifiedDate = fileInfo.LastWriteTimeUtc.Ticks.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetHash(string filePath)
|
|
||||||
{
|
|
||||||
using SHA1CryptoServiceProvider cryptoProvider = new();
|
|
||||||
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,34 @@
|
|||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Plugin;
|
using MareSynchronos.Managers;
|
||||||
using Dalamud.Plugin.Ipc;
|
|
||||||
using MareSynchronos.FileCacheDB;
|
|
||||||
using MareSynchronos.Models;
|
using MareSynchronos.Models;
|
||||||
|
|
||||||
namespace MareSynchronos.Factories
|
namespace MareSynchronos.Factories
|
||||||
{
|
{
|
||||||
public class FileReplacementFactory
|
public class FileReplacementFactory
|
||||||
{
|
{
|
||||||
|
private readonly IpcManager ipcManager;
|
||||||
private readonly ClientState clientState;
|
private readonly ClientState clientState;
|
||||||
private ICallGateSubscriber<string, string, string> resolvePath;
|
|
||||||
private string penumbraDirectory;
|
|
||||||
|
|
||||||
public FileReplacementFactory(DalamudPluginInterface pluginInterface, ClientState clientState)
|
public FileReplacementFactory(IpcManager ipcManager, ClientState clientState)
|
||||||
{
|
{
|
||||||
resolvePath = pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
|
this.ipcManager = ipcManager;
|
||||||
penumbraDirectory = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc().ToLower() + '\\';
|
|
||||||
this.clientState = clientState;
|
this.clientState = clientState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileReplacement Create(string gamePath, bool resolve = true)
|
public FileReplacement Create(string gamePath, bool resolve = true)
|
||||||
{
|
{
|
||||||
var fileReplacement = new FileReplacement(gamePath, penumbraDirectory);
|
var fileReplacement = new FileReplacement(gamePath, ipcManager.PenumbraModDirectory()!);
|
||||||
if (!resolve) return fileReplacement;
|
if (!resolve) return fileReplacement;
|
||||||
|
|
||||||
fileReplacement.SetResolvedPath(resolvePath.InvokeFunc(gamePath, clientState.LocalPlayer!.Name.ToString()));
|
string playerName = clientState.LocalPlayer!.Name.ToString();
|
||||||
|
fileReplacement.SetResolvedPath(ipcManager.PenumbraResolvePath(gamePath, playerName)!);
|
||||||
if (!fileReplacement.HasFileReplacement)
|
if (!fileReplacement.HasFileReplacement)
|
||||||
{
|
{
|
||||||
// try to resolve path with --filename 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 = ipcManager.PenumbraResolvePath(newTempGamePath, playerName)!;
|
||||||
if (resolvedPath != newTempGamePath)
|
if (resolvedPath != newTempGamePath)
|
||||||
{
|
{
|
||||||
fileReplacement.SetResolvedPath(resolvedPath);
|
fileReplacement.SetResolvedPath(resolvedPath);
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ namespace MareSynchronos.FileCacheDB
|
|||||||
public string Hash { get; set; }
|
public string Hash { get; set; }
|
||||||
public string Filepath { get; set; }
|
public string Filepath { get; set; }
|
||||||
public string LastModifiedDate { get; set; }
|
public string LastModifiedDate { get; set; }
|
||||||
|
public int Version { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ namespace MareSynchronos.FileCacheDB
|
|||||||
entity.HasKey(e => new { e.Hash, e.Filepath });
|
entity.HasKey(e => new { e.Hash, e.Filepath });
|
||||||
|
|
||||||
entity.ToTable("FileCache");
|
entity.ToTable("FileCache");
|
||||||
|
|
||||||
|
entity.Property(c => c.Version).HasDefaultValue(0).IsRowVersion();
|
||||||
});
|
});
|
||||||
|
|
||||||
OnModelCreatingPartial(modelBuilder);
|
OnModelCreatingPartial(modelBuilder);
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ namespace MareSynchronos.Hooks
|
|||||||
[Signature("4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour")]
|
[Signature("4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour")]
|
||||||
public Hook<LoadMtrlFilesDelegate>? LoadMtrlTexHook;
|
public Hook<LoadMtrlFilesDelegate>? LoadMtrlTexHook;
|
||||||
|
|
||||||
|
public event EventHandler? PlayerLoadEvent;
|
||||||
|
|
||||||
public Hook<GeneralResolveDelegate>? ResolveMdlPathHook;
|
public Hook<GeneralResolveDelegate>? ResolveMdlPathHook;
|
||||||
public Hook<MaterialResolveDetour>? ResolveMtrlPathHook;
|
public Hook<MaterialResolveDetour>? ResolveMtrlPathHook;
|
||||||
private readonly ClientState clientState;
|
private readonly ClientState clientState;
|
||||||
@@ -51,7 +53,6 @@ namespace MareSynchronos.Hooks
|
|||||||
private ConcurrentBag<FileReplacement> cachedResources = new();
|
private ConcurrentBag<FileReplacement> cachedResources = new();
|
||||||
private GameObject* lastGameObject = null;
|
private GameObject* lastGameObject = null;
|
||||||
private ConcurrentBag<FileReplacement> loadedMaterials = new();
|
private ConcurrentBag<FileReplacement> loadedMaterials = new();
|
||||||
private CharacterCache characterCache;
|
|
||||||
|
|
||||||
public DrawHooks(DalamudPluginInterface pluginInterface, ClientState clientState, ObjectTable objectTable, FileReplacementFactory factory, GameGui gameGui)
|
public DrawHooks(DalamudPluginInterface pluginInterface, ClientState clientState, ObjectTable objectTable, FileReplacementFactory factory, GameGui gameGui)
|
||||||
{
|
{
|
||||||
@@ -60,7 +61,6 @@ namespace MareSynchronos.Hooks
|
|||||||
this.objectTable = objectTable;
|
this.objectTable = objectTable;
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.gameGui = gameGui;
|
this.gameGui = gameGui;
|
||||||
characterCache = new CharacterCache();
|
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,9 +85,9 @@ namespace MareSynchronos.Hooks
|
|||||||
resource.ImcData = string.Empty;
|
resource.ImcData = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cache = new CharacterCache();
|
PluginLog.Debug("Invaldated resource cache");
|
||||||
|
|
||||||
PluginLog.Debug("Invaldated character cache");
|
var cache = new CharacterCache();
|
||||||
|
|
||||||
var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject();
|
var model = (CharacterBase*)((Character*)clientState.LocalPlayer!.Address)->GameObject.GetDrawObject();
|
||||||
for (var idx = 0; idx < model->SlotCount; ++idx)
|
for (var idx = 0; idx < model->SlotCount; ++idx)
|
||||||
@@ -100,6 +100,7 @@ namespace MareSynchronos.Hooks
|
|||||||
|
|
||||||
var mdlResource = factory.Create(new Utf8String(mdl->ResourceHandle->FileName()).ToString());
|
var mdlResource = factory.Create(new Utf8String(mdl->ResourceHandle->FileName()).ToString());
|
||||||
var cachedMdlResource = cachedResources.First(r => r.IsReplacedByThis(mdlResource));
|
var cachedMdlResource = cachedResources.First(r => r.IsReplacedByThis(mdlResource));
|
||||||
|
|
||||||
var imc = (ResourceHandle*)model->IMCArray[idx];
|
var imc = (ResourceHandle*)model->IMCArray[idx];
|
||||||
if (imc != null)
|
if (imc != null)
|
||||||
{
|
{
|
||||||
@@ -178,7 +179,6 @@ namespace MareSynchronos.Hooks
|
|||||||
|
|
||||||
private IntPtr CharacterBaseCreateDetour(uint a, IntPtr b, IntPtr c, byte d)
|
private IntPtr CharacterBaseCreateDetour(uint a, IntPtr b, IntPtr c, byte d)
|
||||||
{
|
{
|
||||||
PluginLog.Debug("Character base detour");
|
|
||||||
var ret = CharacterBaseCreateHook!.Original(a, b, c, d);
|
var ret = CharacterBaseCreateHook!.Original(a, b, c, d);
|
||||||
if (lastGameObject != null)
|
if (lastGameObject != null)
|
||||||
{
|
{
|
||||||
@@ -193,7 +193,7 @@ namespace MareSynchronos.Hooks
|
|||||||
if (DrawObjectToObject.TryGetValue(drawBase, out ushort idx))
|
if (DrawObjectToObject.TryGetValue(drawBase, out ushort idx))
|
||||||
{
|
{
|
||||||
var gameObj = GetGameObjectFromDrawObject(drawBase, idx);
|
var gameObj = GetGameObjectFromDrawObject(drawBase, idx);
|
||||||
if (gameObj == (GameObject*)clientState.LocalPlayer!.Address)
|
if (clientState.LocalPlayer != null && gameObj == (GameObject*)clientState.LocalPlayer!.Address)
|
||||||
{
|
{
|
||||||
PluginLog.Debug("Clearing resources");
|
PluginLog.Debug("Clearing resources");
|
||||||
cachedResources.Clear();
|
cachedResources.Clear();
|
||||||
@@ -347,7 +347,8 @@ namespace MareSynchronos.Hooks
|
|||||||
|
|
||||||
private byte LoadMtrlTexDetour(IntPtr mtrlResourceHandle)
|
private byte LoadMtrlTexDetour(IntPtr mtrlResourceHandle)
|
||||||
{
|
{
|
||||||
LoadMtrlHelper(mtrlResourceHandle);
|
if (clientState.LocalPlayer != null)
|
||||||
|
LoadMtrlHelper(mtrlResourceHandle);
|
||||||
var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle);
|
var ret = LoadMtrlTexHook!.Original(mtrlResourceHandle);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -360,7 +361,7 @@ namespace MareSynchronos.Hooks
|
|||||||
|
|
||||||
private unsafe IntPtr ResolvePathDetour(IntPtr drawObject, IntPtr path)
|
private unsafe IntPtr ResolvePathDetour(IntPtr drawObject, IntPtr path)
|
||||||
{
|
{
|
||||||
if (path == IntPtr.Zero)
|
if (path == IntPtr.Zero || clientState.LocalPlayer == null)
|
||||||
{
|
{
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@@ -393,6 +394,8 @@ namespace MareSynchronos.Hooks
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayerLoadEvent?.Invoke((IntPtr)gameObject, new EventArgs());
|
||||||
|
|
||||||
var resource = factory.Create(gamepath.ToString());
|
var resource = factory.Create(gamepath.ToString());
|
||||||
|
|
||||||
if (gamepath.ToString().EndsWith("mtrl"))
|
if (gamepath.ToString().EndsWith("mtrl"))
|
||||||
|
|||||||
119
MareSynchronos/Managers/IpcManager.cs
Normal file
119
MareSynchronos/Managers/IpcManager.cs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Managers
|
||||||
|
{
|
||||||
|
public class IpcManager : IDisposable
|
||||||
|
{
|
||||||
|
private readonly DalamudPluginInterface pluginInterface;
|
||||||
|
private ICallGateSubscriber<object> penumbraInit;
|
||||||
|
private readonly ICallGateSubscriber<object> penumbraDispose;
|
||||||
|
private ICallGateSubscriber<string, string, string>? penumbraResolvePath;
|
||||||
|
private ICallGateSubscriber<string>? penumbraResolveModDir;
|
||||||
|
private ICallGateSubscriber<string>? glamourerGetCharacterCustomization;
|
||||||
|
private ICallGateSubscriber<string, string, object>? glamourerApplyCharacterCustomization;
|
||||||
|
private ICallGateSubscriber<string, int, object>? penumbraRedraw;
|
||||||
|
|
||||||
|
public bool Initialized { get; private set; } = false;
|
||||||
|
|
||||||
|
public event EventHandler? IpcManagerInitialized;
|
||||||
|
|
||||||
|
public IpcManager(DalamudPluginInterface pi)
|
||||||
|
{
|
||||||
|
pluginInterface = pi;
|
||||||
|
penumbraInit = pluginInterface.GetIpcSubscriber<object>("Penumbra.Initialized");
|
||||||
|
penumbraInit.Subscribe(Initialize);
|
||||||
|
penumbraDispose = pluginInterface.GetIpcSubscriber<object>("Penumbra.Disposed");
|
||||||
|
penumbraDispose.Subscribe(Uninitialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckPenumbraAPI()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var penumbraApiVersion = pluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion").InvokeFunc();
|
||||||
|
return penumbraApiVersion >= 4;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckGlamourerAPI()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var glamourerApiVersion = pluginInterface.GetIpcSubscriber<int>("Glamourer.ApiVersion").InvokeFunc();
|
||||||
|
return glamourerApiVersion >= 0;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
if (Initialized) return;
|
||||||
|
if (!CheckPenumbraAPI()) throw new Exception("Penumbra API is outdated or not available");
|
||||||
|
if (!CheckGlamourerAPI()) throw new Exception("Glamourer API is oudated or not available");
|
||||||
|
penumbraResolvePath = pluginInterface.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
|
||||||
|
penumbraResolveModDir = pluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
|
||||||
|
penumbraRedraw = pluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||||
|
glamourerGetCharacterCustomization = pluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization");
|
||||||
|
glamourerApplyCharacterCustomization = pluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization");
|
||||||
|
Initialized = true;
|
||||||
|
IpcManagerInitialized?.Invoke(this, new EventArgs());
|
||||||
|
PluginLog.Debug("[IPC Manager] initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Uninitialize()
|
||||||
|
{
|
||||||
|
penumbraResolvePath = null;
|
||||||
|
penumbraResolveModDir = null;
|
||||||
|
glamourerGetCharacterCustomization = null;
|
||||||
|
glamourerApplyCharacterCustomization = null;
|
||||||
|
Initialized = false;
|
||||||
|
PluginLog.Debug("IPC Manager disposed");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? PenumbraResolvePath(string path, string characterName)
|
||||||
|
{
|
||||||
|
if (!Initialized) return null;
|
||||||
|
return penumbraResolvePath!.InvokeFunc(path, characterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? PenumbraModDirectory()
|
||||||
|
{
|
||||||
|
if (!Initialized) return null;
|
||||||
|
return penumbraResolveModDir!.InvokeFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GlamourerGetCharacterCustomization()
|
||||||
|
{
|
||||||
|
if (!Initialized) return null;
|
||||||
|
return glamourerGetCharacterCustomization!.InvokeFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GlamourerApplyCharacterCustomization(string customization, string characterName)
|
||||||
|
{
|
||||||
|
if (!Initialized) return;
|
||||||
|
glamourerApplyCharacterCustomization!.InvokeAction(customization, characterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PenumbraRedraw(string actorName)
|
||||||
|
{
|
||||||
|
if (!Initialized) return;
|
||||||
|
penumbraRedraw!.InvokeAction(actorName, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Uninitialize();
|
||||||
|
IpcManagerInitialized = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,41 +11,23 @@ namespace MareSynchronos.Models
|
|||||||
[JsonObject(MemberSerialization.OptIn)]
|
[JsonObject(MemberSerialization.OptIn)]
|
||||||
public class CharacterCache
|
public class CharacterCache
|
||||||
{
|
{
|
||||||
public List<FileReplacement> FileReplacements { get; set; } = new List<FileReplacement>();
|
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public List<FileReplacement> AllReplacements =>
|
public List<FileReplacement> AllReplacements =>
|
||||||
FileReplacements.Where(x => x.HasFileReplacement)
|
FileReplacements.Where(x => x.HasFileReplacement)
|
||||||
.Concat(FileReplacements.SelectMany(f => f.Associated).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))
|
.Concat(FileReplacements.SelectMany(f => f.Associated).SelectMany(f => f.Associated).Where(f => f.HasFileReplacement))
|
||||||
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public CharacterCache()
|
public List<FileReplacement> FileReplacements { get; set; } = new List<FileReplacement>();
|
||||||
{
|
|
||||||
|
|
||||||
}
|
[JsonProperty]
|
||||||
|
public string GlamourerString { get; private set; } = string.Empty;
|
||||||
|
|
||||||
public void Invalidate(List<FileReplacement>? fileReplacements = null)
|
public bool IsReady => FileReplacements.All(f => f.Computed);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[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
|
try
|
||||||
@@ -76,6 +58,31 @@ namespace MareSynchronos.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 SetGlamourerData(string glamourerString)
|
||||||
|
{
|
||||||
|
GlamourerString = glamourerString;
|
||||||
|
}
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using MareSynchronos.FileCacheDB;
|
using MareSynchronos.FileCacheDB;
|
||||||
|
using System.IO;
|
||||||
|
using MareSynchronos.Utils;
|
||||||
|
|
||||||
namespace MareSynchronos.Models
|
namespace MareSynchronos.Models
|
||||||
{
|
{
|
||||||
@@ -24,6 +26,9 @@ namespace MareSynchronos.Models
|
|||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string ImcData { get; set; } = string.Empty;
|
public string ImcData { get; set; } = string.Empty;
|
||||||
public bool HasFileReplacement => GamePath != ResolvedPath;
|
public bool HasFileReplacement => GamePath != 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 gamePath, string penumbraDirectory)
|
||||||
{
|
{
|
||||||
GamePath = gamePath;
|
GamePath = gamePath;
|
||||||
@@ -50,15 +55,58 @@ namespace MareSynchronos.Models
|
|||||||
ResolvedPath = path.ToLower().Replace('/', '\\').Replace(penumbraDirectory, "").Replace('\\', '/');
|
ResolvedPath = path.ToLower().Replace('/', '\\').Replace(penumbraDirectory, "").Replace('\\', '/');
|
||||||
if (!HasFileReplacement) return;
|
if (!HasFileReplacement) return;
|
||||||
|
|
||||||
Task.Run(() =>
|
computationTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
using FileCacheContext db = new FileCacheContext();
|
FileCache? fileCache;
|
||||||
var fileCache = db.FileCaches.SingleOrDefault(f => f.Filepath == path.ToLower());
|
using (FileCacheContext db = new())
|
||||||
|
{
|
||||||
|
fileCache = db.FileCaches.SingleOrDefault(f => f.Filepath == path.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
if (fileCache != null)
|
if (fileCache != null)
|
||||||
Hash = fileCache.Hash;
|
{
|
||||||
|
FileInfo fi = new(fileCache.Filepath);
|
||||||
|
if (fi.LastWriteTimeUtc.Ticks == long.Parse(fileCache.LastModifiedDate))
|
||||||
|
{
|
||||||
|
Hash = fileCache.Hash;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Hash = ComputeHash(fi);
|
||||||
|
using var db = new FileCacheContext();
|
||||||
|
var newTempCache = db.FileCaches.Single(f => f.Filepath == path.ToLower());
|
||||||
|
newTempCache.Hash = Hash;
|
||||||
|
db.Update(newTempCache);
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Hash = ComputeHash(new FileInfo(path));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ComputeHash(FileInfo fi)
|
||||||
|
{
|
||||||
|
// compute hash if hash is not present
|
||||||
|
string hash = Crypto.GetFileHash(fi.FullName);
|
||||||
|
|
||||||
|
using FileCacheContext db = new();
|
||||||
|
var fileAddedDuringCompute = db.FileCaches.SingleOrDefault(f => f.Filepath == fi.FullName.ToLower());
|
||||||
|
if (fileAddedDuringCompute != null) return fileAddedDuringCompute.Hash;
|
||||||
|
|
||||||
|
db.Add(new FileCache()
|
||||||
|
{
|
||||||
|
Hash = hash,
|
||||||
|
Filepath = fi.FullName.ToLower(),
|
||||||
|
LastModifiedDate = fi.LastWriteTimeUtc.Ticks.ToString()
|
||||||
|
});
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsReplacedByThis(string path)
|
public bool IsReplacedByThis(string path)
|
||||||
{
|
{
|
||||||
return GamePath.ToLower() == path.ToLower() || ResolvedPath.ToLower() == path.ToLower();
|
return GamePath.ToLower() == path.ToLower() || ResolvedPath.ToLower() == path.ToLower();
|
||||||
@@ -83,5 +131,16 @@ namespace MareSynchronos.Models
|
|||||||
}
|
}
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return true;
|
||||||
|
if (obj.GetType() == typeof(FileReplacement))
|
||||||
|
{
|
||||||
|
return Hash == ((FileReplacement)obj).Hash && GamePath == ((FileReplacement)obj).GamePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Equals(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ using Dalamud.Game.ClientState.Objects;
|
|||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Glamourer.Customization;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using System;
|
using System;
|
||||||
@@ -31,106 +30,133 @@ using System.Text.Unicode;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Serialization;
|
using Newtonsoft.Json.Serialization;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using MareSynchronos.Managers;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
|
||||||
namespace SamplePlugin
|
namespace MareSynchronos
|
||||||
{
|
{
|
||||||
public sealed class Plugin : IDalamudPlugin
|
public sealed class Plugin : IDalamudPlugin
|
||||||
{
|
{
|
||||||
public string Name => "Mare Synchronos";
|
public string Name => "Mare Synchronos";
|
||||||
|
|
||||||
private const string commandName = "/mare";
|
private const string commandName = "/mare";
|
||||||
|
private readonly Framework framework;
|
||||||
|
private readonly ObjectTable objectTable;
|
||||||
private readonly ClientState clientState;
|
private readonly ClientState clientState;
|
||||||
|
private readonly GameGui gameGui;
|
||||||
|
|
||||||
private DalamudPluginInterface PluginInterface { get; init; }
|
private DalamudPluginInterface pluginInterface { get; init; }
|
||||||
private CommandManager CommandManager { get; init; }
|
private CommandManager commandManager { get; init; }
|
||||||
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 DrawHooks drawHooks;
|
private DrawHooks drawHooks;
|
||||||
|
private IpcManager ipcManager;
|
||||||
|
|
||||||
private CancellationTokenSource cts;
|
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
|
||||||
private IPlayerWatcher playerWatch;
|
Framework framework, ObjectTable objectTable, ClientState clientState, GameGui gameGui)
|
||||||
|
|
||||||
public Plugin(
|
|
||||||
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface,
|
|
||||||
[RequiredVersion("1.0")] CommandManager commandManager,
|
|
||||||
Framework framework, ObjectTable objectTable, ClientState clientState, DataManager dataManager, GameGui gameGui)
|
|
||||||
{
|
{
|
||||||
this.PluginInterface = pluginInterface;
|
this.pluginInterface = pluginInterface;
|
||||||
this.CommandManager = commandManager;
|
this.commandManager = commandManager;
|
||||||
|
this.framework = framework;
|
||||||
|
this.objectTable = objectTable;
|
||||||
this.clientState = clientState;
|
this.clientState = clientState;
|
||||||
this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
this.gameGui = gameGui;
|
||||||
this.Configuration.Initialize(this.PluginInterface);
|
configuration = this.pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||||
|
configuration.Initialize(this.pluginInterface);
|
||||||
|
|
||||||
// you might normally want to embed resources and load them from the manifest stream
|
// you might normally want to embed resources and load them from the manifest stream
|
||||||
this.PluginUi = new PluginUI(this.Configuration);
|
this.PluginUi = new PluginUI(this.configuration);
|
||||||
|
|
||||||
this.CommandManager.AddHandler(commandName, new CommandInfo(OnCommand)
|
this.commandManager.AddHandler(commandName, new CommandInfo(OnCommand)
|
||||||
{
|
{
|
||||||
HelpMessage = "pass 'scan' to initialize or rescan files into the database"
|
HelpMessage = "pass 'scan' to initialize or rescan files into the database"
|
||||||
});
|
});
|
||||||
|
|
||||||
FileCacheFactory = new FileCacheFactory();
|
FileCacheContext db = new FileCacheContext();
|
||||||
|
db.Dispose();
|
||||||
|
|
||||||
this.PluginInterface.UiBuilder.Draw += DrawUI;
|
clientState.Login += ClientState_Login;
|
||||||
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
clientState.Logout += ClientState_Logout;
|
||||||
|
|
||||||
playerWatch = PlayerWatchFactory.Create(framework, clientState, objectTable);
|
if (clientState.IsLoggedIn)
|
||||||
drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new FileReplacementFactory(pluginInterface, clientState), gameGui);
|
{
|
||||||
|
ClientState_Login(null, null!);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pluginInterface.UiBuilder.Draw += DrawUI;
|
||||||
|
this.pluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IpcManager_IpcManagerInitialized(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
PluginLog.Debug("IPC Manager initialized event");
|
||||||
|
ipcManager.IpcManagerInitialized -= IpcManager_IpcManagerInitialized;
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (clientState.LocalPlayer == null)
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
drawHooks.StartHooks();
|
||||||
|
ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientState_Logout(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
PluginLog.Debug("Client logout");
|
||||||
|
drawHooks.PlayerLoadEvent -= DrawHooks_PlayerLoadEvent;
|
||||||
|
ipcManager.Dispose();
|
||||||
|
drawHooks.Dispose();
|
||||||
|
ipcManager = null!;
|
||||||
|
drawHooks = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientState_Login(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
PluginLog.Debug("Client login");
|
||||||
|
ipcManager = new IpcManager(pluginInterface);
|
||||||
|
drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new FileReplacementFactory(ipcManager, clientState), gameGui);
|
||||||
|
ipcManager.IpcManagerInitialized += IpcManager_IpcManagerInitialized;
|
||||||
|
ipcManager.Initialize();
|
||||||
|
drawHooks.PlayerLoadEvent += DrawHooks_PlayerLoadEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task drawHookTask;
|
||||||
|
|
||||||
|
private unsafe void DrawHooks_PlayerLoadEvent(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender == null) return;
|
||||||
|
if (drawHookTask != null && !drawHookTask.IsCompleted) return;
|
||||||
|
|
||||||
|
var obj = (GameObject*)(IntPtr)sender;
|
||||||
|
drawHookTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000) // 0b100000000000 is "still rendering" or something
|
||||||
|
{
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should recalculate cache here
|
||||||
|
// probably needs a different method
|
||||||
|
// at that point we will also have to send data to the api
|
||||||
|
_ = drawHooks.BuildCharacterCache();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.PluginUi.Dispose();
|
this.PluginUi?.Dispose();
|
||||||
this.CommandManager.RemoveHandler(commandName);
|
this.commandManager.RemoveHandler(commandName);
|
||||||
playerWatch.PlayerChanged -= PlayerWatch_PlayerChanged;
|
drawHooks.PlayerLoadEvent -= DrawHooks_PlayerLoadEvent;
|
||||||
playerWatch.RemovePlayerFromWatch("Ilya Zhelmo");
|
clientState.Login -= ClientState_Login;
|
||||||
drawHooks.Dispose();
|
clientState.Logout -= ClientState_Logout;
|
||||||
|
ipcManager?.Dispose();
|
||||||
|
drawHooks?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCommand(string command, string args)
|
private void OnCommand(string command, string args)
|
||||||
{
|
{
|
||||||
if (args == "stop")
|
|
||||||
{
|
|
||||||
cts?.Cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(args == "playerdata")
|
|
||||||
{
|
|
||||||
PluginLog.Debug(PluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization").InvokeFunc());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(args == "applyglam")
|
|
||||||
{
|
|
||||||
PluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization")
|
|
||||||
.InvokeAction("Ah3/DwQBAR4IBHOABIOceTIIApkDAgADQmQBZJqepQZlAAEAAAAAAAAAAACcEwEAyxcBbrAXAUnKFwJIuBcBBkYAAQBIAAEANQABADUAAQACAAQAAQAAAIA/Eg==", "Ilya Zhelmo");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args == "scan")
|
|
||||||
{
|
|
||||||
cts = new CancellationTokenSource();
|
|
||||||
|
|
||||||
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")
|
if (args == "print")
|
||||||
{
|
{
|
||||||
var resources = drawHooks.PrintRequestedResources();
|
var resources = drawHooks.PrintRequestedResources();
|
||||||
@@ -139,8 +165,17 @@ namespace SamplePlugin
|
|||||||
if (args == "printjson")
|
if (args == "printjson")
|
||||||
{
|
{
|
||||||
var cache = drawHooks.BuildCharacterCache();
|
var cache = drawHooks.BuildCharacterCache();
|
||||||
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
|
cache.SetGlamourerData(ipcManager.GlamourerGetCharacterCustomization()!);
|
||||||
PluginLog.Debug(json);
|
cache.JobId = clientState.LocalPlayer!.ClassJob.Id;
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (!cache.IsReady)
|
||||||
|
{
|
||||||
|
await Task.Delay(50);
|
||||||
|
}
|
||||||
|
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
|
||||||
|
PluginLog.Debug(json);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args == "createtestmod")
|
if (args == "createtestmod")
|
||||||
@@ -149,7 +184,7 @@ namespace SamplePlugin
|
|||||||
{
|
{
|
||||||
var playerName = clientState.LocalPlayer!.Name.ToString();
|
var playerName = clientState.LocalPlayer!.Name.ToString();
|
||||||
var modName = $"Mare Synchronos Test Mod {playerName}";
|
var modName = $"Mare Synchronos Test Mod {playerName}";
|
||||||
var modDirectory = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc();
|
var modDirectory = ipcManager.PenumbraModDirectory()!;
|
||||||
string modDirectoryPath = Path.Combine(modDirectory, modName);
|
string modDirectoryPath = Path.Combine(modDirectory, modName);
|
||||||
if (Directory.Exists(modDirectoryPath))
|
if (Directory.Exists(modDirectoryPath))
|
||||||
{
|
{
|
||||||
@@ -216,100 +251,14 @@ namespace SamplePlugin
|
|||||||
|
|
||||||
private void PlayerWatch_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
|
private void PlayerWatch_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
|
||||||
{
|
{
|
||||||
var equipment = playerWatch.UpdatePlayerWithoutEvent(actor);
|
//var equipment = playerWatch.UpdatePlayerWithoutEvent(actor);
|
||||||
var customization = new CharacterCustomization(actor);
|
//var customization = new CharacterCustomization(actor);
|
||||||
//DebugCustomization(customization);
|
//DebugCustomization(customization);
|
||||||
//PluginLog.Debug(customization.Gender.ToString());
|
//PluginLog.Debug(customization.Gender.ToString());
|
||||||
if (equipment != null)
|
//if (equipment != null)
|
||||||
{
|
//{
|
||||||
PluginLog.Debug(equipment.ToString());
|
// PluginLog.Debug(equipment.ToString());
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
|
|
||||||
private void StartScan()
|
|
||||||
{
|
|
||||||
Stopwatch st = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
string penumbraDir = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc();
|
|
||||||
PluginLog.Debug("Getting files from " + penumbraDir);
|
|
||||||
ConcurrentDictionary<string, bool> charaFiles = new ConcurrentDictionary<string, bool>(
|
|
||||||
Directory.GetFiles(penumbraDir, "*.*", SearchOption.AllDirectories)
|
|
||||||
.Select(s => s.ToLowerInvariant())
|
|
||||||
.Where(f => !f.EndsWith(".json"))
|
|
||||||
.Where(f => f.Contains(@"\chara\"))
|
|
||||||
.Select(p => new KeyValuePair<string, bool>(p, false)));
|
|
||||||
int count = 0;
|
|
||||||
using FileCacheContext db = new();
|
|
||||||
var fileCaches = db.FileCaches.ToList();
|
|
||||||
|
|
||||||
var fileCachesToUpdate = new ConcurrentBag<FileCache>();
|
|
||||||
var fileCachesToDelete = new ConcurrentBag<FileCache>();
|
|
||||||
var fileCachesToAdd = new ConcurrentBag<FileCache>();
|
|
||||||
|
|
||||||
// scan files from database
|
|
||||||
Parallel.ForEach(fileCaches, new ParallelOptions()
|
|
||||||
{
|
|
||||||
CancellationToken = cts.Token,
|
|
||||||
MaxDegreeOfParallelism = 10
|
|
||||||
},
|
|
||||||
cache =>
|
|
||||||
{
|
|
||||||
count = Interlocked.Increment(ref count);
|
|
||||||
PluginLog.Debug($"[{count}/{fileCaches.Count}] Checking: {cache.Filepath}");
|
|
||||||
|
|
||||||
if (!File.Exists(cache.Filepath))
|
|
||||||
{
|
|
||||||
PluginLog.Debug("File was not found anymore: " + cache.Filepath);
|
|
||||||
fileCachesToDelete.Add(cache);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
charaFiles[cache.Filepath] = true;
|
|
||||||
|
|
||||||
FileInfo fileInfo = new(cache.Filepath);
|
|
||||||
if (fileInfo.LastWriteTimeUtc.Ticks != long.Parse(cache.LastModifiedDate))
|
|
||||||
{
|
|
||||||
PluginLog.Debug("File was modified since last time: " + cache.Filepath + "; " + cache.LastModifiedDate + " / " + fileInfo.LastWriteTimeUtc.Ticks);
|
|
||||||
FileCacheFactory.UpdateFileCache(cache);
|
|
||||||
fileCachesToUpdate.Add(cache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// scan new files
|
|
||||||
count = 0;
|
|
||||||
Parallel.ForEach(charaFiles.Where(c => c.Value == false), new ParallelOptions()
|
|
||||||
{
|
|
||||||
CancellationToken = cts.Token,
|
|
||||||
MaxDegreeOfParallelism = 10
|
|
||||||
},
|
|
||||||
file =>
|
|
||||||
{
|
|
||||||
count = Interlocked.Increment(ref count);
|
|
||||||
PluginLog.Debug($"[{count}/{charaFiles.Count()}] Hashing: {file.Key}");
|
|
||||||
|
|
||||||
fileCachesToAdd.Add(FileCacheFactory.Create(file.Key));
|
|
||||||
});
|
|
||||||
|
|
||||||
st.Stop();
|
|
||||||
|
|
||||||
if (cts.Token.IsCancellationRequested) return;
|
|
||||||
|
|
||||||
PluginLog.Debug("Scanning complete, total elapsed time: " + st.Elapsed.ToString());
|
|
||||||
|
|
||||||
if (fileCachesToAdd.Any() || fileCachesToUpdate.Any() || fileCachesToDelete.Any())
|
|
||||||
{
|
|
||||||
PluginLog.Debug("Writing files to database…");
|
|
||||||
|
|
||||||
db.FileCaches.AddRange(fileCachesToAdd);
|
|
||||||
db.FileCaches.UpdateRange(fileCachesToUpdate);
|
|
||||||
db.FileCaches.RemoveRange(fileCachesToDelete);
|
|
||||||
|
|
||||||
db.SaveChanges();
|
|
||||||
PluginLog.Debug("Database has been written.");
|
|
||||||
}
|
|
||||||
|
|
||||||
cts = new CancellationTokenSource();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawUI()
|
private void DrawUI()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace SamplePlugin
|
namespace MareSynchronos
|
||||||
{
|
{
|
||||||
// It is good to have this be disposable in general, in case you ever need it
|
// It is good to have this be disposable in general, in case you ever need it
|
||||||
// to do any cleanup
|
// to do any cleanup
|
||||||
|
|||||||
22
MareSynchronos/Utils/Crypto.cs
Normal file
22
MareSynchronos/Utils/Crypto.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Utils
|
||||||
|
{
|
||||||
|
public class Crypto
|
||||||
|
{
|
||||||
|
public static string GetFileHash(string filePath)
|
||||||
|
{
|
||||||
|
using SHA1CryptoServiceProvider cryptoProvider = new();
|
||||||
|
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetHash(string stringToHash)
|
||||||
|
{
|
||||||
|
using SHA1CryptoServiceProvider cryptoProvider = new();
|
||||||
|
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user