diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index dde1c2f..ccb5f5e 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -2,7 +2,7 @@ Snowcloak - 0.1.6.3 + 0.1.7 https://github.com/Eauldane/SnowcloakClient/ diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 3e57c3c..8f4c101 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -126,7 +126,6 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); @@ -210,7 +209,6 @@ public sealed class Plugin : IDalamudPlugin collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); - collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); }) .Build(); diff --git a/MareSynchronos/Services/RemoteConfigurationService.cs b/MareSynchronos/Services/RemoteConfigurationService.cs index ece5780..b99c4f8 100644 --- a/MareSynchronos/Services/RemoteConfigurationService.cs +++ b/MareSynchronos/Services/RemoteConfigurationService.cs @@ -73,7 +73,7 @@ public sealed class RemoteConfigurationService { ulong ts = 1755859494; - var configString = "{\"mainServer\":{\"api_url\":\"wss://hub.snowcloak-sync.com/\",\"hub_url\":\"wss://hub.snowcloak-sync.com/mare\"},\"repoChange\":{\"current_repo\":\"https://raw.githubusercontent.com/Eauldane/ElfShelf/refs/heads/main/repo.json\",\"valid_repos\":[\"https://raw.githubusercontent.com/Eauldane/ElfShelf/refs/heads/main/repo.json\"]},\"noSnap\":{\"listOfPlugins\":[\"Snapper\",\"Snappy\",\"Meddle.Plugin\"]}}"; + var configString = "{\"mainServer\":{\"api_url\":\"wss://hub.snowcloak-sync.com/\",\"hub_url\":\"wss://hub.snowcloak-sync.com/mare\"},\"noSnap\":{\"listOfPlugins\":[\"Snapper\",\"Snappy\",\"Meddle.Plugin\"]}}"; _configService.Current.Configuration = JsonNode.Parse(configString)!.AsObject(); diff --git a/MareSynchronos/Services/RepoChangeConfig.cs b/MareSynchronos/Services/RepoChangeConfig.cs deleted file mode 100644 index eaf0b2e..0000000 --- a/MareSynchronos/Services/RepoChangeConfig.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace MareSynchronos.Services; - -public record RepoChangeConfig -{ - [JsonPropertyName("current_repo")] - public string? CurrentRepo { get; set; } - - [JsonPropertyName("valid_repos")] - public string[]? ValidRepos { get; set; } -} \ No newline at end of file diff --git a/MareSynchronos/Services/RepoChangeService.cs b/MareSynchronos/Services/RepoChangeService.cs deleted file mode 100644 index 3265e02..0000000 --- a/MareSynchronos/Services/RepoChangeService.cs +++ /dev/null @@ -1,401 +0,0 @@ -using Dalamud.Plugin; -using Dalamud.Plugin.Services; -using Dalamud.Utility; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System.Reflection; - -namespace MareSynchronos.Services; - -/* Reflection code based almost entirely on ECommons DalamudReflector - -MIT License - -Copyright (c) 2023 NightmareXIV - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -public sealed class RepoChangeService : IHostedService -{ - #region Reflection Helpers - private const BindingFlags AllFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; - private const BindingFlags StaticFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; - private const BindingFlags InstanceFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - - private static object GetFoP(object obj, string name) - { - Type? type = obj.GetType(); - while (type != null) - { - var fieldInfo = type.GetField(name, AllFlags); - if (fieldInfo != null) - { - return fieldInfo.GetValue(obj)!; - } - var propertyInfo = type.GetProperty(name, AllFlags); - if (propertyInfo != null) - { - return propertyInfo.GetValue(obj)!; - } - type = type.BaseType; - } - throw new Exception($"Reflection GetFoP failed (not found: {obj.GetType().Name}.{name})"); - } - - private static T GetFoP(object obj, string name) - { - return (T)GetFoP(obj, name); - } - - private static void SetFoP(object obj, string name, object value) - { - var type = obj.GetType(); - var field = type.GetField(name, AllFlags); - if (field != null) - { - field.SetValue(obj, value); - } - else - { - var prop = type.GetProperty(name, AllFlags)!; - if (prop == null) - throw new Exception($"Reflection SetFoP failed (not found: {type.Name}.{name})"); - prop.SetValue(obj, value); - } - } - - private static object? Call(object obj, string name, object[] @params, bool matchExactArgumentTypes = false) - { - MethodInfo? info; - var type = obj.GetType(); - if (!matchExactArgumentTypes) - { - info = type.GetMethod(name, AllFlags); - } - else - { - info = type.GetMethod(name, AllFlags, @params.Select(x => x.GetType()).ToArray()); - } - if (info == null) - throw new Exception($"Reflection Call failed (not found: {type.Name}.{name})"); - return info.Invoke(obj, @params); - } - - private static T Call(object obj, string name, object[] @params, bool matchExactArgumentTypes = false) - { - return (T)Call(obj, name, @params, matchExactArgumentTypes)!; - } - #endregion - - #region Dalamud Reflection - public object GetService(string serviceFullName) - { - return _pluginInterface.GetType().Assembly. - GetType("Dalamud.Service`1", true)!.MakeGenericType(_pluginInterface.GetType().Assembly.GetType(serviceFullName, true)!). - GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty(), null)!; - } - - private object GetPluginManager() - { - return _pluginInterface.GetType().Assembly. - GetType("Dalamud.Service`1", true)!.MakeGenericType(_pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)!). - GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty(), null)!; - } - - private void ReloadPluginMasters() - { - var mgr = GetService("Dalamud.Plugin.Internal.PluginManager"); - var pluginReload = mgr.GetType().GetMethod("SetPluginReposFromConfigAsync", BindingFlags.Instance | BindingFlags.Public)!; - pluginReload.Invoke(mgr, [true]); - } - - public void SaveDalamudConfig() - { - var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration"); - var configSave = conf?.GetType().GetMethod("QueueSave", BindingFlags.Instance | BindingFlags.Public); - configSave?.Invoke(conf, null); - } - - private IEnumerable GetRepoByURL(string repoURL) - { - var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration"); - var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList"); - foreach (var r in repolist) - { - if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase)) - yield return r; - } - } - - private bool HasRepo(string repoURL) - { - var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration"); - var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList"); - foreach (var r in repolist) - { - if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase)) - return true; - } - return false; - } - - private void AddRepo(string repoURL, bool enabled) - { - var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration"); - var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList"); - foreach (var r in repolist) - { - if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase)) - return; - } - var instance = Activator.CreateInstance(_pluginInterface.GetType().Assembly.GetType("Dalamud.Configuration.ThirdPartyRepoSettings")!)!; - SetFoP(instance, "Url", repoURL); - SetFoP(instance, "IsEnabled", enabled); - GetFoP(conf, "ThirdRepoList").Add(instance!); - } - - private void RemoveRepo(string repoURL) - { - var toRemove = new List(); - var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration"); - var repolist = (System.Collections.IList)GetFoP(conf, "ThirdRepoList"); - foreach (var r in repolist) - { - if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase)) - toRemove.Add(r); - } - foreach (var r in toRemove) - repolist.Remove(r); - } - - public List<(object LocalPlugin, string InstalledFromUrl)> GetLocalPluginsByName(string internalName) - { - List<(object LocalPlugin, string RepoURL)> result = []; - - var pluginManager = GetPluginManager(); - var installedPlugins = (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins")!.GetValue(pluginManager)!; - - foreach (var plugin in installedPlugins) - { - if (((string)plugin.GetType().GetProperty("InternalName")!.GetValue(plugin)!).Equals(internalName, StringComparison.Ordinal)) - { - var type = plugin.GetType(); - if (type.Name.Equals("LocalDevPlugin", StringComparison.Ordinal)) - continue; - var manifest = GetFoP(plugin, "manifest"); - string installedFromUrl = (string)GetFoP(manifest, "InstalledFromUrl"); - result.Add((plugin, installedFromUrl)); - } - } - - return result; - } - #endregion - - private readonly ILogger _logger; - private readonly RemoteConfigurationService _remoteConfig; - private readonly IDalamudPluginInterface _pluginInterface; - private readonly IFramework _framework; - - public RepoChangeService(ILogger logger, RemoteConfigurationService remoteConfig, IDalamudPluginInterface pluginInterface, IFramework framework) - { - _logger = logger; - _remoteConfig = remoteConfig; - _pluginInterface = pluginInterface; - _framework = framework; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - _logger.LogDebug("Starting RepoChange Service"); - var repoChangeConfig = await _remoteConfig.GetConfigAsync("repoChange").ConfigureAwait(false) ?? new(); - - var currentRepo = repoChangeConfig.CurrentRepo; - var validRepos = (repoChangeConfig.ValidRepos ?? []).ToList(); - - if (!currentRepo.IsNullOrEmpty() && !validRepos.Contains(currentRepo, StringComparer.Ordinal)) - validRepos.Add(currentRepo); - - if (validRepos.Count == 0) - { - _logger.LogInformation("No valid repos configured, skipping"); - return; - } - - await _framework.RunOnTick(() => - { - try - { - var internalName = Assembly.GetExecutingAssembly().GetName().Name!; - var localPlugins = GetLocalPluginsByName(internalName); - - var suffix = string.Empty; - - if (localPlugins.Count == 0) - { - _logger.LogInformation("Skipping: No intalled plugin found"); - return; - } - - var hasValidCustomRepoUrl = false; - - foreach (var vr in validRepos) - { - var vrCN = vr.Replace(".json", "_CN.json", StringComparison.Ordinal); - var vrKR = vr.Replace(".json", "_KR.json", StringComparison.Ordinal); - if (HasRepo(vr) || HasRepo(vrCN) || HasRepo(vrKR)) - { - hasValidCustomRepoUrl = true; - break; - } - } - - List oldRepos = []; - var pluginRepoUrl = localPlugins[0].InstalledFromUrl; - - if (pluginRepoUrl.Contains("_CN.json", StringComparison.Ordinal)) - suffix = "_CN"; - else if (pluginRepoUrl.Contains("_KR.json", StringComparison.Ordinal)) - suffix = "_KR"; - - bool hasOldPluginRepoUrl = false; - - foreach (var plugin in localPlugins) - { - foreach (var vr in validRepos) - { - var validRepo = vr.Replace(".json", $"{suffix}.json"); - if (!plugin.InstalledFromUrl.Equals(validRepo, StringComparison.Ordinal)) - { - oldRepos.Add(plugin.InstalledFromUrl); - hasOldPluginRepoUrl = true; - } - } - } - - if (hasValidCustomRepoUrl) - { - if (hasOldPluginRepoUrl) - _logger.LogInformation("Result: Repo URL is up to date, but plugin install source is incorrect"); - else - _logger.LogInformation("Result: Repo URL is up to date"); - } - else - { - _logger.LogInformation("Result: Repo URL needs to be replaced"); - } - - if (currentRepo.IsNullOrEmpty()) - { - _logger.LogWarning("No current repo URL configured"); - return; - } - - // Pre-test plugin repo url rewriting to ensure it succeeds before replacing the custom repo URL - if (hasOldPluginRepoUrl) - { - foreach (var plugin in localPlugins) - { - var manifest = GetFoP(plugin.LocalPlugin, "manifest"); - if (manifest == null) - throw new Exception("Plugin manifest is null"); - var manifestFile = GetFoP(plugin.LocalPlugin, "manifestFile"); - if (manifestFile == null) - throw new Exception("Plugin manifestFile is null"); - var repo = GetFoP(manifest, "InstalledFromUrl"); - if (((string)repo).IsNullOrEmpty()) - throw new Exception("Plugin repo url is null or empty"); - SetFoP(manifest, "InstalledFromUrl", repo); - } - } - - if (!hasValidCustomRepoUrl) - { - try - { - foreach (var oldRepo in oldRepos) - { - _logger.LogInformation("* Removing old repo: {r}", oldRepo); - RemoveRepo(oldRepo); - } - } - finally - { - _logger.LogInformation("* Adding current repo: {r}", currentRepo); - AddRepo(currentRepo, true); - } - } - - // This time do it for real, and crash the game if we fail, to avoid saving a broken state - if (hasOldPluginRepoUrl) - { - try - { - _logger.LogInformation("* Updating plugins"); - foreach (var plugin in localPlugins) - { - var manifest = GetFoP(plugin.LocalPlugin, "manifest"); - if (manifest == null) - throw new Exception("Plugin manifest is null"); - var manifestFile = GetFoP(plugin.LocalPlugin, "manifestFile"); - if (manifestFile == null) - throw new Exception("Plugin manifestFile is null"); - var repo = GetFoP(manifest, "InstalledFromUrl"); - if (((string)repo).IsNullOrEmpty()) - throw new Exception("Plugin repo url is null or empty"); - SetFoP(manifest, "InstalledFromUrl", currentRepo); - Call(manifest, "Save", [manifestFile, "RepoChange"]); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception while changing plugin install repo"); - foreach (var oldRepo in oldRepos) - { - _logger.LogInformation("* Restoring old repo: {r}", oldRepo); - AddRepo(oldRepo, true); - } - } - } - - if (!hasValidCustomRepoUrl || hasOldPluginRepoUrl) - { - _logger.LogInformation("* Saving dalamud config"); - SaveDalamudConfig(); - _logger.LogInformation("* Reloading plugin masters"); - ReloadPluginMasters(); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception in RepoChangeService"); - } - }, default, 10, cancellationToken).ConfigureAwait(false); - _logger.LogInformation("Started RepoChangeService"); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _ = cancellationToken; - _logger.LogDebug("Stopping RepoChange Service"); - return Task.CompletedTask; - } -}