From ef8e12e20dff2066692a44dabc9462a49d270adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=B2=D0=B8=D0=B4=20=D0=A7=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D1=8F=D1=82=D0=BE=D0=B2?= Date: Sun, 10 Nov 2024 02:29:40 +0200 Subject: [PATCH] 1.5.4 progress - Mafile version was updated (3), new property SteamId added - MafileSerializer was refactored to adjust compability and code readability. - Serialization work moved from Storage to new class NebulaSerializer --- NebulaAuth.LegacyConverter/Program.cs | 17 +- NebulaAuth.sln | 1 + NebulaAuth/Model/Entities/Mafile.cs | 1 + NebulaAuth/Model/NebulaSerializer.cs | 83 ++++++++ NebulaAuth/Model/Storage.cs | 118 ++---------- NebulaAuth/ViewModel/MainVM_Groups.cs | 11 +- NebulaAuth/ViewModel/MainVM_Proxy.cs | 16 +- NebulaAuth/ViewModel/Other/LinkAccountVM.cs | 4 +- SteamLibForked/Account/MobileSessionData.cs | 7 +- SteamLibForked/Account/SessionData.cs | 12 +- SteamLibForked/Account/SteamAuthToken.cs | 1 + SteamLibForked/Account/SteamId.cs | 180 ------------------ SteamLibForked/Api/Mobile/SteamMobileApi.cs | 2 +- .../Api/Mobile/SteamMobileConfirmationsApi.cs | 2 +- .../Authentication/LoginV2/LoginV2Executor.cs | 1 + .../Authentication/SessionDataValidator.cs | 2 +- .../Authentication/SteamTokenHelper.cs | 1 + .../Core/Interfaces/ISessionData.cs | 1 + SteamLibForked/Core/Models/SteamId.cs | 42 ++++ .../Core/Models/SteamIds/SteamId2.cs | 46 +++++ .../Core/Models/SteamIds/SteamId3.cs | 51 +++++ .../Core/Models/SteamIds/SteamId64.cs | 50 +++++ .../MobileSteamClient/MobileData.cs | 28 ++- .../SteamAuthenticatorLinker.cs | 2 +- .../Utility/MaFiles/DeserializedMafileData.cs | 144 -------------- .../Utility/MaFiles/LegacyMafile.cs | 41 ---- .../MaFiles/MafileSerializer_SessionData.cs | 150 --------------- .../DeserializedMafileData.cs | 111 +++++++++++ .../MafileSerialization/LegacyMafile.cs | 19 ++ .../MafileCredits.cs | 5 +- .../MafileSerializer.cs | 93 +++++---- .../MafileSerializerSettings.cs | 34 ++++ .../MafileSerializer_SessionData.cs | 121 ++++++++++++ .../MafileSerializer_Utility.cs | 92 ++++++++- .../MafileSerializer_Validate.cs | 22 +-- .../MafileSerializer_Write.cs | 24 +-- SteamLibForked/Utility/SteamIdParser.cs | 26 ++- .../Web/Converters/SteamIdConverters.cs | 8 +- changelog/1.5.4.html | 80 ++++++++ 39 files changed, 895 insertions(+), 754 deletions(-) create mode 100644 NebulaAuth/Model/NebulaSerializer.cs delete mode 100644 SteamLibForked/Account/SteamId.cs create mode 100644 SteamLibForked/Core/Models/SteamId.cs create mode 100644 SteamLibForked/Core/Models/SteamIds/SteamId2.cs create mode 100644 SteamLibForked/Core/Models/SteamIds/SteamId3.cs create mode 100644 SteamLibForked/Core/Models/SteamIds/SteamId64.cs delete mode 100644 SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs delete mode 100644 SteamLibForked/Utility/MaFiles/LegacyMafile.cs delete mode 100644 SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs create mode 100644 SteamLibForked/Utility/MafileSerialization/DeserializedMafileData.cs create mode 100644 SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs rename SteamLibForked/Utility/{MaFiles => MafileSerialization}/MafileCredits.cs (83%) rename SteamLibForked/Utility/{MaFiles => MafileSerialization}/MafileSerializer.cs (50%) create mode 100644 SteamLibForked/Utility/MafileSerialization/MafileSerializerSettings.cs create mode 100644 SteamLibForked/Utility/MafileSerialization/MafileSerializer_SessionData.cs rename SteamLibForked/Utility/{MaFiles => MafileSerialization}/MafileSerializer_Utility.cs (51%) rename SteamLibForked/Utility/{MaFiles => MafileSerialization}/MafileSerializer_Validate.cs (80%) rename SteamLibForked/Utility/{MaFiles => MafileSerialization}/MafileSerializer_Write.cs (75%) create mode 100644 changelog/1.5.4.html diff --git a/NebulaAuth.LegacyConverter/Program.cs b/NebulaAuth.LegacyConverter/Program.cs index c45f9e0..5e72683 100644 --- a/NebulaAuth.LegacyConverter/Program.cs +++ b/NebulaAuth.LegacyConverter/Program.cs @@ -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; @@ -29,7 +39,7 @@ try } } } - + var decryptMode = false; while (true) { @@ -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); diff --git a/NebulaAuth.sln b/NebulaAuth.sln index 3b17041..29f7e9e 100644 --- a/NebulaAuth.sln +++ b/NebulaAuth.sln @@ -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 diff --git a/NebulaAuth/Model/Entities/Mafile.cs b/NebulaAuth/Model/Entities/Mafile.cs index abc8a61..f869ca9 100644 --- a/NebulaAuth/Model/Entities/Mafile.cs +++ b/NebulaAuth/Model/Entities/Mafile.cs @@ -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) diff --git a/NebulaAuth/Model/NebulaSerializer.cs b/NebulaAuth/Model/NebulaSerializer.cs new file mode 100644 index 0000000..4b9835e --- /dev/null +++ b/NebulaAuth/Model/NebulaSerializer.cs @@ -0,0 +1,83 @@ +using NebulaAuth.Model.Entities; +using Newtonsoft.Json.Linq; +using SteamLib; +using SteamLib.Utility.MafileSerialization; +using System.Collections.Generic; +using System; +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 = true + } + }); + } + + + 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(); + var proxy = GetPropertyValue("Proxy", props); + var group = GetPropertyValue("Group", props); + var password = GetPropertyValue("Password", props); + return Mafile.FromMobileDataExtended((MobileDataExtended)mobileData, proxy, group, password); + } + + private static T? GetPropertyValue(string name, Dictionary dictionary) + { + if (dictionary.TryGetValue(name, out var prop) == false) return default; + var value = prop.Value; + try + { + return value.ToObject(); + } + 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 + { + {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? properties) + { + if (Settings.Instance.LegacyMode) + { + return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties); + } + else + { + return MafileSerializer.Serialize(data); + } + } +} \ No newline at end of file diff --git a/NebulaAuth/Model/Storage.cs b/NebulaAuth/Model/Storage.cs index feb3e1f..574eff5 100644 --- a/NebulaAuth/Model/Storage.cs +++ b/NebulaAuth/Model/Storage.cs @@ -1,19 +1,18 @@ 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 SteamLib; namespace NebulaAuth.Model; +//RETHINK public static class Storage { public const string MAFILE_F = "maFiles"; @@ -42,21 +41,20 @@ public static class Storage var hashIds = new HashSet(); 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) @@ -76,6 +74,11 @@ public static class Storage { data = ReadMafile(path); } + catch (ArgumentException ex) + when(ex.ParamName == nameof(MobileDataExtended.SteamId)) + { + throw new SessionInvalidException(); //Allows to handle LoginOnImport + } catch (Exception ex) { Shell.Logger.Warn(ex, "Can't load mafile"); @@ -94,14 +97,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 +110,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(); - var proxy = GetPropertyValue("Proxy", props); - var group = GetPropertyValue("Group", props); - var password = GetPropertyValue("Password", props); - return Mafile.FromMobileDataExtended((MobileDataExtended)mafile, proxy, group, password); - } - - public static string SerializeMafile(Mafile data) - { - var props = new Dictionary - { - {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? properties) - { - if (Settings.Instance.LegacyMode) - { - return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties); - } - else - { - return MafileSerializer.Serialize(data); - } - } - - - private static T? GetPropertyValue(string name, Dictionary dictionary) - { - if (dictionary.TryGetValue(name, out var prop) == false) return default; - var value = prop.Value; - try - { - return value.ToObject(); - } - 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 +134,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 +156,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 +167,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 +188,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 { public bool MafileNameMode { get; } diff --git a/NebulaAuth/ViewModel/MainVM_Groups.cs b/NebulaAuth/ViewModel/MainVM_Groups.cs index 7e7f08e..157cdb3 100644 --- a/NebulaAuth/ViewModel/MainVM_Groups.cs +++ b/NebulaAuth/ViewModel/MainVM_Groups.cs @@ -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); } } diff --git a/NebulaAuth/ViewModel/MainVM_Proxy.cs b/NebulaAuth/ViewModel/MainVM_Proxy.cs index 349c4f9..8a3c64c 100644 --- a/NebulaAuth/ViewModel/MainVM_Proxy.cs +++ b/NebulaAuth/ViewModel/MainVM_Proxy.cs @@ -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; - } + } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/Other/LinkAccountVM.cs b/NebulaAuth/ViewModel/Other/LinkAccountVM.cs index f75ba35..3c37c58 100644 --- a/NebulaAuth/ViewModel/Other/LinkAccountVM.cs +++ b/NebulaAuth/ViewModel/Other/LinkAccountVM.cs @@ -328,8 +328,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 diff --git a/SteamLibForked/Account/MobileSessionData.cs b/SteamLibForked/Account/MobileSessionData.cs index e68b6bd..6512377 100644 --- a/SteamLibForked/Account/MobileSessionData.cs +++ b/SteamLibForked/Account/MobileSessionData.cs @@ -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? tokens) - : base(sessionId, steamId, refreshToken, tokens) + SteamAuthToken? mobileToken, IEnumerable? 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); } - - } \ No newline at end of file diff --git a/SteamLibForked/Account/SessionData.cs b/SteamLibForked/Account/SessionData.cs index 662e585..a9a8bb7 100644 --- a/SteamLibForked/Account/SessionData.cs +++ b/SteamLibForked/Account/SessionData.cs @@ -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(tokens ?? new Dictionary()); } - public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable? tokens) + public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable? tokensCollection) { SessionId = sessionId; SteamId = steamId; RefreshToken = refreshToken; - tokens ??= Array.Empty(); - Tokens = new ConcurrentDictionary(tokens.ToDictionary(t => t.Domain, t => t)); + tokensCollection ??= Array.Empty(); + Tokens = new ConcurrentDictionary(tokensCollection.ToDictionary(t => t.Domain, t => t)); } public virtual SteamAuthToken? GetToken(SteamDomain domain) diff --git a/SteamLibForked/Account/SteamAuthToken.cs b/SteamLibForked/Account/SteamAuthToken.cs index 2ea2507..c3c4e43 100644 --- a/SteamLibForked/Account/SteamAuthToken.cs +++ b/SteamLibForked/Account/SteamAuthToken.cs @@ -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; diff --git a/SteamLibForked/Account/SteamId.cs b/SteamLibForked/Account/SteamId.cs deleted file mode 100644 index 4985a58..0000000 --- a/SteamLibForked/Account/SteamId.cs +++ /dev/null @@ -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); - - /// - 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); - /// - 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); - /// - 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); - - /// - public static SteamId3 Parse(string input) => SteamIdParser.Parse3(input); -} \ No newline at end of file diff --git a/SteamLibForked/Api/Mobile/SteamMobileApi.cs b/SteamLibForked/Api/Mobile/SteamMobileApi.cs index 16e8076..bcb1ce1 100644 --- a/SteamLibForked/Api/Mobile/SteamMobileApi.cs +++ b/SteamLibForked/Api/Mobile/SteamMobileApi.cs @@ -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; diff --git a/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs b/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs index d5bf9d2..fabd470 100644 --- a/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs +++ b/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs @@ -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; diff --git a/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs b/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs index db4783c..f9864ec 100644 --- a/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs +++ b/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs @@ -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; diff --git a/SteamLibForked/Authentication/SessionDataValidator.cs b/SteamLibForked/Authentication/SessionDataValidator.cs index 4e6cdf8..d875ac0 100644 --- a/SteamLibForked/Authentication/SessionDataValidator.cs +++ b/SteamLibForked/Authentication/SessionDataValidator.cs @@ -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; diff --git a/SteamLibForked/Authentication/SteamTokenHelper.cs b/SteamLibForked/Authentication/SteamTokenHelper.cs index 63fb5db..e90d6f8 100644 --- a/SteamLibForked/Authentication/SteamTokenHelper.cs +++ b/SteamLibForked/Authentication/SteamTokenHelper.cs @@ -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; diff --git a/SteamLibForked/Core/Interfaces/ISessionData.cs b/SteamLibForked/Core/Interfaces/ISessionData.cs index 2cbcdda..f556acd 100644 --- a/SteamLibForked/Core/Interfaces/ISessionData.cs +++ b/SteamLibForked/Core/Interfaces/ISessionData.cs @@ -1,5 +1,6 @@ using SteamLib.Account; using SteamLib.Core.Enums; +using SteamLib.Core.Models; namespace SteamLib.Core.Interfaces; diff --git a/SteamLibForked/Core/Models/SteamId.cs b/SteamLibForked/Core/Models/SteamId.cs new file mode 100644 index 0000000..9889c91 --- /dev/null +++ b/SteamLibForked/Core/Models/SteamId.cs @@ -0,0 +1,42 @@ +using SteamLib.Utility; + +namespace SteamLib.Core.Models; + +public readonly struct SteamId : IEquatable //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); + + /// + 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(); +} \ No newline at end of file diff --git a/SteamLibForked/Core/Models/SteamIds/SteamId2.cs b/SteamLibForked/Core/Models/SteamIds/SteamId2.cs new file mode 100644 index 0000000..0000562 --- /dev/null +++ b/SteamLibForked/Core/Models/SteamIds/SteamId2.cs @@ -0,0 +1,46 @@ +using SteamLib.Utility; + +namespace SteamLib.Core.Models; + +public readonly struct SteamId2 : IEquatable +{ + 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); + /// + public static SteamId2 Parse(string input) => SteamIdParser.Parse2(input); +} \ No newline at end of file diff --git a/SteamLibForked/Core/Models/SteamIds/SteamId3.cs b/SteamLibForked/Core/Models/SteamIds/SteamId3.cs new file mode 100644 index 0000000..6858db7 --- /dev/null +++ b/SteamLibForked/Core/Models/SteamIds/SteamId3.cs @@ -0,0 +1,51 @@ +using SteamLib.Utility; + +namespace SteamLib.Core.Models; + +public readonly struct SteamId3 : IEquatable +{ + 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); + + /// + public static SteamId3 Parse(string input) => SteamIdParser.Parse3(input); +} \ No newline at end of file diff --git a/SteamLibForked/Core/Models/SteamIds/SteamId64.cs b/SteamLibForked/Core/Models/SteamIds/SteamId64.cs new file mode 100644 index 0000000..f8cd0f1 --- /dev/null +++ b/SteamLibForked/Core/Models/SteamIds/SteamId64.cs @@ -0,0 +1,50 @@ +using SteamLib.Utility; + +namespace SteamLib.Core.Models; + +public readonly struct SteamId64 : IEquatable +{ + 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); + /// + public static SteamId64 Parse(string input) => SteamIdParser.Parse64(input); + + public static implicit operator long(SteamId64 steamId64) => steamId64.ToLong(); +} \ No newline at end of file diff --git a/SteamLibForked/MobileSteamClient/MobileData.cs b/SteamLibForked/MobileSteamClient/MobileData.cs index 32529ac..07ab22d 100644 --- a/SteamLibForked/MobileSteamClient/MobileData.cs +++ b/SteamLibForked/MobileSteamClient/MobileData.cs @@ -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 } \ No newline at end of file diff --git a/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs b/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs index 540ee7e..18d76ea 100644 --- a/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs +++ b/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs @@ -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); diff --git a/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs b/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs deleted file mode 100644 index e1d3224..0000000 --- a/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs +++ /dev/null @@ -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? UnusedProperties { get; init; } - public HashSet? 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? properties = null, HashSet? missingProperties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Missing) - { - return new DeserializedMafileData - { - Version = version, - UnusedProperties = properties, - IsExtended = isExtended, - MissingProperties = missingProperties, - SessionResult = sessionResult - }; - } - - internal static DeserializedMafileData CreateActual(Dictionary? properties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Valid) - { - return new DeserializedMafileData - { - Version = MafileSerializer.MAFILE_VERSION, - UnusedProperties = properties, - IsExtended = true, - SessionResult = sessionResult - }; - } -} - - -/// -/// Representation class just for displaying deserialization result -/// -[PublicAPI] -public class DeserializedMafileResult -{ - public static readonly HashSet ImportantProperties = new() - { - nameof(MobileDataExtended.RevocationCode), - nameof(MobileDataExtended.AccountName), - }; - - public static readonly HashSet 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? MissingImportantProperties { get; init; } = new(); - public HashSet? MissingProperties { get; init; } = new(); - public Dictionary? 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? missingImportantProperties = null; - HashSet? 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().ToString(); - case JTokenType.Float: - return property.Value.Value().ToString(CultureInfo.InvariantCulture); - case JTokenType.String: - return property.Value.Value()!; - case JTokenType.Boolean: - return property.Value.Value().ToString(); - case JTokenType.Date: - return property.Value.Value().ToString(CultureInfo.InvariantCulture); - case JTokenType.Guid: - return property.Value.Value().ToString(); - case JTokenType.Uri: - return property.Value.Value()!.ToString(); - case JTokenType.TimeSpan: - return property.Value.Value().ToString(); - default: - return "unknown"; - } - } -} \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/LegacyMafile.cs b/SteamLibForked/Utility/MaFiles/LegacyMafile.cs deleted file mode 100644 index 1e14bda..0000000 --- a/SteamLibForked/Utility/MaFiles/LegacyMafile.cs +++ /dev/null @@ -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 - -} \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs deleted file mode 100644 index 975750f..0000000 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs +++ /dev/null @@ -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()!, out var parsed)) - { - refreshToken = parsed; - } - else if (jRefreshToken.Type == JTokenType.Object) - { - try - { - refreshToken = jRefreshToken.ToObject(); - } - 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(); - } - catch - { - // ignored - } - } - else if (jAccessToken is { Type: JTokenType.String } && - SteamTokenHelper.TryParse(jAccessToken.Value()!, 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()); - 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()); - - if (token.Type == JTokenType.String && long.TryParse(token.Value()!, 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(); - } -} \ No newline at end of file diff --git a/SteamLibForked/Utility/MafileSerialization/DeserializedMafileData.cs b/SteamLibForked/Utility/MafileSerialization/DeserializedMafileData.cs new file mode 100644 index 0000000..ce11117 --- /dev/null +++ b/SteamLibForked/Utility/MafileSerialization/DeserializedMafileData.cs @@ -0,0 +1,111 @@ +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? properties = null, HashSet? 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? properties = null, DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Valid) + { + var info = DeserializedMafileInfo.Create(mobileData, MafileSerializer.MAFILE_VERSION, properties, null, sessionResult); + return new DeserializedMafileData(mobileData, info); + } +} + + +/// +/// Represents information about deserialized mafile +/// +[PublicAPI] +public class DeserializedMafileInfo +{ + public static readonly HashSet ImportantProperties = + [ + nameof(MobileDataExtended.RevocationCode), + nameof(MobileDataExtended.AccountName), + ]; + + public static readonly HashSet 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? MissingImportantProperties { get; init; } = []; + public HashSet? MissingProperties { get; init; } = []; + public Dictionary? 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; } + + internal static DeserializedMafileInfo Create(MobileData mobileData, int? version = null, Dictionary? unusedProperties = null, HashSet? missingProperties = null, + DeserializedMafileSessionResult sessionResult = DeserializedMafileSessionResult.Missing) + { + HashSet? missingImportantProperties = null; + var hasIdentificationProperty = false; + var isExtended = false; + if (mobileData is MobileDataExtended ext) + { + hasIdentificationProperty = !string.IsNullOrWhiteSpace(ext.AccountName) || ext.SteamId.Steam64.Id > SteamId64.SEED; + 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 + }; + } + +} \ No newline at end of file diff --git a/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs b/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs new file mode 100644 index 0000000..098339e --- /dev/null +++ b/SteamLibForked/Utility/MafileSerialization/LegacyMafile.cs @@ -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 +} \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileCredits.cs b/SteamLibForked/Utility/MafileSerialization/MafileCredits.cs similarity index 83% rename from SteamLibForked/Utility/MaFiles/MafileCredits.cs rename to SteamLibForked/Utility/MafileSerialization/MafileCredits.cs index 1a79ed9..9374164 100644 --- a/SteamLibForked/Utility/MaFiles/MafileCredits.cs +++ b/SteamLibForked/Utility/MafileSerialization/MafileCredits.cs @@ -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(); diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer.cs b/SteamLibForked/Utility/MafileSerialization/MafileSerializer.cs similarity index 50% rename from SteamLibForked/Utility/MaFiles/MafileSerializer.cs rename to SteamLibForked/Utility/MafileSerialization/MafileSerializer.cs index 2a13171..55e2b6d 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer.cs +++ b/SteamLibForked/Utility/MafileSerialization/MafileSerializer.cs @@ -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 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()!; 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,68 +60,71 @@ 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(); 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(); - var revocationCode = revocationCodeToken?.Value(); - - + //TODO: Better handling & determination of minified var minified - = string.IsNullOrWhiteSpace(accountName) - && string.IsNullOrWhiteSpace(revocationCode) + = string.IsNullOrWhiteSpace(accountName) + && string.IsNullOrWhiteSpace(revocationCode) && (sessionDataToken == null || sessionDataToken.Type == JTokenType.Null); 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); + } var missingProperties = new List(); if (string.IsNullOrWhiteSpace(accountName)) missingProperties.Add(nameof(MobileDataExtended.AccountName)); 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 serverTime = GetLong(j, unusedProperties, nameof(MobileDataExtended.ServerTime), "server_time", "servertime"); + 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); + } } \ No newline at end of file diff --git a/SteamLibForked/Utility/MafileSerialization/MafileSerializerSettings.cs b/SteamLibForked/Utility/MafileSerialization/MafileSerializerSettings.cs new file mode 100644 index 0000000..ab0fdac --- /dev/null +++ b/SteamLibForked/Utility/MafileSerialization/MafileSerializerSettings.cs @@ -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; } + + /// + /// Throws if the is 0 or invalid. Otherwise, SerialNumber will be set to 0. + /// + public bool ThrowIfInvalidSerialNumber { get; set; } + + /// + /// Restricts recovering an invalid 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. + ///
Returns 0 if , instead of attempting to repair the value. + ///
+ public bool RestrictOverflowSerialNumberRecovery { get; set; } + public bool ThrowIfInvalidSteamId { get; set; } + +} + +public class MafileSerializationOptions +{ + +} \ No newline at end of file diff --git a/SteamLibForked/Utility/MafileSerialization/MafileSerializer_SessionData.cs b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_SessionData.cs new file mode 100644 index 0000000..b8b8785 --- /dev/null +++ b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_SessionData.cs @@ -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()!, out var parsed)) + { + token = parsed; + } + else if (jAuthToken.Type == JTokenType.Object) + { + try + { + token = jAuthToken.ToObject(); + } + 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()); + + if (token.Type == JTokenType.String && SteamId64.TryParse(token.Value(), 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(); + } + } + +} + diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Utility.cs similarity index 51% rename from SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs rename to SteamLibForked/Utility/MafileSerialization/MafileSerializer_Utility.cs index 8211cfa..2c490b2 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_Utility.cs +++ b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Utility.cs @@ -1,14 +1,16 @@ -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 { - private static JToken? GetToken(JObject j, params string[] aliases) + private static JToken? GetToken(JObject j, params string[] aliases) { 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 removeFrom, params string[] aliases) + private static JToken GetTokenOrThrow(JObject j, Dictionary 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"); @@ -66,7 +68,7 @@ public partial class MafileSerializer //Utility if (token == null || token.Type == JTokenType.Null) return null; - + return token.Value(); @@ -129,4 +131,76 @@ public partial class MafileSerializer //Utility $"Not valid token type for base64 property '{propertyName}'. Type: {token.Type}"); } -} \ No newline at end of file + + private static string GetDeviceId(JObject j, MafileSerializerSettings settings, Dictionary 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 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(); + 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()}'. 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 removeFrom, + params string[] aliases) + { + var id = GetLong(j, removeFrom, aliases); + return id switch + { + null or < SteamId64.SEED => null, + _ => SteamId.FromSteam64(id.Value) + }; + } +} + diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs similarity index 80% rename from SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs rename to SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs index 83b37c5..97e0cda 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_Validate.cs +++ b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Validate.cs @@ -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; - - } } } \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Write.cs similarity index 75% rename from SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs rename to SteamLibForked/Utility/MafileSerialization/MafileSerializer_Write.cs index dace869..31776b5 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs +++ b/SteamLibForked/Utility/MafileSerialization/MafileSerializer_Write.cs @@ -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 SerializeAsync(MobileData mobileData, Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null) + public static async Task 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) @@ -62,18 +61,19 @@ public static partial class MafileSerializer //Write result.SessionData = ext.SessionData == null ? null : new - { - AccessToken = ext.SessionData?.MobileToken?.Token, - steamLoginSecure = ext.SessionData?.MobileToken?.SignedToken, - RefreshToken = ext.SessionData?.RefreshToken.Token, - SteamID = ext.SessionData?.SteamId.Steam64.Id, - SessionID = ext.SessionData?.SessionId - }; + { + AccessToken = ext.SessionData?.MobileToken?.Token, + steamLoginSecure = ext.SessionData?.MobileToken?.SignedToken, + RefreshToken = ext.SessionData?.RefreshToken.Token, + SteamID = ext.SessionData?.SteamId.Steam64.Id, + SessionID = ext.SessionData?.SessionId + }; result.ServerTime = ext.ServerTime; result.SerialNumber = ext.SerialNumber.ToString(); result.Uri = ext.Uri; result.TokenGid = ext.TokenGid; result.Secret1 = ext.Secret1; + result.SteamId = ext.SteamId.Steam64.Id; } diff --git a/SteamLibForked/Utility/SteamIdParser.cs b/SteamLibForked/Utility/SteamIdParser.cs index d0a622a..14d2111 100644 --- a/SteamLibForked/Utility/SteamIdParser.cs +++ b/SteamLibForked/Utility/SteamIdParser.cs @@ -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); @@ -95,7 +107,7 @@ public static class SteamIdParser if (TryParse2(input, out var steam2)) { return new SteamId(steam2); - + } if (TryParse3(input, out var steam3)) @@ -144,7 +156,7 @@ public static class SteamIdParser var type = match3.Groups["type"].Value[0]; var id = int.Parse(match3.Groups["id"].Value); return new SteamId3(id, type); - + } else { diff --git a/SteamLibForked/Web/Converters/SteamIdConverters.cs b/SteamLibForked/Web/Converters/SteamIdConverters.cs index a0ada56..878c153 100644 --- a/SteamLibForked/Web/Converters/SteamIdConverters.cs +++ b/SteamLibForked/Web/Converters/SteamIdConverters.cs @@ -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 return SteamId.FromSteam64(l); } - var str = (string) reader.Value!; + var str = (string)reader.Value!; return new SteamId(SteamId64.Parse(str)); } } @@ -85,8 +85,8 @@ public class SteamId2ToStringConverter : JsonConverter public override SteamId2 ReadJson(JsonReader reader, Type objectType, SteamId2 existingValue, bool hasExistingValue, JsonSerializer serializer) { - var str = (string)reader.Value!; - return SteamId2.Parse(str); + var str = (string)reader.Value!; + return SteamId2.Parse(str); } } diff --git a/changelog/1.5.4.html b/changelog/1.5.4.html new file mode 100644 index 0000000..2121b04 --- /dev/null +++ b/changelog/1.5.4.html @@ -0,0 +1,80 @@ + + + + + + Changelog + + + + +
+ +
+
Version 1.5.4
+
DATE
+
+ - NEWS: Official Telegram group now available! Join us at t.me/nebulaauth
+ - FIX: TEMPORARY: Added workaround for old mafiles in MafileSerializer
+ +
+
+
+ + + \ No newline at end of file