Compare commits
28 Commits
0.1.4
...
legacy-mit
Author | SHA1 | Date | |
---|---|---|---|
9e5398105e | |||
7c2f5445e1 | |||
ed4630cd51 | |||
ac231ef800 | |||
a3f5ee766a | |||
a697301895
|
|||
27a09aa420 | |||
86cb7d2221 | |||
bbf79bacac
|
|||
91d4cf8532 | |||
df7e6b2a51 | |||
b9ce34bf10 | |||
267fb2d25c | |||
291f005888
|
|||
2e1bc4840b | |||
8526f6c240 | |||
77d62b68b4 | |||
bc72660c7f | |||
8a27634d06 | |||
d7355c5eb9 | |||
46558bf4f9 | |||
e5dd13bc3e | |||
a1a3858653 | |||
deea0e90b1 | |||
d8b306148f | |||
702b91b87a | |||
084cfb518a | |||
fa2d647a64 |
@@ -122,24 +122,24 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public bool StorageisNTFS { get; private set; } = false;
|
public bool StorageisNTFS { get; private set; } = false;
|
||||||
|
|
||||||
public void StartMareWatcher(string? marePath)
|
public void StartMareWatcher(string? snowPath)
|
||||||
{
|
{
|
||||||
MareWatcher?.Dispose();
|
MareWatcher?.Dispose();
|
||||||
if (string.IsNullOrEmpty(marePath) || !Directory.Exists(marePath))
|
if (string.IsNullOrEmpty(snowPath) || !Directory.Exists(snowPath))
|
||||||
{
|
{
|
||||||
MareWatcher = null;
|
MareWatcher = null;
|
||||||
Logger.LogWarning("Mare file path is not set, cannot start the FSW for Mare.");
|
Logger.LogWarning("Snowcloak file path is not set, cannot start the FSW for Snowcloak.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DriveInfo di = new(new DirectoryInfo(_configService.Current.CacheFolder).Root.FullName);
|
DriveInfo di = new(new DirectoryInfo(_configService.Current.CacheFolder).Root.FullName);
|
||||||
StorageisNTFS = string.Equals("NTFS", di.DriveFormat, StringComparison.OrdinalIgnoreCase);
|
StorageisNTFS = string.Equals("NTFS", di.DriveFormat, StringComparison.OrdinalIgnoreCase);
|
||||||
Logger.LogInformation("Mare Storage is on NTFS drive: {isNtfs}", StorageisNTFS);
|
Logger.LogInformation("Snowcloak Storage is on NTFS drive: {isNtfs}", StorageisNTFS);
|
||||||
|
|
||||||
Logger.LogDebug("Initializing Mare FSW on {path}", marePath);
|
Logger.LogDebug("Initializing Mare FSW on {path}", snowPath);
|
||||||
MareWatcher = new()
|
MareWatcher = new()
|
||||||
{
|
{
|
||||||
Path = marePath,
|
Path = snowPath,
|
||||||
InternalBufferSize = 8388608,
|
InternalBufferSize = 8388608,
|
||||||
NotifyFilter = NotifyFilters.CreationTime
|
NotifyFilter = NotifyFilters.CreationTime
|
||||||
| NotifyFilters.LastWrite
|
| NotifyFilters.LastWrite
|
||||||
@@ -161,7 +161,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
if (string.IsNullOrEmpty(substPath))
|
if (string.IsNullOrEmpty(substPath))
|
||||||
{
|
{
|
||||||
SubstWatcher = null;
|
SubstWatcher = null;
|
||||||
Logger.LogWarning("Mare file path is not set, cannot start the FSW for Mare.");
|
Logger.LogWarning("Snowcloak file path is not set, cannot start the FSW for Snowcloak.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void MareWatcher_FileChanged(object sender, FileSystemEventArgs e)
|
private void MareWatcher_FileChanged(object sender, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.LogTrace("Mare FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
|
Logger.LogTrace("Snowcloak FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
|
||||||
|
|
||||||
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
||||||
|
|
||||||
|
@@ -114,7 +114,7 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
|
|||||||
bool penumbraAvailable = false;
|
bool penumbraAvailable = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
penumbraAvailable = _pluginLoaded && _pluginVersion >= new Version(1, 0, 1, 0);
|
penumbraAvailable = _pluginLoaded && _pluginVersion >= new Version(1, 5, 1, 0);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
penumbraAvailable &= _penumbraEnabled.Invoke();
|
penumbraAvailable &= _penumbraEnabled.Invoke();
|
||||||
@@ -225,9 +225,15 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
|
|||||||
|
|
||||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
|
Guid collId;
|
||||||
var collName = "ElfSync_" + uid;
|
var collName = "ElfSync_" + uid;
|
||||||
var collId = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
|
PenumbraApiEc penEC = _penumbraCreateNamedTemporaryCollection.Invoke(uid, collName, out collId);
|
||||||
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
|
logger.LogTrace("Creating Temp Collection {collName}, GUID: {collId}", collName, collId);
|
||||||
|
if (penEC != PenumbraApiEc.Success)
|
||||||
|
{
|
||||||
|
logger.LogError("Failed to create temporary collection for {collName} with error code {penEC}. Please include this line in any error reports", collName, penEC);
|
||||||
|
return Guid.Empty;
|
||||||
|
}
|
||||||
return collId;
|
return collId;
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
@@ -8,6 +8,6 @@ public static class ConfigurationExtensions
|
|||||||
{
|
{
|
||||||
return configuration.AcceptedAgreement && configuration.InitialScanComplete
|
return configuration.AcceptedAgreement && configuration.InitialScanComplete
|
||||||
&& !string.IsNullOrEmpty(configuration.CacheFolder)
|
&& !string.IsNullOrEmpty(configuration.CacheFolder)
|
||||||
&& Directory.Exists(configuration.CacheFolder);
|
&& Directory.Exists(configuration.CacheFolder) && configuration.AcceptedTOSVersion == configuration.ExpectedTOSVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,6 +7,8 @@ namespace MareSynchronos.MareConfiguration.Configurations;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class MareConfig : IMareConfiguration
|
public class MareConfig : IMareConfiguration
|
||||||
{
|
{
|
||||||
|
public int ExpectedTOSVersion = 2;
|
||||||
|
public int AcceptedTOSVersion { get; set; } = 0;
|
||||||
public bool AcceptedAgreement { get; set; } = false;
|
public bool AcceptedAgreement { get; set; } = false;
|
||||||
public string CacheFolder { get; set; } = string.Empty;
|
public string CacheFolder { get; set; } = string.Empty;
|
||||||
public bool DisableOptionalPluginWarnings { get; set; } = false;
|
public bool DisableOptionalPluginWarnings { get; set; } = false;
|
||||||
|
@@ -5,11 +5,11 @@ namespace MareSynchronos.MareConfiguration.Configurations;
|
|||||||
public class PlayerPerformanceConfig : IMareConfiguration
|
public class PlayerPerformanceConfig : IMareConfiguration
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 1;
|
||||||
public bool AutoPausePlayersExceedingThresholds { get; set; } = false;
|
public bool AutoPausePlayersExceedingThresholds { get; set; } = true;
|
||||||
public bool NotifyAutoPauseDirectPairs { get; set; } = true;
|
public bool NotifyAutoPauseDirectPairs { get; set; } = true;
|
||||||
public bool NotifyAutoPauseGroupPairs { get; set; } = false;
|
public bool NotifyAutoPauseGroupPairs { get; set; } = true;
|
||||||
public int VRAMSizeAutoPauseThresholdMiB { get; set; } = 550;
|
public int VRAMSizeAutoPauseThresholdMiB { get; set; } = 500;
|
||||||
public int TrisAutoPauseThresholdThousands { get; set; } = 375;
|
public int TrisAutoPauseThresholdThousands { get; set; } = 400;
|
||||||
public bool IgnoreDirectPairs { get; set; } = true;
|
public bool IgnoreDirectPairs { get; set; } = true;
|
||||||
public TextureShrinkMode TextureShrinkMode { get; set; } = TextureShrinkMode.Default;
|
public TextureShrinkMode TextureShrinkMode { get; set; } = TextureShrinkMode.Default;
|
||||||
public bool TextureShrinkDeleteOriginal { get; set; } = false;
|
public bool TextureShrinkDeleteOriginal { get; set; } = false;
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
|
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>Snowcloak</AssemblyName>
|
<AssemblyName>Snowcloak</AssemblyName>
|
||||||
<Version>0.1.3</Version>
|
<Version>0.1.8.1</Version>
|
||||||
<PackageProjectUrl>https://github.com/Eauldane/SnowcloakClient/</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Eauldane/SnowcloakClient/</PackageProjectUrl>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="!Exists('.\Penumbra.Api\Penumbra.Api.csproj')">
|
<ItemGroup Condition="!Exists('.\Penumbra.Api\Penumbra.Api.csproj')">
|
||||||
<PackageReference Include="Penumbra.Api" Version="5.10.0" />
|
<PackageReference Include="Penumbra.Api" Version="5.12.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="Exists('.\Glamourer.Api\Glamourer.Api.csproj')">
|
<ItemGroup Condition="Exists('.\Glamourer.Api\Glamourer.Api.csproj')">
|
||||||
|
@@ -86,8 +86,8 @@ public class Pair : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
OnClicked = action,
|
OnClicked = action,
|
||||||
PrefixColor = 559,
|
PrefixColor = 526,
|
||||||
PrefixChar = 'L'
|
PrefixChar = 'S'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -126,7 +126,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<BlockedCharacterHandler>();
|
collection.AddSingleton<BlockedCharacterHandler>();
|
||||||
collection.AddSingleton<IpcProvider>();
|
collection.AddSingleton<IpcProvider>();
|
||||||
collection.AddSingleton<VisibilityService>();
|
collection.AddSingleton<VisibilityService>();
|
||||||
collection.AddSingleton<RepoChangeService>();
|
|
||||||
collection.AddSingleton<EventAggregator>();
|
collection.AddSingleton<EventAggregator>();
|
||||||
collection.AddSingleton<DalamudUtilService>();
|
collection.AddSingleton<DalamudUtilService>();
|
||||||
collection.AddSingleton<DtrEntry>();
|
collection.AddSingleton<DtrEntry>();
|
||||||
@@ -210,7 +209,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<RepoChangeService>());
|
|
||||||
collection.AddHostedService(p => p.GetRequiredService<NoSnapService>());
|
collection.AddHostedService(p => p.GetRequiredService<NoSnapService>());
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
@@ -56,112 +56,9 @@ public sealed class RemoteConfigurationService
|
|||||||
|
|
||||||
private async Task DownloadConfig()
|
private async Task DownloadConfig()
|
||||||
{
|
{
|
||||||
string? jsonResponse = null;
|
// Removed Lop's remote config code. Function exists purely to keep things clean.
|
||||||
|
LoadConfig();
|
||||||
foreach (var remoteUrl in ConfigSources)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Fetching {url}", remoteUrl);
|
|
||||||
|
|
||||||
using var httpClient = new HttpClient(
|
|
||||||
new HttpClientHandler
|
|
||||||
{
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
MaxAutomaticRedirections = 5
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(6);
|
|
||||||
|
|
||||||
var ver = Assembly.GetExecutingAssembly().GetName().Version;
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", ver!.Major + "." + ver!.Minor + "." + ver!.Build));
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, remoteUrl);
|
|
||||||
|
|
||||||
if (remoteUrl.Equals(_configService.Current.Origin, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_configService.Current.ETag))
|
|
||||||
request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(_configService.Current.ETag));
|
|
||||||
|
|
||||||
if (_configService.Current.LastModified != null)
|
|
||||||
request.Headers.IfModifiedSince = _configService.Current.LastModified;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.NotModified)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Using cached remote configuration from {url}", remoteUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
var contentType = response.Content.Headers.ContentType?.MediaType;
|
|
||||||
|
|
||||||
if (contentType == null || !contentType.Equals("application/json", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("HTTP request for remote config failed: wrong MIME type");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Downloaded new configuration from {url}", remoteUrl);
|
|
||||||
|
|
||||||
_configService.Current.Origin = remoteUrl;
|
|
||||||
_configService.Current.ETag = response.Headers.ETag?.ToString() ?? string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (response.Content.Headers.Contains("Last-Modified"))
|
|
||||||
{
|
|
||||||
var lastModified = response.Content.Headers.GetValues("Last-Modified").First();
|
|
||||||
_configService.Current.LastModified = DateTimeOffset.Parse(lastModified, System.Globalization.CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_configService.Current.LastModified = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "HTTP request for remote config failed");
|
|
||||||
|
|
||||||
if (remoteUrl.Equals(_configService.Current.Origin, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_configService.Current.ETag = string.Empty;
|
|
||||||
_configService.Current.LastModified = null;
|
|
||||||
_configService.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonResponse == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Could not download remote config");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var jsonDoc = JsonNode.Parse(jsonResponse) as JsonObject;
|
|
||||||
|
|
||||||
if (jsonDoc == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Downloaded remote config is not a JSON object");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadConfig(jsonDoc);
|
|
||||||
}
|
|
||||||
catch (JsonException ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Invalid JSON in remote config response");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool VerifySignature(string message, ulong ts, string signature, string pubKey)
|
private static bool VerifySignature(string message, ulong ts, string signature, string pubKey)
|
||||||
@@ -172,29 +69,12 @@ public sealed class RemoteConfigurationService
|
|||||||
return Ed25519.Verify(sig, msg, pub);
|
return Ed25519.Verify(sig, msg, pub);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadConfig(JsonObject jsonDoc)
|
private void LoadConfig()
|
||||||
{
|
{
|
||||||
ulong ts = 1755859494;
|
ulong ts = 1755859494;
|
||||||
|
|
||||||
//if (ts <= _configService.Current.Timestamp)
|
var configString = "{\"mainServer\":{\"api_url\":\"wss://hub.snowcloak-sync.com/\",\"hub_url\":\"wss://hub.snowcloak-sync.com/mare\"},\"noSnap\":{\"listOfPlugins\":[\"Snapper\",\"Snappy\",\"Meddle.Plugin\"]}}";
|
||||||
//{
|
|
||||||
// _logger.LogDebug("Remote configuration is not newer than cached config");
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
var signatures = jsonDoc["sig"]!.AsObject();
|
|
||||||
var configString = "{\"mainServer\":{\"api_url\":\"wss://hub.snowcloak-sync.com/\",\"hub_url\":\"wss://hub.snowcloak-sync.com/mare\"},\"repoChange\":{\"current_repo\":\"https://hub.snowcloak-sync.com/repo.json\",\"valid_repos\":[\"https://hub.snowcloak-sync.com/repo.json\"]},\"noSnap\":{\"listOfPlugins\":[\"Snapper\",\"Snappy\",\"Meddle.Plugin\"]}}";
|
|
||||||
// var configString = jsonDoc["config"]!.GetValue<string>();
|
|
||||||
// bool verified = signatures.Any(sig =>
|
|
||||||
// ConfigPublicKeys.TryGetValue(sig.Key, out var pubKey) &&
|
|
||||||
// VerifySignature(configString, ts, sig.Value!.GetValue<string>(), pubKey));
|
|
||||||
|
|
||||||
//bool verified = true;
|
|
||||||
// if (!verified)
|
|
||||||
// {
|
|
||||||
// _logger.LogWarning("Could not verify signature for downloaded remote config");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
_configService.Current.Configuration = JsonNode.Parse(configString)!.AsObject();
|
_configService.Current.Configuration = JsonNode.Parse(configString)!.AsObject();
|
||||||
_configService.Current.Timestamp = ts;
|
_configService.Current.Timestamp = ts;
|
||||||
|
@@ -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; }
|
|
||||||
}
|
|
@@ -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<T>(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<T>(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<object>(), 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<object>(), 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<object> 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<System.Collections.IList>(conf, "ThirdRepoList").Add(instance!);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveRepo(string repoURL)
|
|
||||||
{
|
|
||||||
var toRemove = new List<object>();
|
|
||||||
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<RepoChangeService> _logger;
|
|
||||||
private readonly RemoteConfigurationService _remoteConfig;
|
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
|
||||||
private readonly IFramework _framework;
|
|
||||||
|
|
||||||
public RepoChangeService(ILogger<RepoChangeService> 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<RepoChangeConfig>("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<string> 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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -8,7 +8,7 @@
|
|||||||
"Tags": [
|
"Tags": [
|
||||||
"customization"
|
"customization"
|
||||||
],
|
],
|
||||||
"IconUrl": "https://i.imgur.com/cMKeOjm.jpeg",
|
"IconUrl": "https://raw.githubusercontent.com/Eauldane/SnowcloakClient/refs/heads/main/MareSynchronos/images/logo.png",
|
||||||
"RepoUrl": "https://github.com/Eauldane/SnowcloakClient",
|
"RepoUrl": "https://github.com/Eauldane/SnowcloakClient",
|
||||||
"CanUnloadAsync": true
|
"CanUnloadAsync": true
|
||||||
}
|
}
|
||||||
|
@@ -105,7 +105,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
if (_serverManager.CurrentApiUrl.Equals(ApiController.SnowcloakServiceUri, StringComparison.Ordinal))
|
if (_serverManager.CurrentApiUrl.Equals(ApiController.SnowcloakServiceUri, StringComparison.Ordinal))
|
||||||
UiSharedService.AccentColor = new Vector4(1.0f, 0.8666f, 0.06666f, 1.0f);
|
UiSharedService.AccentColor = new(0.4275f, 0.6863f, 1f, 1f);
|
||||||
else
|
else
|
||||||
UiSharedService.AccentColor = ImGuiColors.ParsedGreen;
|
UiSharedService.AccentColor = ImGuiColors.ParsedGreen;
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y);
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y);
|
||||||
|
@@ -163,13 +163,13 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
UiSharedService.Color(0, 0, 0, transparency), 1);
|
UiSharedService.Color(0, 0, 0, transparency), 1);
|
||||||
drawList.AddRectFilled(dlBarStart with { X = dlBarStart.X - dlBarBorder, Y = dlBarStart.Y - dlBarBorder },
|
drawList.AddRectFilled(dlBarStart with { X = dlBarStart.X - dlBarBorder, Y = dlBarStart.Y - dlBarBorder },
|
||||||
dlBarEnd with { X = dlBarEnd.X + dlBarBorder, Y = dlBarEnd.Y + dlBarBorder },
|
dlBarEnd with { X = dlBarEnd.X + dlBarBorder, Y = dlBarEnd.Y + dlBarBorder },
|
||||||
UiSharedService.Color(220, 220, 220, transparency), 1);
|
UiSharedService.Color(220, 220, 255, transparency), 1);
|
||||||
drawList.AddRectFilled(dlBarStart, dlBarEnd,
|
drawList.AddRectFilled(dlBarStart, dlBarEnd,
|
||||||
UiSharedService.Color(0, 0, 0, transparency), 1);
|
UiSharedService.Color(0, 0, 0, transparency), 1);
|
||||||
var dlProgressPercent = transferredBytes / (double)totalBytes;
|
var dlProgressPercent = transferredBytes / (double)totalBytes;
|
||||||
drawList.AddRectFilled(dlBarStart,
|
drawList.AddRectFilled(dlBarStart,
|
||||||
dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) },
|
dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) },
|
||||||
UiSharedService.Color(50, 205, 50, transparency), 1);
|
UiSharedService.Color(100, 100, 255, transparency), 1);
|
||||||
|
|
||||||
if (_configService.Current.TransferBarsShowText)
|
if (_configService.Current.TransferBarsShowText)
|
||||||
{
|
{
|
||||||
|
@@ -106,8 +106,9 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (_uiShared.IsInGpose) return;
|
if (_uiShared.IsInGpose) return;
|
||||||
|
|
||||||
if (!_configService.Current.AcceptedAgreement && !_readFirstPage)
|
if ((!_configService.Current.AcceptedAgreement || _configService.Current.AcceptedTOSVersion != _configService.Current.ExpectedTOSVersion) && !_readFirstPage)
|
||||||
{
|
{
|
||||||
|
// TODO: The UI bugs hard if this page *isn't* shown before the new TOS. There's probably a way around it.
|
||||||
_uiShared.BigText("Welcome to Snowcloak");
|
_uiShared.BigText("Welcome to Snowcloak");
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
UiSharedService.TextWrapped("Snowcloak is a plugin that will replicate your full current character state including all Penumbra mods to other paired users. " +
|
UiSharedService.TextWrapped("Snowcloak is a plugin that will replicate your full current character state including all Penumbra mods to other paired users. " +
|
||||||
@@ -125,7 +126,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
_timeoutTask = Task.Run(async () =>
|
_timeoutTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
for (int i = 10; i > 0; i--)
|
for (int i = 45; i > 0; i--)
|
||||||
{
|
{
|
||||||
_timeoutLabel = $"'I agree' button will be available in {i}s";
|
_timeoutLabel = $"'I agree' button will be available in {i}s";
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
||||||
@@ -136,7 +137,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!_configService.Current.AcceptedAgreement && _readFirstPage)
|
else if ((!_configService.Current.AcceptedAgreement || _configService.Current.AcceptedTOSVersion != _configService.Current.ExpectedTOSVersion) && _readFirstPage)
|
||||||
{
|
{
|
||||||
using (_uiShared.UidFont.Push())
|
using (_uiShared.UidFont.Push())
|
||||||
{
|
{
|
||||||
@@ -153,23 +154,35 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
UiSharedService.TextWrapped("""
|
UiSharedService.TextWrapped("""
|
||||||
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.
|
To use Snowcloak, you must be over the age of 18, or 21 in some jurisdictions.
|
||||||
""");
|
""");
|
||||||
UiSharedService.TextWrapped("""
|
UiSharedService.TextWrapped("""
|
||||||
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.
|
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.
|
||||||
""");
|
""");
|
||||||
UiSharedService.TextWrapped("""
|
UiSharedService.TextWrapped("""
|
||||||
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.
|
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.
|
||||||
""");
|
""");
|
||||||
UiSharedService.TextWrapped("""
|
UiSharedService.TextWrapped("""
|
||||||
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.
|
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.
|
||||||
""");
|
""");
|
||||||
UiSharedService.TextWrapped("""
|
UiSharedService.TextWrapped("""
|
||||||
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.
|
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.
|
||||||
""");
|
""");
|
||||||
UiSharedService.TextWrapped("""
|
UiSharedService.TextWrapped("""
|
||||||
This service is provided as-is.
|
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.
|
||||||
""");
|
""");
|
||||||
|
UiSharedService.TextWrapped("""
|
||||||
|
Accounts that are inactive for ninety (90) days will be deleted for privacy reasons.
|
||||||
|
""");
|
||||||
|
UiSharedService.TextWrapped("""
|
||||||
|
Snowcloak is operated from servers located in the European Union. You agree not to upload any content to the service that violates EU law; and more specifically, German law.
|
||||||
|
""");
|
||||||
|
UiSharedService.TextWrapped("""
|
||||||
|
You may delete your account at any time from within the Settings panel of the plugin. Any mods unique to you will then be removed from the server within 14 days.
|
||||||
|
""");
|
||||||
|
UiSharedService.TextWrapped("""
|
||||||
|
This service is provided as-is.
|
||||||
|
""");
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (_timeoutTask?.IsCompleted ?? true)
|
if (_timeoutTask?.IsCompleted ?? true)
|
||||||
@@ -177,6 +190,7 @@ This service is provided as-is.
|
|||||||
if (ImGui.Button("I agree##toSetup"))
|
if (ImGui.Button("I agree##toSetup"))
|
||||||
{
|
{
|
||||||
_configService.Current.AcceptedAgreement = true;
|
_configService.Current.AcceptedAgreement = true;
|
||||||
|
_configService.Current.AcceptedTOSVersion = _configService.Current.ExpectedTOSVersion;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +199,7 @@ This service is provided as-is.
|
|||||||
UiSharedService.TextWrapped(_timeoutLabel);
|
UiSharedService.TextWrapped(_timeoutLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_configService.Current.AcceptedAgreement
|
else if ((!_configService.Current.AcceptedAgreement || _configService.Current.AcceptedTOSVersion != _configService.Current.ExpectedTOSVersion)
|
||||||
&& (string.IsNullOrEmpty(_configService.Current.CacheFolder)
|
&& (string.IsNullOrEmpty(_configService.Current.CacheFolder)
|
||||||
|| !_configService.Current.InitialScanComplete
|
|| !_configService.Current.InitialScanComplete
|
||||||
|| !Directory.Exists(_configService.Current.CacheFolder)))
|
|| !Directory.Exists(_configService.Current.CacheFolder)))
|
||||||
|
@@ -581,32 +581,32 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_uiShared.BigText("Advanced");
|
_uiShared.BigText("Advanced");
|
||||||
|
|
||||||
bool mareApi = _configService.Current.MareAPI;
|
bool mareApi = _configService.Current.MareAPI;
|
||||||
if (ImGui.Checkbox("Enable Mare Synchronos API", ref mareApi))
|
if (ImGui.Checkbox("Enable Snowcloak Sync API", ref mareApi))
|
||||||
{
|
{
|
||||||
_configService.Current.MareAPI = mareApi;
|
_configService.Current.MareAPI = mareApi;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
_ipcProvider.HandleMareImpersonation();
|
_ipcProvider.HandleMareImpersonation();
|
||||||
}
|
}
|
||||||
_uiShared.DrawHelpText("Enables handling of the Mare Synchronos API. This currently includes:\n\n" +
|
_uiShared.DrawHelpText("Enables handling of the Snowcloak Sync API. This currently includes:\n\n" +
|
||||||
" - MCDF loading support for other plugins\n" +
|
" - MCDF loading support for other plugins\n" +
|
||||||
" - Blocking Moodles applications to paired users\n\n" +
|
" - Blocking Moodles applications to paired users\n\n" +
|
||||||
"If the Mare Synchronos plugin is loaded while this option is enabled, control of its API will be relinquished.");
|
"If the Snowcloak Sync plugin is loaded while this option is enabled, control of its API will be relinquished.");
|
||||||
|
|
||||||
using (_ = ImRaii.PushIndent())
|
using (_ = ImRaii.PushIndent())
|
||||||
{
|
{
|
||||||
ImGui.SameLine(300.0f * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(300.0f * ImGuiHelpers.GlobalScale);
|
||||||
if (_ipcProvider.ImpersonationActive)
|
if (_ipcProvider.ImpersonationActive)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("Mare API active!", ImGuiColors.HealerGreen);
|
UiSharedService.ColorTextWrapped("Snowcloak API active!", ImGuiColors.HealerGreen);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!mareApi)
|
if (!mareApi)
|
||||||
UiSharedService.ColorTextWrapped("Mare API inactive: Option is disabled", ImGuiColors.DalamudYellow);
|
UiSharedService.ColorTextWrapped("Snowcloak API inactive: Option is disabled", ImGuiColors.DalamudYellow);
|
||||||
else if (_ipcProvider.MarePluginEnabled)
|
else if (_ipcProvider.MarePluginEnabled)
|
||||||
UiSharedService.ColorTextWrapped("Mare API inactive: Mare plugin is loaded", ImGuiColors.DalamudYellow);
|
UiSharedService.ColorTextWrapped("Snowcloak API inactive: Snowcloak plugin is loaded", ImGuiColors.DalamudYellow);
|
||||||
else
|
else
|
||||||
UiSharedService.ColorTextWrapped("Mare API inactive: Unknown reason", ImGuiColors.DalamudRed);
|
UiSharedService.ColorTextWrapped("Snowcloak API inactive: Unknown reason", ImGuiColors.DalamudRed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -113,9 +113,9 @@
|
|||||||
},
|
},
|
||||||
"Penumbra.Api": {
|
"Penumbra.Api": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[5.10.0, )",
|
"requested": "[5.12.0, )",
|
||||||
"resolved": "5.10.0",
|
"resolved": "5.12.0",
|
||||||
"contentHash": "ZRIIXQCluNlLoI4I4gNYuYXqb7p48OR1P2/LcL27owToR/A/IG0mH8Ks2HguB/Kjq4RpCinaVkyufpGxGspbTA=="
|
"contentHash": "XGWviAZgokj2djpH50FWgM24jOTpKUuDHvd0HwrzBRY6BEMmpb3HfGIl8+BDE/DqbpH63u6aO2TvzUV6BmXT5w=="
|
||||||
},
|
},
|
||||||
"SonarAnalyzer.CSharp": {
|
"SonarAnalyzer.CSharp": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
# Snowcloak Sync
|
# Snowcloak Sync
|
||||||
|
The snow may cloak the world in silence, but come in, warm up, and reveal your true colours.
|
||||||
|
|
||||||
Come in from the cold, and be your true self.
|
A Dalamud plugin.
|
||||||
|
|
||||||
A Dalamud plugin.
|
[](https://discord.gg/snowcloak)
|
||||||
|
Reference in New Issue
Block a user