Add MafileExport feature and Perform Resharper Cleanup

This commit is contained in:
achiez
2026-01-19 21:28:44 +02:00
parent 3e87a0d50e
commit 969590d9f2
42 changed files with 1487 additions and 146 deletions
+2 -8
View File
@@ -3,8 +3,6 @@ using System.Windows;
using NebulaAuth.Core; using NebulaAuth.Core;
using NebulaAuth.Model; using NebulaAuth.Model;
using NebulaAuth.Model.Exceptions; using NebulaAuth.Model.Exceptions;
using NebulaAuth.Model.MAAC;
using NebulaAuth.Model.Mafiles;
namespace NebulaAuth; namespace NebulaAuth;
@@ -17,12 +15,8 @@ public partial class App
var splashScreen = new SplashScreen("Theme\\SplashScreen.png"); var splashScreen = new SplashScreen("Theme\\SplashScreen.png");
splashScreen.Show(false, true); splashScreen.Show(false, true);
base.OnStartup(e); base.OnStartup(e);
LocManager.Init();
LocManager.SetApplicationLocalization(Settings.Instance.Language); await Shell.Initialize();
Shell.Initialize();
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
await Storage.Initialize(threads);
MAACStorage.Initialize();
var mainWindow = new MainWindow(); var mainWindow = new MainWindow();
Current.MainWindow = mainWindow; Current.MainWindow = mainWindow;
mainWindow.Show(); mainWindow.Show();
@@ -23,6 +23,7 @@
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<materialDesign:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" /> <materialDesign:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
<system:Boolean x:Key="True">True</system:Boolean> <system:Boolean x:Key="True">True</system:Boolean>
<system:Boolean x:Key="False">False</system:Boolean> <system:Boolean x:Key="False">False</system:Boolean>
@@ -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;
}
}
+10
View File
@@ -113,6 +113,16 @@ public static class DialogsController
await DialogHost.Show(dialog); 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 #region CommonDialogs
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null) public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
+1 -1
View File
@@ -69,7 +69,7 @@ public static class TrayManager
private static void HideMainWindow() private static void HideMainWindow()
{ {
if (!Init) return; if (!Init) return;
if (IsEnabled == false) return; if (!IsEnabled) return;
_notifyIcon.Visible = true; _notifyIcon.Visible = true;
MainWindow.Hide(); MainWindow.Hide();
} }
+5 -3
View File
@@ -73,7 +73,7 @@
Kind="FileReplaceOutline" /> Kind="FileReplaceOutline" />
</StackPanel> </StackPanel>
<ToolBarTray> <ToolBarTray Grid.Row="0">
<ToolBar ClipToBounds="True" HorizontalContentAlignment="Stretch" <ToolBar ClipToBounds="True" HorizontalContentAlignment="Stretch"
Style="{StaticResource MaterialDesignToolBar}" FontSize="16"> Style="{StaticResource MaterialDesignToolBar}" FontSize="16">
<Menu FontSize="16"> <Menu FontSize="16">
@@ -92,7 +92,9 @@
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" /> <MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Other.Title}"> <MenuItem Header="{Tr MainWindow.Menu.File.Other.Title}">
<MenuItem Header="{Tr MainWindow.Menu.File.Other.SetPasswords}" <MenuItem Header="{Tr MainWindow.Menu.File.Other.SetPasswords}"
Command="{Binding DebugCommand}" /> Command="{Binding OpenSetPasswordsDialogCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Other.OpenExport}"
Command="{Binding OpenExporterDialogCommand}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
</Menu> </Menu>
@@ -356,7 +358,7 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<md:PackIcon Kind="PlusCircle" <md:PackIcon Kind="PlusCircle"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" <TextBlock VerticalAlignment="Center"
Margin="5,0,0,0" Grid.Column="1" Margin="5,0,0,0" Grid.Column="1"
Text="{Tr MainWindow.ContextMenus.Mafile.CreateGroup}" /> Text="{Tr MainWindow.ContextMenus.Mafile.CreateGroup}" />
+5 -5
View File
@@ -30,7 +30,7 @@ public partial class MainWindow
private async void OnApplicationStarted(object? sender, EventArgs e) private async void OnApplicationStarted(object? sender, EventArgs e)
{ {
((MainVM) DataContext).CurrentDialogHost = DialogHostInstance; ((MainVM) DataContext).CurrentDialogHost = DialogHostInstance;
if (Settings.Instance.IsPasswordSet == false) return; if (!Settings.Instance.IsPasswordSet) return;
Topmost = false; Topmost = false;
await Dispatcher.InvokeAsync(ShowSetPasswordDialog, DispatcherPriority.ContextIdle); await Dispatcher.InvokeAsync(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
} }
@@ -46,7 +46,7 @@ public partial class MainWindow
var result = await DialogHost.Show(dialog); var result = await DialogHost.Show(dialog);
var pass = vm.Password; var pass = vm.Password;
if (result is true && string.IsNullOrWhiteSpace(pass) == false) if (result is true && !string.IsNullOrWhiteSpace(pass))
{ {
PHandler.SetPassword(pass); PHandler.SetPassword(pass);
} }
@@ -79,12 +79,12 @@ public partial class MainWindow
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e) 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) private void Rectangle_DragEnter(object sender, DragEventArgs e)
{ {
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{ {
e.Handled = true; e.Handled = true;
return; return;
@@ -96,7 +96,7 @@ public partial class MainWindow
private async void Rectangle_Drop(object sender, DragEventArgs e) 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)!; var filePaths = (string[]) e.Data.GetData(DataFormats.FileDrop)!;
if (filePaths.Length == 0) return; if (filePaths.Length == 0) return;
if (DataContext is MainVM mainVm) if (DataContext is MainVM mainVm)
@@ -187,7 +187,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
private bool IgnoreTimerErrors() private bool IgnoreTimerErrors()
{ {
if (Settings.Instance.IgnorePatchTuesdayErrors == false) return false; if (!Settings.Instance.IgnorePatchTuesdayErrors) return false;
var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone); var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone);
+1 -1
View File
@@ -153,7 +153,7 @@ public static class MaClient
if (mafile.SessionData.RefreshToken.IsExpired) if (mafile.SessionData.RefreshToken.IsExpired)
throw new SessionPermanentlyExpiredException(); throw new SessionPermanentlyExpiredException();
if (ignoreAccessToken == false) if (!ignoreAccessToken)
{ {
var access = mafile.SessionData.GetMobileToken(); var access = mafile.SessionData.GetMobileToken();
if (access == null || access.Value.IsExpired) if (access == null || access.Value.IsExpired)
@@ -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<Mafile, string>? Exported { get; }
public List<string>? NotFound { get; }
public List<string>? 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<Mafile, string>? exported, List<string>? notFound,
List<string>? conflict)
{
Error = error;
Exported = exported;
NotFound = notFound;
Conflict = conflict;
}
public ExportResult(Dictionary<Mafile, string> exported, List<string> notFound, List<string> conflict)
: this(null, exported, notFound, conflict)
{
}
public ExportResult(string? error)
: this(error, null, null, null)
{
}
}
@@ -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; }
}
@@ -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<ExportResult> ExportMafiles(IEnumerable<string> 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<Mafile, string>();
var notFound = new List<string>();
var conflict = new List<string>();
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<string?> 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));
}
}
@@ -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<MafileExportTemplate> 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<List<MafileExportTemplate>>(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<MafileExportTemplate> 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
}
];
}
}
@@ -1,12 +1,13 @@
using NebulaAuth.Model.Entities; using System.IO;
using System.IO; using NebulaAuth.Model.Entities;
namespace NebulaAuth.Model.Mafiles; namespace NebulaAuth.Model.Mafiles;
internal static class MafilesStorageHelper internal static class MafilesStorageHelper
{ {
/// <summary> /// <summary>
/// Returns <see cref="Mafile.Filename" /> or creates a new one and updates the property. <see cref="Mafile.Filename"/> is always not <see langword="null"/> after this method call. /// Returns <see cref="Mafile.Filename" /> or creates a new one and updates the property.
/// <see cref="Mafile.Filename" /> is always not <see langword="null" /> after this method call.
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <returns></returns> /// <returns></returns>
+8
View File
@@ -22,12 +22,19 @@ public static class Storage
public const string DIR_MAFILES = "maFiles"; public const string DIR_MAFILES = "maFiles";
public const string DIR_REMOVED_MAFILES = "maFiles_removed"; public const string DIR_REMOVED_MAFILES = "maFiles_removed";
public const string DIR_BACKUP_MAFILES = "maFiles_backup"; 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 MafilesDirectory { get; } = Path.GetFullPath(DIR_MAFILES);
public static string RemovedMafilesDirectory { get; } = Path.GetFullPath(DIR_REMOVED_MAFILES); public static string RemovedMafilesDirectory { get; } = Path.GetFullPath(DIR_REMOVED_MAFILES);
public static string BackupMafilesDirectory { get; } = Path.GetFullPath(DIR_BACKUP_MAFILES); public static string BackupMafilesDirectory { get; } = Path.GetFullPath(DIR_BACKUP_MAFILES);
public static ObservableCollection<Mafile> MaFiles { get; private set; } = []; public static ObservableCollection<Mafile> MaFiles { get; private set; } = [];
static Storage()
{
MafilesDirectories = [MafilesDirectory, RemovedMafilesDirectory, BackupMafilesDirectory];
}
public static async Task Initialize(int threadCount, CancellationToken token = default) public static async Task Initialize(int threadCount, CancellationToken token = default)
{ {
Directory.CreateDirectory(MafilesDirectory); Directory.CreateDirectory(MafilesDirectory);
@@ -60,6 +67,7 @@ public static class Storage
}, token); }, token);
MaFiles = new ObservableCollection<Mafile>(localList.OrderBy(m => m.AccountName)); MaFiles = new ObservableCollection<Mafile>(localList.OrderBy(m => m.AccountName));
} }
/// <summary> /// <summary>
+2 -2
View File
@@ -38,7 +38,7 @@ public static class NebulaSerializer
var data = Serializer.Deserialize(cont); var data = Serializer.Deserialize(cont);
var mobileData = data.Data; var mobileData = data.Data;
var info = data.Info; var info = data.Info;
if (info.IsExtended == false) if (!info.IsExtended)
throw new FormatException("Mafile is not extended data"); throw new FormatException("Mafile is not extended data");
@@ -58,7 +58,7 @@ public static class NebulaSerializer
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary) private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
{ {
if (dictionary.TryGetValue(name, out var prop) == false) return default; if (!dictionary.TryGetValue(name, out var prop)) return default;
var value = prop.Value; var value = prop.Value;
try try
{ {
+1 -1
View File
@@ -28,7 +28,7 @@ public static class ProxyStorage
static ProxyStorage() static ProxyStorage()
{ {
if (File.Exists("proxies.json") == false) if (!File.Exists("proxies.json"))
return; return;
try try
{ {
+1 -1
View File
@@ -45,7 +45,7 @@ public static partial class SessionHandler
return await func(); return await func();
} }
catch (SessionInvalidException ex) catch (SessionInvalidException ex)
when (refreshTokenExpired == false || password != null) when (!refreshTokenExpired || password != null)
{ {
if (ex is SessionPermanentlyExpiredException) if (ex is SessionPermanentlyExpiredException)
{ {
+1 -1
View File
@@ -25,7 +25,7 @@ public partial class Settings : ObservableObject
static Settings() static Settings()
{ {
if (File.Exists("settings.json") == false) if (!File.Exists("settings.json"))
{ {
Instance = new Settings(); Instance = new Settings();
Instance.PropertyChanged += SettingsOnPropertyChanged; Instance.PropertyChanged += SettingsOnPropertyChanged;
+16 -2
View File
@@ -1,6 +1,12 @@
using System; using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NebulaAuth.Core;
using NebulaAuth.Model.Exceptions; using NebulaAuth.Model.Exceptions;
using NebulaAuth.Model.MAAC;
using NebulaAuth.Model.MafileExport;
using NebulaAuth.Model.Mafiles;
using NLog; using NLog;
using NLog.Extensions.Logging; using NLog.Extensions.Logging;
using SteamLib.Core; using SteamLib.Core;
@@ -14,8 +20,11 @@ public static class Shell
public static Logger Logger { get; private set; } = null!; public static Logger Logger { get; private set; } = null!;
public static ILogger ExtensionsLogger { 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"); Logger = LogManager.GetLogger("Logger");
var lp = new NLogLoggerProvider(); var lp = new NLogLoggerProvider();
var logger = lp.CreateLogger("SteamLib"); var logger = lp.CreateLogger("SteamLib");
@@ -27,13 +36,18 @@ public static class Shell
try try
{ {
TimeAligner.AlignTime(); await TimeAligner.AlignTimeAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new CantAlignTimeException("", 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"); ExtensionsLogger.LogDebug("Application started");
} }
+1 -2
View File
@@ -5,8 +5,7 @@
throwExceptions="true"> throwExceptions="true">
<targets> <targets>
<target xsi:type="File" name="File" fileName="log.log" <target xsi:type="File" name="File" fileName="${basedir}/logs/log-${shortdate}.log">
deleteOldFileOnStartup="true">
<layout xsi:type='CompoundLayout'> <layout xsi:type='CompoundLayout'>
<layout xsi:type="JsonLayout" includeEventProperties="true" IncludeScopeProperties="true"> <layout xsi:type="JsonLayout" includeEventProperties="true" IncludeScopeProperties="true">
<attribute name="time" layout="${longdate}" /> <attribute name="time" layout="${longdate}" />
+1 -1
View File
@@ -28,7 +28,7 @@
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.4.1" /> <PackageReference Include="CodingSeb.Localization.WPF" Version="1.4.1" />
<PackageReference Include="CodingSebLocalization.Fody" Version="1.4.0" /> <PackageReference Include="CodingSebLocalization.Fody" Version="1.4.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="MaterialDesignThemes" Version="5.2.1" /> <PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageReference Include="NLog" Version="5.5.0" /> <PackageReference Include="NLog" Version="5.5.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.5.0" /> <PackageReference Include="NLog.Extensions.Logging" Version="5.5.0" />
+2 -2
View File
@@ -31,8 +31,8 @@ public static class LanguageUtility
"RU", "BY", "KZ", "KG", "TJ", "TM", "UZ", "AM", "AZ", "GE", "MD" "RU", "BY", "KZ", "KG", "TJ", "TM", "UZ", "AM", "AZ", "GE", "MD"
]; ];
return cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase)) return cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase))
? LocalizationLanguage.Russian ? LocalizationLanguage.Russian
: LocalizationLanguage.English; : LocalizationLanguage.English;
} }
} }
+240
View File
@@ -0,0 +1,240 @@
<UserControl x:Class="NebulaAuth.View.MafileExporterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
FontFamily="{materialDesign:MaterialDesignFont}"
MinWidth="400"
MaxWidth="400"
Background="{DynamicResource WindowBackground}"
d:DataContext="{d:DesignInstance other:MafileExporterVM}"
d:Background="#141119"
d:Foreground="White"
FontSize="18">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FileExport" Width="20" Height="20" Margin="0,0,5,0"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryContentBrush}" />
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="18" Text="{Tr ExportDialog.ExportTitle}" />
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
IsCancel="True">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Background="DarkGray" Opacity="0.4" Grid.Row="1" />
<Grid Grid.Row="2" Margin="15" TextElement.FontSize="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0"
Padding="10"
Style="{StaticResource MaterialDesignOutlinedComboBox}"
Margin="0,0,5,0"
materialDesign:HintAssist.Hint="{Tr ExportDialog.TemplateHint}"
ItemsSource="{Binding Templates}"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentTemplate}"
Visibility="{Binding EditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
<TextBox x:Name="EditTemplateNameTextBox"
Grid.Column="0"
Padding="10"
Margin="0,0,5,0"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
materialDesign:HintAssist.Hint="{Tr ExportDialog.TemplateHint}"
Text="{Binding CurrentTemplate.Name,
FallbackValue=''}"
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}"
Visibility="{Binding EditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding ToggleEditModeCommand}" />
</TextBox.InputBindings>
</TextBox>
<Button Command="{Binding AddTemplateCommand}" Grid.Column="1">
<materialDesign:PackIcon Kind="Add" />
</Button>
<Button Command="{Binding ToggleEditModeCommand}" Click="EditButton_Click" Grid.Column="2">
<materialDesign:PackIcon Kind="Edit" />
</Button>
<Button Grid.Column="3" Command="{Binding RemoveCurrentTemplateCommand}">
<materialDesign:PackIcon Kind="Trash" />
</Button>
</Grid>
<Grid Margin="0,15,0,0" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}"
MouseDoubleClick="PathTB_DoubleClick" materialDesign:TextFieldAssist.HasClearButton="True"
Text="{Binding CurrentTemplate.Path, FallbackValue=''}" Padding="10"
Style="{StaticResource MaterialDesignOutlinedTextBox}" Grid.Column="0"
materialDesign:HintAssist.Hint="{Tr ExportDialog.SelectPathHint}" />
<Button Command="{Binding OpenSelectDirectoryDialogCommand}" Grid.Column="1" Margin="5,0,0,0">
<materialDesign:PackIcon Kind="FolderOpen" />
</Button>
</Grid>
<Border Grid.Row="2" BorderThickness="1" CornerRadius="4" Margin="0,15,0,0" Padding="2">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseContentColor}" Opacity="0.4" />
</Border.BorderBrush>
<Expander x:Name="TemplateExpander"
Header="{Tr ExportDialog.ExportSettingsHeader}"
materialDesign:ExpanderAssist.HorizontalHeaderPadding="5"
materialDesign:ExpanderAssist.ExpanderButtonPosition="Start"
HorizontalAlignment="Stretch"
Padding="5,0,5,5"
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}">
<StackPanel>
<TextBlock FontWeight="DemiBold"
Text="{Tr ExportDialog.ExportSettingsCommon}" />
<CheckBox IsChecked="{Binding CurrentTemplate.UseLoginAsMafileName, FallbackValue=False}"
Margin="3,0,0,0"
Content="{Tr ExportDialog.ExportOptions.UseLoginAsMafileName}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.UseLoginAsMafileName}" />
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeSharedSecret, FallbackValue=False}"
Margin="3,0,0,0"
Content="{Tr ExportDialog.ExportOptions.IncludeSharedSecret}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.IncludeSharedSecret}" />
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeIdentitySecret, FallbackValue=False}"
Margin="3,0,0,0"
Content="{Tr ExportDialog.ExportOptions.IncludeIdentitySecret}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.IncludeIdentitySecret}" />
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeRCode, FallbackValue=False}"
Margin="3,0,0,0"
Content="{Tr ExportDialog.ExportOptions.IncludeRCode}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.IncludeRCode}" />
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeSessionData, FallbackValue=False}"
Margin="3,0,0,0"
Content="{Tr ExportDialog.ExportOptions.IncludeSessionData}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.IncludeSessionData}" />
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeOtherInfo, FallbackValue=False}"
Margin="3,0,0,0"
Content="{Tr ExportDialog.ExportOptions.IncludeOtherInfo}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.IncludeOtherInfo}" />
<TextBlock FontWeight="DemiBold"
Margin="0,10,0,0"
Text="{Tr ExportDialog.ExportSettingsNebula}" />
<Grid Margin="3,0,3,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding CurrentTemplate.IncludeNebulaProxy, FallbackValue=False}"
Content="{Tr ExportDialog.ExportOptions.NebulaProxy}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.NebulaProxy}" />
<CheckBox Grid.Column="1"
HorizontalAlignment="Center"
IsChecked="{Binding CurrentTemplate.IncludeNebulaPassword, FallbackValue=False}"
Content="{Tr ExportDialog.ExportOptions.NebulaPassword}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.NebulaPassword}" />
<CheckBox Grid.Column="2"
HorizontalAlignment="Right"
IsChecked="{Binding CurrentTemplate.IncludeNebulaGroup, FallbackValue=False}"
Content="{Tr ExportDialog.ExportOptions.NebulaGroup}"
ToolTipService.InitialShowDelay="200"
ToolTip="{Tr ExportDialog.Tooltips.NebulaGroup}" />
</Grid>
</StackPanel>
</Expander>
</Border>
<TextBox GotFocus="ExportTB_GotFocus" PreviewMouseUp="ExportTB_GotFocus" Grid.Row="3"
Style="{StaticResource MaterialDesignOutlinedTextBox}" MaxHeight="600" Margin="0,10,0,0"
MinHeight="250" Height="250" AcceptsReturn="True"
Text="{Binding AccountsText, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding ExportCommand.IsRunning, Converter={StaticResource ReverseBooleanConverter}}"
Name="AccountsTB" />
<TextBlock Grid.Row="3"
Text="{Tr ExportDialog.ExportAccountsPlaceholder}"
Margin="18,27,10,10"
Opacity="0.6"
IsHitTestVisible="False"
TextWrapping="Wrap"
Visibility="{Binding Text.IsEmpty,
ElementName=AccountsTB,
Converter={StaticResource BooleanToVisibilityConverter}}" />
<nebulaAuth:HintBox Margin="0,10,0,0" Severity="{Binding HintBoxSeverity}" Grid.Row="4"
Text="{Binding HintText}" />
</Grid>
</Grid>
<Separator Grid.Row="3" />
<Button Command="{Binding ExportCommand}" Style="{StaticResource MaterialDesignFlatDarkBgButton}" Grid.Row="4"
Margin="20,7,20,7" MaxWidth="150" Content="{Tr ExportDialog.ExportButton}" />
</Grid>
</UserControl>
@@ -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);
}
}
-20
View File
@@ -12,7 +12,6 @@ using NebulaAuth.Model;
using NebulaAuth.Model.Entities; using NebulaAuth.Model.Entities;
using NebulaAuth.Model.Mafiles; using NebulaAuth.Model.Mafiles;
using NebulaAuth.Utility; using NebulaAuth.Utility;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs; using NebulaAuth.View.Dialogs;
using SteamLib.SteamMobile; using SteamLib.SteamMobile;
using SteamLibForked.Exceptions.Authorization; using SteamLibForked.Exceptions.Authorization;
@@ -23,7 +22,6 @@ public partial class MainVM : ObservableObject
{ {
[UsedImplicitly] public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue; [UsedImplicitly] public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
public Mafile? SelectedMafile public Mafile? SelectedMafile
{ {
get => _selectedMafile; get => _selectedMafile;
@@ -50,10 +48,8 @@ public partial class MainVM : ObservableObject
[RelayCommand] [RelayCommand]
public async Task Debug() public async Task Debug()
{ {
await DialogsController.ShowSetAccountsPasswordDialog();
} }
private void SetMafile(Mafile? mafile) private void SetMafile(Mafile? mafile)
{ {
if (mafile != SelectedMafile) if (mafile != SelectedMafile)
@@ -135,12 +131,6 @@ public partial class MainVM : ObservableObject
} }
} }
[RelayCommand]
public async Task LinkAccount()
{
await DialogsController.ShowLinkerDialog();
}
[RelayCommand] [RelayCommand]
public async Task MoveAccount() 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) private static string GetLocalization(string key)
{ {
const string locPath = "MainVM"; const string locPath = "MainVM";
+1 -1
View File
@@ -240,7 +240,7 @@ public partial class MainVM //File //TODO: Refactor
} }
var arr = files.Cast<string>().ToArray(); var arr = files.Cast<string>().ToArray();
if (arr.All(p => p.ContainsIgnoreCase("mafile") == false)) return; if (arr.All(p => !p.ContainsIgnoreCase("mafile"))) return;
await AddMafile(arr); await AddMafile(arr);
+2 -2
View File
@@ -88,7 +88,7 @@ public partial class MainVM //Groups
Storage.UpdateMafile(mafile); Storage.UpdateMafile(mafile);
OnPropertyChanged(nameof(SelectedMafile)); //For bindings OnPropertyChanged(nameof(SelectedMafile)); //For bindings
QueryGroups(); QueryGroups();
if (Groups.All(g => g.Equals(mafGroup) == false)) if (Groups.All(g => !g.Equals(mafGroup)))
{ {
SelectedGroup = null; SelectedGroup = null;
} }
@@ -104,7 +104,7 @@ public partial class MainVM //Groups
private void QueryGroups() private void QueryGroups()
{ {
var groups = Storage.MaFiles var groups = Storage.MaFiles
.Where(m => string.IsNullOrWhiteSpace(m.Group) == false) .Where(m => !string.IsNullOrWhiteSpace(m.Group))
.Select(m => m.Group) .Select(m => m.Group)
.Distinct() .Distinct()
.Order() .Order()
+1 -1
View File
@@ -130,7 +130,7 @@ public partial class MainVM //MAAC
{ {
mafiles = mafiles.ToArray(); 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) if (turnOn)
{ {
foreach (var mafile in mafiles) foreach (var mafile in mafiles)
+38
View File
@@ -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;
}
}
@@ -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<MafileExportTemplateVM> 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<MafileExportTemplateVM>(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;
}
}
@@ -61,7 +61,7 @@ public partial class ProxyManagerVM : ObservableObject
var split = input var split = input
.Split(Environment.NewLine) .Split(Environment.NewLine)
.Where(s => string.IsNullOrWhiteSpace(s) == false) .Where(s => !string.IsNullOrWhiteSpace(s))
.Select(x => x.Trim()) .Select(x => x.Trim())
.ToArray(); .ToArray();
@@ -72,7 +72,7 @@ public partial class ProxyManagerVM : ObservableObject
var i = 0; 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++; i++;
var str = s; var str = s;
+1 -1
View File
@@ -144,7 +144,7 @@ public partial class SettingsVM : ObservableObject
get => IconColor != null; get => IconColor != null;
set set
{ {
if (value == false) if (!value)
IconColor = null; IconColor = null;
else else
IconColor = Color.FromRgb(202, 39, 39); IconColor = Color.FromRgb(202, 39, 39);
+355 -46
View File
@@ -180,6 +180,13 @@
"zh": "设置密码", "zh": "设置密码",
"ua": "Призначити паролі", "ua": "Призначити паролі",
"fr": "Définir les mots de passe" "fr": "Définir les mots de passe"
},
"OpenExport": {
"ru": "Экспорт",
"en": "Export",
"ua": "Експорт",
"fr": "Exporter",
"zh": "导出"
} }
} }
}, },
@@ -652,10 +659,12 @@
}, },
"LegacyMafileModeHint": { "LegacyMafileModeHint": {
"en": "Save mafiles in compatibility format with Steam Desktop Authenticator and other applications", "en": "Save mafiles in compatibility format with Steam Desktop Authenticator and other applications",
"ru": "Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями", "ru":
"Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
"zh": "以与 Steam 桌面认证器及其他应用程序兼容的格式保存 mafiles", "zh": "以与 Steam 桌面认证器及其他应用程序兼容的格式保存 mafiles",
"ua": "Зберігати мафайли у старому форматі для сумісності зі Steam Desktop Authenticator та іншими додатками", "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": { "AllowAutoUpdate": {
"en": "Allow auto update", "en": "Allow auto update",
@@ -686,10 +695,13 @@
"fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir dans le timer (exp.)" "fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir dans le timer (exp.)"
}, },
"IgnorePatchTuesdayErrorsHint": { "IgnorePatchTuesdayErrorsHint": {
"en": "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily", "en":
"ru": "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности", "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 进行技术维护时自动确认功能会被无故关闭", "zh": "忽略每周三(格林尼治标准时间周三夜间)发生的错误,因为 Steam 进行技术维护时自动确认功能会被无故关闭",
"ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби", "ua":
"Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби",
"fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir (Mercredi à minuit en GMT)" "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" "fr": "Mot de passe de chiffrement"
}, },
"DialogText": { "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)", "en":
"ru": "Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией.\r\n(Не обязательно)", "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(非必填)", "zh": "之前您为密码文件设置了加密密码,请输入它,以便应用程序在会话出现问题时可以自动登录。\r\n(非必填)",
"ua": "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)", "ua":
"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)" "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\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": { "Ok": {
"en": "Ok", "en": "Ok",
@@ -1019,6 +1035,209 @@
"fr": "Entrer la valeur" "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 dexportation",
"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 didentité",
"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 dautres 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 daccè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 daccé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 à lavenir 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": { "SetAccountPasswordsView": {
"Title": { "Title": {
"en": "Set passwords", "en": "Set passwords",
@@ -1087,11 +1306,15 @@
"fr": "RCode manquant" "fr": "RCode manquant"
}, },
"ConfirmRemovingAuthenticator": { "ConfirmRemovingAuthenticator": {
"ru": "Вы точно уверены, что хотите удалить аутентификатор?\r\nПосле этого вы не сможете обмениваться, использовать торговую площадку 15 дней. Мафайл будет удалён навсегда", "ru":
"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", "Вы точно уверены, что хотите удалить аутентификатор?\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 将被永久删除。", "zh": "您确定要移除身份验证器吗?\r\n之后,您将在15天内无法进行交易或使用市场。Mafile 将被永久删除。",
"ua": "Ви точно впевнені, що хочете видалити автентифікатор?\r\nПісля цього ви не зможете обмінюватися, використовувати торгову площадку 15 днів. Мафайл буде видалено назавжди", "ua":
"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" "Ви точно впевнені, що хочете видалити автентифікатор?\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": { "AuthenticatorRemoved": {
"ru": "Аутентификатор удален", "ru": "Аутентификатор удален",
@@ -1185,11 +1408,15 @@
"fr": "erreurs:" "fr": "erreurs:"
}, },
"RemoveMafileConfirmation": { "RemoveMafileConfirmation": {
"ru": "Удалить mafile?\r\nМафайл будет перемещен в папку 'mafiles_removed'\r\nЭто действие НЕ удаляет Steam Guard с аккаунта", "ru":
"en": "Remove mafile?\r\nMafile will be moved to 'mafiles_removed' directory\r\nThis action will NOT remove the Steam Guard from the account", "Удалить 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此操作不会从账户中移除令牌", "zh": "是否删除mafile\r\nMafile将被移动到“mafiles_removed”目录\r\n此操作不会从账户中移除令牌",
"ua": "Видалити mafile?\r\nМафайл буде переміщено у каталог 'mafiles_removed'\r\nЦя дія НЕ видаляє автентифікатор з облікового запису", "ua":
"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" "Видалити 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": { "CantRemoveAlreadyExist": {
"ru": "Не удалось переместить mafile, возможно в папке уже существует мафайл с таким именем.", "ru": "Не удалось переместить mafile, возможно в папке уже существует мафайл с таким именем.",
@@ -1224,7 +1451,8 @@
"en": "Can't retrieve SteamID from mafile to save changes. Need to relogin. Canceled", "en": "Can't retrieve SteamID from mafile to save changes. Need to relogin. Canceled",
"zh": "无法从配置文件获取 SteamID 并保存更改。需要重新登录。已取消", "zh": "无法从配置文件获取 SteamID 并保存更改。需要重新登录。已取消",
"ua": "Не вдалося отримати 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": { "LoginCopied": {
"en": "Login copied", "en": "Login copied",
@@ -1263,9 +1491,11 @@
}, },
"CantDecryptPassword": { "CantDecryptPassword": {
"en": "Can't decrypt password. Maybe password from this mafile was encrypted with another encryption password", "en": "Can't decrypt password. Maybe password from this mafile was encrypted with another encryption password",
"ru": "Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования", "ru":
"Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
"zh": "无法解密密码。可能此文件中的密码是用其他加密密码加密的。", "zh": "无法解密密码。可能此文件中的密码是用其他加密密码加密的。",
"ua": "Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування", "ua":
"Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування",
"fr": "Impossible de déchiffrer le mot de passe. Le mot de passe de chiffrement est peut-être différent" "fr": "Impossible de déchiffrer le mot de passe. Le mot de passe de chiffrement est peut-être différent"
}, },
"CreateGroupTitle": { "CreateGroupTitle": {
@@ -1620,10 +1850,13 @@
}, },
"InvalidStateWithStatus2": { "InvalidStateWithStatus2": {
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.", "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. 请打开本窗口顶部的故障排除指南链接。", "zh": "状态无效(InvalidState),状态码为 2. 请打开本窗口顶部的故障排除指南链接。",
"ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна.", "ua":
"fr": "État invalide (InvalidState) avec le statut 2. Ouvrez le lien vers le guide de dépannage en haut de cette fenêtre." "Невірний стан (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": { "GeneralFailure": {
"ru": "General Failure", "ru": "General Failure",
@@ -1689,7 +1922,8 @@
"ru": "Ошибка: Был запрошен вход с помощью Steam Guard. На аккаунте уже активирован мобильный аутентификатор", "ru": "Ошибка: Был запрошен вход с помощью Steam Guard. На аккаунте уже активирован мобильный аутентификатор",
"en": "Error: Login with Steam Guard was requested. Mobile authenticator is already activated on the account", "en": "Error: Login with Steam Guard was requested. Mobile authenticator is already activated on the account",
"zh": "错误:请求使用令牌登录。该账户已激活移动验证器", "zh": "错误:请求使用令牌登录。该账户已激活移动验证器",
"ua": "Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор", "ua":
"Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор",
"fr": "Erreur: Connexion avec Steam Guard demandée. Authenticateur mobile est déjà activé sur le compte" "fr": "Erreur: Connexion avec Steam Guard demandée. Authenticateur mobile est déjà activé sur le compte"
}, },
"Unknown": { "Unknown": {
@@ -1828,11 +2062,15 @@
"fr": "Un email a été envoyé. Ouvrez le lien de l'email, puis cliquez sur 'Continuer'" "fr": "Un email a été envoyé. Ouvrez le lien de l'email, puis cliquez sur 'Continuer'"
}, },
"ClickOnEmailLinkRetry": { "ClickOnEmailLinkRetry": {
"ru": "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'", "ru":
"en": "We couldnt verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'", "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
"en":
"We couldnt verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
"zh": "无法确认通过链接的跳转。请确保您是从邮件中打开的链接,然后点击——“继续”", "zh": "无法确认通过链接的跳转。请确保您是从邮件中打开的链接,然后点击——“继续”",
"ua": "Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'", "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'" "Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'",
"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": { "PhoneHint": {
"ru": "На телефон {0} была отправлена СМС", "ru": "На телефон {0} была отправлена СМС",
@@ -1842,11 +2080,15 @@
"fr": "SMS envoyé au téléphone {0}" "fr": "SMS envoyé au téléphone {0}"
}, },
"EnterPhoneNumber": { "EnterPhoneNumber": {
"ru": "Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера", "ru":
"en": "Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number", "Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
"en":
"Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
"zh": "请输入国际格式的号码,仅数字(不含空格和括号),或留空以绑定无号码", "zh": "请输入国际格式的号码,仅数字(不含空格和括号),或留空以绑定无号码",
"ua": "Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера", "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" "Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера",
"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": { "MafileMoverVM": {
@@ -1879,10 +2121,12 @@
"fr": "RCode: {0}\r\nNom du fichier: {1}.mafile" "fr": "RCode: {0}\r\nNom du fichier: {1}.mafile"
}, },
"GuardIsNotActive": { "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", "en": "Steam Guard is set on this account. To link mafile to the account, use the 'Link' window",
"zh": "此账户已启用令牌。要将 mafile 绑定到此账户,请使用“绑定”窗口。", "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'" "fr": "Steam Guard est configuré sur ce compte. Pour lier mafile au compte, utilisez la fenêtre 'Lien'"
}, },
"SeemsNoPhoneNumber": { "SeemsNoPhoneNumber": {
@@ -1930,11 +2174,15 @@
"fr": "Auto " "fr": "Auto "
}, },
"TimerPreventedOverlap": { "TimerPreventedOverlap": {
"ru": "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку", "ru":
"en": "Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay", "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
"en":
"Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
"zh": "自动: 确认计时器在前一个周期尚未完成时尝试启动新周期。我们建议增加延迟。", "zh": "自动: 确认计时器在前一个周期尚未完成时尝试启动新周期。我们建议增加延迟。",
"ua": "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку", "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" "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку",
"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": { "FailedToLoadStorage": {
"en": "Failed to load auto-confirm timers. File seems to be corrupted", "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}" "fr": "Tous les mafiles ont été renommés avec succès ({0}).\r\nUn backup a été créé: {1}"
}, },
"PartialSuccess": { "PartialSuccess": {
"en": "Total mafiles: {0}\r\nRenamed: {1}\r\nConflicts: {2}\r\nErrors: {3}\r\n\r\nBackup of mafiles saved in file: {4}", "en":
"ru": "Всего мафайлов: {0}\r\nПереименовано: {1}\r\nКонфликты: {2}\r\nОшибок: {3}\r\n\r\nРезервная копия мафайлов сохранена в файле: {4}", "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}", "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}", "ua":
"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}" "Всього мафайлів: {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}", "ua": "Успішно: {0}, Не знайдено: {1}, Помилки: {2}",
"fr": "Succès: {0}, Non trouvé: {1}, Erreurs: {2}" "fr": "Succès: {0}, Non trouvé: {1}, Erreurs: {2}"
} }
},
"MafileExporter": {
"InvalidPath": {
"ru": "Указан некорректный путь для экспорта.",
"en": "The specified export path is invalid.",
"ua": "Вказано некоректний шлях для експорту.",
"fr": "Le chemin dexportation 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 dexporter vers un dossier utilisé par lapplication. 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 daccè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": { "CantAlignTimeError": {
"ru": "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log", "ru":
"en": "Can't align time with Steam. Maybe no internet connection or Steam is unavailable. Detailed error saved in log.log", "Не удается синхронизировать время со 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 中", "zh": "无法与 Steam 对齐时间。可能没有互联网连接或 Steam 不可用。详细错误已保存到 log.log 中",
"ua": "Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log", "ua":
"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" "Не вдається синхронізувати час з 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"
} }
} }
@@ -152,7 +152,7 @@ public static class SteamAuthenticatorLinkerApi
var content = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); var content = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
var j = JObject.Parse(content); var j = JObject.Parse(content);
if (j["success"]!.Value<bool>() == false) if (!j["success"]!.Value<bool>())
return CheckPhoneResult.GeneralFailure; return CheckPhoneResult.GeneralFailure;
if (j["is_voip"]!.Value<bool>()) if (j["is_voip"]!.Value<bool>())
@@ -187,7 +187,7 @@ public static class SteamAuthenticatorLinkerApi
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri,
new EmptyMessage()); new EmptyMessage());
if (resp.IsWaiting == false) return true; if (!resp.IsWaiting) return true;
await Task.Delay(resp.SecondsToWait * 1000); await Task.Delay(resp.SecondsToWait * 1000);
} }
@@ -68,7 +68,7 @@ public static class AdmissionHelper
{ {
var uri = SteamDomains.GetDomainUri(domain); var uri = SteamDomains.GetDomainUri(domain);
foreach (var cookie in container.GetCookies(uri) 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; cookie.Expired = true;
} }
@@ -131,7 +131,7 @@ public static class AdmissionHelper
} }
var mobileToken = mobileSession.GetMobileToken(); var mobileToken = mobileSession.GetMobileToken();
if (domainCookieSet == false && mobileToken is {IsExpired: false}) if (!domainCookieSet && mobileToken is {IsExpired: false})
{ {
var domain = SteamDomains.GetDomainUri(SteamDomain.Community); var domain = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, mobileToken.Value.SignedToken) container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, mobileToken.Value.SignedToken)
@@ -193,7 +193,7 @@ public static class AdmissionHelper
foreach (Cookie cookie in cookies) 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; cookie.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)) continue;
@@ -253,7 +253,7 @@ public static class AdmissionHelper
var cookies = container.GetAllCookies(); var cookies = container.GetAllCookies();
return cookies return cookies
.FirstOrDefault(c => c.Name.Equals(SESSION_ID_COOKIE_NAME, StringComparison.InvariantCultureIgnoreCase) .FirstOrDefault(c => c.Name.Equals(SESSION_ID_COOKIE_NAME, StringComparison.InvariantCultureIgnoreCase)
&& c.Expired == false && !c.Expired
&& c.Domain.Contains(domain, StringComparison.InvariantCultureIgnoreCase))? && c.Domain.Contains(domain, StringComparison.InvariantCultureIgnoreCase))?
.Value; .Value;
} }
@@ -44,7 +44,7 @@ public static class SteamTokenHelper
{ {
result = default; result = default;
var match = SteamTokenRegex.Match(input); var match = SteamTokenRegex.Match(input);
if (match.Success == false) if (!match.Success)
{ {
if (trying) return false; if (trying) return false;
throw new ArgumentException("Provided Token is not valid SteamLoginSecure or SteamAccess token"); throw new ArgumentException("Provided Token is not valid SteamLoginSecure or SteamAccess token");
@@ -64,7 +64,7 @@ public static class SteamTokenHelper
} }
var steamIdStr = jwt.Subject; 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; if (trying) return false;
throw new ArgumentException( throw new ArgumentException(
@@ -101,7 +101,7 @@ public static class SteamTokenHelper
if (jwt.Audiences.ToList().Count > 0) if (jwt.Audiences.ToList().Count > 0)
{ {
var aud = audiences[0]; var aud = audiences[0];
if (AudDomains.TryGetValue(aud, out domain) == false && aud != "web") if (!AudDomains.TryGetValue(aud, out domain) && aud != "web")
{ {
if (trying) if (trying)
{ {
@@ -180,13 +180,13 @@ public static class SteamTokenHelper
internal static string CombineLoginValueIfNeeded(long steamId, string loginValue) 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) public static string ExtractJwtToken(string steamLoginSecure)
{ {
var match = SteamTokenRegex.Match(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; return match.Groups["jwt"].Value;
} }
} }
@@ -11,7 +11,7 @@ public static class ProtoHttpClientExtension
this HttpClient client, Uri uri, HttpMethod method, string? protoMsg, CancellationToken cancellationToken) this HttpClient client, Uri uri, HttpMethod method, string? protoMsg, CancellationToken cancellationToken)
where TProtoResponse : class, IProtoMsg 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<TProtoResponse>.FromHttpResponseAsync(resp, cancellationToken); return await ProtoResponse<TProtoResponse>.FromHttpResponseAsync(resp, cancellationToken);
} }
@@ -19,7 +19,7 @@ public static class ProtoHttpClientExtension
string? protoMsg, string? protoMsg,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var resp = await SendProto(client, uri, method, protoMsg, cancellationToken); var resp = await client.SendProto(uri, method, protoMsg, cancellationToken);
resp.EnsureSuccessStatusCode(); resp.EnsureSuccessStatusCode();
return ProtoHelpers.GetEResult(resp); return ProtoHelpers.GetEResult(resp);
} }
@@ -97,7 +97,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
return SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Post, str, cancellationToken); return client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Post, str, cancellationToken);
} }
public static Task<ProtoResponse<TProtoResponse>> PostProtoMsg<TProtoResponse>(this HttpClient client, string uri, public static Task<ProtoResponse<TProtoResponse>> PostProtoMsg<TProtoResponse>(this HttpClient client, string uri,
@@ -105,7 +105,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
return SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Post, str, cancellationToken); return client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Post, str, cancellationToken);
} }
#endregion #endregion
@@ -117,7 +117,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
var res = await SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Post, str, cancellationToken); var res = await client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Post, str, cancellationToken);
return res.GetResponseEnsureSuccess(); return res.GetResponseEnsureSuccess();
} }
@@ -126,7 +126,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
var res = await SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Post, str, var res = await client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Post, str,
cancellationToken); cancellationToken);
return res.GetResponseEnsureSuccess(); return res.GetResponseEnsureSuccess();
} }
@@ -179,7 +179,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
return SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Get, str, cancellationToken); return client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Get, str, cancellationToken);
} }
public static Task<ProtoResponse<TProtoResponse>> GetProtoMsg<TProtoResponse>(this HttpClient client, string uri, public static Task<ProtoResponse<TProtoResponse>> GetProtoMsg<TProtoResponse>(this HttpClient client, string uri,
@@ -187,7 +187,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
return SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Get, str, cancellationToken); return client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Get, str, cancellationToken);
} }
#endregion #endregion
@@ -199,7 +199,7 @@ public static class ProtoHttpClientExtension
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
var res = await SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Get, str, cancellationToken); var res = await client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Get, str, cancellationToken);
res.Result.EnsureSuccessEResult(); res.Result.EnsureSuccessEResult();
return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null"); 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 CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
{ {
var str = ProtoHelpers.ProtoToString(request); var str = ProtoHelpers.ProtoToString(request);
var res = await SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Get, str, var res = await client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Get, str,
cancellationToken); cancellationToken);
res.Result.EnsureSuccessEResult(); res.Result.EnsureSuccessEResult();
return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null"); return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null");
@@ -74,18 +74,18 @@ public class SteamAuthenticatorLinker
long? phoneNumber = null; long? phoneNumber = null;
if (hasPhone == false && Options.PhoneNumberProvider != null) if (!hasPhone && Options.PhoneNumberProvider != null)
{ {
phoneNumber = await Options.PhoneNumberProvider.GetPhoneNumber(Consumer); phoneNumber = await Options.PhoneNumberProvider.GetPhoneNumber(Consumer);
} }
if (hasPhone == false && phoneNumber != null) if (!hasPhone && phoneNumber != null)
{ {
Logger?.LogInformation("Attaching phone number {phoneNumber}", phoneNumber); Logger?.LogInformation("Attaching phone number {phoneNumber}", phoneNumber);
//if (await this.IsValidPhoneNumber(phoneNumber.Value) == false) //if (await this.IsValidPhoneNumber(phoneNumber.Value) == false)
// throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidPhoneNumber); // throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidPhoneNumber);
if (await this.AttachPhone(phoneNumber.Value) == false) if (!await this.AttachPhone(phoneNumber.Value))
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.CantAttachPhone); throw new AuthenticatorLinkerException(AuthenticatorLinkerError.CantAttachPhone);
@@ -150,7 +150,7 @@ public class SteamAuthenticatorLinker
Logger?.LogInformation("Finalizing link"); Logger?.LogInformation("Finalizing link");
var result = await this.FinalizeLink(code, resp.SharedSecret, isPhone); var result = await this.FinalizeLink(code, resp.SharedSecret, isPhone);
if (result.Success == false) if (!result.Success)
{ {
var error = result.Error switch var error = result.Error switch
{ {
@@ -5,24 +5,21 @@ namespace SteamLib.Utility.MafileSerialization;
internal class LegacyMafile internal class LegacyMafile
{ {
[JsonProperty("shared_secret")] [JsonProperty("shared_secret")]
[JsonRequired] public string SharedSecret { get; set; } = null!;
public string SharedSecret { get; set; } = default!;
[JsonProperty("identity_secret")] [JsonProperty("identity_secret")]
[JsonRequired] public string IdentitySecret { get; set; } = null!;
public string IdentitySecret { get; set; } = default!;
[JsonProperty("device_id")] [JsonProperty("device_id")]
[JsonRequired] public string DeviceId { get; set; } = null!;
public string DeviceId { get; set; } = default!;
[JsonProperty("revocation_code")] public string RevocationCode { get; set; } = default!; [JsonProperty("revocation_code")] public string RevocationCode { get; set; } = null!;
[JsonProperty("account_name")] public string AccountName { get; set; } = default!; [JsonProperty("account_name")] public string AccountName { get; set; } = null!;
[JsonProperty("Session")] public object? SessionData { get; set; } [JsonProperty("Session")] public object? SessionData { get; set; }
[JsonProperty("server_time")] public long ServerTime { get; set; } //Unused [JsonProperty("server_time")] public long ServerTime { get; set; } //Unused
[JsonProperty("steamid")] public long SteamId { get; set; } [JsonProperty("steamid")] public long SteamId { get; set; }
[JsonProperty("serial_number")] public string SerialNumber { get; set; } = default!; //Unused [JsonProperty("serial_number")] public string SerialNumber { get; set; } = null!; //Unused
[JsonProperty("uri")] public string Uri { get; set; } = default!; //Unused [JsonProperty("uri")] public string Uri { get; set; } = null!; //Unused
[JsonProperty("token_gid")] public string TokenGid { get; set; } = default!; //Unused [JsonProperty("token_gid")] public string TokenGid { get; set; } = null!; //Unused
[JsonProperty("secret_1")] public string Secret1 { get; set; } = default!; //Unused [JsonProperty("secret_1")] public string Secret1 { get; set; } = null!; //Unused
} }
@@ -38,7 +38,7 @@ public partial class MafileSerializer //Validate
public static void IsValidBase64(string name, string base64) public static void IsValidBase64(string name, string base64)
{ {
var buffer = new Span<byte>(new byte[base64.Length]); var buffer = new Span<byte>(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"); throw new ArgumentException($"{name} is not valid base64 string");
} }
@@ -39,7 +39,7 @@ public static class MobileConfirmationScrapper
throw new SessionInvalidException(); throw new SessionInvalidException();
} }
if (conf.Success == false) if (!conf.Success)
{ {
var error = LoadConfirmationsError.Unknown; var error = LoadConfirmationsError.Unknown;
if (conf.Message != null && ErrorMessages.TryGetValue(conf.Message, out var e)) if (conf.Message != null && ErrorMessages.TryGetValue(conf.Message, out var e))