diff --git a/Directory.Packages.props b/Directory.Packages.props index 5e1d7238..b7c4445f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,7 @@ - + @@ -64,4 +64,4 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - \ No newline at end of file + diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/AppManifestParser.cs b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/AppManifestParser.cs index ee4e630c..56fff7ce 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/AppManifestParser.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/AppManifestParser.cs @@ -56,7 +56,7 @@ public static Result ParseManifestFile(AbsolutePath manifestPath) ); } - if (!appState.Name.Equals("AppState", StringComparison.Ordinal)) + if (!string.Equals(appState.Name, "AppState", StringComparison.Ordinal)) { return Result.Fail( new Error("Manifest file is potentially broken because the name doesn't match!") @@ -242,21 +242,21 @@ private static Result> ParseInstall ); } - private static Result ParseInstalledDepot(KVObject depotObject) + private static Result ParseInstalledDepot(KeyValuePair depotObject) { - if (!uint.TryParse(depotObject.Name, NumberFormatInfo.InvariantInfo, out var rawDepotId)) + if (!uint.TryParse(depotObject.Key, NumberFormatInfo.InvariantInfo, out var rawDepotId)) { return Result.Fail( new Error("Unable to parse Depot name as a 32-bit unsigned integer!") - .WithMetadata("OriginalName", depotObject.Name) + .WithMetadata("OriginalName", depotObject.Key) ); } var depotId = DepotId.From(rawDepotId); - var manifestIdResult = ParseRequiredChildObject(depotObject, "manifest", ParseManifestId); - var sizeOnDiskResult = ParseRequiredChildObject(depotObject, "size", ParseSize); - var dlcAppIdResult = ParseOptionalChildObject(depotObject, "dlcappid", ParseAppId, AppId.DefaultValue); + var manifestIdResult = ParseRequiredChildObject(depotObject.Value, "manifest", ParseManifestId); + var sizeOnDiskResult = ParseRequiredChildObject(depotObject.Value, "size", ParseSize); + var dlcAppIdResult = ParseOptionalChildObject(depotObject.Value, "dlcappid", ParseAppId, AppId.DefaultValue); var mergedResults = Result.Merge( manifestIdResult, diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LibraryFoldersManifestParser.cs b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LibraryFoldersManifestParser.cs index 356ce241..4a102fef 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LibraryFoldersManifestParser.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LibraryFoldersManifestParser.cs @@ -47,7 +47,7 @@ public static Result ParseManifestFile(AbsolutePath mani ); } - if (!data.Name.Equals("libraryfolders", StringComparison.Ordinal)) + if (!string.Equals(data.Name, "libraryfolders", StringComparison.Ordinal)) { return Result.Fail( new Error("Manifest file is potentially broken because the name doesn't match!") @@ -80,7 +80,7 @@ public static Result ParseManifestFile(AbsolutePath mani private static Result> ParseLibraryFolders(KVObject data, IFileSystem fileSystem) { var libraryFolderResults = data.Children - .Select(c => ParseLibraryFolder(c, fileSystem)) + .Select(c => ParseLibraryFolder(c.Value, fileSystem)) .ToArray(); return Result.Merge(libraryFolderResults).Bind(e => Result.Ok((IReadOnlyList)e.ToList())); diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LocalUserConfigParser.cs b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LocalUserConfigParser.cs index 23c52375..1c19f200 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LocalUserConfigParser.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/LocalUserConfigParser.cs @@ -49,7 +49,7 @@ public static Result ParseConfigFile(SteamId steamId, AbsoluteP ); } - if (!localConfigStore.Name.Equals("UserLocalConfigStore", StringComparison.Ordinal)) + if (!string.Equals(localConfigStore.Name, "UserLocalConfigStore", StringComparison.Ordinal)) { return Result.Fail( new Error("Config file is potentially broken because the name doesn't match!") @@ -125,21 +125,21 @@ private static Result> ParseLocalAppDat ); } - private static Result ParseSingleLocalAppData(KVObject appObject) + private static Result ParseSingleLocalAppData(KeyValuePair appObject) { - if (!uint.TryParse(appObject.Name, NumberFormatInfo.InvariantInfo, out var rawAppId)) + if (!uint.TryParse(appObject.Key, NumberFormatInfo.InvariantInfo, out var rawAppId)) { return Result.Fail( new Error("Unable to parse AppId as a 32-bit unsigned integer!") - .WithMetadata("OriginalName", appObject.Name) + .WithMetadata("OriginalName", appObject.Key) ); } var appId = AppId.From(rawAppId); - var lastPlayedResult = ParseOptionalChildObject(appObject, "LastPlayed", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); - var playtimeResult = ParseOptionalChildObject(appObject, "Playtime", ParseUInt32, default).Map(x => TimeSpan.FromMinutes(x)); - var launchOptionsResult = ParseOptionalChildObject(appObject, "LaunchOptions", ParseString, string.Empty); + var lastPlayedResult = ParseOptionalChildObject(appObject.Value, "LastPlayed", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); + var playtimeResult = ParseOptionalChildObject(appObject.Value, "Playtime", ParseUInt32, default).Map(x => TimeSpan.FromMinutes(x)); + var launchOptionsResult = ParseOptionalChildObject(appObject.Value, "LaunchOptions", ParseString, string.Empty); var mergedResults = Result.Merge( lastPlayedResult, diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/ParserHelpers.cs b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/ParserHelpers.cs index a13ffe04..43216a2a 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/ParserHelpers.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/ParserHelpers.cs @@ -22,7 +22,7 @@ internal static Result> ParseBasicDictionary keyParser, - Func valueParser, + Func valueParser, IEqualityComparer? equalityComparer = null) where TKey : notnull { @@ -35,9 +35,9 @@ internal static Result> ParseBasicDictionary>>(childObject => + .Select, Result>>(childObject => { - var keyResult = Result.Try(() => keyParser(childObject.Name)); + var keyResult = Result.Try(() => keyParser(childObject.Key)); var valueResult = ParseValue(childObject.Value, valueParser); var mergedResult = Result.Merge( @@ -56,7 +56,7 @@ internal static Result> ParseBasicDictionary ParseValue(KVValue value, Func parser) + private static Result ParseValue(KVObject value, Func parser) { return Result.Try( () => parser(value), @@ -67,13 +67,12 @@ private static Result ParseValue(KVValue value, Func parser) internal static Result ParseChildObjectValue( KVObject childObject, KVObject parentObject, - Func parser) + Func parser) { return Result.Try( - () => parser(childObject.Value), + () => parser(childObject), ex => new ExceptionalError("Unable to parse value of child object!", ex) - .WithMetadata("ChildObjectName", childObject.Name) - .WithMetadata("ParentObjectName", parentObject.Name) + .WithMetadata("ParentObject", parentObject.ToString()) ); } @@ -83,20 +82,20 @@ internal static Result ParseChildObjectValue( var childObject = parentObject .Children - .FirstOrDefault(child => child.Name.Equals(childObjectName, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(child => child.Key.Equals(childObjectName, StringComparison.OrdinalIgnoreCase)); - if (childObject is null && Debugger.IsLogging()) + if (childObject.Key is null && Debugger.IsLogging()) { - Debugger.Log(0, Debugger.DefaultCategory, $"Optional child object {childObjectName} was not found in {parentObject.Name}"); + Debugger.Log(0, Debugger.DefaultCategory, $"Optional child object {childObjectName} was not found."); } - return childObject; + return childObject.Key is null ? null : childObject.Value; } internal static Result ParseOptionalChildObject( KVObject parentObject, string childObjectName, - Func parser, + Func parser, T defaultValue) { var childObject = FindOptionalChildObject(parentObject, childObjectName); @@ -114,7 +113,7 @@ internal static Result FindRequiredChildObject(KVObject parentObject, return Result.Fail( new Error("Unable to find required child object by name in parent!") .WithMetadata("ChildObjectName", childObjectName) - .WithMetadata("ParentObjectName", parentObject.Name) + .WithMetadata("ParentObject", parentObject.ToString()) ); } @@ -124,7 +123,7 @@ internal static Result FindRequiredChildObject(KVObject parentObject, internal static Result ParseRequiredChildObject( KVObject parentObject, string childObjectName, - Func parser) + Func parser) { var childObjectResult = FindRequiredChildObject(parentObject, childObjectName); return childObjectResult.Bind(childObject => ParseChildObjectValue(childObject, parentObject, parser)); @@ -135,9 +134,9 @@ internal static Result ParseRequiredChildObject( #region Type Parser [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static byte ParseByte(KVValue value) => byte.Parse(ParseString(value), CultureInfo.InvariantCulture); + internal static byte ParseByte(KVObject value) => byte.Parse(ParseString(value), CultureInfo.InvariantCulture); - internal static bool ParseBool(KVValue value) + internal static bool ParseBool(KVObject value) { var s = ParseString(value); if (string.Equals(s, "0", StringComparison.Ordinal)) return false; @@ -146,43 +145,43 @@ internal static bool ParseBool(KVValue value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static uint ParseUInt32(KVValue value) => uint.Parse(ParseString(value), CultureInfo.InvariantCulture); + internal static uint ParseUInt32(KVObject value) => uint.Parse(ParseString(value), CultureInfo.InvariantCulture); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ulong ParseUInt64(KVValue value) => ulong.Parse(ParseString(value), CultureInfo.InvariantCulture); + internal static ulong ParseUInt64(KVObject value) => ulong.Parse(ParseString(value), CultureInfo.InvariantCulture); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string ParseString(KVValue value) => value.ToString(CultureInfo.InvariantCulture); + internal static string ParseString(KVObject value) => value.ToString(CultureInfo.InvariantCulture); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static DateTimeOffset ParseDateTimeOffset(KVValue value) => DateTimeOffset.FromUnixTimeSeconds(ParseUInt32(value)); + internal static DateTimeOffset ParseDateTimeOffset(KVObject value) => DateTimeOffset.FromUnixTimeSeconds(ParseUInt32(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static SteamId ParseSteamId(KVValue value) => SteamId.From(ParseUInt64(value)); + internal static SteamId ParseSteamId(KVObject value) => SteamId.From(ParseUInt64(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static AppId ParseAppId(KVValue value) => AppId.From(ParseUInt32(value)); + internal static AppId ParseAppId(KVObject value) => AppId.From(ParseUInt32(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static BuildId ParseBuildId(KVValue value) => BuildId.From(ParseUInt32(value)); + internal static BuildId ParseBuildId(KVObject value) => BuildId.From(ParseUInt32(value)); // [MethodImpl(MethodImplOptions.AggressiveInlining)] // internal static DepotId ParseDepotId(KVValue value) => DepotId.From(ParseUInt32(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ManifestId ParseManifestId(KVValue value) => ManifestId.From(ParseUInt64(value)); + internal static ManifestId ParseManifestId(KVObject value) => ManifestId.From(ParseUInt64(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static WorkshopManifestId ParseWorkshopManifestId(KVValue value) => WorkshopManifestId.From(ParseUInt64(value)); + internal static WorkshopManifestId ParseWorkshopManifestId(KVObject value) => WorkshopManifestId.From(ParseUInt64(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Size ParseSize(KVValue value) => Size.From(ParseUInt64(value)); + internal static Size ParseSize(KVObject value) => Size.From(ParseUInt64(value)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static RelativePath ParseRelativePath(KVValue value) => ParseString(value); + internal static RelativePath ParseRelativePath(KVObject value) => ParseString(value); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static AbsolutePath ParseAbsolutePath(KVValue value, IFileSystem fileSystem) => fileSystem.FromUnsanitizedFullPath(ParseString(value)); + internal static AbsolutePath ParseAbsolutePath(KVObject value, IFileSystem fileSystem) => fileSystem.FromUnsanitizedFullPath(ParseString(value)); #endregion } diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/WorkshopManifestParser.cs b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/WorkshopManifestParser.cs index 6d6bc4dc..bf849c61 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/WorkshopManifestParser.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/WorkshopManifestParser.cs @@ -47,7 +47,7 @@ public static Result ParseManifestFile(AbsolutePath manifestPa ); } - if (!appWorkshop.Name.Equals("AppWorkshop", StringComparison.Ordinal)) + if (!string.Equals(appWorkshop.Name, "AppWorkshop", StringComparison.Ordinal)) { return Result.Fail( new Error("Manifest file is potentially broken because the name doesn't match!") @@ -139,21 +139,21 @@ private static Result> ); } - private static Result ParseInstalledWorkshopItem(KVObject installedWorkshopItemObject) + private static Result ParseInstalledWorkshopItem(KeyValuePair installedWorkshopItemObject) { - if (!ulong.TryParse(installedWorkshopItemObject.Name, NumberFormatInfo.InvariantInfo, out var rawWorkshopItemId)) + if (!ulong.TryParse(installedWorkshopItemObject.Key, NumberFormatInfo.InvariantInfo, out var rawWorkshopItemId)) { return Result.Fail( new Error("Unable to parse WorkshopItem name as a 64-bit unsigned integer!") - .WithMetadata("OriginalName", installedWorkshopItemObject.Name) + .WithMetadata("OriginalName", installedWorkshopItemObject.Key) ); } var workshopItemId = WorkshopItemId.From(rawWorkshopItemId); - var sizeOnDiskResult = ParseRequiredChildObject(installedWorkshopItemObject, "size", ParseSize); - var lastUpdatedResult = ParseOptionalChildObject(installedWorkshopItemObject, "timeupdated", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); - var manifestResult = ParseRequiredChildObject(installedWorkshopItemObject, "manifest", ParseWorkshopManifestId); + var sizeOnDiskResult = ParseRequiredChildObject(installedWorkshopItemObject.Value, "size", ParseSize); + var lastUpdatedResult = ParseOptionalChildObject(installedWorkshopItemObject.Value, "timeupdated", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); + var manifestResult = ParseRequiredChildObject(installedWorkshopItemObject.Value, "manifest", ParseWorkshopManifestId); var mergedResults = Result.Merge( sizeOnDiskResult, @@ -175,23 +175,23 @@ private static Result ParseInstalledWorkshopItem(KVObject i } private static Result ParseWorkshopItemDetails( - KVObject workshopItemDetailObject, + KeyValuePair workshopItemDetailObject, IEnumerable installedWorkshopItems) { - if (!ulong.TryParse(workshopItemDetailObject.Name, NumberFormatInfo.InvariantInfo, out var rawWorkshopItemId)) + if (!ulong.TryParse(workshopItemDetailObject.Key, NumberFormatInfo.InvariantInfo, out var rawWorkshopItemId)) { return Result.Fail( new Error("Unable to parse WorkshopItem name as a 64-bit unsigned integer!") - .WithMetadata("OriginalName", workshopItemDetailObject.Name) + .WithMetadata("OriginalName", workshopItemDetailObject.Key) ); } var workshopItemId = WorkshopItemId.From(rawWorkshopItemId); - var manifestResult = ParseRequiredChildObject(workshopItemDetailObject, "manifest", ParseWorkshopManifestId); - var lastUpdatedResult = ParseOptionalChildObject(workshopItemDetailObject, "timeupdated", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); - var lastTouchedResult = ParseOptionalChildObject(workshopItemDetailObject, "timetouched", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); - var subscribedByResult = ParseOptionalChildObject(workshopItemDetailObject, "subscribedby", ParseUInt32, default).Map(x => SteamId.FromAccountId(x)); + var manifestResult = ParseRequiredChildObject(workshopItemDetailObject.Value, "manifest", ParseWorkshopManifestId); + var lastUpdatedResult = ParseOptionalChildObject(workshopItemDetailObject.Value, "timeupdated", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); + var lastTouchedResult = ParseOptionalChildObject(workshopItemDetailObject.Value, "timetouched", ParseDateTimeOffset, DateTimeOffset.UnixEpoch); + var subscribedByResult = ParseOptionalChildObject(workshopItemDetailObject.Value, "subscribedby", ParseUInt32, default).Map(x => SteamId.FromAccountId(x)); var mergedResults = Result.Merge( manifestResult, diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Writers/AppManifestWriter.cs b/src/GameFinder.StoreHandlers.Steam/Services/Writers/AppManifestWriter.cs index 18b6dcdb..23ca8e0d 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Writers/AppManifestWriter.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Writers/AppManifestWriter.cs @@ -22,7 +22,7 @@ public static class AppManifestWriter /// public static Result Write(AppManifest manifest, AbsolutePath outputPath) { - var values = new List(); + var values = KVObject.ListCollection(); values.AddValue("appid", manifest.AppId, AppId.DefaultValue); values.AddValue("Universe", (byte)manifest.Universe, -1); values.AddValue("name", manifest.Name, string.Empty); @@ -46,21 +46,20 @@ public static Result Write(AppManifest manifest, AbsolutePath outputPath) if (manifest.InstalledDepots.Count != 0) { - var children = new List(); + var children = KVObject.ListCollection(); foreach (var kv in manifest.InstalledDepots) { var (depotId, installedDepot) = kv; - var objValues = new List(); + var objValues = KVObject.ListCollection(); objValues.AddValue("manifest", installedDepot.ManifestId, ManifestId.DefaultValue); objValues.AddValue("size", installedDepot.SizeOnDisk.Value, default); objValues.AddValue("dlcappid", installedDepot.DLCAppId, AppId.DefaultValue); - var obj = new KVObject(depotId.ToString(), objValues); - children.Add(obj); + children.Add(depotId.ToString(), objValues); } - values.Add(new KVObject("InstalledDepots", children)); + values.Add("InstalledDepots", children); } values.AddDictionary("InstallScripts", manifest.InstallScripts, RelativePath.Empty); @@ -68,13 +67,11 @@ public static Result Write(AppManifest manifest, AbsolutePath outputPath) values.AddDictionary("UserConfig", manifest.UserConfig, string.Empty); values.AddDictionary("MountedConfig", manifest.MountedConfig, string.Empty); - var data = new KVObject("AppState", values); - try { var serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Text); using var stream = outputPath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None); - serializer.Serialize(stream, data); + serializer.Serialize(stream, values, "AppState", KVSerializerOptions.DefaultOptions); } catch (Exception e) { diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Writers/LibraryFoldersManifestWriter.cs b/src/GameFinder.StoreHandlers.Steam/Services/Writers/LibraryFoldersManifestWriter.cs index 4c05ca37..0babf399 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Writers/LibraryFoldersManifestWriter.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Writers/LibraryFoldersManifestWriter.cs @@ -24,12 +24,12 @@ public static class LibraryFoldersManifestWriter /// public static Result Write(LibraryFoldersManifest manifest, AbsolutePath outputPath) { - var values = new List(); + var values = KVObject.ListCollection(); for (var i = 0; i < manifest.Count; i++) { var libraryFolder = manifest[i]; - var children = new List(); + var children = KVObject.ListCollection(); children.AddValue("path", libraryFolder.Path.ToString(), string.Empty); children.AddValue("label", libraryFolder.Label, string.Empty); children.AddValue("totalsize", libraryFolder.TotalDiskSize.Value, default); @@ -39,16 +39,14 @@ public static Result Write(LibraryFoldersManifest manifest, AbsolutePath outputP default ); - values.Add(new KVObject($"{i.ToString(CultureInfo.InvariantCulture)}", children)); + values.Add(i.ToString(CultureInfo.InvariantCulture), children); } - var data = new KVObject("libraryfolders", values); - try { var serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Text); using var stream = outputPath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None); - serializer.Serialize(stream, data); + serializer.Serialize(stream, values, "libraryfolders", KVSerializerOptions.DefaultOptions); } catch (Exception e) { diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Writers/LocalUserConfigWriter.cs b/src/GameFinder.StoreHandlers.Steam/Services/Writers/LocalUserConfigWriter.cs index ffa3e3ff..d598f0c0 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Writers/LocalUserConfigWriter.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Writers/LocalUserConfigWriter.cs @@ -23,46 +23,43 @@ public static class LocalUserConfigWriter /// public static Result Write(LocalUserConfig config, AbsolutePath outputPath) { - var values = new List(); + var values = KVObject.ListCollection(); var appDataObjects = config.LocalAppData.Select(x => { var (appId, data) = x; - var innerValues = new List(); + var innerValues = KVObject.ListCollection(); innerValues.AddValue("LastPlayed", data.LastPlayed.ToUnixTimeSeconds(), default); innerValues.AddValue("Playtime", (int)data.Playtime.TotalMinutes, default); innerValues.AddValue("LaunchOptions", data.LaunchOptions, string.Empty); - return new KVObject(appId.Value.ToString(CultureInfo.InvariantCulture), innerValues); + return new KeyValuePair(appId.Value.ToString(CultureInfo.InvariantCulture), innerValues); }); - values.Add(new KVObject("Software", new[] - { - new KVObject("Valve", new [] - { - new KVObject("Steam", new [] - { - new KVObject("apps", appDataObjects), - }), - }), - })); + var apps = KVObject.ListCollection(appDataObjects); + var steam = KVObject.ListCollection(); + steam.Add("apps", apps); - var systemObjects = new List(); - systemObjects.AddValue("InGameOverlayScreenshotSaveUncompressedPath", config.InGameOverlayScreenshotSaveUncompressedPath?.ToString() ?? string.Empty, string.Empty); + var valve = KVObject.ListCollection(); + valve.Add("Steam", steam); - values.Add(new KVObject("WebStorage", new[] - { - new KVObject("system", systemObjects), - })); + var software = KVObject.ListCollection(); + software.Add("Valve", valve); + values.Add("Software", software); + + var systemObjects = KVObject.ListCollection(); + systemObjects.AddValue("InGameOverlayScreenshotSaveUncompressedPath", config.InGameOverlayScreenshotSaveUncompressedPath?.ToString() ?? string.Empty, string.Empty); - var data = new KVObject("UserLocalConfigStore", values); + var webStorage = KVObject.ListCollection(); + webStorage.Add("system", systemObjects); + values.Add("WebStorage", webStorage); try { var serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Text); using var stream = outputPath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None); - serializer.Serialize(stream, data); + serializer.Serialize(stream, values, "UserLocalConfigStore", KVSerializerOptions.DefaultOptions); } catch (Exception e) { diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Writers/WorkshopManifestWriter.cs b/src/GameFinder.StoreHandlers.Steam/Services/Writers/WorkshopManifestWriter.cs index b3a801e8..ab5bde81 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Writers/WorkshopManifestWriter.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Writers/WorkshopManifestWriter.cs @@ -22,7 +22,7 @@ public class WorkshopManifestWriter /// public static Result Write(WorkshopManifest manifest, AbsolutePath outputPath) { - var values = new List(); + var values = KVObject.ListCollection(); values.AddValue("appid", manifest.AppId, AppId.DefaultValue); values.AddValue("SizeOnDisk", manifest.SizeOnDisk.Value, default); values.AddValue("NeedsUpdate", manifest.NeedsUpdate ? "1" : "0", string.Empty); @@ -32,39 +32,37 @@ public static Result Write(WorkshopManifest manifest, AbsolutePath outputPath) if (manifest.InstalledWorkshopItems.Count != 0) { - var workshopItemsInstalledChildren = new List(); - var workshopItemDetailsChildren = new List(); + var workshopItemsInstalledChildren = KVObject.ListCollection(); + var workshopItemDetailsChildren = KVObject.ListCollection(); foreach (var kv in manifest.InstalledWorkshopItems) { var (workshopItemId, workshopItemDetails) = kv; - var workshopItemInstalledValues = new List(); + var workshopItemInstalledValues = KVObject.ListCollection(); workshopItemInstalledValues.AddValue("size", workshopItemDetails.SizeOnDisk.Value, default); workshopItemInstalledValues.AddValue("timeupdated", workshopItemDetails.LastUpdated.ToUnixTimeSeconds(), default); workshopItemInstalledValues.AddValue("manifest", workshopItemDetails.ManifestId, WorkshopManifestId.DefaultValue); - var workshopItemDetailsValues = new List(); + var workshopItemDetailsValues = KVObject.ListCollection(); workshopItemDetailsValues.AddValue("manifest", workshopItemDetails.ManifestId, WorkshopManifestId.DefaultValue); workshopItemDetailsValues.AddValue("timeupdated", workshopItemDetails.LastUpdated.ToUnixTimeSeconds(), default); workshopItemDetailsValues.AddValue("timetouched", workshopItemDetails.LastTouched.ToUnixTimeSeconds(), default); workshopItemDetailsValues.AddValue("subscribedby", workshopItemDetails.SubscribedBy.AccountId, default); - workshopItemsInstalledChildren.Add(new KVObject(workshopItemId.ToString(), workshopItemInstalledValues)); - workshopItemDetailsChildren.Add(new KVObject(workshopItemId.ToString(), workshopItemDetailsValues)); + workshopItemsInstalledChildren.Add(workshopItemId.ToString(), workshopItemInstalledValues); + workshopItemDetailsChildren.Add(workshopItemId.ToString(), workshopItemDetailsValues); } - values.Add(new KVObject("WorkshopItemsInstalled", workshopItemsInstalledChildren)); - values.Add(new KVObject("WorkshopItemDetails", workshopItemDetailsChildren)); + values.Add("WorkshopItemsInstalled", workshopItemsInstalledChildren); + values.Add("WorkshopItemDetails", workshopItemDetailsChildren); } - var data = new KVObject("AppWorkshop", values); - try { var serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Text); using var stream = outputPath.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.None); - serializer.Serialize(stream, data); + serializer.Serialize(stream, values, "AppWorkshop", KVSerializerOptions.DefaultOptions); } catch (Exception e) { diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Writers/WriterHelpers.cs b/src/GameFinder.StoreHandlers.Steam/Services/Writers/WriterHelpers.cs index 3ec55fd0..3d7dc974 100644 --- a/src/GameFinder.StoreHandlers.Steam/Services/Writers/WriterHelpers.cs +++ b/src/GameFinder.StoreHandlers.Steam/Services/Writers/WriterHelpers.cs @@ -1,70 +1,41 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Globalization; using ValveKeyValue; namespace GameCollector.StoreHandlers.Steam.Services; internal static class WriterHelpers { - internal static void AddValue(this List list, string name, TValue value, TValue defaultValue) + internal static void AddValue(this KVObject collection, string name, TValue value, TValue defaultValue) where TValue : notnull { if (value.Equals(defaultValue)) return; - list.Add(new KVObject(name, new StringValue(value))); + collection.Add(name, ConvertToString(value)); } - internal static void AddDictionary(this List list, string name, IReadOnlyDictionary dictionary, TValue defaultValue) + internal static void AddDictionary(this KVObject collection, string name, IReadOnlyDictionary dictionary, TValue defaultValue) where TKey : notnull where TValue : notnull { if (dictionary.Count == 0) return; - var children = new List(); + var children = KVObject.ListCollection(); foreach (var kv in dictionary) { children.AddValue(kv.Key.ToString()!, kv.Value, defaultValue); } - list.Add(new KVObject(name, children)); + collection.Add(name, children); } - [ExcludeFromCodeCoverage] - private class StringValue : KVValue + private static string ConvertToString(TValue value) { - private readonly string _value; + if (value is null) throw new ArgumentNullException(nameof(value)); - public StringValue(string value) - { - _value = value; - } - - public StringValue(object obj) - { - _value = obj.ToString() ?? throw new ArgumentException($"Doesn't have a ToString: {obj.GetType()}", nameof(obj)); - } - - public override string ToString() => _value; - - public override KVValueType ValueType => KVValueType.String; - public override TypeCode GetTypeCode() => TypeCode.String; - - public override bool ToBoolean(IFormatProvider? provider) => Convert.ToBoolean(_value, provider); - public override byte ToByte(IFormatProvider? provider) => Convert.ToByte(_value, provider); - public override char ToChar(IFormatProvider? provider) => Convert.ToChar(_value, provider); - public override DateTime ToDateTime(IFormatProvider? provider) => Convert.ToDateTime(_value, provider); - public override decimal ToDecimal(IFormatProvider? provider) => Convert.ToDecimal(_value, provider); - public override double ToDouble(IFormatProvider? provider) => Convert.ToDouble(_value, provider); - public override short ToInt16(IFormatProvider? provider) => Convert.ToInt16(_value, provider); - public override int ToInt32(IFormatProvider? provider) => Convert.ToInt32(_value, provider); - public override long ToInt64(IFormatProvider? provider) => Convert.ToInt64(_value, provider); - public override sbyte ToSByte(IFormatProvider? provider) => Convert.ToSByte(_value, provider); - public override float ToSingle(IFormatProvider? provider) => Convert.ToSingle(_value, provider); - public override string ToString(IFormatProvider? provider) => Convert.ToString(_value, provider); - public override object ToType(Type conversionType, IFormatProvider? provider) => throw new NotSupportedException(); - public override ushort ToUInt16(IFormatProvider? provider) => Convert.ToUInt16(_value, provider); - public override uint ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(_value, provider); - public override ulong ToUInt64(IFormatProvider? provider) => Convert.ToUInt64(_value, provider); + return value is IFormattable formattable + ? formattable.ToString(null, CultureInfo.InvariantCulture) + : value.ToString() ?? throw new ArgumentException($"Doesn't have a ToString: {typeof(TValue)}", nameof(value)); } } diff --git a/src/GameFinder.StoreHandlers.Steam/SteamAPI.cs b/src/GameFinder.StoreHandlers.Steam/SteamAPI.cs index af155f33..763b41eb 100644 --- a/src/GameFinder.StoreHandlers.Steam/SteamAPI.cs +++ b/src/GameFinder.StoreHandlers.Steam/SteamAPI.cs @@ -148,7 +148,7 @@ private static AbsolutePath GetLoginUsersFile(AbsolutePath steamDirectory) List<(ulong, uint, bool)> userList = new(); try { - KVValue autoUser = ""; + KVObject autoUser = ""; if (!SteamLocationFinder.TryFindSteam(_fileSystem, _registry, _logger, out var steamPathResult)) { @@ -188,7 +188,7 @@ private static AbsolutePath GetLoginUsersFile(AbsolutePath steamDirectory) var data = kv.Deserialize(stream, KvSerializerOptions); if (data is null) return null; - if (!data.Name.Equals("SteamAppData", StringComparison.OrdinalIgnoreCase)) return null; + if (!string.Equals(data.Name, "SteamAppData", StringComparison.OrdinalIgnoreCase)) return null; autoUser = data["AutoLoginUser"]; } @@ -199,12 +199,12 @@ private static AbsolutePath GetLoginUsersFile(AbsolutePath steamDirectory) var data = kv.Deserialize(stream, KvSerializerOptions); if (data is null) return null; - if (!data.Name.Equals("users", StringComparison.OrdinalIgnoreCase)) return null; + if (!string.Equals(data.Name, "users", StringComparison.OrdinalIgnoreCase)) return null; - var users = data.Children.ToList(); + var users = data.Root.Children.ToList(); foreach (var user in users) { - if (!ulong.TryParse(user.Name, out var id)) + if (!ulong.TryParse(user.Key, out var id)) continue; var auto = false;