using NebulaAuth.Model.Entities; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SteamLib; using SteamLib.Account; 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; namespace NebulaAuth.Model; public static class Storage { public const string MAFILE_F = "maFiles"; public const string REMOVED_F = "maFiles_removed"; public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F); public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F); public static ObservableCollection MaFiles { get; } = new(); public static readonly int DuplicateFound; static Storage() { if (Directory.Exists(MafileFolder) == false) { Directory.CreateDirectory(MafileFolder); } if (Directory.Exists(RemovedMafileFolder) == false) { Directory.CreateDirectory(RemovedMafileFolder); } var files = Directory.GetFiles(MafileFolder); var hashNames = new HashSet(); var hashIds = new HashSet(); var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName); var ordered = files.Order(comparer).ToList(); foreach (var file in ordered) { 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))) { DuplicateFound++; Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file)); continue; } hashNames.Add(data.AccountName); if (data.SessionData != null) hashIds.Add(data.SessionData.SteamId); MaFiles.Add(data); } catch (Exception ex) { Shell.Logger.Error(ex, "Can't load mafile {file}", Path.GetFileName(file)); } } MaFiles = new ObservableCollection(MaFiles.OrderBy(m => m.AccountName)); } public static void AddNewMafile(string path, bool overwrite) { Mafile data; try { data = ReadMafile(path); } catch (Exception ex) { 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"); try { var code = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret); } catch (Exception ex) { 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"); } SaveMafile(data); } public static Mafile ReadMafile(string path) { var str = File.ReadAllText(path); var mafile = MafileSerializer.Deserialize(str, 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; } } public static void SaveMafile(Mafile data) { var path = CreatePathForMafile(data); var str = SerializeMafile(data); File.WriteAllText(Path.GetFullPath(path), str); var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName); if (existed != null) { var index = MaFiles.IndexOf(existed); MaFiles[index] = data; } else { MaFiles.Add(data); } } public static void UpdateMafile(Mafile data) { var path = CreatePathForMafile(data); var str = 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 copyPathCompleted = copyPath; var i = 0; while (File.Exists(copyPathCompleted)) { i++; copyPathCompleted = copyPath + $" ({i})"; } File.Copy(path, copyPathCompleted, false); File.Delete(path); MaFiles.Remove(data); } 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); } return Path.Combine(MafileFolder, fileName); } private static string CreateFileNameWithAccountName(string accountName) => accountName + ".mafile"; 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)); } var steamIdExist = pathSteamId != null && File.Exists(pathSteamId); var accountNameExist = File.Exists(pathFileName); if (steamIdExist && accountNameExist) { return Settings.Instance.UseAccountNameAsMafileName ? pathFileName : pathSteamId; } if (steamIdExist ^ accountNameExist) { return steamIdExist ? pathSteamId : pathFileName; } return null; } public static bool ValidateCanSave(Mafile data) { return Settings.Instance.UseAccountNameAsMafileName || data.SessionData != null; } } internal class MafileNameComparer : IComparer { public bool MafileNameMode { get; } private const string MAF_64_START = "765"; private static readonly IComparer _defaultComparer = Comparer.Default; public MafileNameComparer(bool mafileNameMode) { MafileNameMode = mafileNameMode; } public int Compare(string? x, string? y) { if (x == null && y == null) return 0; if (x == null) return -1; if (y == null) return 1; bool xisSteamId = Path.GetFileName(x).StartsWith(MAF_64_START); bool yisSteamId = Path.GetFileName(y).StartsWith(MAF_64_START); if (xisSteamId ^ yisSteamId) { if (MafileNameMode) { return xisSteamId ? 1 : -1; } else { return yisSteamId ? 1 : -1; } } return _defaultComparer.Compare(x, y); } }