refactor and fix some shit

This commit is contained in:
Stanley Dimant
2022-06-15 21:30:08 +02:00
parent 8143090033
commit 4f72daa0eb
10 changed files with 351 additions and 188 deletions

View File

@@ -9,7 +9,9 @@ namespace MareSynchronos
{
public int Version { get; set; } = 0;
public string PenumbraFolder { get; set; } = string.Empty;
public string CacheFolder { get; set; } = string.Empty;
public string ClientSecret { get; internal set; } = string.Empty;
public string ApiUri { get; internal set; } = string.Empty;
// the below exist just to make saving less cumbersome

View File

@@ -31,7 +31,7 @@ namespace MareSynchronos.Factories
{
// try to resolve path with --filename instead?
string[] tempGamePath = gamePath.Split('/');
tempGamePath[tempGamePath.Length - 1] = "--" + tempGamePath[tempGamePath.Length - 1];
tempGamePath[^1] = "--" + tempGamePath[^1];
string newTempGamePath = string.Join('/', tempGamePath);
var resolvedPath = ipcManager.PenumbraResolvePath(newTempGamePath, playerName)!;
if (resolvedPath != newTempGamePath)

View File

@@ -144,7 +144,7 @@ namespace MareSynchronos.Hooks
return cache;
}
public List<FileReplacement> PrintRequestedResources()
public void PrintRequestedResources()
{
var cache = BuildCharacterCache();
@@ -158,8 +158,6 @@ namespace MareSynchronos.Hooks
{
PluginLog.Verbose(resource.Value.ToString());
}
return cache.FileReplacements;
}
public void StartHooks()

View File

@@ -0,0 +1,143 @@
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using MareSynchronos.Hooks;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
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;
public CharacterManager(DrawHooks drawhooks, ClientState clientState, Framework framework, ApiController apiController, ObjectTable objectTable, IpcManager ipcManager)
{
this.drawHooks = drawhooks;
this.clientState = clientState;
this.framework = framework;
this.apiController = apiController;
this.objectTable = objectTable;
this.ipcManager = ipcManager;
drawHooks.StartHooks();
clientState.TerritoryChanged += ClientState_TerritoryChanged;
framework.Update += Framework_Update;
drawhooks.PlayerLoadEvent += Drawhooks_PlayerLoadEvent;
}
Dictionary<string, string> localPlayers = new();
private DateTime lastCheck = DateTime.Now;
private unsafe void Framework_Update(Framework framework)
{
try
{
if (clientState.LocalPlayer == null) return;
if (DateTime.Now < lastCheck.AddSeconds(5)) return;
List<string> localPlayersList = new();
List<string> newPlayers = new();
foreach (var obj in objectTable)
{
if (obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
string playerName = obj.Name.ToString();
if (playerName == clientState.LocalPlayer.Name.ToString()) continue;
var hashedName = Crypto.GetHash(playerName);
}
foreach (var item in localPlayers.ToList())
{
if (!localPlayersList.Contains(item.Key))
{
localPlayers.Remove(item.Key);
}
}
if (newPlayers.Any())
PluginLog.Debug("New players: " + string.Join(",", newPlayers.Select(p => p + ":" + localPlayers[p])));
lastCheck = DateTime.Now;
}
catch (Exception ex)
{
PluginLog.Error(ex, "error");
}
}
private void ClientState_TerritoryChanged(object? sender, ushort e)
{
localPlayers.Clear();
}
public void Dispose()
{
framework.Update -= Framework_Update;
drawHooks.PlayerLoadEvent -= Drawhooks_PlayerLoadEvent;
clientState.TerritoryChanged -= ClientState_TerritoryChanged;
drawHooks?.Dispose();
}
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(() =>
{
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);
apiController.SendCharacterData(drawHooks.BuildCharacterCache()).RunSynchronously();
});
}
public CharacterCache GetCharacterCache() => drawHooks.BuildCharacterCache();
public void PrintRequestedResources() => drawHooks.PrintRequestedResources();
public void DebugJson()
{
var cache = drawHooks.BuildCharacterCache();
cache.SetGlamourerData(ipcManager.GlamourerGetCharacterCustomization()!);
cache.JobId = clientState.LocalPlayer!.ClassJob.Id;
Task.Run(async () =>
{
while (!cache.IsReady)
{
await Task.Delay(50);
}
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
cache.CacheHash = Crypto.GetHash(json);
json = JsonConvert.SerializeObject(cache, Formatting.Indented);
PluginLog.Debug(json);
});
}
}
}

View File

@@ -26,6 +26,7 @@
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.7" />
<PackageReference Include="lz4net" Version="1.0.15.93" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.17">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -45,9 +46,6 @@
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Glamourer.GameData">
<HintPath>..\..\..\..\..\AppData\Roaming\XIVLauncher\installedPlugins\Glamourer\0.0.9.0\Glamourer.GameData.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>false</Private>

View File

@@ -88,7 +88,7 @@ namespace MareSynchronos.Models
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilder = new();
foreach (var fileReplacement in FileReplacements.OrderBy(a => a.GamePath))
{
stringBuilder.AppendLine(fileReplacement.ToString());

View File

@@ -119,7 +119,7 @@ namespace MareSynchronos.Models
public override string ToString()
{
StringBuilder builder = new StringBuilder();
StringBuilder builder = new();
builder.AppendLine($"Modded: {HasFileReplacement} - {GamePath} => {ResolvedPath}");
foreach (var l1 in Associated)
{

View File

@@ -1,80 +1,56 @@
using Dalamud.Game.Command;
using Dalamud.IoC;
using Dalamud.Logging;
using Dalamud.Plugin;
using MareSynchronos.FileCacheDB;
using MareSynchronos.Factories;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
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 System.Text;
using Penumbra.GameData.Enums;
using System;
using MareSynchronos.Models;
using Dalamud.Game.Gui;
using MareSynchronos.PenumbraMod;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;
using MareSynchronos.Managers;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using MareSynchronos.Utils;
using LZ4;
using MareSynchronos.WebAPI;
namespace MareSynchronos
{
public sealed class Plugin : IDalamudPlugin
{
public string Name => "Mare Synchronos";
private const string commandName = "/mare";
private readonly Framework framework;
private readonly ObjectTable objectTable;
private readonly ClientState clientState;
private readonly Framework framework;
private readonly GameGui gameGui;
private DalamudPluginInterface pluginInterface { get; init; }
private CommandManager commandManager { get; init; }
private Configuration configuration { get; init; }
private PluginUI PluginUi { get; init; }
private DrawHooks drawHooks;
private IpcManager ipcManager;
private readonly ObjectTable objectTable;
private readonly ApiController apiController;
private CharacterManager? characterManager;
private IpcManager? ipcManager;
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
Framework framework, ObjectTable objectTable, ClientState clientState, GameGui gameGui)
{
this.pluginInterface = pluginInterface;
this.commandManager = commandManager;
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);
Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
Configuration.Initialize(this.PluginInterface);
apiController = new ApiController(Configuration);
// 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)
{
HelpMessage = "pass 'scan' to initialize or rescan files into the database"
});
FileCacheContext db = new FileCacheContext();
db.Dispose();
new FileCacheContext().Dispose(); // make sure db is initialized I guess
clientState.Login += ClientState_Login;
clientState.Logout += ClientState_Logout;
@@ -84,160 +60,68 @@ namespace MareSynchronos
ClientState_Login(null, null!);
}
this.pluginInterface.UiBuilder.Draw += DrawUI;
this.pluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
this.PluginInterface.UiBuilder.Draw += DrawUI;
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
}
private void IpcManager_IpcManagerInitialized(object? sender, EventArgs e)
public string Name => "Mare Synchronos";
private CommandManager CommandManager { get; init; }
private Configuration Configuration { get; init; }
private DalamudPluginInterface PluginInterface { get; init; }
private PluginUI PluginUi { get; init; }
public void Dispose()
{
PluginLog.Debug("IPC Manager initialized event");
ipcManager.IpcManagerInitialized -= IpcManager_IpcManagerInitialized;
Task.Run(async () =>
{
while (clientState.LocalPlayer == null)
{
await Task.Delay(500);
this.PluginUi?.Dispose();
this.CommandManager.RemoveHandler(commandName);
clientState.Login -= ClientState_Login;
clientState.Logout -= ClientState_Logout;
ipcManager?.Dispose();
characterManager?.Dispose();
}
drawHooks.StartHooks();
ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString());
private void ClientState_Login(object? sender, EventArgs e)
{
PluginLog.Debug("Client login");
ipcManager = new IpcManager(PluginInterface);
ipcManager.IpcManagerInitialized += IpcManager_IpcManagerInitialized;
ipcManager.Initialize();
CommandManager.AddHandler(commandName, new CommandInfo(OnCommand)
{
HelpMessage = "pass 'scan' to initialize or rescan files into the database"
});
}
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(() =>
{
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");
// 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()
{
this.PluginUi?.Dispose();
this.commandManager.RemoveHandler(commandName);
drawHooks.PlayerLoadEvent -= DrawHooks_PlayerLoadEvent;
clientState.Login -= ClientState_Login;
clientState.Logout -= ClientState_Logout;
characterManager?.Dispose();
ipcManager?.Dispose();
drawHooks?.Dispose();
ipcManager = null!;
CommandManager.RemoveHandler(commandName);
}
private void OnCommand(string command, string args)
{
if (args == "print")
{
var resources = drawHooks.PrintRequestedResources();
}
if (args == "printjson")
{
var cache = drawHooks.BuildCharacterCache();
cache.SetGlamourerData(ipcManager.GlamourerGetCharacterCustomization()!);
cache.JobId = clientState.LocalPlayer!.ClassJob.Id;
Task.Run(async () =>
{
while (!cache.IsReady)
{
await Task.Delay(50);
}
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
cache.CacheHash = Crypto.GetHash(json);
json = JsonConvert.SerializeObject(cache, Formatting.Indented);
PluginLog.Debug(json);
});
}
if (args == "createtestmod")
{
Task.Run(() =>
{
var playerName = clientState.LocalPlayer!.Name.ToString();
var modName = $"Mare Synchronos Test Mod {playerName}";
var modDirectory = ipcManager.PenumbraModDirectory()!;
string modDirectoryPath = Path.Combine(modDirectory, modName);
if (Directory.Exists(modDirectoryPath))
{
Directory.Delete(modDirectoryPath, true);
}
Directory.CreateDirectory(modDirectoryPath);
Directory.CreateDirectory(Path.Combine(modDirectoryPath, "files"));
Meta meta = new Meta()
{
Name = modName,
Author = playerName,
Description = "Mare Synchronous Test Mod Export",
};
var resources = drawHooks.PrintRequestedResources();
var metaJson = JsonConvert.SerializeObject(meta);
File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson);
DefaultMod defaultMod = new DefaultMod();
using var db = new FileCacheContext();
foreach (var resource in resources)
{
CopyRecursive(resource, modDirectoryPath, db, defaultMod.Files);
}
var defaultModJson = JsonConvert.SerializeObject(defaultMod);
File.WriteAllText(Path.Combine(modDirectoryPath, "default_mod.json"), defaultModJson);
PluginLog.Debug("Mod created to " + modDirectoryPath);
});
}
}
private void CopyRecursive(FileReplacement replacement, string targetDirectory, FileCacheContext db, Dictionary<string, string>? resourceDict = null)
private void CopyFile(FileReplacement replacement, string targetDirectory, Dictionary<string, string>? resourceDict = null)
{
if (replacement.HasFileReplacement)
{
PluginLog.Debug("Copying file \"" + replacement.ResolvedPath + "\"");
var fileCache = db.FileCaches.Single(f => f.Filepath.Contains(replacement.ResolvedPath.Replace('/', '\\')));
var db1 = new FileCacheContext();
var fileCache = db1.FileCaches.Single(f => f.Filepath.Contains(replacement.ResolvedPath.Replace('/', '\\')));
db1.Dispose();
try
{
var ext = new FileInfo(fileCache.Filepath).Extension;
File.Copy(fileCache.Filepath, Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext));
var newFilePath = Path.Combine(targetDirectory, "files", fileCache.Hash.ToLower() + ext);
string lc4hcPath = Path.Combine(targetDirectory, "files", "lz4hc." + fileCache.Hash.ToLower() + ext);
if (!File.Exists(lc4hcPath))
{
Stopwatch st = Stopwatch.StartNew();
File.WriteAllBytes(lc4hcPath, LZ4Codec.Encode(File.ReadAllBytes(fileCache.Filepath), 0, (int)new FileInfo(fileCache.Filepath).Length));
st.Stop();
PluginLog.Debug("Compressed " + new FileInfo(fileCache.Filepath).Length + " bytes to " + new FileInfo(lc4hcPath).Length + " bytes in " + st.Elapsed);
File.Copy(fileCache.Filepath, newFilePath);
if (resourceDict != null)
{
resourceDict[replacement.GamePath] = $"files\\{fileCache.Hash.ToLower() + ext}";
@@ -247,25 +131,17 @@ namespace MareSynchronos
File.AppendAllLines(Path.Combine(targetDirectory, "filelist.txt"), new[] { $"\"{replacement.GamePath}\": \"files\\\\{fileCache.Hash.ToLower() + ext}\"," });
}
}
catch { }
}
foreach (var associated in replacement.Associated)
catch (Exception ex)
{
CopyRecursive(associated, targetDirectory, db, resourceDict);
PluginLog.Error(ex, "error during copy");
}
}
}
private void PlayerWatch_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
private void DrawConfigUI()
{
//var equipment = playerWatch.UpdatePlayerWithoutEvent(actor);
//var customization = new CharacterCustomization(actor);
//DebugCustomization(customization);
//PluginLog.Debug(customization.Gender.ToString());
//if (equipment != null)
//{
// PluginLog.Debug(equipment.ToString());
//}
this.PluginUi.SettingsVisible = true;
}
private void DrawUI()
@@ -273,9 +149,78 @@ namespace MareSynchronos
this.PluginUi.Draw();
}
private void DrawConfigUI()
private void IpcManager_IpcManagerInitialized(object? sender, EventArgs e)
{
this.PluginUi.SettingsVisible = true;
PluginLog.Debug("IPC Manager initialized event");
ipcManager!.IpcManagerInitialized -= IpcManager_IpcManagerInitialized;
Task.Run(async () =>
{
while (clientState.LocalPlayer == null)
{
await Task.Delay(500);
}
characterManager = new CharacterManager(
new DrawHooks(PluginInterface, clientState, objectTable, new FileReplacementFactory(ipcManager, clientState), gameGui),
clientState, framework, apiController, objectTable, ipcManager);
ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString());
});
}
private void OnCommand(string command, string args)
{
if (args == "print")
{
characterManager?.PrintRequestedResources();
}
if (args == "printjson")
{
characterManager?.DebugJson();
}
if (args == "createtestmod")
{
Task.Run(() =>
{
var playerName = clientState.LocalPlayer!.Name.ToString();
var modName = $"Mare Synchronos Test Mod {playerName}";
var modDirectory = ipcManager!.PenumbraModDirectory()!;
string modDirectoryPath = Path.Combine(modDirectory, modName);
if (Directory.Exists(modDirectoryPath))
{
Directory.Delete(modDirectoryPath, true);
}
Directory.CreateDirectory(modDirectoryPath);
Directory.CreateDirectory(Path.Combine(modDirectoryPath, "files"));
Meta meta = new()
{
Name = modName,
Author = playerName,
Description = "Mare Synchronous Test Mod Export",
};
var resources = characterManager!.GetCharacterCache();
var metaJson = JsonConvert.SerializeObject(meta);
File.WriteAllText(Path.Combine(modDirectoryPath, "meta.json"), metaJson);
DefaultMod defaultMod = new();
//using var db = new FileCacheContext();
Stopwatch st = Stopwatch.StartNew();
Parallel.ForEach(resources.AllReplacements, resource =>
{
CopyFile(resource, modDirectoryPath, defaultMod.Files);
});
PluginLog.Debug("Compression took " + st.Elapsed);
var defaultModJson = JsonConvert.SerializeObject(defaultMod);
File.WriteAllText(Path.Combine(modDirectoryPath, "default_mod.json"), defaultModJson);
PluginLog.Debug("Mod created to " + modDirectoryPath);
});
}
}
}
}

View File

@@ -81,9 +81,9 @@ namespace MareSynchronos
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
{
// can't ref a property, so use a local copy
string penumbraFolder = configuration.PenumbraFolder;
string penumbraFolder = configuration.CacheFolder;
if(ImGui.InputText("Penumbra mod folder", ref penumbraFolder, 255)) {
this.configuration.PenumbraFolder = penumbraFolder;
this.configuration.CacheFolder = penumbraFolder;
this.configuration.Save();
}
}

View File

@@ -0,0 +1,77 @@
using Dalamud.Configuration;
using Dalamud.Logging;
using MareSynchronos.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MareSynchronos.WebAPI
{
public class ApiController
{
private readonly Configuration pluginConfiguration;
private string SecretKey => pluginConfiguration.ClientSecret;
private string CacheFolder => pluginConfiguration.CacheFolder;
private string ApiUri => pluginConfiguration.ApiUri;
public ApiController(Configuration pluginConfiguration)
{
this.pluginConfiguration = pluginConfiguration;
}
public async Task Heartbeat()
{
PluginLog.Debug("Sending heartbeat to " + ApiUri);
}
public async Task<(string, string)> Register()
{
PluginLog.Debug("Registering at service " + ApiUri);
return (string.Empty, string.Empty);
}
public async Task UploadFile(string filePath)
{
PluginLog.Debug("Uploading file " + filePath + " to " + ApiUri);
}
public async Task<byte[]> DownloadFile(string hash)
{
PluginLog.Debug("Downloading file from service " + ApiUri);
return Array.Empty<byte>();
}
public async Task<List<string>> SendCharacterData(CharacterCache character)
{
PluginLog.Debug("Sending Character data to service " + ApiUri);
List<string> list = new();
return list;
}
public async Task<CharacterCache> GetCharacterData(string uid)
{
PluginLog.Debug("Getting character data for " + uid + " from service " + ApiUri);
CharacterCache characterCache = new();
return characterCache;
}
public async Task SendWhitelist()
{
PluginLog.Debug("Sending whitelist to service " + ApiUri);
}
public async Task<List<string>> GetWhitelist()
{
PluginLog.Debug("Getting whitelist from service " + ApiUri);
List<string> whitelist = new();
return whitelist;
}
}
}