mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 06:14:31 +00:00
@@ -1,10 +1,20 @@
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.LegacyConverter;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Utility.MaFiles;
|
||||
using SteamLib.Utility.MafileSerialization;
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var mafileSerializer = new MafileSerializer(new MafileSerializerSettings
|
||||
{
|
||||
DeserializationOptions =
|
||||
{
|
||||
AllowDeviceIdGeneration = true,
|
||||
AllowSessionIdGeneration = true,
|
||||
}
|
||||
});
|
||||
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
if (currentPath != null)
|
||||
Environment.CurrentDirectory = currentPath;
|
||||
@@ -120,7 +130,8 @@ try
|
||||
}
|
||||
}
|
||||
|
||||
var maf = MafileSerializer.Deserialize(text, true, out _);
|
||||
var data = mafileSerializer.Deserialize(text);
|
||||
var maf = data.Data;
|
||||
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
|
||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
|
||||
Write(legacy, fileNameWithoutExtension);
|
||||
|
||||
@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
|
||||
changelog\1.5.1.html = changelog\1.5.1.html
|
||||
changelog\1.5.2.html = changelog\1.5.2.html
|
||||
changelog\1.5.3.html = changelog\1.5.3.html
|
||||
changelog\1.5.4.html = changelog\1.5.4.html
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem InputGestureText="Ctrl+C" Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}" Command="{Binding CopySteamIdCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem InputGestureText="Ctrl+X" Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}" Command="{Binding CopyMafileCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
|
||||
@@ -45,6 +45,7 @@ public partial class Mafile : MobileDataExtended
|
||||
ServerTime = data.ServerTime,
|
||||
TokenGid = data.TokenGid,
|
||||
Uri = data.Uri,
|
||||
SteamId = data.SteamId
|
||||
};
|
||||
}
|
||||
public static Mafile FromMobileDataExtended(MobileDataExtended data, MaProxy? proxy, string? group, string? password)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib;
|
||||
|
||||
namespace NebulaAuth.Model.Exceptions;
|
||||
|
||||
public class MafileNeedReloginException : Exception
|
||||
{
|
||||
public Mafile Mafile { get; }
|
||||
|
||||
public MafileNeedReloginException(Mafile mafile)
|
||||
{
|
||||
Mafile = mafile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using NebulaAuth.Model.Entities;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib;
|
||||
using SteamLib.Utility.MafileSerialization;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public static class NebulaSerializer
|
||||
{
|
||||
public static MafileSerializer Serializer { get; }
|
||||
|
||||
static NebulaSerializer()
|
||||
{
|
||||
Serializer = new MafileSerializer(new MafileSerializerSettings
|
||||
{
|
||||
DeserializationOptions =
|
||||
{
|
||||
AllowDeviceIdGeneration = true,
|
||||
AllowSessionIdGeneration = true,
|
||||
ThrowIfInvalidSteamId = false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static Mafile Deserialize(string cont)
|
||||
{
|
||||
var data = Serializer.Deserialize(cont);
|
||||
var mobileData = data.Data;
|
||||
var info = data.Info;
|
||||
if (info.IsExtended == false)
|
||||
throw new FormatException("Mafile is not extended data");
|
||||
|
||||
|
||||
var props = info.UnusedProperties ?? new Dictionary<string, JProperty>();
|
||||
var proxy = GetPropertyValue<MaProxy>("Proxy", props);
|
||||
var group = GetPropertyValue<string>("Group", props);
|
||||
var password = GetPropertyValue<string>("Password", props);
|
||||
var mafile = Mafile.FromMobileDataExtended((MobileDataExtended)mobileData, proxy, group, password);
|
||||
|
||||
|
||||
if (!info.SteamIdValid)
|
||||
throw new MafileNeedReloginException(mafile);
|
||||
|
||||
return mafile;
|
||||
}
|
||||
|
||||
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out var prop) == false) return default;
|
||||
var value = prop.Value;
|
||||
try
|
||||
{
|
||||
return value.ToObject<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Can't deserialize property {name}", name);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerializeMafile(Mafile data)
|
||||
{
|
||||
var props = new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(Mafile.Proxy), data.Proxy},
|
||||
{nameof(Mafile.Group), data.Group},
|
||||
{nameof(Mafile.Password), data.Password}
|
||||
};
|
||||
return SerializeMafile(data, props);
|
||||
}
|
||||
|
||||
|
||||
public static string SerializeMafile(MobileDataExtended data, Dictionary<string, object?>? properties)
|
||||
{
|
||||
if (Settings.Instance.LegacyMode)
|
||||
{
|
||||
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MafileSerializer.Serialize(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-99
@@ -1,19 +1,20 @@
|
||||
using NebulaAuth.Model.Entities;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using SteamLib.Utility.MaFiles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using NLog.Targets;
|
||||
using SteamLib;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
//RETHINK
|
||||
public static class Storage
|
||||
{
|
||||
public const string MAFILE_F = "maFiles";
|
||||
@@ -42,21 +43,20 @@ public static class Storage
|
||||
var hashIds = new HashSet<SteamId>();
|
||||
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
|
||||
var ordered = files.Order(comparer).ToList();
|
||||
foreach (var file in ordered)
|
||||
foreach (var file in ordered.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile")))
|
||||
{
|
||||
if (Path.GetExtension(file).Equals(".mafile", StringComparison.OrdinalIgnoreCase) == false) continue;
|
||||
try
|
||||
{
|
||||
var data = ReadMafile(file);
|
||||
|
||||
if (hashNames.Contains(data.AccountName) || (data.SessionData != null && hashIds.Contains(data.SessionData.SteamId)))
|
||||
if (hashNames.Contains(data.AccountName) || hashIds.Contains(data.SteamId))
|
||||
{
|
||||
DuplicateFound++;
|
||||
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
|
||||
continue;
|
||||
}
|
||||
hashNames.Add(data.AccountName);
|
||||
if (data.SessionData != null) hashIds.Add(data.SessionData.SteamId);
|
||||
if (data.SessionData != null) hashIds.Add(data.SteamId);
|
||||
MaFiles.Add(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -68,7 +68,14 @@ public static class Storage
|
||||
MaFiles = new ObservableCollection<Mafile>(MaFiles.OrderBy(m => m.AccountName));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="overwrite"></param>
|
||||
/// <exception cref="FormatException"></exception>
|
||||
/// <exception cref="SessionInvalidException"></exception>
|
||||
/// <exception cref="IOException"></exception>
|
||||
public static void AddNewMafile(string path, bool overwrite)
|
||||
{
|
||||
Mafile data;
|
||||
@@ -77,11 +84,11 @@ public static class Storage
|
||||
data = ReadMafile(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when(ex is not MafileNeedReloginException)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Can't load mafile");
|
||||
throw new FormatException("File data is not valid", ex);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data.AccountName))
|
||||
throw new FormatException("File data is not valid. Missing AccountName");
|
||||
|
||||
@@ -94,14 +101,6 @@ public static class Storage
|
||||
throw new FormatException("Can't generate code on this mafile", ex);
|
||||
}
|
||||
|
||||
|
||||
if (data.SessionData == null)
|
||||
throw new SessionInvalidException("File data is not valid. Missing SessionData")
|
||||
{
|
||||
Data = { { "mafile", data } }
|
||||
};
|
||||
|
||||
|
||||
if (overwrite == false && File.Exists(CreatePathForMafile(data)))
|
||||
{
|
||||
throw new IOException("File already exist and overwrite is False");
|
||||
@@ -115,61 +114,13 @@ public static class Storage
|
||||
public static Mafile ReadMafile(string path)
|
||||
{
|
||||
var str = File.ReadAllText(path);
|
||||
var mafile = MafileSerializer.Deserialize(str, true, out var mafileData);
|
||||
if (mafileData.IsExtended == false)
|
||||
throw new FormatException("Mafile is not extended data");
|
||||
|
||||
|
||||
var props = mafileData.UnusedProperties ?? new Dictionary<string, JProperty>();
|
||||
var proxy = GetPropertyValue<MaProxy>("Proxy", props);
|
||||
var group = GetPropertyValue<string>("Group", props);
|
||||
var password = GetPropertyValue<string>("Password", props);
|
||||
return Mafile.FromMobileDataExtended((MobileDataExtended)mafile, proxy, group, password);
|
||||
}
|
||||
|
||||
public static string SerializeMafile(Mafile data)
|
||||
{
|
||||
var props = new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(Mafile.Proxy), data.Proxy},
|
||||
{nameof(Mafile.Group), data.Group},
|
||||
{nameof(Mafile.Password), data.Password}
|
||||
};
|
||||
return SerializeMafile(data, props);
|
||||
}
|
||||
|
||||
public static string SerializeMafile(MobileDataExtended data, Dictionary<string, object?>? properties)
|
||||
{
|
||||
if (Settings.Instance.LegacyMode)
|
||||
{
|
||||
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MafileSerializer.Serialize(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out var prop) == false) return default;
|
||||
var value = prop.Value;
|
||||
try
|
||||
{
|
||||
return value.ToObject<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Can't deserialize property {name}", name);
|
||||
return default;
|
||||
}
|
||||
return NebulaSerializer.Deserialize(str);
|
||||
}
|
||||
|
||||
public static void SaveMafile(Mafile data)
|
||||
{
|
||||
var path = CreatePathForMafile(data);
|
||||
var str = SerializeMafile(data);
|
||||
var str = NebulaSerializer.SerializeMafile(data);
|
||||
File.WriteAllText(Path.GetFullPath(path), str);
|
||||
|
||||
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
|
||||
@@ -187,23 +138,14 @@ public static class Storage
|
||||
public static void UpdateMafile(Mafile data)
|
||||
{
|
||||
var path = CreatePathForMafile(data);
|
||||
var str = SerializeMafile(data);
|
||||
var str = NebulaSerializer.SerializeMafile(data);
|
||||
File.WriteAllText(Path.GetFullPath(path), str);
|
||||
}
|
||||
|
||||
public static void RemoveMafile(Mafile data)
|
||||
{
|
||||
var path = CreatePathForMafile(data);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
MaFiles.Remove(data);
|
||||
}
|
||||
public static void MoveToRemoved(Mafile data)
|
||||
{
|
||||
var path = CreatePathForMafile(data);
|
||||
var copyPath = Path.Combine(REMOVED_F, data.SessionData!.SteamId + ".mafile");
|
||||
var copyPath = Path.Combine(REMOVED_F, data.SteamId + ".mafile");
|
||||
var copyPathCompleted = copyPath;
|
||||
var i = 0;
|
||||
while (File.Exists(copyPathCompleted))
|
||||
@@ -218,18 +160,9 @@ public static class Storage
|
||||
|
||||
private static string CreatePathForMafile(Mafile data)
|
||||
{
|
||||
string fileName;
|
||||
if (Settings.Instance.UseAccountNameAsMafileName)
|
||||
{
|
||||
fileName = CreateFileNameWithAccountName(data.AccountName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(data.SessionData == null)
|
||||
throw new NullReferenceException("SessionData was null can't retrieve SteamId"); //FIXME: think about better way to handle
|
||||
|
||||
fileName = CreateFileNameWithSteamId(data.SessionData.SteamId);
|
||||
}
|
||||
var fileName = Settings.Instance.UseAccountNameAsMafileName
|
||||
? CreateFileNameWithAccountName(data.AccountName)
|
||||
: CreateFileNameWithSteamId(data.SteamId);
|
||||
|
||||
return Path.Combine(MafileFolder, fileName);
|
||||
}
|
||||
@@ -238,13 +171,12 @@ public static class Storage
|
||||
private static string CreateFileNameWithSteamId(SteamId steamId) => steamId.Steam64.Id + ".mafile";
|
||||
|
||||
public static string? TryFindMafilePath(Mafile data)
|
||||
//FIXME: write mode to mafile instead of searching it. Search sometimes represents not actual mafile (in case of duplicate steamId + accountName)
|
||||
{
|
||||
var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
|
||||
string? pathSteamId = null;
|
||||
if (data.SessionData != null)
|
||||
{
|
||||
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SessionData.SteamId));
|
||||
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SteamId));
|
||||
}
|
||||
|
||||
var steamIdExist = pathSteamId != null && File.Exists(pathSteamId);
|
||||
@@ -260,13 +192,9 @@ public static class Storage
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool ValidateCanSave(Mafile data)
|
||||
{
|
||||
return Settings.Instance.UseAccountNameAsMafileName || data.SessionData != null;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Refactor
|
||||
internal class MafileNameComparer : IComparer<string>
|
||||
{
|
||||
public bool MafileNameMode { get; }
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
|
||||
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||
<AssemblyVersion>1.5.3</AssemblyVersion>
|
||||
<AssemblyVersion>1.5.4</AssemblyVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -42,11 +42,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Model\Exceptions\" />
|
||||
<Folder Include="Utility\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
• Есть люди которым нужна функция просмотра и завершения сессий аккаунта. Скорее всего в следующей версии это будет реализовано
|
||||
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
|
||||
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
|
||||
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
|
||||
• Ускорить появление подсказок в интерфейсе
|
||||
• Добавить запоминание пароля при привязке мафайла
|
||||
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
|
||||
• Добавить полное шифрование мафайлов по аналогии с SDA
|
||||
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
|
||||
@@ -0,0 +1,60 @@
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
|
||||
namespace NebulaAuth.Utility;
|
||||
|
||||
public class ClipboardHelper
|
||||
{
|
||||
public static bool Set(string text)
|
||||
{
|
||||
var i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SetFiles(StringCollection files)
|
||||
{
|
||||
var i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetFileDropList(files);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,13 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal" Text="{Tr SetEncryptedPasswordDialog.DialogText}" theme:FontScaleWindow.Scale="0.8" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left"/>
|
||||
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
|
||||
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr SetEncryptedPasswordDialog.Ok}"/>
|
||||
<Button IsDefault="True" Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr SetEncryptedPasswordDialog.Ok}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Margin="5,5,10,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr SetEncryptedPasswordDialog.Cancel}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using SteamLib.Exceptions;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
@@ -99,11 +100,11 @@ public partial class MainVM //File //TODO: Refactor
|
||||
notAdded++;
|
||||
}
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
catch (MafileNeedReloginException ex)
|
||||
{
|
||||
if (path.Length == 1 && ex.Data.Contains("mafile"))
|
||||
if (path.Length == 1 && ex.Mafile != null)
|
||||
{
|
||||
var mafile = (Mafile)ex.Data["mafile"]!;
|
||||
var mafile = ex.Mafile;
|
||||
if (await HandleAddMafileWithoutSession(mafile))
|
||||
{
|
||||
added++;
|
||||
@@ -243,58 +244,25 @@ public partial class MainVM //File //TODO: Refactor
|
||||
private void CopyLogin(object? mafile)
|
||||
{
|
||||
if(mafile is not Mafile maf) return;
|
||||
var i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(maf.AccountName);
|
||||
if(ClipboardHelper.Set(maf.AccountName))
|
||||
SnackbarController.SendSnackbar(GetLocalization("LoginCopied"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
[RelayCommand]
|
||||
private void CopySteamId(object? mafile)
|
||||
{
|
||||
if (mafile is not Mafile maf) return;
|
||||
|
||||
if(ClipboardHelper.Set(maf.SteamId.ToString()))
|
||||
SnackbarController.SendSnackbar(GetLocalization("SteamIdCopied"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyMafile(object? mafile)
|
||||
{
|
||||
if (mafile is not Mafile maf) return;
|
||||
var i = 0;
|
||||
var path = Storage.TryFindMafilePath(maf);
|
||||
if (path == null)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalization("MafileNotCopied"));
|
||||
return;
|
||||
}
|
||||
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetFileDropList([path]);
|
||||
if(ClipboardHelper.SetFiles([path]))
|
||||
SnackbarController.SendSnackbar(GetLocalization("MafileCopied"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using AchiesUtilities.Extensions;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -47,7 +47,6 @@ public partial class MainVM //Groups
|
||||
var mafile = SelectedMafile;
|
||||
if (mafile == null) return;
|
||||
if (string.IsNullOrEmpty(value)) return;
|
||||
if (!ValidateCanSaveAndWarn(mafile)) return;
|
||||
|
||||
mafile.Group = value;
|
||||
Storage.UpdateMafile(mafile);
|
||||
@@ -67,7 +66,6 @@ public partial class MainVM //Groups
|
||||
var mafile = (Mafile?)value[1];
|
||||
|
||||
if (group == null || mafile == null) return;
|
||||
if (!ValidateCanSaveAndWarn(mafile)) return;
|
||||
mafile.Group = group;
|
||||
Storage.UpdateMafile(mafile);
|
||||
QueryGroups();
|
||||
@@ -79,7 +77,6 @@ public partial class MainVM //Groups
|
||||
private void RemoveGroup(Mafile? mafile)
|
||||
{
|
||||
if (mafile?.Group == null) return;
|
||||
if (!ValidateCanSaveAndWarn(mafile)) return;
|
||||
var mafGroup = mafile.Group;
|
||||
mafile.Group = null;
|
||||
Storage.UpdateMafile(mafile);
|
||||
@@ -136,11 +133,7 @@ public partial class MainVM //Groups
|
||||
|
||||
bool SearchPredicate(Mafile mafile)
|
||||
{
|
||||
if (!mafile.AccountName.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return mafile.SessionData != null && mafile.SessionData.SteamId.Steam64.Id.Equals(searchSteamId);
|
||||
}
|
||||
return true;
|
||||
return mafile.AccountName.ContainsIgnoreCase(SearchText) || mafile.SteamId.Steam64.Id.Equals(searchSteamId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
@@ -101,7 +102,6 @@ public partial class MainVM
|
||||
{
|
||||
if (SelectedProxy == null) return;
|
||||
if (SelectedMafile == null) return;
|
||||
if (!ValidateCanSaveAndWarn(SelectedMafile)) return;
|
||||
SelectedMafile.Proxy = null;
|
||||
SelectedProxy = null;
|
||||
Storage.UpdateMafile(SelectedMafile);
|
||||
@@ -117,19 +117,9 @@ public partial class MainVM
|
||||
}
|
||||
|
||||
if (SelectedMafile == null) return;
|
||||
if (!ValidateCanSaveAndWarn(SelectedMafile)) return;
|
||||
ProxyExist = true;
|
||||
SelectedMafile.Proxy = SelectedProxy;
|
||||
Storage.UpdateMafile(SelectedMafile);
|
||||
}
|
||||
|
||||
private bool ValidateCanSaveAndWarn(Mafile data)
|
||||
{
|
||||
var canSave = Storage.ValidateCanSave(data);
|
||||
if (!canSave)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalization("CantRetrieveSteamIDToUpdate"));
|
||||
}
|
||||
return canSave;
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,8 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
|
||||
private bool _isLinkStarted;
|
||||
private string _rCode = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProceedCommand))]
|
||||
private bool _canProceed = true;
|
||||
@@ -136,6 +138,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
IsLogin = true;
|
||||
var userName = FieldText;
|
||||
var pass = PassFieldText;
|
||||
_password = pass;
|
||||
FieldText = string.Empty;
|
||||
IsFieldVisible = false;
|
||||
HintText = string.Empty;
|
||||
@@ -192,6 +195,18 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
IsLinkCode = true;
|
||||
IsCompleted = true;
|
||||
var mafile = Mafile.FromMobileDataExtended(result);
|
||||
try
|
||||
{
|
||||
if (SelectedProxy.HasValue)
|
||||
mafile.Proxy = new MaProxy(SelectedProxy.Value.Key, SelectedProxy.Value.Value);
|
||||
if (Settings.Instance.IsPasswordSet)
|
||||
mafile.Password = PHandler.Encrypt(_password);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error during saving Nebula data to mafile");
|
||||
}
|
||||
|
||||
Storage.SaveMafile(mafile);
|
||||
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
|
||||
HintText =
|
||||
@@ -320,6 +335,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
CanProceed = true;
|
||||
_emailCodeTcs = new TaskCompletionSource<string>();
|
||||
_rCode = string.Empty;
|
||||
_password = string.Empty;
|
||||
}
|
||||
|
||||
private void Backup(MobileDataExtended data)
|
||||
@@ -328,8 +344,8 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
{
|
||||
Directory.CreateDirectory("mafiles_backup");
|
||||
}
|
||||
var json = Storage.SerializeMafile(data, null);
|
||||
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"), json);
|
||||
var json = NebulaSerializer.SerializeMafile(data, null);
|
||||
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"), json); //TODO: Move logic to Storage
|
||||
}
|
||||
|
||||
#region Providers
|
||||
|
||||
@@ -269,6 +269,11 @@
|
||||
"ru": "Скопировать мафайл",
|
||||
"ua": "Скопіювати мафайл"
|
||||
},
|
||||
"CopySteamId": {
|
||||
"en": "Copy SteamID",
|
||||
"ru": "Скопировать SteamID",
|
||||
"ua": "Скопіювати SteamID"
|
||||
},
|
||||
"AddToGroup": {
|
||||
"en": "Add to group",
|
||||
"ru": "Добавить в группу",
|
||||
@@ -620,9 +625,9 @@
|
||||
"ua": "помилки:"
|
||||
},
|
||||
"RemoveMafileConfirmation": {
|
||||
"ru": "Удалить мафайл? Это действие переместит мафайл в папку 'mafiles_removed' (Это действие не удаляет Steam Guard с аккаунта)",
|
||||
"en": "Remove mafile? This action will move mafile to 'mafiles_removed' (This action does not remove the Steam Guard from the account)",
|
||||
"ua": "Видалити мафайл? Ця дія перемістить мафайл у каталог 'mafiles_removed' (Ця дія не видаляє автентифікатор з облікового запису)"
|
||||
"ru": "Удалить мафайл? Мафайл будет перемещен в папку 'mafiles_removed' (Это действие не удаляет Steam Guard с аккаунта)",
|
||||
"en": "Remove mafile? Mafile will be moved to 'mafiles_removed' directory (This action does not remove the Steam Guard from the account)",
|
||||
"ua": "Видалити мафайл? Мафайл буде переміщено у каталог 'mafiles_removed' (Ця дія не видаляє автентифікатор з облікового запису)"
|
||||
},
|
||||
"CantRemoveAlreadyExist": {
|
||||
"ru": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
|
||||
@@ -659,6 +664,11 @@
|
||||
"ru": "Логин скопирован",
|
||||
"ua": "Логін скопійовано"
|
||||
},
|
||||
"SteamIdCopied": {
|
||||
"en": "SteamID copied",
|
||||
"ru": "SteamID скопирован",
|
||||
"ua": "SteamID скопійовано"
|
||||
},
|
||||
"MafileCopied": {
|
||||
"en": "Mafile copied",
|
||||
"ru": "Мафайл скопирован",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>1.5.3.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.3/NebulaAuth.1.5.3.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.3.html</changelog>
|
||||
<version>1.5.4.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.4/NebulaAuth.1.5.4.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.4.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
</item>
|
||||
@@ -2,6 +2,7 @@
|
||||
using SteamLib.Core.Interfaces;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Account;
|
||||
|
||||
@@ -35,8 +36,8 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData
|
||||
}
|
||||
|
||||
public MobileSessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken,
|
||||
SteamAuthToken? mobileToken, IEnumerable<SteamAuthToken>? tokens)
|
||||
: base(sessionId, steamId, refreshToken, tokens)
|
||||
SteamAuthToken? mobileToken, IEnumerable<SteamAuthToken>? tokensCollection)
|
||||
: base(sessionId, steamId, refreshToken, tokensCollection)
|
||||
{
|
||||
MobileToken = mobileToken;
|
||||
}
|
||||
@@ -46,6 +47,4 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData
|
||||
{
|
||||
return new MobileSessionData(SessionId, SteamId, RefreshToken, MobileToken, Tokens);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Core.Enums;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Web.Converters;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
@@ -8,8 +9,9 @@ namespace SteamLib.Account;
|
||||
|
||||
public class SessionData : ISessionData
|
||||
{
|
||||
[JsonIgnore]
|
||||
public bool? IsValid { get; set; }
|
||||
[JsonIgnore] public bool? IsValid { get; set; }
|
||||
[JsonIgnore] public bool IsExpired => RefreshToken.IsExpired;
|
||||
|
||||
public string SessionId { get; }
|
||||
|
||||
[JsonConverter(typeof(SteamIdToSteam64Converter))]
|
||||
@@ -26,13 +28,13 @@ public class SessionData : ISessionData
|
||||
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokens ?? new Dictionary<SteamDomain, SteamAuthToken>());
|
||||
}
|
||||
|
||||
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable<SteamAuthToken>? tokens)
|
||||
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable<SteamAuthToken>? tokensCollection)
|
||||
{
|
||||
SessionId = sessionId;
|
||||
SteamId = steamId;
|
||||
RefreshToken = refreshToken;
|
||||
tokens ??= Array.Empty<SteamAuthToken>();
|
||||
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokens.ToDictionary(t => t.Domain, t => t));
|
||||
tokensCollection ??= Array.Empty<SteamAuthToken>();
|
||||
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokensCollection.ToDictionary(t => t.Domain, t => t));
|
||||
}
|
||||
|
||||
public virtual SteamAuthToken? GetToken(SteamDomain domain)
|
||||
|
||||
@@ -3,6 +3,7 @@ using AchiesUtilities.Newtonsoft.JSON.Converters.Special;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Core.Enums;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Web.Converters;
|
||||
|
||||
namespace SteamLib.Account;
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
using SteamLib.Utility;
|
||||
|
||||
namespace SteamLib.Account;
|
||||
|
||||
public struct SteamId //TODO: validation in parse methods
|
||||
{
|
||||
public SteamId64 Steam64 { get; }
|
||||
public SteamId2 Steam2 { get; }
|
||||
public SteamId3 Steam3 { get; }
|
||||
public SteamId(SteamId64 steam64, char type = 'U', short universe = 0)
|
||||
{
|
||||
Steam64 = steam64;
|
||||
Steam2 = steam64.ToSteam2(universe);
|
||||
Steam3 = steam64.ToSteam3(type);
|
||||
}
|
||||
|
||||
public SteamId(SteamId2 steam2, char type = 'U')
|
||||
{
|
||||
Steam2 = steam2;
|
||||
Steam64 = steam2.ToSteam64();
|
||||
Steam3 = steam2.ToSteam3(type);
|
||||
}
|
||||
|
||||
public SteamId(SteamId3 steam3, byte universe = 0)
|
||||
{
|
||||
Steam3 = steam3;
|
||||
Steam64 = steam3.ToSteam64();
|
||||
Steam2 = steam3.ToSteam2(universe);
|
||||
}
|
||||
|
||||
public static SteamId FromSteam64(long steam64, char type = 'U', short universe = 0) => new(new SteamId64(steam64), type, universe);
|
||||
public override string ToString() => Steam64.ToString();
|
||||
public static bool TryParse(string input, out SteamId result) => SteamIdParser.TryParse(input, out result);
|
||||
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId Parse(string input) => SteamIdParser.Parse(input);
|
||||
public static bool operator ==(SteamId left, SteamId right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId left, SteamId right) => !left.Equals(right);
|
||||
public bool Equals(SteamId other) => Steam64.Equals(other.Steam64);
|
||||
public override bool Equals(object? obj) => obj is SteamId other && Equals(other);
|
||||
public override int GetHashCode() => Steam64.GetHashCode();
|
||||
}
|
||||
|
||||
public struct SteamId64
|
||||
{
|
||||
public const long SEED = 76561197960265728L;
|
||||
public long Id { get; }
|
||||
public SteamId64(long id)
|
||||
{
|
||||
if (id < SEED)
|
||||
throw new ArgumentOutOfRangeException(nameof(id),$"Invalid SteamID provided {id}");
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public SteamId2 ToSteam2(short universe = 0)
|
||||
{
|
||||
var accountIdLowBit = (byte)(Id & 1);
|
||||
var accountIdHighBits = (int)(Id >> 1) & 0x7FFFFFF;
|
||||
return new SteamId2((byte)universe, accountIdLowBit, accountIdHighBits);
|
||||
}
|
||||
|
||||
public SteamId3 ToSteam3(char type = 'U')
|
||||
{
|
||||
var accountIdLowBit = (byte)(Id & 1);
|
||||
var accountIdHighBits = (int)(Id >> 1) & 0x7FFFFFF;
|
||||
return new SteamId3(accountIdLowBit + accountIdHighBits * 2, type);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Id.ToString();
|
||||
}
|
||||
|
||||
public ulong ToUlong() => (ulong)Id;
|
||||
public long ToLong() => Id;
|
||||
|
||||
public bool Equals(SteamId64 other) => Id == other.Id;
|
||||
public override bool Equals(object? obj) => obj is SteamId64 other && Equals(other);
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
public static bool operator ==(SteamId64 left, SteamId64 right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId64 left, SteamId64 right) => !left.Equals(right);
|
||||
public static bool TryParse(string input, out SteamId64 result) => SteamIdParser.TryParse64(input, out result);
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId64 Parse(string input) => SteamIdParser.Parse64(input);
|
||||
|
||||
public static implicit operator long(SteamId64 steamId64) => steamId64.ToLong();
|
||||
}
|
||||
|
||||
public struct SteamId2
|
||||
{
|
||||
|
||||
public byte Universe { get; }
|
||||
public byte LowestBit { get; }
|
||||
public int HighestBit { get; }
|
||||
|
||||
|
||||
public SteamId2(byte lowestBit, int highestBit)
|
||||
{
|
||||
if(lowestBit > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(lowestBit), $"Invalid SteamID2 lowestBit provided {lowestBit}. Max value is 1");
|
||||
|
||||
if(highestBit < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(highestBit), $"Invalid SteamID2 highestBit provided {highestBit}");
|
||||
|
||||
LowestBit = lowestBit;
|
||||
HighestBit = highestBit;
|
||||
Universe = 0;
|
||||
}
|
||||
|
||||
public SteamId2(byte universe, byte lowestBit, int highestBit)
|
||||
{
|
||||
Universe = universe;
|
||||
LowestBit = lowestBit;
|
||||
HighestBit = highestBit;
|
||||
}
|
||||
|
||||
public SteamId64 ToSteam64() => new SteamId64(SteamId64.SEED + LowestBit + HighestBit * 2);
|
||||
|
||||
public SteamId3 ToSteam3(char type = 'U') => new SteamId3(HighestBit * 2 + LowestBit);
|
||||
|
||||
public override string ToString() => $"STEAM_{Universe}:{LowestBit}:{HighestBit}";
|
||||
|
||||
public bool Equals(SteamId2 other) => Universe == other.Universe && LowestBit == other.LowestBit && HighestBit == other.HighestBit;
|
||||
public override bool Equals(object? obj) => obj is SteamId2 other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(Universe, LowestBit, HighestBit);
|
||||
public static bool operator ==(SteamId2 left, SteamId2 right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId2 left, SteamId2 right) => !left.Equals(right);
|
||||
public static bool TryParse(string input, out SteamId2 result) => SteamIdParser.TryParse2(input, out result);
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId2 Parse(string input) => SteamIdParser.Parse2(input);
|
||||
}
|
||||
|
||||
public struct SteamId3
|
||||
{
|
||||
public char Type { get; }
|
||||
public int Id { get; }
|
||||
|
||||
public SteamId3(int id, char type = 'U')
|
||||
{
|
||||
if (id < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Invalid SteamID provided {id}");
|
||||
Type = type;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public SteamId2 ToSteam2(byte universe = 0)
|
||||
{
|
||||
var bit = Id % 2;
|
||||
var highestBits = Id / 2;
|
||||
return new SteamId2(universe, (byte)bit, highestBits);
|
||||
}
|
||||
|
||||
|
||||
public SteamId64 ToSteam64() => new SteamId64(SteamId64.SEED + Id);
|
||||
public override string ToString() => $"[{Type}:1:{Id}]";
|
||||
|
||||
public string ToString(bool withBrackets)
|
||||
{
|
||||
if (withBrackets)
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{Type}:1:{Id}";
|
||||
}
|
||||
}
|
||||
|
||||
public int ToInt() => Id;
|
||||
public bool Equals(SteamId3 other) => Type == other.Type && Id == other.Id;
|
||||
public override bool Equals(object? obj) => obj is SteamId3 other && Equals(other);
|
||||
public readonly override int GetHashCode() => HashCode.Combine(Type, Id);
|
||||
public static bool operator ==(SteamId3 left, SteamId3 right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId3 left, SteamId3 right) => !left.Equals(right);
|
||||
public static bool TryParse(string input, out SteamId3 result) => SteamIdParser.TryParse3(input, out result);
|
||||
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId3 Parse(string input) => SteamIdParser.Parse3(input);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using SteamLib.ProtoCore.Enums;
|
||||
using SteamLib.ProtoCore.Exceptions;
|
||||
using SteamLib.ProtoCore.Services;
|
||||
using System.Net;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Api.Mobile;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using AchiesUtilities.Web.Extensions;
|
||||
using JetBrains.Annotations;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Core.StatusCodes;
|
||||
using SteamLib.Exceptions;
|
||||
@@ -9,6 +8,7 @@ using SteamLib.SteamMobile.Confirmations;
|
||||
using SteamLib.Utility;
|
||||
using SteamLib.Web.Scrappers.JSON;
|
||||
using System.Net;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Api.Mobile;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using SteamLib.Account;
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Core.Enums;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Core.StatusCodes;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.Login.Default;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Enums;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Exceptions;
|
||||
|
||||
namespace SteamLib.Authentication;
|
||||
|
||||
@@ -5,6 +5,7 @@ using SteamLib.Core.Enums;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.RegularExpressions;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Authentication;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Enums;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Core.Interfaces;
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using SteamLib.Utility;
|
||||
|
||||
namespace SteamLib.Core.Models;
|
||||
|
||||
public readonly struct SteamId : IEquatable<SteamId> //TODO: validation in parse methods (in siblings also)
|
||||
{
|
||||
public SteamId64 Steam64 { get; }
|
||||
public SteamId2 Steam2 { get; }
|
||||
public SteamId3 Steam3 { get; }
|
||||
public SteamId(SteamId64 steam64, char type = 'U', short universe = 0)
|
||||
{
|
||||
Steam64 = steam64;
|
||||
Steam2 = steam64.ToSteam2(universe);
|
||||
Steam3 = steam64.ToSteam3(type);
|
||||
}
|
||||
|
||||
public SteamId(SteamId2 steam2, char type = 'U')
|
||||
{
|
||||
Steam2 = steam2;
|
||||
Steam64 = steam2.ToSteam64();
|
||||
Steam3 = steam2.ToSteam3(type);
|
||||
}
|
||||
|
||||
public SteamId(SteamId3 steam3, byte universe = 0)
|
||||
{
|
||||
Steam3 = steam3;
|
||||
Steam64 = steam3.ToSteam64();
|
||||
Steam2 = steam3.ToSteam2(universe);
|
||||
}
|
||||
|
||||
public static SteamId FromSteam64(long steam64, char type = 'U', short universe = 0) => new(new SteamId64(steam64), type, universe);
|
||||
public override string ToString() => Steam64.ToString();
|
||||
public static bool TryParse(string input, out SteamId result) => SteamIdParser.TryParse(input, out result);
|
||||
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId Parse(string input) => SteamIdParser.Parse(input);
|
||||
public static bool operator ==(SteamId left, SteamId right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId left, SteamId right) => !left.Equals(right);
|
||||
public bool Equals(SteamId other) => Steam64.Equals(other.Steam64);
|
||||
public override bool Equals(object? obj) => obj is SteamId other && Equals(other);
|
||||
public override int GetHashCode() => Steam64.GetHashCode();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using SteamLib.Utility;
|
||||
|
||||
namespace SteamLib.Core.Models;
|
||||
|
||||
public readonly struct SteamId2 : IEquatable<SteamId2>
|
||||
{
|
||||
public byte Universe { get; }
|
||||
public byte LowestBit { get; }
|
||||
public int HighestBit { get; }
|
||||
|
||||
|
||||
public SteamId2(byte lowestBit, int highestBit)
|
||||
{
|
||||
if (lowestBit > 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(lowestBit), $"Invalid SteamID2 lowestBit provided {lowestBit}. Max value is 1");
|
||||
|
||||
if (highestBit < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(highestBit), $"Invalid SteamID2 highestBit provided {highestBit}");
|
||||
|
||||
LowestBit = lowestBit;
|
||||
HighestBit = highestBit;
|
||||
Universe = 0;
|
||||
}
|
||||
|
||||
public SteamId2(byte universe, byte lowestBit, int highestBit)
|
||||
{
|
||||
Universe = universe;
|
||||
LowestBit = lowestBit;
|
||||
HighestBit = highestBit;
|
||||
}
|
||||
|
||||
public SteamId64 ToSteam64() => new SteamId64(SteamId64.SEED + LowestBit + HighestBit * 2);
|
||||
|
||||
public SteamId3 ToSteam3(char type = 'U') => new SteamId3(HighestBit * 2 + LowestBit, type);
|
||||
|
||||
public override string ToString() => $"STEAM_{Universe}:{LowestBit}:{HighestBit}";
|
||||
|
||||
public bool Equals(SteamId2 other) => Universe == other.Universe && LowestBit == other.LowestBit && HighestBit == other.HighestBit;
|
||||
public override bool Equals(object? obj) => obj is SteamId2 other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(Universe, LowestBit, HighestBit);
|
||||
public static bool operator ==(SteamId2 left, SteamId2 right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId2 left, SteamId2 right) => !left.Equals(right);
|
||||
public static bool TryParse(string input, out SteamId2 result) => SteamIdParser.TryParse2(input, out result);
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId2 Parse(string input) => SteamIdParser.Parse2(input);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using SteamLib.Utility;
|
||||
|
||||
namespace SteamLib.Core.Models;
|
||||
|
||||
public readonly struct SteamId3 : IEquatable<SteamId3>
|
||||
{
|
||||
public char Type { get; }
|
||||
public int Id { get; }
|
||||
|
||||
public SteamId3(int id, char type = 'U')
|
||||
{
|
||||
if (id < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Invalid SteamID provided {id}");
|
||||
Type = type;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public SteamId2 ToSteam2(byte universe = 0)
|
||||
{
|
||||
var bit = Id % 2;
|
||||
var highestBits = Id / 2;
|
||||
return new SteamId2(universe, (byte)bit, highestBits);
|
||||
}
|
||||
|
||||
|
||||
public SteamId64 ToSteam64() => new SteamId64(SteamId64.SEED + Id);
|
||||
public override string ToString() => $"[{Type}:1:{Id}]";
|
||||
|
||||
public string ToString(bool withBrackets)
|
||||
{
|
||||
if (withBrackets)
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{Type}:1:{Id}";
|
||||
}
|
||||
}
|
||||
|
||||
public int ToInt() => Id;
|
||||
public bool Equals(SteamId3 other) => Type == other.Type && Id == other.Id;
|
||||
public override bool Equals(object? obj) => obj is SteamId3 other && Equals(other);
|
||||
public override int GetHashCode() => HashCode.Combine(Type, Id);
|
||||
public static bool operator ==(SteamId3 left, SteamId3 right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId3 left, SteamId3 right) => !left.Equals(right);
|
||||
public static bool TryParse(string input, out SteamId3 result) => SteamIdParser.TryParse3(input, out result);
|
||||
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId3 Parse(string input) => SteamIdParser.Parse3(input);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using SteamLib.Utility;
|
||||
|
||||
namespace SteamLib.Core.Models;
|
||||
|
||||
public readonly struct SteamId64 : IEquatable<SteamId64>
|
||||
{
|
||||
public const long SEED = 76561197960265728L;
|
||||
public long Id { get; }
|
||||
public SteamId64(long id)
|
||||
{
|
||||
if (id < SEED)
|
||||
throw new ArgumentOutOfRangeException(nameof(id), $"Invalid SteamID provided {id}");
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public SteamId2 ToSteam2(short universe = 0)
|
||||
{
|
||||
var accountIdLowBit = (byte)(Id & 1);
|
||||
var accountIdHighBits = (int)(Id >> 1) & 0x7FFFFFF;
|
||||
return new SteamId2((byte)universe, accountIdLowBit, accountIdHighBits);
|
||||
}
|
||||
|
||||
public SteamId3 ToSteam3(char type = 'U')
|
||||
{
|
||||
var accountIdLowBit = (byte)(Id & 1);
|
||||
var accountIdHighBits = (int)(Id >> 1) & 0x7FFFFFF;
|
||||
return new SteamId3(accountIdLowBit + accountIdHighBits * 2, type);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Id.ToString();
|
||||
}
|
||||
|
||||
public ulong ToUlong() => (ulong)Id;
|
||||
public long ToLong() => Id;
|
||||
|
||||
public bool Equals(SteamId64 other) => Id == other.Id;
|
||||
public override bool Equals(object? obj) => obj is SteamId64 other && Equals(other);
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
public static bool operator ==(SteamId64 left, SteamId64 right) => left.Equals(right);
|
||||
public static bool operator !=(SteamId64 left, SteamId64 right) => !left.Equals(right);
|
||||
public static bool TryParse(string? input, out SteamId64 result) => SteamIdParser.TryParse64(input, out result);
|
||||
public static bool TryParse(long input, out SteamId64 result) => SteamIdParser.TryParse64(input, out result);
|
||||
/// <exception cref="FormatException"></exception>
|
||||
public static SteamId64 Parse(string input) => SteamIdParser.Parse64(input);
|
||||
|
||||
public static implicit operator long(SteamId64 steamId64) => steamId64.ToLong();
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Web.Converters;
|
||||
|
||||
namespace SteamLib;
|
||||
|
||||
@@ -9,24 +11,38 @@ public class MobileData
|
||||
{
|
||||
[JsonRequired] public string SharedSecret { get; set; } = null!;
|
||||
[JsonRequired] public string IdentitySecret { get; set; } = null!;
|
||||
[JsonRequired] public string DeviceId { get; set; } = null!;
|
||||
//TODO: This property used only for tracing purposes in Steam, so if it's not provided, we can generate it manually
|
||||
|
||||
public string DeviceId { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class MobileDataExtended : MobileData
|
||||
{
|
||||
public string? RevocationCode { get; set; } = null!;
|
||||
public string AccountName { get; set; } = null!;
|
||||
public MobileSessionData? SessionData { get; set; }
|
||||
|
||||
public MobileSessionData? SessionData
|
||||
{
|
||||
get => _sessionData;
|
||||
set
|
||||
{
|
||||
_sessionData = value;
|
||||
if (value != null)
|
||||
{
|
||||
SteamId = value.SteamId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MobileSessionData? _sessionData;
|
||||
|
||||
[JsonConverter(typeof(SteamIdToSteam64Converter))]
|
||||
public SteamId SteamId { get; set; }
|
||||
|
||||
|
||||
#region Unused
|
||||
public long ServerTime { get; set; } //Unused
|
||||
public ulong SerialNumber { get; set; } //Unused //greater than long must be ulong or string
|
||||
public ulong SerialNumber { get; set; } //Unused //fixed64 greater than long must be ulong or string
|
||||
public string Uri { get; set; } = null!;//Unused
|
||||
public string TokenGid { get; set; } = null!;//Unused
|
||||
public string Secret1 { get; set; } = null!; //Unused
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class SteamAuthenticatorLinker
|
||||
SessionData = data;
|
||||
|
||||
data.EnsureValidated();
|
||||
if (data.RefreshToken.IsExpired)
|
||||
if (data.IsExpired)
|
||||
{
|
||||
Logger?.LogError("Session expired");
|
||||
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG);
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
|
||||
public enum DeserializedMafileSessionResult
|
||||
{
|
||||
Missing,
|
||||
Invalid,
|
||||
Expired,
|
||||
Valid,
|
||||
}
|
||||
public class DeserializedMafileData
|
||||
{
|
||||
public int? Version { get; init; }
|
||||
public bool IsExtended { get; init; }
|
||||
public DeserializedMafileSessionResult SessionResult { get; init; }
|
||||
public Dictionary<string, JProperty>? UnusedProperties { get; init; }
|
||||
public HashSet<string>? MissingProperties { get; init; } = new();
|
||||
|
||||
|
||||
public bool IsActual => Version == MafileSerializer.MAFILE_VERSION;
|
||||
public bool IsLegacy => Version == 0;
|
||||
public bool HasUnusedProperties => UnusedProperties is { Count: > 0 };
|
||||
public bool HasMissingProperties => MissingProperties is { Count: > 0 };
|
||||
|
||||
internal static DeserializedMafileData Create(int? version = null, bool isExtended = false, Dictionary<string, JProperty>? properties = null, HashSet<string>? missingProperties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Missing)
|
||||
{
|
||||
return new DeserializedMafileData
|
||||
{
|
||||
Version = version,
|
||||
UnusedProperties = properties,
|
||||
IsExtended = isExtended,
|
||||
MissingProperties = missingProperties,
|
||||
SessionResult = sessionResult
|
||||
};
|
||||
}
|
||||
|
||||
internal static DeserializedMafileData CreateActual(Dictionary<string, JProperty>? properties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Valid)
|
||||
{
|
||||
return new DeserializedMafileData
|
||||
{
|
||||
Version = MafileSerializer.MAFILE_VERSION,
|
||||
UnusedProperties = properties,
|
||||
IsExtended = true,
|
||||
SessionResult = sessionResult
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Representation class just for displaying deserialization result
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class DeserializedMafileResult
|
||||
{
|
||||
public static readonly HashSet<string> ImportantProperties = new()
|
||||
{
|
||||
nameof(MobileDataExtended.RevocationCode),
|
||||
nameof(MobileDataExtended.AccountName),
|
||||
};
|
||||
|
||||
public static readonly HashSet<string> NotImportantProperties = new()
|
||||
{
|
||||
nameof(MobileDataExtended.ServerTime),
|
||||
nameof(MobileDataExtended.SerialNumber),
|
||||
nameof(MobileDataExtended.Uri),
|
||||
nameof(MobileDataExtended.TokenGid),
|
||||
nameof(MobileDataExtended.Secret1),
|
||||
};
|
||||
|
||||
public int? Version { get; init; }
|
||||
|
||||
public bool IsExtended { get; init; }
|
||||
public DeserializedMafileSessionResult SessionResult { get; init; }
|
||||
|
||||
public HashSet<string>? MissingImportantProperties { get; init; } = new();
|
||||
public HashSet<string>? MissingProperties { get; init; } = new();
|
||||
public Dictionary<string, string>? UnusedProperties { get; init; }
|
||||
|
||||
public bool IsActual => Version == MafileSerializer.MAFILE_VERSION;
|
||||
public bool HasUnusedProperties => UnusedProperties is { Count: > 0 };
|
||||
public bool HasMissingProperties => MissingProperties is { Count: > 0 };
|
||||
public bool HasMissingImportantProperties => MissingImportantProperties is { Count: > 0 };
|
||||
public bool HasSession => SessionResult == DeserializedMafileSessionResult.Valid;
|
||||
|
||||
|
||||
public static DeserializedMafileResult FromData(DeserializedMafileData data)
|
||||
{
|
||||
HashSet<string>? missingImportantProperties = null;
|
||||
HashSet<string>? missingProperties = null;
|
||||
if (data is { IsExtended: true, HasMissingProperties: true })
|
||||
{
|
||||
var important = data.MissingProperties?.Intersect(ImportantProperties).ToList();
|
||||
if (important?.Count > 0) missingImportantProperties = important.ToHashSet();
|
||||
var notImportant = data.MissingProperties?.Intersect(NotImportantProperties).ToList();
|
||||
if (notImportant?.Count > 0) missingProperties = notImportant.ToHashSet();
|
||||
}
|
||||
|
||||
return new DeserializedMafileResult
|
||||
{
|
||||
Version = data.Version,
|
||||
IsExtended = data.IsExtended,
|
||||
MissingImportantProperties = missingImportantProperties,
|
||||
MissingProperties = missingProperties,
|
||||
UnusedProperties = data.UnusedProperties?.ToDictionary(x => x.Key, x => JTokenToString(x.Value)),
|
||||
SessionResult = data.SessionResult
|
||||
};
|
||||
}
|
||||
|
||||
private static string JTokenToString(JProperty property)
|
||||
{
|
||||
switch (property.Value.Type)
|
||||
{
|
||||
case JTokenType.None:
|
||||
case JTokenType.Null:
|
||||
return "null";
|
||||
case JTokenType.Object:
|
||||
return "object";
|
||||
case JTokenType.Array:
|
||||
return "array";
|
||||
case JTokenType.Integer:
|
||||
return property.Value.Value<long>().ToString();
|
||||
case JTokenType.Float:
|
||||
return property.Value.Value<double>().ToString(CultureInfo.InvariantCulture);
|
||||
case JTokenType.String:
|
||||
return property.Value.Value<string>()!;
|
||||
case JTokenType.Boolean:
|
||||
return property.Value.Value<bool>().ToString();
|
||||
case JTokenType.Date:
|
||||
return property.Value.Value<DateTime>().ToString(CultureInfo.InvariantCulture);
|
||||
case JTokenType.Guid:
|
||||
return property.Value.Value<Guid>().ToString();
|
||||
case JTokenType.Uri:
|
||||
return property.Value.Value<Uri>()!.ToString();
|
||||
case JTokenType.TimeSpan:
|
||||
return property.Value.Value<TimeSpan>().ToString();
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Web.Converters;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
//TODO: Fix pragma warning
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
|
||||
internal class LegacyMafile //TODO: move
|
||||
{
|
||||
|
||||
[JsonProperty("shared_secret")]
|
||||
[JsonRequired]
|
||||
public string SharedSecret { get; set; }
|
||||
|
||||
[JsonProperty("identity_secret")]
|
||||
[JsonRequired]
|
||||
public string IdentitySecret { get; set; }
|
||||
|
||||
[JsonProperty("device_id")]
|
||||
[JsonRequired]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
[JsonProperty("revocation_code")]
|
||||
public string RevocationCode { get; set; }
|
||||
|
||||
[JsonProperty("account_name")]
|
||||
public string AccountName { get; set; }
|
||||
|
||||
[JsonProperty("Session")]
|
||||
public object? SessionData { get; set; }
|
||||
[JsonProperty("server_time")] public long ServerTime { get; set; } //Unused
|
||||
|
||||
[JsonProperty("serial_number")]
|
||||
[JsonConverter(typeof(StringToLongIfNeededConverter))]
|
||||
public string SerialNumber { get; set; } //Unused
|
||||
[JsonProperty("uri")] public string Uri { get; set; } //Unused
|
||||
[JsonProperty("token_gid")] public string TokenGid { get; set; } //Unused
|
||||
[JsonProperty("secret_1")] public string Secret1 { get; set; } //Unused
|
||||
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
using AchiesUtilities.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Core.Enums;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
|
||||
public partial class MafileSerializer //SessionData
|
||||
{
|
||||
private static MobileSessionData? DeserializeMobileSessionData(JObject j, bool allowSessionIdGeneration, out DeserializedMafileSessionResult result)
|
||||
{
|
||||
result = DeserializedMafileSessionResult.Invalid;
|
||||
var jRefreshToken = GetToken(j, nameof(MobileSessionData.RefreshToken), "refreshtoken", "refresh_token",
|
||||
"refresh", "OAuthToken");
|
||||
|
||||
|
||||
SteamAuthToken? refreshToken = null;
|
||||
if (jRefreshToken == null || jRefreshToken.Type == JTokenType.Null) return null;
|
||||
if (jRefreshToken.Type == JTokenType.String && SteamTokenHelper.TryParse(jRefreshToken.Value<string>()!, out var parsed))
|
||||
{
|
||||
refreshToken = parsed;
|
||||
}
|
||||
else if (jRefreshToken.Type == JTokenType.Object)
|
||||
{
|
||||
try
|
||||
{
|
||||
refreshToken = jRefreshToken.ToObject<SteamAuthToken>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
|
||||
//if (refreshToken == null)
|
||||
//{
|
||||
// result = DeserializedMafileSessionResult.Invalid;
|
||||
// return null;
|
||||
//}
|
||||
|
||||
|
||||
var sessionId = GetString(j, "sessionid", "session_id", "session");
|
||||
var jAccessToken = GetToken(j, "accesstoken", "access_token", "access");
|
||||
jAccessToken ??= GetToken(j, "steamLoginSecure");
|
||||
|
||||
if (sessionId == null && allowSessionIdGeneration)
|
||||
{
|
||||
sessionId = GenerateRandomHex(12);
|
||||
}else if (sessionId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
SteamAuthToken? accessToken = null;
|
||||
|
||||
if (jAccessToken == null || jAccessToken.Type == JTokenType.Null)
|
||||
{
|
||||
accessToken = null;
|
||||
}
|
||||
else if (jAccessToken is { Type: JTokenType.Object })
|
||||
{
|
||||
try
|
||||
{
|
||||
accessToken = jRefreshToken.ToObject<SteamAuthToken>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
else if (jAccessToken is { Type: JTokenType.String } &&
|
||||
SteamTokenHelper.TryParse(jAccessToken.Value<string>()!, out var token) && token.Type == SteamAccessTokenType.Mobile)
|
||||
{
|
||||
accessToken = token;
|
||||
}
|
||||
|
||||
|
||||
var steamId = refreshToken?.SteamId ?? GetSessionSteamId(j);
|
||||
if (steamId == null)
|
||||
{
|
||||
result = DeserializedMafileSessionResult.Invalid;
|
||||
return null;
|
||||
}
|
||||
|
||||
refreshToken ??= CreateInvalid(steamId.Value);
|
||||
var sessionData = new MobileSessionData(sessionId, steamId.Value, refreshToken.Value, accessToken, new Dictionary<SteamDomain, SteamAuthToken>());
|
||||
sessionData.IsValid = SessionDataValidator.Validate(null, sessionData).Succeeded;
|
||||
if(sessionData.IsValid == false)
|
||||
return null;
|
||||
|
||||
if (refreshToken.Value.IsExpired || refreshToken.Value.Type != SteamAccessTokenType.MobileRefresh)
|
||||
{
|
||||
result = DeserializedMafileSessionResult.Expired;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = DeserializedMafileSessionResult.Valid;
|
||||
}
|
||||
|
||||
return sessionData;
|
||||
}
|
||||
|
||||
private static SteamId? GetSessionSteamId(JObject j)
|
||||
{
|
||||
var token = GetToken(j, "steamid");
|
||||
if (token == null || token.Type == JTokenType.Null)
|
||||
return null;
|
||||
|
||||
if(token.Type == JTokenType.Integer)
|
||||
return SteamId.FromSteam64(token.Value<long>());
|
||||
|
||||
if (token.Type == JTokenType.String && long.TryParse(token.Value<string>()!, out var steamId))
|
||||
{
|
||||
return SteamId.FromSteam64(steamId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//Workaround to avoid session being invalidated due to missing a valid token.
|
||||
//The reason for this change is the inability to proxy/change group for old mafiles, which creates more problems than benefits.
|
||||
//A temporary solution until I decide how to read the SteamID correctly without invalidating the entire session.
|
||||
//It also makes the LoginAgainOnImport mechanism useless, which is good outcome.
|
||||
//Most likely I need to reconsider the reaction to an “invalid” session and simply feed it to the software as “expired”.
|
||||
//Also, when deciding not to validate RefreshToken, I need to reconsider the entire validation method in the Validator class and think through the consequences in the rest of the code.
|
||||
//FIXME: Refactor code to avoid this workaround and make it more organic.
|
||||
//TODO: after fixing the issue, reflect changes in the original library
|
||||
private static SteamAuthToken CreateInvalid(SteamId steamId)
|
||||
{
|
||||
return new SteamAuthToken("invalid", steamId, UnixTimeStamp.Zero, SteamDomain.Community, SteamAccessTokenType.MobileRefresh);
|
||||
}
|
||||
|
||||
private static string GenerateRandomHex(int byteLength = 12)
|
||||
{
|
||||
byte[] randomBytes = new byte[byteLength];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(randomBytes);
|
||||
}
|
||||
|
||||
var hex = new StringBuilder(byteLength * 2);
|
||||
foreach (var b in randomBytes)
|
||||
hex.Append($"{b:x2}");
|
||||
|
||||
return hex.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public enum DeserializedMafileSessionResult
|
||||
{
|
||||
Missing,
|
||||
Invalid,
|
||||
Valid,
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class DeserializedMafileData
|
||||
{
|
||||
public MobileData Data { get; init; }
|
||||
public DeserializedMafileInfo Info { get; }
|
||||
|
||||
|
||||
private DeserializedMafileData(MobileData mobileData, DeserializedMafileInfo info)
|
||||
{
|
||||
Data = mobileData;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
internal static DeserializedMafileData Create(MobileData mobileData, int? version = null, Dictionary<string, JProperty>? properties = null, HashSet<string>? missingProperties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Missing)
|
||||
{
|
||||
var info = DeserializedMafileInfo.Create(mobileData, version, properties, missingProperties, sessionResult);
|
||||
return new DeserializedMafileData(mobileData, info);
|
||||
}
|
||||
|
||||
internal static DeserializedMafileData CreateActual(MobileData mobileData, Dictionary<string, JProperty>? properties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Valid)
|
||||
{
|
||||
var info = DeserializedMafileInfo.Create(mobileData, MafileSerializer.MAFILE_VERSION, properties, null, sessionResult);
|
||||
return new DeserializedMafileData(mobileData, info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents information about deserialized mafile
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class DeserializedMafileInfo
|
||||
{
|
||||
public static readonly HashSet<string> ImportantProperties =
|
||||
[
|
||||
nameof(MobileDataExtended.RevocationCode),
|
||||
nameof(MobileDataExtended.AccountName),
|
||||
];
|
||||
|
||||
public static readonly HashSet<string> NotImportantProperties =
|
||||
[
|
||||
nameof(MobileDataExtended.ServerTime),
|
||||
nameof(MobileDataExtended.SerialNumber),
|
||||
nameof(MobileDataExtended.Uri),
|
||||
nameof(MobileDataExtended.TokenGid),
|
||||
nameof(MobileDataExtended.Secret1),
|
||||
nameof(MobileDataExtended.SteamId)
|
||||
];
|
||||
|
||||
public int? Version { get; init; }
|
||||
|
||||
public bool IsExtended { get; init; }
|
||||
public DeserializedMafileSessionResult SessionResult { get; init; }
|
||||
|
||||
public HashSet<string>? MissingImportantProperties { get; init; } = [];
|
||||
public HashSet<string>? MissingProperties { get; init; } = [];
|
||||
public Dictionary<string, JProperty>? UnusedProperties { get; init; }
|
||||
|
||||
public bool IsActual => Version == MafileSerializer.MAFILE_VERSION;
|
||||
public bool HasUnusedProperties => UnusedProperties is { Count: > 0 };
|
||||
public bool HasMissingProperties => MissingProperties is { Count: > 0 };
|
||||
public bool HasMissingImportantProperties => MissingImportantProperties is { Count: > 0 };
|
||||
public bool HasSession => SessionResult == DeserializedMafileSessionResult.Valid;
|
||||
public bool HasIdentificationProperty { get; init; }
|
||||
public bool SteamIdValid { get; init; }
|
||||
|
||||
internal static DeserializedMafileInfo Create(MobileData mobileData, int? version = null, Dictionary<string, JProperty>? unusedProperties = null, HashSet<string>? missingProperties = null,
|
||||
DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Missing)
|
||||
{
|
||||
HashSet<string>? missingImportantProperties = null;
|
||||
var hasIdentificationProperty = false;
|
||||
var steamIdValid = false;
|
||||
var isExtended = false;
|
||||
if (mobileData is MobileDataExtended ext)
|
||||
{
|
||||
steamIdValid = ext.SteamId.Steam64.Id > SteamId64.SEED;
|
||||
hasIdentificationProperty = !string.IsNullOrWhiteSpace(ext.AccountName) || steamIdValid;
|
||||
isExtended = true;
|
||||
}
|
||||
|
||||
if (isExtended && missingProperties is { Count: > 0 })
|
||||
{
|
||||
var important = missingProperties.Intersect(ImportantProperties).ToList();
|
||||
if (important.Count > 0) missingImportantProperties = important.ToHashSet();
|
||||
var notImportant = missingProperties.Intersect(NotImportantProperties).ToList();
|
||||
if (notImportant.Count > 0) missingProperties = notImportant.ToHashSet();
|
||||
}
|
||||
|
||||
return new DeserializedMafileInfo
|
||||
{
|
||||
Version = version,
|
||||
IsExtended = isExtended,
|
||||
MissingImportantProperties = missingImportantProperties,
|
||||
MissingProperties = missingProperties,
|
||||
UnusedProperties = unusedProperties,
|
||||
SessionResult = sessionResult,
|
||||
HasIdentificationProperty = hasIdentificationProperty,
|
||||
SteamIdValid = steamIdValid
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
internal class LegacyMafile
|
||||
{
|
||||
[JsonProperty("shared_secret"), JsonRequired] public string SharedSecret { get; set; } = default!;
|
||||
[JsonProperty("identity_secret"), JsonRequired] public string IdentitySecret { get; set; } = default!;
|
||||
[JsonProperty("device_id"), JsonRequired] public string DeviceId { get; set; } = default!;
|
||||
[JsonProperty("revocation_code")] public string RevocationCode { get; set; } = default!;
|
||||
[JsonProperty("account_name")] public string AccountName { get; set; } = default!;
|
||||
[JsonProperty("Session")] public object? SessionData { get; set; } = default!;
|
||||
[JsonProperty("server_time")] public long ServerTime { get; set; } //Unused
|
||||
[JsonProperty("steamid")] public long SteamId { get; set; }
|
||||
[JsonProperty("serial_number")] public string SerialNumber { get; set; } = default!; //Unused
|
||||
[JsonProperty("uri")] public string Uri { get; set; } = default!; //Unused
|
||||
[JsonProperty("token_gid")] public string TokenGid { get; set; } = default!; //Unused
|
||||
[JsonProperty("secret_1")] public string Secret1 { get; set; } = default!; //Unused
|
||||
}
|
||||
+4
-1
@@ -1,5 +1,8 @@
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
[PublicAPI]
|
||||
public class MafileCredits : IMafileCredits
|
||||
{
|
||||
internal static readonly MafileCredits Instance = new();
|
||||
+47
-38
@@ -1,23 +1,28 @@
|
||||
using Newtonsoft.Json;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public static partial class MafileSerializer
|
||||
[PublicAPI]
|
||||
public partial class MafileSerializer
|
||||
{
|
||||
public const int MAFILE_VERSION = 2;
|
||||
|
||||
public const int MAFILE_VERSION = 3;
|
||||
public const string SIGNATURE_PROPERTY_NAME = "@SLSV";
|
||||
private static readonly HashSet<string> ActualProperties = typeof(MobileDataExtended).GetProperties().Select(x => x.Name).ToHashSet();
|
||||
|
||||
public MafileSerializerSettings Settings { get; }
|
||||
|
||||
//TODO: Options with:
|
||||
//allowSessionIdGeneration
|
||||
//allowDeviceIdGeneration
|
||||
//allowInvalidTokensGeneration
|
||||
//etc…
|
||||
public static MobileData Deserialize(string json, bool allowSessionIdGeneration, out DeserializedMafileData mafileData)
|
||||
public MafileSerializer(MafileSerializerSettings? settings = null)
|
||||
{
|
||||
Settings = settings ?? new MafileSerializerSettings();
|
||||
}
|
||||
|
||||
|
||||
public DeserializedMafileData Deserialize(string json)
|
||||
{
|
||||
|
||||
var j = JObject.Parse(json);
|
||||
@@ -47,9 +52,7 @@ public static partial class MafileSerializer
|
||||
var data = j.ToObject<MobileDataExtended>()!;
|
||||
data.SessionData = Validate.ValidateMobileData(data, out var sessionResult);
|
||||
|
||||
mafileData = DeserializedMafileData.CreateActual(unused, sessionResult);
|
||||
return data;
|
||||
|
||||
return DeserializedMafileData.CreateActual(data, unused, sessionResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -57,24 +60,19 @@ public static partial class MafileSerializer
|
||||
}
|
||||
}
|
||||
|
||||
var sharedSecretToken = GetTokenOrThrow(j, nameof(MobileData.SharedSecret), unusedProperties, "sharedsecret", "shared_secret", "shared");
|
||||
var identitySecretToken = GetTokenOrThrow(j, nameof(MobileData.IdentitySecret), unusedProperties, "identitysecret", "identity_secret", "identity");
|
||||
var deviceIdToken = GetTokenOrThrow(j, nameof(MobileData.DeviceId), unusedProperties, "deviceid", "device_id", "device");
|
||||
//TODO: see MobileData.DeviceId ToDo
|
||||
var sharedSecretToken = GetTokenOrThrow(j, unusedProperties, nameof(MobileData.SharedSecret), "sharedsecret", "shared_secret", "shared");
|
||||
var identitySecretToken = GetTokenOrThrow(j, unusedProperties, nameof(MobileData.IdentitySecret), "identitysecret", "identity_secret", "identity");
|
||||
var deviceId = GetDeviceId(j, Settings, unusedProperties, nameof(MobileData.DeviceId), "deviceid", "device_id", "device");
|
||||
|
||||
var sharedSecret = GetBase64(nameof(MobileData.SharedSecret), sharedSecretToken);
|
||||
var identitySecret = GetBase64(nameof(MobileData.IdentitySecret), identitySecretToken);
|
||||
var deviceId = deviceIdToken.Value<string>();
|
||||
Validate.NotNullOrEmpty(nameof(MobileData.DeviceId), deviceId);
|
||||
|
||||
var accountNameToken = GetToken(j, unusedProperties, nameof(MobileDataExtended.AccountName), "account_name", "accountname");
|
||||
var revocationCodeToken = GetToken(j, unusedProperties, nameof(MobileDataExtended.RevocationCode), "revocation_code", "rcode", "r_code", "revocationcode");
|
||||
var accountName = GetString(j, unusedProperties, nameof(MobileDataExtended.AccountName), "account_name", "accountname");
|
||||
var revocationCode = GetString(j, unusedProperties, nameof(MobileDataExtended.RevocationCode), "revocation_code", "rcode", "r_code", "revocationcode");
|
||||
var sessionDataToken = GetToken(j, unusedProperties, nameof(MobileDataExtended.SessionData), "session_data", "sessiondata", "session");
|
||||
|
||||
var accountName = accountNameToken?.Value<string>();
|
||||
var revocationCode = revocationCodeToken?.Value<string>();
|
||||
|
||||
|
||||
//TODO: Better handling & determination of minified
|
||||
var minified
|
||||
= string.IsNullOrWhiteSpace(accountName)
|
||||
&& string.IsNullOrWhiteSpace(revocationCode)
|
||||
@@ -83,13 +81,14 @@ public static partial class MafileSerializer
|
||||
|
||||
if (minified)
|
||||
{
|
||||
mafileData = DeserializedMafileData.Create(version, false, unusedProperties);
|
||||
return new MobileData
|
||||
var data = new MobileData
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
IdentitySecret = identitySecret,
|
||||
SharedSecret = sharedSecret
|
||||
};
|
||||
return DeserializedMafileData.Create(data, version, unusedProperties);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -98,27 +97,34 @@ public static partial class MafileSerializer
|
||||
if (string.IsNullOrWhiteSpace(revocationCode)) missingProperties.Add(nameof(MobileDataExtended.RevocationCode));
|
||||
|
||||
var serverTime = GetLong(j, unusedProperties, nameof(MobileDataExtended.ServerTime), "server_time", "servertime");
|
||||
var serialNumber = GetULong(j, unusedProperties, nameof(MobileDataExtended.SerialNumber), "serial_number", "serialnumber");
|
||||
var uri = GetString(j,unusedProperties, nameof(MobileDataExtended.Uri), "url", "uri");
|
||||
var serialNumber = GetSerialNumber(j, Settings, nameof(MobileDataExtended.SerialNumber), unusedProperties, "serial_number", "serialnumber");
|
||||
var uri = GetString(j, unusedProperties, nameof(MobileDataExtended.Uri), "url", "uri");
|
||||
var tokenGid = GetString(j, unusedProperties, nameof(MobileDataExtended.TokenGid), "token_gid", "tokengid");
|
||||
var secret1 = GetString(j, unusedProperties, nameof(MobileDataExtended.Secret1), "secret_1", "seecret1");
|
||||
var steamId = GetSteamId(j, unusedProperties, nameof(MobileDataExtended.SteamId), "steam_id", "id");
|
||||
|
||||
if(serverTime == null) missingProperties.Add(nameof(MobileDataExtended.ServerTime));
|
||||
if(serialNumber == null) missingProperties.Add(nameof(MobileDataExtended.SerialNumber));
|
||||
if(string.IsNullOrWhiteSpace(uri)) missingProperties.Add(nameof(MobileDataExtended.Uri));
|
||||
if(string.IsNullOrWhiteSpace(tokenGid)) missingProperties.Add(nameof(MobileDataExtended.TokenGid));
|
||||
if(string.IsNullOrWhiteSpace(secret1)) missingProperties.Add(nameof(MobileDataExtended.Secret1));
|
||||
if (serverTime == null) missingProperties.Add(nameof(MobileDataExtended.ServerTime));
|
||||
if (serialNumber == null) missingProperties.Add(nameof(MobileDataExtended.SerialNumber));
|
||||
if (string.IsNullOrWhiteSpace(uri)) missingProperties.Add(nameof(MobileDataExtended.Uri));
|
||||
if (string.IsNullOrWhiteSpace(tokenGid)) missingProperties.Add(nameof(MobileDataExtended.TokenGid));
|
||||
if (string.IsNullOrWhiteSpace(secret1)) missingProperties.Add(nameof(MobileDataExtended.Secret1));
|
||||
|
||||
MobileSessionData? sessionData = null;
|
||||
var sResult = DeserializedMafileSessionResult.Missing;
|
||||
if (sessionDataToken is { Type: JTokenType.Object })
|
||||
if (sessionDataToken is JObject sessionObj)
|
||||
{
|
||||
sessionData = DeserializeMobileSessionData((JObject) sessionDataToken, allowSessionIdGeneration, out sResult);
|
||||
sessionData = DeserializeMobileSessionData(sessionObj, out sResult, out steamId);
|
||||
}
|
||||
|
||||
if ((steamId == null || steamId.Value.Steam64.Id < SteamId64.SEED) && Settings.DeserializationOptions.ThrowIfInvalidSteamId)
|
||||
{
|
||||
throw new ArgumentException("Can't retrieve SteamId from Mafile", nameof(MobileDataExtended.SteamId));
|
||||
}
|
||||
|
||||
mafileData = DeserializedMafileData.Create(version, true, unusedProperties, missingProperties.ToHashSet(), sResult);
|
||||
return new MobileDataExtended
|
||||
if (steamId == null) missingProperties.Add(nameof(MobileDataExtended.SteamId));
|
||||
|
||||
// ReSharper disable once UseObjectOrCollectionInitializer
|
||||
var mobileData = new MobileDataExtended
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
IdentitySecret = identitySecret,
|
||||
@@ -130,8 +136,11 @@ public static partial class MafileSerializer
|
||||
Uri = uri ?? string.Empty,
|
||||
TokenGid = tokenGid ?? string.Empty,
|
||||
Secret1 = secret1 ?? string.Empty,
|
||||
SessionData = sessionData
|
||||
SessionData = sessionData,
|
||||
};
|
||||
mobileData.SteamId = steamId.GetValueOrDefault(); //Keep it here because setting SessionData will override SteamId
|
||||
return DeserializedMafileData.Create(mobileData, version, unusedProperties, missingProperties.ToHashSet(), sResult);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public class MafileSerializerSettings
|
||||
{
|
||||
public MafileDeserializationOptions DeserializationOptions { get; set; } = new();
|
||||
|
||||
[Obsolete("Currently not used")]
|
||||
public MafileDeserializationOptions SerializationOptions { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MafileDeserializationOptions
|
||||
{
|
||||
public bool AllowDeviceIdGeneration { get; set; }
|
||||
public bool AllowSessionIdGeneration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Throws if the <see cref="MobileDataExtended.SerialNumber"/> is 0 or invalid. Otherwise, SerialNumber will be set to 0.
|
||||
/// </summary>
|
||||
public bool ThrowIfInvalidSerialNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Restricts recovering an invalid <see cref="MobileDataExtended.SerialNumber"/> if the value is written as a negative number.
|
||||
/// This can occur when an incompatible type is used, one that does not support large proto fixed64 values.
|
||||
/// <br/> Returns 0 if <see langword="true"/>, instead of attempting to repair the value.
|
||||
/// </summary>
|
||||
public bool RestrictOverflowSerialNumberRecovery { get; set; }
|
||||
public bool ThrowIfInvalidSteamId { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class MafileSerializationOptions
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Core.Enums;
|
||||
using SteamLib.Core.Models;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public partial class MafileSerializer //SessionData
|
||||
{
|
||||
private MobileSessionData? DeserializeMobileSessionData(JObject j, out DeserializedMafileSessionResult result,
|
||||
out SteamId? steamId)
|
||||
{
|
||||
steamId = GetSessionSteamId(j);
|
||||
result = DeserializedMafileSessionResult.Invalid;
|
||||
var refreshToken = GetAuthToken(j, nameof(MobileSessionData.RefreshToken), "refreshtoken", "refresh_token",
|
||||
"refresh", "OAuthToken");
|
||||
|
||||
if (refreshToken is not { Type: SteamAccessTokenType.MobileRefresh })
|
||||
{
|
||||
result = DeserializedMafileSessionResult.Invalid;
|
||||
return null;
|
||||
}
|
||||
|
||||
var accessToken = GetAuthToken(j, "accesstoken", "access_token", "access", "steamLoginSecure");
|
||||
if (accessToken is not { Type: SteamAccessTokenType.Mobile })
|
||||
{
|
||||
accessToken = null;
|
||||
}
|
||||
|
||||
steamId = refreshToken.Value.SteamId;
|
||||
|
||||
|
||||
var sessionId = GetSessionId(j, Settings, "sessionid", "session_id", "session");
|
||||
if (sessionId == null)
|
||||
{
|
||||
result = DeserializedMafileSessionResult.Invalid;
|
||||
return null;
|
||||
}
|
||||
|
||||
var sessionData = new MobileSessionData(sessionId, steamId.Value, refreshToken.Value, accessToken, tokens: null);
|
||||
sessionData.IsValid = SessionDataValidator.Validate(null, sessionData).Succeeded;
|
||||
if (sessionData.IsValid == false)
|
||||
return null;
|
||||
|
||||
return sessionData;
|
||||
}
|
||||
|
||||
private static SteamAuthToken? GetAuthToken(JObject j, params string[] aliases)
|
||||
{
|
||||
var jAuthToken = GetToken(j, aliases);
|
||||
|
||||
|
||||
SteamAuthToken? token = null;
|
||||
if (jAuthToken == null || jAuthToken.Type == JTokenType.Null) return null;
|
||||
if (jAuthToken.Type == JTokenType.String &&
|
||||
SteamTokenHelper.TryParse(jAuthToken.Value<string>()!, out var parsed))
|
||||
{
|
||||
token = parsed;
|
||||
}
|
||||
else if (jAuthToken.Type == JTokenType.Object)
|
||||
{
|
||||
try
|
||||
{
|
||||
token = jAuthToken.ToObject<SteamAuthToken>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private static SteamId? GetSessionSteamId(JObject j)
|
||||
{
|
||||
var token = GetToken(j, "steamid");
|
||||
if (token == null || token.Type == JTokenType.Null)
|
||||
return null;
|
||||
|
||||
if (token.Type == JTokenType.Integer)
|
||||
return SteamId.FromSteam64(token.Value<long>());
|
||||
|
||||
if (token.Type == JTokenType.String && SteamId64.TryParse(token.Value<string>(), out var steamId))
|
||||
{
|
||||
return new SteamId(steamId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? GetSessionId(JObject j, MafileSerializerSettings settings, params string[] aliases)
|
||||
{
|
||||
var sessionId = GetString(j, aliases);
|
||||
if (sessionId == null && settings.DeserializationOptions.AllowSessionIdGeneration)
|
||||
{
|
||||
return GenerateRandomHex();
|
||||
}
|
||||
return sessionId;
|
||||
|
||||
static string GenerateRandomHex(int byteLength = 12)
|
||||
{
|
||||
byte[] randomBytes = new byte[byteLength];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(randomBytes);
|
||||
}
|
||||
|
||||
var hex = new StringBuilder(byteLength * 2);
|
||||
foreach (var b in randomBytes)
|
||||
hex.Append($"{b:x2}");
|
||||
|
||||
return hex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+80
-6
@@ -1,6 +1,8 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Numerics;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public partial class MafileSerializer //Utility
|
||||
{
|
||||
@@ -8,7 +10,7 @@ public partial class MafileSerializer //Utility
|
||||
{
|
||||
foreach (var name in aliases)
|
||||
{
|
||||
if (j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token))
|
||||
if (j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token))
|
||||
{
|
||||
return token;
|
||||
}
|
||||
@@ -21,7 +23,7 @@ public partial class MafileSerializer //Utility
|
||||
{
|
||||
foreach (var name in aliases)
|
||||
{
|
||||
if (!j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) continue;
|
||||
if (!j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) continue;
|
||||
var parent = token.Parent as JProperty;
|
||||
removeFrom.Remove(parent!.Name);
|
||||
return token;
|
||||
@@ -30,11 +32,11 @@ public partial class MafileSerializer //Utility
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JToken GetTokenOrThrow(JObject j, string propertyName, Dictionary<string, JProperty> removeFrom, params string[] aliases)
|
||||
private static JToken GetTokenOrThrow(JObject j, Dictionary<string, JProperty> removeFrom, string propertyName, params string[] aliases)
|
||||
{
|
||||
foreach (var name in aliases)
|
||||
{
|
||||
if (!j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) continue;
|
||||
if (!j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) continue;
|
||||
if (token.Type == JTokenType.Null)
|
||||
{
|
||||
throw new ArgumentException($"Required property {propertyName} is null");
|
||||
@@ -129,4 +131,76 @@ public partial class MafileSerializer //Utility
|
||||
$"Not valid token type for base64 property '{propertyName}'. Type: {token.Type}");
|
||||
}
|
||||
|
||||
|
||||
private static string GetDeviceId(JObject j, MafileSerializerSettings settings, Dictionary<string, JProperty> removeFrom, string propertyName,
|
||||
params string[] aliases)
|
||||
{
|
||||
var deviceId = GetString(j, removeFrom, aliases);
|
||||
if (string.IsNullOrWhiteSpace(deviceId) && !settings.DeserializationOptions.AllowDeviceIdGeneration)
|
||||
{
|
||||
throw new ArgumentException($"Required property {propertyName} not found");
|
||||
}
|
||||
|
||||
return deviceId ?? GenerateDeviceId();
|
||||
|
||||
|
||||
static string GenerateDeviceId()
|
||||
{
|
||||
return "android:" + Guid.NewGuid();
|
||||
}
|
||||
}
|
||||
private static ulong? GetSerialNumber(JObject j, MafileSerializerSettings settings, string propertyName, Dictionary<string, JProperty> removeFrom,
|
||||
params string[] aliases)
|
||||
{
|
||||
var token = GetToken(j, removeFrom, aliases);
|
||||
if (token == null || token.Type == JTokenType.Null)
|
||||
return null;
|
||||
|
||||
if (token.Type is JTokenType.Integer or JTokenType.String)
|
||||
{
|
||||
var bigInt = token.ToObject<BigInteger>();
|
||||
ulong res;
|
||||
if (bigInt < ulong.MinValue && bigInt > long.MinValue) //Negative, e.g. -2260921916482386064
|
||||
{
|
||||
res = settings.DeserializationOptions.RestrictOverflowSerialNumberRecovery ? 0 : GetFromOverflow((long)bigInt);
|
||||
}
|
||||
else if (bigInt > ulong.MinValue && bigInt < ulong.MaxValue) //Valid range
|
||||
{
|
||||
res = (ulong)bigInt;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (res == 0 && settings.DeserializationOptions.ThrowIfInvalidSerialNumber)
|
||||
throw new ArgumentException(
|
||||
$"SerialNumber has invalid value. Value: '{token.ToObject<object>()}'. Property: '{(token as JProperty)?.Name ?? propertyName}'");
|
||||
return res;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(token.Type),
|
||||
$"Not valid token type for base64 property '{propertyName}'. Type: {token.Type}");
|
||||
|
||||
static ulong GetFromOverflow(long overflow)
|
||||
{
|
||||
ulong originalValue;
|
||||
unchecked
|
||||
{
|
||||
originalValue = (ulong)overflow + ulong.MaxValue + 1;
|
||||
}
|
||||
return originalValue;
|
||||
}
|
||||
}
|
||||
private static SteamId? GetSteamId(JObject j, Dictionary<string, JProperty> removeFrom,
|
||||
params string[] aliases)
|
||||
{
|
||||
var id = GetLong(j, removeFrom, aliases);
|
||||
return id switch
|
||||
{
|
||||
null or < SteamId64.SEED => null,
|
||||
_ => SteamId.FromSteam64(id.Value)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-21
@@ -1,9 +1,8 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Core.Interfaces;
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public partial class MafileSerializer //Validate
|
||||
{
|
||||
@@ -60,11 +59,6 @@ public partial class MafileSerializer //Validate
|
||||
if (d.SessionData == null) return null;
|
||||
|
||||
sessionResult = DeserializedMafileSessionResult.Invalid;
|
||||
if (d.SessionData.RefreshToken.IsExpired)
|
||||
{
|
||||
sessionResult = DeserializedMafileSessionResult.Expired;
|
||||
}
|
||||
|
||||
d.SessionData.IsValid = SessionDataValidator.Validate(null, d.SessionData).Succeeded;
|
||||
if (d.SessionData.IsValid == false) return null;
|
||||
|
||||
@@ -73,19 +67,5 @@ public partial class MafileSerializer //Validate
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool ValidateSessionData(ISessionData sessionData, out bool isOutdated)
|
||||
{
|
||||
|
||||
if (sessionData.RefreshToken.IsExpired)
|
||||
{
|
||||
isOutdated = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
isOutdated = false;
|
||||
return SessionDataValidator.Validate(null, sessionData).Succeeded;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace SteamLib.Utility.MaFiles;
|
||||
namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
public static partial class MafileSerializer //Write
|
||||
public partial class MafileSerializer //Write
|
||||
{
|
||||
private const string CREDITS_PROPERTY_NAME = "Credits";
|
||||
public static string Serialize(MobileData mobileData, Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null)
|
||||
public static string Serialize(MobileDataExtended mobileData, Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null)
|
||||
{
|
||||
using var w = new StringWriter();
|
||||
using var write = new JsonTextWriter(w);
|
||||
@@ -25,7 +25,7 @@ public static partial class MafileSerializer //Write
|
||||
|
||||
}
|
||||
|
||||
public static async Task<string> SerializeAsync(MobileData mobileData, Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null)
|
||||
public static async Task<string> SerializeAsync(MobileDataExtended mobileData, Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null)
|
||||
{
|
||||
await using var w = new StringWriter();
|
||||
await using var write = new JsonTextWriter(w);
|
||||
@@ -52,7 +52,6 @@ public static partial class MafileSerializer //Write
|
||||
SharedSecret = mobileData.SharedSecret,
|
||||
IdentitySecret = mobileData.IdentitySecret,
|
||||
DeviceId = mobileData.DeviceId,
|
||||
|
||||
};
|
||||
|
||||
if (mobileData is MobileDataExtended ext)
|
||||
@@ -74,6 +73,7 @@ public static partial class MafileSerializer //Write
|
||||
result.Uri = ext.Uri;
|
||||
result.TokenGid = ext.TokenGid;
|
||||
result.Secret1 = ext.Secret1;
|
||||
result.SteamId = ext.SteamId.Steam64.Id;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Utility;
|
||||
|
||||
@@ -37,19 +37,31 @@ public static class SteamIdParser
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryParse64(string input, out SteamId64 result)
|
||||
public static bool TryParse64(string? input, out SteamId64 result)
|
||||
{
|
||||
result = default;
|
||||
if (input == null) return false;
|
||||
var match64 = Steam64Regex.Match(input);
|
||||
if (match64.Success)
|
||||
{
|
||||
result = new SteamId64(long.Parse(match64.Value));
|
||||
return true;
|
||||
return TryParse64(long.Parse(match64.Value), out result);
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryParse64(long input, out SteamId64 result)
|
||||
{
|
||||
result = default;
|
||||
if (input < SteamId64.SEED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result = new SteamId64(input);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParse2(string input, out SteamId2 result)
|
||||
{
|
||||
var match2 = Steam2Regex.Match(input);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core.Models;
|
||||
|
||||
namespace SteamLib.Web.Converters;
|
||||
|
||||
@@ -18,7 +18,7 @@ public class SteamIdToSteam64Converter : JsonConverter<SteamId>
|
||||
return SteamId.FromSteam64(l);
|
||||
}
|
||||
|
||||
var str = (string) reader.Value!;
|
||||
var str = (string)reader.Value!;
|
||||
return new SteamId(SteamId64.Parse(str));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Changelog</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #eeeeee;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.changelog-container {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
margin: 25px auto;
|
||||
max-width: 800px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.change {
|
||||
margin-bottom: 20px;
|
||||
padding: 0;
|
||||
border-left: 4px solid #a50ec7;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.version {
|
||||
font-weight: 600;
|
||||
font-size: 1.5em;
|
||||
color: #a50ec7;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-style: italic;
|
||||
color: #888;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1em;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.description ul {
|
||||
list-style: inside square;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.changelog-container {
|
||||
width: 90%;
|
||||
margin: 25px auto;
|
||||
padding: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="changelog-container">
|
||||
<!-- Changelog entry -->
|
||||
<div class="change">
|
||||
<div class="version">Version 1.5.4</div>
|
||||
<div class="date">10.11.2024</div>
|
||||
<div class="description">
|
||||
<ul>
|
||||
<li><b>NEWS:</b> Official Telegram group now available! Join us at <b><a href="https://t.me/nebulaauth">t.me/nebulaauth</a></b></li>
|
||||
<li>
|
||||
<b>Compatibility:</b> Improved mafiles compatibility:
|
||||
<ul style="margin-left: 15px">
|
||||
<li>Full: with SDA, MarketApp, SIH</li>
|
||||
<li>Re-login after import: LZT, TO SDA, old mafiles</li>
|
||||
<li>Re-login <b>on</b> import: Mafiles without "Session" data or "SteamId" property</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><b>UPDATE:</b> Proxy and password (if set) now automatically saved in mafile after linking guard</li>
|
||||
<li><b>UI:</b> Encryption password dialog on startup now supports "Enter" hot-key</li>
|
||||
<li><b>UI:</b> Added "Copy SteamId" button in the maFile context menu.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user