add api to mare, change all to file scoped namespace
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 9dc1e901aa...57a7ab8262
@@ -7,195 +7,194 @@ using System.Linq;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI;
|
||||
|
||||
namespace MareSynchronos
|
||||
namespace MareSynchronos;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static bool HasValidSetup(this Configuration configuration)
|
||||
{
|
||||
public static bool HasValidSetup(this Configuration configuration)
|
||||
{
|
||||
return configuration.AcceptedAgreement && configuration.InitialScanComplete
|
||||
&& !string.IsNullOrEmpty(configuration.CacheFolder)
|
||||
&& Directory.Exists(configuration.CacheFolder)
|
||||
&& configuration.ClientSecret.ContainsKey(configuration.ApiUri);
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> GetCurrentServerUidComments(this Configuration configuration)
|
||||
{
|
||||
return configuration.UidServerComments.ContainsKey(configuration.ApiUri)
|
||||
? configuration.UidServerComments[configuration.ApiUri]
|
||||
: new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment)
|
||||
{
|
||||
if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri))
|
||||
{
|
||||
configuration.UidServerComments[configuration.ApiUri] = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
configuration.UidServerComments[configuration.ApiUri][uid] = comment;
|
||||
}
|
||||
return configuration.AcceptedAgreement && configuration.InitialScanComplete
|
||||
&& !string.IsNullOrEmpty(configuration.CacheFolder)
|
||||
&& Directory.Exists(configuration.CacheFolder)
|
||||
&& configuration.ClientSecret.ContainsKey(configuration.ApiUri);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
public static Dictionary<string, string> GetCurrentServerUidComments(this Configuration configuration)
|
||||
{
|
||||
private string _apiUri = string.Empty;
|
||||
private int _maxParallelScan = 10;
|
||||
[NonSerialized]
|
||||
private DalamudPluginInterface? _pluginInterface;
|
||||
return configuration.UidServerComments.ContainsKey(configuration.ApiUri)
|
||||
? configuration.UidServerComments[configuration.ApiUri]
|
||||
: new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public bool DarkSoulsAgreement { get; set; } = false;
|
||||
public bool AcceptedAgreement { get; set; } = false;
|
||||
public string ApiUri
|
||||
public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment)
|
||||
{
|
||||
if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri))
|
||||
{
|
||||
get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri;
|
||||
set => _apiUri = value;
|
||||
configuration.UidServerComments[configuration.ApiUri] = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public string CacheFolder { get; set; } = string.Empty;
|
||||
public Dictionary<string, string> ClientSecret { get; set; } = new();
|
||||
public Dictionary<string, string> CustomServerList { get; set; } = new();
|
||||
public int MaxLocalCacheInGiB { get; set; } = 20;
|
||||
public bool ReverseUserSort { get; set; } = true;
|
||||
configuration.UidServerComments[configuration.ApiUri][uid] = comment;
|
||||
}
|
||||
}
|
||||
|
||||
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
|
||||
public bool FileScanPaused { get; set; } = false;
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private string _apiUri = string.Empty;
|
||||
private int _maxParallelScan = 10;
|
||||
[NonSerialized]
|
||||
private DalamudPluginInterface? _pluginInterface;
|
||||
|
||||
public bool InitialScanComplete { get; set; } = false;
|
||||
public bool DarkSoulsAgreement { get; set; } = false;
|
||||
public bool AcceptedAgreement { get; set; } = false;
|
||||
public string ApiUri
|
||||
{
|
||||
get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri;
|
||||
set => _apiUri = value;
|
||||
}
|
||||
|
||||
public bool FullPause { get; set; } = false;
|
||||
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new();
|
||||
public string CacheFolder { get; set; } = string.Empty;
|
||||
public Dictionary<string, string> ClientSecret { get; set; } = new();
|
||||
public Dictionary<string, string> CustomServerList { get; set; } = new();
|
||||
public int MaxLocalCacheInGiB { get; set; } = 20;
|
||||
public bool ReverseUserSort { get; set; } = true;
|
||||
|
||||
public Dictionary<string, string> UidComments { get; set; } = new();
|
||||
public int Version { get; set; } = 5;
|
||||
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
|
||||
public bool FileScanPaused { get; set; } = false;
|
||||
|
||||
public bool ShowTransferWindow { get; set; } = true;
|
||||
public bool InitialScanComplete { get; set; } = false;
|
||||
|
||||
// the below exist just to make saving less cumbersome
|
||||
public void Initialize(DalamudPluginInterface pluginInterface)
|
||||
public bool FullPause { get; set; } = false;
|
||||
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new();
|
||||
|
||||
public Dictionary<string, string> UidComments { get; set; } = new();
|
||||
public int Version { get; set; } = 5;
|
||||
|
||||
public bool ShowTransferWindow { get; set; } = true;
|
||||
|
||||
// the below exist just to make saving less cumbersome
|
||||
public void Initialize(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
|
||||
if (!Directory.Exists(CacheFolder))
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
InitialScanComplete = false;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(CacheFolder))
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_pluginInterface!.SavePluginConfig(this);
|
||||
}
|
||||
|
||||
public void Migrate()
|
||||
{
|
||||
if (Version == 0)
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V0 to V1");
|
||||
Version = 1;
|
||||
ApiUri = ApiUri.Replace("https", "wss");
|
||||
foreach (var kvp in ClientSecret.ToList())
|
||||
{
|
||||
InitialScanComplete = false;
|
||||
var newKey = kvp.Key.Replace("https", "wss");
|
||||
ClientSecret.Remove(kvp.Key);
|
||||
if (ClientSecret.ContainsKey(newKey))
|
||||
{
|
||||
ClientSecret[newKey] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSecret.Add(newKey, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value));
|
||||
UidComments.Clear();
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
if (Version == 1)
|
||||
{
|
||||
_pluginInterface!.SavePluginConfig(this);
|
||||
Logger.Debug("Migrating Configuration from V1 to V2");
|
||||
ApiUri = ApiUri.Replace("5001", "5000");
|
||||
foreach (var kvp in ClientSecret.ToList())
|
||||
{
|
||||
var newKey = kvp.Key.Replace("5001", "5000");
|
||||
ClientSecret.Remove(kvp.Key);
|
||||
if (ClientSecret.ContainsKey(newKey))
|
||||
{
|
||||
ClientSecret[newKey] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSecret.Add(newKey, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in UidServerComments.ToList())
|
||||
{
|
||||
var newKey = kvp.Key.Replace("5001", "5000");
|
||||
UidServerComments.Remove(kvp.Key);
|
||||
UidServerComments.Add(newKey, kvp.Value);
|
||||
}
|
||||
|
||||
Version = 2;
|
||||
Save();
|
||||
}
|
||||
|
||||
public void Migrate()
|
||||
if (Version == 2)
|
||||
{
|
||||
if (Version == 0)
|
||||
Logger.Debug("Migrating Configuration from V2 to V3");
|
||||
ApiUri = "wss://v2202207178628194299.powersrv.de:6871";
|
||||
ClientSecret.Clear();
|
||||
UidServerComments.Clear();
|
||||
|
||||
Version = 3;
|
||||
Save();
|
||||
}
|
||||
|
||||
if (Version == 3)
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V3 to V4");
|
||||
|
||||
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
|
||||
foreach (var kvp in ClientSecret.ToList())
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V0 to V1");
|
||||
Version = 1;
|
||||
ApiUri = ApiUri.Replace("https", "wss");
|
||||
foreach (var kvp in ClientSecret.ToList())
|
||||
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
|
||||
ClientSecret.Remove(kvp.Key);
|
||||
if (ClientSecret.ContainsKey(newKey))
|
||||
{
|
||||
var newKey = kvp.Key.Replace("https", "wss");
|
||||
ClientSecret.Remove(kvp.Key);
|
||||
if (ClientSecret.ContainsKey(newKey))
|
||||
{
|
||||
ClientSecret[newKey] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSecret.Add(newKey, kvp.Value);
|
||||
}
|
||||
ClientSecret[newKey] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSecret.Add(newKey, kvp.Value);
|
||||
}
|
||||
UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value));
|
||||
UidComments.Clear();
|
||||
Save();
|
||||
}
|
||||
|
||||
if (Version == 1)
|
||||
foreach (var kvp in UidServerComments.ToList())
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V1 to V2");
|
||||
ApiUri = ApiUri.Replace("5001", "5000");
|
||||
foreach (var kvp in ClientSecret.ToList())
|
||||
{
|
||||
var newKey = kvp.Key.Replace("5001", "5000");
|
||||
ClientSecret.Remove(kvp.Key);
|
||||
if (ClientSecret.ContainsKey(newKey))
|
||||
{
|
||||
ClientSecret[newKey] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSecret.Add(newKey, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in UidServerComments.ToList())
|
||||
{
|
||||
var newKey = kvp.Key.Replace("5001", "5000");
|
||||
UidServerComments.Remove(kvp.Key);
|
||||
UidServerComments.Add(newKey, kvp.Value);
|
||||
}
|
||||
|
||||
Version = 2;
|
||||
Save();
|
||||
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
|
||||
UidServerComments.Remove(kvp.Key);
|
||||
UidServerComments.Add(newKey, kvp.Value);
|
||||
}
|
||||
|
||||
if (Version == 2)
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V2 to V3");
|
||||
ApiUri = "wss://v2202207178628194299.powersrv.de:6871";
|
||||
ClientSecret.Clear();
|
||||
UidServerComments.Clear();
|
||||
Version = 4;
|
||||
Save();
|
||||
}
|
||||
|
||||
Version = 3;
|
||||
Save();
|
||||
}
|
||||
if (Version == 4)
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V4 to V5");
|
||||
|
||||
if (Version == 3)
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V3 to V4");
|
||||
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com");
|
||||
ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872");
|
||||
UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872");
|
||||
|
||||
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
|
||||
foreach (var kvp in ClientSecret.ToList())
|
||||
{
|
||||
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
|
||||
ClientSecret.Remove(kvp.Key);
|
||||
if (ClientSecret.ContainsKey(newKey))
|
||||
{
|
||||
ClientSecret[newKey] = kvp.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientSecret.Add(newKey, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in UidServerComments.ToList())
|
||||
{
|
||||
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
|
||||
UidServerComments.Remove(kvp.Key);
|
||||
UidServerComments.Add(newKey, kvp.Value);
|
||||
}
|
||||
|
||||
Version = 4;
|
||||
Save();
|
||||
}
|
||||
|
||||
if (Version == 4)
|
||||
{
|
||||
Logger.Debug("Migrating Configuration from V4 to V5");
|
||||
|
||||
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com");
|
||||
ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872");
|
||||
UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872");
|
||||
|
||||
Version = 5;
|
||||
Save();
|
||||
}
|
||||
Version = 5;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,35 +4,34 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace MareSynchronos.Interop
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct Weapon
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct Weapon
|
||||
{
|
||||
[FieldOffset(0x18)] public IntPtr Parent;
|
||||
[FieldOffset(0x20)] public IntPtr NextSibling;
|
||||
[FieldOffset(0x28)] public IntPtr PreviousSibling;
|
||||
[FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct WeaponDrawObject
|
||||
{
|
||||
[FieldOffset(0x00)] public RenderModel* RenderModel;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct HumanExt
|
||||
{
|
||||
[FieldOffset(0x0)] public Human Human;
|
||||
[FieldOffset(0x9E8)] public ResourceHandle* Decal;
|
||||
[FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct CharaExt
|
||||
{
|
||||
[FieldOffset(0x0)] public Character Character;
|
||||
[FieldOffset(0x650)] public Character* Mount;
|
||||
}
|
||||
[FieldOffset(0x18)] public IntPtr Parent;
|
||||
[FieldOffset(0x20)] public IntPtr NextSibling;
|
||||
[FieldOffset(0x28)] public IntPtr PreviousSibling;
|
||||
[FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct WeaponDrawObject
|
||||
{
|
||||
[FieldOffset(0x00)] public RenderModel* RenderModel;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct HumanExt
|
||||
{
|
||||
[FieldOffset(0x0)] public Human Human;
|
||||
[FieldOffset(0x9E8)] public ResourceHandle* Decal;
|
||||
[FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct CharaExt
|
||||
{
|
||||
[FieldOffset(0x0)] public Character Character;
|
||||
[FieldOffset(0x650)] public Character* Mount;
|
||||
}
|
||||
|
||||
@@ -1,68 +1,67 @@
|
||||
using CheapLoc;
|
||||
|
||||
namespace MareSynchronos.Localization
|
||||
namespace MareSynchronos.Localization;
|
||||
|
||||
public static class Strings
|
||||
{
|
||||
public static class Strings
|
||||
public class ToSStrings
|
||||
{
|
||||
public class ToSStrings
|
||||
{
|
||||
public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language");
|
||||
public readonly string AgreementLabel = Loc.Localize("AgreementLabel", "Agreement of Usage of Service");
|
||||
public readonly string ReadLabel = Loc.Localize("ReadLabel", "READ THIS CAREFULLY");
|
||||
public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language");
|
||||
public readonly string AgreementLabel = Loc.Localize("AgreementLabel", "Agreement of Usage of Service");
|
||||
public readonly string ReadLabel = Loc.Localize("ReadLabel", "READ THIS CAREFULLY");
|
||||
|
||||
public readonly string Paragraph1 = Loc.Localize("Paragraph1",
|
||||
"All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. " +
|
||||
"The plugin will exclusively upload the necessary mod files and not the whole mod.");
|
||||
public readonly string Paragraph1 = Loc.Localize("Paragraph1",
|
||||
"All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. " +
|
||||
"The plugin will exclusively upload the necessary mod files and not the whole mod.");
|
||||
|
||||
public readonly string Paragraph2 = Loc.Localize("Paragraph2",
|
||||
"If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. " +
|
||||
"Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. " +
|
||||
"Files present on the service that already represent your active mod files will not be uploaded again.");
|
||||
public readonly string Paragraph2 = Loc.Localize("Paragraph2",
|
||||
"If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. " +
|
||||
"Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. " +
|
||||
"Files present on the service that already represent your active mod files will not be uploaded again.");
|
||||
|
||||
public readonly string Paragraph3 = Loc.Localize("Paragraph3",
|
||||
"The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. " +
|
||||
"Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. " +
|
||||
"Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod.");
|
||||
public readonly string Paragraph3 = Loc.Localize("Paragraph3",
|
||||
"The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. " +
|
||||
"Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. " +
|
||||
"Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod.");
|
||||
|
||||
public readonly string Paragraph4 = Loc.Localize("Paragraph4",
|
||||
"The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone.");
|
||||
public readonly string Paragraph4 = Loc.Localize("Paragraph4",
|
||||
"The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone.");
|
||||
|
||||
public readonly string Paragraph5 = Loc.Localize("Paragraph5",
|
||||
"Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. " +
|
||||
"After a period of not being used, the mod files will be automatically deleted. " +
|
||||
"You will also be able to wipe all the files you have personally uploaded on request. " +
|
||||
"The service holds no information about which mod files belong to which mod.");
|
||||
public readonly string Paragraph5 = Loc.Localize("Paragraph5",
|
||||
"Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. " +
|
||||
"After a period of not being used, the mod files will be automatically deleted. " +
|
||||
"You will also be able to wipe all the files you have personally uploaded on request. " +
|
||||
"The service holds no information about which mod files belong to which mod.");
|
||||
|
||||
public readonly string Paragraph6 = Loc.Localize("Paragraph6",
|
||||
"This service is provided as-is. In case of abuse, contact darkarchon#4313 on Discord or join the Mare Synchronos Discord. " +
|
||||
"To accept those conditions hold CTRL while clicking 'I agree'");
|
||||
public readonly string Paragraph6 = Loc.Localize("Paragraph6",
|
||||
"This service is provided as-is. In case of abuse, contact darkarchon#4313 on Discord or join the Mare Synchronos Discord. " +
|
||||
"To accept those conditions hold CTRL while clicking 'I agree'");
|
||||
|
||||
public readonly string AgreeLabel = Loc.Localize("AgreeLabel", "I agree");
|
||||
public readonly string AgreeLabel = Loc.Localize("AgreeLabel", "I agree");
|
||||
|
||||
public readonly string RemainingLabel = Loc.Localize("RemainingLabel", "remaining");
|
||||
public readonly string RemainingLabel = Loc.Localize("RemainingLabel", "remaining");
|
||||
|
||||
public readonly string FailedLabel = Loc.Localize("FailedLabel",
|
||||
"Congratulations. You have failed to read the agreements.");
|
||||
public readonly string FailedLabel = Loc.Localize("FailedLabel",
|
||||
"Congratulations. You have failed to read the agreements.");
|
||||
|
||||
public readonly string TimeoutLabel = Loc.Localize("TimeoutLabel",
|
||||
"I'm going to give you 1 minute to read the agreements carefully again. If you fail once more you will have to solve an annoying puzzle.");
|
||||
public readonly string TimeoutLabel = Loc.Localize("TimeoutLabel",
|
||||
"I'm going to give you 1 minute to read the agreements carefully again. If you fail once more you will have to solve an annoying puzzle.");
|
||||
|
||||
public readonly string FailedAgainLabel = Loc.Localize("FailedAgainLabel",
|
||||
"Congratulations. You have failed to read the agreements. Again.");
|
||||
public readonly string FailedAgainLabel = Loc.Localize("FailedAgainLabel",
|
||||
"Congratulations. You have failed to read the agreements. Again.");
|
||||
|
||||
public readonly string PuzzleLabel = Loc.Localize("PuzzleLabel",
|
||||
"I did warn you. Here's your annoying puzzle:");
|
||||
public readonly string PuzzleLabel = Loc.Localize("PuzzleLabel",
|
||||
"I did warn you. Here's your annoying puzzle:");
|
||||
|
||||
public readonly string PuzzleDescLabel = Loc.Localize("PuzzleDescLabel",
|
||||
"Enter the following 3 words from the agreement exactly as described without punctuation to make the \"I agree\" button visible again.");
|
||||
public readonly string PuzzleDescLabel = Loc.Localize("PuzzleDescLabel",
|
||||
"Enter the following 3 words from the agreement exactly as described without punctuation to make the \"I agree\" button visible again.");
|
||||
|
||||
public readonly string ParagraphLabel = Loc.Localize("ParagraphLabel", "Paragraph");
|
||||
public readonly string ParagraphLabel = Loc.Localize("ParagraphLabel", "Paragraph");
|
||||
|
||||
public readonly string SentenceLabel = Loc.Localize("SentenceLabel", "Sentence");
|
||||
public readonly string SentenceLabel = Loc.Localize("SentenceLabel", "Sentence");
|
||||
|
||||
public readonly string WordLabel = Loc.Localize("WordLabel", "Word");
|
||||
}
|
||||
|
||||
public static ToSStrings ToS { get; set; } = new();
|
||||
public readonly string WordLabel = Loc.Localize("WordLabel", "Word");
|
||||
}
|
||||
|
||||
public static ToSStrings ToS { get; set; } = new();
|
||||
}
|
||||
@@ -8,394 +8,393 @@ using MareSynchronos.WebAPI;
|
||||
using Action = System.Action;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
|
||||
public delegate void HeelsOffsetChange(float change);
|
||||
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
|
||||
public class IpcManager : IDisposable
|
||||
{
|
||||
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
|
||||
public delegate void HeelsOffsetChange(float change);
|
||||
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
|
||||
public class IpcManager : IDisposable
|
||||
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll;
|
||||
private readonly ICallGateSubscriber<GameObject?, string>? _glamourerGetAllCustomization;
|
||||
private readonly ICallGateSubscriber<GameObject?, object> _glamourerRevertCustomization;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyOnlyEquipment;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyOnlyCustomization;
|
||||
private readonly ICallGateSubscriber<(int, int)> _penumbraApiVersion;
|
||||
private readonly ICallGateSubscriber<string, string, bool, (int, string)> _penumbraCreateTemporaryCollection;
|
||||
private readonly ICallGateSubscriber<string> _penumbraGetMetaManipulations;
|
||||
private readonly ICallGateSubscriber<object> _penumbraInit;
|
||||
private readonly ICallGateSubscriber<object> _penumbraDispose;
|
||||
private readonly ICallGateSubscriber<IntPtr, int, object?> _penumbraObjectIsRedrawn;
|
||||
private readonly ICallGateSubscriber<string, int, object>? _penumbraRedraw;
|
||||
private readonly ICallGateSubscriber<GameObject, int, object>? _penumbraRedrawObject;
|
||||
private readonly ICallGateSubscriber<string, int> _penumbraRemoveTemporaryCollection;
|
||||
private readonly ICallGateSubscriber<string>? _penumbraResolveModDir;
|
||||
private readonly ICallGateSubscriber<string, string>? _penumbraResolvePlayer;
|
||||
private readonly ICallGateSubscriber<string, string[]>? _reverseResolvePlayer;
|
||||
private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int>
|
||||
_penumbraSetTemporaryMod;
|
||||
private readonly ICallGateSubscriber<IntPtr, string, string, object?> _penumbraGameObjectResourcePathResolved;
|
||||
|
||||
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
|
||||
private readonly ICallGateSubscriber<float> _heelsGetOffset;
|
||||
private readonly ICallGateSubscriber<float, object?> _heelsOffsetUpdate;
|
||||
private readonly ICallGateSubscriber<GameObject, float, object?> _heelsRegisterPlayer;
|
||||
private readonly ICallGateSubscriber<GameObject, object?> _heelsUnregisterPlayer;
|
||||
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly ConcurrentQueue<Action> actionQueue = new();
|
||||
|
||||
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
|
||||
{
|
||||
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll;
|
||||
private readonly ICallGateSubscriber<GameObject?, string>? _glamourerGetAllCustomization;
|
||||
private readonly ICallGateSubscriber<GameObject?, object> _glamourerRevertCustomization;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyOnlyEquipment;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyOnlyCustomization;
|
||||
private readonly ICallGateSubscriber<(int, int)> _penumbraApiVersion;
|
||||
private readonly ICallGateSubscriber<string, string, bool, (int, string)> _penumbraCreateTemporaryCollection;
|
||||
private readonly ICallGateSubscriber<string> _penumbraGetMetaManipulations;
|
||||
private readonly ICallGateSubscriber<object> _penumbraInit;
|
||||
private readonly ICallGateSubscriber<object> _penumbraDispose;
|
||||
private readonly ICallGateSubscriber<IntPtr, int, object?> _penumbraObjectIsRedrawn;
|
||||
private readonly ICallGateSubscriber<string, int, object>? _penumbraRedraw;
|
||||
private readonly ICallGateSubscriber<GameObject, int, object>? _penumbraRedrawObject;
|
||||
private readonly ICallGateSubscriber<string, int> _penumbraRemoveTemporaryCollection;
|
||||
private readonly ICallGateSubscriber<string>? _penumbraResolveModDir;
|
||||
private readonly ICallGateSubscriber<string, string>? _penumbraResolvePlayer;
|
||||
private readonly ICallGateSubscriber<string, string[]>? _reverseResolvePlayer;
|
||||
private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int>
|
||||
_penumbraSetTemporaryMod;
|
||||
private readonly ICallGateSubscriber<IntPtr, string, string, object?> _penumbraGameObjectResourcePathResolved;
|
||||
Logger.Verbose("Creating " + nameof(IpcManager));
|
||||
|
||||
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
|
||||
private readonly ICallGateSubscriber<float> _heelsGetOffset;
|
||||
private readonly ICallGateSubscriber<float, object?> _heelsOffsetUpdate;
|
||||
private readonly ICallGateSubscriber<GameObject, float, object?> _heelsRegisterPlayer;
|
||||
private readonly ICallGateSubscriber<GameObject, object?> _heelsUnregisterPlayer;
|
||||
_penumbraInit = pi.GetIpcSubscriber<object>("Penumbra.Initialized");
|
||||
_penumbraDispose = pi.GetIpcSubscriber<object>("Penumbra.Disposed");
|
||||
_penumbraResolvePlayer = pi.GetIpcSubscriber<string, string>("Penumbra.ResolvePlayerPath");
|
||||
_penumbraResolveModDir = pi.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
|
||||
_penumbraRedraw = pi.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||
_penumbraRedrawObject = pi.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
||||
_reverseResolvePlayer = pi.GetIpcSubscriber<string, string[]>("Penumbra.ReverseResolvePlayerPath");
|
||||
_penumbraApiVersion = pi.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions");
|
||||
_penumbraObjectIsRedrawn = pi.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn");
|
||||
_penumbraGetMetaManipulations =
|
||||
pi.GetIpcSubscriber<string>("Penumbra.GetPlayerMetaManipulations");
|
||||
_penumbraSetTemporaryMod = pi.GetIpcSubscriber<string, string, Dictionary<string, string>, string, int,
|
||||
int>("Penumbra.AddTemporaryMod");
|
||||
_penumbraCreateTemporaryCollection =
|
||||
pi.GetIpcSubscriber<string, string, bool, (int, string)>("Penumbra.CreateTemporaryCollection");
|
||||
_penumbraRemoveTemporaryCollection =
|
||||
pi.GetIpcSubscriber<string, int>("Penumbra.RemoveTemporaryCollection");
|
||||
_penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber<IntPtr, string, string, object?>("Penumbra.GameObjectResourcePathResolved");
|
||||
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly ConcurrentQueue<Action> actionQueue = new();
|
||||
_penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded);
|
||||
_penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
|
||||
_penumbraInit.Subscribe(PenumbraInit);
|
||||
_penumbraDispose.Subscribe(PenumbraDispose);
|
||||
|
||||
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(IpcManager));
|
||||
_glamourerApiVersion = pi.GetIpcSubscriber<int>("Glamourer.ApiVersion");
|
||||
_glamourerGetAllCustomization = pi.GetIpcSubscriber<GameObject?, string>("Glamourer.GetAllCustomizationFromCharacter");
|
||||
_glamourerApplyAll = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyAllToCharacter");
|
||||
_glamourerApplyOnlyCustomization = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyCustomizationToCharacter");
|
||||
_glamourerApplyOnlyEquipment = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyEquipmentToCharacter");
|
||||
_glamourerRevertCustomization = pi.GetIpcSubscriber<GameObject?, object>("Glamourer.RevertCharacter");
|
||||
|
||||
_penumbraInit = pi.GetIpcSubscriber<object>("Penumbra.Initialized");
|
||||
_penumbraDispose = pi.GetIpcSubscriber<object>("Penumbra.Disposed");
|
||||
_penumbraResolvePlayer = pi.GetIpcSubscriber<string, string>("Penumbra.ResolvePlayerPath");
|
||||
_penumbraResolveModDir = pi.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
|
||||
_penumbraRedraw = pi.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||
_penumbraRedrawObject = pi.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
||||
_reverseResolvePlayer = pi.GetIpcSubscriber<string, string[]>("Penumbra.ReverseResolvePlayerPath");
|
||||
_penumbraApiVersion = pi.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions");
|
||||
_penumbraObjectIsRedrawn = pi.GetIpcSubscriber<IntPtr, int, object?>("Penumbra.GameObjectRedrawn");
|
||||
_penumbraGetMetaManipulations =
|
||||
pi.GetIpcSubscriber<string>("Penumbra.GetPlayerMetaManipulations");
|
||||
_penumbraSetTemporaryMod = pi.GetIpcSubscriber<string, string, Dictionary<string, string>, string, int,
|
||||
int>("Penumbra.AddTemporaryMod");
|
||||
_penumbraCreateTemporaryCollection =
|
||||
pi.GetIpcSubscriber<string, string, bool, (int, string)>("Penumbra.CreateTemporaryCollection");
|
||||
_penumbraRemoveTemporaryCollection =
|
||||
pi.GetIpcSubscriber<string, int>("Penumbra.RemoveTemporaryCollection");
|
||||
_penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber<IntPtr, string, string, object?>("Penumbra.GameObjectResourcePathResolved");
|
||||
_heelsGetApiVersion = pi.GetIpcSubscriber<string>("HeelsPlugin.ApiVersion");
|
||||
_heelsGetOffset = pi.GetIpcSubscriber<float>("HeelsPlugin.GetOffset");
|
||||
_heelsRegisterPlayer = pi.GetIpcSubscriber<GameObject, float, object?>("HeelsPlugin.RegisterPlayer");
|
||||
_heelsUnregisterPlayer = pi.GetIpcSubscriber<GameObject, object?>("HeelsPlugin.UnregisterPlayer");
|
||||
_heelsOffsetUpdate = pi.GetIpcSubscriber<float, object?>("HeelsPlugin.OffsetChanged");
|
||||
|
||||
_penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded);
|
||||
_penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
|
||||
_penumbraInit.Subscribe(PenumbraInit);
|
||||
_penumbraDispose.Subscribe(PenumbraDispose);
|
||||
_heelsOffsetUpdate.Subscribe(HeelsOffsetChange);
|
||||
|
||||
_glamourerApiVersion = pi.GetIpcSubscriber<int>("Glamourer.ApiVersion");
|
||||
_glamourerGetAllCustomization = pi.GetIpcSubscriber<GameObject?, string>("Glamourer.GetAllCustomizationFromCharacter");
|
||||
_glamourerApplyAll = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyAllToCharacter");
|
||||
_glamourerApplyOnlyCustomization = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyCustomizationToCharacter");
|
||||
_glamourerApplyOnlyEquipment = pi.GetIpcSubscriber<string, GameObject?, object>("Glamourer.ApplyOnlyEquipmentToCharacter");
|
||||
_glamourerRevertCustomization = pi.GetIpcSubscriber<GameObject?, object>("Glamourer.RevertCharacter");
|
||||
|
||||
_heelsGetApiVersion = pi.GetIpcSubscriber<string>("HeelsPlugin.ApiVersion");
|
||||
_heelsGetOffset = pi.GetIpcSubscriber<float>("HeelsPlugin.GetOffset");
|
||||
_heelsRegisterPlayer = pi.GetIpcSubscriber<GameObject, float, object?>("HeelsPlugin.RegisterPlayer");
|
||||
_heelsUnregisterPlayer = pi.GetIpcSubscriber<GameObject, object?>("HeelsPlugin.UnregisterPlayer");
|
||||
_heelsOffsetUpdate = pi.GetIpcSubscriber<float, object?>("HeelsPlugin.OffsetChanged");
|
||||
|
||||
_heelsOffsetUpdate.Subscribe(HeelsOffsetChange);
|
||||
|
||||
if (Initialized)
|
||||
{
|
||||
PenumbraInitialized?.Invoke();
|
||||
}
|
||||
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_dalamudUtil.FrameworkUpdate += HandleActionQueue;
|
||||
_dalamudUtil.ZoneSwitchEnd += ClearActionQueue;
|
||||
}
|
||||
|
||||
private void ClearActionQueue()
|
||||
{
|
||||
actionQueue.Clear();
|
||||
}
|
||||
|
||||
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
|
||||
{
|
||||
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0)
|
||||
{
|
||||
PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2);
|
||||
//Logger.Debug($"Resolved {ptr:X}: {arg1} => {arg2}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleActionQueue()
|
||||
{
|
||||
if (actionQueue.TryDequeue(out var action))
|
||||
{
|
||||
if (action == null) return;
|
||||
Logger.Debug("Execution action in queue: " + action.Method);
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
public event VoidDelegate? PenumbraInitialized;
|
||||
public event VoidDelegate? PenumbraDisposed;
|
||||
public event PenumbraRedrawEvent? PenumbraRedrawEvent;
|
||||
public event HeelsOffsetChange? HeelsOffsetChangeEvent;
|
||||
public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
|
||||
|
||||
public bool Initialized => CheckPenumbraApi();
|
||||
public bool CheckGlamourerApi()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _glamourerApiVersion.InvokeFunc() >= 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckPenumbraApi()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 };
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckHeelsApi()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _heelsGetApiVersion.InvokeFunc() == "1.0.1";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(IpcManager));
|
||||
|
||||
int totalSleepTime = 0;
|
||||
while (actionQueue.Count > 0 && totalSleepTime < 2000)
|
||||
{
|
||||
Logger.Verbose("Waiting for actionqueue to clear...");
|
||||
HandleActionQueue();
|
||||
System.Threading.Thread.Sleep(16);
|
||||
totalSleepTime += 16;
|
||||
}
|
||||
|
||||
if (totalSleepTime >= 2000)
|
||||
{
|
||||
Logger.Verbose("Action queue clear or not, disposing");
|
||||
}
|
||||
|
||||
_dalamudUtil.FrameworkUpdate -= HandleActionQueue;
|
||||
_dalamudUtil.ZoneSwitchEnd -= ClearActionQueue;
|
||||
actionQueue.Clear();
|
||||
|
||||
_penumbraDispose.Unsubscribe(PenumbraDispose);
|
||||
_penumbraInit.Unsubscribe(PenumbraInit);
|
||||
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
|
||||
_penumbraGameObjectResourcePathResolved.Unsubscribe(ResourceLoaded);
|
||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||
}
|
||||
|
||||
public float GetHeelsOffset()
|
||||
{
|
||||
if (!CheckHeelsApi()) return 0.0f;
|
||||
return _heelsGetOffset.InvokeFunc();
|
||||
}
|
||||
|
||||
public void HeelsSetOffsetForPlayer(float offset, IntPtr character)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Applying Heels data to " + character.ToString("X"));
|
||||
_heelsRegisterPlayer.InvokeAction(gameObj, offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void HeelsRestoreOffsetForPlayer(IntPtr character)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Restoring Heels data to " + character.ToString("X"));
|
||||
_heelsUnregisterPlayer.InvokeAction(gameObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyAll(string? customization, IntPtr obj)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer applying for " + c.Address.ToString("X"));
|
||||
_glamourerApplyAll!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyOnlyEquipment(string customization, IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer apply only equipment to " + c.Address.ToString("X"));
|
||||
_glamourerApplyOnlyEquipment!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyOnlyCustomization(string customization, IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X"));
|
||||
_glamourerApplyOnlyCustomization!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string GlamourerGetCharacterCustomization(IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return string.Empty;
|
||||
try
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c);
|
||||
byte[] bytes = Convert.FromBase64String(glamourerString);
|
||||
// ignore transparency
|
||||
bytes[88] = 128;
|
||||
bytes[89] = 63;
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void GlamourerRevertCharacterCustomization(GameObject character)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return;
|
||||
actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
|
||||
}
|
||||
|
||||
public string PenumbraGetMetaManipulations()
|
||||
{
|
||||
if (!CheckPenumbraApi()) return string.Empty;
|
||||
return _penumbraGetMetaManipulations.InvokeFunc();
|
||||
}
|
||||
|
||||
public string? PenumbraModDirectory()
|
||||
{
|
||||
if (!CheckPenumbraApi()) return null;
|
||||
return _penumbraResolveModDir!.InvokeFunc().ToLowerInvariant();
|
||||
}
|
||||
|
||||
public void PenumbraRedraw(IntPtr obj)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Redrawing " + gameObj);
|
||||
_penumbraRedrawObject!.InvokeAction(gameObj, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void PenumbraRedraw(string actorName)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
actionQueue.Enqueue(() => _penumbraRedraw!.InvokeAction(actorName, 0));
|
||||
}
|
||||
|
||||
public void PenumbraRemoveTemporaryCollection(string characterName)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
Logger.Verbose("Removing temp collection for " + characterName);
|
||||
_penumbraRemoveTemporaryCollection.InvokeFunc(characterName);
|
||||
});
|
||||
}
|
||||
|
||||
public string PenumbraResolvePath(string path)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return path;
|
||||
var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path);
|
||||
return resolvedPath ?? path;
|
||||
}
|
||||
|
||||
public string[] PenumbraReverseResolvePlayer(string path)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return new[] { path };
|
||||
var resolvedPaths = _reverseResolvePlayer!.InvokeFunc(path);
|
||||
if (resolvedPaths.Length == 0)
|
||||
{
|
||||
resolvedPaths = new[] { path };
|
||||
}
|
||||
return resolvedPaths;
|
||||
}
|
||||
|
||||
public void PenumbraSetTemporaryMods(string characterName, Dictionary<string, string> modPaths, string manipulationData)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true);
|
||||
Logger.Verbose("Assigning temp mods for " + ret.Item2);
|
||||
foreach (var mod in modPaths)
|
||||
{
|
||||
Logger.Verbose(mod.Key + " => " + mod.Value);
|
||||
}
|
||||
_penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", ret.Item2, modPaths, manipulationData, 0);
|
||||
});
|
||||
}
|
||||
|
||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||
{
|
||||
PenumbraRedrawEvent?.Invoke(objectAddress, objectTableIndex);
|
||||
}
|
||||
|
||||
private void PenumbraInit()
|
||||
if (Initialized)
|
||||
{
|
||||
PenumbraInitialized?.Invoke();
|
||||
_penumbraRedraw!.InvokeAction("self", 0);
|
||||
}
|
||||
|
||||
private void HeelsOffsetChange(float offset)
|
||||
{
|
||||
HeelsOffsetChangeEvent?.Invoke(offset);
|
||||
}
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_dalamudUtil.FrameworkUpdate += HandleActionQueue;
|
||||
_dalamudUtil.ZoneSwitchEnd += ClearActionQueue;
|
||||
}
|
||||
|
||||
private void PenumbraDispose()
|
||||
private void ClearActionQueue()
|
||||
{
|
||||
actionQueue.Clear();
|
||||
}
|
||||
|
||||
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
|
||||
{
|
||||
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0)
|
||||
{
|
||||
PenumbraDisposed?.Invoke();
|
||||
actionQueue.Clear();
|
||||
PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2);
|
||||
//Logger.Debug($"Resolved {ptr:X}: {arg1} => {arg2}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleActionQueue()
|
||||
{
|
||||
if (actionQueue.TryDequeue(out var action))
|
||||
{
|
||||
if (action == null) return;
|
||||
Logger.Debug("Execution action in queue: " + action.Method);
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
public event VoidDelegate? PenumbraInitialized;
|
||||
public event VoidDelegate? PenumbraDisposed;
|
||||
public event PenumbraRedrawEvent? PenumbraRedrawEvent;
|
||||
public event HeelsOffsetChange? HeelsOffsetChangeEvent;
|
||||
public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
|
||||
|
||||
public bool Initialized => CheckPenumbraApi();
|
||||
public bool CheckGlamourerApi()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _glamourerApiVersion.InvokeFunc() >= 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckPenumbraApi()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 };
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckHeelsApi()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _heelsGetApiVersion.InvokeFunc() == "1.0.1";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(IpcManager));
|
||||
|
||||
int totalSleepTime = 0;
|
||||
while (actionQueue.Count > 0 && totalSleepTime < 2000)
|
||||
{
|
||||
Logger.Verbose("Waiting for actionqueue to clear...");
|
||||
HandleActionQueue();
|
||||
System.Threading.Thread.Sleep(16);
|
||||
totalSleepTime += 16;
|
||||
}
|
||||
|
||||
if (totalSleepTime >= 2000)
|
||||
{
|
||||
Logger.Verbose("Action queue clear or not, disposing");
|
||||
}
|
||||
|
||||
_dalamudUtil.FrameworkUpdate -= HandleActionQueue;
|
||||
_dalamudUtil.ZoneSwitchEnd -= ClearActionQueue;
|
||||
actionQueue.Clear();
|
||||
|
||||
_penumbraDispose.Unsubscribe(PenumbraDispose);
|
||||
_penumbraInit.Unsubscribe(PenumbraInit);
|
||||
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
|
||||
_penumbraGameObjectResourcePathResolved.Unsubscribe(ResourceLoaded);
|
||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||
}
|
||||
|
||||
public float GetHeelsOffset()
|
||||
{
|
||||
if (!CheckHeelsApi()) return 0.0f;
|
||||
return _heelsGetOffset.InvokeFunc();
|
||||
}
|
||||
|
||||
public void HeelsSetOffsetForPlayer(float offset, IntPtr character)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Applying Heels data to " + character.ToString("X"));
|
||||
_heelsRegisterPlayer.InvokeAction(gameObj, offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void HeelsRestoreOffsetForPlayer(IntPtr character)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Restoring Heels data to " + character.ToString("X"));
|
||||
_heelsUnregisterPlayer.InvokeAction(gameObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyAll(string? customization, IntPtr obj)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer applying for " + c.Address.ToString("X"));
|
||||
_glamourerApplyAll!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyOnlyEquipment(string customization, IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer apply only equipment to " + c.Address.ToString("X"));
|
||||
_glamourerApplyOnlyEquipment!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyOnlyCustomization(string customization, IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X"));
|
||||
_glamourerApplyOnlyCustomization!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string GlamourerGetCharacterCustomization(IntPtr character)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return string.Empty;
|
||||
try
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c);
|
||||
byte[] bytes = Convert.FromBase64String(glamourerString);
|
||||
// ignore transparency
|
||||
bytes[88] = 128;
|
||||
bytes[89] = 63;
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void GlamourerRevertCharacterCustomization(GameObject character)
|
||||
{
|
||||
if (!CheckGlamourerApi()) return;
|
||||
actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
|
||||
}
|
||||
|
||||
public string PenumbraGetMetaManipulations()
|
||||
{
|
||||
if (!CheckPenumbraApi()) return string.Empty;
|
||||
return _penumbraGetMetaManipulations.InvokeFunc();
|
||||
}
|
||||
|
||||
public string? PenumbraModDirectory()
|
||||
{
|
||||
if (!CheckPenumbraApi()) return null;
|
||||
return _penumbraResolveModDir!.InvokeFunc().ToLowerInvariant();
|
||||
}
|
||||
|
||||
public void PenumbraRedraw(IntPtr obj)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Redrawing " + gameObj);
|
||||
_penumbraRedrawObject!.InvokeAction(gameObj, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void PenumbraRedraw(string actorName)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
actionQueue.Enqueue(() => _penumbraRedraw!.InvokeAction(actorName, 0));
|
||||
}
|
||||
|
||||
public void PenumbraRemoveTemporaryCollection(string characterName)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
Logger.Verbose("Removing temp collection for " + characterName);
|
||||
_penumbraRemoveTemporaryCollection.InvokeFunc(characterName);
|
||||
});
|
||||
}
|
||||
|
||||
public string PenumbraResolvePath(string path)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return path;
|
||||
var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path);
|
||||
return resolvedPath ?? path;
|
||||
}
|
||||
|
||||
public string[] PenumbraReverseResolvePlayer(string path)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return new[] { path };
|
||||
var resolvedPaths = _reverseResolvePlayer!.InvokeFunc(path);
|
||||
if (resolvedPaths.Length == 0)
|
||||
{
|
||||
resolvedPaths = new[] { path };
|
||||
}
|
||||
return resolvedPaths;
|
||||
}
|
||||
|
||||
public void PenumbraSetTemporaryMods(string characterName, Dictionary<string, string> modPaths, string manipulationData)
|
||||
{
|
||||
if (!CheckPenumbraApi()) return;
|
||||
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true);
|
||||
Logger.Verbose("Assigning temp mods for " + ret.Item2);
|
||||
foreach (var mod in modPaths)
|
||||
{
|
||||
Logger.Verbose(mod.Key + " => " + mod.Value);
|
||||
}
|
||||
_penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", ret.Item2, modPaths, manipulationData, 0);
|
||||
});
|
||||
}
|
||||
|
||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||
{
|
||||
PenumbraRedrawEvent?.Invoke(objectAddress, objectTableIndex);
|
||||
}
|
||||
|
||||
private void PenumbraInit()
|
||||
{
|
||||
PenumbraInitialized?.Invoke();
|
||||
_penumbraRedraw!.InvokeAction("self", 0);
|
||||
}
|
||||
|
||||
private void HeelsOffsetChange(float offset)
|
||||
{
|
||||
HeelsOffsetChangeEvent?.Invoke(offset);
|
||||
}
|
||||
|
||||
private void PenumbraDispose()
|
||||
{
|
||||
PenumbraDisposed?.Invoke();
|
||||
actionQueue.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,258 +14,257 @@ using MareSynchronos.FileCache;
|
||||
using Newtonsoft.Json;
|
||||
#endif
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
|
||||
|
||||
public class PlayerManager : IDisposable
|
||||
{
|
||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CharacterDataFactory _characterDataFactory;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly TransientResourceManager _transientResourceManager;
|
||||
private readonly PeriodicFileScanner _periodicFileScanner;
|
||||
private readonly IpcManager _ipcManager;
|
||||
public event PlayerHasChanged? PlayerHasChanged;
|
||||
public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
|
||||
public CharacterData PermanentDataCache { get; private set; } = new();
|
||||
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
|
||||
|
||||
public class PlayerManager : IDisposable
|
||||
private CancellationTokenSource? _playerChangedCts = new();
|
||||
private CancellationTokenSource _transientUpdateCts = new();
|
||||
|
||||
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
|
||||
|
||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager,
|
||||
PeriodicFileScanner periodicFileScanner)
|
||||
{
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CharacterDataFactory _characterDataFactory;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly TransientResourceManager _transientResourceManager;
|
||||
private readonly PeriodicFileScanner _periodicFileScanner;
|
||||
private readonly IpcManager _ipcManager;
|
||||
public event PlayerHasChanged? PlayerHasChanged;
|
||||
public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
|
||||
public CharacterData PermanentDataCache { get; private set; } = new();
|
||||
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
|
||||
Logger.Verbose("Creating " + nameof(PlayerManager));
|
||||
|
||||
private CancellationTokenSource? _playerChangedCts = new();
|
||||
private CancellationTokenSource _transientUpdateCts = new();
|
||||
_apiController = apiController;
|
||||
_ipcManager = ipcManager;
|
||||
_characterDataFactory = characterDataFactory;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_transientResourceManager = transientResourceManager;
|
||||
_periodicFileScanner = periodicFileScanner;
|
||||
_apiController.Connected += ApiControllerOnConnected;
|
||||
_apiController.Disconnected += ApiController_Disconnected;
|
||||
_transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad;
|
||||
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
|
||||
_ipcManager.HeelsOffsetChangeEvent += HeelsOffsetChanged;
|
||||
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
|
||||
|
||||
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
|
||||
|
||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager,
|
||||
PeriodicFileScanner periodicFileScanner)
|
||||
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
|
||||
if (_apiController.IsConnected)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(PlayerManager));
|
||||
|
||||
_apiController = apiController;
|
||||
_ipcManager = ipcManager;
|
||||
_characterDataFactory = characterDataFactory;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_transientResourceManager = transientResourceManager;
|
||||
_periodicFileScanner = periodicFileScanner;
|
||||
_apiController.Connected += ApiControllerOnConnected;
|
||||
_apiController.Disconnected += ApiController_Disconnected;
|
||||
_transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad;
|
||||
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
|
||||
_ipcManager.HeelsOffsetChangeEvent += HeelsOffsetChanged;
|
||||
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
|
||||
|
||||
|
||||
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
|
||||
if (_apiController.IsConnected)
|
||||
{
|
||||
ApiControllerOnConnected();
|
||||
}
|
||||
|
||||
playerRelatedObjects = new List<PlayerRelatedObject>()
|
||||
{
|
||||
new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
|
||||
new PlayerRelatedObject(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
|
||||
new PlayerRelatedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()),
|
||||
new PlayerRelatedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()),
|
||||
};
|
||||
ApiControllerOnConnected();
|
||||
}
|
||||
|
||||
private void DalamudUtilOnFrameworkUpdate()
|
||||
playerRelatedObjects = new List<PlayerRelatedObject>()
|
||||
{
|
||||
_transientResourceManager.PlayerRelatedPointers = playerRelatedObjects.Select(f => f.CurrentAddress).ToArray();
|
||||
}
|
||||
new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
|
||||
new PlayerRelatedObject(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
|
||||
new PlayerRelatedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()),
|
||||
new PlayerRelatedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()),
|
||||
};
|
||||
}
|
||||
|
||||
public void HandleTransientResourceLoad(IntPtr gameObj)
|
||||
private void DalamudUtilOnFrameworkUpdate()
|
||||
{
|
||||
_transientResourceManager.PlayerRelatedPointers = playerRelatedObjects.Select(f => f.CurrentAddress).ToArray();
|
||||
}
|
||||
|
||||
public void HandleTransientResourceLoad(IntPtr gameObj)
|
||||
{
|
||||
foreach (var obj in playerRelatedObjects)
|
||||
{
|
||||
foreach (var obj in playerRelatedObjects)
|
||||
if (obj.Address == gameObj && !obj.HasUnprocessedUpdate)
|
||||
{
|
||||
if (obj.Address == gameObj && !obj.HasUnprocessedUpdate)
|
||||
_transientUpdateCts.Cancel();
|
||||
_transientUpdateCts = new CancellationTokenSource();
|
||||
var token = _transientUpdateCts.Token;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_transientUpdateCts.Cancel();
|
||||
_transientUpdateCts = new CancellationTokenSource();
|
||||
var token = _transientUpdateCts.Token;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Logger.Debug("Delaying transient resource load update");
|
||||
await Task.Delay(750, token);
|
||||
if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
|
||||
Logger.Debug("Firing transient resource load update");
|
||||
obj.HasTransientsUpdate = true;
|
||||
}, token);
|
||||
Logger.Debug("Delaying transient resource load update");
|
||||
await Task.Delay(750, token);
|
||||
if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
|
||||
Logger.Debug("Firing transient resource load update");
|
||||
obj.HasTransientsUpdate = true;
|
||||
}, token);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HeelsOffsetChanged(float change)
|
||||
{
|
||||
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
||||
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing)
|
||||
{
|
||||
Logger.Debug("Heels offset changed to " + change);
|
||||
playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasTransientsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(PlayerManager));
|
||||
|
||||
_apiController.Connected -= ApiControllerOnConnected;
|
||||
_apiController.Disconnected -= ApiController_Disconnected;
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
||||
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
|
||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||
|
||||
_transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad;
|
||||
|
||||
_playerChangedCts?.Cancel();
|
||||
_ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged;
|
||||
}
|
||||
|
||||
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
|
||||
{
|
||||
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
|
||||
|
||||
playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
|
||||
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
|
||||
{
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApiControllerOnConnected()
|
||||
{
|
||||
Logger.Debug("ApiController Connected");
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
|
||||
}
|
||||
|
||||
private void ApiController_Disconnected()
|
||||
{
|
||||
Logger.Debug(nameof(ApiController_Disconnected));
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
||||
}
|
||||
|
||||
private async Task<CharacterCacheDto?> CreateFullCharacterCacheDto(CancellationToken token)
|
||||
{
|
||||
foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
|
||||
{
|
||||
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
|
||||
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
unprocessedObject.HasUnprocessedUpdate = false;
|
||||
unprocessedObject.IsProcessing = false;
|
||||
unprocessedObject.HasTransientsUpdate = false;
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Verbose("Waiting until cache is ready");
|
||||
await Task.Delay(50, token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) return null;
|
||||
|
||||
Logger.Verbose("Cache creation complete");
|
||||
|
||||
var cache = PermanentDataCache.ToCharacterCacheDto();
|
||||
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
|
||||
{
|
||||
Logger.Verbose("RedrawEvent for addr " + address);
|
||||
|
||||
foreach (var item in playerRelatedObjects)
|
||||
{
|
||||
if (address == item.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
|
||||
item.HasUnprocessedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
|
||||
{
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerOrAttachedObjectsChanged()
|
||||
{
|
||||
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
|
||||
foreach (var unprocessedObject in unprocessedObjects)
|
||||
{
|
||||
unprocessedObject.IsProcessing = true;
|
||||
}
|
||||
Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind)));
|
||||
bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate);
|
||||
unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false);
|
||||
_playerChangedCts?.Cancel();
|
||||
_playerChangedCts = new CancellationTokenSource();
|
||||
var token = _playerChangedCts.Token;
|
||||
|
||||
// fix for redraw from anamnesis
|
||||
while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Waiting Until Player is Present");
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ipcManager.Initialized)
|
||||
{
|
||||
Logger.Warn("Penumbra not active, doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_periodicFileScanner.HaltScan("Character creation");
|
||||
foreach (var item in unprocessedObjects)
|
||||
{
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token);
|
||||
}
|
||||
|
||||
CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token));
|
||||
_periodicFileScanner.ResumeScan("Character creation");
|
||||
if (cacheDto == null || token.IsCancellationRequested) return;
|
||||
|
||||
#if DEBUG
|
||||
//var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented);
|
||||
//Logger.Verbose(json);
|
||||
#endif
|
||||
|
||||
if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode())
|
||||
{
|
||||
Logger.Debug("Not sending data, already sent");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastCreatedCharacterData = cacheDto;
|
||||
}
|
||||
|
||||
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
|
||||
{
|
||||
Logger.Verbose("Invoking PlayerHasChanged");
|
||||
PlayerHasChanged?.Invoke(cacheDto);
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
|
||||
private void HeelsOffsetChanged(float change)
|
||||
{
|
||||
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
||||
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing)
|
||||
{
|
||||
Logger.Debug("Heels offset changed to " + change);
|
||||
playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasTransientsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(PlayerManager));
|
||||
|
||||
_apiController.Connected -= ApiControllerOnConnected;
|
||||
_apiController.Disconnected -= ApiController_Disconnected;
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
||||
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
|
||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||
|
||||
_transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad;
|
||||
|
||||
_playerChangedCts?.Cancel();
|
||||
_ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged;
|
||||
}
|
||||
|
||||
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
|
||||
{
|
||||
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
|
||||
|
||||
playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
|
||||
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
|
||||
{
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApiControllerOnConnected()
|
||||
{
|
||||
Logger.Debug("ApiController Connected");
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
|
||||
}
|
||||
|
||||
private void ApiController_Disconnected()
|
||||
{
|
||||
Logger.Debug(nameof(ApiController_Disconnected));
|
||||
|
||||
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
||||
}
|
||||
|
||||
private async Task<CharacterCacheDto?> CreateFullCharacterCacheDto(CancellationToken token)
|
||||
{
|
||||
foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
|
||||
{
|
||||
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
|
||||
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
unprocessedObject.HasUnprocessedUpdate = false;
|
||||
unprocessedObject.IsProcessing = false;
|
||||
unprocessedObject.HasTransientsUpdate = false;
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Verbose("Waiting until cache is ready");
|
||||
await Task.Delay(50, token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) return null;
|
||||
|
||||
Logger.Verbose("Cache creation complete");
|
||||
|
||||
var cache = PermanentDataCache.ToCharacterCacheDto();
|
||||
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
|
||||
{
|
||||
Logger.Verbose("RedrawEvent for addr " + address);
|
||||
|
||||
foreach (var item in playerRelatedObjects)
|
||||
{
|
||||
if (address == item.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
|
||||
item.HasUnprocessedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
|
||||
{
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerOrAttachedObjectsChanged()
|
||||
{
|
||||
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
|
||||
foreach (var unprocessedObject in unprocessedObjects)
|
||||
{
|
||||
unprocessedObject.IsProcessing = true;
|
||||
}
|
||||
Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind)));
|
||||
bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate);
|
||||
unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false);
|
||||
_playerChangedCts?.Cancel();
|
||||
_playerChangedCts = new CancellationTokenSource();
|
||||
var token = _playerChangedCts.Token;
|
||||
|
||||
// fix for redraw from anamnesis
|
||||
while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Waiting Until Player is Present");
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ipcManager.Initialized)
|
||||
{
|
||||
Logger.Warn("Penumbra not active, doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_periodicFileScanner.HaltScan("Character creation");
|
||||
foreach (var item in unprocessedObjects)
|
||||
{
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token);
|
||||
}
|
||||
|
||||
CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token));
|
||||
_periodicFileScanner.ResumeScan("Character creation");
|
||||
if (cacheDto == null || token.IsCancellationRequested) return;
|
||||
|
||||
#if DEBUG
|
||||
//var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented);
|
||||
//Logger.Verbose(json);
|
||||
#endif
|
||||
|
||||
if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode())
|
||||
{
|
||||
Logger.Debug("Not sending data, already sent");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastCreatedCharacterData = cacheDto;
|
||||
}
|
||||
|
||||
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
|
||||
{
|
||||
Logger.Verbose("Invoking PlayerHasChanged");
|
||||
PlayerHasChanged?.Invoke(cacheDto);
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,198 +8,197 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
|
||||
|
||||
public class TransientResourceManager : IDisposable
|
||||
{
|
||||
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
|
||||
private readonly IpcManager manager;
|
||||
private readonly DalamudUtil dalamudUtil;
|
||||
|
||||
public class TransientResourceManager : IDisposable
|
||||
public event TransientResourceLoadedEvent? TransientResourceLoaded;
|
||||
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
|
||||
private readonly string[] FileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp" };
|
||||
|
||||
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
||||
public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil)
|
||||
{
|
||||
private readonly IpcManager manager;
|
||||
private readonly DalamudUtil dalamudUtil;
|
||||
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
|
||||
this.manager = manager;
|
||||
this.dalamudUtil = dalamudUtil;
|
||||
dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate;
|
||||
dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged;
|
||||
}
|
||||
|
||||
public event TransientResourceLoadedEvent? TransientResourceLoaded;
|
||||
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
|
||||
private readonly string[] FileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp" };
|
||||
|
||||
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
||||
public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil)
|
||||
private void DalamudUtil_ClassJobChanged()
|
||||
{
|
||||
if (SemiTransientResources.ContainsKey(ObjectKind.Pet))
|
||||
{
|
||||
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
|
||||
this.manager = manager;
|
||||
this.dalamudUtil = dalamudUtil;
|
||||
dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate;
|
||||
dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged;
|
||||
SemiTransientResources[ObjectKind.Pet].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void DalamudUtil_ClassJobChanged()
|
||||
private void DalamudUtil_FrameworkUpdate()
|
||||
{
|
||||
foreach (var item in TransientResources.ToList())
|
||||
{
|
||||
if (SemiTransientResources.ContainsKey(ObjectKind.Pet))
|
||||
if (!dalamudUtil.IsGameObjectPresent(item.Key))
|
||||
{
|
||||
SemiTransientResources[ObjectKind.Pet].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void DalamudUtil_FrameworkUpdate()
|
||||
{
|
||||
foreach (var item in TransientResources.ToList())
|
||||
{
|
||||
if (!dalamudUtil.IsGameObjectPresent(item.Key))
|
||||
{
|
||||
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
|
||||
TransientResources.TryRemove(item.Key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanSemiTransientResources(ObjectKind objectKind)
|
||||
{
|
||||
if (SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetTransientResources(IntPtr gameObject)
|
||||
{
|
||||
if (TransientResources.TryGetValue(gameObject, out var result))
|
||||
{
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
|
||||
{
|
||||
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
||||
{
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
return new List<FileReplacement>();
|
||||
}
|
||||
|
||||
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath)
|
||||
{
|
||||
if (!FileTypesToHandle.Any(type => gamePath.ToLowerInvariant().EndsWith(type)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!PlayerRelatedPointers.Contains(gameObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TransientResources.ContainsKey(gameObject))
|
||||
{
|
||||
TransientResources[gameObject] = new();
|
||||
}
|
||||
|
||||
if (filePath.StartsWith("|"))
|
||||
{
|
||||
filePath = filePath.Split("|")[2];
|
||||
}
|
||||
|
||||
filePath = filePath.ToLowerInvariant().Replace("\\", "/");
|
||||
|
||||
var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/");
|
||||
|
||||
if (TransientResources[gameObject].Contains(replacedGamePath) ||
|
||||
SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
|
||||
&& f.ResolvedPath.ToLowerInvariant() == filePath)))
|
||||
{
|
||||
Logger.Debug("Not adding " + replacedGamePath + ":" + filePath);
|
||||
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
|
||||
&& f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
TransientResources[gameObject].Add(replacedGamePath);
|
||||
Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})");
|
||||
TransientResourceLoaded?.Invoke(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
|
||||
{
|
||||
if (TransientResources.ContainsKey(gameObject))
|
||||
{
|
||||
TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => g.ToLowerInvariant() == f.ToLowerInvariant()));
|
||||
}
|
||||
}
|
||||
|
||||
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func<string, bool, FileReplacement> createFileReplacement)
|
||||
{
|
||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
}
|
||||
|
||||
if (!TransientResources.TryGetValue(gameObject, out var resources))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transientResources = resources.ToList();
|
||||
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
|
||||
foreach (var gamePath in transientResources)
|
||||
{
|
||||
var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant());
|
||||
if (existingResource)
|
||||
{
|
||||
Logger.Debug("Semi Transient resource replaced: " + gamePath);
|
||||
SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), true);
|
||||
if (!fileReplacement.HasFileReplacement)
|
||||
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), false);
|
||||
if (fileReplacement.HasFileReplacement)
|
||||
{
|
||||
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
|
||||
if (SemiTransientResources[objectKind].Add(fileReplacement))
|
||||
{
|
||||
Logger.Debug("Added " + fileReplacement);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Not added " + fileReplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Issue during transient file persistence");
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
TransientResources[gameObject].Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
|
||||
manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
|
||||
dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
|
||||
TransientResources.Clear();
|
||||
}
|
||||
|
||||
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
|
||||
{
|
||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
}
|
||||
|
||||
if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant()))
|
||||
{
|
||||
SemiTransientResources[objectKind].Add(item);
|
||||
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
|
||||
TransientResources.TryRemove(item.Key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanSemiTransientResources(ObjectKind objectKind)
|
||||
{
|
||||
if (SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetTransientResources(IntPtr gameObject)
|
||||
{
|
||||
if (TransientResources.TryGetValue(gameObject, out var result))
|
||||
{
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
|
||||
{
|
||||
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
||||
{
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
return new List<FileReplacement>();
|
||||
}
|
||||
|
||||
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath)
|
||||
{
|
||||
if (!FileTypesToHandle.Any(type => gamePath.ToLowerInvariant().EndsWith(type)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!PlayerRelatedPointers.Contains(gameObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TransientResources.ContainsKey(gameObject))
|
||||
{
|
||||
TransientResources[gameObject] = new();
|
||||
}
|
||||
|
||||
if (filePath.StartsWith("|"))
|
||||
{
|
||||
filePath = filePath.Split("|")[2];
|
||||
}
|
||||
|
||||
filePath = filePath.ToLowerInvariant().Replace("\\", "/");
|
||||
|
||||
var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/");
|
||||
|
||||
if (TransientResources[gameObject].Contains(replacedGamePath) ||
|
||||
SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
|
||||
&& f.ResolvedPath.ToLowerInvariant() == filePath)))
|
||||
{
|
||||
Logger.Debug("Not adding " + replacedGamePath + ":" + filePath);
|
||||
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
|
||||
&& f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
TransientResources[gameObject].Add(replacedGamePath);
|
||||
Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})");
|
||||
TransientResourceLoaded?.Invoke(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
|
||||
{
|
||||
if (TransientResources.ContainsKey(gameObject))
|
||||
{
|
||||
TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => g.ToLowerInvariant() == f.ToLowerInvariant()));
|
||||
}
|
||||
}
|
||||
|
||||
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func<string, bool, FileReplacement> createFileReplacement)
|
||||
{
|
||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
}
|
||||
|
||||
if (!TransientResources.TryGetValue(gameObject, out var resources))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transientResources = resources.ToList();
|
||||
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
|
||||
foreach (var gamePath in transientResources)
|
||||
{
|
||||
var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant());
|
||||
if (existingResource)
|
||||
{
|
||||
Logger.Debug("Semi Transient resource replaced: " + gamePath);
|
||||
SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), true);
|
||||
if (!fileReplacement.HasFileReplacement)
|
||||
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), false);
|
||||
if (fileReplacement.HasFileReplacement)
|
||||
{
|
||||
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
|
||||
if (SemiTransientResources[objectKind].Add(fileReplacement))
|
||||
{
|
||||
Logger.Debug("Added " + fileReplacement);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Not added " + fileReplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Issue during transient file persistence");
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
TransientResources[gameObject].Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
|
||||
manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
|
||||
dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
|
||||
TransientResources.Clear();
|
||||
}
|
||||
|
||||
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
|
||||
{
|
||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
}
|
||||
|
||||
if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant()))
|
||||
{
|
||||
SemiTransientResources[objectKind].Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,83 +6,82 @@ using MareSynchronos.API;
|
||||
using MareSynchronos.Utils;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace MareSynchronos.Models
|
||||
namespace MareSynchronos.Models;
|
||||
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class CharacterData
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class CharacterData
|
||||
[JsonProperty]
|
||||
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
|
||||
|
||||
[JsonProperty]
|
||||
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
||||
|
||||
public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed);
|
||||
|
||||
[JsonProperty]
|
||||
public string ManipulationString { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty]
|
||||
public float HeelsOffset { get; set; } = 0f;
|
||||
|
||||
public void AddFileReplacement(ObjectKind objectKind, FileReplacement fileReplacement)
|
||||
{
|
||||
[JsonProperty]
|
||||
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
|
||||
if (!fileReplacement.HasFileReplacement) return;
|
||||
|
||||
[JsonProperty]
|
||||
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
||||
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
|
||||
|
||||
public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed);
|
||||
|
||||
[JsonProperty]
|
||||
public string ManipulationString { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty]
|
||||
public float HeelsOffset { get; set; } = 0f;
|
||||
|
||||
public void AddFileReplacement(ObjectKind objectKind, FileReplacement fileReplacement)
|
||||
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath);
|
||||
if (existingReplacement != null)
|
||||
{
|
||||
if (!fileReplacement.HasFileReplacement) return;
|
||||
|
||||
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
|
||||
|
||||
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath);
|
||||
if (existingReplacement != null)
|
||||
{
|
||||
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e)));
|
||||
}
|
||||
else
|
||||
{
|
||||
FileReplacements[objectKind].Add(fileReplacement);
|
||||
}
|
||||
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e)));
|
||||
}
|
||||
|
||||
public CharacterCacheDto ToCharacterCacheDto()
|
||||
else
|
||||
{
|
||||
var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g =>
|
||||
{
|
||||
return new FileReplacementDto()
|
||||
{
|
||||
GamePaths = g.SelectMany(f => f.GamePaths).Distinct().ToArray(),
|
||||
Hash = g.First().Hash,
|
||||
};
|
||||
}).ToList());
|
||||
|
||||
Logger.Debug("Adding fileSwaps");
|
||||
foreach (var item in FileReplacements)
|
||||
{
|
||||
Logger.Debug("Checking fileSwaps for " + item.Key);
|
||||
var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto());
|
||||
Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps");
|
||||
foreach (var swap in fileSwapsToAdd)
|
||||
{
|
||||
Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath);
|
||||
}
|
||||
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
|
||||
}
|
||||
|
||||
return new CharacterCacheDto()
|
||||
{
|
||||
FileReplacements = fileReplacements,
|
||||
GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value),
|
||||
ManipulationData = ManipulationString,
|
||||
HeelsOffset = HeelsOffset
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0]))
|
||||
{
|
||||
stringBuilder.AppendLine(fileReplacement.ToString());
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
FileReplacements[objectKind].Add(fileReplacement);
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterCacheDto ToCharacterCacheDto()
|
||||
{
|
||||
var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g =>
|
||||
{
|
||||
return new FileReplacementDto()
|
||||
{
|
||||
GamePaths = g.SelectMany(f => f.GamePaths).Distinct().ToArray(),
|
||||
Hash = g.First().Hash,
|
||||
};
|
||||
}).ToList());
|
||||
|
||||
Logger.Debug("Adding fileSwaps");
|
||||
foreach (var item in FileReplacements)
|
||||
{
|
||||
Logger.Debug("Checking fileSwaps for " + item.Key);
|
||||
var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto());
|
||||
Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps");
|
||||
foreach (var swap in fileSwapsToAdd)
|
||||
{
|
||||
Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath);
|
||||
}
|
||||
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
|
||||
}
|
||||
|
||||
return new CharacterCacheDto()
|
||||
{
|
||||
FileReplacements = fileReplacements,
|
||||
GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value),
|
||||
ManipulationData = ManipulationString,
|
||||
HeelsOffset = HeelsOffset
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0]))
|
||||
{
|
||||
stringBuilder.AppendLine(fileReplacement.ToString());
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,55 +6,54 @@ using MareSynchronos.API;
|
||||
using System.Text.RegularExpressions;
|
||||
using MareSynchronos.FileCache;
|
||||
|
||||
namespace MareSynchronos.Models
|
||||
namespace MareSynchronos.Models;
|
||||
|
||||
public class FileReplacement
|
||||
{
|
||||
public class FileReplacement
|
||||
private readonly FileCacheManager fileDbManager;
|
||||
|
||||
public FileReplacement(FileCacheManager fileDbManager)
|
||||
{
|
||||
private readonly FileCacheManager fileDbManager;
|
||||
this.fileDbManager = fileDbManager;
|
||||
}
|
||||
|
||||
public FileReplacement(FileCacheManager fileDbManager)
|
||||
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
|
||||
|
||||
public List<string> GamePaths { get; set; } = new();
|
||||
|
||||
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath);
|
||||
|
||||
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath;
|
||||
|
||||
public string Hash { get; set; } = string.Empty;
|
||||
|
||||
public string ResolvedPath { get; set; } = string.Empty;
|
||||
|
||||
public void SetResolvedPath(string path)
|
||||
{
|
||||
ResolvedPath = path.ToLowerInvariant().Replace('\\', '/');
|
||||
if (!HasFileReplacement || IsFileSwap) return;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
this.fileDbManager = fileDbManager;
|
||||
}
|
||||
var cache = fileDbManager.GetFileCacheByPath(ResolvedPath);
|
||||
Hash = cache.Hash;
|
||||
});
|
||||
}
|
||||
|
||||
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
|
||||
|
||||
public List<string> GamePaths { get; set; } = new();
|
||||
|
||||
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath);
|
||||
|
||||
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath;
|
||||
|
||||
public string Hash { get; set; } = string.Empty;
|
||||
|
||||
public string ResolvedPath { get; set; } = string.Empty;
|
||||
|
||||
public void SetResolvedPath(string path)
|
||||
public FileReplacementDto ToFileReplacementDto()
|
||||
{
|
||||
return new FileReplacementDto
|
||||
{
|
||||
ResolvedPath = path.ToLowerInvariant().Replace('\\', '/');
|
||||
if (!HasFileReplacement || IsFileSwap) return;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
var cache = fileDbManager.GetFileCacheByPath(ResolvedPath);
|
||||
Hash = cache.Hash;
|
||||
});
|
||||
}
|
||||
|
||||
public FileReplacementDto ToFileReplacementDto()
|
||||
{
|
||||
return new FileReplacementDto
|
||||
{
|
||||
GamePaths = GamePaths.ToArray(),
|
||||
Hash = Hash,
|
||||
FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty
|
||||
};
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
|
||||
return builder.ToString();
|
||||
}
|
||||
GamePaths = GamePaths.ToArray(),
|
||||
Hash = Hash,
|
||||
FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty
|
||||
};
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,132 +5,131 @@ using System.Runtime.InteropServices;
|
||||
using MareSynchronos.Utils;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace MareSynchronos.Models
|
||||
namespace MareSynchronos.Models;
|
||||
|
||||
public class PlayerRelatedObject
|
||||
{
|
||||
public class PlayerRelatedObject
|
||||
private readonly Func<IntPtr> getAddress;
|
||||
|
||||
public unsafe Character* Character => (Character*)Address;
|
||||
|
||||
private string _name;
|
||||
|
||||
public ObjectKind ObjectKind { get; }
|
||||
public IntPtr Address { get; set; }
|
||||
public IntPtr DrawObjectAddress { get; set; }
|
||||
|
||||
public IntPtr CurrentAddress
|
||||
{
|
||||
private readonly Func<IntPtr> getAddress;
|
||||
|
||||
public unsafe Character* Character => (Character*)Address;
|
||||
|
||||
private string _name;
|
||||
|
||||
public ObjectKind ObjectKind { get; }
|
||||
public IntPtr Address { get; set; }
|
||||
public IntPtr DrawObjectAddress { get; set; }
|
||||
|
||||
public IntPtr CurrentAddress
|
||||
get
|
||||
{
|
||||
get
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
return getAddress.Invoke();
|
||||
}
|
||||
catch
|
||||
{ return IntPtr.Zero; }
|
||||
return getAddress.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
|
||||
{
|
||||
ObjectKind = objectKind;
|
||||
Address = address;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
this.getAddress = getAddress;
|
||||
_name = string.Empty;
|
||||
}
|
||||
|
||||
public byte[] EquipSlotData { get; set; } = new byte[40];
|
||||
public byte[] CustomizeData { get; set; } = new byte[26];
|
||||
public byte? HatState { get; set; }
|
||||
public byte? VisorWeaponState { get; set; }
|
||||
|
||||
public bool HasTransientsUpdate { get; set; } = false;
|
||||
public bool HasUnprocessedUpdate { get; set; } = false;
|
||||
public bool DoNotSendUpdate { get; set; } = false;
|
||||
public bool IsProcessing { get; set; } = false;
|
||||
|
||||
public unsafe void CheckAndUpdateObject()
|
||||
{
|
||||
var curPtr = CurrentAddress;
|
||||
if (curPtr != IntPtr.Zero)
|
||||
{
|
||||
var chara = (Character*)curPtr;
|
||||
bool addr = Address == IntPtr.Zero || Address != curPtr;
|
||||
bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData);
|
||||
bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress;
|
||||
var name = new Utf8String(chara->GameObject.Name).ToString();
|
||||
bool nameChange = (name != _name);
|
||||
if (addr || equip || drawObj || nameChange)
|
||||
{
|
||||
_name = name;
|
||||
Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}");
|
||||
|
||||
Address = curPtr;
|
||||
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
||||
HasUnprocessedUpdate = true;
|
||||
}
|
||||
}
|
||||
else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero)
|
||||
{
|
||||
Address = IntPtr.Zero;
|
||||
DrawObjectAddress = IntPtr.Zero;
|
||||
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData)
|
||||
{
|
||||
bool hasChanges = false;
|
||||
DoNotSendUpdate = false;
|
||||
for (int i = 0; i < EquipSlotData.Length; i++)
|
||||
{
|
||||
var data = Marshal.ReadByte((IntPtr)equipSlotData, i);
|
||||
if (EquipSlotData[i] != data)
|
||||
{
|
||||
EquipSlotData[i] = data;
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CustomizeData.Length; i++)
|
||||
{
|
||||
var data = Marshal.ReadByte((IntPtr)customizeData, i);
|
||||
if (CustomizeData[i] != data)
|
||||
{
|
||||
CustomizeData[i] = data;
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
var newHatState = Marshal.ReadByte((IntPtr)customizeData + 30, 0);
|
||||
var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0);
|
||||
if (newHatState != HatState)
|
||||
{
|
||||
if (HatState != null && !hasChanges && !HasUnprocessedUpdate)
|
||||
{
|
||||
Logger.Debug("Not Sending Update, only Hat changed");
|
||||
DoNotSendUpdate = true;
|
||||
}
|
||||
HatState = newHatState;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
newWeaponOrVisorState &= 0b1101; // ignore drawing weapon
|
||||
|
||||
if (newWeaponOrVisorState != VisorWeaponState)
|
||||
{
|
||||
if (VisorWeaponState != null && !hasChanges && !HasUnprocessedUpdate)
|
||||
{
|
||||
Logger.Debug("Not Sending Update, only Visor/Weapon changed");
|
||||
DoNotSendUpdate = true;
|
||||
}
|
||||
VisorWeaponState = newWeaponOrVisorState;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
catch
|
||||
{ return IntPtr.Zero; }
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
|
||||
{
|
||||
ObjectKind = objectKind;
|
||||
Address = address;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
this.getAddress = getAddress;
|
||||
_name = string.Empty;
|
||||
}
|
||||
|
||||
public byte[] EquipSlotData { get; set; } = new byte[40];
|
||||
public byte[] CustomizeData { get; set; } = new byte[26];
|
||||
public byte? HatState { get; set; }
|
||||
public byte? VisorWeaponState { get; set; }
|
||||
|
||||
public bool HasTransientsUpdate { get; set; } = false;
|
||||
public bool HasUnprocessedUpdate { get; set; } = false;
|
||||
public bool DoNotSendUpdate { get; set; } = false;
|
||||
public bool IsProcessing { get; set; } = false;
|
||||
|
||||
public unsafe void CheckAndUpdateObject()
|
||||
{
|
||||
var curPtr = CurrentAddress;
|
||||
if (curPtr != IntPtr.Zero)
|
||||
{
|
||||
var chara = (Character*)curPtr;
|
||||
bool addr = Address == IntPtr.Zero || Address != curPtr;
|
||||
bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData);
|
||||
bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress;
|
||||
var name = new Utf8String(chara->GameObject.Name).ToString();
|
||||
bool nameChange = (name != _name);
|
||||
if (addr || equip || drawObj || nameChange)
|
||||
{
|
||||
_name = name;
|
||||
Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}");
|
||||
|
||||
Address = curPtr;
|
||||
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
||||
HasUnprocessedUpdate = true;
|
||||
}
|
||||
}
|
||||
else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero)
|
||||
{
|
||||
Address = IntPtr.Zero;
|
||||
DrawObjectAddress = IntPtr.Zero;
|
||||
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData)
|
||||
{
|
||||
bool hasChanges = false;
|
||||
DoNotSendUpdate = false;
|
||||
for (int i = 0; i < EquipSlotData.Length; i++)
|
||||
{
|
||||
var data = Marshal.ReadByte((IntPtr)equipSlotData, i);
|
||||
if (EquipSlotData[i] != data)
|
||||
{
|
||||
EquipSlotData[i] = data;
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < CustomizeData.Length; i++)
|
||||
{
|
||||
var data = Marshal.ReadByte((IntPtr)customizeData, i);
|
||||
if (CustomizeData[i] != data)
|
||||
{
|
||||
CustomizeData[i] = data;
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
var newHatState = Marshal.ReadByte((IntPtr)customizeData + 30, 0);
|
||||
var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0);
|
||||
if (newHatState != HatState)
|
||||
{
|
||||
if (HatState != null && !hasChanges && !HasUnprocessedUpdate)
|
||||
{
|
||||
Logger.Debug("Not Sending Update, only Hat changed");
|
||||
DoNotSendUpdate = true;
|
||||
}
|
||||
HatState = newHatState;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
newWeaponOrVisorState &= 0b1101; // ignore drawing weapon
|
||||
|
||||
if (newWeaponOrVisorState != VisorWeaponState)
|
||||
{
|
||||
if (VisorWeaponState != null && !hasChanges && !HasUnprocessedUpdate)
|
||||
{
|
||||
Logger.Debug("Not Sending Update, only Visor/Weapon changed");
|
||||
DoNotSendUpdate = true;
|
||||
}
|
||||
VisorWeaponState = newWeaponOrVisorState;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,204 +15,203 @@ using MareSynchronos.Utils;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using MareSynchronos.FileCache;
|
||||
|
||||
namespace MareSynchronos
|
||||
namespace MareSynchronos;
|
||||
|
||||
public sealed class Plugin : IDalamudPlugin
|
||||
{
|
||||
public sealed class Plugin : IDalamudPlugin
|
||||
private const string CommandName = "/mare";
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CommandManager _commandManager;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly PeriodicFileScanner _periodicFileScanner;
|
||||
private readonly IntroUi _introUi;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly SettingsUi _settingsUi;
|
||||
private readonly WindowSystem _windowSystem;
|
||||
private PlayerManager? _playerManager;
|
||||
private TransientResourceManager? _transientResourceManager;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private OnlinePlayerManager? _characterCacheManager;
|
||||
private readonly DownloadUi _downloadUi;
|
||||
private readonly FileDialogManager _fileDialogManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly CompactUi _compactUi;
|
||||
private readonly UiShared _uiSharedComponent;
|
||||
private readonly Dalamud.Localization _localization;
|
||||
|
||||
|
||||
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
|
||||
Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition)
|
||||
{
|
||||
private const string CommandName = "/mare";
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CommandManager _commandManager;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly PeriodicFileScanner _periodicFileScanner;
|
||||
private readonly IntroUi _introUi;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly SettingsUi _settingsUi;
|
||||
private readonly WindowSystem _windowSystem;
|
||||
private PlayerManager? _playerManager;
|
||||
private TransientResourceManager? _transientResourceManager;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private OnlinePlayerManager? _characterCacheManager;
|
||||
private readonly DownloadUi _downloadUi;
|
||||
private readonly FileDialogManager _fileDialogManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly CompactUi _compactUi;
|
||||
private readonly UiShared _uiSharedComponent;
|
||||
private readonly Dalamud.Localization _localization;
|
||||
Logger.Debug("Launching " + Name);
|
||||
_pluginInterface = pluginInterface;
|
||||
_commandManager = commandManager;
|
||||
_configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
_configuration.Initialize(_pluginInterface);
|
||||
_configuration.Migrate();
|
||||
|
||||
_localization = new Dalamud.Localization("MareSynchronos.Localization.", "", true);
|
||||
_localization.SetupWithLangCode("en");
|
||||
|
||||
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
|
||||
Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition)
|
||||
_windowSystem = new WindowSystem("MareSynchronos");
|
||||
|
||||
// those can be initialized outside of game login
|
||||
_dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition);
|
||||
|
||||
_ipcManager = new IpcManager(_pluginInterface, _dalamudUtil);
|
||||
_fileDialogManager = new FileDialogManager();
|
||||
_fileDbManager = new FileCacheManager(_ipcManager, _configuration, _pluginInterface.ConfigDirectory.FullName);
|
||||
_apiController = new ApiController(_configuration, _dalamudUtil, _fileDbManager);
|
||||
_periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configuration, _fileDbManager, _apiController, _dalamudUtil);
|
||||
|
||||
_uiSharedComponent =
|
||||
new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configuration, _dalamudUtil, _pluginInterface, _localization);
|
||||
_settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
|
||||
_compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
|
||||
|
||||
_introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configuration, _periodicFileScanner);
|
||||
_settingsUi.SwitchToIntroUi += () =>
|
||||
{
|
||||
Logger.Debug("Launching " + Name);
|
||||
_pluginInterface = pluginInterface;
|
||||
_commandManager = commandManager;
|
||||
_configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
_configuration.Initialize(_pluginInterface);
|
||||
_configuration.Migrate();
|
||||
|
||||
_localization = new Dalamud.Localization("MareSynchronos.Localization.", "", true);
|
||||
_localization.SetupWithLangCode("en");
|
||||
|
||||
_windowSystem = new WindowSystem("MareSynchronos");
|
||||
|
||||
// those can be initialized outside of game login
|
||||
_dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition);
|
||||
|
||||
_ipcManager = new IpcManager(_pluginInterface, _dalamudUtil);
|
||||
_fileDialogManager = new FileDialogManager();
|
||||
_fileDbManager = new FileCacheManager(_ipcManager, _configuration, _pluginInterface.ConfigDirectory.FullName);
|
||||
_apiController = new ApiController(_configuration, _dalamudUtil, _fileDbManager);
|
||||
_periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configuration, _fileDbManager, _apiController, _dalamudUtil);
|
||||
|
||||
_uiSharedComponent =
|
||||
new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configuration, _dalamudUtil, _pluginInterface, _localization);
|
||||
_settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
|
||||
_compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
|
||||
|
||||
_introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configuration, _periodicFileScanner);
|
||||
_settingsUi.SwitchToIntroUi += () =>
|
||||
{
|
||||
_introUi.IsOpen = true;
|
||||
_settingsUi.IsOpen = false;
|
||||
_compactUi.IsOpen = false;
|
||||
};
|
||||
_introUi.SwitchToMainUi += () =>
|
||||
{
|
||||
_introUi.IsOpen = false;
|
||||
_compactUi.IsOpen = true;
|
||||
_periodicFileScanner.StartScan();
|
||||
ReLaunchCharacterManager();
|
||||
};
|
||||
_compactUi.OpenSettingsUi += () =>
|
||||
{
|
||||
_settingsUi.Toggle();
|
||||
};
|
||||
_downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController, _uiSharedComponent);
|
||||
|
||||
|
||||
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
|
||||
|
||||
if (_dalamudUtil.IsLoggedIn)
|
||||
{
|
||||
DalamudUtilOnLogIn();
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => "Mare Synchronos";
|
||||
public void Dispose()
|
||||
_introUi.IsOpen = true;
|
||||
_settingsUi.IsOpen = false;
|
||||
_compactUi.IsOpen = false;
|
||||
};
|
||||
_introUi.SwitchToMainUi += () =>
|
||||
{
|
||||
Logger.Verbose("Disposing " + Name);
|
||||
_apiController?.Dispose();
|
||||
|
||||
_commandManager.RemoveHandler(CommandName);
|
||||
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
|
||||
|
||||
_uiSharedComponent.Dispose();
|
||||
_settingsUi?.Dispose();
|
||||
_introUi?.Dispose();
|
||||
_downloadUi?.Dispose();
|
||||
_compactUi?.Dispose();
|
||||
|
||||
_periodicFileScanner?.Dispose();
|
||||
_fileDbManager?.Dispose();
|
||||
_playerManager?.Dispose();
|
||||
_characterCacheManager?.Dispose();
|
||||
_ipcManager?.Dispose();
|
||||
_transientResourceManager?.Dispose();
|
||||
_dalamudUtil.Dispose();
|
||||
Logger.Debug("Shut down");
|
||||
}
|
||||
|
||||
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
Logger.Debug("Client login");
|
||||
|
||||
_pluginInterface.UiBuilder.Draw += Draw;
|
||||
_pluginInterface.UiBuilder.OpenConfigUi += OpenUi;
|
||||
_commandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
|
||||
{
|
||||
HelpMessage = "Opens the Mare Synchronos UI"
|
||||
});
|
||||
|
||||
if (!_configuration.HasValidSetup())
|
||||
{
|
||||
_introUi.IsOpen = true;
|
||||
_configuration.FullPause = false;
|
||||
_configuration.Save();
|
||||
return;
|
||||
}
|
||||
|
||||
_introUi.IsOpen = false;
|
||||
_compactUi.IsOpen = true;
|
||||
_periodicFileScanner.StartScan();
|
||||
ReLaunchCharacterManager();
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogOut()
|
||||
};
|
||||
_compactUi.OpenSettingsUi += () =>
|
||||
{
|
||||
Logger.Debug("Client logout");
|
||||
_characterCacheManager?.Dispose();
|
||||
_playerManager?.Dispose();
|
||||
_transientResourceManager?.Dispose();
|
||||
_pluginInterface.UiBuilder.Draw -= Draw;
|
||||
_pluginInterface.UiBuilder.OpenConfigUi -= OpenUi;
|
||||
_commandManager.RemoveHandler(CommandName);
|
||||
}
|
||||
_settingsUi.Toggle();
|
||||
};
|
||||
_downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController, _uiSharedComponent);
|
||||
|
||||
public void ReLaunchCharacterManager()
|
||||
|
||||
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
|
||||
|
||||
if (_dalamudUtil.IsLoggedIn)
|
||||
{
|
||||
_characterCacheManager?.Dispose();
|
||||
_playerManager?.Dispose();
|
||||
_transientResourceManager?.Dispose();
|
||||
|
||||
Task.Run(WaitForPlayerAndLaunchCharacterManager);
|
||||
}
|
||||
|
||||
private async Task WaitForPlayerAndLaunchCharacterManager()
|
||||
{
|
||||
while (!_dalamudUtil.IsPlayerPresent)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil);
|
||||
var characterCacheFactory =
|
||||
new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager, _fileDbManager);
|
||||
_playerManager = new PlayerManager(_apiController, _ipcManager,
|
||||
characterCacheFactory, _dalamudUtil, _transientResourceManager, _periodicFileScanner);
|
||||
_characterCacheManager = new OnlinePlayerManager(_apiController,
|
||||
_dalamudUtil, _ipcManager, _playerManager, _fileDbManager);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
_windowSystem.Draw();
|
||||
_fileDialogManager.Draw();
|
||||
}
|
||||
|
||||
private void OnCommand(string command, string args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(args))
|
||||
{
|
||||
OpenUi();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenUi()
|
||||
{
|
||||
if (_configuration.HasValidSetup())
|
||||
_compactUi.Toggle();
|
||||
else
|
||||
_introUi.Toggle();
|
||||
DalamudUtilOnLogIn();
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => "Mare Synchronos";
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + Name);
|
||||
_apiController?.Dispose();
|
||||
|
||||
_commandManager.RemoveHandler(CommandName);
|
||||
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
|
||||
|
||||
_uiSharedComponent.Dispose();
|
||||
_settingsUi?.Dispose();
|
||||
_introUi?.Dispose();
|
||||
_downloadUi?.Dispose();
|
||||
_compactUi?.Dispose();
|
||||
|
||||
_periodicFileScanner?.Dispose();
|
||||
_fileDbManager?.Dispose();
|
||||
_playerManager?.Dispose();
|
||||
_characterCacheManager?.Dispose();
|
||||
_ipcManager?.Dispose();
|
||||
_transientResourceManager?.Dispose();
|
||||
_dalamudUtil.Dispose();
|
||||
Logger.Debug("Shut down");
|
||||
}
|
||||
|
||||
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
Logger.Debug("Client login");
|
||||
|
||||
_pluginInterface.UiBuilder.Draw += Draw;
|
||||
_pluginInterface.UiBuilder.OpenConfigUi += OpenUi;
|
||||
_commandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
|
||||
{
|
||||
HelpMessage = "Opens the Mare Synchronos UI"
|
||||
});
|
||||
|
||||
if (!_configuration.HasValidSetup())
|
||||
{
|
||||
_introUi.IsOpen = true;
|
||||
_configuration.FullPause = false;
|
||||
_configuration.Save();
|
||||
return;
|
||||
}
|
||||
|
||||
_periodicFileScanner.StartScan();
|
||||
ReLaunchCharacterManager();
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogOut()
|
||||
{
|
||||
Logger.Debug("Client logout");
|
||||
_characterCacheManager?.Dispose();
|
||||
_playerManager?.Dispose();
|
||||
_transientResourceManager?.Dispose();
|
||||
_pluginInterface.UiBuilder.Draw -= Draw;
|
||||
_pluginInterface.UiBuilder.OpenConfigUi -= OpenUi;
|
||||
_commandManager.RemoveHandler(CommandName);
|
||||
}
|
||||
|
||||
public void ReLaunchCharacterManager()
|
||||
{
|
||||
_characterCacheManager?.Dispose();
|
||||
_playerManager?.Dispose();
|
||||
_transientResourceManager?.Dispose();
|
||||
|
||||
Task.Run(WaitForPlayerAndLaunchCharacterManager);
|
||||
}
|
||||
|
||||
private async Task WaitForPlayerAndLaunchCharacterManager()
|
||||
{
|
||||
while (!_dalamudUtil.IsPlayerPresent)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil);
|
||||
var characterCacheFactory =
|
||||
new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager, _fileDbManager);
|
||||
_playerManager = new PlayerManager(_apiController, _ipcManager,
|
||||
characterCacheFactory, _dalamudUtil, _transientResourceManager, _periodicFileScanner);
|
||||
_characterCacheManager = new OnlinePlayerManager(_apiController,
|
||||
_dalamudUtil, _ipcManager, _playerManager, _fileDbManager);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
_windowSystem.Draw();
|
||||
_fileDialogManager.Draw();
|
||||
}
|
||||
|
||||
private void OnCommand(string command, string args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(args))
|
||||
{
|
||||
OpenUi();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenUi()
|
||||
{
|
||||
if (_configuration.HasValidSetup())
|
||||
_compactUi.Toggle();
|
||||
else
|
||||
_introUi.Toggle();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,287 +12,286 @@ using MareSynchronos.Localization;
|
||||
using Dalamud.Utility;
|
||||
using MareSynchronos.FileCache;
|
||||
|
||||
namespace MareSynchronos.UI
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
internal class IntroUi : Window, IDisposable
|
||||
{
|
||||
internal class IntroUi : Window, IDisposable
|
||||
private readonly UiShared _uiShared;
|
||||
private readonly Configuration _pluginConfiguration;
|
||||
private readonly PeriodicFileScanner _fileCacheManager;
|
||||
private readonly WindowSystem _windowSystem;
|
||||
private bool _readFirstPage;
|
||||
|
||||
public event SwitchUi? SwitchToMainUi;
|
||||
|
||||
private string[] TosParagraphs;
|
||||
|
||||
private Tuple<string, string> _darkSoulsCaptcha1 = new(string.Empty, string.Empty);
|
||||
private Tuple<string, string> _darkSoulsCaptcha2 = new(string.Empty, string.Empty);
|
||||
private Tuple<string, string> _darkSoulsCaptcha3 = new(string.Empty, string.Empty);
|
||||
private string _enteredDarkSoulsCaptcha1 = string.Empty;
|
||||
private string _enteredDarkSoulsCaptcha2 = string.Empty;
|
||||
private string _enteredDarkSoulsCaptcha3 = string.Empty;
|
||||
|
||||
private bool _failedOnce = false;
|
||||
private Task _timeoutTask;
|
||||
private string _timeoutTime;
|
||||
|
||||
private Dictionary<string, string> _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
|
||||
private int _currentLanguage;
|
||||
|
||||
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim()
|
||||
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim()
|
||||
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim();
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
private readonly UiShared _uiShared;
|
||||
private readonly Configuration _pluginConfiguration;
|
||||
private readonly PeriodicFileScanner _fileCacheManager;
|
||||
private readonly WindowSystem _windowSystem;
|
||||
private bool _readFirstPage;
|
||||
Logger.Verbose("Disposing " + nameof(IntroUi));
|
||||
|
||||
public event SwitchUi? SwitchToMainUi;
|
||||
_windowSystem.RemoveWindow(this);
|
||||
}
|
||||
|
||||
private string[] TosParagraphs;
|
||||
public IntroUi(WindowSystem windowSystem, UiShared uiShared, Configuration pluginConfiguration,
|
||||
PeriodicFileScanner fileCacheManager) : base("Mare Synchronos Setup")
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(IntroUi));
|
||||
|
||||
private Tuple<string, string> _darkSoulsCaptcha1 = new(string.Empty, string.Empty);
|
||||
private Tuple<string, string> _darkSoulsCaptcha2 = new(string.Empty, string.Empty);
|
||||
private Tuple<string, string> _darkSoulsCaptcha3 = new(string.Empty, string.Empty);
|
||||
private string _enteredDarkSoulsCaptcha1 = string.Empty;
|
||||
private string _enteredDarkSoulsCaptcha2 = string.Empty;
|
||||
private string _enteredDarkSoulsCaptcha3 = string.Empty;
|
||||
_uiShared = uiShared;
|
||||
_pluginConfiguration = pluginConfiguration;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_windowSystem = windowSystem;
|
||||
|
||||
private bool _failedOnce = false;
|
||||
private Task _timeoutTask;
|
||||
private string _timeoutTime;
|
||||
|
||||
private Dictionary<string, string> _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
|
||||
private int _currentLanguage;
|
||||
|
||||
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim()
|
||||
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim()
|
||||
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim();
|
||||
|
||||
|
||||
public void Dispose()
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(IntroUi));
|
||||
MinimumSize = new Vector2(600, 400),
|
||||
MaximumSize = new Vector2(600, 2000)
|
||||
};
|
||||
|
||||
_windowSystem.RemoveWindow(this);
|
||||
}
|
||||
GetToSLocalization();
|
||||
|
||||
public IntroUi(WindowSystem windowSystem, UiShared uiShared, Configuration pluginConfiguration,
|
||||
PeriodicFileScanner fileCacheManager) : base("Mare Synchronos Setup")
|
||||
_windowSystem.AddWindow(this);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(IntroUi));
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("Welcome to Mare Synchronos");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
UiShared.TextWrapped("Mare Synchronos is a plugin that will replicate your full current character state including all Penumbra mods to other paired Mare Synchronos users. " +
|
||||
"Note that you will have to have Penumbra as well as Glamourer installed to use this plugin.");
|
||||
UiShared.TextWrapped("We will have to setup a few things first before you can start using this plugin. Click on next to continue.");
|
||||
|
||||
_uiShared = uiShared;
|
||||
_pluginConfiguration = pluginConfiguration;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_windowSystem = windowSystem;
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
UiShared.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " +
|
||||
"might look broken because of this or others players mods might not apply on your end altogether. " +
|
||||
"If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow);
|
||||
if (!_uiShared.DrawOtherPluginState()) return;
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Next##toAgreement"))
|
||||
{
|
||||
MinimumSize = new Vector2(600, 400),
|
||||
MaximumSize = new Vector2(600, 2000)
|
||||
};
|
||||
|
||||
GetToSLocalization();
|
||||
|
||||
_windowSystem.AddWindow(this);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage)
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("Welcome to Mare Synchronos");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
UiShared.TextWrapped("Mare Synchronos is a plugin that will replicate your full current character state including all Penumbra mods to other paired Mare Synchronos users. " +
|
||||
"Note that you will have to have Penumbra as well as Glamourer installed to use this plugin.");
|
||||
UiShared.TextWrapped("We will have to setup a few things first before you can start using this plugin. Click on next to continue.");
|
||||
|
||||
UiShared.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " +
|
||||
"might look broken because of this or others players mods might not apply on your end altogether. " +
|
||||
"If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow);
|
||||
if (!_uiShared.DrawOtherPluginState()) return;
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Next##toAgreement"))
|
||||
{
|
||||
_readFirstPage = true;
|
||||
}
|
||||
_readFirstPage = true;
|
||||
}
|
||||
else if (!_pluginConfiguration.AcceptedAgreement && _readFirstPage)
|
||||
}
|
||||
else if (!_pluginConfiguration.AcceptedAgreement && _readFirstPage)
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
var textSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel);
|
||||
ImGui.TextUnformatted(Strings.ToS.AgreementLabel);
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
|
||||
ImGui.SameLine();
|
||||
var languageSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel);
|
||||
ImGui.SetCursorPosX(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - languageSize.X - 80);
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - languageSize.Y / 2);
|
||||
|
||||
ImGui.TextUnformatted(Strings.ToS.LanguageLabel);
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - (languageSize.Y + ImGui.GetStyle().FramePadding.Y) / 2);
|
||||
ImGui.SetNextItemWidth(80);
|
||||
if (ImGui.Combo("", ref _currentLanguage, _languages.Keys.ToArray(), _languages.Count))
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
var textSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel);
|
||||
ImGui.TextUnformatted(Strings.ToS.AgreementLabel);
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
GetToSLocalization(_currentLanguage);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
var languageSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel);
|
||||
ImGui.SetCursorPosX(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - languageSize.X - 80);
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - languageSize.Y / 2);
|
||||
ImGui.Separator();
|
||||
ImGui.SetWindowFontScale(1.5f);
|
||||
string readThis = Strings.ToS.ReadLabel;
|
||||
textSize = ImGui.CalcTextSize(readThis);
|
||||
ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2);
|
||||
UiShared.ColorText(readThis, ImGuiColors.DalamudRed);
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
ImGui.Separator();
|
||||
|
||||
ImGui.TextUnformatted(Strings.ToS.LanguageLabel);
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - (languageSize.Y + ImGui.GetStyle().FramePadding.Y) / 2);
|
||||
ImGui.SetNextItemWidth(80);
|
||||
if (ImGui.Combo("", ref _currentLanguage, _languages.Keys.ToArray(), _languages.Count))
|
||||
|
||||
UiShared.TextWrapped(TosParagraphs[0]);
|
||||
UiShared.TextWrapped(TosParagraphs[1]);
|
||||
UiShared.TextWrapped(TosParagraphs[2]);
|
||||
UiShared.TextWrapped(TosParagraphs[3]);
|
||||
UiShared.TextWrapped(TosParagraphs[4]);
|
||||
UiShared.TextWrapped(TosParagraphs[5]);
|
||||
|
||||
ImGui.Separator();
|
||||
if ((!_pluginConfiguration.DarkSoulsAgreement || DarkSoulsCaptchaValid) && (_timeoutTask?.IsCompleted ?? true))
|
||||
{
|
||||
if (ImGui.Button(Strings.ToS.AgreeLabel + "##toSetup"))
|
||||
{
|
||||
GetToSLocalization(_currentLanguage);
|
||||
}
|
||||
_enteredDarkSoulsCaptcha1 = string.Empty;
|
||||
_enteredDarkSoulsCaptcha2 = string.Empty;
|
||||
_enteredDarkSoulsCaptcha3 = string.Empty;
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.SetWindowFontScale(1.5f);
|
||||
string readThis = Strings.ToS.ReadLabel;
|
||||
textSize = ImGui.CalcTextSize(readThis);
|
||||
ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2);
|
||||
UiShared.ColorText(readThis, ImGuiColors.DalamudRed);
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
ImGui.Separator();
|
||||
|
||||
|
||||
UiShared.TextWrapped(TosParagraphs[0]);
|
||||
UiShared.TextWrapped(TosParagraphs[1]);
|
||||
UiShared.TextWrapped(TosParagraphs[2]);
|
||||
UiShared.TextWrapped(TosParagraphs[3]);
|
||||
UiShared.TextWrapped(TosParagraphs[4]);
|
||||
UiShared.TextWrapped(TosParagraphs[5]);
|
||||
|
||||
ImGui.Separator();
|
||||
if ((!_pluginConfiguration.DarkSoulsAgreement || DarkSoulsCaptchaValid) && (_timeoutTask?.IsCompleted ?? true))
|
||||
{
|
||||
if (ImGui.Button(Strings.ToS.AgreeLabel + "##toSetup"))
|
||||
if (UiShared.CtrlPressed())
|
||||
{
|
||||
_enteredDarkSoulsCaptcha1 = string.Empty;
|
||||
_enteredDarkSoulsCaptcha2 = string.Empty;
|
||||
_enteredDarkSoulsCaptcha3 = string.Empty;
|
||||
|
||||
if (UiShared.CtrlPressed())
|
||||
{
|
||||
_pluginConfiguration.AcceptedAgreement = true;
|
||||
_pluginConfiguration.Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_failedOnce)
|
||||
{
|
||||
_failedOnce = true;
|
||||
_timeoutTask = Task.Run(async () =>
|
||||
{
|
||||
for (int i = 60; i > 0; i--)
|
||||
{
|
||||
_timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel;
|
||||
Logger.Debug(_timeoutTime);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_pluginConfiguration.DarkSoulsAgreement = true;
|
||||
_pluginConfiguration.Save();
|
||||
GenerateDarkSoulsAgreementCaptcha();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_failedOnce && (!_timeoutTask?.IsCompleted ?? true))
|
||||
{
|
||||
UiShared.ColorTextWrapped(Strings.ToS.FailedLabel, ImGuiColors.DalamudYellow);
|
||||
UiShared.TextWrapped(Strings.ToS.TimeoutLabel);
|
||||
UiShared.TextWrapped(_timeoutTime);
|
||||
_pluginConfiguration.AcceptedAgreement = true;
|
||||
_pluginConfiguration.Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
UiShared.ColorTextWrapped(Strings.ToS.FailedAgainLabel, ImGuiColors.DalamudYellow);
|
||||
UiShared.TextWrapped(Strings.ToS.PuzzleLabel);
|
||||
UiShared.TextWrapped(Strings.ToS.PuzzleDescLabel);
|
||||
ImGui.SetNextItemWidth(100);
|
||||
ImGui.InputText(_darkSoulsCaptcha1.Item1, ref _enteredDarkSoulsCaptcha1, 255);
|
||||
ImGui.SetNextItemWidth(100);
|
||||
ImGui.InputText(_darkSoulsCaptcha2.Item1, ref _enteredDarkSoulsCaptcha2, 255);
|
||||
ImGui.SetNextItemWidth(100);
|
||||
ImGui.InputText(_darkSoulsCaptcha3.Item1, ref _enteredDarkSoulsCaptcha3, 255);
|
||||
if (!_failedOnce)
|
||||
{
|
||||
_failedOnce = true;
|
||||
_timeoutTask = Task.Run(async () =>
|
||||
{
|
||||
for (int i = 60; i > 0; i--)
|
||||
{
|
||||
_timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel;
|
||||
Logger.Debug(_timeoutTime);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_pluginConfiguration.DarkSoulsAgreement = true;
|
||||
_pluginConfiguration.Save();
|
||||
GenerateDarkSoulsAgreementCaptcha();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_pluginConfiguration.AcceptedAgreement
|
||||
&& (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder)
|
||||
|| _pluginConfiguration.InitialScanComplete == false
|
||||
|| !Directory.Exists(_pluginConfiguration.CacheFolder)))
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("File Cache Setup");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
|
||||
if (!_uiShared.HasValidPenumbraModPath)
|
||||
{
|
||||
UiShared.ColorTextWrapped("You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory.", ImGuiColors.DalamudRed);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiShared.TextWrapped("To not unnecessary download files already present on your computer, Mare Synchronos will have to scan your Penumbra mod directory. " +
|
||||
"Additionally, a local cache folder must be set where Mare Synchronos will download its local file cache to. " +
|
||||
"Once the Cache Folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
||||
UiShared.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");
|
||||
UiShared.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.db of Mare Synchronos in the Plugin Configurations folder of Dalamud. " +
|
||||
"Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow);
|
||||
_uiShared.DrawCacheDirectorySetting();
|
||||
}
|
||||
|
||||
if (!_fileCacheManager.IsScanRunning && !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_pluginConfiguration.CacheFolder))
|
||||
{
|
||||
if (ImGui.Button("Start Scan##startScan"))
|
||||
{
|
||||
_fileCacheManager.InvokeScan(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_uiShared.DrawFileScanState();
|
||||
}
|
||||
}
|
||||
else if (!_uiShared.ApiController.ServerAlive)
|
||||
{
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("Service Registration");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
UiShared.TextWrapped("To be able to use Mare Synchronos you will have to register an account.");
|
||||
UiShared.TextWrapped("For the official Mare Synchronos Servers the account creation will be handled on the official Mare Synchronos Discord. Due to security risks for the server, there is no way to handle this senisibly otherwise.");
|
||||
UiShared.TextWrapped("If you want to register at the main server \"" + WebAPI.ApiController.MainServer + "\" join the Discord and follow the instructions as described in #registration.");
|
||||
|
||||
if (ImGui.Button("Join the Mare Synchronos Discord"))
|
||||
{
|
||||
Util.OpenLink("https://discord.gg/mpNdkrTRjW");
|
||||
}
|
||||
|
||||
UiShared.TextWrapped("For all other non official services you will have to contact the appropriate service provider how to obtain a secret key.");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
UiShared.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below.");
|
||||
|
||||
_uiShared.DrawServiceSelection(() => { });
|
||||
}
|
||||
else
|
||||
{
|
||||
SwitchToMainUi?.Invoke();
|
||||
IsOpen = false;
|
||||
if (_failedOnce && (!_timeoutTask?.IsCompleted ?? true))
|
||||
{
|
||||
UiShared.ColorTextWrapped(Strings.ToS.FailedLabel, ImGuiColors.DalamudYellow);
|
||||
UiShared.TextWrapped(Strings.ToS.TimeoutLabel);
|
||||
UiShared.TextWrapped(_timeoutTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
UiShared.ColorTextWrapped(Strings.ToS.FailedAgainLabel, ImGuiColors.DalamudYellow);
|
||||
UiShared.TextWrapped(Strings.ToS.PuzzleLabel);
|
||||
UiShared.TextWrapped(Strings.ToS.PuzzleDescLabel);
|
||||
ImGui.SetNextItemWidth(100);
|
||||
ImGui.InputText(_darkSoulsCaptcha1.Item1, ref _enteredDarkSoulsCaptcha1, 255);
|
||||
ImGui.SetNextItemWidth(100);
|
||||
ImGui.InputText(_darkSoulsCaptcha2.Item1, ref _enteredDarkSoulsCaptcha2, 255);
|
||||
ImGui.SetNextItemWidth(100);
|
||||
ImGui.InputText(_darkSoulsCaptcha3.Item1, ref _enteredDarkSoulsCaptcha3, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _secretKey = string.Empty;
|
||||
|
||||
private void GetToSLocalization(int changeLanguageTo = -1)
|
||||
else if (_pluginConfiguration.AcceptedAgreement
|
||||
&& (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder)
|
||||
|| _pluginConfiguration.InitialScanComplete == false
|
||||
|| !Directory.Exists(_pluginConfiguration.CacheFolder)))
|
||||
{
|
||||
if (changeLanguageTo != -1)
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("File Cache Setup");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
|
||||
if (!_uiShared.HasValidPenumbraModPath)
|
||||
{
|
||||
_uiShared.LoadLocalization(_languages.ElementAt(changeLanguageTo).Value);
|
||||
UiShared.ColorTextWrapped("You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory.", ImGuiColors.DalamudRed);
|
||||
}
|
||||
|
||||
TosParagraphs = new[] { Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6 };
|
||||
|
||||
if (_pluginConfiguration.DarkSoulsAgreement)
|
||||
else
|
||||
{
|
||||
GenerateDarkSoulsAgreementCaptcha();
|
||||
UiShared.TextWrapped("To not unnecessary download files already present on your computer, Mare Synchronos will have to scan your Penumbra mod directory. " +
|
||||
"Additionally, a local cache folder must be set where Mare Synchronos will download its local file cache to. " +
|
||||
"Once the Cache Folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
||||
UiShared.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");
|
||||
UiShared.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.db of Mare Synchronos in the Plugin Configurations folder of Dalamud. " +
|
||||
"Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow);
|
||||
_uiShared.DrawCacheDirectorySetting();
|
||||
}
|
||||
|
||||
if (!_fileCacheManager.IsScanRunning && !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_pluginConfiguration.CacheFolder))
|
||||
{
|
||||
if (ImGui.Button("Start Scan##startScan"))
|
||||
{
|
||||
_fileCacheManager.InvokeScan(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_uiShared.DrawFileScanState();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateDarkSoulsAgreementCaptcha()
|
||||
else if (!_uiShared.ApiController.ServerAlive)
|
||||
{
|
||||
_darkSoulsCaptcha1 = GetCaptchaTuple();
|
||||
_darkSoulsCaptcha2 = GetCaptchaTuple();
|
||||
_darkSoulsCaptcha3 = GetCaptchaTuple();
|
||||
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
|
||||
ImGui.TextUnformatted("Service Registration");
|
||||
if (_uiShared.UidFontBuilt) ImGui.PopFont();
|
||||
ImGui.Separator();
|
||||
UiShared.TextWrapped("To be able to use Mare Synchronos you will have to register an account.");
|
||||
UiShared.TextWrapped("For the official Mare Synchronos Servers the account creation will be handled on the official Mare Synchronos Discord. Due to security risks for the server, there is no way to handle this senisibly otherwise.");
|
||||
UiShared.TextWrapped("If you want to register at the main server \"" + WebAPI.ApiController.MainServer + "\" join the Discord and follow the instructions as described in #registration.");
|
||||
|
||||
if (ImGui.Button("Join the Mare Synchronos Discord"))
|
||||
{
|
||||
Util.OpenLink("https://discord.gg/mpNdkrTRjW");
|
||||
}
|
||||
|
||||
UiShared.TextWrapped("For all other non official services you will have to contact the appropriate service provider how to obtain a secret key.");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
UiShared.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below.");
|
||||
|
||||
_uiShared.DrawServiceSelection(() => { });
|
||||
}
|
||||
|
||||
private Tuple<string, string> GetCaptchaTuple()
|
||||
else
|
||||
{
|
||||
Random random = new Random();
|
||||
var paragraphIdx = random.Next(TosParagraphs.Length);
|
||||
var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
|
||||
var sentenceIdx = random.Next(splitParagraph.Length);
|
||||
var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "").Replace(",", "").Replace("'", "")).ToArray();
|
||||
var wordIdx = random.Next(splitSentence.Length);
|
||||
return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]);
|
||||
SwitchToMainUi?.Invoke();
|
||||
IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string _secretKey = string.Empty;
|
||||
|
||||
private void GetToSLocalization(int changeLanguageTo = -1)
|
||||
{
|
||||
if (changeLanguageTo != -1)
|
||||
{
|
||||
_uiShared.LoadLocalization(_languages.ElementAt(changeLanguageTo).Value);
|
||||
}
|
||||
|
||||
TosParagraphs = new[] { Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6 };
|
||||
|
||||
if (_pluginConfiguration.DarkSoulsAgreement)
|
||||
{
|
||||
GenerateDarkSoulsAgreementCaptcha();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateDarkSoulsAgreementCaptcha()
|
||||
{
|
||||
_darkSoulsCaptcha1 = GetCaptchaTuple();
|
||||
_darkSoulsCaptcha2 = GetCaptchaTuple();
|
||||
_darkSoulsCaptcha3 = GetCaptchaTuple();
|
||||
}
|
||||
|
||||
private Tuple<string, string> GetCaptchaTuple()
|
||||
{
|
||||
Random random = new Random();
|
||||
var paragraphIdx = random.Next(TosParagraphs.Length);
|
||||
var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
|
||||
var sentenceIdx = random.Next(splitParagraph.Length);
|
||||
var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "").Replace(",", "").Replace("'", "")).ToArray();
|
||||
var wordIdx = random.Next(splitSentence.Length);
|
||||
return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,32 +4,31 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
||||
namespace MareSynchronos.Utils
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public class Crypto
|
||||
{
|
||||
public class Crypto
|
||||
public static string GetFileHash(string filePath)
|
||||
{
|
||||
public static string GetFileHash(string filePath)
|
||||
{
|
||||
using SHA1CryptoServiceProvider cryptoProvider = new();
|
||||
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "");
|
||||
}
|
||||
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("-", "");
|
||||
}
|
||||
public static string GetHash(string stringToHash)
|
||||
{
|
||||
using SHA1CryptoServiceProvider cryptoProvider = new();
|
||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
|
||||
}
|
||||
|
||||
public static string GetHash256(string stringToHash)
|
||||
{
|
||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
|
||||
}
|
||||
public static string GetHash256(string stringToHash)
|
||||
{
|
||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
|
||||
}
|
||||
|
||||
public static string GetHash256(PlayerCharacter character)
|
||||
{
|
||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "");
|
||||
}
|
||||
public static string GetHash256(PlayerCharacter character)
|
||||
{
|
||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,232 +11,231 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
|
||||
namespace MareSynchronos.Utils
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
|
||||
|
||||
public delegate void LogIn();
|
||||
public delegate void LogOut();
|
||||
public delegate void ClassJobChanged();
|
||||
|
||||
public delegate void FrameworkUpdate();
|
||||
public delegate void VoidDelegate();
|
||||
|
||||
public class DalamudUtil : IDisposable
|
||||
{
|
||||
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objectTable;
|
||||
private readonly Framework _framework;
|
||||
private readonly Condition _condition;
|
||||
|
||||
public delegate void LogIn();
|
||||
public delegate void LogOut();
|
||||
public delegate void ClassJobChanged();
|
||||
public event LogIn? LogIn;
|
||||
public event LogOut? LogOut;
|
||||
public event FrameworkUpdate? FrameworkUpdate;
|
||||
public event ClassJobChanged? ClassJobChanged;
|
||||
private uint? classJobId = 0;
|
||||
public event FrameworkUpdate? DelayedFrameworkUpdate;
|
||||
public event VoidDelegate? ZoneSwitchStart;
|
||||
public event VoidDelegate? ZoneSwitchEnd;
|
||||
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
|
||||
private bool _sentBetweenAreas = false;
|
||||
|
||||
public delegate void FrameworkUpdate();
|
||||
public delegate void VoidDelegate();
|
||||
|
||||
public class DalamudUtil : IDisposable
|
||||
public unsafe bool IsGameObjectPresent(IntPtr key)
|
||||
{
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objectTable;
|
||||
private readonly Framework _framework;
|
||||
private readonly Condition _condition;
|
||||
|
||||
public event LogIn? LogIn;
|
||||
public event LogOut? LogOut;
|
||||
public event FrameworkUpdate? FrameworkUpdate;
|
||||
public event ClassJobChanged? ClassJobChanged;
|
||||
private uint? classJobId = 0;
|
||||
public event FrameworkUpdate? DelayedFrameworkUpdate;
|
||||
public event VoidDelegate? ZoneSwitchStart;
|
||||
public event VoidDelegate? ZoneSwitchEnd;
|
||||
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
|
||||
private bool _sentBetweenAreas = false;
|
||||
|
||||
public unsafe bool IsGameObjectPresent(IntPtr key)
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
foreach (var obj in _objectTable)
|
||||
if (obj.Address == key)
|
||||
{
|
||||
if (obj.Address == key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition)
|
||||
{
|
||||
_clientState = clientState;
|
||||
_objectTable = objectTable;
|
||||
_framework = framework;
|
||||
_condition = condition;
|
||||
_clientState.Login += ClientStateOnLogin;
|
||||
_clientState.Logout += ClientStateOnLogout;
|
||||
_framework.Update += FrameworkOnUpdate;
|
||||
if (IsLoggedIn)
|
||||
{
|
||||
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
|
||||
ClientStateOnLogin(null, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(Framework framework)
|
||||
{
|
||||
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
|
||||
{
|
||||
if (!_sentBetweenAreas)
|
||||
{
|
||||
Logger.Debug("Zone switch/Gpose start");
|
||||
_sentBetweenAreas = true;
|
||||
ZoneSwitchStart?.Invoke();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else if (_sentBetweenAreas)
|
||||
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition)
|
||||
{
|
||||
_clientState = clientState;
|
||||
_objectTable = objectTable;
|
||||
_framework = framework;
|
||||
_condition = condition;
|
||||
_clientState.Login += ClientStateOnLogin;
|
||||
_clientState.Logout += ClientStateOnLogout;
|
||||
_framework.Update += FrameworkOnUpdate;
|
||||
if (IsLoggedIn)
|
||||
{
|
||||
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
|
||||
ClientStateOnLogin(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(Framework framework)
|
||||
{
|
||||
if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
|
||||
{
|
||||
if (!_sentBetweenAreas)
|
||||
{
|
||||
Logger.Debug("Zone switch/Gpose end");
|
||||
_sentBetweenAreas = false;
|
||||
ZoneSwitchEnd?.Invoke();
|
||||
Logger.Debug("Zone switch/Gpose start");
|
||||
_sentBetweenAreas = true;
|
||||
ZoneSwitchStart?.Invoke();
|
||||
}
|
||||
|
||||
foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
|
||||
return;
|
||||
}
|
||||
else if (_sentBetweenAreas)
|
||||
{
|
||||
Logger.Debug("Zone switch/Gpose end");
|
||||
_sentBetweenAreas = false;
|
||||
ZoneSwitchEnd?.Invoke();
|
||||
}
|
||||
|
||||
foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
frameworkInvocation?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
frameworkInvocation?.Invoke();
|
||||
}
|
||||
|
||||
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
|
||||
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
|
||||
catch (Exception ex)
|
||||
{
|
||||
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
|
||||
|
||||
if (classJobId != newclassJobId)
|
||||
{
|
||||
classJobId = newclassJobId;
|
||||
ClassJobChanged?.Invoke();
|
||||
}
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
|
||||
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
|
||||
if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid())
|
||||
{
|
||||
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
|
||||
|
||||
if (classJobId != newclassJobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
frameworkInvocation?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
classJobId = newclassJobId;
|
||||
ClassJobChanged?.Invoke();
|
||||
}
|
||||
_delayedFrameworkUpdateCheck = DateTime.Now;
|
||||
}
|
||||
|
||||
private void ClientStateOnLogout(object? sender, EventArgs e)
|
||||
foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
|
||||
{
|
||||
LogOut?.Invoke();
|
||||
}
|
||||
|
||||
private void ClientStateOnLogin(object? sender, EventArgs e)
|
||||
{
|
||||
LogIn?.Invoke();
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
|
||||
{
|
||||
return _objectTable.CreateObjectReference(reference);
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => _clientState.IsLoggedIn;
|
||||
|
||||
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
|
||||
|
||||
public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
|
||||
{
|
||||
return obj != null && obj.IsValid();
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetMinion()
|
||||
{
|
||||
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
|
||||
{
|
||||
var mgr = CharacterManager.Instance();
|
||||
if (playerPointer == null) playerPointer = PlayerPointer;
|
||||
return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
|
||||
{
|
||||
var mgr = CharacterManager.Instance();
|
||||
if (playerPointer == null) playerPointer = PlayerPointer;
|
||||
return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
|
||||
|
||||
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
|
||||
|
||||
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
|
||||
|
||||
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
|
||||
|
||||
public bool IsInGpose => _objectTable[201] != null;
|
||||
|
||||
public List<PlayerCharacter> GetPlayerCharacters()
|
||||
{
|
||||
return _objectTable.Where(obj =>
|
||||
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
|
||||
obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList();
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
|
||||
{
|
||||
var objTableObj = _objectTable[index];
|
||||
if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
|
||||
return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj;
|
||||
}
|
||||
|
||||
public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
|
||||
{
|
||||
foreach (var item in _objectTable)
|
||||
try
|
||||
{
|
||||
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
|
||||
if (item.Name.ToString() == characterName) return (PlayerCharacter)item;
|
||||
frameworkInvocation?.Invoke();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
|
||||
{
|
||||
return await _framework.RunOnFrameworkThread(func);
|
||||
}
|
||||
|
||||
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
|
||||
{
|
||||
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
|
||||
|
||||
var obj = (GameObject*)characterAddress;
|
||||
const int tick = 250;
|
||||
int curWaitTime = 0;
|
||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Verbose($"Waiting for {name} to finish drawing");
|
||||
curWaitTime += tick;
|
||||
Thread.Sleep(tick);
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
}
|
||||
_delayedFrameworkUpdateCheck = DateTime.Now;
|
||||
}
|
||||
|
||||
if (ct?.IsCancellationRequested ?? false) return;
|
||||
// wait quarter a second just in case
|
||||
private void ClientStateOnLogout(object? sender, EventArgs e)
|
||||
{
|
||||
LogOut?.Invoke();
|
||||
}
|
||||
|
||||
private void ClientStateOnLogin(object? sender, EventArgs e)
|
||||
{
|
||||
LogIn?.Invoke();
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference)
|
||||
{
|
||||
return _objectTable.CreateObjectReference(reference);
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => _clientState.IsLoggedIn;
|
||||
|
||||
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
|
||||
|
||||
public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
|
||||
{
|
||||
return obj != null && obj.IsValid();
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetMinion()
|
||||
{
|
||||
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
|
||||
{
|
||||
var mgr = CharacterManager.Instance();
|
||||
if (playerPointer == null) playerPointer = PlayerPointer;
|
||||
return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
|
||||
{
|
||||
var mgr = CharacterManager.Instance();
|
||||
if (playerPointer == null) playerPointer = PlayerPointer;
|
||||
return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
|
||||
|
||||
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
|
||||
|
||||
public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
|
||||
|
||||
public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id);
|
||||
|
||||
public bool IsInGpose => _objectTable[201] != null;
|
||||
|
||||
public List<PlayerCharacter> GetPlayerCharacters()
|
||||
{
|
||||
return _objectTable.Where(obj =>
|
||||
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
|
||||
obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList();
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
|
||||
{
|
||||
var objTableObj = _objectTable[index];
|
||||
if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
|
||||
return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj;
|
||||
}
|
||||
|
||||
public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
|
||||
{
|
||||
foreach (var item in _objectTable)
|
||||
{
|
||||
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
|
||||
if (item.Name.ToString() == characterName) return (PlayerCharacter)item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
|
||||
{
|
||||
return await _framework.RunOnFrameworkThread(func);
|
||||
}
|
||||
|
||||
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
|
||||
{
|
||||
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
|
||||
|
||||
var obj = (GameObject*)characterAddress;
|
||||
const int tick = 250;
|
||||
int curWaitTime = 0;
|
||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
|
||||
{
|
||||
Logger.Verbose($"Waiting for {name} to finish drawing");
|
||||
curWaitTime += tick;
|
||||
Thread.Sleep(tick);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientState.Login -= ClientStateOnLogin;
|
||||
_clientState.Logout -= ClientStateOnLogout;
|
||||
_framework.Update -= FrameworkOnUpdate;
|
||||
}
|
||||
if (ct?.IsCancellationRequested ?? false) return;
|
||||
// wait quarter a second just in case
|
||||
Thread.Sleep(tick);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clientState.Login -= ClientStateOnLogin;
|
||||
_clientState.Logout -= ClientStateOnLogout;
|
||||
_framework.Update -= FrameworkOnUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,108 +5,107 @@ using Dalamud.Logging;
|
||||
using Dalamud.Utility;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Utils
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
[ProviderAlias("Dalamud")]
|
||||
public class DalamudLoggingProvider : ILoggerProvider
|
||||
{
|
||||
[ProviderAlias("Dalamud")]
|
||||
public class DalamudLoggingProvider : ILoggerProvider
|
||||
private readonly ConcurrentDictionary<string, Logger> _loggers =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public DalamudLoggingProvider()
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Logger> _loggers =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public DalamudLoggingProvider()
|
||||
{
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loggers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Logger : ILogger
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
private readonly string name;
|
||||
return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName));
|
||||
}
|
||||
|
||||
public static void Info(string info)
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
PluginLog.Information($"[{caller}] {info}");
|
||||
}
|
||||
|
||||
public static void Debug(string debug, string stringToHighlight = "")
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty())
|
||||
{
|
||||
PluginLog.Warning($"[{caller}] {debug}");
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug($"[{caller}] {debug}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Warn(string warn)
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
PluginLog.Warning($"[{caller}] {warn}");
|
||||
}
|
||||
|
||||
public static void Verbose(string verbose)
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
#if DEBUG
|
||||
PluginLog.Debug($"[{caller}] {verbose}");
|
||||
#else
|
||||
PluginLog.Verbose($"[{caller}] {verbose}");
|
||||
#endif
|
||||
}
|
||||
|
||||
public Logger(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel)) return;
|
||||
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Debug:
|
||||
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
case LogLevel.Critical:
|
||||
PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Information:
|
||||
PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Trace:
|
||||
default:
|
||||
#if DEBUG
|
||||
PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
#else
|
||||
PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => default!;
|
||||
public void Dispose()
|
||||
{
|
||||
_loggers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Logger : ILogger
|
||||
{
|
||||
private readonly string name;
|
||||
|
||||
public static void Info(string info)
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
PluginLog.Information($"[{caller}] {info}");
|
||||
}
|
||||
|
||||
public static void Debug(string debug, string stringToHighlight = "")
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty())
|
||||
{
|
||||
PluginLog.Warning($"[{caller}] {debug}");
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug($"[{caller}] {debug}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Warn(string warn)
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
PluginLog.Warning($"[{caller}] {warn}");
|
||||
}
|
||||
|
||||
public static void Verbose(string verbose)
|
||||
{
|
||||
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
|
||||
#if DEBUG
|
||||
PluginLog.Debug($"[{caller}] {verbose}");
|
||||
#else
|
||||
PluginLog.Verbose($"[{caller}] {verbose}");
|
||||
#endif
|
||||
}
|
||||
|
||||
public Logger(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
if (!IsEnabled(logLevel)) return;
|
||||
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Debug:
|
||||
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
case LogLevel.Critical:
|
||||
PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Information:
|
||||
PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
break;
|
||||
case LogLevel.Trace:
|
||||
default:
|
||||
#if DEBUG
|
||||
PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}");
|
||||
#else
|
||||
PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => default!;
|
||||
}
|
||||
|
||||
@@ -6,27 +6,26 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Utils
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public static class VariousExtensions
|
||||
{
|
||||
public static class VariousExtensions
|
||||
public static DateTime GetLinkerTime(Assembly assembly)
|
||||
{
|
||||
public static DateTime GetLinkerTime(Assembly assembly)
|
||||
const string BuildVersionMetadataPrefix = "+build";
|
||||
|
||||
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
if (attribute?.InformationalVersion != null)
|
||||
{
|
||||
const string BuildVersionMetadataPrefix = "+build";
|
||||
|
||||
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
if (attribute?.InformationalVersion != null)
|
||||
var value = attribute.InformationalVersion;
|
||||
var index = value.IndexOf(BuildVersionMetadataPrefix);
|
||||
if (index > 0)
|
||||
{
|
||||
var value = attribute.InformationalVersion;
|
||||
var index = value.IndexOf(BuildVersionMetadataPrefix);
|
||||
if (index > 0)
|
||||
{
|
||||
value = value[(index + BuildVersionMetadataPrefix.Length)..];
|
||||
return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture);
|
||||
}
|
||||
value = value[(index + BuildVersionMetadataPrefix.Length)..];
|
||||
return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,306 +13,305 @@ using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Utils;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace MareSynchronos.WebAPI
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public partial class ApiController
|
||||
{
|
||||
public partial class ApiController
|
||||
private readonly HashSet<string> _verifiedUploadedHashes;
|
||||
|
||||
private int _downloadId = 0;
|
||||
public void CancelUpload()
|
||||
{
|
||||
private readonly HashSet<string> _verifiedUploadedHashes;
|
||||
|
||||
private int _downloadId = 0;
|
||||
public void CancelUpload()
|
||||
if (_uploadCancellationTokenSource != null)
|
||||
{
|
||||
if (_uploadCancellationTokenSource != null)
|
||||
{
|
||||
Logger.Debug("Cancelling upload");
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
_mareHub!.SendAsync(Api.SendFileAbortUpload);
|
||||
CurrentUploads.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAllMyFiles()
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
|
||||
}
|
||||
|
||||
private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
|
||||
{
|
||||
using WebClient wc = new();
|
||||
wc.Headers.Add("Authorization", SecretKey);
|
||||
DownloadProgressChangedEventHandler progChanged = (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Could not set download progress for " + hash);
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
};
|
||||
wc.DownloadProgressChanged += progChanged;
|
||||
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
ct.Register(wc.CancelAsync);
|
||||
|
||||
try
|
||||
{
|
||||
await wc.DownloadFileTaskAsync(downloadUri, fileName);
|
||||
}
|
||||
catch { }
|
||||
|
||||
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total;
|
||||
|
||||
wc.DownloadProgressChanged -= progChanged;
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public int GetDownloadId() => _downloadId++;
|
||||
|
||||
public async Task DownloadFiles(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
|
||||
{
|
||||
DownloadStarted?.Invoke();
|
||||
try
|
||||
{
|
||||
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CancelDownload(currentDownloadId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DownloadFinished?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFilesInternal(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
|
||||
{
|
||||
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
|
||||
|
||||
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
|
||||
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
|
||||
|
||||
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
|
||||
|
||||
CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
|
||||
.Where(d => d.CanBeTransferred).ToList();
|
||||
|
||||
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
|
||||
{
|
||||
if (ForbiddenTransfers.All(f => f.Hash != dto.Hash))
|
||||
{
|
||||
ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
|
||||
}
|
||||
}
|
||||
|
||||
await Parallel.ForEachAsync(CurrentDownloads[currentDownloadId].Where(f => f.CanBeTransferred), new ParallelOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = 5,
|
||||
CancellationToken = ct
|
||||
},
|
||||
async (file, token) =>
|
||||
{
|
||||
var hash = file.Hash;
|
||||
var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
Logger.Debug("Detected cancellation, removing " + currentDownloadId);
|
||||
DownloadFinished?.Invoke();
|
||||
CancelDownload(currentDownloadId);
|
||||
return;
|
||||
}
|
||||
|
||||
var tempFileData = await File.ReadAllBytesAsync(tempFile, token);
|
||||
var extractedFile = LZ4Codec.Unwrap(tempFileData);
|
||||
File.Delete(tempFile);
|
||||
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash);
|
||||
await File.WriteAllBytesAsync(filePath, extractedFile, token);
|
||||
var fi = new FileInfo(filePath);
|
||||
Func<DateTime> RandomDayFunc()
|
||||
{
|
||||
DateTime start = new DateTime(1995, 1, 1);
|
||||
Random gen = new Random();
|
||||
int range = (DateTime.Today - start).Days;
|
||||
return () => start.AddDays(gen.Next(range));
|
||||
}
|
||||
|
||||
fi.CreationTime = RandomDayFunc().Invoke();
|
||||
fi.LastAccessTime = RandomDayFunc().Invoke();
|
||||
fi.LastWriteTime = RandomDayFunc().Invoke();
|
||||
try
|
||||
{
|
||||
_ = _fileDbManager.CreateCacheEntry(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Issue adding file to the DB");
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace);
|
||||
}
|
||||
});
|
||||
|
||||
Logger.Debug("Download complete, removing " + currentDownloadId);
|
||||
CancelDownload(currentDownloadId);
|
||||
}
|
||||
|
||||
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
Logger.Debug("Sending Character data to service " + ApiUri);
|
||||
|
||||
CancelUpload();
|
||||
_uploadCancellationTokenSource = new CancellationTokenSource();
|
||||
var uploadToken = _uploadCancellationTokenSource.Token;
|
||||
Logger.Verbose("New Token Created");
|
||||
|
||||
List<string> unverifiedUploadHashes = new();
|
||||
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList())
|
||||
{
|
||||
if (!_verifiedUploadedHashes.Contains(item))
|
||||
{
|
||||
unverifiedUploadHashes.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (unverifiedUploadHashes.Any())
|
||||
{
|
||||
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files");
|
||||
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken);
|
||||
|
||||
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentUploads.Add(new UploadFileTransfer(file)
|
||||
{
|
||||
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Tried to request file " + file.Hash + " but file was not present");
|
||||
Logger.Warn(ex.StackTrace!);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in filesToUpload.Where(c => c.IsForbidden))
|
||||
{
|
||||
if (ForbiddenTransfers.All(f => f.Hash != file.Hash))
|
||||
{
|
||||
ForbiddenTransfers.Add(new UploadFileTransfer(file)
|
||||
{
|
||||
LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var totalSize = CurrentUploads.Sum(c => c.Total);
|
||||
Logger.Debug("Compressing and uploading files");
|
||||
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
|
||||
{
|
||||
Logger.Debug("Compressing and uploading " + file);
|
||||
var data = await GetCompressedFileData(file.Hash, uploadToken);
|
||||
CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length;
|
||||
await UploadFile(data.Item2, file.Hash, uploadToken);
|
||||
if (!uploadToken.IsCancellationRequested) continue;
|
||||
Logger.Warn("Cancel in filesToUpload loop detected");
|
||||
CurrentUploads.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentUploads.Any())
|
||||
{
|
||||
var compressedSize = CurrentUploads.Sum(c => c.Total);
|
||||
Logger.Debug($"Compressed {totalSize} to {compressedSize} ({(compressedSize / (double)totalSize):P2})");
|
||||
}
|
||||
|
||||
Logger.Debug("Upload tasks complete, waiting for server to confirm");
|
||||
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
|
||||
Logger.Debug("Uploads open: " + anyUploadsOpen);
|
||||
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
|
||||
{
|
||||
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken);
|
||||
Logger.Debug("Waiting for uploads to finish");
|
||||
}
|
||||
|
||||
foreach (var item in unverifiedUploadHashes)
|
||||
{
|
||||
_verifiedUploadedHashes.Add(item);
|
||||
}
|
||||
|
||||
CurrentUploads.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("All files already verified");
|
||||
}
|
||||
|
||||
if (!uploadToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var item in character.FileReplacements)
|
||||
{
|
||||
sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}");
|
||||
}
|
||||
foreach (var item in character.GlamourerData)
|
||||
{
|
||||
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
|
||||
}
|
||||
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString());
|
||||
await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warn("=== Upload operation was cancelled ===");
|
||||
}
|
||||
|
||||
Logger.Verbose("Upload complete for " + character.GetHashCode());
|
||||
_uploadCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
|
||||
{
|
||||
var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath;
|
||||
return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0,
|
||||
(int)new FileInfo(fileCache).Length));
|
||||
}
|
||||
|
||||
private async Task UploadFile(byte[] compressedFile, string fileHash, CancellationToken uploadToken)
|
||||
{
|
||||
if (uploadToken.IsCancellationRequested) return;
|
||||
|
||||
async IAsyncEnumerable<byte[]> AsyncFileData([EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
var chunkSize = 1024 * 512; // 512kb
|
||||
using var ms = new MemoryStream(compressedFile);
|
||||
var buffer = new byte[chunkSize];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested)
|
||||
{
|
||||
CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead;
|
||||
token.ThrowIfCancellationRequested();
|
||||
yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken);
|
||||
}
|
||||
|
||||
public void CancelDownload(int downloadId)
|
||||
{
|
||||
while (CurrentDownloads.ContainsKey(downloadId))
|
||||
{
|
||||
CurrentDownloads.TryRemove(downloadId, out _);
|
||||
}
|
||||
Logger.Debug("Cancelling upload");
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
_mareHub!.SendAsync(Api.SendFileAbortUpload);
|
||||
CurrentUploads.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAllMyFiles()
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
|
||||
}
|
||||
|
||||
private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
|
||||
{
|
||||
using WebClient wc = new();
|
||||
wc.Headers.Add("Authorization", SecretKey);
|
||||
DownloadProgressChangedEventHandler progChanged = (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Could not set download progress for " + hash);
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
};
|
||||
wc.DownloadProgressChanged += progChanged;
|
||||
|
||||
string fileName = Path.GetTempFileName();
|
||||
|
||||
ct.Register(wc.CancelAsync);
|
||||
|
||||
try
|
||||
{
|
||||
await wc.DownloadFileTaskAsync(downloadUri, fileName);
|
||||
}
|
||||
catch { }
|
||||
|
||||
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total;
|
||||
|
||||
wc.DownloadProgressChanged -= progChanged;
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public int GetDownloadId() => _downloadId++;
|
||||
|
||||
public async Task DownloadFiles(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
|
||||
{
|
||||
DownloadStarted?.Invoke();
|
||||
try
|
||||
{
|
||||
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
CancelDownload(currentDownloadId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DownloadFinished?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFilesInternal(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
|
||||
{
|
||||
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
|
||||
|
||||
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
|
||||
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
|
||||
|
||||
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
|
||||
|
||||
CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
|
||||
.Where(d => d.CanBeTransferred).ToList();
|
||||
|
||||
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
|
||||
{
|
||||
if (ForbiddenTransfers.All(f => f.Hash != dto.Hash))
|
||||
{
|
||||
ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
|
||||
}
|
||||
}
|
||||
|
||||
await Parallel.ForEachAsync(CurrentDownloads[currentDownloadId].Where(f => f.CanBeTransferred), new ParallelOptions()
|
||||
{
|
||||
MaxDegreeOfParallelism = 5,
|
||||
CancellationToken = ct
|
||||
},
|
||||
async (file, token) =>
|
||||
{
|
||||
var hash = file.Hash;
|
||||
var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
Logger.Debug("Detected cancellation, removing " + currentDownloadId);
|
||||
DownloadFinished?.Invoke();
|
||||
CancelDownload(currentDownloadId);
|
||||
return;
|
||||
}
|
||||
|
||||
var tempFileData = await File.ReadAllBytesAsync(tempFile, token);
|
||||
var extractedFile = LZ4Codec.Unwrap(tempFileData);
|
||||
File.Delete(tempFile);
|
||||
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash);
|
||||
await File.WriteAllBytesAsync(filePath, extractedFile, token);
|
||||
var fi = new FileInfo(filePath);
|
||||
Func<DateTime> RandomDayFunc()
|
||||
{
|
||||
DateTime start = new DateTime(1995, 1, 1);
|
||||
Random gen = new Random();
|
||||
int range = (DateTime.Today - start).Days;
|
||||
return () => start.AddDays(gen.Next(range));
|
||||
}
|
||||
|
||||
fi.CreationTime = RandomDayFunc().Invoke();
|
||||
fi.LastAccessTime = RandomDayFunc().Invoke();
|
||||
fi.LastWriteTime = RandomDayFunc().Invoke();
|
||||
try
|
||||
{
|
||||
_ = _fileDbManager.CreateCacheEntry(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Issue adding file to the DB");
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace);
|
||||
}
|
||||
});
|
||||
|
||||
Logger.Debug("Download complete, removing " + currentDownloadId);
|
||||
CancelDownload(currentDownloadId);
|
||||
}
|
||||
|
||||
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
Logger.Debug("Sending Character data to service " + ApiUri);
|
||||
|
||||
CancelUpload();
|
||||
_uploadCancellationTokenSource = new CancellationTokenSource();
|
||||
var uploadToken = _uploadCancellationTokenSource.Token;
|
||||
Logger.Verbose("New Token Created");
|
||||
|
||||
List<string> unverifiedUploadHashes = new();
|
||||
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList())
|
||||
{
|
||||
if (!_verifiedUploadedHashes.Contains(item))
|
||||
{
|
||||
unverifiedUploadHashes.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (unverifiedUploadHashes.Any())
|
||||
{
|
||||
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files");
|
||||
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken);
|
||||
|
||||
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentUploads.Add(new UploadFileTransfer(file)
|
||||
{
|
||||
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Tried to request file " + file.Hash + " but file was not present");
|
||||
Logger.Warn(ex.StackTrace!);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var file in filesToUpload.Where(c => c.IsForbidden))
|
||||
{
|
||||
if (ForbiddenTransfers.All(f => f.Hash != file.Hash))
|
||||
{
|
||||
ForbiddenTransfers.Add(new UploadFileTransfer(file)
|
||||
{
|
||||
LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var totalSize = CurrentUploads.Sum(c => c.Total);
|
||||
Logger.Debug("Compressing and uploading files");
|
||||
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
|
||||
{
|
||||
Logger.Debug("Compressing and uploading " + file);
|
||||
var data = await GetCompressedFileData(file.Hash, uploadToken);
|
||||
CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length;
|
||||
await UploadFile(data.Item2, file.Hash, uploadToken);
|
||||
if (!uploadToken.IsCancellationRequested) continue;
|
||||
Logger.Warn("Cancel in filesToUpload loop detected");
|
||||
CurrentUploads.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if (CurrentUploads.Any())
|
||||
{
|
||||
var compressedSize = CurrentUploads.Sum(c => c.Total);
|
||||
Logger.Debug($"Compressed {totalSize} to {compressedSize} ({(compressedSize / (double)totalSize):P2})");
|
||||
}
|
||||
|
||||
Logger.Debug("Upload tasks complete, waiting for server to confirm");
|
||||
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
|
||||
Logger.Debug("Uploads open: " + anyUploadsOpen);
|
||||
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
|
||||
{
|
||||
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken);
|
||||
Logger.Debug("Waiting for uploads to finish");
|
||||
}
|
||||
|
||||
foreach (var item in unverifiedUploadHashes)
|
||||
{
|
||||
_verifiedUploadedHashes.Add(item);
|
||||
}
|
||||
|
||||
CurrentUploads.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("All files already verified");
|
||||
}
|
||||
|
||||
if (!uploadToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var item in character.FileReplacements)
|
||||
{
|
||||
sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}");
|
||||
}
|
||||
foreach (var item in character.GlamourerData)
|
||||
{
|
||||
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
|
||||
}
|
||||
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString());
|
||||
await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warn("=== Upload operation was cancelled ===");
|
||||
}
|
||||
|
||||
Logger.Verbose("Upload complete for " + character.GetHashCode());
|
||||
_uploadCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
|
||||
{
|
||||
var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath;
|
||||
return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0,
|
||||
(int)new FileInfo(fileCache).Length));
|
||||
}
|
||||
|
||||
private async Task UploadFile(byte[] compressedFile, string fileHash, CancellationToken uploadToken)
|
||||
{
|
||||
if (uploadToken.IsCancellationRequested) return;
|
||||
|
||||
async IAsyncEnumerable<byte[]> AsyncFileData([EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
var chunkSize = 1024 * 512; // 512kb
|
||||
using var ms = new MemoryStream(compressedFile);
|
||||
var buffer = new byte[chunkSize];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested)
|
||||
{
|
||||
CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead;
|
||||
token.ThrowIfCancellationRequested();
|
||||
yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken);
|
||||
}
|
||||
|
||||
public void CancelDownload(int downloadId)
|
||||
{
|
||||
while (CurrentDownloads.ContainsKey(downloadId))
|
||||
{
|
||||
CurrentDownloads.TryRemove(downloadId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace MareSynchronos.WebAPI
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public partial class ApiController
|
||||
{
|
||||
public partial class ApiController
|
||||
public async Task DeleteAccount()
|
||||
{
|
||||
public async Task DeleteAccount()
|
||||
{
|
||||
_pluginConfiguration.ClientSecret.Remove(ApiUri);
|
||||
_pluginConfiguration.Save();
|
||||
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
|
||||
await _mareHub!.SendAsync(Api.SendUserDeleteAccount);
|
||||
await CreateConnections();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetOnlineCharacters()
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<string>>(Api.InvokeUserGetOnlineCharacters);
|
||||
}
|
||||
|
||||
public async Task SendPairedClientAddition(string uid)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid);
|
||||
}
|
||||
|
||||
public async Task SendPairedClientPauseChange(string uid, bool paused)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused);
|
||||
}
|
||||
|
||||
public async Task SendPairedClientRemoval(string uid)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid);
|
||||
}
|
||||
_pluginConfiguration.ClientSecret.Remove(ApiUri);
|
||||
_pluginConfiguration.Save();
|
||||
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
|
||||
await _mareHub!.SendAsync(Api.SendUserDeleteAccount);
|
||||
await CreateConnections();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetOnlineCharacters()
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<string>>(Api.InvokeUserGetOnlineCharacters);
|
||||
}
|
||||
|
||||
public async Task SendPairedClientAddition(string uid)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid);
|
||||
}
|
||||
|
||||
public async Task SendPairedClientPauseChange(string uid, bool paused)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused);
|
||||
}
|
||||
|
||||
public async Task SendPairedClientRemoval(string uid)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,372 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Utils;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.WebAPI
|
||||
{
|
||||
public delegate void SimpleStringDelegate(string str);
|
||||
public enum ServerState
|
||||
{
|
||||
Offline,
|
||||
Disconnected,
|
||||
Connected,
|
||||
Unauthorized,
|
||||
VersionMisMatch,
|
||||
RateLimited
|
||||
}
|
||||
|
||||
public partial class ApiController : IDisposable
|
||||
{
|
||||
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
|
||||
public const string MainServiceUri = "wss://maresynchronos.com";
|
||||
|
||||
public readonly int[] SupportedServerVersions = { Api.Version };
|
||||
|
||||
private readonly Configuration _pluginConfiguration;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private CancellationTokenSource _connectionCancellationTokenSource;
|
||||
|
||||
private HubConnection? _mareHub;
|
||||
|
||||
private CancellationTokenSource? _uploadCancellationTokenSource = new();
|
||||
|
||||
private ConnectionDto? _connectionDto;
|
||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||
public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false);
|
||||
|
||||
public bool IsAdmin => _connectionDto?.IsAdmin ?? false;
|
||||
|
||||
public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(ApiController));
|
||||
|
||||
_pluginConfiguration = pluginConfiguration;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_fileDbManager = fileDbManager;
|
||||
_connectionCancellationTokenSource = new CancellationTokenSource();
|
||||
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
|
||||
ServerState = ServerState.Offline;
|
||||
_verifiedUploadedHashes = new();
|
||||
|
||||
if (_dalamudUtil.IsLoggedIn)
|
||||
{
|
||||
DalamudUtilOnLogIn();
|
||||
}
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogOut()
|
||||
{
|
||||
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
|
||||
ServerState = ServerState.Offline;
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
Task.Run(CreateConnections);
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler<CharacterReceivedEventArgs>? CharacterReceived;
|
||||
|
||||
public event VoidDelegate? Connected;
|
||||
|
||||
public event VoidDelegate? Disconnected;
|
||||
|
||||
public event SimpleStringDelegate? PairedClientOffline;
|
||||
|
||||
public event SimpleStringDelegate? PairedClientOnline;
|
||||
|
||||
public event SimpleStringDelegate? PairedWithOther;
|
||||
|
||||
public event SimpleStringDelegate? UnpairedFromOther;
|
||||
public event VoidDelegate? DownloadStarted;
|
||||
public event VoidDelegate? DownloadFinished;
|
||||
|
||||
public ConcurrentDictionary<int, List<DownloadFileTransfer>> CurrentDownloads { get; } = new();
|
||||
|
||||
public List<FileTransfer> CurrentUploads { get; } = new();
|
||||
|
||||
public List<FileTransfer> ForbiddenTransfers { get; } = new();
|
||||
|
||||
public List<BannedUserDto> AdminBannedUsers { get; private set; } = new();
|
||||
|
||||
public List<ForbiddenFileDto> AdminForbiddenFiles { get; private set; } = new();
|
||||
|
||||
public bool IsConnected => ServerState == ServerState.Connected;
|
||||
|
||||
public bool IsDownloading => CurrentDownloads.Count > 0;
|
||||
|
||||
public bool IsUploading => CurrentUploads.Count > 0;
|
||||
|
||||
public List<ClientPairDto> PairedClients { get; set; } = new();
|
||||
|
||||
public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri)
|
||||
? _pluginConfiguration.ClientSecret[ApiUri] : string.Empty;
|
||||
|
||||
public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected;
|
||||
|
||||
public Dictionary<string, string> ServerDictionary => new Dictionary<string, string>()
|
||||
{ { MainServiceUri, MainServer } }
|
||||
.Concat(_pluginConfiguration.CustomServerList)
|
||||
.ToDictionary(k => k.Key, k => k.Value);
|
||||
|
||||
public string UID => _connectionDto?.UID ?? string.Empty;
|
||||
private string ApiUri => _pluginConfiguration.ApiUri;
|
||||
public int OnlineUsers => SystemInfoDto.OnlineUsers;
|
||||
|
||||
private ServerState _serverState;
|
||||
public ServerState ServerState
|
||||
{
|
||||
get => _serverState;
|
||||
private set
|
||||
{
|
||||
Logger.Debug($"New ServerState: {value}, prev ServerState: {_serverState}");
|
||||
_serverState = value;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateConnections()
|
||||
{
|
||||
Logger.Debug("CreateConnections called");
|
||||
|
||||
if (_pluginConfiguration.FullPause)
|
||||
{
|
||||
Logger.Info("Not recreating Connection, paused");
|
||||
ServerState = ServerState.Disconnected;
|
||||
_connectionDto = null;
|
||||
await StopConnection(_connectionCancellationTokenSource.Token);
|
||||
return;
|
||||
}
|
||||
|
||||
await StopConnection(_connectionCancellationTokenSource.Token);
|
||||
|
||||
Logger.Info("Recreating Connection");
|
||||
|
||||
_connectionCancellationTokenSource.Cancel();
|
||||
_connectionCancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _connectionCancellationTokenSource.Token;
|
||||
_verifiedUploadedHashes.Clear();
|
||||
while (ServerState is not ServerState.Connected && !token.IsCancellationRequested)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SecretKey))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
continue;
|
||||
}
|
||||
|
||||
await StopConnection(token);
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Debug("Building connection");
|
||||
|
||||
while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Player not loaded in yet, waiting");
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
_mareHub = BuildHubConnection(Api.Path);
|
||||
|
||||
await _mareHub.StartAsync(token);
|
||||
|
||||
_mareHub.On<SystemInfoDto>(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto);
|
||||
|
||||
_connectionDto =
|
||||
await _mareHub.InvokeAsync<ConnectionDto>(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token);
|
||||
|
||||
ServerState = ServerState.Connected;
|
||||
|
||||
if (_connectionDto.ServerVersion != Api.Version)
|
||||
{
|
||||
ServerState = ServerState.VersionMisMatch;
|
||||
await StopConnection(token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServerState is ServerState.Connected) // user is authorized && server is legit
|
||||
{
|
||||
await InitializeData(token);
|
||||
|
||||
_mareHub.Closed += MareHubOnClosed;
|
||||
_mareHub.Reconnecting += MareHubOnReconnecting;
|
||||
_mareHub.Reconnected += MareHubOnReconnected;
|
||||
}
|
||||
}
|
||||
catch (HubException ex)
|
||||
{
|
||||
Logger.Warn(ex.GetType().ToString());
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
|
||||
ServerState = ServerState.RateLimited;
|
||||
await StopConnection(token);
|
||||
return;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Warn(ex.GetType().ToString());
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
|
||||
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
ServerState = ServerState.Unauthorized;
|
||||
await StopConnection(token);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerState = ServerState.Offline;
|
||||
Logger.Info("Failed to establish connection, retrying");
|
||||
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn(ex.GetType().ToString());
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
Logger.Info("Failed to establish connection, retrying");
|
||||
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task MareHubOnReconnected(string? arg)
|
||||
{
|
||||
_ = Task.Run(CreateConnections);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task InitializeData(CancellationToken token)
|
||||
{
|
||||
if (_mareHub == null) return;
|
||||
|
||||
Logger.Debug("Initializing data");
|
||||
_mareHub.On<ClientPairDto, string>(Api.OnUserUpdateClientPairs,
|
||||
UpdateLocalClientPairsCallback);
|
||||
_mareHub.On<CharacterCacheDto, string>(Api.OnUserReceiveCharacterData,
|
||||
ReceiveCharacterDataCallback);
|
||||
_mareHub.On<string>(Api.OnUserRemoveOnlinePairedPlayer,
|
||||
(s) => PairedClientOffline?.Invoke(s));
|
||||
_mareHub.On<string>(Api.OnUserAddOnlinePairedPlayer,
|
||||
(s) => PairedClientOnline?.Invoke(s));
|
||||
_mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback);
|
||||
|
||||
PairedClients =
|
||||
await _mareHub!.InvokeAsync<List<ClientPairDto>>(Api.InvokeUserGetPairedClients, token);
|
||||
|
||||
if (IsModerator)
|
||||
{
|
||||
AdminForbiddenFiles =
|
||||
await _mareHub.InvokeAsync<List<ForbiddenFileDto>>(Api.InvokeAdminGetForbiddenFiles,
|
||||
token);
|
||||
AdminBannedUsers =
|
||||
await _mareHub.InvokeAsync<List<BannedUserDto>>(Api.InvokeAdminGetBannedUsers,
|
||||
token);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminUpdateOrAddBannedUser,
|
||||
UpdateOrAddBannedUserCallback);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminUpdateOrAddForbiddenFile,
|
||||
UpdateOrAddForbiddenFileCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminDeleteForbiddenFile,
|
||||
DeleteForbiddenFileCallback);
|
||||
}
|
||||
|
||||
Connected?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(ApiController));
|
||||
|
||||
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
|
||||
|
||||
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private HubConnection BuildHubConnection(string hubName)
|
||||
{
|
||||
return new HubConnectionBuilder()
|
||||
.WithUrl(ApiUri + hubName, options =>
|
||||
{
|
||||
options.Headers.Add("Authorization", SecretKey);
|
||||
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
|
||||
})
|
||||
.WithAutomaticReconnect(new ForeverRetryPolicy())
|
||||
.ConfigureLogging(a => {
|
||||
a.ClearProviders().AddProvider(new DalamudLoggingProvider());
|
||||
a.SetMinimumLevel(LogLevel.Warning);
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
private Task MareHubOnClosed(Exception? arg)
|
||||
{
|
||||
CurrentUploads.Clear();
|
||||
CurrentDownloads.Clear();
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Disconnected?.Invoke();
|
||||
ServerState = ServerState.Offline;
|
||||
Logger.Info("Connection closed");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task MareHubOnReconnecting(Exception? arg)
|
||||
{
|
||||
ServerState = ServerState.Disconnected;
|
||||
Logger.Warn("Connection closed... Reconnecting");
|
||||
Logger.Warn(arg?.Message ?? string.Empty);
|
||||
Logger.Warn(arg?.StackTrace ?? string.Empty);
|
||||
Disconnected?.Invoke();
|
||||
ServerState = ServerState.Offline;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StopConnection(CancellationToken token)
|
||||
{
|
||||
if (_mareHub is not null)
|
||||
{
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Logger.Info("Stopping existing connection");
|
||||
_mareHub.Closed -= MareHubOnClosed;
|
||||
_mareHub.Reconnecting -= MareHubOnReconnecting;
|
||||
_mareHub.Reconnected -= MareHubOnReconnected;
|
||||
await _mareHub.StopAsync(token);
|
||||
await _mareHub.DisposeAsync();
|
||||
CurrentUploads.Clear();
|
||||
CurrentDownloads.Clear();
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Disconnected?.Invoke();
|
||||
_mareHub = null;
|
||||
}
|
||||
|
||||
if (ServerState != ServerState.Disconnected)
|
||||
{
|
||||
while (ServerState != ServerState.Offline)
|
||||
{
|
||||
await Task.Delay(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,45 +3,44 @@ using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
|
||||
namespace MareSynchronos.WebAPI
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public partial class ApiController
|
||||
{
|
||||
public partial class ApiController
|
||||
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
|
||||
{
|
||||
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile);
|
||||
}
|
||||
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile);
|
||||
}
|
||||
|
||||
public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile);
|
||||
}
|
||||
public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile);
|
||||
}
|
||||
|
||||
public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser);
|
||||
}
|
||||
public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser);
|
||||
}
|
||||
|
||||
public async Task DeleteBannedUserEntry(BannedUserDto bannedUser)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser);
|
||||
}
|
||||
public async Task DeleteBannedUserEntry(BannedUserDto bannedUser)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser);
|
||||
}
|
||||
|
||||
public async Task RefreshOnlineUsers()
|
||||
{
|
||||
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers);
|
||||
}
|
||||
public async Task RefreshOnlineUsers()
|
||||
{
|
||||
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers);
|
||||
}
|
||||
|
||||
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
|
||||
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
|
||||
|
||||
public void PromoteToModerator(string onlineUserUID)
|
||||
{
|
||||
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, true);
|
||||
}
|
||||
public void PromoteToModerator(string onlineUserUID)
|
||||
{
|
||||
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, true);
|
||||
}
|
||||
|
||||
public void DemoteFromModerator(string onlineUserUID)
|
||||
{
|
||||
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
|
||||
}
|
||||
public void DemoteFromModerator(string onlineUserUID)
|
||||
{
|
||||
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,75 +4,84 @@ using MareSynchronos.API;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Utils;
|
||||
|
||||
namespace MareSynchronos.WebAPI
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public partial class ApiController
|
||||
{
|
||||
public partial class ApiController
|
||||
private void UserForcedReconnectCallback()
|
||||
{
|
||||
private void UserForcedReconnectCallback()
|
||||
_ = CreateConnections();
|
||||
}
|
||||
|
||||
private void UpdateLocalClientPairsCallback(ClientPairDto dto)
|
||||
{
|
||||
var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID);
|
||||
if (dto.IsRemoved)
|
||||
{
|
||||
_ = CreateConnections();
|
||||
PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID);
|
||||
return;
|
||||
}
|
||||
if (entry == null)
|
||||
{
|
||||
PairedClients.Add(dto);
|
||||
return;
|
||||
}
|
||||
|
||||
private void UpdateLocalClientPairsCallback(ClientPairDto dto)
|
||||
{
|
||||
var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID);
|
||||
if (dto.IsRemoved)
|
||||
{
|
||||
PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID);
|
||||
return;
|
||||
}
|
||||
if (entry == null)
|
||||
{
|
||||
PairedClients.Add(dto);
|
||||
return;
|
||||
}
|
||||
entry.IsPaused = dto.IsPaused;
|
||||
entry.IsPausedFromOthers = dto.IsPausedFromOthers;
|
||||
entry.IsSynced = dto.IsSynced;
|
||||
}
|
||||
|
||||
entry.IsPaused = dto.IsPaused;
|
||||
entry.IsPausedFromOthers = dto.IsPausedFromOthers;
|
||||
entry.IsSynced = dto.IsSynced;
|
||||
private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash)
|
||||
{
|
||||
Logger.Verbose("Received DTO for " + characterHash);
|
||||
CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void UpdateOrAddBannedUserCallback(BannedUserDto obj)
|
||||
{
|
||||
var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash);
|
||||
if (user == null)
|
||||
{
|
||||
AdminBannedUsers.Add(obj);
|
||||
}
|
||||
|
||||
private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash)
|
||||
else
|
||||
{
|
||||
Logger.Verbose("Received DTO for " + characterHash);
|
||||
CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void UpdateOrAddBannedUserCallback(BannedUserDto obj)
|
||||
{
|
||||
var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash);
|
||||
if (user == null)
|
||||
{
|
||||
AdminBannedUsers.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.Reason = obj.Reason;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteBannedUserCallback(BannedUserDto obj)
|
||||
{
|
||||
AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash);
|
||||
}
|
||||
|
||||
private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj)
|
||||
{
|
||||
var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash);
|
||||
if (user == null)
|
||||
{
|
||||
AdminForbiddenFiles.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ForbiddenBy = obj.ForbiddenBy;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteForbiddenFileCallback(ForbiddenFileDto obj)
|
||||
{
|
||||
AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash);
|
||||
user.Reason = obj.Reason;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteBannedUserCallback(BannedUserDto obj)
|
||||
{
|
||||
AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash);
|
||||
}
|
||||
|
||||
private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj)
|
||||
{
|
||||
var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash);
|
||||
if (user == null)
|
||||
{
|
||||
AdminForbiddenFiles.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
user.ForbiddenBy = obj.ForbiddenBy;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteForbiddenFileCallback(ForbiddenFileDto obj)
|
||||
{
|
||||
AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash);
|
||||
}
|
||||
|
||||
private void GroupPairChangedCallback(GroupPairDto dto)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void GroupChangedCallback(GroupDto dto)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
64
MareSynchronos/WebAPI/ApiController.Functions.Groups.cs
Normal file
64
MareSynchronos/WebAPI/ApiController.Functions.Groups.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using MareSynchronos.API;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.WebAPI;
|
||||
public partial class ApiController
|
||||
{
|
||||
public async Task<GroupCreatedDto> CreateGroup()
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<GroupCreatedDto>(Api.InvokeGroupCreate);
|
||||
}
|
||||
|
||||
public async Task<bool> ChangeGroupPassword(string gid, string newpassword)
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<bool>(Api.InvokeGroupChangePassword, gid, newpassword);
|
||||
}
|
||||
|
||||
public async Task<List<GroupDto>> GetGroups()
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<GroupDto>>(Api.InvokeGroupGetGroups);
|
||||
}
|
||||
|
||||
public async Task<List<GroupPairDto>> GetUsersInGroup(string gid)
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<GroupPairDto>>(Api.InvokeGroupGetUsersInGroup, gid);
|
||||
}
|
||||
|
||||
public async Task SendGroupJoin(string gid, string password)
|
||||
{
|
||||
if (!IsConnected || SecretKey == "-") return;
|
||||
await _mareHub!.SendAsync(Api.SendGroupJoin, gid, password);
|
||||
}
|
||||
|
||||
public async Task SendGroupChangeInviteState(string gid, bool opened)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendGroupChangeInviteState, gid, opened);
|
||||
}
|
||||
|
||||
public async Task SendDeleteGroup(string gid)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendGroupDelete, gid);
|
||||
}
|
||||
|
||||
public async Task SendLeaveGroup(string gid)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendGroupLeave, gid);
|
||||
}
|
||||
|
||||
public async Task SendPauseGroup(string gid, bool isPaused)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendGroupPause, gid, isPaused);
|
||||
}
|
||||
|
||||
public async Task SendRemoveUserFromGroup(string gid, string uid)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendGroupRemoveUser, gid, uid);
|
||||
}
|
||||
|
||||
public async Task ChangeOwnerOfGroup(string gid, string uid)
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid);
|
||||
}
|
||||
}
|
||||
372
MareSynchronos/WebAPI/ApiController.cs
Normal file
372
MareSynchronos/WebAPI/ApiController.cs
Normal file
@@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI.Utils;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public delegate void SimpleStringDelegate(string str);
|
||||
|
||||
public partial class ApiController : IDisposable
|
||||
{
|
||||
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
|
||||
public const string MainServiceUri = "wss://maresynchronos.com";
|
||||
|
||||
public readonly int[] SupportedServerVersions = { Api.Version };
|
||||
|
||||
private readonly Configuration _pluginConfiguration;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private CancellationTokenSource _connectionCancellationTokenSource;
|
||||
|
||||
private HubConnection? _mareHub;
|
||||
|
||||
private CancellationTokenSource? _uploadCancellationTokenSource = new();
|
||||
|
||||
private ConnectionDto? _connectionDto;
|
||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||
public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false);
|
||||
|
||||
public bool IsAdmin => _connectionDto?.IsAdmin ?? false;
|
||||
|
||||
public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(ApiController));
|
||||
|
||||
_pluginConfiguration = pluginConfiguration;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_fileDbManager = fileDbManager;
|
||||
_connectionCancellationTokenSource = new CancellationTokenSource();
|
||||
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
|
||||
ServerState = ServerState.Offline;
|
||||
_verifiedUploadedHashes = new();
|
||||
|
||||
if (_dalamudUtil.IsLoggedIn)
|
||||
{
|
||||
DalamudUtilOnLogIn();
|
||||
}
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogOut()
|
||||
{
|
||||
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
|
||||
ServerState = ServerState.Offline;
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
Task.Run(CreateConnections);
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler<CharacterReceivedEventArgs>? CharacterReceived;
|
||||
|
||||
public event VoidDelegate? Connected;
|
||||
|
||||
public event VoidDelegate? Disconnected;
|
||||
|
||||
public event SimpleStringDelegate? PairedClientOffline;
|
||||
|
||||
public event SimpleStringDelegate? PairedClientOnline;
|
||||
|
||||
public event SimpleStringDelegate? PairedWithOther;
|
||||
|
||||
public event SimpleStringDelegate? UnpairedFromOther;
|
||||
public event VoidDelegate? DownloadStarted;
|
||||
public event VoidDelegate? DownloadFinished;
|
||||
|
||||
public ConcurrentDictionary<int, List<DownloadFileTransfer>> CurrentDownloads { get; } = new();
|
||||
|
||||
public List<FileTransfer> CurrentUploads { get; } = new();
|
||||
|
||||
public List<FileTransfer> ForbiddenTransfers { get; } = new();
|
||||
|
||||
public List<BannedUserDto> AdminBannedUsers { get; private set; } = new();
|
||||
|
||||
public List<ForbiddenFileDto> AdminForbiddenFiles { get; private set; } = new();
|
||||
|
||||
public bool IsConnected => ServerState == ServerState.Connected;
|
||||
|
||||
public bool IsDownloading => CurrentDownloads.Count > 0;
|
||||
|
||||
public bool IsUploading => CurrentUploads.Count > 0;
|
||||
|
||||
public List<ClientPairDto> PairedClients { get; set; } = new();
|
||||
public List<GroupPairDto> GroupPairedClients { get; set; } = new();
|
||||
public List<GroupDto> Groups { get; set; } = new();
|
||||
|
||||
public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri)
|
||||
? _pluginConfiguration.ClientSecret[ApiUri] : string.Empty;
|
||||
|
||||
public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected;
|
||||
|
||||
public Dictionary<string, string> ServerDictionary => new Dictionary<string, string>()
|
||||
{ { MainServiceUri, MainServer } }
|
||||
.Concat(_pluginConfiguration.CustomServerList)
|
||||
.ToDictionary(k => k.Key, k => k.Value);
|
||||
|
||||
public string UID => _connectionDto?.UID ?? string.Empty;
|
||||
private string ApiUri => _pluginConfiguration.ApiUri;
|
||||
public int OnlineUsers => SystemInfoDto.OnlineUsers;
|
||||
|
||||
private ServerState _serverState;
|
||||
public ServerState ServerState
|
||||
{
|
||||
get => _serverState;
|
||||
private set
|
||||
{
|
||||
Logger.Debug($"New ServerState: {value}, prev ServerState: {_serverState}");
|
||||
_serverState = value;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateConnections()
|
||||
{
|
||||
Logger.Debug("CreateConnections called");
|
||||
|
||||
if (_pluginConfiguration.FullPause)
|
||||
{
|
||||
Logger.Info("Not recreating Connection, paused");
|
||||
ServerState = ServerState.Disconnected;
|
||||
_connectionDto = null;
|
||||
await StopConnection(_connectionCancellationTokenSource.Token);
|
||||
return;
|
||||
}
|
||||
|
||||
await StopConnection(_connectionCancellationTokenSource.Token);
|
||||
|
||||
Logger.Info("Recreating Connection");
|
||||
|
||||
_connectionCancellationTokenSource.Cancel();
|
||||
_connectionCancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _connectionCancellationTokenSource.Token;
|
||||
_verifiedUploadedHashes.Clear();
|
||||
while (ServerState is not ServerState.Connected && !token.IsCancellationRequested)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SecretKey))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||
continue;
|
||||
}
|
||||
|
||||
await StopConnection(token);
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Debug("Building connection");
|
||||
|
||||
while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Player not loaded in yet, waiting");
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), token);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
_mareHub = BuildHubConnection(Api.Path);
|
||||
|
||||
await _mareHub.StartAsync(token);
|
||||
|
||||
_mareHub.On<SystemInfoDto>(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto);
|
||||
|
||||
_connectionDto =
|
||||
await _mareHub.InvokeAsync<ConnectionDto>(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token);
|
||||
|
||||
ServerState = ServerState.Connected;
|
||||
|
||||
if (_connectionDto.ServerVersion != Api.Version)
|
||||
{
|
||||
ServerState = ServerState.VersionMisMatch;
|
||||
await StopConnection(token);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServerState is ServerState.Connected) // user is authorized && server is legit
|
||||
{
|
||||
await InitializeData(token);
|
||||
|
||||
_mareHub.Closed += MareHubOnClosed;
|
||||
_mareHub.Reconnecting += MareHubOnReconnecting;
|
||||
_mareHub.Reconnected += MareHubOnReconnected;
|
||||
}
|
||||
}
|
||||
catch (HubException ex)
|
||||
{
|
||||
Logger.Warn(ex.GetType().ToString());
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
|
||||
ServerState = ServerState.RateLimited;
|
||||
await StopConnection(token);
|
||||
return;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Logger.Warn(ex.GetType().ToString());
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
|
||||
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
ServerState = ServerState.Unauthorized;
|
||||
await StopConnection(token);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerState = ServerState.Offline;
|
||||
Logger.Info("Failed to establish connection, retrying");
|
||||
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn(ex.GetType().ToString());
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
Logger.Info("Failed to establish connection, retrying");
|
||||
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task MareHubOnReconnected(string? arg)
|
||||
{
|
||||
_ = Task.Run(CreateConnections);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task InitializeData(CancellationToken token)
|
||||
{
|
||||
if (_mareHub == null) return;
|
||||
|
||||
Logger.Debug("Initializing data");
|
||||
_mareHub.On<ClientPairDto>(Api.OnUserUpdateClientPairs,
|
||||
UpdateLocalClientPairsCallback);
|
||||
_mareHub.On<CharacterCacheDto, string>(Api.OnUserReceiveCharacterData,
|
||||
ReceiveCharacterDataCallback);
|
||||
_mareHub.On<string>(Api.OnUserRemoveOnlinePairedPlayer,
|
||||
(s) => PairedClientOffline?.Invoke(s));
|
||||
_mareHub.On<string>(Api.OnUserAddOnlinePairedPlayer,
|
||||
(s) => PairedClientOnline?.Invoke(s));
|
||||
_mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback);
|
||||
_mareHub.On<GroupDto>(Api.OnGroupChange, GroupChangedCallback);
|
||||
_mareHub.On<GroupPairDto>(Api.OnGroupUserChange, GroupPairChangedCallback);
|
||||
|
||||
PairedClients =
|
||||
await _mareHub!.InvokeAsync<List<ClientPairDto>>(Api.InvokeUserGetPairedClients, token);
|
||||
Groups = await GetGroups();
|
||||
foreach (var group in Groups)
|
||||
{
|
||||
GroupPairedClients.AddRange(await GetUsersInGroup(group.GID));
|
||||
}
|
||||
|
||||
if (IsModerator)
|
||||
{
|
||||
AdminForbiddenFiles =
|
||||
await _mareHub.InvokeAsync<List<ForbiddenFileDto>>(Api.InvokeAdminGetForbiddenFiles,
|
||||
token);
|
||||
AdminBannedUsers =
|
||||
await _mareHub.InvokeAsync<List<BannedUserDto>>(Api.InvokeAdminGetBannedUsers,
|
||||
token);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminUpdateOrAddBannedUser,
|
||||
UpdateOrAddBannedUserCallback);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminUpdateOrAddForbiddenFile,
|
||||
UpdateOrAddForbiddenFileCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminDeleteForbiddenFile,
|
||||
DeleteForbiddenFileCallback);
|
||||
}
|
||||
|
||||
Connected?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(ApiController));
|
||||
|
||||
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
|
||||
|
||||
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
|
||||
_connectionCancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private HubConnection BuildHubConnection(string hubName)
|
||||
{
|
||||
return new HubConnectionBuilder()
|
||||
.WithUrl(ApiUri + hubName, options =>
|
||||
{
|
||||
options.Headers.Add("Authorization", SecretKey);
|
||||
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
|
||||
})
|
||||
.WithAutomaticReconnect(new ForeverRetryPolicy())
|
||||
.ConfigureLogging(a =>
|
||||
{
|
||||
a.ClearProviders().AddProvider(new DalamudLoggingProvider());
|
||||
a.SetMinimumLevel(LogLevel.Warning);
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
private Task MareHubOnClosed(Exception? arg)
|
||||
{
|
||||
CurrentUploads.Clear();
|
||||
CurrentDownloads.Clear();
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Disconnected?.Invoke();
|
||||
ServerState = ServerState.Offline;
|
||||
Logger.Info("Connection closed");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task MareHubOnReconnecting(Exception? arg)
|
||||
{
|
||||
ServerState = ServerState.Disconnected;
|
||||
Logger.Warn("Connection closed... Reconnecting");
|
||||
Logger.Warn(arg?.Message ?? string.Empty);
|
||||
Logger.Warn(arg?.StackTrace ?? string.Empty);
|
||||
Disconnected?.Invoke();
|
||||
ServerState = ServerState.Offline;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StopConnection(CancellationToken token)
|
||||
{
|
||||
if (_mareHub is not null)
|
||||
{
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Logger.Info("Stopping existing connection");
|
||||
_mareHub.Closed -= MareHubOnClosed;
|
||||
_mareHub.Reconnecting -= MareHubOnReconnecting;
|
||||
_mareHub.Reconnected -= MareHubOnReconnected;
|
||||
await _mareHub.StopAsync(token);
|
||||
await _mareHub.DisposeAsync();
|
||||
CurrentUploads.Clear();
|
||||
CurrentDownloads.Clear();
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Disconnected?.Invoke();
|
||||
_mareHub = null;
|
||||
}
|
||||
|
||||
if (ServerState != ServerState.Disconnected)
|
||||
{
|
||||
while (ServerState != ServerState.Offline)
|
||||
{
|
||||
await Task.Delay(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MareSynchronos/WebAPI/ServerState.cs
Normal file
11
MareSynchronos/WebAPI/ServerState.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public enum ServerState
|
||||
{
|
||||
Offline,
|
||||
Disconnected,
|
||||
Connected,
|
||||
Unauthorized,
|
||||
VersionMisMatch,
|
||||
RateLimited
|
||||
}
|
||||
Reference in New Issue
Block a user