diff --git a/src/NebulaAuth/App.xaml.cs b/src/NebulaAuth/App.xaml.cs
index 0d90552..a140999 100644
--- a/src/NebulaAuth/App.xaml.cs
+++ b/src/NebulaAuth/App.xaml.cs
@@ -3,8 +3,6 @@ using System.Windows;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Exceptions;
-using NebulaAuth.Model.MAAC;
-using NebulaAuth.Model.Mafiles;
namespace NebulaAuth;
@@ -17,12 +15,8 @@ public partial class App
var splashScreen = new SplashScreen("Theme\\SplashScreen.png");
splashScreen.Show(false, true);
base.OnStartup(e);
- LocManager.Init();
- LocManager.SetApplicationLocalization(Settings.Instance.Language);
- Shell.Initialize();
- var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
- await Storage.Initialize(threads);
- MAACStorage.Initialize();
+
+ await Shell.Initialize();
var mainWindow = new MainWindow();
Current.MainWindow = mainWindow;
mainWindow.Show();
diff --git a/src/NebulaAuth/Converters/Converters.xaml b/src/NebulaAuth/Converters/Converters.xaml
index 9a7a20c..d99c88e 100644
--- a/src/NebulaAuth/Converters/Converters.xaml
+++ b/src/NebulaAuth/Converters/Converters.xaml
@@ -23,6 +23,7 @@
+
True
False
diff --git a/src/NebulaAuth/Converters/InverseBooleanToVisibilityConverter.cs b/src/NebulaAuth/Converters/InverseBooleanToVisibilityConverter.cs
new file mode 100644
index 0000000..e2baf75
--- /dev/null
+++ b/src/NebulaAuth/Converters/InverseBooleanToVisibilityConverter.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace NebulaAuth.Converters;
+
+public class InverseBooleanToVisibilityConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value is bool b && !b ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value is Visibility v && v != Visibility.Visible;
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/Core/DialogsController.cs b/src/NebulaAuth/Core/DialogsController.cs
index 7be3a6c..9ee4da4 100644
--- a/src/NebulaAuth/Core/DialogsController.cs
+++ b/src/NebulaAuth/Core/DialogsController.cs
@@ -113,6 +113,16 @@ public static class DialogsController
await DialogHost.Show(dialog);
}
+ public static async Task ShowExportDialog()
+ {
+ var vm = new MafileExporterVM();
+ var dialog = new MafileExporterView
+ {
+ DataContext = vm
+ };
+ await DialogHost.Show(dialog);
+ }
+
#region CommonDialogs
public static async Task ShowConfirmCancelDialog(string? msg = null)
diff --git a/src/NebulaAuth/Core/TrayManager.cs b/src/NebulaAuth/Core/TrayManager.cs
index 44e9b20..988991d 100644
--- a/src/NebulaAuth/Core/TrayManager.cs
+++ b/src/NebulaAuth/Core/TrayManager.cs
@@ -69,7 +69,7 @@ public static class TrayManager
private static void HideMainWindow()
{
if (!Init) return;
- if (IsEnabled == false) return;
+ if (!IsEnabled) return;
_notifyIcon.Visible = true;
MainWindow.Hide();
}
diff --git a/src/NebulaAuth/MainWindow.xaml b/src/NebulaAuth/MainWindow.xaml
index a151e3a..40f25d5 100644
--- a/src/NebulaAuth/MainWindow.xaml
+++ b/src/NebulaAuth/MainWindow.xaml
@@ -73,7 +73,7 @@
Kind="FileReplaceOutline" />
-
+
@@ -356,7 +358,7 @@
+ VerticalAlignment="Center" />
diff --git a/src/NebulaAuth/MainWindow.xaml.cs b/src/NebulaAuth/MainWindow.xaml.cs
index 13fda2a..760919d 100644
--- a/src/NebulaAuth/MainWindow.xaml.cs
+++ b/src/NebulaAuth/MainWindow.xaml.cs
@@ -30,7 +30,7 @@ public partial class MainWindow
private async void OnApplicationStarted(object? sender, EventArgs e)
{
((MainVM) DataContext).CurrentDialogHost = DialogHostInstance;
- if (Settings.Instance.IsPasswordSet == false) return;
+ if (!Settings.Instance.IsPasswordSet) return;
Topmost = false;
await Dispatcher.InvokeAsync(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
}
@@ -46,7 +46,7 @@ public partial class MainWindow
var result = await DialogHost.Show(dialog);
var pass = vm.Password;
- if (result is true && string.IsNullOrWhiteSpace(pass) == false)
+ if (result is true && !string.IsNullOrWhiteSpace(pass))
{
PHandler.SetPassword(pass);
}
@@ -79,12 +79,12 @@ public partial class MainWindow
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
- if (int.TryParse(e.Text, out _) == false) e.Handled = true;
+ if (!int.TryParse(e.Text, out _)) e.Handled = true;
}
private void Rectangle_DragEnter(object sender, DragEventArgs e)
{
- if (e.Data.GetDataPresent(DataFormats.FileDrop) == false)
+ if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Handled = true;
return;
@@ -96,7 +96,7 @@ public partial class MainWindow
private async void Rectangle_Drop(object sender, DragEventArgs e)
{
- if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
+ if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
var filePaths = (string[]) e.Data.GetData(DataFormats.FileDrop)!;
if (filePaths.Length == 0) return;
if (DataContext is MainVM mainVm)
diff --git a/src/NebulaAuth/Model/MAAC/PortableMaClient.cs b/src/NebulaAuth/Model/MAAC/PortableMaClient.cs
index 2ec1137..0de4ec7 100644
--- a/src/NebulaAuth/Model/MAAC/PortableMaClient.cs
+++ b/src/NebulaAuth/Model/MAAC/PortableMaClient.cs
@@ -187,7 +187,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
private bool IgnoreTimerErrors()
{
- if (Settings.Instance.IgnorePatchTuesdayErrors == false) return false;
+ if (!Settings.Instance.IgnorePatchTuesdayErrors) return false;
var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone);
diff --git a/src/NebulaAuth/Model/MaClient.cs b/src/NebulaAuth/Model/MaClient.cs
index b37d974..3be9ca6 100644
--- a/src/NebulaAuth/Model/MaClient.cs
+++ b/src/NebulaAuth/Model/MaClient.cs
@@ -153,7 +153,7 @@ public static class MaClient
if (mafile.SessionData.RefreshToken.IsExpired)
throw new SessionPermanentlyExpiredException();
- if (ignoreAccessToken == false)
+ if (!ignoreAccessToken)
{
var access = mafile.SessionData.GetMobileToken();
if (access == null || access.Value.IsExpired)
diff --git a/src/NebulaAuth/Model/MafileExport/ExportResult.cs b/src/NebulaAuth/Model/MafileExport/ExportResult.cs
new file mode 100644
index 0000000..9d47215
--- /dev/null
+++ b/src/NebulaAuth/Model/MafileExport/ExportResult.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using NebulaAuth.Model.Entities;
+
+namespace NebulaAuth.Model.MafileExport;
+
+public class ExportResult
+{
+ public string? Error { get; }
+ public Dictionary? Exported { get; }
+ public List? NotFound { get; }
+ public List? Conflict { get; }
+
+ [MemberNotNullWhen(true, nameof(Exported))]
+ [MemberNotNullWhen(true, nameof(NotFound))]
+ [MemberNotNullWhen(true, nameof(Conflict))]
+ [MemberNotNullWhen(false, nameof(Error))]
+ public bool Success => Error == null;
+
+ private ExportResult(string? error, Dictionary? exported, List? notFound,
+ List? conflict)
+ {
+ Error = error;
+ Exported = exported;
+ NotFound = notFound;
+ Conflict = conflict;
+ }
+
+ public ExportResult(Dictionary exported, List notFound, List conflict)
+ : this(null, exported, notFound, conflict)
+ {
+ }
+
+
+ public ExportResult(string? error)
+ : this(error, null, null, null)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/Model/MafileExport/MafileExportTemplate.cs b/src/NebulaAuth/Model/MafileExport/MafileExportTemplate.cs
new file mode 100644
index 0000000..3a5311f
--- /dev/null
+++ b/src/NebulaAuth/Model/MafileExport/MafileExportTemplate.cs
@@ -0,0 +1,16 @@
+namespace NebulaAuth.Model.MafileExport;
+
+public class MafileExportTemplate
+{
+ public string Name { get; set; }
+ public bool UseLoginAsMafileName { get; set; }
+ public bool IncludeSharedSecret { get; set; }
+ public bool IncludeIdentitySecret { get; set; }
+ public bool IncludeRCode { get; set; }
+ public bool IncludeSessionData { get; set; }
+ public bool IncludeOtherInfo { get; set; }
+ public bool IncludeNebulaProxy { get; set; }
+ public bool IncludeNebulaPassword { get; set; }
+ public bool IncludeNebulaGroup { get; set; }
+ public string? Path { get; set; }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/Model/MafileExport/MafileExporter.cs b/src/NebulaAuth/Model/MafileExport/MafileExporter.cs
new file mode 100644
index 0000000..141cd38
--- /dev/null
+++ b/src/NebulaAuth/Model/MafileExport/MafileExporter.cs
@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using AchiesUtilities.Extensions;
+using NebulaAuth.Core;
+using NebulaAuth.Model.Entities;
+using NebulaAuth.Model.Mafiles;
+using SteamLibForked.Models.SteamIds;
+
+namespace NebulaAuth.Model.MafileExport;
+
+public static class MafileExporter
+{
+ public static async Task ExportMafiles(IEnumerable keys, MafileExportTemplate template)
+ {
+ if (template.Path == null || !PathIsValid(template.Path))
+ {
+ return new ExportResult(LocManager.GetCodeBehindOrDefault("InvalidPath", "MafileExporter.InvalidPath"));
+ }
+
+ if (!Directory.Exists(template.Path))
+ {
+ Directory.CreateDirectory(template.Path);
+ }
+
+ if (IsNebulaUtilizedDirectory(template.Path))
+ {
+ return new ExportResult(LocManager.GetCodeBehindOrDefault("NebulaUtilizedDirectory",
+ "MafileExporter.NebulaUtilizedDirectory"));
+ }
+
+ if (!IsAllowedToWrite(template.Path))
+ {
+ return new ExportResult(LocManager.GetCodeBehindOrDefault("AccessDenied", "MafileExporter.AccessDenied"));
+ }
+
+
+ var exported = new Dictionary();
+ var notFound = new List();
+ var conflict = new List();
+
+ foreach (var key in keys)
+ {
+ SteamId? steamId = null;
+ if (SteamId64.TryParse(key, out var id64))
+ {
+ steamId = id64;
+ }
+
+ var maf = Storage.MaFiles.FirstOrDefault(m => m.AccountName.EqualsIgnoreCase(key) || m.SteamId == steamId);
+ if (maf != null)
+ {
+ var fileName = await ExportMafile(template.Path, template, maf);
+ if (fileName == null)
+ {
+ conflict.Add(key);
+ continue;
+ }
+
+ exported[maf] = fileName;
+ }
+ else
+ {
+ notFound.Add(key);
+ }
+ }
+
+ return new ExportResult(exported, notFound, conflict);
+ }
+
+ private static async Task ExportMafile(string path, MafileExportTemplate template, Mafile mafile)
+ {
+ var serializeMaf = new Mafile
+ {
+ SharedSecret = template.IncludeSharedSecret ? mafile.SharedSecret : null!,
+ IdentitySecret = template.IncludeIdentitySecret ? mafile.IdentitySecret : null!,
+ DeviceId = template.IncludeIdentitySecret ? mafile.DeviceId : null!,
+ RevocationCode = template.IncludeRCode ? mafile.RevocationCode : null!,
+ AccountName = mafile.AccountName,
+ SessionData = template.IncludeSessionData ? mafile.SessionData : null!,
+ SteamId = mafile.SteamId,
+ ServerTime = template.IncludeOtherInfo ? mafile.ServerTime : 0,
+ SerialNumber = template.IncludeOtherInfo ? mafile.SerialNumber : 0,
+ Uri = template.IncludeOtherInfo ? mafile.Uri : null!,
+ TokenGid = template.IncludeOtherInfo ? mafile.TokenGid : null!,
+ Secret1 = template.IncludeOtherInfo ? mafile.Secret1 : null!,
+ Proxy = template.IncludeNebulaProxy ? mafile.Proxy : null!,
+ Group = template.IncludeNebulaGroup ? mafile.Group : null!,
+ Password = template.IncludeNebulaPassword ? mafile.Password : null!,
+ LinkedClient = null,
+ Filename = null
+ };
+
+ var serialized = NebulaSerializer.SerializeMafile(serializeMaf);
+ var strategy = template.UseLoginAsMafileName ? MafileNamingStrategy.Login : MafileNamingStrategy.SteamId;
+ var fileName = strategy.GetMafileName(serializeMaf);
+ var fullPath = Path.Combine(path, fileName);
+ if (File.Exists(fullPath))
+ {
+ return null;
+ }
+
+ await File.WriteAllTextAsync(fullPath, serialized);
+ return fullPath;
+ }
+
+ private static bool PathIsValid(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path)) return false;
+
+ try
+ {
+ var full = Path.GetFullPath(path);
+
+ var invalidPathChars = Path.GetInvalidPathChars();
+ if (full.IndexOfAny(invalidPathChars) >= 0) return false;
+
+ var invalidFileChars = Path.GetInvalidFileNameChars();
+
+ foreach (var part in full.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
+ {
+ if (string.IsNullOrEmpty(part)) continue;
+ if (part is [_, ':']) continue;
+
+ if (part.IndexOfAny(invalidFileChars) >= 0) return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static bool IsAllowedToWrite(string path)
+ {
+ try
+ {
+ var testFilePath = Path.Combine(path, "test.tmp");
+ using (var fs = File.Create(testFilePath))
+ {
+ }
+
+ File.Delete(testFilePath);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static bool IsNebulaUtilizedDirectory(string path)
+ {
+ return Storage.MafilesDirectories.Any(x => Path.GetFullPath(path).EqualsIgnoreCase(x));
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/Model/MafileExport/MafileExporterStorage.cs b/src/NebulaAuth/Model/MafileExport/MafileExporterStorage.cs
new file mode 100644
index 0000000..6445994
--- /dev/null
+++ b/src/NebulaAuth/Model/MafileExport/MafileExporterStorage.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace NebulaAuth.Model.MafileExport;
+
+public static class MafileExporterStorage
+{
+ private const string FILE_NAME = "export_templates.json";
+ public static ObservableCollection Templates { get; } = new();
+
+
+ public static void Initialize()
+ {
+ LoadOrCreateTemplate();
+ }
+
+ public static MafileExportTemplate? GetTemplate(string name)
+ {
+ return Templates.FirstOrDefault(x => x.Name == name);
+ }
+
+ public static void AddTemplate(MafileExportTemplate template)
+ {
+ Templates.Add(template);
+ Save();
+ }
+
+ public static void DeleteTemplate(MafileExportTemplate template)
+ {
+ if (Templates.Remove(template))
+ {
+ Save();
+ }
+ }
+
+ private static void LoadOrCreateTemplate()
+ {
+ if (!File.Exists(FILE_NAME))
+ {
+ Templates.Clear();
+ foreach (var mafileExportTemplate in CreateDefaultTemplate())
+ {
+ Templates.Add(mafileExportTemplate);
+ }
+
+ Save();
+ return;
+ }
+
+ var json = File.ReadAllText(FILE_NAME);
+ try
+ {
+ var loadedTemplates = JsonConvert.DeserializeObject>(json);
+ if (loadedTemplates != null)
+ {
+ Templates.Clear();
+ foreach (var tmpl in loadedTemplates)
+ {
+ Templates.Add(tmpl);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Shell.Logger.Error(ex, $"Failed to deserialize {FILE_NAME}");
+ Templates.Clear();
+ Save();
+ }
+ }
+
+ public static void Save()
+ {
+ var json = JsonConvert.SerializeObject(Templates, Formatting.Indented);
+ File.WriteAllText(FILE_NAME, json);
+ }
+
+ private static IEnumerable CreateDefaultTemplate()
+ {
+ return
+ [
+ new MafileExportTemplate
+ {
+ Name = "Default",
+ UseLoginAsMafileName = false,
+ IncludeSharedSecret = true,
+ IncludeIdentitySecret = true,
+ IncludeRCode = true,
+ IncludeSessionData = true,
+ IncludeOtherInfo = true,
+ IncludeNebulaProxy = true,
+ IncludeNebulaPassword = true,
+ IncludeNebulaGroup = true,
+ Path = null
+ },
+ new MafileExportTemplate
+ {
+ Name = "Auth only",
+ UseLoginAsMafileName = true,
+ IncludeSharedSecret = true,
+ IncludeIdentitySecret = false,
+ IncludeRCode = false,
+ IncludeSessionData = false,
+ IncludeOtherInfo = false,
+ IncludeNebulaProxy = false,
+ IncludeNebulaPassword = false,
+ IncludeNebulaGroup = false,
+ Path = null
+ }
+ ];
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/Model/Mafiles/MafilesStorageHelper.cs b/src/NebulaAuth/Model/Mafiles/MafilesStorageHelper.cs
index bda9e96..c1dcae9 100644
--- a/src/NebulaAuth/Model/Mafiles/MafilesStorageHelper.cs
+++ b/src/NebulaAuth/Model/Mafiles/MafilesStorageHelper.cs
@@ -1,12 +1,13 @@
-using NebulaAuth.Model.Entities;
-using System.IO;
+using System.IO;
+using NebulaAuth.Model.Entities;
namespace NebulaAuth.Model.Mafiles;
internal static class MafilesStorageHelper
{
///
- /// Returns or creates a new one and updates the property. is always not after this method call.
+ /// Returns or creates a new one and updates the property.
+ /// is always not after this method call.
///
///
///
diff --git a/src/NebulaAuth/Model/Mafiles/Storage.cs b/src/NebulaAuth/Model/Mafiles/Storage.cs
index ce64835..f11f93f 100644
--- a/src/NebulaAuth/Model/Mafiles/Storage.cs
+++ b/src/NebulaAuth/Model/Mafiles/Storage.cs
@@ -22,12 +22,19 @@ public static class Storage
public const string DIR_MAFILES = "maFiles";
public const string DIR_REMOVED_MAFILES = "maFiles_removed";
public const string DIR_BACKUP_MAFILES = "maFiles_backup";
+
+ public static readonly string[] MafilesDirectories;
public static string MafilesDirectory { get; } = Path.GetFullPath(DIR_MAFILES);
public static string RemovedMafilesDirectory { get; } = Path.GetFullPath(DIR_REMOVED_MAFILES);
public static string BackupMafilesDirectory { get; } = Path.GetFullPath(DIR_BACKUP_MAFILES);
public static ObservableCollection MaFiles { get; private set; } = [];
+ static Storage()
+ {
+ MafilesDirectories = [MafilesDirectory, RemovedMafilesDirectory, BackupMafilesDirectory];
+ }
+
public static async Task Initialize(int threadCount, CancellationToken token = default)
{
Directory.CreateDirectory(MafilesDirectory);
@@ -60,6 +67,7 @@ public static class Storage
}, token);
MaFiles = new ObservableCollection(localList.OrderBy(m => m.AccountName));
+
}
///
diff --git a/src/NebulaAuth/Model/NebulaSerializer.cs b/src/NebulaAuth/Model/NebulaSerializer.cs
index 0783cf9..7908887 100644
--- a/src/NebulaAuth/Model/NebulaSerializer.cs
+++ b/src/NebulaAuth/Model/NebulaSerializer.cs
@@ -38,7 +38,7 @@ public static class NebulaSerializer
var data = Serializer.Deserialize(cont);
var mobileData = data.Data;
var info = data.Info;
- if (info.IsExtended == false)
+ if (!info.IsExtended)
throw new FormatException("Mafile is not extended data");
@@ -58,7 +58,7 @@ public static class NebulaSerializer
private static T? GetPropertyValue(string name, Dictionary dictionary)
{
- if (dictionary.TryGetValue(name, out var prop) == false) return default;
+ if (!dictionary.TryGetValue(name, out var prop)) return default;
var value = prop.Value;
try
{
diff --git a/src/NebulaAuth/Model/ProxyStorage.cs b/src/NebulaAuth/Model/ProxyStorage.cs
index 53cda1c..7f94419 100644
--- a/src/NebulaAuth/Model/ProxyStorage.cs
+++ b/src/NebulaAuth/Model/ProxyStorage.cs
@@ -28,7 +28,7 @@ public static class ProxyStorage
static ProxyStorage()
{
- if (File.Exists("proxies.json") == false)
+ if (!File.Exists("proxies.json"))
return;
try
{
diff --git a/src/NebulaAuth/Model/SessionHandler.cs b/src/NebulaAuth/Model/SessionHandler.cs
index a04ed52..7e3a0e3 100644
--- a/src/NebulaAuth/Model/SessionHandler.cs
+++ b/src/NebulaAuth/Model/SessionHandler.cs
@@ -45,7 +45,7 @@ public static partial class SessionHandler
return await func();
}
catch (SessionInvalidException ex)
- when (refreshTokenExpired == false || password != null)
+ when (!refreshTokenExpired || password != null)
{
if (ex is SessionPermanentlyExpiredException)
{
diff --git a/src/NebulaAuth/Model/Settings.cs b/src/NebulaAuth/Model/Settings.cs
index 0e8746d..e4d3963 100644
--- a/src/NebulaAuth/Model/Settings.cs
+++ b/src/NebulaAuth/Model/Settings.cs
@@ -25,7 +25,7 @@ public partial class Settings : ObservableObject
static Settings()
{
- if (File.Exists("settings.json") == false)
+ if (!File.Exists("settings.json"))
{
Instance = new Settings();
Instance.PropertyChanged += SettingsOnPropertyChanged;
diff --git a/src/NebulaAuth/Model/Shell.cs b/src/NebulaAuth/Model/Shell.cs
index f9d598a..7a74a5d 100644
--- a/src/NebulaAuth/Model/Shell.cs
+++ b/src/NebulaAuth/Model/Shell.cs
@@ -1,6 +1,12 @@
using System;
+using System.IO;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using NebulaAuth.Core;
using NebulaAuth.Model.Exceptions;
+using NebulaAuth.Model.MAAC;
+using NebulaAuth.Model.MafileExport;
+using NebulaAuth.Model.Mafiles;
using NLog;
using NLog.Extensions.Logging;
using SteamLib.Core;
@@ -14,8 +20,11 @@ public static class Shell
public static Logger Logger { get; private set; } = null!;
public static ILogger ExtensionsLogger { get; private set; } = null!;
- public static void Initialize()
+ public static async Task Initialize()
{
+ File.Delete("log.log");
+ LocManager.Init();
+ LocManager.SetApplicationLocalization(Settings.Instance.Language);
Logger = LogManager.GetLogger("Logger");
var lp = new NLogLoggerProvider();
var logger = lp.CreateLogger("SteamLib");
@@ -27,13 +36,18 @@ public static class Shell
try
{
- TimeAligner.AlignTime();
+ await TimeAligner.AlignTimeAsync();
}
catch (Exception ex)
{
throw new CantAlignTimeException("", ex);
}
+ var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
+ await Storage.Initialize(threads);
+ MAACStorage.Initialize();
+ MafileExporterStorage.Initialize();
+
ExtensionsLogger.LogDebug("Application started");
}
diff --git a/src/NebulaAuth/NLog.config b/src/NebulaAuth/NLog.config
index c9bf400..259810c 100644
--- a/src/NebulaAuth/NLog.config
+++ b/src/NebulaAuth/NLog.config
@@ -5,8 +5,7 @@
throwExceptions="true">
-
+
diff --git a/src/NebulaAuth/NebulaAuth.csproj b/src/NebulaAuth/NebulaAuth.csproj
index 5b58580..6237b34 100644
--- a/src/NebulaAuth/NebulaAuth.csproj
+++ b/src/NebulaAuth/NebulaAuth.csproj
@@ -28,7 +28,7 @@
-
+
diff --git a/src/NebulaAuth/Utility/LanguageUtility.cs b/src/NebulaAuth/Utility/LanguageUtility.cs
index 253b10c..884ecb7 100644
--- a/src/NebulaAuth/Utility/LanguageUtility.cs
+++ b/src/NebulaAuth/Utility/LanguageUtility.cs
@@ -31,8 +31,8 @@ public static class LanguageUtility
"RU", "BY", "KZ", "KG", "TJ", "TM", "UZ", "AM", "AZ", "GE", "MD"
];
- return cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase))
- ? LocalizationLanguage.Russian
+ return cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase))
+ ? LocalizationLanguage.Russian
: LocalizationLanguage.English;
}
}
\ No newline at end of file
diff --git a/src/NebulaAuth/View/MafileExporterView.xaml b/src/NebulaAuth/View/MafileExporterView.xaml
new file mode 100644
index 0000000..aafeb63
--- /dev/null
+++ b/src/NebulaAuth/View/MafileExporterView.xaml
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/NebulaAuth/View/MafileExporterView.xaml.cs b/src/NebulaAuth/View/MafileExporterView.xaml.cs
new file mode 100644
index 0000000..f118802
--- /dev/null
+++ b/src/NebulaAuth/View/MafileExporterView.xaml.cs
@@ -0,0 +1,32 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace NebulaAuth.View;
+
+public partial class MafileExporterView : UserControl
+{
+ public MafileExporterView()
+ {
+ InitializeComponent();
+ }
+
+ private void PathTB_DoubleClick(object sender, MouseButtonEventArgs e)
+ {
+ if (sender is TextBox textBox)
+ {
+ textBox.SelectAll();
+ }
+ }
+
+ private void ExportTB_GotFocus(object sender, RoutedEventArgs e)
+ {
+ TemplateExpander.IsExpanded = false;
+ }
+
+ private void EditButton_Click(object sender, RoutedEventArgs e)
+ {
+ Dispatcher.BeginInvoke(() => { EditTemplateNameTextBox.Focus(); }, DispatcherPriority.Input);
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/ViewModel/MainVM.cs b/src/NebulaAuth/ViewModel/MainVM.cs
index 94e7ffa..0538bc3 100644
--- a/src/NebulaAuth/ViewModel/MainVM.cs
+++ b/src/NebulaAuth/ViewModel/MainVM.cs
@@ -12,7 +12,6 @@ using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Model.Mafiles;
using NebulaAuth.Utility;
-using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
using SteamLib.SteamMobile;
using SteamLibForked.Exceptions.Authorization;
@@ -23,7 +22,6 @@ public partial class MainVM : ObservableObject
{
[UsedImplicitly] public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
-
public Mafile? SelectedMafile
{
get => _selectedMafile;
@@ -50,10 +48,8 @@ public partial class MainVM : ObservableObject
[RelayCommand]
public async Task Debug()
{
- await DialogsController.ShowSetAccountsPasswordDialog();
}
-
private void SetMafile(Mafile? mafile)
{
if (mafile != SelectedMafile)
@@ -135,12 +131,6 @@ public partial class MainVM : ObservableObject
}
}
- [RelayCommand]
- public async Task LinkAccount()
- {
- await DialogsController.ShowLinkerDialog();
- }
-
[RelayCommand]
public async Task MoveAccount()
{
@@ -218,16 +208,6 @@ public partial class MainVM : ObservableObject
}
}
- [RelayCommand]
- private async Task OpenLinksView()
- {
- CurrentDialogHost.CloseOnClickAway = true;
- var view = new LinksView();
- await DialogHost.Show(view);
- CurrentDialogHost.CloseOnClickAway = false;
- }
-
-
private static string GetLocalization(string key)
{
const string locPath = "MainVM";
diff --git a/src/NebulaAuth/ViewModel/MainVM_File.cs b/src/NebulaAuth/ViewModel/MainVM_File.cs
index 3f20fa0..1341adc 100644
--- a/src/NebulaAuth/ViewModel/MainVM_File.cs
+++ b/src/NebulaAuth/ViewModel/MainVM_File.cs
@@ -240,7 +240,7 @@ public partial class MainVM //File //TODO: Refactor
}
var arr = files.Cast().ToArray();
- if (arr.All(p => p.ContainsIgnoreCase("mafile") == false)) return;
+ if (arr.All(p => !p.ContainsIgnoreCase("mafile"))) return;
await AddMafile(arr);
diff --git a/src/NebulaAuth/ViewModel/MainVM_Groups.cs b/src/NebulaAuth/ViewModel/MainVM_Groups.cs
index 30fcb9d..9b8b7cb 100644
--- a/src/NebulaAuth/ViewModel/MainVM_Groups.cs
+++ b/src/NebulaAuth/ViewModel/MainVM_Groups.cs
@@ -88,7 +88,7 @@ public partial class MainVM //Groups
Storage.UpdateMafile(mafile);
OnPropertyChanged(nameof(SelectedMafile)); //For bindings
QueryGroups();
- if (Groups.All(g => g.Equals(mafGroup) == false))
+ if (Groups.All(g => !g.Equals(mafGroup)))
{
SelectedGroup = null;
}
@@ -104,7 +104,7 @@ public partial class MainVM //Groups
private void QueryGroups()
{
var groups = Storage.MaFiles
- .Where(m => string.IsNullOrWhiteSpace(m.Group) == false)
+ .Where(m => !string.IsNullOrWhiteSpace(m.Group))
.Select(m => m.Group)
.Distinct()
.Order()
diff --git a/src/NebulaAuth/ViewModel/MainVM_MAAC.cs b/src/NebulaAuth/ViewModel/MainVM_MAAC.cs
index 9abea47..8992afd 100644
--- a/src/NebulaAuth/ViewModel/MainVM_MAAC.cs
+++ b/src/NebulaAuth/ViewModel/MainVM_MAAC.cs
@@ -130,7 +130,7 @@ public partial class MainVM //MAAC
{
mafiles = mafiles.ToArray();
- var turnOn = mafiles.All(m => m.LinkedClient == null || GetCurrentMode(m.LinkedClient) == false);
+ var turnOn = mafiles.All(m => m.LinkedClient == null || !GetCurrentMode(m.LinkedClient));
if (turnOn)
{
foreach (var mafile in mafiles)
diff --git a/src/NebulaAuth/ViewModel/MainVM_Menu.cs b/src/NebulaAuth/ViewModel/MainVM_Menu.cs
new file mode 100644
index 0000000..cae05e5
--- /dev/null
+++ b/src/NebulaAuth/ViewModel/MainVM_Menu.cs
@@ -0,0 +1,38 @@
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MaterialDesignThemes.Wpf;
+using NebulaAuth.Core;
+using NebulaAuth.View;
+
+namespace NebulaAuth.ViewModel;
+
+public partial class MainVM : ObservableObject
+{
+ [RelayCommand]
+ private Task OpenSetPasswordsDialog()
+ {
+ return DialogsController.ShowSetAccountsPasswordDialog();
+ }
+
+ [RelayCommand]
+ private Task OpenExporterDialog()
+ {
+ return DialogsController.ShowExportDialog();
+ }
+
+ [RelayCommand]
+ public Task LinkAccount()
+ {
+ return DialogsController.ShowLinkerDialog();
+ }
+
+ [RelayCommand]
+ private async Task OpenLinksView()
+ {
+ CurrentDialogHost.CloseOnClickAway = true;
+ var view = new LinksView();
+ await DialogHost.Show(view);
+ CurrentDialogHost.CloseOnClickAway = false;
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/ViewModel/Other/MafileExporterVM.cs b/src/NebulaAuth/ViewModel/Other/MafileExporterVM.cs
new file mode 100644
index 0000000..8678f2f
--- /dev/null
+++ b/src/NebulaAuth/ViewModel/Other/MafileExporterVM.cs
@@ -0,0 +1,368 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using NebulaAuth.Core;
+using NebulaAuth.Model.MafileExport;
+
+namespace NebulaAuth.ViewModel.Other;
+
+public partial class MafileExporterVM : ObservableObject
+{
+ public MafileExportTemplateVM? CurrentTemplate
+ {
+ get => _currentTemplate;
+ set => SetCurrentTemplate(value);
+ }
+
+
+ public ObservableCollection Templates { get; }
+
+ [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ExportCommand))]
+ private string? _accountsText;
+
+ private string? _cachedName;
+ private MafileExportTemplateVM? _currentTemplate;
+
+ [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(AddTemplateCommand), nameof(RemoveCurrentTemplateCommand))]
+ private bool _editMode;
+
+ [ObservableProperty] private HintBoxSeverity _hintBoxSeverity = HintBoxSeverity.Error;
+
+ [ObservableProperty] private string? _hintText;
+
+ public MafileExporterVM()
+ {
+ var templates = MafileExporterStorage.Templates
+ .Select(MafileExportTemplateVM.FromModel);
+
+
+ Templates = new ObservableCollection(templates);
+ MafileExporterStorage.Templates.CollectionChanged += OnTemplatesOnCollectionChanged;
+ CurrentTemplate = Templates.FirstOrDefault();
+ }
+
+ private void SetCurrentTemplate(MafileExportTemplateVM? value)
+ {
+ if (_currentTemplate != null)
+ {
+ _currentTemplate.PropertyChanged -= OnCurrentTemplatePropertyChanged;
+ }
+
+ SetProperty(ref _currentTemplate, value, nameof(CurrentTemplate));
+ if (value != null)
+ {
+ value.PropertyChanged += OnCurrentTemplatePropertyChanged;
+ }
+
+ ToggleEditModeCommand.NotifyCanExecuteChanged();
+ RemoveCurrentTemplateCommand.NotifyCanExecuteChanged();
+ OpenSelectDirectoryDialogCommand.NotifyCanExecuteChanged();
+ ExportCommand.NotifyCanExecuteChanged();
+ }
+
+ private void OnTemplatesOnCollectionChanged(object? s, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ foreach (MafileExportTemplate newItem in e.NewItems!)
+ {
+ Templates.Add(MafileExportTemplateVM.FromModel(newItem));
+ }
+
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ foreach (MafileExportTemplate oldItem in e.OldItems!)
+ {
+ var vmToRemove = Templates.FirstOrDefault(x => x.Name == oldItem.Name);
+ if (vmToRemove != null)
+ {
+ Templates.Remove(vmToRemove);
+ }
+ }
+
+ break;
+ case NotifyCollectionChangedAction.Replace:
+ foreach (MafileExportTemplate newItem in e.NewItems!)
+ {
+ var vmToReplace = Templates.FirstOrDefault(x => x.Name == newItem.Name);
+ if (vmToReplace != null)
+ {
+ var index = Templates.IndexOf(vmToReplace);
+ Templates[index] = MafileExportTemplateVM.FromModel(newItem);
+ }
+ }
+
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ Templates.Clear();
+ break;
+ case NotifyCollectionChangedAction.Move:
+ default:
+ break;
+ }
+ }
+
+ private void OnCurrentTemplatePropertyChanged(object? sender, PropertyChangedEventArgs propertyChangedEventArgs)
+ {
+ var currentTemplate = CurrentTemplate;
+ if (currentTemplate == null) return;
+ var key = propertyChangedEventArgs.PropertyName == nameof(MafileExportTemplateVM.Name)
+ ? _cachedName!
+ : currentTemplate.Name;
+
+ var existed = MafileExporterStorage.GetTemplate(key);
+ if (existed != null)
+ {
+ currentTemplate.ApplyTo(existed);
+ MafileExporterStorage.Save();
+ }
+ else
+ {
+ MafileExporterStorage.AddTemplate(currentTemplate.ToModel());
+ }
+ }
+
+ [RelayCommand(CanExecute = nameof(CurrentTemplateNotNull))]
+ private void ToggleEditMode()
+ {
+ EditMode = !EditMode;
+ if (EditMode) _cachedName = CurrentTemplate?.Name;
+ }
+
+ [RelayCommand(CanExecute = nameof(NotEditMode))]
+ private void AddTemplate()
+ {
+ ResetHintText();
+ var name = "New Template";
+ var i = 0;
+ while (Templates.Any(x => x.Name == name))
+ {
+ name = $"New Template ({++i})";
+ }
+
+ var template = new MafileExportTemplate
+ {
+ Name = name,
+ UseLoginAsMafileName = false,
+ IncludeSharedSecret = true,
+ IncludeIdentitySecret = true,
+ IncludeRCode = true,
+ IncludeSessionData = true,
+ IncludeOtherInfo = true,
+ IncludeNebulaProxy = true,
+ IncludeNebulaPassword = true,
+ IncludeNebulaGroup = true,
+ Path = null
+ };
+ MafileExporterStorage.AddTemplate(template);
+ CurrentTemplate = Templates.FirstOrDefault(x => x.Name == name);
+ }
+
+ [RelayCommand(CanExecute = nameof(CurrentTemplateNotNullAndNotEditMode))]
+ private void RemoveCurrentTemplate()
+ {
+ if (CurrentTemplate != null)
+ {
+ ResetHintText();
+ var index = Templates.IndexOf(CurrentTemplate);
+ var existed = MafileExporterStorage.GetTemplate(CurrentTemplate.Name);
+ if (existed != null)
+ {
+ MafileExporterStorage.DeleteTemplate(existed);
+ }
+
+ if (Templates.Count > 0)
+ {
+ if (index >= Templates.Count)
+ {
+ index = Templates.Count - 1;
+ }
+
+ CurrentTemplate = Templates[index];
+ }
+ else
+ {
+ CurrentTemplate = null;
+ }
+ }
+ }
+
+ [RelayCommand(CanExecute = nameof(CurrentTemplateNotNull))]
+ private void OpenSelectDirectoryDialog()
+ {
+ if (CurrentTemplate == null) return;
+ var dialog = new FolderBrowserDialog
+ {
+ UseDescriptionForTitle = true,
+ ShowNewFolderButton = true
+ };
+ var result = dialog.ShowDialog();
+ if (result == DialogResult.OK)
+ {
+ CurrentTemplate!.Path = dialog.SelectedPath;
+ }
+ }
+
+ [RelayCommand(CanExecute = nameof(ExportCanExecute))]
+ private async Task Export()
+ {
+ var lines = AccountsText;
+ var template = CurrentTemplate;
+ if (string.IsNullOrWhiteSpace(lines) || template == null)
+ {
+ return;
+ }
+
+ var split = Regex.Split(lines, "\r\n|\r|\n").Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
+ ResetHintText();
+ ExportResult res;
+ try
+ {
+ res = await MafileExporter.ExportMafiles(split, template.ToModel());
+ }
+ catch (Exception ex)
+ {
+ SetHintText(ex.Message);
+ return;
+ }
+
+ if (!res.Success)
+ {
+ SetHintText(res.Error);
+ return;
+ }
+
+ var hint = string.Format(LocManager.GetCodeBehindOrDefault("Exported", "MafileExporterVM.Result.Exported"),
+ res.Exported.Count);
+
+ if (res.NotFound.Count > 0)
+ {
+ hint += string.Format(LocManager.GetCodeBehindOrDefault("NotFound", "MafileExporterVM.Result.NotFound"),
+ res.NotFound.Count);
+ }
+
+ if (res.Conflict.Count > 0)
+ {
+ hint += string.Format(LocManager.GetCodeBehindOrDefault("Conflict", "MafileExporterVM.Result.Conflict"),
+ res.Conflict.Count);
+ }
+
+ SetHintText(hint, HintBoxSeverity.Info);
+ var errors = res.NotFound.Concat(res.Conflict).ToHashSet();
+ var errorLines = split.Where(errors.Contains);
+ var text = string.Join(Environment.NewLine, errorLines);
+
+ AccountsText = text;
+ }
+
+ private void SetHintText(string? text, HintBoxSeverity severity = HintBoxSeverity.Error)
+ {
+ HintText = text;
+ HintBoxSeverity = severity;
+ }
+
+ private void ResetHintText()
+ {
+ SetHintText(null);
+ }
+
+ #region CanExecute
+
+ private bool ExportCanExecute()
+ {
+ return CurrentTemplateNotNull() && !string.IsNullOrWhiteSpace(AccountsText);
+ }
+
+
+ private bool CurrentTemplateNotNull()
+ {
+ return CurrentTemplate != null;
+ }
+
+ private bool CurrentTemplateNotNullAndNotEditMode()
+ {
+ return CurrentTemplateNotNull() && NotEditMode();
+ }
+
+ private bool NotEditMode()
+ {
+ return !EditMode;
+ }
+
+ #endregion
+}
+
+public partial class MafileExportTemplateVM : ObservableObject
+{
+ [ObservableProperty] private bool _includeIdentitySecret;
+ [ObservableProperty] private bool _includeNebulaGroup;
+ [ObservableProperty] private bool _includeNebulaPassword;
+ [ObservableProperty] private bool _includeNebulaProxy;
+ [ObservableProperty] private bool _includeOtherInfo;
+ [ObservableProperty] private bool _includeRCode;
+ [ObservableProperty] private bool _includeSessionData;
+ [ObservableProperty] private bool _includeSharedSecret;
+ [ObservableProperty] private string _name;
+ [ObservableProperty] private string? _path;
+ [ObservableProperty] private bool _useLoginAsMafileName;
+
+
+ public static MafileExportTemplateVM FromModel(MafileExportTemplate x)
+ {
+ return new MafileExportTemplateVM
+ {
+ Name = x.Name,
+ UseLoginAsMafileName = x.UseLoginAsMafileName,
+ IncludeSharedSecret = x.IncludeSharedSecret,
+ IncludeIdentitySecret = x.IncludeIdentitySecret,
+ IncludeRCode = x.IncludeRCode,
+ IncludeSessionData = x.IncludeSessionData,
+ IncludeOtherInfo = x.IncludeOtherInfo,
+ IncludeNebulaProxy = x.IncludeNebulaProxy,
+ IncludeNebulaPassword = x.IncludeNebulaPassword,
+ IncludeNebulaGroup = x.IncludeNebulaGroup,
+ Path = x.Path
+ };
+ }
+
+ public MafileExportTemplate ToModel()
+ {
+ return new MafileExportTemplate
+ {
+ Name = Name,
+ UseLoginAsMafileName = UseLoginAsMafileName,
+ IncludeSharedSecret = IncludeSharedSecret,
+ IncludeIdentitySecret = IncludeIdentitySecret,
+ IncludeRCode = IncludeRCode,
+ IncludeSessionData = IncludeSessionData,
+ IncludeOtherInfo = IncludeOtherInfo,
+ IncludeNebulaProxy = IncludeNebulaProxy,
+ IncludeNebulaPassword = IncludeNebulaPassword,
+ IncludeNebulaGroup = IncludeNebulaGroup,
+ Path = Path
+ };
+ }
+
+ public void ApplyTo(MafileExportTemplate model)
+ {
+ model.Name = Name;
+ model.UseLoginAsMafileName = UseLoginAsMafileName;
+ model.IncludeSharedSecret = IncludeSharedSecret;
+ model.IncludeIdentitySecret = IncludeIdentitySecret;
+ model.IncludeRCode = IncludeRCode;
+ model.IncludeSessionData = IncludeSessionData;
+ model.IncludeOtherInfo = IncludeOtherInfo;
+ model.IncludeNebulaProxy = IncludeNebulaProxy;
+ model.IncludeNebulaPassword = IncludeNebulaPassword;
+ model.IncludeNebulaGroup = IncludeNebulaGroup;
+ model.Path = Path;
+ }
+}
\ No newline at end of file
diff --git a/src/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs b/src/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs
index f74fc0e..d1bef95 100644
--- a/src/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs
+++ b/src/NebulaAuth/ViewModel/Other/ProxyManagerVM.cs
@@ -61,7 +61,7 @@ public partial class ProxyManagerVM : ObservableObject
var split = input
.Split(Environment.NewLine)
- .Where(s => string.IsNullOrWhiteSpace(s) == false)
+ .Where(s => !string.IsNullOrWhiteSpace(s))
.Select(x => x.Trim())
.ToArray();
@@ -72,7 +72,7 @@ public partial class ProxyManagerVM : ObservableObject
var i = 0;
- foreach (var s in split.Where(s => string.IsNullOrWhiteSpace(s) == false))
+ foreach (var s in split.Where(s => !string.IsNullOrWhiteSpace(s)))
{
i++;
var str = s;
diff --git a/src/NebulaAuth/ViewModel/Other/SettingsVM.cs b/src/NebulaAuth/ViewModel/Other/SettingsVM.cs
index fb23dc9..1c66f20 100644
--- a/src/NebulaAuth/ViewModel/Other/SettingsVM.cs
+++ b/src/NebulaAuth/ViewModel/Other/SettingsVM.cs
@@ -144,7 +144,7 @@ public partial class SettingsVM : ObservableObject
get => IconColor != null;
set
{
- if (value == false)
+ if (!value)
IconColor = null;
else
IconColor = Color.FromRgb(202, 39, 39);
diff --git a/src/NebulaAuth/localization.loc.json b/src/NebulaAuth/localization.loc.json
index 9d81896..4f40b88 100644
--- a/src/NebulaAuth/localization.loc.json
+++ b/src/NebulaAuth/localization.loc.json
@@ -180,6 +180,13 @@
"zh": "设置密码",
"ua": "Призначити паролі",
"fr": "Définir les mots de passe"
+ },
+ "OpenExport": {
+ "ru": "Экспорт",
+ "en": "Export",
+ "ua": "Експорт",
+ "fr": "Exporter",
+ "zh": "导出"
}
}
},
@@ -652,10 +659,12 @@
},
"LegacyMafileModeHint": {
"en": "Save mafiles in compatibility format with Steam Desktop Authenticator and other applications",
- "ru": "Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
+ "ru":
+ "Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
"zh": "以与 Steam 桌面认证器及其他应用程序兼容的格式保存 mafiles",
"ua": "Зберігати мафайли у старому форматі для сумісності зі Steam Desktop Authenticator та іншими додатками",
- "fr": "Enregistrer les mafiles dans un format compatible avec Steam Desktop Authenticator et d'autres applications"
+ "fr":
+ "Enregistrer les mafiles dans un format compatible avec Steam Desktop Authenticator et d'autres applications"
},
"AllowAutoUpdate": {
"en": "Allow auto update",
@@ -686,10 +695,13 @@
"fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir dans le timer (exp.)"
},
"IgnorePatchTuesdayErrorsHint": {
- "en": "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily",
- "ru": "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности",
+ "en":
+ "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily",
+ "ru":
+ "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности",
"zh": "忽略每周三(格林尼治标准时间周三夜间)发生的错误,因为 Steam 进行技术维护时自动确认功能会被无故关闭",
- "ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби",
+ "ua":
+ "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби",
"fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir (Mercredi à minuit en GMT)"
}
},
@@ -941,11 +953,15 @@
"fr": "Mot de passe de chiffrement"
},
"DialogText": {
- "en": "You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session.\r\n(Optional)",
- "ru": "Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией.\r\n(Не обязательно)",
+ "en":
+ "You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session.\r\n(Optional)",
+ "ru":
+ "Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией.\r\n(Не обязательно)",
"zh": "之前您为密码文件设置了加密密码,请输入它,以便应用程序在会话出现问题时可以自动登录。\r\n(非必填)",
- "ua": "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)",
- "fr": "Vous avez déjà défini un mot de passe de chiffrement pour les mots de passe des mafiles, spécifiez-le pour que l'appli puisse se connecter automatiquement en cas de problèmes avec la session.\r\n(Facultatif)"
+ "ua":
+ "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)",
+ "fr":
+ "Vous avez déjà défini un mot de passe de chiffrement pour les mots de passe des mafiles, spécifiez-le pour que l'appli puisse se connecter automatiquement en cas de problèmes avec la session.\r\n(Facultatif)"
},
"Ok": {
"en": "Ok",
@@ -1019,6 +1035,209 @@
"fr": "Entrer la valeur"
}
},
+ "ExportDialog": {
+ "ExportTitle": {
+ "ru": "Экспорт",
+ "en": "Export",
+ "ua": "Експорт",
+ "fr": "Exportation",
+ "zh": "导出"
+ },
+ "ExportButton": {
+ "ru": "Экспорт",
+ "en": "Export",
+ "ua": "Експорт",
+ "fr": "Exporter",
+ "zh": "导出"
+ },
+ "TemplateHint": {
+ "ru": "Шаблон",
+ "en": "Template",
+ "ua": "Шаблон",
+ "fr": "Modèle",
+ "zh": "模板"
+ },
+ "SelectPathHint": {
+ "ru": "Выберите путь",
+ "en": "Select path",
+ "ua": "Виберіть шлях",
+ "fr": "Sélectionner le chemin",
+ "zh": "选择路径"
+ },
+ "ExportSettingsHeader": {
+ "ru": "Настройки экспорта",
+ "en": "Export settings",
+ "ua": "Налаштування експорту",
+ "fr": "Paramètres d’exportation",
+ "zh": "导出设置"
+ },
+ "ExportSettingsCommon": {
+ "ru": "Основные:",
+ "en": "General:",
+ "ua": "Основні:",
+ "fr": "Général :",
+ "zh": "常规:"
+ },
+ "ExportSettingsNebula": {
+ "ru": "Nebula:",
+ "en": "Nebula:",
+ "ua": "Nebula:",
+ "fr": "Nebula :",
+ "zh": "Nebula:"
+ },
+ "ExportOptions": {
+ "UseLoginAsMafileName": {
+ "ru": "Использовать логин как имя mafile",
+ "en": "Use login as mafile name",
+ "ua": "Використовувати логін як ім’я mafile",
+ "fr": "Utiliser le login comme nom de mafile",
+ "zh": "使用登录名作为 mafile 名称"
+ },
+ "IncludeSharedSecret": {
+ "ru": "Включать shared secret",
+ "en": "Include shared secret",
+ "ua": "Включати shared secret",
+ "fr": "Inclure le secret partagé",
+ "zh": "包含 shared secret"
+ },
+ "IncludeIdentitySecret": {
+ "ru": "Включать identity secret",
+ "en": "Include identity secret",
+ "ua": "Включати identity secret",
+ "fr": "Inclure le secret d’identité",
+ "zh": "包含 identity secret"
+ },
+ "IncludeRCode": {
+ "ru": "Включать RCode",
+ "en": "Include RCode",
+ "ua": "Включати RCode",
+ "fr": "Inclure RCode",
+ "zh": "包含 RCode"
+ },
+ "IncludeSessionData": {
+ "ru": "Включать данные сессии",
+ "en": "Include session data",
+ "ua": "Включати дані сесії",
+ "fr": "Inclure les données de session",
+ "zh": "包含会话数据"
+ },
+ "IncludeOtherInfo": {
+ "ru": "Включать прочую информацию",
+ "en": "Include other info",
+ "ua": "Включати іншу інформацію",
+ "fr": "Inclure d’autres informations",
+ "zh": "包含其他信息"
+ },
+ "NebulaProxy": {
+ "ru": "Прокси",
+ "en": "Proxy",
+ "ua": "Проксі",
+ "fr": "Proxy",
+ "zh": "代理"
+ },
+ "NebulaPassword": {
+ "ru": "Пароль",
+ "en": "Password",
+ "ua": "Пароль",
+ "fr": "Mot de passe",
+ "zh": "密码"
+ },
+ "NebulaGroup": {
+ "ru": "Группа",
+ "en": "Group",
+ "ua": "Група",
+ "fr": "Groupe",
+ "zh": "组"
+ }
+ },
+ "Tooltips": {
+ "UseLoginAsMafileName": {
+ "ru": "Использовать логин аккаунта в качестве имени mafile.",
+ "en": "Use the account login as the mafile name.",
+ "ua": "Використовувати логін акаунта як ім’я mafile.",
+ "fr": "Utiliser le login du compte comme nom du mafile.",
+ "zh": "使用账户登录名作为 mafile 的名称。"
+ },
+ "IncludeSharedSecret": {
+ "ru": "Позволяет генерировать коды подтверждения и входить в аккаунт при наличии пароля или активной сессии.",
+ "en":
+ "Allows generating confirmation codes and logging into the account if a password or active session is available.",
+ "ua": "Дозволяє генерувати коди підтвердження та входити в акаунт за наявності пароля або активної сесії.",
+ "fr":
+ "Permet de générer des codes de confirmation et de se connecter au compte si un mot de passe ou une session active est disponible.",
+ "zh": "允许生成确认代码,并在有密码或活动会话的情况下登录账户。"
+ },
+ "IncludeIdentitySecret": {
+ "ru":
+ "Включает identity_secret и device_id. Необходимы для отправки подтверждений и дают доступ к трейдам и торговой площадке.",
+ "en":
+ "Includes identity_secret and device_id. Required for confirmations and provides access to trades and the market.",
+ "ua":
+ "Включає identity_secret і device_id. Потрібні для надсилання підтверджень і надають доступ до трейдів та торгового майданчика.",
+ "fr":
+ "Inclut identity_secret et device_id. Nécessaires pour les confirmations et donnent accès aux échanges et au marché.",
+ "zh": "包含 identity_secret 和 device_id。用于发送确认,并可访问交易和市场。"
+ },
+ "IncludeRCode": {
+ "ru": "Код восстановления. Позволяет отвязать Steam Guard от аккаунта в случае потери доступа.",
+ "en": "Recovery code. Allows removing Steam Guard from the account if access is lost.",
+ "ua": "Код відновлення. Дозволяє відв’язати Steam Guard від акаунта у разі втрати доступу.",
+ "fr": "Code de récupération. Permet de supprimer Steam Guard du compte en cas de perte d’accès.",
+ "zh": "恢复代码。在失去访问权限时可将 Steam Guard 与账户解绑。"
+ },
+ "IncludeSessionData": {
+ "ru":
+ "Включает данные текущей сессии. Если сессия активна и не истекла, позволяет получить доступ к аккаунту без ввода пароля с помощью mafile.",
+ "en":
+ "Includes current session data. If the session is active and valid, allows accessing the account without entering a password using the mafile.",
+ "ua":
+ "Включає дані поточної сесії. Якщо сесія активна й не прострочена, дозволяє отримати доступ до акаунта без введення пароля за допомогою mafile.",
+ "fr":
+ "Inclut les données de la session actuelle. Si la session est active et valide, permet d’accéder au compte sans saisir de mot de passe via le mafile.",
+ "zh": "包含当前会话数据。如果会话处于活动状态且未过期,可通过 mafile 在无需输入密码的情况下访问账户。"
+ },
+ "IncludeOtherInfo": {
+ "ru":
+ "Включает дополнительные необязательные поля, которые в данный момент не используются Steam, но могут применяться в будущем и содержать чувствительную информацию.",
+ "en":
+ "Includes additional optional fields not currently used by Steam, but potentially usable in the future and may contain sensitive information.",
+ "ua":
+ "Включає додаткові необов’язкові поля, які наразі не використовуються Steam, але можуть бути задіяні в майбутньому та містити чутливу інформацію.",
+ "fr":
+ "Inclut des champs supplémentaires optionnels actuellement non utilisés par Steam, mais susceptibles d’être exploités à l’avenir et de contenir des informations sensibles.",
+ "zh": "包含目前未被 Steam 使用的可选附加字段,这些字段将来可能会被使用,并可能包含敏感信息。"
+ },
+ "NebulaProxy": {
+ "ru": "Включить Nebula-совместимые данные прокси.",
+ "en": "Include Nebula-compatible proxy data.",
+ "ua": "Увімкнути Nebula-сумісні дані проксі.",
+ "fr": "Inclure les données de proxy compatibles avec Nebula.",
+ "zh": "包含与 Nebula 兼容的代理数据。"
+ },
+ "NebulaPassword": {
+ "ru": "Включить Nebula-совместимые данные пароля.",
+ "en": "Include Nebula-compatible password data.",
+ "ua": "Увімкнути Nebula-сумісні дані пароля.",
+ "fr": "Inclure les données de mot de passe compatibles avec Nebula.",
+ "zh": "包含与 Nebula 兼容的密码数据。"
+ },
+ "NebulaGroup": {
+ "ru": "Включить Nebula-совместимые данные группы.",
+ "en": "Include Nebula-compatible group data.",
+ "ua": "Увімкнути Nebula-сумісні дані групи.",
+ "fr": "Inclure les données de groupe compatibles avec Nebula.",
+ "zh": "包含与 Nebula 兼容的组数据。"
+ }
+ },
+ "ExportAccountsPlaceholder": {
+ "ru": "Введите логины аккаунтов или SteamID (по одному на строку) для экспорта в выбранную директорию.",
+ "en": "Enter account logins or SteamIDs (one per line) to export to the selected directory.",
+ "ua": "Введіть логіни акаунтів або SteamID (по одному на рядок) для експорту у вибрану директорію.",
+ "fr":
+ "Entrez les identifiants de compte ou les SteamID (un par ligne) pour exporter vers le répertoire sélectionné.",
+ "zh": "输入要导出到所选目录的账户登录名或 SteamID(每行一个)。"
+ }
+ },
"SetAccountPasswordsView": {
"Title": {
"en": "Set passwords",
@@ -1087,11 +1306,15 @@
"fr": "RCode manquant"
},
"ConfirmRemovingAuthenticator": {
- "ru": "Вы точно уверены, что хотите удалить аутентификатор?\r\nПосле этого вы не сможете обмениваться, использовать торговую площадку 15 дней. Мафайл будет удалён навсегда",
- "en": "Are you sure you want to remove the authenticator?\r\nAfter that, you will not be able to trade or use the marketplace for 15 days. Mafile will be deleted permanently",
+ "ru":
+ "Вы точно уверены, что хотите удалить аутентификатор?\r\nПосле этого вы не сможете обмениваться, использовать торговую площадку 15 дней. Мафайл будет удалён навсегда",
+ "en":
+ "Are you sure you want to remove the authenticator?\r\nAfter that, you will not be able to trade or use the marketplace for 15 days. Mafile will be deleted permanently",
"zh": "您确定要移除身份验证器吗?\r\n之后,您将在15天内无法进行交易或使用市场。Mafile 将被永久删除。",
- "ua": "Ви точно впевнені, що хочете видалити автентифікатор?\r\nПісля цього ви не зможете обмінюватися, використовувати торгову площадку 15 днів. Мафайл буде видалено назавжди",
- "fr": "Êtes-vous sûr de vouloir supprimer l'authenticateur?\r\nAprès cela, vous ne pourrez pas échanger ou utiliser le marché pendant 15 jours. Le mafile sera supprimé définitivement"
+ "ua":
+ "Ви точно впевнені, що хочете видалити автентифікатор?\r\nПісля цього ви не зможете обмінюватися, використовувати торгову площадку 15 днів. Мафайл буде видалено назавжди",
+ "fr":
+ "Êtes-vous sûr de vouloir supprimer l'authenticateur?\r\nAprès cela, vous ne pourrez pas échanger ou utiliser le marché pendant 15 jours. Le mafile sera supprimé définitivement"
},
"AuthenticatorRemoved": {
"ru": "Аутентификатор удален",
@@ -1185,11 +1408,15 @@
"fr": "erreurs:"
},
"RemoveMafileConfirmation": {
- "ru": "Удалить mafile?\r\nМафайл будет перемещен в папку 'mafiles_removed'\r\nЭто действие НЕ удаляет Steam Guard с аккаунта",
- "en": "Remove mafile?\r\nMafile will be moved to 'mafiles_removed' directory\r\nThis action will NOT remove the Steam Guard from the account",
+ "ru":
+ "Удалить mafile?\r\nМафайл будет перемещен в папку 'mafiles_removed'\r\nЭто действие НЕ удаляет Steam Guard с аккаунта",
+ "en":
+ "Remove mafile?\r\nMafile will be moved to 'mafiles_removed' directory\r\nThis action will NOT remove the Steam Guard from the account",
"zh": "是否删除mafile?\r\nMafile将被移动到“mafiles_removed”目录\r\n此操作不会从账户中移除令牌",
- "ua": "Видалити mafile?\r\nМафайл буде переміщено у каталог 'mafiles_removed'\r\nЦя дія НЕ видаляє автентифікатор з облікового запису",
- "fr": "Supprimer le mafile?\r\nLe mafile sera déplacé dans le dossier 'mafiles_removed'\r\nCette action NE SUPPRIME PAS le Steam Guard du compte"
+ "ua":
+ "Видалити mafile?\r\nМафайл буде переміщено у каталог 'mafiles_removed'\r\nЦя дія НЕ видаляє автентифікатор з облікового запису",
+ "fr":
+ "Supprimer le mafile?\r\nLe mafile sera déplacé dans le dossier 'mafiles_removed'\r\nCette action NE SUPPRIME PAS le Steam Guard du compte"
},
"CantRemoveAlreadyExist": {
"ru": "Не удалось переместить mafile, возможно в папке уже существует мафайл с таким именем.",
@@ -1224,7 +1451,8 @@
"en": "Can't retrieve SteamID from mafile to save changes. Need to relogin. Canceled",
"zh": "无法从配置文件获取 SteamID 并保存更改。需要重新登录。已取消",
"ua": "Не вдалося отримати SteamID з мафайла та зберегти зміни. Потрібно зробити релогін. Скасовано",
- "fr": "Impossible de récupérer le SteamID du mafile pour sauvegarder les modifications. Reconnexion nécessaire. Annulé"
+ "fr":
+ "Impossible de récupérer le SteamID du mafile pour sauvegarder les modifications. Reconnexion nécessaire. Annulé"
},
"LoginCopied": {
"en": "Login copied",
@@ -1263,9 +1491,11 @@
},
"CantDecryptPassword": {
"en": "Can't decrypt password. Maybe password from this mafile was encrypted with another encryption password",
- "ru": "Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
+ "ru":
+ "Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
"zh": "无法解密密码。可能此文件中的密码是用其他加密密码加密的。",
- "ua": "Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування",
+ "ua":
+ "Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування",
"fr": "Impossible de déchiffrer le mot de passe. Le mot de passe de chiffrement est peut-être différent"
},
"CreateGroupTitle": {
@@ -1620,10 +1850,13 @@
},
"InvalidStateWithStatus2": {
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.",
- "en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
+ "en":
+ "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
"zh": "状态无效(InvalidState),状态码为 2. 请打开本窗口顶部的故障排除指南链接。",
- "ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна.",
- "fr": "État invalide (InvalidState) avec le statut 2. Ouvrez le lien vers le guide de dépannage en haut de cette fenêtre."
+ "ua":
+ "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна.",
+ "fr":
+ "État invalide (InvalidState) avec le statut 2. Ouvrez le lien vers le guide de dépannage en haut de cette fenêtre."
},
"GeneralFailure": {
"ru": "General Failure",
@@ -1689,7 +1922,8 @@
"ru": "Ошибка: Был запрошен вход с помощью Steam Guard. На аккаунте уже активирован мобильный аутентификатор",
"en": "Error: Login with Steam Guard was requested. Mobile authenticator is already activated on the account",
"zh": "错误:请求使用令牌登录。该账户已激活移动验证器",
- "ua": "Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор",
+ "ua":
+ "Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор",
"fr": "Erreur: Connexion avec Steam Guard demandée. Authenticateur mobile est déjà activé sur le compte"
},
"Unknown": {
@@ -1828,11 +2062,15 @@
"fr": "Un email a été envoyé. Ouvrez le lien de l'email, puis cliquez sur 'Continuer'"
},
"ClickOnEmailLinkRetry": {
- "ru": "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
- "en": "We couldn’t verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
+ "ru":
+ "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
+ "en":
+ "We couldn’t verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
"zh": "无法确认通过链接的跳转。请确保您是从邮件中打开的链接,然后点击——“继续”",
- "ua": "Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'",
- "fr": "Nous n'avons pas pu vérifier que vous avez cliqué sur le lien. Assurez-vous d'avoir ouvert le lien de l'email, puis cliquez sur 'Continuer'"
+ "ua":
+ "Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'",
+ "fr":
+ "Nous n'avons pas pu vérifier que vous avez cliqué sur le lien. Assurez-vous d'avoir ouvert le lien de l'email, puis cliquez sur 'Continuer'"
},
"PhoneHint": {
"ru": "На телефон {0} была отправлена СМС",
@@ -1842,11 +2080,15 @@
"fr": "SMS envoyé au téléphone {0}"
},
"EnterPhoneNumber": {
- "ru": "Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
- "en": "Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
+ "ru":
+ "Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
+ "en":
+ "Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
"zh": "请输入国际格式的号码,仅数字(不含空格和括号),或留空以绑定无号码",
- "ua": "Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера",
- "fr": "Entrez un numéro au format international, uniquement des chiffres (sans '+', espaces ou parenthèses), ou laissez le champ vide pour lier sans numéro"
+ "ua":
+ "Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера",
+ "fr":
+ "Entrez un numéro au format international, uniquement des chiffres (sans '+', espaces ou parenthèses), ou laissez le champ vide pour lier sans numéro"
}
},
"MafileMoverVM": {
@@ -1879,10 +2121,12 @@
"fr": "RCode: {0}\r\nNom du fichier: {1}.mafile"
},
"GuardIsNotActive": {
- "ru": "Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
+ "ru":
+ "Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
"en": "Steam Guard is set on this account. To link mafile to the account, use the 'Link' window",
"zh": "此账户已启用令牌。要将 mafile 绑定到此账户,请使用“绑定”窗口。",
- "ua": "Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'",
+ "ua":
+ "Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'",
"fr": "Steam Guard est configuré sur ce compte. Pour lier mafile au compte, utilisez la fenêtre 'Lien'"
},
"SeemsNoPhoneNumber": {
@@ -1930,11 +2174,15 @@
"fr": "Auto "
},
"TimerPreventedOverlap": {
- "ru": "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
- "en": "Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
+ "ru":
+ "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
+ "en":
+ "Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
"zh": "自动: 确认计时器在前一个周期尚未完成时尝试启动新周期。我们建议增加延迟。",
- "ua": "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку",
- "fr": "Auto: le timer de confirmation a tenté de démarrer un nouveau cycle alors que le précédent n'était pas encore terminé. Nous recommandons d'augmenter le délai"
+ "ua":
+ "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку",
+ "fr":
+ "Auto: le timer de confirmation a tenté de démarrer un nouveau cycle alors que le précédent n'était pas encore terminé. Nous recommandons d'augmenter le délai"
},
"FailedToLoadStorage": {
"en": "Failed to load auto-confirm timers. File seems to be corrupted",
@@ -1961,11 +2209,15 @@
"fr": "Tous les mafiles ont été renommés avec succès ({0}).\r\nUn backup a été créé: {1}"
},
"PartialSuccess": {
- "en": "Total mafiles: {0}\r\nRenamed: {1}\r\nConflicts: {2}\r\nErrors: {3}\r\n\r\nBackup of mafiles saved in file: {4}",
- "ru": "Всего мафайлов: {0}\r\nПереименовано: {1}\r\nКонфликты: {2}\r\nОшибок: {3}\r\n\r\nРезервная копия мафайлов сохранена в файле: {4}",
+ "en":
+ "Total mafiles: {0}\r\nRenamed: {1}\r\nConflicts: {2}\r\nErrors: {3}\r\n\r\nBackup of mafiles saved in file: {4}",
+ "ru":
+ "Всего мафайлов: {0}\r\nПереименовано: {1}\r\nКонфликты: {2}\r\nОшибок: {3}\r\n\r\nРезервная копия мафайлов сохранена в файле: {4}",
"zh": "总文件数: {0}\r\n重命名: {1}\r\n冲突: {2}\r\n错误: {3}\r\n\r\n文件备份已保存至: {4}",
- "ua": "Всього мафайлів: {0}\r\nПерейменовано: {1}\r\nКонфлікти: {2}\r\nПомилок: {3}\r\n\r\nРезервна копія мафайлів збережена у файлі: {4}",
- "fr": "Total des mafiles: {0}\r\nRenommés: {1}\r\nConflits: {2}\r\nErreurs: {3}\r\n\r\nBackup des mafiles enregistré dans le fichier: {4}"
+ "ua":
+ "Всього мафайлів: {0}\r\nПерейменовано: {1}\r\nКонфлікти: {2}\r\nПомилок: {3}\r\n\r\nРезервна копія мафайлів збережена у файлі: {4}",
+ "fr":
+ "Total des mafiles: {0}\r\nRenommés: {1}\r\nConflits: {2}\r\nErreurs: {3}\r\n\r\nBackup des mafiles enregistré dans le fichier: {4}"
}
}
},
@@ -2002,13 +2254,70 @@
"ua": "Успішно: {0}, Не знайдено: {1}, Помилки: {2}",
"fr": "Succès: {0}, Non trouvé: {1}, Erreurs: {2}"
}
+ },
+ "MafileExporter": {
+ "InvalidPath": {
+ "ru": "Указан некорректный путь для экспорта.",
+ "en": "The specified export path is invalid.",
+ "ua": "Вказано некоректний шлях для експорту.",
+ "fr": "Le chemin d’exportation spécifié est invalide.",
+ "zh": "指定的导出路径无效。"
+ },
+ "NebulaUtilizedDirectory": {
+ "ru":
+ "Невозможно выполнить экспорт в папку, которая используется приложением. Рекомендуется выбрать отдельную директорию.",
+ "en":
+ "Cannot export to a directory that is currently used by the application. Please choose a separate folder.",
+ "ua":
+ "Неможливо виконати експорт у папку, яка використовується застосунком. Рекомендується вибрати окрему директорію.",
+ "fr": "Impossible d’exporter vers un dossier utilisé par l’application. Veuillez choisir un dossier distinct.",
+ "zh": "无法导出到应用程序正在使用的文件夹。请选择一个单独的目录。"
+ },
+ "AccessDenied": {
+ "ru": "Недостаточно прав для записи в указанную папку. Проверьте права доступа или выберите другой путь.",
+ "en":
+ "Insufficient permissions to write to the specified directory. Please check access rights or choose another path.",
+ "ua": "Недостатньо прав для запису в указану папку. Перевірте права доступу або виберіть інший шлях.",
+ "fr":
+ "Autorisation insuffisante pour écrire dans le dossier spécifié. Vérifiez les droits d’accès ou choisissez un autre chemin.",
+ "zh": "没有权限写入指定的文件夹。请检查访问权限或选择其他路径。"
+ }
+ },
+ "MafileExporterVM": {
+ "Result": {
+ "Exported": {
+ "ru": "Экспортировано: {0}",
+ "en": "Exported: {0}",
+ "ua": "Експортовано: {0}",
+ "fr": "Exporté : {0}",
+ "zh": "已导出:{0}"
+ },
+ "NotFound": {
+ "ru": ". Не найдено: {0}",
+ "en": ". Not found: {0}",
+ "ua": ". Не знайдено: {0}",
+ "fr": ". Introuvable : {0}",
+ "zh": "。未找到:{0}"
+ },
+ "Conflict": {
+ "ru": ". Конфликты: {0}",
+ "en": ". Conflicts: {0}",
+ "ua": ". Конфлікти: {0}",
+ "fr": ". Conflits : {0}",
+ "zh": "。冲突:{0}"
+ }
+ }
}
},
"CantAlignTimeError": {
- "ru": "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log",
- "en": "Can't align time with Steam. Maybe no internet connection or Steam is unavailable. Detailed error saved in log.log",
+ "ru":
+ "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log",
+ "en":
+ "Can't align time with Steam. Maybe no internet connection or Steam is unavailable. Detailed error saved in log.log",
"zh": "无法与 Steam 对齐时间。可能没有互联网连接或 Steam 不可用。详细错误已保存到 log.log 中",
- "ua": "Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log",
- "fr": "Impossible de synchroniser l'heure avec Steam. Peut-être qu'il n'y a pas de connexion internet ou Steam est indisponible. Détails de l'erreur enregistrés dans log.log"
+ "ua":
+ "Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log",
+ "fr":
+ "Impossible de synchroniser l'heure avec Steam. Peut-être qu'il n'y a pas de connexion internet ou Steam est indisponible. Détails de l'erreur enregistrés dans log.log"
}
}
\ No newline at end of file
diff --git a/src/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs b/src/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs
index 8d61aed..76bf30a 100644
--- a/src/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs
+++ b/src/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs
@@ -152,7 +152,7 @@ public static class SteamAuthenticatorLinkerApi
var content = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
var j = JObject.Parse(content);
- if (j["success"]!.Value() == false)
+ if (!j["success"]!.Value())
return CheckPhoneResult.GeneralFailure;
if (j["is_voip"]!.Value())
@@ -187,7 +187,7 @@ public static class SteamAuthenticatorLinkerApi
var resp = await client.PostProto(reqUri,
new EmptyMessage());
- if (resp.IsWaiting == false) return true;
+ if (!resp.IsWaiting) return true;
await Task.Delay(resp.SecondsToWait * 1000);
}
diff --git a/src/SteamLibForked/Authentication/AdmissionHelper.cs b/src/SteamLibForked/Authentication/AdmissionHelper.cs
index 8404c84..e86fc2c 100644
--- a/src/SteamLibForked/Authentication/AdmissionHelper.cs
+++ b/src/SteamLibForked/Authentication/AdmissionHelper.cs
@@ -68,7 +68,7 @@ public static class AdmissionHelper
{
var uri = SteamDomains.GetDomainUri(domain);
foreach (var cookie in container.GetCookies(uri)
- .Where(c => c.Expired == false && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)))
+ .Where(c => !c.Expired && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)))
{
cookie.Expired = true;
}
@@ -131,7 +131,7 @@ public static class AdmissionHelper
}
var mobileToken = mobileSession.GetMobileToken();
- if (domainCookieSet == false && mobileToken is {IsExpired: false})
+ if (!domainCookieSet && mobileToken is {IsExpired: false})
{
var domain = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, mobileToken.Value.SignedToken)
@@ -193,7 +193,7 @@ public static class AdmissionHelper
foreach (Cookie cookie in cookies)
{
- if (cookie.Domain.Contains("steamcommunity.com") == false || cookie.Expired ||
+ if (!cookie.Domain.Contains("steamcommunity.com") || cookie.Expired ||
cookie.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)) continue;
@@ -253,7 +253,7 @@ public static class AdmissionHelper
var cookies = container.GetAllCookies();
return cookies
.FirstOrDefault(c => c.Name.Equals(SESSION_ID_COOKIE_NAME, StringComparison.InvariantCultureIgnoreCase)
- && c.Expired == false
+ && !c.Expired
&& c.Domain.Contains(domain, StringComparison.InvariantCultureIgnoreCase))?
.Value;
}
diff --git a/src/SteamLibForked/Authentication/SteamTokenHelper.cs b/src/SteamLibForked/Authentication/SteamTokenHelper.cs
index 6c8361e..ba4ccbc 100644
--- a/src/SteamLibForked/Authentication/SteamTokenHelper.cs
+++ b/src/SteamLibForked/Authentication/SteamTokenHelper.cs
@@ -44,7 +44,7 @@ public static class SteamTokenHelper
{
result = default;
var match = SteamTokenRegex.Match(input);
- if (match.Success == false)
+ if (!match.Success)
{
if (trying) return false;
throw new ArgumentException("Provided Token is not valid SteamLoginSecure or SteamAccess token");
@@ -64,7 +64,7 @@ public static class SteamTokenHelper
}
var steamIdStr = jwt.Subject;
- if (steamIdStr == null || long.TryParse(steamIdStr, out var steamId) == false || steamId < SteamId64.SEED)
+ if (steamIdStr == null || !long.TryParse(steamIdStr, out var steamId) || steamId < SteamId64.SEED)
{
if (trying) return false;
throw new ArgumentException(
@@ -101,7 +101,7 @@ public static class SteamTokenHelper
if (jwt.Audiences.ToList().Count > 0)
{
var aud = audiences[0];
- if (AudDomains.TryGetValue(aud, out domain) == false && aud != "web")
+ if (!AudDomains.TryGetValue(aud, out domain) && aud != "web")
{
if (trying)
{
@@ -180,13 +180,13 @@ public static class SteamTokenHelper
internal static string CombineLoginValueIfNeeded(long steamId, string loginValue)
{
- return CheckIfProperLoginValue(loginValue) == false ? CombineJwtWithSteamId(steamId, loginValue) : loginValue;
+ return !CheckIfProperLoginValue(loginValue) ? CombineJwtWithSteamId(steamId, loginValue) : loginValue;
}
public static string ExtractJwtToken(string steamLoginSecure)
{
var match = SteamTokenRegex.Match(steamLoginSecure);
- if (match.Success == false) throw new ArgumentException("Can't extract JWT Access Token from steamLoginSecure");
+ if (!match.Success) throw new ArgumentException("Can't extract JWT Access Token from steamLoginSecure");
return match.Groups["jwt"].Value;
}
}
\ No newline at end of file
diff --git a/src/SteamLibForked/ProtoCore/ProtoHttpClientExtension.cs b/src/SteamLibForked/ProtoCore/ProtoHttpClientExtension.cs
index 80eaded..98567e8 100644
--- a/src/SteamLibForked/ProtoCore/ProtoHttpClientExtension.cs
+++ b/src/SteamLibForked/ProtoCore/ProtoHttpClientExtension.cs
@@ -11,7 +11,7 @@ public static class ProtoHttpClientExtension
this HttpClient client, Uri uri, HttpMethod method, string? protoMsg, CancellationToken cancellationToken)
where TProtoResponse : class, IProtoMsg
{
- var resp = await SendProto(client, uri, method, protoMsg, cancellationToken);
+ var resp = await client.SendProto(uri, method, protoMsg, cancellationToken);
return await ProtoResponse.FromHttpResponseAsync(resp, cancellationToken);
}
@@ -19,7 +19,7 @@ public static class ProtoHttpClientExtension
string? protoMsg,
CancellationToken cancellationToken)
{
- var resp = await SendProto(client, uri, method, protoMsg, cancellationToken);
+ var resp = await client.SendProto(uri, method, protoMsg, cancellationToken);
resp.EnsureSuccessStatusCode();
return ProtoHelpers.GetEResult(resp);
}
@@ -97,7 +97,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- return SendProtoWithResponse(client, uri, HttpMethod.Post, str, cancellationToken);
+ return client.SendProtoWithResponse(uri, HttpMethod.Post, str, cancellationToken);
}
public static Task> PostProtoMsg(this HttpClient client, string uri,
@@ -105,7 +105,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- return SendProtoWithResponse(client, new Uri(uri), HttpMethod.Post, str, cancellationToken);
+ return client.SendProtoWithResponse(new Uri(uri), HttpMethod.Post, str, cancellationToken);
}
#endregion
@@ -117,7 +117,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- var res = await SendProtoWithResponse(client, uri, HttpMethod.Post, str, cancellationToken);
+ var res = await client.SendProtoWithResponse(uri, HttpMethod.Post, str, cancellationToken);
return res.GetResponseEnsureSuccess();
}
@@ -126,7 +126,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- var res = await SendProtoWithResponse(client, new Uri(uri), HttpMethod.Post, str,
+ var res = await client.SendProtoWithResponse(new Uri(uri), HttpMethod.Post, str,
cancellationToken);
return res.GetResponseEnsureSuccess();
}
@@ -179,7 +179,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- return SendProtoWithResponse(client, uri, HttpMethod.Get, str, cancellationToken);
+ return client.SendProtoWithResponse(uri, HttpMethod.Get, str, cancellationToken);
}
public static Task> GetProtoMsg(this HttpClient client, string uri,
@@ -187,7 +187,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- return SendProtoWithResponse(client, new Uri(uri), HttpMethod.Get, str, cancellationToken);
+ return client.SendProtoWithResponse(new Uri(uri), HttpMethod.Get, str, cancellationToken);
}
#endregion
@@ -199,7 +199,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- var res = await SendProtoWithResponse(client, uri, HttpMethod.Get, str, cancellationToken);
+ var res = await client.SendProtoWithResponse(uri, HttpMethod.Get, str, cancellationToken);
res.Result.EnsureSuccessEResult();
return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null");
@@ -210,7 +210,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{
var str = ProtoHelpers.ProtoToString(request);
- var res = await SendProtoWithResponse(client, new Uri(uri), HttpMethod.Get, str,
+ var res = await client.SendProtoWithResponse(new Uri(uri), HttpMethod.Get, str,
cancellationToken);
res.Result.EnsureSuccessEResult();
return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null");
diff --git a/src/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs b/src/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs
index 942138e..fda44bd 100644
--- a/src/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs
+++ b/src/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs
@@ -74,18 +74,18 @@ public class SteamAuthenticatorLinker
long? phoneNumber = null;
- if (hasPhone == false && Options.PhoneNumberProvider != null)
+ if (!hasPhone && Options.PhoneNumberProvider != null)
{
phoneNumber = await Options.PhoneNumberProvider.GetPhoneNumber(Consumer);
}
- if (hasPhone == false && phoneNumber != null)
+ if (!hasPhone && phoneNumber != null)
{
Logger?.LogInformation("Attaching phone number {phoneNumber}", phoneNumber);
//if (await this.IsValidPhoneNumber(phoneNumber.Value) == false)
// throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidPhoneNumber);
- if (await this.AttachPhone(phoneNumber.Value) == false)
+ if (!await this.AttachPhone(phoneNumber.Value))
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.CantAttachPhone);
@@ -150,7 +150,7 @@ public class SteamAuthenticatorLinker
Logger?.LogInformation("Finalizing link");
var result = await this.FinalizeLink(code, resp.SharedSecret, isPhone);
- if (result.Success == false)
+ if (!result.Success)
{
var error = result.Error switch
{
diff --git a/src/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs b/src/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs
index fecfd46..331fc4d 100644
--- a/src/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs
+++ b/src/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs
@@ -5,24 +5,21 @@ namespace SteamLib.Utility.MafileSerialization;
internal class LegacyMafile
{
[JsonProperty("shared_secret")]
- [JsonRequired]
- public string SharedSecret { get; set; } = default!;
+ public string SharedSecret { get; set; } = null!;
[JsonProperty("identity_secret")]
- [JsonRequired]
- public string IdentitySecret { get; set; } = default!;
+ public string IdentitySecret { get; set; } = null!;
[JsonProperty("device_id")]
- [JsonRequired]
- public string DeviceId { get; set; } = default!;
+ public string DeviceId { get; set; } = null!;
- [JsonProperty("revocation_code")] public string RevocationCode { get; set; } = default!;
- [JsonProperty("account_name")] public string AccountName { get; set; } = default!;
+ [JsonProperty("revocation_code")] public string RevocationCode { get; set; } = null!;
+ [JsonProperty("account_name")] public string AccountName { get; set; } = null!;
[JsonProperty("Session")] public object? SessionData { get; set; }
[JsonProperty("server_time")] public long ServerTime { get; set; } //Unused
[JsonProperty("steamid")] public long SteamId { get; set; }
- [JsonProperty("serial_number")] public string SerialNumber { get; set; } = default!; //Unused
- [JsonProperty("uri")] public string Uri { get; set; } = default!; //Unused
- [JsonProperty("token_gid")] public string TokenGid { get; set; } = default!; //Unused
- [JsonProperty("secret_1")] public string Secret1 { get; set; } = default!; //Unused
+ [JsonProperty("serial_number")] public string SerialNumber { get; set; } = null!; //Unused
+ [JsonProperty("uri")] public string Uri { get; set; } = null!; //Unused
+ [JsonProperty("token_gid")] public string TokenGid { get; set; } = null!; //Unused
+ [JsonProperty("secret_1")] public string Secret1 { get; set; } = null!; //Unused
}
\ No newline at end of file
diff --git a/src/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs b/src/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs
index 0f966e8..d710288 100644
--- a/src/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs
+++ b/src/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs
@@ -38,7 +38,7 @@ public partial class MafileSerializer //Validate
public static void IsValidBase64(string name, string base64)
{
var buffer = new Span(new byte[base64.Length]);
- if (Convert.TryFromBase64String(base64, buffer, out _) == false)
+ if (!Convert.TryFromBase64String(base64, buffer, out _))
throw new ArgumentException($"{name} is not valid base64 string");
}
diff --git a/src/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs b/src/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs
index d1f02b1..bfcce0b 100644
--- a/src/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs
+++ b/src/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs
@@ -39,7 +39,7 @@ public static class MobileConfirmationScrapper
throw new SessionInvalidException();
}
- if (conf.Success == false)
+ if (!conf.Success)
{
var error = LoadConfirmationsError.Unknown;
if (conf.Message != null && ErrorMessages.TryGetValue(conf.Message, out var e))