From 837e0a78151b79850dbaa1c3f9b56fc1cb8a00c3 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Sat, 21 Sep 2024 02:33:53 -0700 Subject: [PATCH 1/8] Add experimental handler for Windows Package Manager (winget) --- CHANGELOG.md | 6 + Directory.Packages.props | 13 +- GameCollector.sln | 10 + README.md | 11 +- codecov.yml | 4 + .../GameFinder.Example.csproj | 4 + other/GameFinder.Example/Options.cs | 3 + other/GameFinder.Example/Program.cs | 18 +- src/Directory.Build.props | 2 +- .../GameCollector.PkgHandlers.Winget.csproj | 19 + .../WingetGame.cs | 83 ++ .../WingetGameId.cs | 50 ++ .../WingetHandler.cs | 774 ++++++++++++++++++ src/GameFinder.Common/Handler.cs | 5 + src/GameFinder/GameFinder.csproj | 1 + 15 files changed, 994 insertions(+), 9 deletions(-) create mode 100644 src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj create mode 100644 src/GameCollector.PkgHandlers.Winget/WingetGame.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WingetGameId.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WingetHandler.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a25c13..90f4c560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [ - Add [DataHandlers](https://github.com/Nutzzz/GameCollector/pull/13) to download metadata and images from online sources +## [4.2.4-2](https://github.com/Nutzzz/GameCollector/compare/v4.2.4.1...v4.2.4.2) - UNRELEASED + +### Added + +- Add Winget package handler to detect both installed apps and show free game packages + ## [Released](https://github.com/Nutzzz/GameCollector/releases) ## [4.2.4-1](https://github.com/Nutzzz/GameCollector/compare/v4.2.3.2...v4.2.4.1) - 2024-07-08 diff --git a/Directory.Packages.props b/Directory.Packages.props index 52373ea6..94c5b638 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,16 +15,25 @@ - + + + + + + + + + + - + diff --git a/GameCollector.sln b/GameCollector.sln index c3ec654c..4a86d154 100644 --- a/GameCollector.sln +++ b/GameCollector.sln @@ -134,6 +134,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.EmuHandlers.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.EmuHandlers.MAME", "src\GameCollector.EmuHandlers.MAME\GameCollector.EmuHandlers.MAME.csproj", "{EA0A79A3-639B-4983-A28F-1D07EC1113C7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PkgHandlers", "PkgHandlers", "{55345886-D1F6-4733-9943-1283AF39CEA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.PkgHandlers.Winget", "src\GameCollector.PkgHandlers.Winget\GameCollector.PkgHandlers.Winget.csproj", "{7C74842C-69FE-4908-BA71-210652A15744}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataHandlers", "DataHandlers", "{82816E34-196A-4304-8A28-BF22FA937DF0}" EndProject Global @@ -318,6 +322,10 @@ Global {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Release|Any CPU.Build.0 = Release|Any CPU + {7C74842C-69FE-4908-BA71-210652A15744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C74842C-69FE-4908-BA71-210652A15744}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C74842C-69FE-4908-BA71-210652A15744}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C74842C-69FE-4908-BA71-210652A15744}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -371,6 +379,8 @@ Global {1F5F230D-146C-4794-A2E6-F9C13279D721} = {EFB4E7DB-DE24-4BDE-B660-DAF1394ECC21} {6173D1F4-48C2-4B14-B907-C7A746CF081D} = {1F5F230D-146C-4794-A2E6-F9C13279D721} {EA0A79A3-639B-4983-A28F-1D07EC1113C7} = {1F5F230D-146C-4794-A2E6-F9C13279D721} + {55345886-D1F6-4733-9943-1283AF39CEA5} = {EFB4E7DB-DE24-4BDE-B660-DAF1394ECC21} + {7C74842C-69FE-4908-BA71-210652A15744} = {55345886-D1F6-4733-9943-1283AF39CEA5} {82816E34-196A-4304-8A28-BF22FA937DF0} = {EFB4E7DB-DE24-4BDE-B660-DAF1394ECC21} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/README.md b/README.md index ea77af59..3dd28709 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ .NET library for finding games. GameCollector expands on upstream GameFinder (which is primarily designed to support modding tools), by adding additional supported store launchers, emulators, and data sources, and includes additional information about each game (sufficient for a multi-store game launcher such as [GLC](https://github.com/Solaire/GLC)). This fork is indebted to erri120's hard work, but as [he is reticent](https://github.com/erri120/GameFinder/issues/49) to expand the scope of GameFinder, GameCollector continues with a different philosophy. -The following launchers and emulators are supported: +The following launchers, emulators, and package managers are supported: | handler | package | | -- | -- | @@ -35,6 +35,7 @@ The following launchers and emulators are supported: | [Steam](#steam) | [![Nuget](https://img.shields.io/nuget/v/GameCollector.StoreHandlers.Steam)](https://www.nuget.org/packages/GameCollector.StoreHandlers.Steam) [![Nuget](https://img.shields.io/nuget/v/GameFinder.StoreHandlers.Steam?color=red&label=upstream)](https://www.nuget.org/packages/GameFinder.StoreHandlers.Steam) | | Ubisoft Connect | [![Nuget](https://img.shields.io/nuget/v/GameCollector.StoreHandlers.Ubisoft)](https://www.nuget.org/packages/GameCollector.StoreHandlers.Ubisoft) | | Wargaming.net Game Center | [![Nuget](https://img.shields.io/nuget/v/GameCollector.StoreHandlers.WargamingNet)](https://www.nuget.org/packages/GameCollector.StoreHandlers.WargamingNet) | +| Windows Package Manager | [![Nuget](https://img.shields.io/nuget/v/GameCollector.PkgHandlers.Winget)](https://www.nuget.org/packages/GameCollector.PkgHandlers.Winget) | | [Xbox Game Pass](#xbox-game-pass) | [![Nuget](https://img.shields.io/nuget/v/GameCollector.StoreHandlers.Xbox)](https://www.nuget.org/packages/GameCollector.StoreHandlers.Xbox) [![Nuget](https://img.shields.io/nuget/v/GameFinder.StoreHandlers.Xbox?color=red&label=upstream)](https://www.nuget.org/packages/GameFinder.StoreHandlers.Xbox) | If you are interested in understanding _how_ GameCollector/GameFinder finds these games, check [the upstream wiki](https://github.com/erri120/GameFinder/wiki) for more information (descriptions of the added handlers should eventually be added to a wiki here). @@ -97,6 +98,12 @@ The TGame implementations of GameCollector's handlers inherit a generic GameData The Metadata dictionary may include (depending on available information): "ReleaseDate", "Description", "Developers", "Publishers", "Genres", "ImageUrl", etc. +### Supported Package Managers + +This is a new category of handler for GameCollector. It is Windows-only for now. + +- Winget + ### Supported Emulators This is a new category of handler for GameCollector. They are Windows-only for now. @@ -417,7 +424,7 @@ Self-contained deployments and executables can be [trimmed](https://learn.micros **NOT Trimmable**: -- `GameCollector.StoreHandlers.EADesktop`: This package references `System.Management`, which is **not trimmable** due to COM interop issues. See [dotnet/runtime#78038](https://github.com/dotnet/runtime/issues/78038), [dotnet/runtime#75176](https://github.com/dotnet/runtime/pull/75176) and [dotnet/runtime#61960](https://github.com/dotnet/runtime/issues/61960) for more details. +- `GameCollector.StoreHandlers.EADesktop` and `GameCollector.PkgHandlers.Winget`: These packages reference `System.Management`, which is **not trimmable** due to COM interop issues. See [dotnet/runtime#78038](https://github.com/dotnet/runtime/issues/78038), [dotnet/runtime#75176](https://github.com/dotnet/runtime/pull/75176) and [dotnet/runtime#61960](https://github.com/dotnet/runtime/issues/61960) for more details. I recommend looking at the [project file](./other/GameFinder.Example/GameFinder.Example.csproj) of the example project, if you run into warnings or errors with trimming. diff --git a/codecov.yml b/codecov.yml index 95a801a0..db5c0e8a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -132,6 +132,10 @@ component_management: name: Wargaming.net Game Center paths: - "src/GameCollector.StoreHandlers.WargamingNet/**" + - component_id: pkg_handler_winget + name: Windows Package Manager + paths: + - "src/GameCollector.PkgHandlers.Winget/**" - component_id: emu_handler_dolphin name: Dolphin Emulator paths: diff --git a/other/GameFinder.Example/GameFinder.Example.csproj b/other/GameFinder.Example/GameFinder.Example.csproj index fbb55ba2..b24db6ab 100644 --- a/other/GameFinder.Example/GameFinder.Example.csproj +++ b/other/GameFinder.Example/GameFinder.Example.csproj @@ -51,6 +51,10 @@ + + + + diff --git a/other/GameFinder.Example/Options.cs b/other/GameFinder.Example/Options.cs index 0d9cdc45..773f3d05 100644 --- a/other/GameFinder.Example/Options.cs +++ b/other/GameFinder.Example/Options.cs @@ -112,6 +112,9 @@ public class Options [Option("wargaming", HelpText = "Search for Wargaming.Net Game Center games", Hidden = true, Group = "Handlers")] public bool Wargaming { get; set; } = false; + [Option("winget", HelpText = "Search for Windows Package Manager packages", Hidden = true, Group = "Handlers")] + public bool Winget { get; set; } = false; + [Option("xbox", HelpText = "Search for Xbox Games Pass games", Hidden = true, Group = "Handlers")] public bool Xbox { get; set; } = false; diff --git a/other/GameFinder.Example/Program.cs b/other/GameFinder.Example/Program.cs index 7ed8f6ee..945dc969 100644 --- a/other/GameFinder.Example/Program.cs +++ b/other/GameFinder.Example/Program.cs @@ -36,6 +36,7 @@ using GameCollector.StoreHandlers.Rockstar; using GameCollector.StoreHandlers.Ubisoft; using GameCollector.StoreHandlers.WargamingNet; +using GameCollector.PkgHandlers.Winget; using GameCollector.EmuHandlers.Dolphin; using GameCollector.EmuHandlers.MAME; //using GameCollector.DataHandlers.TheGamesDb; @@ -114,13 +115,13 @@ private async static void Run(Options options, ILogger logger) var settings = new Settings(options.Installed, options.Parent || options.Base, options.Owned, options.Games); - if (options.All) // Enable all emulator and handlers + if (options.All) // Enable all handlers { options.Amazon = true; options.Arc = true; options.BattleNet = true; options.BigFish = true; - //options.Dolphin ??= ""; + //options.Dolphin ??= ""; // needs path options.EA = true; options.Epic = true; options.GameJolt = true; @@ -129,7 +130,7 @@ private async static void Run(Options options, ILogger logger) options.IG = true; options.Itch = true; options.Legacy = true; - //options.MAME ??= ""; + //options.MAME ??= ""; // needs path options.Oculus = true; options.Origin = true; options.Paradox = true; @@ -138,9 +139,10 @@ private async static void Run(Options options, ILogger logger) options.RobotCache = true; options.Rockstar = true; options.Steam = true; - //options.TheGamesDB = true; + //options.TheGamesDB = true; // WIP options.Ubisoft = true; options.Wargaming = true; + options.Winget = true; options.Xbox = true; } @@ -185,6 +187,8 @@ private async static void Run(Options options, ILogger logger) if (options.Ubisoft || options.Uplay) tasks.Add(Task.Run(() => RunUbisoftHandler(settings, windowsRegistry, realFileSystem), cancelToken)); if (options.Wargaming || options.WargamingNet) tasks.Add(Task.Run(() => RunWargamingNetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); + if (options.Winget) tasks.Add(Task.Run(() => RunWingetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); + if (options.Dolphin is not null) { tasks.Add(Task.Run(() => @@ -445,6 +449,12 @@ private static void RunWargamingNetHandler(Settings settings, IRegistry registry var handler = new WargamingNetHandler(registry, fileSystem); LogGamesAndErrors(handler.FindAllGames(settings), logger); } + private static void RunWingetHandler(Settings settings, IRegistry registry, IFileSystem fileSystem) + { + var logger = _provider.CreateLogger(nameof(WingetHandler)); + var handler = new WingetHandler(registry, fileSystem); + LogGamesAndErrors(handler.FindAllGames(settings), logger); + } private static void RunDolphinHandler(Settings settings, IRegistry registry, IFileSystem fileSystem, AbsolutePath path) { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index af241bef..dea2dd2d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ - 4.2.4.1 + 4.2.4.2 Nutzzz, erri120 docs\README.md diff --git a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj new file mode 100644 index 00000000..68e27a54 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj @@ -0,0 +1,19 @@ + + + Library for finding installed apps and available packages via Windows Package Manager. + winget windows-package-manager games + + + + + + + + + + + + + + + diff --git a/src/GameCollector.PkgHandlers.Winget/WingetGame.cs b/src/GameCollector.PkgHandlers.Winget/WingetGame.cs new file mode 100644 index 00000000..95356f66 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WingetGame.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using GameFinder.Common; +using JetBrains.Annotations; +using NexusMods.Paths; + +namespace GameCollector.PkgHandlers.Winget; + +/// +/// Represents an installed app or available package via Windows Package Manager. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +[PublicAPI] +public record WingetGame(WingetGameId Id, + string? Name, + AbsolutePath InstallDirectory, + AbsolutePath Launch = new(), + string? InstallerUrl = "", + AbsolutePath Uninstall = new(), + bool IsInstalled = true, + DateTime? InstallDate = null, + string? Description = "", + string? Publisher = "", + List? PackageTags = default, + string? PublisherUrl = "", + string? SupportUrl = "", + string? Homepage = "", + string? Author = "", + string? PackageName = "", + string? Moniker = "", + string? Source = "", + string? LicenseType = "", + string? InstalledVersion = "", + string? DefaultVersion = "", + string? AgeRating = "") : + GameData(Handler: Handler.PkgHandler_Winget, + GameId: Id.ToString(), + GameName: Name ?? "", + GamePath: InstallDirectory, + Launch: Launch, + LaunchUrl: InstallerUrl ?? "", + Icon: Launch, + Uninstall: Uninstall, + IsInstalled: IsInstalled, + InstallDate: InstallDate, + Metadata: new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + ["Description"] = new() { Description ?? "", }, + ["Developers"] = new() { Author ?? "", }, + ["Publishers"] = new() { Publisher ?? "", }, + ["Genres"] = PackageTags ?? new(), + ["WebInfo"] = new() { Homepage ?? PublisherUrl ?? "" }, + ["WebSupport"] = new() { SupportUrl ?? "" }, + ["PackageName"] = new() { PackageName ?? "" }, + ["Moniker"] = new() { Moniker ?? "", }, + ["Source"] = new() { Source ?? "", }, + ["License"] = new() { LicenseType ?? "", }, + ["InstalledVersion"] = new() { InstalledVersion ?? "", }, + ["DefaultVersion"] = new() { DefaultVersion ?? "", }, + ["AgeRating"] = new() { AgeRating ?? "", }, + }); diff --git a/src/GameCollector.PkgHandlers.Winget/WingetGameId.cs b/src/GameCollector.PkgHandlers.Winget/WingetGameId.cs new file mode 100644 index 00000000..315f8258 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WingetGameId.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using TransparentValueObjects; + +namespace GameCollector.PkgHandlers.Winget; + +/// +/// Represents an id for installed apps and available packages via Windows Package Manager. +/// +[ValueObject] +public readonly partial struct WingetGameId : IAugmentWith +{ + /// + public static IEqualityComparer InnerValueDefaultEqualityComparer { get; } = StringComparer.OrdinalIgnoreCase; +} + +/// +[PublicAPI] +public class WingetGameIdComparer : IEqualityComparer +{ + private static WingetGameIdComparer? _default; + + /// + /// Default equality comparer that uses . + /// + public static WingetGameIdComparer Default => _default ??= new(); + + private readonly StringComparison _stringComparison; + + /// + /// Default constructor that uses . + /// + public WingetGameIdComparer() : this(StringComparison.OrdinalIgnoreCase) { } + + /// + /// Constructor. + /// + /// + public WingetGameIdComparer(StringComparison stringComparison) + { + _stringComparison = stringComparison; + } + + /// + public bool Equals(WingetGameId x, WingetGameId y) => string.Equals(x.Value, y.Value, _stringComparison); + + /// + public int GetHashCode(WingetGameId obj) => obj.Value.GetHashCode(_stringComparison); +} diff --git a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs new file mode 100644 index 00000000..6f01a01a --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs @@ -0,0 +1,774 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using GameFinder.Common; +using GameFinder.RegistryUtils; +using JetBrains.Annotations; +using NexusMods.Paths; +using OneOf; + +namespace GameCollector.PkgHandlers.Winget; + +/// +/// Handler for finding installed apps via Windows Package Manager. +/// +/// +/// Leverages winget, which inspects registry and WindowsApps (Microsoft Store) for installed programs. +/// +[PublicAPI] +public class WingetHandler : AHandler +{ + internal const string TagQuery = "game"; + internal const string UninstallRegKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; + + private readonly IRegistry _registry; + private readonly IFileSystem _fileSystem; + + //private WindowsPackageManagerFactory? _winGetFactory = null; + + private ProcessStartInfo _startInfo = new() + { + UseShellExecute = false, + RedirectStandardOutput = true, + StandardOutputEncoding = Encoding.UTF8, + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + }; + + /// + /// Constructor. + /// + /// + /// The implementation of to use. For a shared instance + /// use on Windows. On Linux use null. + /// For tests either use , a custom implementation or just a mock + /// of the interface. + /// + /// + /// The implementation of to use. For a shared instance use + /// . For tests either use , + /// a custom implementation or just a mock of the interface. + /// + public WingetHandler(IRegistry registry, IFileSystem fileSystem) + { + _registry = registry; + _fileSystem = fileSystem; + + if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) + { + // If the user is an administrator, use the elevated factory. Otherwhise COM will crash + /* + if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) + _winGetFactory = new WindowsPackageManagerElevatedFactory(); + else + _winGetFactory = new WindowsPackageManagerStandardFactory(); + */ + } + } + + /// + public override IEqualityComparer? IdEqualityComparer => WingetGameIdComparer.Default; + + /// + public override Func IdSelector => game => game.Id; + + /// + public override AbsolutePath FindClient() + { + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var wingetExe = _fileSystem.FromUnsanitizedFullPath(Path.Combine(localAppData, "Microsoft", "WindowsApps", "winget.exe")); + if (Path.IsPathRooted(localAppData) && _fileSystem.FileExists(wingetExe)) + return wingetExe; + + return default; + } + + /// + public override IEnumerable> FindAllGames(Settings? settings = null) + { + return FindAllGames(settings?.InstalledOnly ?? false); + } + + /// + /// Finds all apps supported by this package handler. The return type + /// will always be a non-null game or a non-null error message. + /// + /// + /// Get additional information about winget/msstore packages + /// + public IEnumerable> FindAllGames(bool? installedOnly = false, bool? expandPackage = false) + { + if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) + { + yield return new ErrorMessage("Only supported on Windows 10.0.19041.0 or later."); + yield break; + } + /* + if (_winGetFactory is null) + { + yield return new ErrorMessage("Could not access Windows Package Manager."); + yield break; + } + */ + + Dictionary> games = new(); + + // Create Package Manager and get available catalogs + //var manager = _winGetFactory.CreatePackageManager(); + //var availableCatalogs = manager.GetPackageCatalogs(); + + /* + foreach (var catalog in availableCatalogs.ToArray()) + { + // Create a filter to search for packages with "game" tag + //var filterList = _winGetFactory.CreateFindPackagesOptions(); + + // Add the query to the filter + var tagFilter = _winGetFactory.CreatePackageMatchFilter(); + tagFilter.Field = PackageMatchField.Tag; + tagFilter.Value = TagQuery; + filterList.Filters.Add(tagFilter); + + // Find the packages with the filters + //var searchResults = await catalog.Connect().PackageCatalog.FindPackagesAsync(filterList); + var searchResults = catalog.Connect().PackageCatalog.FindPackages(filterList); + foreach (var match in searchResults.Matches.ToArray()) + { + var pkg = match.CatalogPackage; + var id = WingetGameId.From(pkg.Id); + + games.Add(id, new WingetGame( + Id: id, + Name: pkg.Name, + InstallDirectory: default, + //PkgTags: pkg.Tags, + //Source: pkg.Source, + InstalledVersion: pkg.InstalledVersion.DisplayName, + DefaultVersion: pkg.DefaultInstallVersion.DisplayName + )); + } + } + */ + + // Get all installed items + //var installedCatalogs = manager.GetLocalPackageCatalog; + + foreach (var item in GetInstalled(expandPackage)) + { + if (item.IsT0) + { + if (!games.ContainsKey(item.AsT0.Id)) + continue; + + yield return new WingetGame( + Id: item.AsT0.Id, + Name: item.AsT0.Name, + InstallDirectory: default, + IsInstalled: true, + //PkgTags: item.CatalogPackage.Tags, + //Source: item.CatalogPackage.Source, + InstalledVersion: item.AsT0.InstalledVersion, + DefaultVersion: item.AsT0.DefaultVersion + ); + } + else yield return item.AsT1; + } + + //var freeGames = SearchFreeGames(); + + } + + private IEnumerable> GetInstalled() + { + if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) + { + yield return new ErrorMessage("Only supported on Windows 10.0.19041.0 or later."); + yield break; + } + /* + if (_winGetFactory is null) + { + yield return new ErrorMessage("Could not access Windows Package Manager."); + yield break; + } + */ + + // Fetching installed packages + //var winGetManager = _winGetFactory.CreatePackageManager(); + + // CHANGE THIS INDEX TO CHANGE THE SOURCE + var selectedIndex = 0; // does this refer to winget vs. msstore? + + /* + PackageCatalogReference installedSearchCatalogRef; + if (selectedIndex < 0) + { + installedSearchCatalogRef = winGetManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); + } + else + { + var selectedRemoteCatalogRef = winGetManager.GetPackageCatalogs().ToArray()[selectedIndex]; + + //Console.WriteLine($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); + + var createCompositePackageCatalogOptions = _winGetFactory.CreateCreateCompositePackageCatalogOptions(); + createCompositePackageCatalogOptions.Catalogs.Add(selectedRemoteCatalogRef); + createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.LocalCatalogs; + installedSearchCatalogRef = winGetManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); + } + + var connectResult = installedSearchCatalogRef.Connect(); + if (connectResult.Status != ConnectResultStatus.Ok) + { + yield return new ErrorMessage("Failed to connect to local catalog."); + yield break; + } + + var findPackagesOptions = _winGetFactory.CreateFindPackagesOptions(); + var filter = _winGetFactory.CreatePackageMatchFilter(); + filter.Field = PackageMatchField.Id; + filter.Option = PackageFieldMatchOption.ContainsCaseInsensitive; + filter.Value = ""; + findPackagesOptions.Filters.Add(filter); + + var taskResult = connectResult.PackageCatalog.FindPackages(findPackagesOptions); + */ + + // Begin enumeration + /* + foreach (var match in taskResult.Matches.ToArray()) + { + var pkg = match.CatalogPackage; + //Console.WriteLine($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); + yield return new WingetGame( + Id: WingetGameId.From(pkg.Id), + Name: pkg.Name, + InstallDirectory: default, + IsInstalled: pkg.DefaultInstallVersion != null, + //PackageTags: pkg.Tags???, + Source: pkg.DefaultInstallVersion?.Channel, // is this winget vs. msstore? + PackageName: pkg.DefaultInstallVersion?.PackageCatalog.Info.Name, + ProductCodes: pkg.DefaultInstallVersion?.ProductCodes.ToList(), + Publisher: pkg.InstalledVersion.Publisher, + InstalledVersion: pkg.InstalledVersion.DisplayName, + DefaultVersion: pkg.DefaultInstallVersion?.DisplayName); + } + */ + + // End enumeration + } + + private IEnumerable> GetInstalled(bool? expandPackage) + { + //************************************* + Console.OutputEncoding = Encoding.UTF8; + //************************************* + + var wingetExe = FindClient(); + if (wingetExe == default) + { + yield return new ErrorMessage("Winget not installed"); + yield break; + } + + using var process = new Process(); + process.StartInfo = _startInfo; + process.StartInfo.FileName = wingetExe.GetFullPath(); + process.StartInfo.Arguments = "list --nowarn --disable-interactivity"; + process.Start(); + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + if (string.IsNullOrEmpty(output)) + { + yield return new ErrorMessage("No output from winget"); + yield break; + } + + List colPos = new(); + var i = 0; + foreach (var line in output.Split('\n')) + { + if (i == 0) + { + if (!line.Contains("Name ", StringComparison.Ordinal)) + continue; + + var headLine = line[line.IndexOf("Name ", StringComparison.Ordinal)..]; + foreach (var col in headLine.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + { + var c = headLine.IndexOf(col, StringComparison.Ordinal); + if (c > -1) + colPos.Add(c); + } + if (colPos.Count < 5) + break; + } + else if (i > 1 && !string.IsNullOrWhiteSpace(line)) // skip separator line + { + var name = line[colPos[0]..colPos[1]].Trim(); + var strId = line[colPos[1]..colPos[2]].Trim(); + var id = WingetGameId.From(strId); + var version = line[colPos[2]..colPos[3]].Trim(); + var available = line[colPos[3]..colPos[4]].Trim(); + var source = line[colPos[4]..].Trim(); + + if (string.IsNullOrWhiteSpace(source)) // non-package install + { + if (expandPackage != true) + { + Console.WriteLine(" " + name); + yield return new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + IsInstalled: true, + InstalledVersion: version); + i++; + continue; + } + + if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) + { + (name, strId, version) = Relist(name, strId, version); + id = WingetGameId.From(strId); + } + + if (!string.IsNullOrEmpty(strId) && !strId.EndsWith('…')) + { + var game = ParseRegistry(strId); + if (game.IsT0) + { + Console.WriteLine("@ " + name); + yield return new WingetGame( + Id: id, + Name: name, + InstallDirectory: game.AsT0.InstallDirectory, + Launch: game.AsT0.Launch, + Uninstall: game.AsT0.Uninstall, + IsInstalled: true, + InstallDate: game.AsT0.InstallDate, + Publisher: game.AsT0.Publisher, + SupportUrl: game.AsT0.SupportUrl, + Homepage: game.AsT0.Homepage, + InstalledVersion: version); + i++; + continue; + } + + yield return game.AsT1; + } + + Console.WriteLine("? " + name); + yield return new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + IsInstalled: true, + InstalledVersion: version); + } + else // package install + { + if (expandPackage != true) + { + if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) + { + (name, strId, version) = Relist(name, strId, version); + id = WingetGameId.From(strId); + } + Console.WriteLine(" " + name); + yield return new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + IsInstalled: true, + Source: source, + InstalledVersion: version, + DefaultVersion: available); + i++; + continue; + } + + if (strId.EndsWith('…')) + { + (name, strId, version) = Relist(name, strId, version); + id = WingetGameId.From(strId); + } + yield return GetPackageInfo(colPos, line, search: false); + } + } + i++; + } + } + + private (string, string, string) Relist(string name, string id, string version) + { + var subName = name; + if (name.EndsWith('…')) + subName = name[..^1]; + var subId = id; + if (id.EndsWith('…')) + subId = id[..^1]; + + var wingetExe = FindClient(); + + using var process = new Process(); + process.StartInfo = _startInfo; + process.StartInfo.FileName = wingetExe.GetFullPath(); + //process.StartInfo.Arguments = $"list --name \"{subName}\" --nowarn --disable-interactivity"; + process.StartInfo.Arguments = $"list --id \"{subId}\" --nowarn --disable-interactivity"; + process.Start(); + var listOutput = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + List listCols = new(); + var i = 0; + foreach (var listLine in listOutput.Split('\n')) + { + if (i == 0) + { + if (!listLine.Contains("Name", StringComparison.Ordinal)) + continue; + + var headLine = listLine[listLine.IndexOf("Name ", StringComparison.Ordinal)..]; + foreach (var col in headLine.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + { + var c = headLine.IndexOf(col, StringComparison.Ordinal); + if (c > -1) + listCols.Add(c); + } + if (listCols.Count < 3) + break; + } + else if (i > 1 && !string.IsNullOrWhiteSpace(listLine)) // skip separator line + { + name = listLine[listCols[0]..listCols[1]].Trim(); + id = listLine[listCols[1]..listCols[2]].Trim(); + version = listLine[listCols[2]..]; + } + i++; + } + + return (name, id, version); + } + + private Dictionary> SearchFreeGames(bool expandPackage = false) + { + Dictionary> freeGames = new(); + + var wingetExe = FindClient(); + + using var process = new Process(); + process.StartInfo = _startInfo; + process.StartInfo.FileName = wingetExe.GetFullPath(); + process.StartInfo.Arguments = "search --tag game --nowarn --disable-interactivity"; + process.Start(); + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + if (string.IsNullOrEmpty(output)) + { + return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; + } + + List colPos = new(); + var i = 0; + foreach (var line in output.Split('\n')) + { + if (i == 0) + { + if (!line.Contains("Name ", StringComparison.Ordinal)) + continue; + + var headLine = line[line.IndexOf("Name ", StringComparison.Ordinal)..]; + foreach (var col in headLine.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + { + var c = headLine.IndexOf(col, StringComparison.Ordinal); + if (c > -1) + colPos.Add(c); + } + if (colPos.Count < 5) + break; + } + else if (i > 1 && !string.IsNullOrWhiteSpace(line)) // skip separator line + { + var name = line[colPos[0]..colPos[1]].Trim(); + var strId = line[colPos[1]..colPos[2]].Trim(); + var id = WingetGameId.From(strId); + var available = line[colPos[2]..colPos[3]].Trim(); + //var match = line[colPos[3]..colPos[4]].Trim(); + var source = line[colPos[4]..].Trim(); + + if (!expandPackage) + { + freeGames.Add(id, new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + IsInstalled: false, + DefaultVersion: available)); + i++; + continue; + } + + freeGames.Add(id, GetPackageInfo(colPos, line, search: true)); + } + i++; + } + + return freeGames; + } + + private OneOf ParseRegistry(string id) + { + // id syntax = "ARP\[Machine|User]\[X64|X86]\ + + var regKeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; + var baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine); + if (id.StartsWith(@"ARP\Machine\X64\", StringComparison.Ordinal)) + { + regKeyName += id[@"ARP\Machine\X64\".Length..]; + baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + } + else if (id.StartsWith(@"ARP\Machine\X86\", StringComparison.Ordinal)) + { + regKeyName += id[@"ARP\Machine\X86\".Length..]; + baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); + } + else if (id.StartsWith(@"ARP\User\X64\", StringComparison.Ordinal)) + { + regKeyName += id[@"ARP\User\X64\".Length..]; + baseKey = _registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); + } + else if (id.StartsWith(@"ARP\User\X86\", StringComparison.Ordinal)) + { + regKeyName += id[@"ARP\User\X86\".Length..]; + baseKey = _registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); + } + + try + { + using var regKey = baseKey.OpenSubKey(regKeyName); + if (regKey is null) + { + return new ErrorMessage($"Unable to open {regKeyName}"); + } + + if (!regKey.TryGetString("DisplayName", out var name)) + name = ""; + + if (!regKey.TryGetString("HelpLink", out var help)) + help = ""; + + DateTime installDate = default; + if (regKey.TryGetString("InstallDate", out var date)) + DateTime.TryParseExact(date, "yyyyMMdd", new CultureInfo("en-US"), DateTimeStyles.None, out installDate); + + if (!regKey.TryGetString("InstallLocation", out var path)) + path = ""; + + if (!regKey.TryGetString("DisplayIcon", out var launch)) + launch = ""; + + if (!regKey.TryGetString("Publisher", out var pub)) + pub = ""; + + if (!regKey.TryGetString("UninstallString", out var uninst)) + uninst = ""; + + if (!regKey.TryGetString("URLInfoAbout", out var url)) + url = ""; + + return new WingetGame( + Id: WingetGameId.From(id), + Name: name, + InstallDirectory: Path.IsPathRooted(path) ? _fileSystem.FromUnsanitizedFullPath(path) : new(), + InstallDate: installDate == default ? null : installDate, + Launch: Path.IsPathRooted(launch) ? _fileSystem.FromUnsanitizedFullPath(launch) : new(), + Uninstall: Path.IsPathRooted(uninst) ? _fileSystem.FromUnsanitizedFullPath(uninst) : new(), + Publisher: pub, + SupportUrl: help, + Homepage: url + ); + } + catch (Exception e) + { + return new ErrorMessage(e, $"Exception while parsing registry key {regKeyName}"); + } + } + + private OneOf GetPackageInfo(List colPos, string listInfo, bool search = false) + { + //list header = "Name,Id,Version,Available,Source" + //search header = "Name,Id,Version,Match,Source" + + var wingetExe = FindClient(); + + var isDescription = false; + //var isReleaseNotes = false; + var isTags = false; + + var name = listInfo[colPos[0]..colPos[1]].Trim(); + var strId = listInfo[colPos[1]..colPos[2]].Trim(); + var id = WingetGameId.From(strId); + var version = ""; + string? available; + //string? match; + if (search) + { + available = listInfo[colPos[2]..colPos[3]].Trim(); + //match = listInfo[colPos[3]..colPos[4]].Trim(); + } + else + { + version = listInfo[colPos[2]..colPos[3]].Trim(); + available = listInfo[colPos[3]..colPos[4]].Trim(); + } + var source = listInfo[colPos[4]..].Trim(); + var pkgName = ""; + var publisher = ""; + //var publisherUrl = ""; + var publisherSupportUrl = ""; + var author = ""; + var moniker = ""; + var description = ""; + var homepage = ""; + var license = ""; + //var licenseUrl = ""; + //var privacyUrl = ""; + //var copyright = ""; + //var releaseNotes = ""; + //var releaseNotesUrl = ""; + //var purchaseUrl = ""; + //var documentationTutorials = ""; + //var documentationManual = ""; + List tags = new(); + //var agreementsCategory = ""; + //var agreementsPricing = ""; + //string agreementsFreeTrial = ""; + var agreementsAgeRatings = ""; + //string agreementsTermsOfTransaction = ""; + //string agreementsSeizureWarning = ""; + //string agreementsStoreLicenseTerms = ""; + //var installerType = ""; + //var installerLocale = ""; + var installerUrl = ""; + //var installerSha256 = ""; + //var installerStoreProductId = ""; + //var installerReleaseDate = ""; + //var installerOfflineDistributionSupported = false; + + using var process = new Process(); + process.StartInfo = _startInfo; + process.StartInfo.FileName = wingetExe.GetFullPath(); + process.StartInfo.Arguments = $"show {strId} --nowarn --disable-interactivity"; + process.Start(); + var pkgOutput = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + var i = 0; + foreach (var pkgLine in pkgOutput.Split('\n')) + { + if (isDescription) + { + if (pkgLine.StartsWith(' ')) + { + description += pkgLine.TrimStart(); + continue; + } + isDescription = false; + } + /* + if (isReleaseNotes) + { + if (pkgLine.StartsWith(' ')) + { + releaseNotes += pkgLine.TrimStart(); + continue; + } + isReleaseNotes = false; + } + */ + if (isTags) + { + if (pkgLine.StartsWith(' ')) + { + tags.Add(pkgLine.TrimStart()); + continue; + } + isTags = false; + } + + if (i == 0) + { + if (pkgLine.Contains("Found ", StringComparison.Ordinal)) + { + var headLine = pkgLine[pkgLine.IndexOf("Found ", StringComparison.Ordinal)..]; + if (headLine.LastIndexOf('[') > 7) + { + strId = headLine[(headLine.LastIndexOf('[') + 1)..^1]; + id = WingetGameId.From(strId); + pkgName = headLine["Found ".Length..headLine.LastIndexOf('[')]; + } + else + pkgName = headLine["Found ".Length..]; + } + else + continue; + } + else if (pkgLine.StartsWith("Version: ", StringComparison.Ordinal)) + version = pkgLine["Version: ".Length..]; + else if (pkgLine.StartsWith("Publisher: ", StringComparison.Ordinal)) + publisher = pkgLine["Publisher: ".Length..]; + else if (pkgLine.StartsWith("Publisher Support Url: ", StringComparison.Ordinal)) + publisherSupportUrl = pkgLine["Publisher Support Url: ".Length..]; + else if (pkgLine.StartsWith("Moniker: ", StringComparison.Ordinal)) + moniker = pkgLine["Moniker: ".Length..]; + else if (pkgLine.StartsWith("Description:", StringComparison.Ordinal)) + { + description = pkgLine["Description:".Length..].TrimStart(); + isDescription = true; + } + else if (pkgLine.StartsWith("Homepage: ", StringComparison.Ordinal)) + homepage = pkgLine["Homepage: ".Length..]; + else if (pkgLine.StartsWith("License: ", StringComparison.Ordinal)) + license = pkgLine["License: ".Length..]; + //else if (pkgLine.StartsWith("Release Notes:", StringComparison.Ordinal)) + //{ + // releaseNotes = pkgLine["Release Notes:".Length..].TrimStart(); + // isReleaseNotes = true; + //} + else if (pkgLine.StartsWith(" Age Ratings: ", StringComparison.Ordinal)) + agreementsAgeRatings = pkgLine[" Age Ratings: ".Length..]; + else if (pkgLine.StartsWith("Tags:", StringComparison.Ordinal)) + isTags = true; + else if (pkgLine.StartsWith(" Installer Url: ", StringComparison.Ordinal)) + installerUrl = pkgLine[" Installer Url: ".Length..]; + i++; + } + + Console.WriteLine("> " + name); + return new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + InstallerUrl: installerUrl, + IsInstalled: true, + Description: description, + Publisher: publisher, + Author: author, + PackageTags: tags, + Homepage: homepage, + SupportUrl: publisherSupportUrl, + PackageName: pkgName, + Moniker: moniker, + Source: source, + LicenseType: license, + InstalledVersion: version, + DefaultVersion: available, + AgeRating: agreementsAgeRatings); + } +} diff --git a/src/GameFinder.Common/Handler.cs b/src/GameFinder.Common/Handler.cs index e53e8ad2..45bd95ec 100644 --- a/src/GameFinder.Common/Handler.cs +++ b/src/GameFinder.Common/Handler.cs @@ -139,6 +139,11 @@ public enum Handler [Description("GameCollector.EmuHandlers.MAME")] EmuHandler_MAME, /// + /// Windows Package Manager package handler + /// + [Description("GameCollector.PkgHandlers.Winget")] + PkgHandler_Winget, + /// /// TheGamesDb.net data handler /// [Description("GameCollector.DataHandlers.TheGamesDb")] diff --git a/src/GameFinder/GameFinder.csproj b/src/GameFinder/GameFinder.csproj index 10b9f037..eb7891e8 100644 --- a/src/GameFinder/GameFinder.csproj +++ b/src/GameFinder/GameFinder.csproj @@ -45,6 +45,7 @@ + From 581ebf887fdbaf2d1e76bee5281e702c6720910c Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Tue, 1 Oct 2024 12:05:50 -0700 Subject: [PATCH 2/8] v4.3.1-1 --- CHANGELOG.md | 12 +++++++++++- Directory.Build.props | 2 +- src/Directory.Build.props | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d7fec9..4bf7ac19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com). Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [Semantic Versioning](https://semver.org), for the moment this project simply uses GameFinder's version number and adds a new digit to the end. Where normally a fourth digit would be used for hotfixes or pre-release versions, because GameCollector is still in its infancy, breaking changes may occur between point releases. Because the scope of this project is so much larger than upstream, eventually a different versioning scheme will need to be used. -## [Unreleased](https://github.com/Nutzzz/GameCollector/compare/v4.2.3.1...HEAD) +## [Unreleased](https://github.com/Nutzzz/GameCollector/compare/v4.3.1.1...HEAD) **TODO**: @@ -20,6 +20,16 @@ Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [ ## [Released](https://github.com/Nutzzz/GameCollector/releases) +## [4.3.1-1](https://github.com/Nutzzz/GameCollector/compare/v4.2.4.1...v4.3.1.1) - 2024-10-01 + +### Added + +- Upstream: Adds support for Heroic + +### Changed + +- Upstream: Heroic: pass along the wine prefix + ## [4.2.4-1](https://github.com/Nutzzz/GameCollector/compare/v4.2.3.2...v4.2.4.1) - 2024-07-08 ### Added diff --git a/Directory.Build.props b/Directory.Build.props index 9c04d808..93d14f12 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net8.0 true - 4.2.4 + 4.3.1 Nutzzz, erri120 https://github.com/Nutzzz/GameCollector git diff --git a/src/Directory.Build.props b/src/Directory.Build.props index af241bef..f71bfdcf 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ - 4.2.4.1 + 4.3.1.1 Nutzzz, erri120 docs\README.md From 926ca087820203a085616076f35afc2c7f8eb0ca Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Wed, 2 Oct 2024 09:03:39 -0700 Subject: [PATCH 3/8] v4.3.1-2 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e662407..afdd6d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [ - Add [DataHandlers](https://github.com/Nutzzz/GameCollector/pull/13) to download metadata and images from online sources -## [4.2.4-2](https://github.com/Nutzzz/GameCollector/compare/v4.2.4.1...v4.2.4.2) - UNRELEASED +## [4.3.1-2](https://github.com/Nutzzz/GameCollector/compare/v4.3.1.1...v4.3.1.2) - UNRELEASED ### Added From 7dd6fcadd1c162edb049fd0cdad609827b16c58d Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Mon, 14 Oct 2024 12:22:38 -0700 Subject: [PATCH 4/8] Revert "v4.3.1-2" This reverts commit 926ca087820203a085616076f35afc2c7f8eb0ca. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afdd6d82..6e662407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [ - Add [DataHandlers](https://github.com/Nutzzz/GameCollector/pull/13) to download metadata and images from online sources -## [4.3.1-2](https://github.com/Nutzzz/GameCollector/compare/v4.3.1.1...v4.3.1.2) - UNRELEASED +## [4.2.4-2](https://github.com/Nutzzz/GameCollector/compare/v4.2.4.1...v4.2.4.2) - UNRELEASED ### Added From 9aa34ce5350108e0e13a38fde85d4a3ace5b77e0 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Fri, 18 Oct 2024 15:37:56 -0700 Subject: [PATCH 5/8] Some tweaks to winget experiment --- other/GameFinder.Example/Program.cs | 2 +- .../GameCollector.PkgHandlers.Winget.csproj | 2 +- .../WingetGame.cs | 3 + .../WingetHandler.cs | 327 ++++++++++++------ 4 files changed, 225 insertions(+), 109 deletions(-) diff --git a/other/GameFinder.Example/Program.cs b/other/GameFinder.Example/Program.cs index 51e1c2a2..ef052ca0 100644 --- a/other/GameFinder.Example/Program.cs +++ b/other/GameFinder.Example/Program.cs @@ -462,7 +462,7 @@ private static void RunWargamingNetHandler(Settings settings, IRegistry registry private static void RunWingetHandler(Settings settings, IRegistry registry, IFileSystem fileSystem) { var logger = _provider.CreateLogger(nameof(WingetHandler)); - var handler = new WingetHandler(registry, fileSystem); + var handler = new WingetHandler(registry, fileSystem, logger); //, logger); LogGamesAndErrors(handler.FindAllGames(settings), logger); } diff --git a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj index 68e27a54..fe57ff16 100644 --- a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj +++ b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj @@ -1,6 +1,6 @@ - Library for finding installed apps and available packages via Windows Package Manager. + Library for finding installed apps and available game packages via Windows Package Manager. winget windows-package-manager games diff --git a/src/GameCollector.PkgHandlers.Winget/WingetGame.cs b/src/GameCollector.PkgHandlers.Winget/WingetGame.cs index 95356f66..39ff5a8d 100644 --- a/src/GameCollector.PkgHandlers.Winget/WingetGame.cs +++ b/src/GameCollector.PkgHandlers.Winget/WingetGame.cs @@ -16,6 +16,7 @@ namespace GameCollector.PkgHandlers.Winget; /// /// /// +/// /// /// /// @@ -40,6 +41,7 @@ public record WingetGame(WingetGameId Id, string? InstallerUrl = "", AbsolutePath Uninstall = new(), bool IsInstalled = true, + bool IsOwned = true, DateTime? InstallDate = null, string? Description = "", string? Publisher = "", @@ -64,6 +66,7 @@ public record WingetGame(WingetGameId Id, Icon: Launch, Uninstall: Uninstall, IsInstalled: IsInstalled, + IsOwned: IsOwned, InstallDate: InstallDate, Metadata: new Dictionary>(StringComparer.OrdinalIgnoreCase) { diff --git a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs index 6f01a01a..81ebb16f 100644 --- a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs +++ b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs @@ -3,10 +3,12 @@ using System.Diagnostics; using System.Globalization; using System.IO; +//using System.Security.Principal; using System.Text; using GameFinder.Common; using GameFinder.RegistryUtils; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; using NexusMods.Paths; using OneOf; @@ -21,11 +23,12 @@ namespace GameCollector.PkgHandlers.Winget; [PublicAPI] public class WingetHandler : AHandler { - internal const string TagQuery = "game"; + internal const string DefaultQuery = "game"; internal const string UninstallRegKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; private readonly IRegistry _registry; private readonly IFileSystem _fileSystem; + private ILogger? _logger; //private WindowsPackageManagerFactory? _winGetFactory = null; @@ -34,6 +37,7 @@ public class WingetHandler : AHandler UseShellExecute = false, RedirectStandardOutput = true, StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, }; @@ -52,10 +56,11 @@ public class WingetHandler : AHandler /// . For tests either use , /// a custom implementation or just a mock of the interface. /// - public WingetHandler(IRegistry registry, IFileSystem fileSystem) + public WingetHandler(IRegistry registry, IFileSystem fileSystem, ILogger? logger = null) { _registry = registry; _fileSystem = fileSystem; + _logger = logger; if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { @@ -89,7 +94,7 @@ public override AbsolutePath FindClient() /// public override IEnumerable> FindAllGames(Settings? settings = null) { - return FindAllGames(settings?.InstalledOnly ?? false); + return FindAllGames(settings?.InstalledOnly ?? false, settings?.OwnedOnly ?? false, settings?.GamesOnly ?? false); } /// @@ -97,9 +102,17 @@ public override IEnumerable> FindAllGames(Settin /// will always be a non-null game or a non-null error message. /// /// + /// + /// + /// /// Get additional information about winget/msstore packages /// - public IEnumerable> FindAllGames(bool? installedOnly = false, bool? expandPackage = false) + public IEnumerable> FindAllGames( + bool installedOnly = false, + bool ownedOnly = false, + bool gamesOnly = false, + string? query = DefaultQuery, + bool expandPackage = false) { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { @@ -114,7 +127,7 @@ public IEnumerable> FindAllGames(bool? installed } */ - Dictionary> games = new(); + Dictionary> installed = new(); // Create Package Manager and get available catalogs //var manager = _winGetFactory.CreatePackageManager(); @@ -123,13 +136,13 @@ public IEnumerable> FindAllGames(bool? installed /* foreach (var catalog in availableCatalogs.ToArray()) { - // Create a filter to search for packages with "game" tag //var filterList = _winGetFactory.CreateFindPackagesOptions(); + // Create a filter to search for packages with query ["game" default] // Add the query to the filter var tagFilter = _winGetFactory.CreatePackageMatchFilter(); tagFilter.Field = PackageMatchField.Tag; - tagFilter.Value = TagQuery; + tagFilter.Value = query; filterList.Filters.Add(tagFilter); // Find the packages with the filters @@ -140,7 +153,7 @@ public IEnumerable> FindAllGames(bool? installed var pkg = match.CatalogPackage; var id = WingetGameId.From(pkg.Id); - games.Add(id, new WingetGame( + installed.Add(id, new WingetGame( Id: id, Name: pkg.Name, InstallDirectory: default, @@ -156,43 +169,67 @@ public IEnumerable> FindAllGames(bool? installed // Get all installed items //var installedCatalogs = manager.GetLocalPackageCatalog; - foreach (var item in GetInstalled(expandPackage)) + if (!gamesOnly) { - if (item.IsT0) + //installed = GetInstalled(); + installed = GetInstalledParse(expandPackage); + foreach (var item in installed) { - if (!games.ContainsKey(item.AsT0.Id)) - continue; - - yield return new WingetGame( - Id: item.AsT0.Id, - Name: item.AsT0.Name, - InstallDirectory: default, - IsInstalled: true, - //PkgTags: item.CatalogPackage.Tags, - //Source: item.CatalogPackage.Source, - InstalledVersion: item.AsT0.InstalledVersion, - DefaultVersion: item.AsT0.DefaultVersion - ); + if (item.Value.IsT0) + { + yield return new WingetGame( + Id: item.Key, + Name: item.Value.AsT0.Name, + InstallDirectory: default, + IsInstalled: true, + IsOwned: true, + //PkgTags: item.CatalogPackage.Tags, + //Source: item.CatalogPackage.Source, + InstalledVersion: item.Value.AsT0.InstalledVersion, + DefaultVersion: item.Value.AsT0.DefaultVersion + ); + } + else yield return item.Value.AsT1; } - else yield return item.AsT1; } //var freeGames = SearchFreeGames(); + if (!installedOnly && !string.IsNullOrEmpty(query)) + { + foreach (var game in SearchFreeGamesParse(query, expandPackage)) + { + if (!installed.ContainsKey(game.Key)) + { + if (game.Value.IsT0) + { + yield return new WingetGame( + Id: game.Key, + Name: game.Value.AsT0.Name, + InstallDirectory: default, + IsInstalled: false, + IsOwned: false, + DefaultVersion: game.Value.AsT0.DefaultVersion + ); + } + else yield return game.Value.AsT1; + } + } + } } - private IEnumerable> GetInstalled() + private Dictionary> GetInstalled() { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { - yield return new ErrorMessage("Only supported on Windows 10.0.19041.0 or later."); - yield break; + _logger?.LogDebug("***Only supported on Windows 10.0.19041.0 or later."); + return new() { [WingetGameId.From("")] = new ErrorMessage("Only supported on Windows 10.0.19041.0 or later.") }; } /* if (_winGetFactory is null) { - yield return new ErrorMessage("Could not access Windows Package Manager."); - yield break; + _logger?.LogDebug("***Could not access Windows Package Manager."); + return new() { [WingetGameId.From("")] = new ErrorMessage("Could not access Windows Package Manager.") }; } */ @@ -212,7 +249,7 @@ private IEnumerable> GetInstalled() { var selectedRemoteCatalogRef = winGetManager.GetPackageCatalogs().ToArray()[selectedIndex]; - //Console.WriteLine($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); + //_logger?.LogDebug($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); var createCompositePackageCatalogOptions = _winGetFactory.CreateCreateCompositePackageCatalogOptions(); createCompositePackageCatalogOptions.Catalogs.Add(selectedRemoteCatalogRef); @@ -242,16 +279,17 @@ private IEnumerable> GetInstalled() foreach (var match in taskResult.Matches.ToArray()) { var pkg = match.CatalogPackage; - //Console.WriteLine($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); + //_logger?.LogDebug($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); yield return new WingetGame( Id: WingetGameId.From(pkg.Id), Name: pkg.Name, InstallDirectory: default, IsInstalled: pkg.DefaultInstallVersion != null, + IsOwned: pkg.DefaultInstallVersion != null, //PackageTags: pkg.Tags???, Source: pkg.DefaultInstallVersion?.Channel, // is this winget vs. msstore? PackageName: pkg.DefaultInstallVersion?.PackageCatalog.Info.Name, - ProductCodes: pkg.DefaultInstallVersion?.ProductCodes.ToList(), + //ProductCodes: pkg.DefaultInstallVersion?.ProductCodes.ToList(), Publisher: pkg.InstalledVersion.Publisher, InstalledVersion: pkg.InstalledVersion.DisplayName, DefaultVersion: pkg.DefaultInstallVersion?.DisplayName); @@ -261,17 +299,25 @@ private IEnumerable> GetInstalled() // End enumeration } - private IEnumerable> GetInstalled(bool? expandPackage) + private Dictionary> GetInstalled(bool? expandPackage) + { + Dictionary> installed = new(); + return installed; + } + + private Dictionary> GetInstalledParse(bool expandPackage) { //************************************* Console.OutputEncoding = Encoding.UTF8; //************************************* + Dictionary> installed = new(); + var wingetExe = FindClient(); if (wingetExe == default) { - yield return new ErrorMessage("Winget not installed"); - yield break; + _logger?.LogDebug("***Winget not installed"); + return new() { [WingetGameId.From("")] = new ErrorMessage("Winget not installed") }; } using var process = new Process(); @@ -284,8 +330,8 @@ private IEnumerable> GetInstalled(bool? expandPa if (string.IsNullOrEmpty(output)) { - yield return new ErrorMessage("No output from winget"); - yield break; + _logger?.LogDebug("***No output from winget"); + return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } List colPos = new(); @@ -309,31 +355,47 @@ private IEnumerable> GetInstalled(bool? expandPa } else if (i > 1 && !string.IsNullOrWhiteSpace(line)) // skip separator line { - var name = line[colPos[0]..colPos[1]].Trim(); - var strId = line[colPos[1]..colPos[2]].Trim(); - var id = WingetGameId.From(strId); - var version = line[colPos[2]..colPos[3]].Trim(); - var available = line[colPos[3]..colPos[4]].Trim(); - var source = line[colPos[4]..].Trim(); + string name = "", strId = "", version = "", available = ""; + string? source = null; + WingetGameId id = default; + // TODO: This doesn't work if there are surrogate pairs in the string + if (line.Length > colPos[1]) + name = line[colPos[0]..colPos[1]].Trim(); + if (line.Length > colPos[2]) + { + strId = line[colPos[1]..colPos[2]].Trim(); + id = WingetGameId.From(strId); + } + if (line.Length > colPos[3]) + version = line[colPos[2]..colPos[3]].Trim(); + if (line.Length > colPos[4]) + { + available = line[colPos[3]..colPos[4]].Trim(); + source = line[colPos[4]..].Trim(); + } if (string.IsNullOrWhiteSpace(source)) // non-package install { - if (expandPackage != true) + if (!expandPackage) { - Console.WriteLine(" " + name); - yield return new WingetGame( + if (installed.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: default, IsInstalled: true, - InstalledVersion: version); + IsOwned: true, + InstalledVersion: version))) + { + _logger?.LogDebug(" " + name); + } i++; continue; } if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) { - (name, strId, version) = Relist(name, strId, version); + (name, strId, version) = Relist(name, strId, version, source); + strId = strId.TrimEnd('…'); id = WingetGameId.From(strId); } @@ -342,84 +404,105 @@ private IEnumerable> GetInstalled(bool? expandPa var game = ParseRegistry(strId); if (game.IsT0) { - Console.WriteLine("@ " + name); - yield return new WingetGame( + if (installed.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: game.AsT0.InstallDirectory, Launch: game.AsT0.Launch, Uninstall: game.AsT0.Uninstall, IsInstalled: true, + IsOwned: true, InstallDate: game.AsT0.InstallDate, Publisher: game.AsT0.Publisher, SupportUrl: game.AsT0.SupportUrl, Homepage: game.AsT0.Homepage, - InstalledVersion: version); + InstalledVersion: version))) + { + _logger?.LogDebug("@ " + name); + } i++; continue; } - yield return game.AsT1; + if (installed.TryAdd(id, game.AsT1)) + { + _logger?.LogDebug("***" + game.AsT1.Message); + } + i++; } - Console.WriteLine("? " + name); - yield return new WingetGame( + if (installed.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: default, IsInstalled: true, - InstalledVersion: version); + IsOwned: true, + InstalledVersion: version))) + { + _logger?.LogDebug("? " + name); + } + i++; + continue; } - else // package install + + //else [package install] + + if (!expandPackage) { - if (expandPackage != true) + if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) { - if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) - { - (name, strId, version) = Relist(name, strId, version); - id = WingetGameId.From(strId); - } - Console.WriteLine(" " + name); - yield return new WingetGame( - Id: id, - Name: name, - InstallDirectory: default, - IsInstalled: true, - Source: source, - InstalledVersion: version, - DefaultVersion: available); - i++; - continue; + (name, strId, version) = Relist(name, strId, version, source); + strId = strId.TrimEnd('…'); + id = WingetGameId.From(strId); } - if (strId.EndsWith('…')) + if (installed.TryAdd(id, new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + IsInstalled: true, + IsOwned: true, + Source: source, + InstalledVersion: version, + DefaultVersion: available))) { - (name, strId, version) = Relist(name, strId, version); - id = WingetGameId.From(strId); + _logger?.LogDebug(" " + name); } - yield return GetPackageInfo(colPos, line, search: false); + i++; + continue; + } + + if (strId.EndsWith('…')) + { + (name, strId, version) = Relist(name, strId, version, source); + strId = strId.TrimEnd('…'); + id = WingetGameId.From(strId); + } + if (installed.TryAdd(id, GetPackageInfo(colPos, line, search: false))) + { + _logger?.LogDebug(" " + name); } } + i++; } + _logger?.LogDebug("GetInstalledParse(): " + i + " apps"); + + return installed; } - private (string, string, string) Relist(string name, string id, string version) + private (string, string, string) Relist(string name, string id, string version, string? source) { - var subName = name; - if (name.EndsWith('…')) - subName = name[..^1]; - var subId = id; - if (id.EndsWith('…')) - subId = id[..^1]; - var wingetExe = FindClient(); + if (string.IsNullOrWhiteSpace(source)) + source = "winget"; + using var process = new Process(); process.StartInfo = _startInfo; process.StartInfo.FileName = wingetExe.GetFullPath(); - //process.StartInfo.Arguments = $"list --name \"{subName}\" --nowarn --disable-interactivity"; - process.StartInfo.Arguments = $"list --id \"{subId}\" --nowarn --disable-interactivity"; + //process.StartInfo.Arguments = $"list --name \"{name.TrimEnd('…')}\" --source {source} --nowarn --disable-interactivity"; + process.StartInfo.Arguments = $"list --id \"{id.TrimEnd('…')}\" --source {source} --nowarn --disable-interactivity"; process.Start(); var listOutput = process.StandardOutput.ReadToEnd(); process.WaitForExit(); @@ -445,17 +528,23 @@ private IEnumerable> GetInstalled(bool? expandPa } else if (i > 1 && !string.IsNullOrWhiteSpace(listLine)) // skip separator line { - name = listLine[listCols[0]..listCols[1]].Trim(); - id = listLine[listCols[1]..listCols[2]].Trim(); - version = listLine[listCols[2]..]; + // TODO: This doesn't work if there are surrogate pairs in the string + if (listLine.Length > listCols[1]) + name = listLine[listCols[0]..listCols[1]].Trim(); + if (listLine.Length > listCols[2]) + { + id = listLine[listCols[1]..listCols[2]].Trim(); + version = listLine[listCols[2]..].Trim(); + } } + i++; } return (name, id, version); } - private Dictionary> SearchFreeGames(bool expandPackage = false) + private Dictionary> SearchFreeGamesParse(string query = DefaultQuery, bool expandPackage = false) { Dictionary> freeGames = new(); @@ -464,13 +553,14 @@ private Dictionary> SearchFreeGame using var process = new Process(); process.StartInfo = _startInfo; process.StartInfo.FileName = wingetExe.GetFullPath(); - process.StartInfo.Arguments = "search --tag game --nowarn --disable-interactivity"; + process.StartInfo.Arguments = $"search --tag {query} --source winget --nowarn --disable-interactivity"; process.Start(); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); if (string.IsNullOrEmpty(output)) { + _logger?.LogDebug("***No output from winget"); return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } @@ -495,29 +585,50 @@ private Dictionary> SearchFreeGame } else if (i > 1 && !string.IsNullOrWhiteSpace(line)) // skip separator line { - var name = line[colPos[0]..colPos[1]].Trim(); - var strId = line[colPos[1]..colPos[2]].Trim(); - var id = WingetGameId.From(strId); - var available = line[colPos[2]..colPos[3]].Trim(); - //var match = line[colPos[3]..colPos[4]].Trim(); - var source = line[colPos[4]..].Trim(); + string name = "", strId = "", available = "", match = ""; + string? source = null; + WingetGameId id = default; + // TODO: This doesn't work if there are surrogate pairs in the string + if (line.Length > colPos[1]) + name = line[colPos[0]..colPos[1]].Trim(); + if (line.Length > colPos[2]) + { + strId = line[colPos[1]..colPos[2]].Trim(); + id = WingetGameId.From(strId); + } + if (line.Length > colPos[3]) + available = line[colPos[2]..colPos[3]].Trim(); + if (line.Length > colPos[4]) + { + match = line[colPos[3]..colPos[4]].Trim(); + source = line[colPos[4]..].Trim(); + } if (!expandPackage) { - freeGames.Add(id, new WingetGame( + if (freeGames.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: default, IsInstalled: false, - DefaultVersion: available)); - i++; + IsOwned: false, + DefaultVersion: available))) + { + _logger?.LogDebug("* " + name); + i++; + } continue; } - freeGames.Add(id, GetPackageInfo(colPos, line, search: true)); + if (freeGames.TryAdd(id, GetPackageInfo(colPos, line, search: true))) + { + _logger?.LogDebug("*@" + name); + } } + i++; } + _logger?.LogDebug("SearchFreeGamesParse(): " + i + " games"); return freeGames; } @@ -530,22 +641,22 @@ private OneOf ParseRegistry(string id) var baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine); if (id.StartsWith(@"ARP\Machine\X64\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\Machine\X64\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\Machine\X64\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); } else if (id.StartsWith(@"ARP\Machine\X86\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\Machine\X86\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\Machine\X86\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); } else if (id.StartsWith(@"ARP\User\X64\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\User\X64\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\User\X64\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); } else if (id.StartsWith(@"ARP\User\X86\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\User\X86\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\User\X86\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); } @@ -747,16 +858,18 @@ private OneOf GetPackageInfo(List colPos, string isTags = true; else if (pkgLine.StartsWith(" Installer Url: ", StringComparison.Ordinal)) installerUrl = pkgLine[" Installer Url: ".Length..]; + i++; } - Console.WriteLine("> " + name); + _logger?.LogDebug("> " + name); return new WingetGame( Id: id, Name: name, InstallDirectory: default, InstallerUrl: installerUrl, IsInstalled: true, + IsOwned: true, Description: description, Publisher: publisher, Author: author, From 5e0d51fc59f9d18c4606e05c0ad8da9ca472b8fc Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Fri, 18 Oct 2024 15:37:56 -0700 Subject: [PATCH 6/8] Some tweaks to winget experiment --- Directory.Packages.props | 14 +- other/GameFinder.Example/Program.cs | 2 +- src/Directory.Build.props | 2 +- .../GameCollector.PkgHandlers.Winget.csproj | 2 +- .../WingetGame.cs | 3 + .../WingetHandler.cs | 338 ++++++++++++------ 6 files changed, 242 insertions(+), 119 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 12176566..77dba488 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,9 +15,9 @@ - + - + @@ -27,13 +27,13 @@ - + - - - + + + - + diff --git a/other/GameFinder.Example/Program.cs b/other/GameFinder.Example/Program.cs index 51e1c2a2..ef052ca0 100644 --- a/other/GameFinder.Example/Program.cs +++ b/other/GameFinder.Example/Program.cs @@ -462,7 +462,7 @@ private static void RunWargamingNetHandler(Settings settings, IRegistry registry private static void RunWingetHandler(Settings settings, IRegistry registry, IFileSystem fileSystem) { var logger = _provider.CreateLogger(nameof(WingetHandler)); - var handler = new WingetHandler(registry, fileSystem); + var handler = new WingetHandler(registry, fileSystem, logger); //, logger); LogGamesAndErrors(handler.FindAllGames(settings), logger); } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 15998280..a3209bf4 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ - 4.3.2.1 + 4.3.2.2 Nutzzz, erri120 docs\README.md diff --git a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj index 68e27a54..fe57ff16 100644 --- a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj +++ b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj @@ -1,6 +1,6 @@ - Library for finding installed apps and available packages via Windows Package Manager. + Library for finding installed apps and available game packages via Windows Package Manager. winget windows-package-manager games diff --git a/src/GameCollector.PkgHandlers.Winget/WingetGame.cs b/src/GameCollector.PkgHandlers.Winget/WingetGame.cs index 95356f66..39ff5a8d 100644 --- a/src/GameCollector.PkgHandlers.Winget/WingetGame.cs +++ b/src/GameCollector.PkgHandlers.Winget/WingetGame.cs @@ -16,6 +16,7 @@ namespace GameCollector.PkgHandlers.Winget; /// /// /// +/// /// /// /// @@ -40,6 +41,7 @@ public record WingetGame(WingetGameId Id, string? InstallerUrl = "", AbsolutePath Uninstall = new(), bool IsInstalled = true, + bool IsOwned = true, DateTime? InstallDate = null, string? Description = "", string? Publisher = "", @@ -64,6 +66,7 @@ public record WingetGame(WingetGameId Id, Icon: Launch, Uninstall: Uninstall, IsInstalled: IsInstalled, + IsOwned: IsOwned, InstallDate: InstallDate, Metadata: new Dictionary>(StringComparer.OrdinalIgnoreCase) { diff --git a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs index 6f01a01a..6325e05f 100644 --- a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs +++ b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs @@ -3,10 +3,12 @@ using System.Diagnostics; using System.Globalization; using System.IO; +//using System.Security.Principal; using System.Text; using GameFinder.Common; using GameFinder.RegistryUtils; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; using NexusMods.Paths; using OneOf; @@ -21,11 +23,12 @@ namespace GameCollector.PkgHandlers.Winget; [PublicAPI] public class WingetHandler : AHandler { - internal const string TagQuery = "game"; + internal const string DefaultQuery = "game"; internal const string UninstallRegKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; private readonly IRegistry _registry; private readonly IFileSystem _fileSystem; + private ILogger? _logger; //private WindowsPackageManagerFactory? _winGetFactory = null; @@ -34,6 +37,7 @@ public class WingetHandler : AHandler UseShellExecute = false, RedirectStandardOutput = true, StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, }; @@ -52,10 +56,11 @@ public class WingetHandler : AHandler /// . For tests either use , /// a custom implementation or just a mock of the interface. /// - public WingetHandler(IRegistry registry, IFileSystem fileSystem) + public WingetHandler(IRegistry registry, IFileSystem fileSystem, ILogger? logger = null) { _registry = registry; _fileSystem = fileSystem; + _logger = logger; if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { @@ -89,7 +94,7 @@ public override AbsolutePath FindClient() /// public override IEnumerable> FindAllGames(Settings? settings = null) { - return FindAllGames(settings?.InstalledOnly ?? false); + return FindAllGames(settings?.InstalledOnly ?? false, settings?.OwnedOnly ?? false, settings?.GamesOnly ?? false); } /// @@ -97,9 +102,17 @@ public override IEnumerable> FindAllGames(Settin /// will always be a non-null game or a non-null error message. /// /// + /// + /// + /// /// Get additional information about winget/msstore packages /// - public IEnumerable> FindAllGames(bool? installedOnly = false, bool? expandPackage = false) + public IEnumerable> FindAllGames( + bool installedOnly = false, + bool ownedOnly = false, + bool gamesOnly = false, + string? query = DefaultQuery, + bool expandPackage = false) { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { @@ -114,7 +127,7 @@ public IEnumerable> FindAllGames(bool? installed } */ - Dictionary> games = new(); + Dictionary> installed = new(); // Create Package Manager and get available catalogs //var manager = _winGetFactory.CreatePackageManager(); @@ -123,13 +136,13 @@ public IEnumerable> FindAllGames(bool? installed /* foreach (var catalog in availableCatalogs.ToArray()) { - // Create a filter to search for packages with "game" tag //var filterList = _winGetFactory.CreateFindPackagesOptions(); + // Create a filter to search for packages with query ["game" default] // Add the query to the filter var tagFilter = _winGetFactory.CreatePackageMatchFilter(); tagFilter.Field = PackageMatchField.Tag; - tagFilter.Value = TagQuery; + tagFilter.Value = query; filterList.Filters.Add(tagFilter); // Find the packages with the filters @@ -140,7 +153,7 @@ public IEnumerable> FindAllGames(bool? installed var pkg = match.CatalogPackage; var id = WingetGameId.From(pkg.Id); - games.Add(id, new WingetGame( + installed.Add(id, new WingetGame( Id: id, Name: pkg.Name, InstallDirectory: default, @@ -156,43 +169,67 @@ public IEnumerable> FindAllGames(bool? installed // Get all installed items //var installedCatalogs = manager.GetLocalPackageCatalog; - foreach (var item in GetInstalled(expandPackage)) + if (!gamesOnly) { - if (item.IsT0) + //installed = GetInstalled(); + installed = GetInstalledParse(expandPackage); + foreach (var item in installed) { - if (!games.ContainsKey(item.AsT0.Id)) - continue; - - yield return new WingetGame( - Id: item.AsT0.Id, - Name: item.AsT0.Name, - InstallDirectory: default, - IsInstalled: true, - //PkgTags: item.CatalogPackage.Tags, - //Source: item.CatalogPackage.Source, - InstalledVersion: item.AsT0.InstalledVersion, - DefaultVersion: item.AsT0.DefaultVersion - ); + if (item.Value.IsT0) + { + yield return new WingetGame( + Id: item.Key, + Name: item.Value.AsT0.Name, + InstallDirectory: default, + IsInstalled: true, + IsOwned: true, + //PkgTags: item.CatalogPackage.Tags, + //Source: item.CatalogPackage.Source, + InstalledVersion: item.Value.AsT0.InstalledVersion, + DefaultVersion: item.Value.AsT0.DefaultVersion + ); + } + else yield return item.Value.AsT1; } - else yield return item.AsT1; } //var freeGames = SearchFreeGames(); + if (!installedOnly && !string.IsNullOrEmpty(query)) + { + foreach (var game in SearchFreeGamesParse(query, expandPackage)) + { + if (!installed.ContainsKey(game.Key)) + { + if (game.Value.IsT0) + { + yield return new WingetGame( + Id: game.Key, + Name: game.Value.AsT0.Name, + InstallDirectory: default, + IsInstalled: false, + IsOwned: false, + DefaultVersion: game.Value.AsT0.DefaultVersion + ); + } + else yield return game.Value.AsT1; + } + } + } } - private IEnumerable> GetInstalled() + private Dictionary> GetInstalled() { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { - yield return new ErrorMessage("Only supported on Windows 10.0.19041.0 or later."); - yield break; + _logger?.LogDebug("***Only supported on Windows 10.0.19041.0 or later."); + return new() { [WingetGameId.From("")] = new ErrorMessage("Only supported on Windows 10.0.19041.0 or later.") }; } /* if (_winGetFactory is null) { - yield return new ErrorMessage("Could not access Windows Package Manager."); - yield break; + _logger?.LogDebug("***Could not access Windows Package Manager."); + return new() { [WingetGameId.From("")] = new ErrorMessage("Could not access Windows Package Manager.") }; } */ @@ -212,7 +249,7 @@ private IEnumerable> GetInstalled() { var selectedRemoteCatalogRef = winGetManager.GetPackageCatalogs().ToArray()[selectedIndex]; - //Console.WriteLine($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); + //_logger?.LogDebug($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); var createCompositePackageCatalogOptions = _winGetFactory.CreateCreateCompositePackageCatalogOptions(); createCompositePackageCatalogOptions.Catalogs.Add(selectedRemoteCatalogRef); @@ -242,16 +279,17 @@ private IEnumerable> GetInstalled() foreach (var match in taskResult.Matches.ToArray()) { var pkg = match.CatalogPackage; - //Console.WriteLine($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); + //_logger?.LogDebug($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); yield return new WingetGame( Id: WingetGameId.From(pkg.Id), Name: pkg.Name, InstallDirectory: default, IsInstalled: pkg.DefaultInstallVersion != null, + IsOwned: pkg.DefaultInstallVersion != null, //PackageTags: pkg.Tags???, Source: pkg.DefaultInstallVersion?.Channel, // is this winget vs. msstore? PackageName: pkg.DefaultInstallVersion?.PackageCatalog.Info.Name, - ProductCodes: pkg.DefaultInstallVersion?.ProductCodes.ToList(), + //ProductCodes: pkg.DefaultInstallVersion?.ProductCodes.ToList(), Publisher: pkg.InstalledVersion.Publisher, InstalledVersion: pkg.InstalledVersion.DisplayName, DefaultVersion: pkg.DefaultInstallVersion?.DisplayName); @@ -259,33 +297,46 @@ private IEnumerable> GetInstalled() */ // End enumeration + return new(); } - private IEnumerable> GetInstalled(bool? expandPackage) + private Dictionary> GetInstalled(bool? expandPackage) + { + Dictionary> installed = new(); + return installed; + } + + private Dictionary> GetInstalledParse(bool expandPackage) { //************************************* Console.OutputEncoding = Encoding.UTF8; //************************************* + Dictionary> installed = new(); + var wingetExe = FindClient(); if (wingetExe == default) { - yield return new ErrorMessage("Winget not installed"); - yield break; + _logger?.LogDebug("***Winget not installed"); + return new() { [WingetGameId.From("")] = new ErrorMessage("Winget not installed") }; } using var process = new Process(); process.StartInfo = _startInfo; process.StartInfo.FileName = wingetExe.GetFullPath(); process.StartInfo.Arguments = "list --nowarn --disable-interactivity"; + process.StartInfo.StandardOutputEncoding = Encoding.UTF8; + process.StartInfo.StandardErrorEncoding = Encoding.UTF8; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; process.Start(); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); if (string.IsNullOrEmpty(output)) { - yield return new ErrorMessage("No output from winget"); - yield break; + _logger?.LogDebug("***No output from winget"); + return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } List colPos = new(); @@ -309,31 +360,47 @@ private IEnumerable> GetInstalled(bool? expandPa } else if (i > 1 && !string.IsNullOrWhiteSpace(line)) // skip separator line { - var name = line[colPos[0]..colPos[1]].Trim(); - var strId = line[colPos[1]..colPos[2]].Trim(); - var id = WingetGameId.From(strId); - var version = line[colPos[2]..colPos[3]].Trim(); - var available = line[colPos[3]..colPos[4]].Trim(); - var source = line[colPos[4]..].Trim(); + string name = "", strId = "", version = "", available = ""; + string? source = null; + WingetGameId id = default; + // TODO: This doesn't work if there are surrogate pairs in the string + if (line.Length > colPos[1]) + name = line[colPos[0]..colPos[1]].Trim(); + if (line.Length > colPos[2]) + { + strId = line[colPos[1]..colPos[2]].Trim(); + id = WingetGameId.From(strId); + } + if (line.Length > colPos[3]) + version = line[colPos[2]..colPos[3]].Trim(); + if (line.Length > colPos[4]) + { + available = line[colPos[3]..colPos[4]].Trim(); + source = line[colPos[4]..].Trim(); + } if (string.IsNullOrWhiteSpace(source)) // non-package install { - if (expandPackage != true) + if (!expandPackage) { - Console.WriteLine(" " + name); - yield return new WingetGame( + if (installed.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: default, IsInstalled: true, - InstalledVersion: version); + IsOwned: true, + InstalledVersion: version))) + { + _logger?.LogDebug(" " + name); + } i++; continue; } if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) { - (name, strId, version) = Relist(name, strId, version); + (name, strId, version) = Relist(name, strId, version, source); + strId = strId.TrimEnd('…'); id = WingetGameId.From(strId); } @@ -342,84 +409,105 @@ private IEnumerable> GetInstalled(bool? expandPa var game = ParseRegistry(strId); if (game.IsT0) { - Console.WriteLine("@ " + name); - yield return new WingetGame( + if (installed.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: game.AsT0.InstallDirectory, Launch: game.AsT0.Launch, Uninstall: game.AsT0.Uninstall, IsInstalled: true, + IsOwned: true, InstallDate: game.AsT0.InstallDate, Publisher: game.AsT0.Publisher, SupportUrl: game.AsT0.SupportUrl, Homepage: game.AsT0.Homepage, - InstalledVersion: version); + InstalledVersion: version))) + { + _logger?.LogDebug("@ " + name); + } i++; continue; } - yield return game.AsT1; + if (installed.TryAdd(id, game.AsT1)) + { + _logger?.LogDebug("***" + game.AsT1.Message); + } + i++; } - Console.WriteLine("? " + name); - yield return new WingetGame( + if (installed.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: default, IsInstalled: true, - InstalledVersion: version); + IsOwned: true, + InstalledVersion: version))) + { + _logger?.LogDebug("? " + name); + } + i++; + continue; } - else // package install + + //else [package install] + + if (!expandPackage) { - if (expandPackage != true) + if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) { - if (name.EndsWith('…') || strId.EndsWith('…') || version.EndsWith('…')) - { - (name, strId, version) = Relist(name, strId, version); - id = WingetGameId.From(strId); - } - Console.WriteLine(" " + name); - yield return new WingetGame( - Id: id, - Name: name, - InstallDirectory: default, - IsInstalled: true, - Source: source, - InstalledVersion: version, - DefaultVersion: available); - i++; - continue; + (name, strId, version) = Relist(name, strId, version, source); + strId = strId.TrimEnd('…'); + id = WingetGameId.From(strId); } - if (strId.EndsWith('…')) + if (installed.TryAdd(id, new WingetGame( + Id: id, + Name: name, + InstallDirectory: default, + IsInstalled: true, + IsOwned: true, + Source: source, + InstalledVersion: version, + DefaultVersion: available))) { - (name, strId, version) = Relist(name, strId, version); - id = WingetGameId.From(strId); + _logger?.LogDebug(" " + name); } - yield return GetPackageInfo(colPos, line, search: false); + i++; + continue; + } + + if (strId.EndsWith('…')) + { + (name, strId, version) = Relist(name, strId, version, source); + strId = strId.TrimEnd('…'); + id = WingetGameId.From(strId); + } + if (installed.TryAdd(id, GetPackageInfo(colPos, line, search: false))) + { + _logger?.LogDebug(" " + name); } } + i++; } + _logger?.LogDebug("GetInstalledParse(): " + i + " apps"); + + return installed; } - private (string, string, string) Relist(string name, string id, string version) + private (string, string, string) Relist(string name, string id, string version, string? source) { - var subName = name; - if (name.EndsWith('…')) - subName = name[..^1]; - var subId = id; - if (id.EndsWith('…')) - subId = id[..^1]; - var wingetExe = FindClient(); + if (string.IsNullOrWhiteSpace(source)) + source = "winget"; + using var process = new Process(); process.StartInfo = _startInfo; process.StartInfo.FileName = wingetExe.GetFullPath(); - //process.StartInfo.Arguments = $"list --name \"{subName}\" --nowarn --disable-interactivity"; - process.StartInfo.Arguments = $"list --id \"{subId}\" --nowarn --disable-interactivity"; + //process.StartInfo.Arguments = $"list --name \"{name.TrimEnd('…')}\" --source {source} --nowarn --disable-interactivity"; + process.StartInfo.Arguments = $"list --id \"{id.TrimEnd('…')}\" --source {source} --nowarn --disable-interactivity"; process.Start(); var listOutput = process.StandardOutput.ReadToEnd(); process.WaitForExit(); @@ -445,17 +533,23 @@ private IEnumerable> GetInstalled(bool? expandPa } else if (i > 1 && !string.IsNullOrWhiteSpace(listLine)) // skip separator line { - name = listLine[listCols[0]..listCols[1]].Trim(); - id = listLine[listCols[1]..listCols[2]].Trim(); - version = listLine[listCols[2]..]; + // TODO: This doesn't work if there are surrogate pairs in the string + if (listLine.Length > listCols[1]) + name = listLine[listCols[0]..listCols[1]].Trim(); + if (listLine.Length > listCols[2]) + { + id = listLine[listCols[1]..listCols[2]].Trim(); + version = listLine[listCols[2]..].Trim(); + } } + i++; } return (name, id, version); } - private Dictionary> SearchFreeGames(bool expandPackage = false) + private Dictionary> SearchFreeGamesParse(string query = DefaultQuery, bool expandPackage = false) { Dictionary> freeGames = new(); @@ -464,13 +558,14 @@ private Dictionary> SearchFreeGame using var process = new Process(); process.StartInfo = _startInfo; process.StartInfo.FileName = wingetExe.GetFullPath(); - process.StartInfo.Arguments = "search --tag game --nowarn --disable-interactivity"; + process.StartInfo.Arguments = $"search --tag {query} --source winget --nowarn --disable-interactivity"; process.Start(); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); if (string.IsNullOrEmpty(output)) { + _logger?.LogDebug("***No output from winget"); return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } @@ -495,29 +590,50 @@ private Dictionary> SearchFreeGame } else if (i > 1 && !string.IsNullOrWhiteSpace(line)) // skip separator line { - var name = line[colPos[0]..colPos[1]].Trim(); - var strId = line[colPos[1]..colPos[2]].Trim(); - var id = WingetGameId.From(strId); - var available = line[colPos[2]..colPos[3]].Trim(); - //var match = line[colPos[3]..colPos[4]].Trim(); - var source = line[colPos[4]..].Trim(); + string name = "", strId = "", available = "", match = ""; + string? source = null; + WingetGameId id = default; + // TODO: This doesn't work if there are surrogate pairs in the string + if (line.Length > colPos[1]) + name = line[colPos[0]..colPos[1]].Trim(); + if (line.Length > colPos[2]) + { + strId = line[colPos[1]..colPos[2]].Trim(); + id = WingetGameId.From(strId); + } + if (line.Length > colPos[3]) + available = line[colPos[2]..colPos[3]].Trim(); + if (line.Length > colPos[4]) + { + match = line[colPos[3]..colPos[4]].Trim(); + source = line[colPos[4]..].Trim(); + } if (!expandPackage) { - freeGames.Add(id, new WingetGame( + if (freeGames.TryAdd(id, new WingetGame( Id: id, Name: name, InstallDirectory: default, IsInstalled: false, - DefaultVersion: available)); - i++; + IsOwned: false, + DefaultVersion: available))) + { + _logger?.LogDebug("* " + name); + i++; + } continue; } - freeGames.Add(id, GetPackageInfo(colPos, line, search: true)); + if (freeGames.TryAdd(id, GetPackageInfo(colPos, line, search: true))) + { + _logger?.LogDebug("*@" + name); + } } + i++; } + _logger?.LogDebug("SearchFreeGamesParse(): " + i + " games"); return freeGames; } @@ -526,28 +642,30 @@ private OneOf ParseRegistry(string id) { // id syntax = "ARP\[Machine|User]\[X64|X86]\ - var regKeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; - var baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine); + string? regKeyName; // = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; + IRegistryKey baseKey; // = _registry.OpenBaseKey(RegistryHive.LocalMachine); if (id.StartsWith(@"ARP\Machine\X64\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\Machine\X64\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\Machine\X64\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); } else if (id.StartsWith(@"ARP\Machine\X86\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\Machine\X86\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\Machine\X86\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); } else if (id.StartsWith(@"ARP\User\X64\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\User\X64\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\User\X64\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); } else if (id.StartsWith(@"ARP\User\X86\", StringComparison.Ordinal)) { - regKeyName += id[@"ARP\User\X86\".Length..]; + regKeyName = UninstallRegKey + id[@"ARP\User\X86\".Length..]; baseKey = _registry.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); } + else + return new ErrorMessage("Did not find expected \"ARP\\[Machine|User]\\[X64|X86]\\\" registry key prefix in id " + id); try { @@ -747,16 +865,18 @@ private OneOf GetPackageInfo(List colPos, string isTags = true; else if (pkgLine.StartsWith(" Installer Url: ", StringComparison.Ordinal)) installerUrl = pkgLine[" Installer Url: ".Length..]; + i++; } - Console.WriteLine("> " + name); + _logger?.LogDebug("> " + name); return new WingetGame( Id: id, Name: name, InstallDirectory: default, InstallerUrl: installerUrl, IsInstalled: true, + IsOwned: true, Description: description, Publisher: publisher, Author: author, From 422c46807128e610beec315e4000a9b81b7795a3 Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Mon, 28 Oct 2024 14:32:10 -0700 Subject: [PATCH 7/8] Winget API experiment --- CHANGELOG.md | 23 +- Directory.Build.props | 2 +- Directory.Packages.props | 20 +- GameCollector.sln | 313 ++- .../GameFinder.Benchmarks.csproj | 3 + .../GameFinder.Example.csproj | 20 +- other/GameFinder.Example/Options.cs | 34 +- other/GameFinder.Example/Program.cs | 33 +- src/Directory.Build.props | 2 +- .../GameCollector.Common.csproj | 4 + .../GameCollector.EmuHandlers.Dolphin.csproj | 1 + .../GameCollector.EmuHandlers.MAME.csproj | 1 + .../WinGetConfigurationException.cs | 39 + .../GameCollector.PkgHandlers.Winget.csproj | 79 + .../Microsoft.Management.Deployment.winmd | 1892 +++++++++++++++++ .../NativeMethods.txt | 7 + .../WindowsPackageManager/ClassModel.cs | 52 + .../ClassesDefinition.cs | 117 + .../WindowsPackageManager/ClsidContext.cs | 13 + .../WindowsPackageManagerElevatedFactory.cs | 60 + .../WindowsPackageManagerFactory.cs | 60 + .../WindowsPackageManagerStandardFactory.cs | 53 + .../WingetHandler.cs | 281 ++- .../GameCollector.SQLiteUtils.csproj | 4 + .../GameCollector.StoreHandlers.Amazon.csproj | 1 + .../GameCollector.StoreHandlers.Arc.csproj | 1 + ...meCollector.StoreHandlers.BattleNet.csproj | 1 + ...GameCollector.StoreHandlers.BigFish.csproj | 1 + ...ameCollector.StoreHandlers.GameJolt.csproj | 1 + .../GameCollector.StoreHandlers.Humble.csproj | 1 + ...ameCollector.StoreHandlers.IGClient.csproj | 1 + .../GameCollector.StoreHandlers.Itch.csproj | 1 + .../GameCollector.StoreHandlers.Legacy.csproj | 1 + .../GameCollector.StoreHandlers.Oculus.csproj | 1 + ...GameCollector.StoreHandlers.Paradox.csproj | 1 + ...GameCollector.StoreHandlers.Plarium.csproj | 1 + .../GameCollector.StoreHandlers.Riot.csproj | 1 + ...eCollector.StoreHandlers.RobotCache.csproj | 1 + ...ameCollector.StoreHandlers.Rockstar.csproj | 1 + ...GameCollector.StoreHandlers.Ubisoft.csproj | 1 + ...ollector.StoreHandlers.WargamingNet.csproj | 1 + .../GameCollector.YamlUtils.csproj | 4 + .../GameFinder.Common.csproj | 1 + .../GameFinder.Launcher.Heroic.csproj | 5 +- .../GameFinder.RegistryUtils.csproj | 1 + .../GameFinder.StoreHandlers.EADesktop.csproj | 1 + .../GameFinder.StoreHandlers.EGS.csproj | 1 + .../GameFinder.StoreHandlers.GOG.csproj | 1 + .../GameFinder.StoreHandlers.Origin.csproj | 1 + .../GameFinder.StoreHandlers.Steam.csproj | 3 +- .../ProtonWinePrefix.cs | 2 + .../SteamGame.cs | 2 + .../GameFinder.StoreHandlers.Xbox.csproj | 1 + src/GameFinder.Wine/GameFinder.Wine.csproj | 1 + src/GameFinder/GameFinder.csproj | 6 +- .../GameFinder.Common.Tests.csproj | 3 + .../GameFinder.RegistryUtils.Tests.csproj | 3 + ...inder.StoreHandlers.EADesktop.Tests.csproj | 3 + .../GameFinder.StoreHandlers.EGS.Tests.csproj | 3 + .../GameFinder.StoreHandlers.GOG.Tests.csproj | 3 + ...meFinder.StoreHandlers.Origin.Tests.csproj | 3 + ...ameFinder.StoreHandlers.Steam.Tests.csproj | 3 + ...GameFinder.StoreHandlers.Xbox.Tests.csproj | 3 + .../GameFinder.Wine.Tests.csproj | 3 + tests/TestUtils/TestUtils.csproj | 3 + 65 files changed, 2926 insertions(+), 264 deletions(-) create mode 100644 src/GameCollector.PkgHandlers.Winget/Exceptions/WinGetConfigurationException.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/Microsoft.Management.Deployment.winmd create mode 100644 src/GameCollector.PkgHandlers.Winget/NativeMethods.txt create mode 100644 src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassModel.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassesDefinition.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClsidContext.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerElevatedFactory.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerFactory.cs create mode 100644 src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5122aab8..e7087d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com). Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [Semantic Versioning](https://semver.org), for the moment this project simply uses GameFinder's version number and adds a new digit to the end. Where normally a fourth digit would be used for hotfixes or pre-release versions, because GameCollector is still in its infancy, breaking changes may occur between point releases. Because the scope of this project is so much larger than upstream, eventually a different versioning scheme will need to be used. -## [Unreleased](https://github.com/Nutzzz/GameCollector/compare/v4.3.1.1...HEAD) +## [Unreleased](https://github.com/Nutzzz/GameCollector/compare/v4.4.0.2...HEAD) **TODO**: @@ -16,17 +16,28 @@ Though upstream [GameFinder](https://github.com/erri120/GameFinder) adheres to [ **WIP**: -- Add [PkgHandlers](https://github.com/Nutzzz/GameCollector/pull/40) to find installed software, and to download free games - Add [DataHandlers](https://github.com/Nutzzz/GameCollector/pull/13) to download metadata and images from online sources -## [4.2.4-2](https://github.com/Nutzzz/GameCollector/compare/v4.2.4.1...v4.2.4.2) - UNRELEASED +## [4.4.0-2](https://github.com/Nutzzz/GameCollector/compare/v4.4.0.1...v4.4.0.2) - UNRELEASED ### Added -- Add Winget package handler to detect both installed apps and show free game packages +- Add Winget package handler to detect installed apps and free game packages ## [Released](https://github.com/Nutzzz/GameCollector/releases) +## [4.3.3-1](https://github.com/Nutzzz/GameCollector/compare/v4.3.2.1...v4.3.3.1) - 2024-10-09 + +### Fixed + +- Upstream: Wine: `users` instead of `Users` + +## [4.3.2-1](https://github.com/Nutzzz/GameCollector/compare/v4.3.1.1...v4.3.2.1) - 2024-10-09 + +### Changed + +- Upstream: Heroic: validate that file exists before parsing + ## [4.3.1-1](https://github.com/Nutzzz/GameCollector/compare/v4.2.4.1...v4.3.1.1) - 2024-10-01 ### Added @@ -175,6 +186,10 @@ Initial release of GameCollector ## [Released](https://github.com/erri120/GameFinder/releases) +## [4.3.3](https://github.com/erri120/GameFinder/compare/v4.3.2...v4.3.3) - 2024-10-31 + +- Wine: `users` instead of `Users` + ## [4.3.2](https://github.com/erri120/GameFinder/compare/v4.3.1...v4.3.2) - 2024-10-07 - Heroic: validate that file exists before parsing ([#155](https://github.com/erri120/GameFinder/pull/155)). Thanks @MistaOmega! diff --git a/Directory.Build.props b/Directory.Build.props index 851d89cf..7d36c4f9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net8.0 true - 4.3.2 + 4.4.0 Nutzzz, erri120 https://github.com/Nutzzz/GameCollector git diff --git a/Directory.Packages.props b/Directory.Packages.props index 77dba488..a964b976 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,25 +15,27 @@ - + - - + - - - - - + + + + - + diff --git a/GameCollector.sln b/GameCollector.sln index 2719371b..74597c52 100644 --- a/GameCollector.sln +++ b/GameCollector.sln @@ -4,40 +4,40 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 17.6.33723.286 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solutionItems", ".solutionItems", "{87C3DD03-31FE-4D00-B7BF-D492D6299427}" -ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - CHANGELOG.md = CHANGELOG.md - LICENSE = LICENSE - README.md = README.md - CONTRIBUTING.md = CONTRIBUTING.md - renovate.json = renovate.json - codecov.yml = codecov.yml - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - Directory.Packages.props = Directory.Packages.props -EndProjectSection + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md + codecov.yml = codecov.yml + CONTRIBUTING.md = CONTRIBUTING.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.Packages.props = Directory.Packages.props + LICENSE = LICENSE + README.md = README.md + renovate.json = renovate.json + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.RegistryUtils", "src\GameFinder.RegistryUtils\GameFinder.RegistryUtils.csproj", "{BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.RegistryUtils", "src\GameFinder.RegistryUtils\GameFinder.RegistryUtils.csproj", "{BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.Steam", "src\GameFinder.StoreHandlers.Steam\GameFinder.StoreHandlers.Steam.csproj", "{3CADF882-5158-4D2E-B15C-D54F5FE89B30}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.Steam", "src\GameFinder.StoreHandlers.Steam\GameFinder.StoreHandlers.Steam.csproj", "{3CADF882-5158-4D2E-B15C-D54F5FE89B30}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.GOG", "src\GameFinder.StoreHandlers.GOG\GameFinder.StoreHandlers.GOG.csproj", "{6F00D927-358E-41A4-8DDF-471B1514128B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.GOG", "src\GameFinder.StoreHandlers.GOG\GameFinder.StoreHandlers.GOG.csproj", "{6F00D927-358E-41A4-8DDF-471B1514128B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.EGS", "src\GameFinder.StoreHandlers.EGS\GameFinder.StoreHandlers.EGS.csproj", "{58A62CC7-4A61-4218-9F93-CE85FDF00E15}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.EGS", "src\GameFinder.StoreHandlers.EGS\GameFinder.StoreHandlers.EGS.csproj", "{58A62CC7-4A61-4218-9F93-CE85FDF00E15}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StoreHandlers", "StoreHandlers", "{B765999C-AB3D-495F-9F5E-9108F2D4FE33}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Example", "other\GameFinder.Example\GameFinder.Example.csproj", "{DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Example", "other\GameFinder.Example\GameFinder.Example.csproj", "{DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.Origin", "src\GameFinder.StoreHandlers.Origin\GameFinder.StoreHandlers.Origin.csproj", "{82126DC0-0DF8-4430-9DB7-7326A0192E48}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.Origin", "src\GameFinder.StoreHandlers.Origin\GameFinder.StoreHandlers.Origin.csproj", "{82126DC0-0DF8-4430-9DB7-7326A0192E48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Common", "src\GameFinder.Common\GameFinder.Common.csproj", "{4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Common", "src\GameFinder.Common\GameFinder.Common.csproj", "{4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Benchmarks", "other\GameFinder.Benchmarks\GameFinder.Benchmarks.csproj", "{E91CA2ED-F46D-484B-8759-E7ADED282647}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Benchmarks", "other\GameFinder.Benchmarks\GameFinder.Benchmarks.csproj", "{E91CA2ED-F46D-484B-8759-E7ADED282647}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Common.Tests", "tests\GameFinder.Common.Tests\GameFinder.Common.Tests.csproj", "{17A107B4-38CF-469F-AE09-AA5795734D48}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Common.Tests", "tests\GameFinder.Common.Tests\GameFinder.Common.Tests.csproj", "{17A107B4-38CF-469F-AE09-AA5795734D48}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EFB4E7DB-DE24-4BDE-B660-DAF1394ECC21}" ProjectSection(SolutionItems) = preProject @@ -49,8 +49,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9C6671E1 tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets tests\NexusMods.Paths.TestingHelpers.props = tests\NexusMods.Paths.TestingHelpers.props - tests\SharedUsings.cs = tests\SharedUsings.cs tests\SharedAssemblyAttributes.cs = tests\SharedAssemblyAttributes.cs + tests\SharedUsings.cs = tests\SharedUsings.cs EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "other", "other", "{7EA29B58-0E2E-4714-81AC-1F86FE253664}" @@ -60,278 +60,465 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "other", "other", "{7EA29B58 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{41011D08-70EF-410D-83E7-7BEAF795494E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.RegistryUtils.Tests", "tests\GameFinder.RegistryUtils.Tests\GameFinder.RegistryUtils.Tests.csproj", "{EAE6E28E-DDA5-417D-985F-57B8ADCCA981}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.RegistryUtils.Tests", "tests\GameFinder.RegistryUtils.Tests\GameFinder.RegistryUtils.Tests.csproj", "{EAE6E28E-DDA5-417D-985F-57B8ADCCA981}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.EGS.Tests", "tests\GameFinder.StoreHandlers.EGS.Tests\GameFinder.StoreHandlers.EGS.Tests.csproj", "{CCC94557-9698-4468-AAE6-69CB28C676E2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.EGS.Tests", "tests\GameFinder.StoreHandlers.EGS.Tests\GameFinder.StoreHandlers.EGS.Tests.csproj", "{CCC94557-9698-4468-AAE6-69CB28C676E2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.GOG.Tests", "tests\GameFinder.StoreHandlers.GOG.Tests\GameFinder.StoreHandlers.GOG.Tests.csproj", "{37E8F2F8-FED5-4722-934F-621C4CE085F4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.GOG.Tests", "tests\GameFinder.StoreHandlers.GOG.Tests\GameFinder.StoreHandlers.GOG.Tests.csproj", "{37E8F2F8-FED5-4722-934F-621C4CE085F4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.Origin.Tests", "tests\GameFinder.StoreHandlers.Origin.Tests\GameFinder.StoreHandlers.Origin.Tests.csproj", "{18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.Origin.Tests", "tests\GameFinder.StoreHandlers.Origin.Tests\GameFinder.StoreHandlers.Origin.Tests.csproj", "{18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.Steam.Tests", "tests\GameFinder.StoreHandlers.Steam.Tests\GameFinder.StoreHandlers.Steam.Tests.csproj", "{0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.Steam.Tests", "tests\GameFinder.StoreHandlers.Steam.Tests\GameFinder.StoreHandlers.Steam.Tests.csproj", "{0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "tests\TestUtils\TestUtils.csproj", "{C26BCA96-CF68-41F5-AC55-B24EF0545E4A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtils", "tests\TestUtils\TestUtils.csproj", "{C26BCA96-CF68-41F5-AC55-B24EF0545E4A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.EADesktop", "src\GameFinder.StoreHandlers.EADesktop\GameFinder.StoreHandlers.EADesktop.csproj", "{F7807045-BAA2-46F6-81A6-0127FA662AC8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.EADesktop", "src\GameFinder.StoreHandlers.EADesktop\GameFinder.StoreHandlers.EADesktop.csproj", "{F7807045-BAA2-46F6-81A6-0127FA662AC8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.EADesktop.Tests", "tests\GameFinder.StoreHandlers.EADesktop.Tests\GameFinder.StoreHandlers.EADesktop.Tests.csproj", "{519FD898-CFD1-4093-833F-510BE8BD9379}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.EADesktop.Tests", "tests\GameFinder.StoreHandlers.EADesktop.Tests\GameFinder.StoreHandlers.EADesktop.Tests.csproj", "{519FD898-CFD1-4093-833F-510BE8BD9379}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wine", "Wine", "{21F0CD9E-DC99-49E5-B451-A8A111664E6E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Wine", "src\GameFinder.Wine\GameFinder.Wine.csproj", "{F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Wine", "src\GameFinder.Wine\GameFinder.Wine.csproj", "{F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Wine.Tests", "tests\GameFinder.Wine.Tests\GameFinder.Wine.Tests.csproj", "{C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Wine.Tests", "tests\GameFinder.Wine.Tests\GameFinder.Wine.Tests.csproj", "{C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.Xbox", "src\GameFinder.StoreHandlers.Xbox\GameFinder.StoreHandlers.Xbox.csproj", "{2F75FCA7-DCE5-4162-B2AE-24994D14FC03}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.Xbox", "src\GameFinder.StoreHandlers.Xbox\GameFinder.StoreHandlers.Xbox.csproj", "{2F75FCA7-DCE5-4162-B2AE-24994D14FC03}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.StoreHandlers.Xbox.Tests", "tests\GameFinder.StoreHandlers.Xbox.Tests\GameFinder.StoreHandlers.Xbox.Tests.csproj", "{7D04AF8D-E8AE-44DB-AB36-5452594DC789}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.StoreHandlers.Xbox.Tests", "tests\GameFinder.StoreHandlers.Xbox.Tests\GameFinder.StoreHandlers.Xbox.Tests.csproj", "{7D04AF8D-E8AE-44DB-AB36-5452594DC789}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder", "src\GameFinder\GameFinder.csproj", "{8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder", "src\GameFinder\GameFinder.csproj", "{8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}" + ProjectSection(ProjectDependencies) = postProject + {7D1B9F43-C54A-4903-9839-AAE845A41592} = {7D1B9F43-C54A-4903-9839-AAE845A41592} + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFinder.Launcher.Heroic", "src\GameFinder.Launcher.Heroic\GameFinder.Launcher.Heroic.csproj", "{F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameFinder.Launcher.Heroic", "src\GameFinder.Launcher.Heroic\GameFinder.Launcher.Heroic.csproj", "{F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.Common", "src\GameCollector.Common\GameCollector.Common.csproj", "{C1BA59AD-3143-4102-96F0-07D745198398}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.Common", "src\GameCollector.Common\GameCollector.Common.csproj", "{C1BA59AD-3143-4102-96F0-07D745198398}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.SQLiteUtils", "src\GameCollector.SQLiteUtils\GameCollector.SQLiteUtils.csproj", "{B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.SQLiteUtils", "src\GameCollector.SQLiteUtils\GameCollector.SQLiteUtils.csproj", "{B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.YamlUtils", "src\GameCollector.YamlUtils\GameCollector.YamlUtils.csproj", "{FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.YamlUtils", "src\GameCollector.YamlUtils\GameCollector.YamlUtils.csproj", "{FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Amazon", "src\GameCollector.StoreHandlers.Amazon\GameCollector.StoreHandlers.Amazon.csproj", "{158A060B-0036-4CE0-8C4B-CDD2F876A1ED}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Amazon", "src\GameCollector.StoreHandlers.Amazon\GameCollector.StoreHandlers.Amazon.csproj", "{158A060B-0036-4CE0-8C4B-CDD2F876A1ED}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Arc", "src\GameCollector.StoreHandlers.Arc\GameCollector.StoreHandlers.Arc.csproj", "{F411D87A-7833-44F4-9B41-814C50292CAE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Arc", "src\GameCollector.StoreHandlers.Arc\GameCollector.StoreHandlers.Arc.csproj", "{F411D87A-7833-44F4-9B41-814C50292CAE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.BattleNet", "src\GameCollector.StoreHandlers.BattleNet\GameCollector.StoreHandlers.BattleNet.csproj", "{82823466-00E1-4D38-8CEF-1E7955B4C5A4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.BattleNet", "src\GameCollector.StoreHandlers.BattleNet\GameCollector.StoreHandlers.BattleNet.csproj", "{82823466-00E1-4D38-8CEF-1E7955B4C5A4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.BigFish", "src\GameCollector.StoreHandlers.BigFish\GameCollector.StoreHandlers.BigFish.csproj", "{FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.BigFish", "src\GameCollector.StoreHandlers.BigFish\GameCollector.StoreHandlers.BigFish.csproj", "{FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.GameJolt", "src\GameCollector.StoreHandlers.GameJolt\GameCollector.StoreHandlers.GameJolt.csproj", "{FAEE9122-5E0D-4246-A9FC-A346295A82FD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.GameJolt", "src\GameCollector.StoreHandlers.GameJolt\GameCollector.StoreHandlers.GameJolt.csproj", "{FAEE9122-5E0D-4246-A9FC-A346295A82FD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Humble", "src\GameCollector.StoreHandlers.Humble\GameCollector.StoreHandlers.Humble.csproj", "{5074B706-14AD-4104-A2DA-EBBD36B19255}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Humble", "src\GameCollector.StoreHandlers.Humble\GameCollector.StoreHandlers.Humble.csproj", "{5074B706-14AD-4104-A2DA-EBBD36B19255}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.IGClient", "src\GameCollector.StoreHandlers.IGClient\GameCollector.StoreHandlers.IGClient.csproj", "{06AA6AE5-2FAC-4761-9D44-CA11A3912256}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.IGClient", "src\GameCollector.StoreHandlers.IGClient\GameCollector.StoreHandlers.IGClient.csproj", "{06AA6AE5-2FAC-4761-9D44-CA11A3912256}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Itch", "src\GameCollector.StoreHandlers.Itch\GameCollector.StoreHandlers.Itch.csproj", "{C8DE7215-67F5-494E-B7AB-088270CB28C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Itch", "src\GameCollector.StoreHandlers.Itch\GameCollector.StoreHandlers.Itch.csproj", "{C8DE7215-67F5-494E-B7AB-088270CB28C3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Legacy", "src\GameCollector.StoreHandlers.Legacy\GameCollector.StoreHandlers.Legacy.csproj", "{64EA04B2-A7A7-4184-AB99-180F084033BC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Legacy", "src\GameCollector.StoreHandlers.Legacy\GameCollector.StoreHandlers.Legacy.csproj", "{64EA04B2-A7A7-4184-AB99-180F084033BC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Oculus", "src\GameCollector.StoreHandlers.Oculus\GameCollector.StoreHandlers.Oculus.csproj", "{487FD285-16EB-46AD-AF82-9F058D2A4141}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Oculus", "src\GameCollector.StoreHandlers.Oculus\GameCollector.StoreHandlers.Oculus.csproj", "{487FD285-16EB-46AD-AF82-9F058D2A4141}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Paradox", "src\GameCollector.StoreHandlers.Paradox\GameCollector.StoreHandlers.Paradox.csproj", "{DD16375A-6112-49F4-A4D3-9B1524B952B3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Paradox", "src\GameCollector.StoreHandlers.Paradox\GameCollector.StoreHandlers.Paradox.csproj", "{DD16375A-6112-49F4-A4D3-9B1524B952B3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Plarium", "src\GameCollector.StoreHandlers.Plarium\GameCollector.StoreHandlers.Plarium.csproj", "{6147F764-C8B3-4D80-9311-52974100B916}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Plarium", "src\GameCollector.StoreHandlers.Plarium\GameCollector.StoreHandlers.Plarium.csproj", "{6147F764-C8B3-4D80-9311-52974100B916}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Riot", "src\GameCollector.StoreHandlers.Riot\GameCollector.StoreHandlers.Riot.csproj", "{0E5D8910-979B-46B8-B32F-12298707F892}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Riot", "src\GameCollector.StoreHandlers.Riot\GameCollector.StoreHandlers.Riot.csproj", "{0E5D8910-979B-46B8-B32F-12298707F892}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.RobotCache", "src\GameCollector.StoreHandlers.RobotCache\GameCollector.StoreHandlers.RobotCache.csproj", "{7D1B9F43-C54A-4903-9839-AAE845A41592}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.RobotCache", "src\GameCollector.StoreHandlers.RobotCache\GameCollector.StoreHandlers.RobotCache.csproj", "{7D1B9F43-C54A-4903-9839-AAE845A41592}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Rockstar", "src\GameCollector.StoreHandlers.Rockstar\GameCollector.StoreHandlers.Rockstar.csproj", "{67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Rockstar", "src\GameCollector.StoreHandlers.Rockstar\GameCollector.StoreHandlers.Rockstar.csproj", "{67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.Ubisoft", "src\GameCollector.StoreHandlers.Ubisoft\GameCollector.StoreHandlers.Ubisoft.csproj", "{E939182C-42EC-474A-952C-064FE757CDCA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.Ubisoft", "src\GameCollector.StoreHandlers.Ubisoft\GameCollector.StoreHandlers.Ubisoft.csproj", "{E939182C-42EC-474A-952C-064FE757CDCA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.StoreHandlers.WargamingNet", "src\GameCollector.StoreHandlers.WargamingNet\GameCollector.StoreHandlers.WargamingNet.csproj", "{6368DFB4-1E79-4D24-9603-17449F0C2F71}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.StoreHandlers.WargamingNet", "src\GameCollector.StoreHandlers.WargamingNet\GameCollector.StoreHandlers.WargamingNet.csproj", "{6368DFB4-1E79-4D24-9603-17449F0C2F71}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EmuHandlers", "EmuHandlers", "{1F5F230D-146C-4794-A2E6-F9C13279D721}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.EmuHandlers.Dolphin", "src\GameCollector.EmuHandlers.Dolphin\GameCollector.EmuHandlers.Dolphin.csproj", "{6173D1F4-48C2-4B14-B907-C7A746CF081D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.EmuHandlers.Dolphin", "src\GameCollector.EmuHandlers.Dolphin\GameCollector.EmuHandlers.Dolphin.csproj", "{6173D1F4-48C2-4B14-B907-C7A746CF081D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.EmuHandlers.MAME", "src\GameCollector.EmuHandlers.MAME\GameCollector.EmuHandlers.MAME.csproj", "{EA0A79A3-639B-4983-A28F-1D07EC1113C7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.EmuHandlers.MAME", "src\GameCollector.EmuHandlers.MAME\GameCollector.EmuHandlers.MAME.csproj", "{EA0A79A3-639B-4983-A28F-1D07EC1113C7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PkgHandlers", "PkgHandlers", "{55345886-D1F6-4733-9943-1283AF39CEA5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCollector.PkgHandlers.Winget", "src\GameCollector.PkgHandlers.Winget\GameCollector.PkgHandlers.Winget.csproj", "{7C74842C-69FE-4908-BA71-210652A15744}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GameCollector.PkgHandlers.Winget", "src\GameCollector.PkgHandlers.Winget\GameCollector.PkgHandlers.Winget.csproj", "{7C74842C-69FE-4908-BA71-210652A15744}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataHandlers", "DataHandlers", "{82816E34-196A-4304-8A28-BF22FA937DF0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Debug|x64.ActiveCfg = Debug|x64 + {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Debug|x64.Build.0 = Debug|x64 {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Release|Any CPU.Build.0 = Release|Any CPU + {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Release|x64.ActiveCfg = Release|x64 + {BED6FB0B-C200-4651-BCE8-2DD9210FC7CF}.Release|x64.Build.0 = Release|x64 {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Debug|x64.ActiveCfg = Debug|x64 + {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Debug|x64.Build.0 = Debug|x64 {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Release|Any CPU.ActiveCfg = Release|Any CPU {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Release|Any CPU.Build.0 = Release|Any CPU + {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Release|x64.ActiveCfg = Release|x64 + {3CADF882-5158-4D2E-B15C-D54F5FE89B30}.Release|x64.Build.0 = Release|x64 {6F00D927-358E-41A4-8DDF-471B1514128B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6F00D927-358E-41A4-8DDF-471B1514128B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F00D927-358E-41A4-8DDF-471B1514128B}.Debug|x64.ActiveCfg = Debug|x64 + {6F00D927-358E-41A4-8DDF-471B1514128B}.Debug|x64.Build.0 = Debug|x64 {6F00D927-358E-41A4-8DDF-471B1514128B}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F00D927-358E-41A4-8DDF-471B1514128B}.Release|Any CPU.Build.0 = Release|Any CPU + {6F00D927-358E-41A4-8DDF-471B1514128B}.Release|x64.ActiveCfg = Release|x64 + {6F00D927-358E-41A4-8DDF-471B1514128B}.Release|x64.Build.0 = Release|x64 {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Debug|x64.ActiveCfg = Debug|x64 + {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Debug|x64.Build.0 = Debug|x64 {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Release|Any CPU.ActiveCfg = Release|Any CPU {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Release|Any CPU.Build.0 = Release|Any CPU + {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Release|x64.ActiveCfg = Release|x64 + {58A62CC7-4A61-4218-9F93-CE85FDF00E15}.Release|x64.Build.0 = Release|x64 {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Debug|x64.ActiveCfg = Debug|x64 + {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Debug|x64.Build.0 = Debug|x64 {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Release|Any CPU.Build.0 = Release|Any CPU + {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Release|x64.ActiveCfg = Release|x64 + {DFC0FA99-91A5-4CC0-A12C-1CAA84D2CB53}.Release|x64.Build.0 = Release|x64 {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Debug|x64.ActiveCfg = Debug|x64 + {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Debug|x64.Build.0 = Debug|x64 {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Release|Any CPU.ActiveCfg = Release|Any CPU {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Release|Any CPU.Build.0 = Release|Any CPU + {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Release|x64.ActiveCfg = Release|x64 + {82126DC0-0DF8-4430-9DB7-7326A0192E48}.Release|x64.Build.0 = Release|x64 {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Debug|x64.ActiveCfg = Debug|x64 + {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Debug|x64.Build.0 = Debug|x64 {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Release|Any CPU.Build.0 = Release|Any CPU + {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Release|x64.ActiveCfg = Release|x64 + {4DD3B263-D44B-4D4D-9FDD-9AA6DA8DEDDD}.Release|x64.Build.0 = Release|x64 {E91CA2ED-F46D-484B-8759-E7ADED282647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E91CA2ED-F46D-484B-8759-E7ADED282647}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E91CA2ED-F46D-484B-8759-E7ADED282647}.Debug|x64.ActiveCfg = Debug|x64 + {E91CA2ED-F46D-484B-8759-E7ADED282647}.Debug|x64.Build.0 = Debug|x64 {E91CA2ED-F46D-484B-8759-E7ADED282647}.Release|Any CPU.ActiveCfg = Release|Any CPU {E91CA2ED-F46D-484B-8759-E7ADED282647}.Release|Any CPU.Build.0 = Release|Any CPU + {E91CA2ED-F46D-484B-8759-E7ADED282647}.Release|x64.ActiveCfg = Release|x64 + {E91CA2ED-F46D-484B-8759-E7ADED282647}.Release|x64.Build.0 = Release|x64 {17A107B4-38CF-469F-AE09-AA5795734D48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17A107B4-38CF-469F-AE09-AA5795734D48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17A107B4-38CF-469F-AE09-AA5795734D48}.Debug|x64.ActiveCfg = Debug|x64 + {17A107B4-38CF-469F-AE09-AA5795734D48}.Debug|x64.Build.0 = Debug|x64 {17A107B4-38CF-469F-AE09-AA5795734D48}.Release|Any CPU.ActiveCfg = Release|Any CPU {17A107B4-38CF-469F-AE09-AA5795734D48}.Release|Any CPU.Build.0 = Release|Any CPU + {17A107B4-38CF-469F-AE09-AA5795734D48}.Release|x64.ActiveCfg = Release|x64 + {17A107B4-38CF-469F-AE09-AA5795734D48}.Release|x64.Build.0 = Release|x64 {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Debug|x64.ActiveCfg = Debug|x64 + {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Debug|x64.Build.0 = Debug|x64 {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Release|Any CPU.Build.0 = Release|Any CPU + {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Release|x64.ActiveCfg = Release|x64 + {EAE6E28E-DDA5-417D-985F-57B8ADCCA981}.Release|x64.Build.0 = Release|x64 {CCC94557-9698-4468-AAE6-69CB28C676E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CCC94557-9698-4468-AAE6-69CB28C676E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCC94557-9698-4468-AAE6-69CB28C676E2}.Debug|x64.ActiveCfg = Debug|x64 + {CCC94557-9698-4468-AAE6-69CB28C676E2}.Debug|x64.Build.0 = Debug|x64 {CCC94557-9698-4468-AAE6-69CB28C676E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {CCC94557-9698-4468-AAE6-69CB28C676E2}.Release|Any CPU.Build.0 = Release|Any CPU + {CCC94557-9698-4468-AAE6-69CB28C676E2}.Release|x64.ActiveCfg = Release|x64 + {CCC94557-9698-4468-AAE6-69CB28C676E2}.Release|x64.Build.0 = Release|x64 {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Debug|x64.ActiveCfg = Debug|x64 + {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Debug|x64.Build.0 = Debug|x64 {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Release|Any CPU.Build.0 = Release|Any CPU + {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Release|x64.ActiveCfg = Release|x64 + {37E8F2F8-FED5-4722-934F-621C4CE085F4}.Release|x64.Build.0 = Release|x64 {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Debug|x64.ActiveCfg = Debug|x64 + {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Debug|x64.Build.0 = Debug|x64 {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Release|Any CPU.Build.0 = Release|Any CPU + {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Release|x64.ActiveCfg = Release|x64 + {18AD8AE9-4AAF-4322-8F8E-2DBCDC40D9A9}.Release|x64.Build.0 = Release|x64 {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Debug|x64.ActiveCfg = Debug|x64 + {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Debug|x64.Build.0 = Debug|x64 {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Release|Any CPU.Build.0 = Release|Any CPU + {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Release|x64.ActiveCfg = Release|x64 + {0A89AE18-0C30-4FD6-874A-7DC5DE6FB391}.Release|x64.Build.0 = Release|x64 {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Debug|x64.ActiveCfg = Debug|x64 + {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Debug|x64.Build.0 = Debug|x64 {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Release|Any CPU.Build.0 = Release|Any CPU + {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Release|x64.ActiveCfg = Release|x64 + {C26BCA96-CF68-41F5-AC55-B24EF0545E4A}.Release|x64.Build.0 = Release|x64 {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Debug|x64.ActiveCfg = Debug|x64 + {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Debug|x64.Build.0 = Debug|x64 {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Release|Any CPU.Build.0 = Release|Any CPU + {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Release|x64.ActiveCfg = Release|x64 + {F7807045-BAA2-46F6-81A6-0127FA662AC8}.Release|x64.Build.0 = Release|x64 {519FD898-CFD1-4093-833F-510BE8BD9379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {519FD898-CFD1-4093-833F-510BE8BD9379}.Debug|Any CPU.Build.0 = Debug|Any CPU + {519FD898-CFD1-4093-833F-510BE8BD9379}.Debug|x64.ActiveCfg = Debug|x64 + {519FD898-CFD1-4093-833F-510BE8BD9379}.Debug|x64.Build.0 = Debug|x64 {519FD898-CFD1-4093-833F-510BE8BD9379}.Release|Any CPU.ActiveCfg = Release|Any CPU {519FD898-CFD1-4093-833F-510BE8BD9379}.Release|Any CPU.Build.0 = Release|Any CPU + {519FD898-CFD1-4093-833F-510BE8BD9379}.Release|x64.ActiveCfg = Release|x64 + {519FD898-CFD1-4093-833F-510BE8BD9379}.Release|x64.Build.0 = Release|x64 {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Debug|x64.ActiveCfg = Debug|x64 + {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Debug|x64.Build.0 = Debug|x64 {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Release|Any CPU.Build.0 = Release|Any CPU + {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Release|x64.ActiveCfg = Release|x64 + {F553AEEA-FD48-4C84-B5A1-7CC31F2F76D5}.Release|x64.Build.0 = Release|x64 {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Debug|x64.ActiveCfg = Debug|x64 + {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Debug|x64.Build.0 = Debug|x64 {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Release|Any CPU.Build.0 = Release|Any CPU + {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Release|x64.ActiveCfg = Release|x64 + {C1FA1F2C-8CC2-4EFF-8206-8B52A1B4FBFE}.Release|x64.Build.0 = Release|x64 {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Debug|x64.ActiveCfg = Debug|x64 + {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Debug|x64.Build.0 = Debug|x64 {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Release|Any CPU.Build.0 = Release|Any CPU + {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Release|x64.ActiveCfg = Release|x64 + {2F75FCA7-DCE5-4162-B2AE-24994D14FC03}.Release|x64.Build.0 = Release|x64 {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Debug|x64.ActiveCfg = Debug|x64 + {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Debug|x64.Build.0 = Debug|x64 {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Release|Any CPU.Build.0 = Release|Any CPU + {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Release|x64.ActiveCfg = Release|x64 + {7D04AF8D-E8AE-44DB-AB36-5452594DC789}.Release|x64.Build.0 = Release|x64 {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Debug|x64.ActiveCfg = Debug|x64 + {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Debug|x64.Build.0 = Debug|x64 {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Release|Any CPU.Build.0 = Release|Any CPU + {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Release|x64.ActiveCfg = Release|x64 + {8F9D4B7D-5269-4053-BB20-9DB7B5F8DF8E}.Release|x64.Build.0 = Release|x64 {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Debug|x64.ActiveCfg = Debug|x64 + {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Debug|x64.Build.0 = Debug|x64 {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Release|Any CPU.Build.0 = Release|Any CPU + {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Release|x64.ActiveCfg = Release|x64 + {F0805D2B-7B2C-4F54-A09A-D1FBE02F3D35}.Release|x64.Build.0 = Release|x64 {C1BA59AD-3143-4102-96F0-07D745198398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C1BA59AD-3143-4102-96F0-07D745198398}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1BA59AD-3143-4102-96F0-07D745198398}.Debug|x64.ActiveCfg = Debug|x64 + {C1BA59AD-3143-4102-96F0-07D745198398}.Debug|x64.Build.0 = Debug|x64 {C1BA59AD-3143-4102-96F0-07D745198398}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1BA59AD-3143-4102-96F0-07D745198398}.Release|Any CPU.Build.0 = Release|Any CPU + {C1BA59AD-3143-4102-96F0-07D745198398}.Release|x64.ActiveCfg = Release|x64 + {C1BA59AD-3143-4102-96F0-07D745198398}.Release|x64.Build.0 = Release|x64 {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Debug|x64.ActiveCfg = Debug|x64 + {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Debug|x64.Build.0 = Debug|x64 {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Release|Any CPU.Build.0 = Release|Any CPU + {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Release|x64.ActiveCfg = Release|x64 + {B399A554-BDD6-4A2D-A71A-5CC88FF4DE4C}.Release|x64.Build.0 = Release|x64 {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Debug|x64.ActiveCfg = Debug|x64 + {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Debug|x64.Build.0 = Debug|x64 {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Release|Any CPU.Build.0 = Release|Any CPU + {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Release|x64.ActiveCfg = Release|x64 + {FD458F9E-A2BC-4E60-B26A-F8DEC2AD7DDD}.Release|x64.Build.0 = Release|x64 {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Debug|x64.ActiveCfg = Debug|x64 + {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Debug|x64.Build.0 = Debug|x64 {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Release|Any CPU.ActiveCfg = Release|Any CPU {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Release|Any CPU.Build.0 = Release|Any CPU + {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Release|x64.ActiveCfg = Release|x64 + {158A060B-0036-4CE0-8C4B-CDD2F876A1ED}.Release|x64.Build.0 = Release|x64 {F411D87A-7833-44F4-9B41-814C50292CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F411D87A-7833-44F4-9B41-814C50292CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F411D87A-7833-44F4-9B41-814C50292CAE}.Debug|x64.ActiveCfg = Debug|x64 + {F411D87A-7833-44F4-9B41-814C50292CAE}.Debug|x64.Build.0 = Debug|x64 {F411D87A-7833-44F4-9B41-814C50292CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {F411D87A-7833-44F4-9B41-814C50292CAE}.Release|Any CPU.Build.0 = Release|Any CPU + {F411D87A-7833-44F4-9B41-814C50292CAE}.Release|x64.ActiveCfg = Release|x64 + {F411D87A-7833-44F4-9B41-814C50292CAE}.Release|x64.Build.0 = Release|x64 {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Debug|x64.ActiveCfg = Debug|x64 + {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Debug|x64.Build.0 = Debug|x64 {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Release|Any CPU.Build.0 = Release|Any CPU + {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Release|x64.ActiveCfg = Release|x64 + {82823466-00E1-4D38-8CEF-1E7955B4C5A4}.Release|x64.Build.0 = Release|x64 {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Debug|x64.ActiveCfg = Debug|x64 + {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Debug|x64.Build.0 = Debug|x64 {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Release|Any CPU.ActiveCfg = Release|Any CPU {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Release|Any CPU.Build.0 = Release|Any CPU + {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Release|x64.ActiveCfg = Release|x64 + {FDDB677C-FFDC-4D3F-BB63-CA385BE8B217}.Release|x64.Build.0 = Release|x64 {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Debug|x64.ActiveCfg = Debug|x64 + {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Debug|x64.Build.0 = Debug|x64 {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Release|Any CPU.Build.0 = Release|Any CPU + {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Release|x64.ActiveCfg = Release|x64 + {FAEE9122-5E0D-4246-A9FC-A346295A82FD}.Release|x64.Build.0 = Release|x64 {5074B706-14AD-4104-A2DA-EBBD36B19255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5074B706-14AD-4104-A2DA-EBBD36B19255}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5074B706-14AD-4104-A2DA-EBBD36B19255}.Debug|x64.ActiveCfg = Debug|x64 + {5074B706-14AD-4104-A2DA-EBBD36B19255}.Debug|x64.Build.0 = Debug|x64 {5074B706-14AD-4104-A2DA-EBBD36B19255}.Release|Any CPU.ActiveCfg = Release|Any CPU {5074B706-14AD-4104-A2DA-EBBD36B19255}.Release|Any CPU.Build.0 = Release|Any CPU + {5074B706-14AD-4104-A2DA-EBBD36B19255}.Release|x64.ActiveCfg = Release|x64 + {5074B706-14AD-4104-A2DA-EBBD36B19255}.Release|x64.Build.0 = Release|x64 {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Debug|x64.ActiveCfg = Debug|x64 + {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Debug|x64.Build.0 = Debug|x64 {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Release|Any CPU.ActiveCfg = Release|Any CPU {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Release|Any CPU.Build.0 = Release|Any CPU + {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Release|x64.ActiveCfg = Release|x64 + {06AA6AE5-2FAC-4761-9D44-CA11A3912256}.Release|x64.Build.0 = Release|x64 {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Debug|x64.ActiveCfg = Debug|x64 + {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Debug|x64.Build.0 = Debug|x64 {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Release|Any CPU.Build.0 = Release|Any CPU + {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Release|x64.ActiveCfg = Release|x64 + {C8DE7215-67F5-494E-B7AB-088270CB28C3}.Release|x64.Build.0 = Release|x64 {64EA04B2-A7A7-4184-AB99-180F084033BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {64EA04B2-A7A7-4184-AB99-180F084033BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64EA04B2-A7A7-4184-AB99-180F084033BC}.Debug|x64.ActiveCfg = Debug|x64 + {64EA04B2-A7A7-4184-AB99-180F084033BC}.Debug|x64.Build.0 = Debug|x64 {64EA04B2-A7A7-4184-AB99-180F084033BC}.Release|Any CPU.ActiveCfg = Release|Any CPU {64EA04B2-A7A7-4184-AB99-180F084033BC}.Release|Any CPU.Build.0 = Release|Any CPU + {64EA04B2-A7A7-4184-AB99-180F084033BC}.Release|x64.ActiveCfg = Release|x64 + {64EA04B2-A7A7-4184-AB99-180F084033BC}.Release|x64.Build.0 = Release|x64 {487FD285-16EB-46AD-AF82-9F058D2A4141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {487FD285-16EB-46AD-AF82-9F058D2A4141}.Debug|Any CPU.Build.0 = Debug|Any CPU + {487FD285-16EB-46AD-AF82-9F058D2A4141}.Debug|x64.ActiveCfg = Debug|x64 + {487FD285-16EB-46AD-AF82-9F058D2A4141}.Debug|x64.Build.0 = Debug|x64 {487FD285-16EB-46AD-AF82-9F058D2A4141}.Release|Any CPU.ActiveCfg = Release|Any CPU {487FD285-16EB-46AD-AF82-9F058D2A4141}.Release|Any CPU.Build.0 = Release|Any CPU + {487FD285-16EB-46AD-AF82-9F058D2A4141}.Release|x64.ActiveCfg = Release|x64 + {487FD285-16EB-46AD-AF82-9F058D2A4141}.Release|x64.Build.0 = Release|x64 {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Debug|x64.ActiveCfg = Debug|x64 + {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Debug|x64.Build.0 = Debug|x64 {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Release|Any CPU.Build.0 = Release|Any CPU + {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Release|x64.ActiveCfg = Release|x64 + {DD16375A-6112-49F4-A4D3-9B1524B952B3}.Release|x64.Build.0 = Release|x64 {6147F764-C8B3-4D80-9311-52974100B916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6147F764-C8B3-4D80-9311-52974100B916}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6147F764-C8B3-4D80-9311-52974100B916}.Debug|x64.ActiveCfg = Debug|x64 + {6147F764-C8B3-4D80-9311-52974100B916}.Debug|x64.Build.0 = Debug|x64 {6147F764-C8B3-4D80-9311-52974100B916}.Release|Any CPU.ActiveCfg = Release|Any CPU {6147F764-C8B3-4D80-9311-52974100B916}.Release|Any CPU.Build.0 = Release|Any CPU + {6147F764-C8B3-4D80-9311-52974100B916}.Release|x64.ActiveCfg = Release|x64 + {6147F764-C8B3-4D80-9311-52974100B916}.Release|x64.Build.0 = Release|x64 {0E5D8910-979B-46B8-B32F-12298707F892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E5D8910-979B-46B8-B32F-12298707F892}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E5D8910-979B-46B8-B32F-12298707F892}.Debug|x64.ActiveCfg = Debug|x64 + {0E5D8910-979B-46B8-B32F-12298707F892}.Debug|x64.Build.0 = Debug|x64 {0E5D8910-979B-46B8-B32F-12298707F892}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E5D8910-979B-46B8-B32F-12298707F892}.Release|Any CPU.Build.0 = Release|Any CPU + {0E5D8910-979B-46B8-B32F-12298707F892}.Release|x64.ActiveCfg = Release|x64 + {0E5D8910-979B-46B8-B32F-12298707F892}.Release|x64.Build.0 = Release|x64 {7D1B9F43-C54A-4903-9839-AAE845A41592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7D1B9F43-C54A-4903-9839-AAE845A41592}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D1B9F43-C54A-4903-9839-AAE845A41592}.Debug|x64.ActiveCfg = Debug|x64 + {7D1B9F43-C54A-4903-9839-AAE845A41592}.Debug|x64.Build.0 = Debug|x64 {7D1B9F43-C54A-4903-9839-AAE845A41592}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D1B9F43-C54A-4903-9839-AAE845A41592}.Release|Any CPU.Build.0 = Release|Any CPU + {7D1B9F43-C54A-4903-9839-AAE845A41592}.Release|x64.ActiveCfg = Release|x64 + {7D1B9F43-C54A-4903-9839-AAE845A41592}.Release|x64.Build.0 = Release|x64 {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Debug|x64.ActiveCfg = Debug|x64 + {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Debug|x64.Build.0 = Debug|x64 {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Release|Any CPU.Build.0 = Release|Any CPU + {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Release|x64.ActiveCfg = Release|x64 + {67B56585-D06F-4EEC-9CB0-E3CB5CC1AE6E}.Release|x64.Build.0 = Release|x64 {E939182C-42EC-474A-952C-064FE757CDCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E939182C-42EC-474A-952C-064FE757CDCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E939182C-42EC-474A-952C-064FE757CDCA}.Debug|x64.ActiveCfg = Debug|x64 + {E939182C-42EC-474A-952C-064FE757CDCA}.Debug|x64.Build.0 = Debug|x64 {E939182C-42EC-474A-952C-064FE757CDCA}.Release|Any CPU.ActiveCfg = Release|Any CPU {E939182C-42EC-474A-952C-064FE757CDCA}.Release|Any CPU.Build.0 = Release|Any CPU + {E939182C-42EC-474A-952C-064FE757CDCA}.Release|x64.ActiveCfg = Release|x64 + {E939182C-42EC-474A-952C-064FE757CDCA}.Release|x64.Build.0 = Release|x64 {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Debug|x64.ActiveCfg = Debug|x64 + {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Debug|x64.Build.0 = Debug|x64 {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Release|Any CPU.ActiveCfg = Release|Any CPU {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Release|Any CPU.Build.0 = Release|Any CPU + {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Release|x64.ActiveCfg = Release|x64 + {6368DFB4-1E79-4D24-9603-17449F0C2F71}.Release|x64.Build.0 = Release|x64 {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Debug|x64.ActiveCfg = Debug|x64 + {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Debug|x64.Build.0 = Debug|x64 {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Release|Any CPU.ActiveCfg = Release|Any CPU {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Release|Any CPU.Build.0 = Release|Any CPU + {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Release|x64.ActiveCfg = Release|x64 + {6173D1F4-48C2-4B14-B907-C7A746CF081D}.Release|x64.Build.0 = Release|x64 {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Debug|x64.ActiveCfg = Debug|x64 + {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Debug|x64.Build.0 = Debug|x64 {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Release|Any CPU.Build.0 = Release|Any CPU + {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Release|x64.ActiveCfg = Release|x64 + {EA0A79A3-639B-4983-A28F-1D07EC1113C7}.Release|x64.Build.0 = Release|x64 {7C74842C-69FE-4908-BA71-210652A15744}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C74842C-69FE-4908-BA71-210652A15744}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C74842C-69FE-4908-BA71-210652A15744}.Debug|x64.ActiveCfg = Debug|x64 + {7C74842C-69FE-4908-BA71-210652A15744}.Debug|x64.Build.0 = Debug|x64 {7C74842C-69FE-4908-BA71-210652A15744}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C74842C-69FE-4908-BA71-210652A15744}.Release|Any CPU.Build.0 = Release|Any CPU + {7C74842C-69FE-4908-BA71-210652A15744}.Release|x64.ActiveCfg = Release|x64 + {7C74842C-69FE-4908-BA71-210652A15744}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/other/GameFinder.Benchmarks/GameFinder.Benchmarks.csproj b/other/GameFinder.Benchmarks/GameFinder.Benchmarks.csproj index b31ee1cb..9a75f54c 100644 --- a/other/GameFinder.Benchmarks/GameFinder.Benchmarks.csproj +++ b/other/GameFinder.Benchmarks/GameFinder.Benchmarks.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/other/GameFinder.Example/GameFinder.Example.csproj b/other/GameFinder.Example/GameFinder.Example.csproj index 60e83a1a..8fdb8916 100644 --- a/other/GameFinder.Example/GameFinder.Example.csproj +++ b/other/GameFinder.Example/GameFinder.Example.csproj @@ -9,17 +9,29 @@ publish\ 2f3f5c05-f8b8-4435-901e-1caec9ff8715 ..\..\GameCollector.ico + GameCollector.Program + AnyCPU;x64 + + x64 + net8.0-windows10.0.19041.0 + WIN64 + 10.0.19041.0 + + + AnyCPU + net8.0 + - + - + - + @@ -52,7 +64,7 @@ - + diff --git a/other/GameFinder.Example/Options.cs b/other/GameFinder.Example/Options.cs index 9c2adb3e..6775372c 100644 --- a/other/GameFinder.Example/Options.cs +++ b/other/GameFinder.Example/Options.cs @@ -46,9 +46,6 @@ public class Options [Option("gog", HelpText = "Search for GOG GALAXY games", Hidden = true, Group = "Handlers")] public bool GOG { get; set; } = false; - [Option("heroic", HelpText = "Search for games from Heroic", Hidden = true, Group = "Handlers")] - public bool Heroic { get; set; } = false; - [Option("humble", HelpText = "Search for Humble App games", Hidden = true, Group = "Handlers")] public bool Humble { get; set; } = false; @@ -96,10 +93,8 @@ public class Options [Option("steam", HelpText = "Search for Steam games", Hidden = true, Group = "Handlers")] public bool Steam { get; set; } = false; - /* - [Option("tgdb", HelpText = "Search for TheGamesDb.net games", Group = "Handlers")] + [Option("tgdb", HelpText = "Search for TheGamesDb.net games", Hidden = true, Group = "Handlers")] public bool TheGamesDB { get; set; } = false; - */ // alias for "ubisoft" [Option("uplay", HelpText = "Search for Ubisoft Connect games", Hidden = true)] @@ -115,7 +110,11 @@ public class Options [Option("wargaming", HelpText = "Search for Wargaming.Net Game Center games", Hidden = true, Group = "Handlers")] public bool Wargaming { get; set; } = false; +#if WIN64 + [Option("winget", HelpText = "Search for Windows Package Manager packages", Hidden = false, Group = "Handlers")] +#else [Option("winget", HelpText = "Search for Windows Package Manager packages", Hidden = true, Group = "Handlers")] +#endif public bool Winget { get; set; } = false; [Option("xbox", HelpText = "Search for Xbox Games Pass games", Hidden = true, Group = "Handlers")] @@ -124,15 +123,28 @@ public class Options [Option('s', "steamapi", HelpText = "Specify Steam API key from (optional)", MetaValue = "KEY")] public string? SteamAPI { get; set; } - /* - [Option('t', "tgdbapi", HelpText = "Specify TheGamesDb.net API key from (optional)", MetaValue = "KEY")] + [Option('t', "tgdbapi", HelpText = "Specify TheGamesDb.net API key from (optional)", Hidden = true, MetaValue = "KEY")] public string? TheGamesDBAPI { get; set; } - */ - [Option('w', "wine", HelpText = "Search for Wine prefixes")] +#if WIN64 + [Option("heroic", HelpText = "Search for games from Heroic", Hidden = true)] +#else + [Option("heroic", HelpText = "Search for games from Heroic", Hidden = false)] +#endif + public bool Heroic { get; set; } = false; + +#if WIN64 + [Option('w', "wine", HelpText = "Search for Wine prefixes", Hidden = true)] +#else + [Option('w', "wine", HelpText = "Search for Wine prefixes", Hidden = false)] +#endif public bool Wine { get; set; } = false; - [Option('b', "bottles", HelpText = "Search for Wine prefixes managed with Bottles")] +#if WIN64 + [Option('b', "bottles", HelpText = "Search for Wine prefixes managed with Bottles", Hidden = true)] +#else + [Option('b', "bottles", HelpText = "Search for Wine prefixes managed with Bottles", Hidden = false)] +#endif public bool Bottles { get; set; } = false; [Option('i', "installed", HelpText = "Only retrieve installed games")] diff --git a/other/GameFinder.Example/Program.cs b/other/GameFinder.Example/Program.cs index ef052ca0..fb298932 100644 --- a/other/GameFinder.Example/Program.cs +++ b/other/GameFinder.Example/Program.cs @@ -37,7 +37,9 @@ using GameCollector.StoreHandlers.Rockstar; using GameCollector.StoreHandlers.Ubisoft; using GameCollector.StoreHandlers.WargamingNet; +#if WIN64 using GameCollector.PkgHandlers.Winget; +#endif using GameCollector.EmuHandlers.Dolphin; using GameCollector.EmuHandlers.MAME; //using GameCollector.DataHandlers.TheGamesDb; @@ -52,7 +54,6 @@ using IFileSystem = NexusMods.Paths.IFileSystem; using ILogger = Microsoft.Extensions.Logging.ILogger; -[assembly: ExcludeFromCodeCoverage] namespace GameCollector; public static class Program @@ -127,7 +128,7 @@ private async static void Run(Options options, ILogger logger) options.Epic = true; options.GameJolt = true; options.GOG = true; - options.Heroic = true; + options.Heroic = OperatingSystem.IsLinux(); options.Humble = true; options.IG = true; options.Itch = true; @@ -141,11 +142,12 @@ private async static void Run(Options options, ILogger logger) options.RobotCache = true; options.Rockstar = true; options.Steam = true; - //options.TheGamesDB = true; // WIP + options.TheGamesDB = false; // WIP options.Ubisoft = true; options.Wargaming = true; - options.Winget = true; + options.Winget = OperatingSystem.IsWindows(); // Requires TargetFramework net8.0-windows10.0.19041.0 options.Xbox = true; + options.Wine = OperatingSystem.IsLinux(); } if (OperatingSystem.IsWindows()) @@ -188,9 +190,9 @@ private async static void Run(Options options, ILogger logger) if (options.Rockstar) tasks.Add(Task.Run(() => RunRockstarHandler(settings, windowsRegistry, realFileSystem), cancelToken)); if (options.Ubisoft || options.Uplay) tasks.Add(Task.Run(() => RunUbisoftHandler(settings, windowsRegistry, realFileSystem), cancelToken)); if (options.Wargaming || options.WargamingNet) tasks.Add(Task.Run(() => RunWargamingNetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); - +#if WIN64 if (options.Winget) tasks.Add(Task.Run(() => RunWingetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); - +#endif if (options.Dolphin is not null) { tasks.Add(Task.Run(() => @@ -459,12 +461,20 @@ private static void RunWargamingNetHandler(Settings settings, IRegistry registry var handler = new WargamingNetHandler(registry, fileSystem); LogGamesAndErrors(handler.FindAllGames(settings), logger); } + +#if WIN64 private static void RunWingetHandler(Settings settings, IRegistry registry, IFileSystem fileSystem) { var logger = _provider.CreateLogger(nameof(WingetHandler)); - var handler = new WingetHandler(registry, fileSystem, logger); //, logger); - LogGamesAndErrors(handler.FindAllGames(settings), logger); + if (OperatingSystem.IsWindows()) + { + var handler = new WingetHandler(registry, fileSystem, logger); //, logger); + LogGamesAndErrors(handler.FindAllGames(settings), logger); + } + else + logger.LogError("Handler requires Windows-only target."); } +#endif private static void RunDolphinHandler(Settings settings, IRegistry registry, IFileSystem fileSystem, AbsolutePath path) { @@ -480,15 +490,16 @@ private static void RunMAMEHandler(Settings settings, IFileSystem fileSystem, Ab LogGamesAndErrors(handler.FindAllGames(settings), logger); } - /* private static async void RunTheGamesDbHandler(IFileSystem fileSystem, string? tgdbApi, Settings settings) { - var logger = _provider.CreateLogger(nameof(TheGamesDbHandler)); + var logger = _provider.CreateLogger("TheGamesDbHandler"); + logger.LogError("Handler not yet implemented."); + /* //var handler = new TheGamesDbHandler(fileSystem, tgdbApi, registry: null, logger); var handler = new TheGamesDbHandler(fileSystem, registry: null, logger); LogGamesAndErrors(handler.FindAllGames(settings), logger); + */ } - */ private static List LogWinePrefixes(IWinePrefixManager prefixManager, ILogger logger) where TWinePrefix : AWinePrefix diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a3209bf4..b8081015 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ - 4.3.2.2 + 4.4.0.2 Nutzzz, erri120 docs\README.md diff --git a/src/GameCollector.Common/GameCollector.Common.csproj b/src/GameCollector.Common/GameCollector.Common.csproj index 2aaa7a6e..7069ec33 100644 --- a/src/GameCollector.Common/GameCollector.Common.csproj +++ b/src/GameCollector.Common/GameCollector.Common.csproj @@ -1,5 +1,9 @@ + + AnyCPU;x64 + + diff --git a/src/GameCollector.EmuHandlers.Dolphin/GameCollector.EmuHandlers.Dolphin.csproj b/src/GameCollector.EmuHandlers.Dolphin/GameCollector.EmuHandlers.Dolphin.csproj index f0922131..de1c8327 100644 --- a/src/GameCollector.EmuHandlers.Dolphin/GameCollector.EmuHandlers.Dolphin.csproj +++ b/src/GameCollector.EmuHandlers.Dolphin/GameCollector.EmuHandlers.Dolphin.csproj @@ -2,6 +2,7 @@ Library for finding Dolphin ROMs. dolphin dolphin-emu emulator games + AnyCPU;x64 diff --git a/src/GameCollector.EmuHandlers.MAME/GameCollector.EmuHandlers.MAME.csproj b/src/GameCollector.EmuHandlers.MAME/GameCollector.EmuHandlers.MAME.csproj index 6bbc59e9..130a3d92 100644 --- a/src/GameCollector.EmuHandlers.MAME/GameCollector.EmuHandlers.MAME.csproj +++ b/src/GameCollector.EmuHandlers.MAME/GameCollector.EmuHandlers.MAME.csproj @@ -2,6 +2,7 @@ Library for finding MAME ROMs. mame emulator games + AnyCPU;x64 diff --git a/src/GameCollector.PkgHandlers.Winget/Exceptions/WinGetConfigurationException.cs b/src/GameCollector.PkgHandlers.Winget/Exceptions/WinGetConfigurationException.cs new file mode 100644 index 00000000..c04792e9 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/Exceptions/WinGetConfigurationException.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace GameCollector.PkgHandlers.Winget.Exceptions; + +public class WinGetConfigurationException : Exception +{ + // WinGet Configuration error codes: + // https://github.com/microsoft/winget-cli/blob/master/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorCodes.cs + public const int WingetConfigErrorInvalidConfigurationFile = unchecked((int)0x8A15C001); + public const int WingetConfigErrorInvalidYaml = unchecked((int)0x8A15C002); + public const int WingetConfigErrorInvalidFieldType = unchecked((int)0x8A15C003); + public const int WingetConfigErrorUnknownConfigurationFileVersion = unchecked((int)0x8A15C004); + public const int WingetConfigErrorSetApplyFailed = unchecked((int)0x8A15C005); + public const int WingetConfigErrorDuplicateIdentifier = unchecked((int)0x8A15C006); + public const int WingetConfigErrorMissingDependency = unchecked((int)0x8A15C007); + public const int WingetConfigErrorDependencyUnsatisfied = unchecked((int)0x8A15C008); + public const int WingetConfigErrorAssertionFailed = unchecked((int)0x8A15C009); + public const int WingetConfigErrorManuallySkipped = unchecked((int)0x8A15C00A); + public const int WingetConfigErrorWarningNotAccepted = unchecked((int)0x8A15C00B); + public const int WingetConfigErrorSetDependencyCycle = unchecked((int)0x8A15C00C); + public const int WingetConfigErrorInvalidFieldValue = unchecked((int)0x8A15C00D); + public const int WingetConfigErrorMissingField = unchecked((int)0x8A15C00E); + + // WinGet Configuration unit error codes: + public const int WinGetConfigUnitNotFound = unchecked((int)0x8A15C101); + public const int WinGetConfigUnitNotFoundRepository = unchecked((int)0x8A15C102); + public const int WinGetConfigUnitMultipleMatches = unchecked((int)0x8A15C103); + public const int WinGetConfigUnitInvokeGet = unchecked((int)0x8A15C104); + public const int WinGetConfigUnitInvokeTest = unchecked((int)0x8A15C105); + public const int WinGetConfigUnitInvokeSet = unchecked((int)0x8A15C106); + public const int WinGetConfigUnitModuleConflict = unchecked((int)0x8A15C107); + public const int WinGetConfigUnitImportModule = unchecked((int)0x8A15C108); + public const int WinGetConfigUnitInvokeInvalidResult = unchecked((int)0x8A15C109); + public const int WinGetConfigUnitSettingConfigRoot = unchecked((int)0x8A15C110); + public const int WinGetConfigUnitImportModuleAdmin = unchecked((int)0x8A15C111); +} diff --git a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj index fe57ff16..fa7b529b 100644 --- a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj +++ b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj @@ -2,8 +2,69 @@ Library for finding installed apps and available game packages via Windows Package Manager. winget windows-package-manager games + net8.0-windows10.0.19041.0 + 10.0.19041.0 + x64 + + + 10.0.19041.0 + Microsoft.Management.Deployment + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + NU1701 + true + none + false + + + NU1701 + true + none + + + + @@ -16,4 +77,22 @@ + + + + PreserveNewest + + + + + + + + diff --git a/src/GameCollector.PkgHandlers.Winget/Microsoft.Management.Deployment.winmd b/src/GameCollector.PkgHandlers.Winget/Microsoft.Management.Deployment.winmd new file mode 100644 index 00000000..fbcc32b2 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/Microsoft.Management.Deployment.winmd @@ -0,0 +1,1892 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + winget-cli/tools/SampleWinGetUWPCaller/AppInstallerCaller/GeneratedFromServer/Microsoft.Management.Deployment.winmd at master · microsoft/winget-cli · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ Skip to content + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + +
+ +
+ +
+ +
+ + + + / + + winget-cli + + + Public +
+ + +
+ +
+ + +
+
+ +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ +
+ +
+
+ +
+ +
+

Footer

+ + + + +
+
+ + + + + © 2024 GitHub, Inc. + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + diff --git a/src/GameCollector.PkgHandlers.Winget/NativeMethods.txt b/src/GameCollector.PkgHandlers.Winget/NativeMethods.txt new file mode 100644 index 00000000..a7650f2c --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/NativeMethods.txt @@ -0,0 +1,7 @@ +CoCreateInstance +CoMarshalInterface +CoUnmarshalInterface +CreateStreamOnHGlobal +HRESULT_FROM_WIN32 +MSHCTX +MSHLFLAGS diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassModel.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassModel.cs new file mode 100644 index 00000000..992cb0b8 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassModel.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace GameCollector.PkgHandlers.Winget.WindowsPackageManager; + +internal sealed class ClassModel +{ + /// + /// Gets the interface for the projected class type generated by CsWinRT + /// + public Type? InterfaceType { get; init; } + + /// + /// Gets the projected class type generated by CsWinRT + /// + public Type? ProjectedClassType { get; init; } + + /// + /// Gets the clsids for each context (e.g. OutOfProcProd, OutOfProcDev) + /// + public IReadOnlyDictionary? Clsids { get; init; } + + /// + /// Get CLSID based on the provided context + /// + /// Context + /// CLSID for the provided context. + /// Throw an exception if the clsid context is not available for the current instance. + public Guid GetClsid(ClsidContext context) + { + var clsid = Guid.Empty; + + if (Clsids is not null && !Clsids.TryGetValue(context, out clsid)) + { + throw new InvalidOperationException($"{ProjectedClassType?.FullName} is not implemented in context {context}"); + } + + return clsid; + } + + /// + /// Get IID corresponding to the COM object + /// + /// IID. + public Guid? GetIid() + { + return InterfaceType?.GUID; + } +} diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassesDefinition.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassesDefinition.cs new file mode 100644 index 00000000..1cdee6e9 --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClassesDefinition.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Management.Deployment; + +namespace GameCollector.PkgHandlers.Winget.WindowsPackageManager; + +internal static class ClassesDefinition +{ + private static Dictionary Classes { get; } = new() + { + [typeof(PackageManager)] = new() + { + ProjectedClassType = typeof(PackageManager), + InterfaceType = typeof(IPackageManager), + Clsids = new Dictionary + { + [ClsidContext.Prod] = new Guid("C53A4F16-787E-42A4-B304-29EFFB4BF597"), + [ClsidContext.Dev] = new Guid("74CB3139-B7C5-4B9E-9388-E6616DEA288C"), + }, + }, + + [typeof(FindPackagesOptions)] = new() + { + ProjectedClassType = typeof(FindPackagesOptions), + InterfaceType = typeof(IFindPackagesOptions), + Clsids = new Dictionary + { + [ClsidContext.Prod] = new Guid("572DED96-9C60-4526-8F92-EE7D91D38C1A"), + [ClsidContext.Dev] = new Guid("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"), + }, + }, + + [typeof(CreateCompositePackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(CreateCompositePackageCatalogOptions), + InterfaceType = typeof(ICreateCompositePackageCatalogOptions), + Clsids = new Dictionary + { + [ClsidContext.Prod] = new Guid("526534B8-7E46-47C8-8416-B1685C327D37"), + [ClsidContext.Dev] = new Guid("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"), + }, + }, + + [typeof(InstallOptions)] = new() + { + ProjectedClassType = typeof(InstallOptions), + InterfaceType = typeof(IInstallOptions), + Clsids = new Dictionary + { + [ClsidContext.Prod] = new Guid("1095F097-EB96-453B-B4E6-1613637F3B14"), + [ClsidContext.Dev] = new Guid("44FE0580-62F7-44D4-9E91-AA9614AB3E86"), + }, + }, + + [typeof(UninstallOptions)] = new() + { + ProjectedClassType = typeof(UninstallOptions), + InterfaceType = typeof(IUninstallOptions), + Clsids = new Dictionary + { + [ClsidContext.Prod] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"), + [ClsidContext.Dev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"), + }, + }, + + [typeof(PackageMatchFilter)] = new() + { + ProjectedClassType = typeof(PackageMatchFilter), + InterfaceType = typeof(IPackageMatchFilter), + Clsids = new Dictionary + { + [ClsidContext.Prod] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"), + [ClsidContext.Dev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"), + }, + }, + }; + + /// + /// Get CLSID based on the provided context for the specified type + /// + /// Projected class type + /// Context + /// CLSID for the provided context and type, or throw an exception if not found. + /// Throws an exception if type is not a project class. + public static Guid GetClsid(ClsidContext context) + { + ValidateType(); + return Classes[typeof(T)].GetClsid(context); + } + + /// + /// Get IID corresponding to the COM object + /// + /// Projected class type + /// IID or throw an exception if not found. + /// Throws an exception if type is not a project class. + public static Guid? GetIid() + { + ValidateType(); + return Classes[typeof(T)].GetIid(); + } + + /// + /// Validate that the provided type is defined. + /// + /// Throws an exception if type is not a project class. + private static void ValidateType() + { + if (!Classes.ContainsKey(typeof(TType))) + { + throw new InvalidOperationException($"{typeof(TType).Name} is not a projected class type."); + } + } +} diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClsidContext.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClsidContext.cs new file mode 100644 index 00000000..f5283f3f --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/ClsidContext.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace GameCollector.PkgHandlers.Winget.WindowsPackageManager; + +public enum ClsidContext +{ + // Production CLSID Guids + Prod, + + // Development CLSID Guids + Dev, +} diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerElevatedFactory.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerElevatedFactory.cs new file mode 100644 index 00000000..5d16fd5b --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerElevatedFactory.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; +using WinRT; + +namespace GameCollector.PkgHandlers.Winget.WindowsPackageManager; + +/// +/// Factory for creating winget COM objects using manual activation +/// to have them in an elevated context. +/// +/// +/// This needs to be called from an elevated context, or the winget +/// server will reject the connection. +/// +/// The WinGetServerManualActivation_CreateInstance function used here is defined in +/// https://github.com/microsoft/winget-cli/blob/master/src/WinGetServer/WinGetServerManualActivation_Client.cpp +/// +/// This class is based on what the winget cmdlets do. See +/// https://github.com/microsoft/winget-cli/blob/master/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs +/// +public partial class WindowsPackageManagerElevatedFactory : WindowsPackageManagerFactory +{ + // The only CLSID context supported by the DLL we call is Prod. + // If we want to use Dev classes we have to use a Dev version of the DLL. + public WindowsPackageManagerElevatedFactory() + : base(ClsidContext.Prod) + { + } + + protected override unsafe T CreateInstance(Guid clsid, Guid iid) + { + void* pUnknown = null; + + try + { + var hr = WinGetServerManualActivation_CreateInstance(in clsid, in iid, 0, out pUnknown); + Marshal.ThrowExceptionForHR(hr); + return MarshalInterface.FromAbi((IntPtr)pUnknown); + } + finally + { + // CoCreateInstance and FromAbi both AddRef on the native object. + // Release once to prevent memory leak. + if (pUnknown is not null) + { + Marshal.Release((IntPtr)pUnknown); + } + } + } + + [LibraryImport("winrtact.dll")] + private static unsafe partial int WinGetServerManualActivation_CreateInstance( + in Guid clsid, + in Guid iid, + uint flags, + out void* instance); +} diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerFactory.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerFactory.cs new file mode 100644 index 00000000..d3998ded --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerFactory.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Microsoft.Management.Deployment; + +namespace GameCollector.PkgHandlers.Winget.WindowsPackageManager; + +/// +/// Factory class for creating WinGet COM objects. +/// Details about each method can be found in the source IDL: +/// https://github.com/microsoft/winget-cli/blob/master/src/Microsoft.Management.Deployment/PackageManager.idl +/// +public abstract class WindowsPackageManagerFactory +{ + private readonly ClsidContext _clsidContext; + protected readonly bool _allowLowerTrustRegistration; + + public WindowsPackageManagerFactory(ClsidContext clsidContext, bool allowLowerTrustRegistration = false) + { + _clsidContext = clsidContext; + _allowLowerTrustRegistration = allowLowerTrustRegistration; + } + + /// + /// Creates an instance of the class . + /// + /// + /// Type must be one of the types defined in the winget COM API. + /// Implementations of this method can assume that and + /// are the right GUIDs for the class in the given context. + /// + protected abstract T CreateInstance(Guid clsid, Guid iid); + + public PackageManager CreatePackageManager() => CreateInstance(); + + public FindPackagesOptions CreateFindPackagesOptions() => CreateInstance(); + + public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() => CreateInstance(); + + public InstallOptions CreateInstallOptions() => CreateInstance(); + + public UninstallOptions CreateUninstallOptions() => CreateInstance(); + + public PackageMatchFilter CreatePackageMatchFilter() => CreateInstance(); + + /// + /// Creates an instance of the class . + /// + /// + /// This is a helper for calling the derived class's + /// method with the appropriate GUIDs. + /// + private T CreateInstance() + { + var clsid = ClassesDefinition.GetClsid(_clsidContext); + var iid = ClassesDefinition.GetIid(); + return CreateInstance(clsid, iid ?? Guid.Empty); + } +} diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs new file mode 100644 index 00000000..fef05c0d --- /dev/null +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.System.Com; +using WinRT; + +namespace GameCollector.PkgHandlers.Winget.WindowsPackageManager; + +public class WindowsPackageManagerStandardFactory : WindowsPackageManagerFactory +{ + public WindowsPackageManagerStandardFactory(ClsidContext clsidContext = ClsidContext.Prod, bool allowLowerTrustRegistration = false) + : base(clsidContext, allowLowerTrustRegistration) + { + } + + protected override T CreateInstance(Guid clsid, Guid iid) + { + var pUnknown = nint.Zero; + try + { + var clsctx = CLSCTX.CLSCTX_LOCAL_SERVER; + if (_allowLowerTrustRegistration) + { + clsctx |= CLSCTX.CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION; + } + + var hr = PInvoke.CoCreateInstance(clsid, pUnkOuter: null, clsctx, iid, out var result); + + // !! WARNING !! + // An exception may be thrown on the line below if UniGetUI + // runs as administrator and AllowLowerTrustRegistration settings is not checked + // or when WinGet is not installed on the system. + // It can be safely ignored if any of the conditions + // above are met. + Marshal.ThrowExceptionForHR(hr); + + pUnknown = Marshal.GetIUnknownForObject(result); + return MarshalGeneric.FromAbi(pUnknown); + } + finally + { + // CoCreateInstance and FromAbi both AddRef on the native object. + // Release once to prevent memory leak. + if (pUnknown != nint.Zero) + { + Marshal.Release(pUnknown); + } + } + } +} diff --git a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs index 6325e05f..d59b4d55 100644 --- a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs +++ b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs @@ -3,7 +3,8 @@ using System.Diagnostics; using System.Globalization; using System.IO; -//using System.Security.Principal; +using System.Linq; +using System.Security.Principal; using System.Text; using GameFinder.Common; using GameFinder.RegistryUtils; @@ -11,6 +12,8 @@ using Microsoft.Extensions.Logging; using NexusMods.Paths; using OneOf; +using Microsoft.Management.Deployment; +using GameCollector.PkgHandlers.Winget.WindowsPackageManager; namespace GameCollector.PkgHandlers.Winget; @@ -30,12 +33,15 @@ public class WingetHandler : AHandler private readonly IFileSystem _fileSystem; private ILogger? _logger; - //private WindowsPackageManagerFactory? _winGetFactory = null; + private WindowsPackageManagerFactory? _winGetFactory = null; + private readonly WindowsPackageManagerFactory? _wingetFactory; + private readonly PackageManager? _wingetManager; private ProcessStartInfo _startInfo = new() { UseShellExecute = false, RedirectStandardOutput = true, + RedirectStandardError = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, WindowStyle = ProcessWindowStyle.Hidden, @@ -65,12 +71,13 @@ public WingetHandler(IRegistry registry, IFileSystem fileSystem, ILogger? logger if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { // If the user is an administrator, use the elevated factory. Otherwhise COM will crash - /* if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) - _winGetFactory = new WindowsPackageManagerElevatedFactory(); + _wingetFactory = new WindowsPackageManagerElevatedFactory(); else - _winGetFactory = new WindowsPackageManagerStandardFactory(); - */ + _wingetFactory = new WindowsPackageManagerStandardFactory(); + + // Create Package Manager and get available catalogs + _wingetManager = _wingetFactory?.CreatePackageManager(); } } @@ -116,105 +123,33 @@ public IEnumerable> FindAllGames( { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { + _logger?.LogDebug("***Only supported on Windows 10.0.19041.0 or later."); yield return new ErrorMessage("Only supported on Windows 10.0.19041.0 or later."); yield break; } - /* - if (_winGetFactory is null) + if (_wingetManager is null) { + _logger?.LogDebug("***Could not access Windows Package Manager."); yield return new ErrorMessage("Could not access Windows Package Manager."); yield break; } - */ - - Dictionary> installed = new(); - - // Create Package Manager and get available catalogs - //var manager = _winGetFactory.CreatePackageManager(); - //var availableCatalogs = manager.GetPackageCatalogs(); - /* - foreach (var catalog in availableCatalogs.ToArray()) - { - //var filterList = _winGetFactory.CreateFindPackagesOptions(); - // Create a filter to search for packages with query ["game" default] - - // Add the query to the filter - var tagFilter = _winGetFactory.CreatePackageMatchFilter(); - tagFilter.Field = PackageMatchField.Tag; - tagFilter.Value = query; - filterList.Filters.Add(tagFilter); - - // Find the packages with the filters - //var searchResults = await catalog.Connect().PackageCatalog.FindPackagesAsync(filterList); - var searchResults = catalog.Connect().PackageCatalog.FindPackages(filterList); - foreach (var match in searchResults.Matches.ToArray()) - { - var pkg = match.CatalogPackage; - var id = WingetGameId.From(pkg.Id); - - installed.Add(id, new WingetGame( - Id: id, - Name: pkg.Name, - InstallDirectory: default, - //PkgTags: pkg.Tags, - //Source: pkg.Source, - InstalledVersion: pkg.InstalledVersion.DisplayName, - DefaultVersion: pkg.DefaultInstallVersion.DisplayName - )); - } - } - */ + Dictionary> items = new(); - // Get all installed items - //var installedCatalogs = manager.GetLocalPackageCatalog; + if (!ownedOnly) + items = GetInstalled(); - if (!gamesOnly) + if (!installedOnly && !string.IsNullOrEmpty(query)) { - //installed = GetInstalled(); - installed = GetInstalledParse(expandPackage); - foreach (var item in installed) + foreach (var game in SearchFreeGames(query, expandPackage)) { - if (item.Value.IsT0) - { - yield return new WingetGame( - Id: item.Key, - Name: item.Value.AsT0.Name, - InstallDirectory: default, - IsInstalled: true, - IsOwned: true, - //PkgTags: item.CatalogPackage.Tags, - //Source: item.CatalogPackage.Source, - InstalledVersion: item.Value.AsT0.InstalledVersion, - DefaultVersion: item.Value.AsT0.DefaultVersion - ); - } - else yield return item.Value.AsT1; + items.TryAdd(game.Key, game.Value); } } - //var freeGames = SearchFreeGames(); - - if (!installedOnly && !string.IsNullOrEmpty(query)) + foreach (var item in items) { - foreach (var game in SearchFreeGamesParse(query, expandPackage)) - { - if (!installed.ContainsKey(game.Key)) - { - if (game.Value.IsT0) - { - yield return new WingetGame( - Id: game.Key, - Name: game.Value.AsT0.Name, - InstallDirectory: default, - IsInstalled: false, - IsOwned: false, - DefaultVersion: game.Value.AsT0.DefaultVersion - ); - } - else yield return game.Value.AsT1; - } - } + yield return item.Value; } } @@ -222,65 +157,57 @@ private Dictionary> GetInstalled() { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { - _logger?.LogDebug("***Only supported on Windows 10.0.19041.0 or later."); return new() { [WingetGameId.From("")] = new ErrorMessage("Only supported on Windows 10.0.19041.0 or later.") }; } - /* - if (_winGetFactory is null) + if (_wingetManager is null || _wingetFactory is null) { - _logger?.LogDebug("***Could not access Windows Package Manager."); return new() { [WingetGameId.From("")] = new ErrorMessage("Could not access Windows Package Manager.") }; } - */ - // Fetching installed packages - //var winGetManager = _winGetFactory.CreatePackageManager(); + Dictionary> installed = new(); // CHANGE THIS INDEX TO CHANGE THE SOURCE - var selectedIndex = 0; // does this refer to winget vs. msstore? + var selectedIndex = 0; // TODO: I suspect this refers to winget vs. msstore - /* PackageCatalogReference installedSearchCatalogRef; if (selectedIndex < 0) { - installedSearchCatalogRef = winGetManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); + installedSearchCatalogRef = _wingetManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); } else { - var selectedRemoteCatalogRef = winGetManager.GetPackageCatalogs().ToArray()[selectedIndex]; + var selectedRemoteCatalogRef = _wingetManager.GetPackageCatalogs().ToArray()[selectedIndex]; //_logger?.LogDebug($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); - var createCompositePackageCatalogOptions = _winGetFactory.CreateCreateCompositePackageCatalogOptions(); + var createCompositePackageCatalogOptions = _wingetFactory.CreateCreateCompositePackageCatalogOptions(); createCompositePackageCatalogOptions.Catalogs.Add(selectedRemoteCatalogRef); createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.LocalCatalogs; - installedSearchCatalogRef = winGetManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); + installedSearchCatalogRef = _wingetManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); } var connectResult = installedSearchCatalogRef.Connect(); if (connectResult.Status != ConnectResultStatus.Ok) { - yield return new ErrorMessage("Failed to connect to local catalog."); - yield break; + _logger?.LogDebug("***Failed to connect to local catalog."); + return new() { [WingetGameId.From("")] = new ErrorMessage("Failed to connect to local catalog.") }; } - var findPackagesOptions = _winGetFactory.CreateFindPackagesOptions(); - var filter = _winGetFactory.CreatePackageMatchFilter(); + var findPackagesOptions = _wingetFactory.CreateFindPackagesOptions(); + var filter = _wingetFactory.CreatePackageMatchFilter(); filter.Field = PackageMatchField.Id; filter.Option = PackageFieldMatchOption.ContainsCaseInsensitive; filter.Value = ""; findPackagesOptions.Filters.Add(filter); var taskResult = connectResult.PackageCatalog.FindPackages(findPackagesOptions); - */ // Begin enumeration - /* foreach (var match in taskResult.Matches.ToArray()) { var pkg = match.CatalogPackage; //_logger?.LogDebug($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); - yield return new WingetGame( + installed.TryAdd(WingetGameId.From(pkg.Id), new WingetGame( Id: WingetGameId.From(pkg.Id), Name: pkg.Name, InstallDirectory: default, @@ -292,25 +219,16 @@ private Dictionary> GetInstalled() //ProductCodes: pkg.DefaultInstallVersion?.ProductCodes.ToList(), Publisher: pkg.InstalledVersion.Publisher, InstalledVersion: pkg.InstalledVersion.DisplayName, - DefaultVersion: pkg.DefaultInstallVersion?.DisplayName); + DefaultVersion: pkg.DefaultInstallVersion?.DisplayName)); } - */ - - // End enumeration - return new(); - } - - private Dictionary> GetInstalled(bool? expandPackage) - { - Dictionary> installed = new(); return installed; } private Dictionary> GetInstalledParse(bool expandPackage) { - //************************************* +#if DEBUG Console.OutputEncoding = Encoding.UTF8; - //************************************* +#endif Dictionary> installed = new(); @@ -320,6 +238,9 @@ private Dictionary> GetInstalledPa _logger?.LogDebug("***Winget not installed"); return new() { [WingetGameId.From("")] = new ErrorMessage("Winget not installed") }; } +#if DEBUG + Console.WriteLine("wingetExe: " + wingetExe.GetFullPath()); +#endif using var process = new Process(); process.StartInfo = _startInfo; @@ -338,6 +259,9 @@ private Dictionary> GetInstalledPa _logger?.LogDebug("***No output from winget"); return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } +#if DEBUG + Console.WriteLine("[" + output + "]"); +#endif List colPos = new(); var i = 0; @@ -404,7 +328,9 @@ private Dictionary> GetInstalledPa id = WingetGameId.From(strId); } - if (!string.IsNullOrEmpty(strId) && !strId.EndsWith('…')) + if (!string.IsNullOrEmpty(strId) && + strId.StartsWith(@"ARP\", StringComparison.Ordinal) && + !strId.EndsWith('…')) { var game = ParseRegistry(strId); if (game.IsT0) @@ -496,16 +422,14 @@ private Dictionary> GetInstalledPa return installed; } - private (string, string, string) Relist(string name, string id, string version, string? source) + private (string, string, string) Relist(string name, string id, string version, string? source = null) { - var wingetExe = FindClient(); - if (string.IsNullOrWhiteSpace(source)) source = "winget"; using var process = new Process(); process.StartInfo = _startInfo; - process.StartInfo.FileName = wingetExe.GetFullPath(); + process.StartInfo.FileName = FindClient().GetFullPath(); //process.StartInfo.Arguments = $"list --name \"{name.TrimEnd('…')}\" --source {source} --nowarn --disable-interactivity"; process.StartInfo.Arguments = $"list --id \"{id.TrimEnd('…')}\" --source {source} --nowarn --disable-interactivity"; process.Start(); @@ -533,7 +457,7 @@ private Dictionary> GetInstalledPa } else if (i > 1 && !string.IsNullOrWhiteSpace(listLine)) // skip separator line { - // TODO: This doesn't work if there are surrogate pairs in the string + // TODO: This doesn't work if there are UTF-16 surrogate pairs in the string if (listLine.Length > listCols[1]) name = listLine[listCols[0]..listCols[1]].Trim(); if (listLine.Length > listCols[2]) @@ -549,16 +473,68 @@ private Dictionary> GetInstalledPa return (name, id, version); } - private Dictionary> SearchFreeGamesParse(string query = DefaultQuery, bool expandPackage = false) + private Dictionary> SearchFreeGames(string query = DefaultQuery, bool expandPackage = false) { + if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) + { + return new() { [WingetGameId.From("")] = new ErrorMessage("Only supported on Windows 10.0.19041.0 or later.") }; + } + if (_wingetManager is null || _wingetFactory is null) + { + return new() { [WingetGameId.From("")] = new ErrorMessage("Could not access Windows Package Manager.") }; + } + Dictionary> freeGames = new(); + var availableCatalogs = _wingetManager.GetPackageCatalogs(); - var wingetExe = FindClient(); + foreach (var catalog in availableCatalogs.ToArray()) + { + // Create a filter to search for packages with query ["game" default] + var filterList = _wingetFactory.CreateFindPackagesOptions(); + + // Add the query to the filter + var tagFilter = _wingetFactory.CreatePackageMatchFilter(); + tagFilter.Field = PackageMatchField.Tag; + tagFilter.Value = query; + filterList.Filters.Add(tagFilter); + + // Find the packages with the filters + //var searchResults = await catalog.ConnectAsync().GetResults().PackageCatalog.FindPackagesAsync(filterList); + var searchResults = catalog.Connect().PackageCatalog.FindPackages(filterList); + foreach (var match in searchResults.Matches.ToArray()) + { + var pkg = match.CatalogPackage; + var id = WingetGameId.From(pkg.Id); + + freeGames.Add(id, new WingetGame( + Id: id, + Name: pkg.Name, + InstallDirectory: default, + PackageTags: pkg.DefaultInstallVersion.PackageFamilyNames.ToList(), + Publisher: pkg.DefaultInstallVersion.Publisher, + Source: pkg.DefaultInstallVersion.Channel, + DefaultVersion: pkg.DefaultInstallVersion.DisplayName + )); + + _logger?.LogDebug("* " + pkg.Name); + } + } + + return new(); + } + + private Dictionary> SearchFreeGamesParse(string query = DefaultQuery, bool expandPackage = false) + { + Dictionary> freeGames = new(); using var process = new Process(); process.StartInfo = _startInfo; - process.StartInfo.FileName = wingetExe.GetFullPath(); + process.StartInfo.FileName = FindClient().GetFullPath(); process.StartInfo.Arguments = $"search --tag {query} --source winget --nowarn --disable-interactivity"; + process.StartInfo.StandardOutputEncoding = Encoding.UTF8; + process.StartInfo.StandardErrorEncoding = Encoding.UTF8; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; process.Start(); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); @@ -593,7 +569,7 @@ private Dictionary> SearchFreeGame string name = "", strId = "", available = "", match = ""; string? source = null; WingetGameId id = default; - // TODO: This doesn't work if there are surrogate pairs in the string + // TODO: This doesn't work if there are UTF-16 surrogate pairs in the string if (line.Length > colPos[1]) name = line[colPos[0]..colPos[1]].Trim(); if (line.Length > colPos[2]) @@ -675,30 +651,17 @@ private OneOf ParseRegistry(string id) return new ErrorMessage($"Unable to open {regKeyName}"); } - if (!regKey.TryGetString("DisplayName", out var name)) - name = ""; - - if (!regKey.TryGetString("HelpLink", out var help)) - help = ""; - DateTime installDate = default; + + regKey.TryGetString("DisplayIcon", out var launch); + regKey.TryGetString("DisplayName", out var name); + regKey.TryGetString("HelpLink", out var help); if (regKey.TryGetString("InstallDate", out var date)) DateTime.TryParseExact(date, "yyyyMMdd", new CultureInfo("en-US"), DateTimeStyles.None, out installDate); - - if (!regKey.TryGetString("InstallLocation", out var path)) - path = ""; - - if (!regKey.TryGetString("DisplayIcon", out var launch)) - launch = ""; - - if (!regKey.TryGetString("Publisher", out var pub)) - pub = ""; - - if (!regKey.TryGetString("UninstallString", out var uninst)) - uninst = ""; - - if (!regKey.TryGetString("URLInfoAbout", out var url)) - url = ""; + regKey.TryGetString("InstallLocation", out var path); + regKey.TryGetString("Publisher", out var pub); + regKey.TryGetString("UninstallString", out var uninst); + regKey.TryGetString("URLInfoAbout", out var url); return new WingetGame( Id: WingetGameId.From(id), @@ -723,8 +686,6 @@ private OneOf GetPackageInfo(List colPos, string //list header = "Name,Id,Version,Available,Source" //search header = "Name,Id,Version,Match,Source" - var wingetExe = FindClient(); - var isDescription = false; //var isReleaseNotes = false; var isTags = false; @@ -733,6 +694,7 @@ private OneOf GetPackageInfo(List colPos, string var strId = listInfo[colPos[1]..colPos[2]].Trim(); var id = WingetGameId.From(strId); var version = ""; + string? available; //string? match; if (search) @@ -746,6 +708,13 @@ private OneOf GetPackageInfo(List colPos, string available = listInfo[colPos[3]..colPos[4]].Trim(); } var source = listInfo[colPos[4]..].Trim(); + + if (strId.EndsWith('…')) + { + (name, strId, version) = Relist(name, strId, version); + id = WingetGameId.From(strId); + } + var pkgName = ""; var publisher = ""; //var publisherUrl = ""; @@ -781,7 +750,7 @@ private OneOf GetPackageInfo(List colPos, string using var process = new Process(); process.StartInfo = _startInfo; - process.StartInfo.FileName = wingetExe.GetFullPath(); + process.StartInfo.FileName = FindClient().GetFullPath(); process.StartInfo.Arguments = $"show {strId} --nowarn --disable-interactivity"; process.Start(); var pkgOutput = process.StandardOutput.ReadToEnd(); @@ -827,7 +796,7 @@ private OneOf GetPackageInfo(List colPos, string var headLine = pkgLine[pkgLine.IndexOf("Found ", StringComparison.Ordinal)..]; if (headLine.LastIndexOf('[') > 7) { - strId = headLine[(headLine.LastIndexOf('[') + 1)..^1]; + strId = headLine[(headLine.LastIndexOf('[') + 1)..^2]; id = WingetGameId.From(strId); pkgName = headLine["Found ".Length..headLine.LastIndexOf('[')]; } diff --git a/src/GameCollector.SQLiteUtils/GameCollector.SQLiteUtils.csproj b/src/GameCollector.SQLiteUtils/GameCollector.SQLiteUtils.csproj index 45a238ab..8f668c8b 100644 --- a/src/GameCollector.SQLiteUtils/GameCollector.SQLiteUtils.csproj +++ b/src/GameCollector.SQLiteUtils/GameCollector.SQLiteUtils.csproj @@ -1,5 +1,9 @@ + + AnyCPU;x64 + + diff --git a/src/GameCollector.StoreHandlers.Amazon/GameCollector.StoreHandlers.Amazon.csproj b/src/GameCollector.StoreHandlers.Amazon/GameCollector.StoreHandlers.Amazon.csproj index 991f937a..3c8c0557 100644 --- a/src/GameCollector.StoreHandlers.Amazon/GameCollector.StoreHandlers.Amazon.csproj +++ b/src/GameCollector.StoreHandlers.Amazon/GameCollector.StoreHandlers.Amazon.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with Amazon Games. amazon amazon-games games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Arc/GameCollector.StoreHandlers.Arc.csproj b/src/GameCollector.StoreHandlers.Arc/GameCollector.StoreHandlers.Arc.csproj index 63d45077..b9ed0f92 100644 --- a/src/GameCollector.StoreHandlers.Arc/GameCollector.StoreHandlers.Arc.csproj +++ b/src/GameCollector.StoreHandlers.Arc/GameCollector.StoreHandlers.Arc.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Arc. arc games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.BattleNet/GameCollector.StoreHandlers.BattleNet.csproj b/src/GameCollector.StoreHandlers.BattleNet/GameCollector.StoreHandlers.BattleNet.csproj index 5bb06715..da27e469 100644 --- a/src/GameCollector.StoreHandlers.BattleNet/GameCollector.StoreHandlers.BattleNet.csproj +++ b/src/GameCollector.StoreHandlers.BattleNet/GameCollector.StoreHandlers.BattleNet.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Blizzard Battle.net. battlenet battle-net blizzard games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.BigFish/GameCollector.StoreHandlers.BigFish.csproj b/src/GameCollector.StoreHandlers.BigFish/GameCollector.StoreHandlers.BigFish.csproj index e6872e13..d8418916 100644 --- a/src/GameCollector.StoreHandlers.BigFish/GameCollector.StoreHandlers.BigFish.csproj +++ b/src/GameCollector.StoreHandlers.BigFish/GameCollector.StoreHandlers.BigFish.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with Big Fish Game Manager. bigfish big-fish games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.GameJolt/GameCollector.StoreHandlers.GameJolt.csproj b/src/GameCollector.StoreHandlers.GameJolt/GameCollector.StoreHandlers.GameJolt.csproj index 81416a37..68c78bd1 100644 --- a/src/GameCollector.StoreHandlers.GameJolt/GameCollector.StoreHandlers.GameJolt.csproj +++ b/src/GameCollector.StoreHandlers.GameJolt/GameCollector.StoreHandlers.GameJolt.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Game Jolt Client. gamejolt game-jolt games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Humble/GameCollector.StoreHandlers.Humble.csproj b/src/GameCollector.StoreHandlers.Humble/GameCollector.StoreHandlers.Humble.csproj index f0a8c0d1..400cd186 100644 --- a/src/GameCollector.StoreHandlers.Humble/GameCollector.StoreHandlers.Humble.csproj +++ b/src/GameCollector.StoreHandlers.Humble/GameCollector.StoreHandlers.Humble.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with the Humble App. humble humblebundle humble-bundle games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.IGClient/GameCollector.StoreHandlers.IGClient.csproj b/src/GameCollector.StoreHandlers.IGClient/GameCollector.StoreHandlers.IGClient.csproj index 3f1e16d7..d3442f33 100644 --- a/src/GameCollector.StoreHandlers.IGClient/GameCollector.StoreHandlers.IGClient.csproj +++ b/src/GameCollector.StoreHandlers.IGClient/GameCollector.StoreHandlers.IGClient.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with the Indiegala Client. igclient galaclient indiegala indiegala-client games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Itch/GameCollector.StoreHandlers.Itch.csproj b/src/GameCollector.StoreHandlers.Itch/GameCollector.StoreHandlers.Itch.csproj index 19a44207..6e263355 100644 --- a/src/GameCollector.StoreHandlers.Itch/GameCollector.StoreHandlers.Itch.csproj +++ b/src/GameCollector.StoreHandlers.Itch/GameCollector.StoreHandlers.Itch.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with itch. itch games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Legacy/GameCollector.StoreHandlers.Legacy.csproj b/src/GameCollector.StoreHandlers.Legacy/GameCollector.StoreHandlers.Legacy.csproj index 3a23da74..988844d4 100644 --- a/src/GameCollector.StoreHandlers.Legacy/GameCollector.StoreHandlers.Legacy.csproj +++ b/src/GameCollector.StoreHandlers.Legacy/GameCollector.StoreHandlers.Legacy.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Legacy Games Launcher. legacy legacy-games games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Oculus/GameCollector.StoreHandlers.Oculus.csproj b/src/GameCollector.StoreHandlers.Oculus/GameCollector.StoreHandlers.Oculus.csproj index 07877f55..ac237a9c 100644 --- a/src/GameCollector.StoreHandlers.Oculus/GameCollector.StoreHandlers.Oculus.csproj +++ b/src/GameCollector.StoreHandlers.Oculus/GameCollector.StoreHandlers.Oculus.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Oculus. oculus meta oculus-rift oculus-quest meta-quest games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Paradox/GameCollector.StoreHandlers.Paradox.csproj b/src/GameCollector.StoreHandlers.Paradox/GameCollector.StoreHandlers.Paradox.csproj index d65948da..6aa388d6 100644 --- a/src/GameCollector.StoreHandlers.Paradox/GameCollector.StoreHandlers.Paradox.csproj +++ b/src/GameCollector.StoreHandlers.Paradox/GameCollector.StoreHandlers.Paradox.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with Paradox Launcher. paradox games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Plarium/GameCollector.StoreHandlers.Plarium.csproj b/src/GameCollector.StoreHandlers.Plarium/GameCollector.StoreHandlers.Plarium.csproj index b2d059dd..e56a2745 100644 --- a/src/GameCollector.StoreHandlers.Plarium/GameCollector.StoreHandlers.Plarium.csproj +++ b/src/GameCollector.StoreHandlers.Plarium/GameCollector.StoreHandlers.Plarium.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Plarium Play. plarium plarium-play games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Riot/GameCollector.StoreHandlers.Riot.csproj b/src/GameCollector.StoreHandlers.Riot/GameCollector.StoreHandlers.Riot.csproj index 1c4c60b3..52c2fb81 100644 --- a/src/GameCollector.StoreHandlers.Riot/GameCollector.StoreHandlers.Riot.csproj +++ b/src/GameCollector.StoreHandlers.Riot/GameCollector.StoreHandlers.Riot.csproj @@ -2,6 +2,7 @@ Library for finding games installed with the Riot Client. riot riot-client games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.RobotCache/GameCollector.StoreHandlers.RobotCache.csproj b/src/GameCollector.StoreHandlers.RobotCache/GameCollector.StoreHandlers.RobotCache.csproj index 52d70456..04b42ed1 100644 --- a/src/GameCollector.StoreHandlers.RobotCache/GameCollector.StoreHandlers.RobotCache.csproj +++ b/src/GameCollector.StoreHandlers.RobotCache/GameCollector.StoreHandlers.RobotCache.csproj @@ -2,6 +2,7 @@ Library for finding games installed with the Robot Cache Client. robotcache robot-cache games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Rockstar/GameCollector.StoreHandlers.Rockstar.csproj b/src/GameCollector.StoreHandlers.Rockstar/GameCollector.StoreHandlers.Rockstar.csproj index 3a205661..b10006d4 100644 --- a/src/GameCollector.StoreHandlers.Rockstar/GameCollector.StoreHandlers.Rockstar.csproj +++ b/src/GameCollector.StoreHandlers.Rockstar/GameCollector.StoreHandlers.Rockstar.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Rockstar Games Launcher. rockstar games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.Ubisoft/GameCollector.StoreHandlers.Ubisoft.csproj b/src/GameCollector.StoreHandlers.Ubisoft/GameCollector.StoreHandlers.Ubisoft.csproj index d345bb9b..fc802fa8 100644 --- a/src/GameCollector.StoreHandlers.Ubisoft/GameCollector.StoreHandlers.Ubisoft.csproj +++ b/src/GameCollector.StoreHandlers.Ubisoft/GameCollector.StoreHandlers.Ubisoft.csproj @@ -2,6 +2,7 @@ Library for finding games owned and installed with Ubisoft Connect. ubisoft ubisoft-connect uplay games + AnyCPU;x64 diff --git a/src/GameCollector.StoreHandlers.WargamingNet/GameCollector.StoreHandlers.WargamingNet.csproj b/src/GameCollector.StoreHandlers.WargamingNet/GameCollector.StoreHandlers.WargamingNet.csproj index efde6f96..cfe453e2 100644 --- a/src/GameCollector.StoreHandlers.WargamingNet/GameCollector.StoreHandlers.WargamingNet.csproj +++ b/src/GameCollector.StoreHandlers.WargamingNet/GameCollector.StoreHandlers.WargamingNet.csproj @@ -2,6 +2,7 @@ Library for finding games installed with Wargaming.Net Game Center. wargaming wargaming-net games + AnyCPU;x64 diff --git a/src/GameCollector.YamlUtils/GameCollector.YamlUtils.csproj b/src/GameCollector.YamlUtils/GameCollector.YamlUtils.csproj index 76616889..27778008 100644 --- a/src/GameCollector.YamlUtils/GameCollector.YamlUtils.csproj +++ b/src/GameCollector.YamlUtils/GameCollector.YamlUtils.csproj @@ -1,5 +1,9 @@ + + AnyCPU;x64 + + diff --git a/src/GameFinder.Common/GameFinder.Common.csproj b/src/GameFinder.Common/GameFinder.Common.csproj index b371ba65..7fc9f30c 100644 --- a/src/GameFinder.Common/GameFinder.Common.csproj +++ b/src/GameFinder.Common/GameFinder.Common.csproj @@ -1,6 +1,7 @@ GameCollector.GameFinderCommon + AnyCPU;x64 diff --git a/src/GameFinder.Launcher.Heroic/GameFinder.Launcher.Heroic.csproj b/src/GameFinder.Launcher.Heroic/GameFinder.Launcher.Heroic.csproj index 2404fc86..522743bd 100644 --- a/src/GameFinder.Launcher.Heroic/GameFinder.Launcher.Heroic.csproj +++ b/src/GameFinder.Launcher.Heroic/GameFinder.Launcher.Heroic.csproj @@ -1,9 +1,10 @@ - + GameCollector.Launcher.Heroic false + AnyCPU;x64 - + diff --git a/src/GameFinder.RegistryUtils/GameFinder.RegistryUtils.csproj b/src/GameFinder.RegistryUtils/GameFinder.RegistryUtils.csproj index 3405a2c9..3321cca7 100644 --- a/src/GameFinder.RegistryUtils/GameFinder.RegistryUtils.csproj +++ b/src/GameFinder.RegistryUtils/GameFinder.RegistryUtils.csproj @@ -1,6 +1,7 @@ GameCollector.RegistryUtils + AnyCPU;x64 diff --git a/src/GameFinder.StoreHandlers.EADesktop/GameFinder.StoreHandlers.EADesktop.csproj b/src/GameFinder.StoreHandlers.EADesktop/GameFinder.StoreHandlers.EADesktop.csproj index ca8fb309..ed36abae 100644 --- a/src/GameFinder.StoreHandlers.EADesktop/GameFinder.StoreHandlers.EADesktop.csproj +++ b/src/GameFinder.StoreHandlers.EADesktop/GameFinder.StoreHandlers.EADesktop.csproj @@ -3,6 +3,7 @@ Library for finding games installed with EA Desktop. ea ea-desktop ea-app games GameCollector.StoreHandlers.EADesktop + AnyCPU;x64
diff --git a/src/GameFinder.StoreHandlers.EGS/GameFinder.StoreHandlers.EGS.csproj b/src/GameFinder.StoreHandlers.EGS/GameFinder.StoreHandlers.EGS.csproj index e9539fac..a4ffa84d 100644 --- a/src/GameFinder.StoreHandlers.EGS/GameFinder.StoreHandlers.EGS.csproj +++ b/src/GameFinder.StoreHandlers.EGS/GameFinder.StoreHandlers.EGS.csproj @@ -3,6 +3,7 @@ Library for finding games owned and installed with the Epic Games Store. epic egs epic-games-store games GameCollector.StoreHandlers.EGS + AnyCPU;x64 diff --git a/src/GameFinder.StoreHandlers.GOG/GameFinder.StoreHandlers.GOG.csproj b/src/GameFinder.StoreHandlers.GOG/GameFinder.StoreHandlers.GOG.csproj index 46c43491..eb324201 100644 --- a/src/GameFinder.StoreHandlers.GOG/GameFinder.StoreHandlers.GOG.csproj +++ b/src/GameFinder.StoreHandlers.GOG/GameFinder.StoreHandlers.GOG.csproj @@ -3,6 +3,7 @@ Library for finding games owned and installed with GOG Galaxy. gog gog-galaxy games GameCollector.StoreHandlers.GOG + AnyCPU;x64
diff --git a/src/GameFinder.StoreHandlers.Origin/GameFinder.StoreHandlers.Origin.csproj b/src/GameFinder.StoreHandlers.Origin/GameFinder.StoreHandlers.Origin.csproj index 07571807..410ff23a 100644 --- a/src/GameFinder.StoreHandlers.Origin/GameFinder.StoreHandlers.Origin.csproj +++ b/src/GameFinder.StoreHandlers.Origin/GameFinder.StoreHandlers.Origin.csproj @@ -3,6 +3,7 @@ Library for finding games installed with Origin. ea origin games GameCollector.StoreHandlers.Origin + AnyCPU;x64
diff --git a/src/GameFinder.StoreHandlers.Steam/GameFinder.StoreHandlers.Steam.csproj b/src/GameFinder.StoreHandlers.Steam/GameFinder.StoreHandlers.Steam.csproj index fe40ae1d..b7a93208 100644 --- a/src/GameFinder.StoreHandlers.Steam/GameFinder.StoreHandlers.Steam.csproj +++ b/src/GameFinder.StoreHandlers.Steam/GameFinder.StoreHandlers.Steam.csproj @@ -3,6 +3,7 @@ Library for finding games owned and installed with Steam. valve steam games GameCollector.StoreHandlers.Steam + AnyCPU;x64 @@ -17,7 +18,7 @@ - + diff --git a/src/GameFinder.StoreHandlers.Steam/ProtonWinePrefix.cs b/src/GameFinder.StoreHandlers.Steam/ProtonWinePrefix.cs index 3511410c..dd218762 100644 --- a/src/GameFinder.StoreHandlers.Steam/ProtonWinePrefix.cs +++ b/src/GameFinder.StoreHandlers.Steam/ProtonWinePrefix.cs @@ -1,3 +1,4 @@ +#if !WIN64 using System.Diagnostics.CodeAnalysis; using GameCollector.Wine; using JetBrains.Annotations; @@ -53,3 +54,4 @@ public AbsolutePath GetVersionFile() return ProtonDirectory.Combine("version"); } } +#endif diff --git a/src/GameFinder.StoreHandlers.Steam/SteamGame.cs b/src/GameFinder.StoreHandlers.Steam/SteamGame.cs index f1a3e2e5..1a94da62 100644 --- a/src/GameFinder.StoreHandlers.Steam/SteamGame.cs +++ b/src/GameFinder.StoreHandlers.Steam/SteamGame.cs @@ -77,6 +77,7 @@ public sealed record SteamGame(AbsolutePath SteamPath, /// public AbsolutePath GetCloudSavesDirectoryPath() => AppManifest is not null ? AppManifest.GetUserDataDirectoryPath(SteamPath) : new(); +#if !WIN64 /// /// Gets the Wine prefix managed by Proton for this game, if it exists. /// @@ -92,6 +93,7 @@ public sealed record SteamGame(AbsolutePath SteamPath, ProtonDirectory = protonDirectory, }; } +#endif /// /// Uses to parse the workshop manifest diff --git a/src/GameFinder.StoreHandlers.Xbox/GameFinder.StoreHandlers.Xbox.csproj b/src/GameFinder.StoreHandlers.Xbox/GameFinder.StoreHandlers.Xbox.csproj index a21f0dd7..4532a9f2 100644 --- a/src/GameFinder.StoreHandlers.Xbox/GameFinder.StoreHandlers.Xbox.csproj +++ b/src/GameFinder.StoreHandlers.Xbox/GameFinder.StoreHandlers.Xbox.csproj @@ -3,6 +3,7 @@ Library for finding games installed with Xbox Game Pass. xbox game pass games GameCollector.StoreHandlers.Xbox + AnyCPU;x64 diff --git a/src/GameFinder.Wine/GameFinder.Wine.csproj b/src/GameFinder.Wine/GameFinder.Wine.csproj index 82f8ab66..d4d2882b 100644 --- a/src/GameFinder.Wine/GameFinder.Wine.csproj +++ b/src/GameFinder.Wine/GameFinder.Wine.csproj @@ -3,6 +3,7 @@ Library for finding Wine prefixes. wine games linux GameCollector.Wine + AnyCPU;x64 diff --git a/src/GameFinder/GameFinder.csproj b/src/GameFinder/GameFinder.csproj index 0490b24c..6f2e431a 100644 --- a/src/GameFinder/GameFinder.csproj +++ b/src/GameFinder/GameFinder.csproj @@ -1,6 +1,7 @@ GameCollector + AnyCPU;x64 @@ -46,9 +47,12 @@ - + + + + diff --git a/tests/GameFinder.Common.Tests/GameFinder.Common.Tests.csproj b/tests/GameFinder.Common.Tests/GameFinder.Common.Tests.csproj index 22a419aa..abf1ce9c 100644 --- a/tests/GameFinder.Common.Tests/GameFinder.Common.Tests.csproj +++ b/tests/GameFinder.Common.Tests/GameFinder.Common.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.RegistryUtils.Tests/GameFinder.RegistryUtils.Tests.csproj b/tests/GameFinder.RegistryUtils.Tests/GameFinder.RegistryUtils.Tests.csproj index de6e13eb..f85ade28 100644 --- a/tests/GameFinder.RegistryUtils.Tests/GameFinder.RegistryUtils.Tests.csproj +++ b/tests/GameFinder.RegistryUtils.Tests/GameFinder.RegistryUtils.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.StoreHandlers.EADesktop.Tests/GameFinder.StoreHandlers.EADesktop.Tests.csproj b/tests/GameFinder.StoreHandlers.EADesktop.Tests/GameFinder.StoreHandlers.EADesktop.Tests.csproj index a16c83fe..1277c0c2 100644 --- a/tests/GameFinder.StoreHandlers.EADesktop.Tests/GameFinder.StoreHandlers.EADesktop.Tests.csproj +++ b/tests/GameFinder.StoreHandlers.EADesktop.Tests/GameFinder.StoreHandlers.EADesktop.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.StoreHandlers.EGS.Tests/GameFinder.StoreHandlers.EGS.Tests.csproj b/tests/GameFinder.StoreHandlers.EGS.Tests/GameFinder.StoreHandlers.EGS.Tests.csproj index 997f0409..f074af68 100644 --- a/tests/GameFinder.StoreHandlers.EGS.Tests/GameFinder.StoreHandlers.EGS.Tests.csproj +++ b/tests/GameFinder.StoreHandlers.EGS.Tests/GameFinder.StoreHandlers.EGS.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.StoreHandlers.GOG.Tests/GameFinder.StoreHandlers.GOG.Tests.csproj b/tests/GameFinder.StoreHandlers.GOG.Tests/GameFinder.StoreHandlers.GOG.Tests.csproj index dda66935..2d9b49cb 100644 --- a/tests/GameFinder.StoreHandlers.GOG.Tests/GameFinder.StoreHandlers.GOG.Tests.csproj +++ b/tests/GameFinder.StoreHandlers.GOG.Tests/GameFinder.StoreHandlers.GOG.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.StoreHandlers.Origin.Tests/GameFinder.StoreHandlers.Origin.Tests.csproj b/tests/GameFinder.StoreHandlers.Origin.Tests/GameFinder.StoreHandlers.Origin.Tests.csproj index 97abd25c..3e57a067 100644 --- a/tests/GameFinder.StoreHandlers.Origin.Tests/GameFinder.StoreHandlers.Origin.Tests.csproj +++ b/tests/GameFinder.StoreHandlers.Origin.Tests/GameFinder.StoreHandlers.Origin.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.StoreHandlers.Steam.Tests/GameFinder.StoreHandlers.Steam.Tests.csproj b/tests/GameFinder.StoreHandlers.Steam.Tests/GameFinder.StoreHandlers.Steam.Tests.csproj index eb2f955f..940417b9 100644 --- a/tests/GameFinder.StoreHandlers.Steam.Tests/GameFinder.StoreHandlers.Steam.Tests.csproj +++ b/tests/GameFinder.StoreHandlers.Steam.Tests/GameFinder.StoreHandlers.Steam.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.StoreHandlers.Xbox.Tests/GameFinder.StoreHandlers.Xbox.Tests.csproj b/tests/GameFinder.StoreHandlers.Xbox.Tests/GameFinder.StoreHandlers.Xbox.Tests.csproj index f7b92103..59e3ad48 100644 --- a/tests/GameFinder.StoreHandlers.Xbox.Tests/GameFinder.StoreHandlers.Xbox.Tests.csproj +++ b/tests/GameFinder.StoreHandlers.Xbox.Tests/GameFinder.StoreHandlers.Xbox.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/GameFinder.Wine.Tests/GameFinder.Wine.Tests.csproj b/tests/GameFinder.Wine.Tests/GameFinder.Wine.Tests.csproj index 47118bd9..02b46ddd 100644 --- a/tests/GameFinder.Wine.Tests/GameFinder.Wine.Tests.csproj +++ b/tests/GameFinder.Wine.Tests/GameFinder.Wine.Tests.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + diff --git a/tests/TestUtils/TestUtils.csproj b/tests/TestUtils/TestUtils.csproj index 22a419aa..abf1ce9c 100644 --- a/tests/TestUtils/TestUtils.csproj +++ b/tests/TestUtils/TestUtils.csproj @@ -1,4 +1,7 @@ + + AnyCPU;x64 + From 8b961d17926491be0e503d664b405e29ff2e0d6e Mon Sep 17 00:00:00 2001 From: Nutzzz Date: Wed, 19 Feb 2025 17:24:32 -0800 Subject: [PATCH 8/8] Some tweaks to winget API experiment --- Directory.Build.props | 7 +- Directory.Packages.props | 17 ++-- .../GameFinder.Example.csproj | 4 +- other/GameFinder.Example/Options.cs | 8 +- other/GameFinder.Example/Program.cs | 15 ++-- .../GameCollector.PkgHandlers.Winget.csproj | 34 ++------ .../WindowsPackageManagerStandardFactory.cs | 8 +- .../WingetHandler.cs | 85 +++++++++++-------- 8 files changed, 86 insertions(+), 92 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index bc1da74b..ee3d1078 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,5 @@ - net8.0 true 4.5.0 Nutzzz, erri120 @@ -10,6 +9,12 @@ https://github.com/Nutzzz/GameCollector.git MIT + + net8.0-windows10.0.19041.0 + + + net8.0 + diff --git a/Directory.Packages.props b/Directory.Packages.props index 6a941d84..fa73cc37 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -17,17 +17,12 @@ - - - - - - - + + + + + + diff --git a/other/GameFinder.Example/GameFinder.Example.csproj b/other/GameFinder.Example/GameFinder.Example.csproj index 8fdb8916..9fcc40a1 100644 --- a/other/GameFinder.Example/GameFinder.Example.csproj +++ b/other/GameFinder.Example/GameFinder.Example.csproj @@ -15,7 +15,7 @@ x64 net8.0-windows10.0.19041.0 - WIN64 + WINX64 10.0.19041.0 @@ -64,7 +64,7 @@ - + diff --git a/other/GameFinder.Example/Options.cs b/other/GameFinder.Example/Options.cs index 6775372c..340e7698 100644 --- a/other/GameFinder.Example/Options.cs +++ b/other/GameFinder.Example/Options.cs @@ -110,7 +110,7 @@ public class Options [Option("wargaming", HelpText = "Search for Wargaming.Net Game Center games", Hidden = true, Group = "Handlers")] public bool Wargaming { get; set; } = false; -#if WIN64 +#if WINX64 [Option("winget", HelpText = "Search for Windows Package Manager packages", Hidden = false, Group = "Handlers")] #else [Option("winget", HelpText = "Search for Windows Package Manager packages", Hidden = true, Group = "Handlers")] @@ -126,21 +126,21 @@ public class Options [Option('t', "tgdbapi", HelpText = "Specify TheGamesDb.net API key from (optional)", Hidden = true, MetaValue = "KEY")] public string? TheGamesDBAPI { get; set; } -#if WIN64 +#if WINX64 [Option("heroic", HelpText = "Search for games from Heroic", Hidden = true)] #else [Option("heroic", HelpText = "Search for games from Heroic", Hidden = false)] #endif public bool Heroic { get; set; } = false; -#if WIN64 +#if WINX64 [Option('w', "wine", HelpText = "Search for Wine prefixes", Hidden = true)] #else [Option('w', "wine", HelpText = "Search for Wine prefixes", Hidden = false)] #endif public bool Wine { get; set; } = false; -#if WIN64 +#if WINX64 [Option('b', "bottles", HelpText = "Search for Wine prefixes managed with Bottles", Hidden = true)] #else [Option('b', "bottles", HelpText = "Search for Wine prefixes managed with Bottles", Hidden = false)] diff --git a/other/GameFinder.Example/Program.cs b/other/GameFinder.Example/Program.cs index 3d1b6951..a9a9b863 100644 --- a/other/GameFinder.Example/Program.cs +++ b/other/GameFinder.Example/Program.cs @@ -37,7 +37,7 @@ using GameCollector.StoreHandlers.Rockstar; using GameCollector.StoreHandlers.Ubisoft; using GameCollector.StoreHandlers.WargamingNet; -#if WIN64 +#if WINX64 using GameCollector.PkgHandlers.Winget; #endif using GameCollector.EmuHandlers.Dolphin; @@ -128,7 +128,6 @@ private async static void Run(Options options, ILogger logger) options.Epic = true; options.GameJolt = true; options.GOG = true; - options.Heroic = OperatingSystem.IsLinux(); options.Humble = true; options.IG = true; options.Itch = true; @@ -145,9 +144,13 @@ private async static void Run(Options options, ILogger logger) options.TheGamesDB = false; // WIP options.Ubisoft = true; options.Wargaming = true; - options.Winget = OperatingSystem.IsWindows(); // Requires TargetFramework net8.0-windows10.0.19041.0 options.Xbox = true; +#if WINX64 + options.Winget = OperatingSystem.IsWindows(); // Requires TargetFramework net8.0-windows10.0.19041.0 +#else + options.Heroic = OperatingSystem.IsLinux(); options.Wine = OperatingSystem.IsLinux(); +#endif } if (OperatingSystem.IsWindows()) @@ -190,8 +193,8 @@ private async static void Run(Options options, ILogger logger) if (options.Rockstar) tasks.Add(Task.Run(() => RunRockstarHandler(settings, windowsRegistry, realFileSystem), cancelToken)); if (options.Ubisoft || options.Uplay) tasks.Add(Task.Run(() => RunUbisoftHandler(settings, windowsRegistry, realFileSystem), cancelToken)); if (options.Wargaming || options.WargamingNet) tasks.Add(Task.Run(() => RunWargamingNetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); -#if WIN64 - if (options.Winget) tasks.Add(Task.Run(() => RunWingetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); +#if WINX64 + if (options.Winget) RunWingetHandler(settings, windowsRegistry, realFileSystem); //tasks.Add(Task.Run(() => RunWingetHandler(settings, windowsRegistry, realFileSystem), cancelToken)); #endif if (options.Dolphin is not null) { @@ -462,7 +465,7 @@ private static void RunWargamingNetHandler(Settings settings, IRegistry registry LogGamesAndErrors(handler.FindAllGames(settings), logger); } -#if WIN64 +#if WINX64 private static void RunWingetHandler(Settings settings, IRegistry registry, IFileSystem fileSystem) { var logger = _provider.CreateLogger(nameof(WingetHandler)); diff --git a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj index fa7b529b..717bd3fa 100644 --- a/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj +++ b/src/GameCollector.PkgHandlers.Winget/GameCollector.PkgHandlers.Winget.csproj @@ -4,6 +4,7 @@ winget windows-package-manager games net8.0-windows10.0.19041.0 10.0.19041.0 + x64 @@ -18,14 +19,11 @@ - - - - + + - all @@ -54,15 +52,6 @@ true none - @@ -78,21 +67,8 @@ - - - PreserveNewest - - - - - - - + + diff --git a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs index fef05c0d..badb6233 100644 --- a/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs +++ b/src/GameCollector.PkgHandlers.Winget/WindowsPackageManager/WindowsPackageManagerStandardFactory.cs @@ -18,10 +18,10 @@ public WindowsPackageManagerStandardFactory(ClsidContext clsidContext = ClsidCon protected override T CreateInstance(Guid clsid, Guid iid) { - var pUnknown = nint.Zero; + var pUnknown = IntPtr.Zero; try { - var clsctx = CLSCTX.CLSCTX_LOCAL_SERVER; + var clsctx = CLSCTX.CLSCTX_LOCAL_SERVER; //CLSCTX.CLSCTX_ALL; if (_allowLowerTrustRegistration) { clsctx |= CLSCTX.CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION; @@ -30,7 +30,7 @@ protected override T CreateInstance(Guid clsid, Guid iid) var hr = PInvoke.CoCreateInstance(clsid, pUnkOuter: null, clsctx, iid, out var result); // !! WARNING !! - // An exception may be thrown on the line below if UniGetUI + // An exception may be thrown on the line below if this program // runs as administrator and AllowLowerTrustRegistration settings is not checked // or when WinGet is not installed on the system. // It can be safely ignored if any of the conditions @@ -44,7 +44,7 @@ protected override T CreateInstance(Guid clsid, Guid iid) { // CoCreateInstance and FromAbi both AddRef on the native object. // Release once to prevent memory leak. - if (pUnknown != nint.Zero) + if (pUnknown != IntPtr.Zero) { Marshal.Release(pUnknown); } diff --git a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs index d59b4d55..1a2dfa15 100644 --- a/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs +++ b/src/GameCollector.PkgHandlers.Winget/WingetHandler.cs @@ -10,9 +10,9 @@ using GameFinder.RegistryUtils; using JetBrains.Annotations; using Microsoft.Extensions.Logging; +using Microsoft.Management.Deployment; using NexusMods.Paths; using OneOf; -using Microsoft.Management.Deployment; using GameCollector.PkgHandlers.Winget.WindowsPackageManager; namespace GameCollector.PkgHandlers.Winget; @@ -33,10 +33,10 @@ public class WingetHandler : AHandler private readonly IFileSystem _fileSystem; private ILogger? _logger; - private WindowsPackageManagerFactory? _winGetFactory = null; - - private readonly WindowsPackageManagerFactory? _wingetFactory; - private readonly PackageManager? _wingetManager; + private WindowsPackageManagerFactory? _factory = null; + private static WindowsPackageManagerFactory? _externalFactory; + private PackageManager? _wingetManager; + private static PackageManager? _externalWingetManager; private ProcessStartInfo _startInfo = new() { UseShellExecute = false, @@ -72,12 +72,15 @@ public WingetHandler(IRegistry registry, IFileSystem fileSystem, ILogger? logger { // If the user is an administrator, use the elevated factory. Otherwhise COM will crash if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) - _wingetFactory = new WindowsPackageManagerElevatedFactory(); + _factory = new WindowsPackageManagerElevatedFactory(); + //_factory = new WindowsPackageManagerStandardFactory(allowLowerTrustRegistration: true); else - _wingetFactory = new WindowsPackageManagerStandardFactory(); + _factory = new WindowsPackageManagerStandardFactory(); // Create Package Manager and get available catalogs - _wingetManager = _wingetFactory?.CreatePackageManager(); + _wingetManager = _factory?.CreatePackageManager(); + _externalFactory = _factory; + _externalWingetManager = _wingetManager; } } @@ -90,6 +93,7 @@ public WingetHandler(IRegistry registry, IFileSystem fileSystem, ILogger? logger /// public override AbsolutePath FindClient() { + // Not necessary if using API var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var wingetExe = _fileSystem.FromUnsanitizedFullPath(Path.Combine(localAppData, "Microsoft", "WindowsApps", "winget.exe")); if (Path.IsPathRooted(localAppData) && _fileSystem.FileExists(wingetExe)) @@ -121,15 +125,16 @@ public IEnumerable> FindAllGames( string? query = DefaultQuery, bool expandPackage = false) { + //_logger?.LogDebug("***FindAllGames()"); if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { - _logger?.LogDebug("***Only supported on Windows 10.0.19041.0 or later."); + //_logger?.LogDebug("***Only supported on Windows 10.0.19041.0 or later."); yield return new ErrorMessage("Only supported on Windows 10.0.19041.0 or later."); yield break; } if (_wingetManager is null) { - _logger?.LogDebug("***Could not access Windows Package Manager."); + //_logger?.LogDebug("***Could not access Windows Package Manager."); yield return new ErrorMessage("Could not access Windows Package Manager."); yield break; } @@ -149,17 +154,19 @@ public IEnumerable> FindAllGames( foreach (var item in items) { + //_logger?.LogDebug("***" + item.Value.ToString()); yield return item.Value; } } private Dictionary> GetInstalled() { + //_logger?.LogDebug("***GetInstalled()"); if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { return new() { [WingetGameId.From("")] = new ErrorMessage("Only supported on Windows 10.0.19041.0 or later.") }; } - if (_wingetManager is null || _wingetFactory is null) + if (_wingetManager is null || _factory is null) { return new() { [WingetGameId.From("")] = new ErrorMessage("Could not access Windows Package Manager.") }; } @@ -178,9 +185,9 @@ private Dictionary> GetInstalled() { var selectedRemoteCatalogRef = _wingetManager.GetPackageCatalogs().ToArray()[selectedIndex]; - //_logger?.LogDebug($"Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); + //_logger?.LogDebug($"***Searching on package catalog {selectedRemoteCatalogRef.Info.Name} "); - var createCompositePackageCatalogOptions = _wingetFactory.CreateCreateCompositePackageCatalogOptions(); + var createCompositePackageCatalogOptions = _factory.CreateCreateCompositePackageCatalogOptions(); createCompositePackageCatalogOptions.Catalogs.Add(selectedRemoteCatalogRef); createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.LocalCatalogs; installedSearchCatalogRef = _wingetManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); @@ -189,12 +196,12 @@ private Dictionary> GetInstalled() var connectResult = installedSearchCatalogRef.Connect(); if (connectResult.Status != ConnectResultStatus.Ok) { - _logger?.LogDebug("***Failed to connect to local catalog."); + //_logger?.LogDebug("***Failed to connect to local catalog."); return new() { [WingetGameId.From("")] = new ErrorMessage("Failed to connect to local catalog.") }; } - var findPackagesOptions = _wingetFactory.CreateFindPackagesOptions(); - var filter = _wingetFactory.CreatePackageMatchFilter(); + var findPackagesOptions = _factory.CreateFindPackagesOptions(); + var filter = _factory.CreatePackageMatchFilter(); filter.Field = PackageMatchField.Id; filter.Option = PackageFieldMatchOption.ContainsCaseInsensitive; filter.Value = ""; @@ -206,7 +213,9 @@ private Dictionary> GetInstalled() foreach (var match in taskResult.Matches.ToArray()) { var pkg = match.CatalogPackage; - //_logger?.LogDebug($"Package {pkg.Name} is available Online: " + pkg.DefaultInstallVersion.PackageCatalog.Info.Name); + if (pkg is null) + continue; + installed.TryAdd(WingetGameId.From(pkg.Id), new WingetGame( Id: WingetGameId.From(pkg.Id), Name: pkg.Name, @@ -226,6 +235,7 @@ private Dictionary> GetInstalled() private Dictionary> GetInstalledParse(bool expandPackage) { + //_logger?.LogDebug("***GetInstalledParse()"); #if DEBUG Console.OutputEncoding = Encoding.UTF8; #endif @@ -235,7 +245,7 @@ private Dictionary> GetInstalledPa var wingetExe = FindClient(); if (wingetExe == default) { - _logger?.LogDebug("***Winget not installed"); + //_logger?.LogDebug("***Winget not installed"); return new() { [WingetGameId.From("")] = new ErrorMessage("Winget not installed") }; } #if DEBUG @@ -256,7 +266,7 @@ private Dictionary> GetInstalledPa if (string.IsNullOrEmpty(output)) { - _logger?.LogDebug("***No output from winget"); + //_logger?.LogDebug("***No output from winget"); return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } #if DEBUG @@ -315,7 +325,7 @@ private Dictionary> GetInstalledPa IsOwned: true, InstalledVersion: version))) { - _logger?.LogDebug(" " + name); + //_logger?.LogDebug(" " + name); } i++; continue; @@ -349,7 +359,7 @@ private Dictionary> GetInstalledPa Homepage: game.AsT0.Homepage, InstalledVersion: version))) { - _logger?.LogDebug("@ " + name); + //_logger?.LogDebug("@ " + name); } i++; continue; @@ -357,7 +367,7 @@ private Dictionary> GetInstalledPa if (installed.TryAdd(id, game.AsT1)) { - _logger?.LogDebug("***" + game.AsT1.Message); + //_logger?.LogDebug("***" + game.AsT1.Message); } i++; } @@ -370,7 +380,7 @@ private Dictionary> GetInstalledPa IsOwned: true, InstalledVersion: version))) { - _logger?.LogDebug("? " + name); + //_logger?.LogDebug("? " + name); } i++; continue; @@ -397,7 +407,7 @@ private Dictionary> GetInstalledPa InstalledVersion: version, DefaultVersion: available))) { - _logger?.LogDebug(" " + name); + //_logger?.LogDebug(" " + name); } i++; continue; @@ -411,19 +421,20 @@ private Dictionary> GetInstalledPa } if (installed.TryAdd(id, GetPackageInfo(colPos, line, search: false))) { - _logger?.LogDebug(" " + name); + //_logger?.LogDebug(" " + name); } } i++; } - _logger?.LogDebug("GetInstalledParse(): " + i + " apps"); + //_logger?.LogDebug("GetInstalledParse(): " + i + " apps"); return installed; } private (string, string, string) Relist(string name, string id, string version, string? source = null) { + //_logger?.LogDebug("***Relist()"); if (string.IsNullOrWhiteSpace(source)) source = "winget"; @@ -475,11 +486,12 @@ private Dictionary> GetInstalledPa private Dictionary> SearchFreeGames(string query = DefaultQuery, bool expandPackage = false) { + //_logger?.LogDebug("***SearchFreeGames()"); if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041, 0)) { return new() { [WingetGameId.From("")] = new ErrorMessage("Only supported on Windows 10.0.19041.0 or later.") }; } - if (_wingetManager is null || _wingetFactory is null) + if (_wingetManager is null || _factory is null) { return new() { [WingetGameId.From("")] = new ErrorMessage("Could not access Windows Package Manager.") }; } @@ -490,10 +502,10 @@ private Dictionary> SearchFreeGame foreach (var catalog in availableCatalogs.ToArray()) { // Create a filter to search for packages with query ["game" default] - var filterList = _wingetFactory.CreateFindPackagesOptions(); + var filterList = _factory.CreateFindPackagesOptions(); // Add the query to the filter - var tagFilter = _wingetFactory.CreatePackageMatchFilter(); + var tagFilter = _factory.CreatePackageMatchFilter(); tagFilter.Field = PackageMatchField.Tag; tagFilter.Value = query; filterList.Filters.Add(tagFilter); @@ -516,7 +528,7 @@ private Dictionary> SearchFreeGame DefaultVersion: pkg.DefaultInstallVersion.DisplayName )); - _logger?.LogDebug("* " + pkg.Name); + //_logger?.LogDebug("* " + pkg.Name); } } @@ -525,6 +537,7 @@ private Dictionary> SearchFreeGame private Dictionary> SearchFreeGamesParse(string query = DefaultQuery, bool expandPackage = false) { + //_logger?.LogDebug("***SearchFreeGamesParse()"); Dictionary> freeGames = new(); using var process = new Process(); @@ -541,7 +554,7 @@ private Dictionary> SearchFreeGame if (string.IsNullOrEmpty(output)) { - _logger?.LogDebug("***No output from winget"); + //_logger?.LogDebug("***No output from winget"); return new() { [WingetGameId.From("")] = new ErrorMessage("No output from winget") }; } @@ -595,7 +608,7 @@ private Dictionary> SearchFreeGame IsOwned: false, DefaultVersion: available))) { - _logger?.LogDebug("* " + name); + //_logger?.LogDebug("* " + name); i++; } continue; @@ -603,19 +616,20 @@ private Dictionary> SearchFreeGame if (freeGames.TryAdd(id, GetPackageInfo(colPos, line, search: true))) { - _logger?.LogDebug("*@" + name); + //_logger?.LogDebug("*@" + name); } } i++; } - _logger?.LogDebug("SearchFreeGamesParse(): " + i + " games"); + //_logger?.LogDebug("SearchFreeGamesParse(): " + i + " games"); return freeGames; } private OneOf ParseRegistry(string id) { + //_logger?.LogDebug("***ParseRegistry()"); // id syntax = "ARP\[Machine|User]\[X64|X86]\ string? regKeyName; // = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; @@ -683,6 +697,7 @@ private OneOf ParseRegistry(string id) private OneOf GetPackageInfo(List colPos, string listInfo, bool search = false) { + //_logger?.LogDebug("***GetPackageInfo()"); //list header = "Name,Id,Version,Available,Source" //search header = "Name,Id,Version,Match,Source" @@ -838,7 +853,7 @@ private OneOf GetPackageInfo(List colPos, string i++; } - _logger?.LogDebug("> " + name); + //_logger?.LogDebug("> " + name); return new WingetGame( Id: id, Name: name,