diff --git a/src/GameFinder.StoreHandlers.Steam/Models/RegistryEntry.cs b/src/GameFinder.StoreHandlers.Steam/Models/RegistryEntry.cs
new file mode 100644
index 00000000..95f63d13
--- /dev/null
+++ b/src/GameFinder.StoreHandlers.Steam/Models/RegistryEntry.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Text;
+using FluentResults;
+using GameFinder.RegistryUtils;
+using GameFinder.StoreHandlers.Steam.Models.ValueTypes;
+using GameFinder.StoreHandlers.Steam.Services;
+using JetBrains.Annotations;
+using NexusMods.Paths;
+using NexusMods.Paths.Extensions;
+
+namespace GameFinder.StoreHandlers.Steam.Models;
+
+///
+/// Represents a parsed registry entry.
+///
+[PublicAPI]
+public sealed record RegistryEntry
+{
+ ///
+ /// Gets the unique identifier of the app
+ /// that was parsed to produce this .
+ ///
+ public required AppId AppId { get; init; }
+
+ #region Parsed Values
+
+ ///
+ /// Gets the for the Uninstall registry subkey.
+ ///
+ public required IRegistryKey RegistryPath { get; init; }
+
+ ///
+ /// Gets the path to the icon for this app.
+ ///
+ public AbsolutePath? DisplayIcon { get; init; }
+
+ ///
+ /// Gets name of the app.
+ ///
+ public required string DisplayName { get; init; }
+
+ ///
+ /// Gets the help URL (invariably https://help.steampowered.com/)
+ ///
+ public required string HelpLink { get; init; }
+
+ ///
+ /// Gets the installation directory of the app.
+ ///
+ public required AbsolutePath? InstallLocation { get; init; }
+
+ ///
+ /// Gets the publisher name
+ ///
+ public required string Publisher { get; init; }
+
+ ///
+ /// Gets the uninstall executable
+ ///
+ /// "C:\Program Files\Steam\steam.exe"
+ public required AbsolutePath? UninstallExecutable { get; init; }
+
+ ///
+ /// Gets the uninstall parameters (note the steam:// URL by itself without the executable should be sufficient)
+ ///
+ /// steam://uninstall/262060
+ public required string UninstallParameters { get; init; }
+
+ ///
+ /// Gets the info URL
+ ///
+ public required string URLInfoAbout { get; init; }
+
+ #endregion
+
+ #region Helpers
+
+ private static readonly RelativePath CommonDirectoryName = "common".ToRelativePath();
+ private static readonly RelativePath ShaderCacheDirectoryName = "shadercache".ToRelativePath();
+ private static readonly RelativePath WorkshopDirectoryName = "workshop".ToRelativePath();
+ private static readonly RelativePath CompatabilityDataDirectoryName = "compatdata".ToRelativePath();
+
+ ///
+ /// Parses the registry for again and returns a new
+ /// instance of .
+ ///
+ [Pure]
+ [System.Diagnostics.Contracts.Pure]
+ [MustUseReturnValue]
+ public Result Reload(IFileSystem fileSystem, IRegistry? registry)
+ {
+ return RegistryEntryParser.ParseRegistryEntry(AppId, fileSystem, registry);
+ }
+
+ #endregion
+
+ #region Overwrites
+
+ ///
+ public bool Equals(RegistryEntry? other)
+ {
+ if (other is null) return false;
+ if (AppId != other.AppId) return false;
+ if (DisplayIcon != other.DisplayIcon) return false;
+ if (!string.Equals(DisplayName, other.DisplayName, StringComparison.Ordinal)) return false;
+ if (!string.Equals(HelpLink, other.HelpLink, StringComparison.Ordinal)) return false;
+ if (InstallLocation != other.InstallLocation) return false;
+ if (!string.Equals(Publisher, other.Publisher, StringComparison.Ordinal)) return false;
+ if (UninstallExecutable != other.UninstallExecutable) return false;
+ if (!string.Equals(UninstallParameters, other.UninstallParameters, StringComparison.Ordinal)) return false;
+ if (!string.Equals(URLInfoAbout, other.URLInfoAbout, StringComparison.Ordinal)) return false;
+ return true;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+ hashCode.Add(AppId);
+ hashCode.Add(DisplayIcon);
+ hashCode.Add(DisplayName);
+ hashCode.Add(HelpLink);
+ hashCode.Add(InstallLocation);
+ hashCode.Add(Publisher);
+ hashCode.Add(UninstallExecutable);
+ hashCode.Add(UninstallParameters);
+ hashCode.Add(URLInfoAbout);
+ return hashCode.ToHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+
+ sb.Append("{ ");
+ sb.Append($"DisplayIcon = {DisplayIcon}, ");
+ sb.Append($"Uninstall = {UninstallExecutable} {UninstallParameters}");
+ sb.Append(" }");
+
+ return sb.ToString();
+ }
+
+ #endregion
+}
diff --git a/src/GameFinder.StoreHandlers.Steam/Services/Parsers/RegistryEntryParser.cs b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/RegistryEntryParser.cs
new file mode 100644
index 00000000..01487560
--- /dev/null
+++ b/src/GameFinder.StoreHandlers.Steam/Services/Parsers/RegistryEntryParser.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using FluentResults;
+using GameFinder.RegistryUtils;
+using GameFinder.StoreHandlers.Steam.Models;
+using GameFinder.StoreHandlers.Steam.Models.ValueTypes;
+using JetBrains.Annotations;
+using NexusMods.Paths;
+
+namespace GameFinder.StoreHandlers.Steam.Services;
+
+///
+/// Parser for Steam Uninstall registry entries.
+///
+///
+[PublicAPI]
+public static class RegistryEntryParser
+{
+ internal const string UninstallRegKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall";
+
+ ///
+ /// Parses the registry entry for the given Steam app ID.
+ ///
+ public static Result ParseRegistryEntry(AppId appId, IFileSystem fileSystem, IRegistry? registry)
+ {
+ RegistryEntry regEntry;
+
+ if (fileSystem is null)
+ {
+ return Result.Ok();
+ return Result.Fail(new Error("Invalid filesystem parameter!"));
+ }
+ if (registry is null)
+ {
+ return Result.Ok();
+ return Result.Fail(new Error("Invalid registry parameter!"));
+ }
+
+ IRegistryKey? subKey = default;
+
+ try
+ {
+ // Entries are usually in HKLM64, but occasionally HKLM32 (or both)
+ var localMachine64 = registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
+ var localMachine32 = registry.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
+ using var subKey64 = localMachine64.OpenSubKey(Path.Combine(UninstallRegKey, "Steam App " + appId));
+ using var subKey32 = localMachine32.OpenSubKey(Path.Combine(UninstallRegKey, "Steam App " + appId));
+
+ subKey = subKey64;
+ if (subKey64 is null || string.IsNullOrEmpty(subKey64.GetString("InstallLocation")) &&
+ subKey32 is not null && !string.IsNullOrEmpty(subKey32.GetString("InstallLocation")))
+ {
+ subKey = subKey32;
+ }
+
+ if (subKey is null)
+ {
+ return Result.Ok();
+ return Result.Fail(
+ new Error("Invalid registry key!")
+ .WithMetadata("AppId", appId)
+ .WithMetadata("Key", subKey?.ToString())
+ );
+ }
+
+ var strIcon = subKey.GetString("DisplayIcon");
+ var strLoc = subKey.GetString("InstallLocation");
+ var strUninst = subKey.GetString("UninstallString");
+ var strUnExe = "";
+ var strUnParam = "";
+ if (strUninst is not null)
+ {
+ if (strUninst.StartsWith('"'))
+ {
+ strUnExe = strUninst[..strUninst.LastIndexOf('"')];
+ strUnParam = strUninst[(strUninst.LastIndexOf('"') + 1)..];
+ }
+ else if (strUninst.Contains(' ', StringComparison.Ordinal))
+ {
+ strUnExe = strUninst[..strUninst.IndexOf(' ', StringComparison.Ordinal)];
+ strUnParam = strUninst[(strUninst.IndexOf(' ', StringComparison.Ordinal) + 1)..];
+ }
+ else
+ {
+ strUnExe = strUninst;
+ }
+ }
+ regEntry = new()
+ {
+ AppId = appId,
+ RegistryPath = subKey,
+ DisplayIcon = Path.IsPathRooted(strIcon) ? fileSystem.FromUnsanitizedFullPath(strIcon) : null,
+ DisplayName = subKey.GetString("DisplayName") ?? "",
+ HelpLink = subKey.GetString("HelpLink") ?? "",
+ InstallLocation = Path.IsPathRooted(strLoc) ? fileSystem.FromUnsanitizedFullPath(strLoc) : null,
+ Publisher = subKey.GetString("Publisher") ?? "",
+ UninstallExecutable = Path.IsPathRooted(strUnExe) ? fileSystem.FromUnsanitizedFullPath(strUnExe) : null,
+ UninstallParameters = strUnParam,
+ URLInfoAbout = subKey.GetString("URLInfoAbout") ?? "",
+ };
+
+ return regEntry;
+ }
+ catch (Exception ex)
+ {
+ return Result.Ok();
+ return Result.Fail(
+ new ExceptionalError("Exception was thrown while parsing the registry!", ex)
+ .WithMetadata("Key", subKey?.ToString())
+ );
+ }
+ }
+}
diff --git a/src/GameFinder.StoreHandlers.Steam/SteamGame.cs b/src/GameFinder.StoreHandlers.Steam/SteamGame.cs
index 21369554..3e6ec404 100644
--- a/src/GameFinder.StoreHandlers.Steam/SteamGame.cs
+++ b/src/GameFinder.StoreHandlers.Steam/SteamGame.cs
@@ -19,6 +19,11 @@ public sealed record SteamGame : IGame
///
public required AppManifest AppManifest { get; init; }
+ ///
+ /// Gets the parsed of this game.
+ ///
+ public RegistryEntry? RegistryEntry { get; init; }
+
///
/// Gets the library folder that contains this game.
///
diff --git a/src/GameFinder.StoreHandlers.Steam/SteamHandler.cs b/src/GameFinder.StoreHandlers.Steam/SteamHandler.cs
index 757a0cd6..7fe30a2f 100644
--- a/src/GameFinder.StoreHandlers.Steam/SteamHandler.cs
+++ b/src/GameFinder.StoreHandlers.Steam/SteamHandler.cs
@@ -96,10 +96,19 @@ public override IEnumerable> FindAllGames()
continue;
}
+ var registryEntryResult = RegistryEntryParser.ParseRegistryEntry(appManifestResult.Value.AppId, _fileSystem, _registry);
+ /*
+ if (registryEntryResult.IsFailed)
+ {
+ yield return ConvertResultToErrorMessage(registryEntryResult);
+ }
+ */
+
var steamGame = new SteamGame
{
SteamPath = steamPath,
AppManifest = appManifestResult.Value,
+ RegistryEntry = registryEntryResult.Value ?? default,
LibraryFolder = libraryFolder,
};