Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add libraryfolders.vdf parsing for Steam handler #7

Merged
merged 6 commits into from
Jun 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions GameFinder.StoreHandlers.Steam/SteamHandler.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using GameFinder.RegistryUtils;
using JetBrains.Annotations;
using Microsoft.Win32;
Expand All @@ -27,6 +28,7 @@ public class SteamHandler : AStoreHandler<SteamGame>
/// </summary>
public readonly string? SteamPath;
private string? SteamConfig { get; set; }
private string? SteamLibraries { get; set; }

/// <summary>
/// List of all found Steam Universes
Expand Down Expand Up @@ -73,9 +75,17 @@ public SteamHandler()
return;
}

var steamLibraries = Path.Combine(steamPath, "steamapps", "libraryfolders.vdf");
if (!File.Exists(steamLibraries))
{
_initErrors.Add($"Unable to find libraryfolders.vdf at {steamLibraries}");
return;
}

FoundSteam = true;
SteamPath = steamPath;
SteamConfig = steamConfig;
SteamLibraries = steamLibraries;
}

/// <summary>
Expand All @@ -96,16 +106,24 @@ public SteamHandler(string steamPath)
return;
}

var steamLibraries = Path.Combine(steamPath, "steamapps", "libraryfolders.vdf");
if (!File.Exists(steamLibraries))
{
_initErrors.Add($"Unable to find libraryfolders.vdf at {steamLibraries}");
return;
}

FoundSteam = true;
SteamPath = steamPath;
SteamConfig = steamConfig;
SteamLibraries = steamLibraries;
}

private Result<bool> FindAllUniverses()
{
if (!FoundSteam) return NotOk(_initErrors);
if (SteamConfig == null) return NotOk(_initErrors);
if (SteamConfig == null) return NotOk(_initErrors);
if (SteamLibraries == null) return NotOk(_initErrors);

var res = new Result<bool>();
var lines = File.ReadAllLines(SteamConfig, Encoding.UTF8);
Expand All @@ -130,6 +148,29 @@ private Result<bool> FindAllUniverses()
SteamUniverses.Add(path);
}

lines = File.ReadAllLines(SteamLibraries, Encoding.UTF8);
var rx = new Regex(@"\s+""\d+\""\s+""(?<path>.+)""");

foreach (var line in lines)
{
var matches = rx.Matches(line);
foreach (Match match in matches)
{
var groups = match.Groups;
var path = Path.Combine(groups["path"].Value, "steamapps");
if (!Directory.Exists(path))
{
res.AddError($"Unable to find Universe at {path}");
continue;
}

if (!SteamUniverses.Contains(path))
{
SteamUniverses.Add(path);
}
}
}

if (SteamPath == null) return Ok(res);

var defaultPath = Path.Combine(SteamPath, "steamapps");
Expand All @@ -144,7 +185,7 @@ public override Result<bool> FindAllGames()
{
if (!FoundSteam) return NotOk(_initErrors);
if (SteamConfig == null) return NotOk(_initErrors);
if (SteamConfig == null) return NotOk(_initErrors);
if (SteamLibraries == null) return NotOk(_initErrors);

var universeRes = FindAllUniverses();
if (!universeRes.Value)
Expand Down
5 changes: 4 additions & 1 deletion GameFinder.Tests/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ public static string SetupSteam()
Directory.CreateDirectory(steamConfigDir);
var steamConfig = Path.Combine(steamConfigDir, "config.vdf");
File.WriteAllText(steamConfig, "Hello World!", Encoding.UTF8);

var steamappsDir = Path.Combine(steamDir, "steamapps");
Directory.CreateDirectory(steamappsDir);

var steamLibraries = Path.Combine(steamappsDir, "libraryfolders.vdf");
File.WriteAllText(steamLibraries, "Hello World!", Encoding.UTF8);

var manifestFile = GetFile("appmanifest_72850.acf");
var manifestOutput = Path.Combine(steamappsDir, "appmanifest_72850.acf");

Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ If you build some sort of game library manager like [Playnite](https://github.co

Source: [SteamHandler.cs](GameFinder.StoreHandlers.Steam/SteamHandler.cs)

Steam games can be easily found by searching through _"Steam Universes"_. An Universe is simply a folder where you install Steam games. You can find all Universes by parsing the `config.vdf` file in `Steam/config`. We can get the Steam folder by opening the registry key `HKEY_CURRENT_USER\Software\Valve\Steam` and getting the `SteamPath` value.
Steam games can be easily found by searching through _"Steam Universes"_. An Universe is simply a folder where you install Steam games. You can find all Universes by parsing some configuration files in the Steam folder. We can get the Steam folder by opening the registry key `HKEY_CURRENT_USER\Software\Valve\Steam` and getting the `SteamPath` value.

The `config.vdf` file uses Valve's KeyValue format which is similar to JSON:
The `config/config.vdf` file uses Valve's KeyValue format which is similar to JSON. What we want to look for are these `BaseInstallFolder_X` values which point to a Universe folder.

```text
"InstallConfigStore"
Expand All @@ -57,7 +57,21 @@ The `config.vdf` file uses Valve's KeyValue format which is similar to JSON:
}
```

What we want to look for are these `BaseInstallFolder_X` values which point to a Universe folder. The `steamapps` subdirectory contains the `appmanifest_*.acf` files we need. `.acf` files have the same KeyValue format as `.vdf` files so parsing very easy:
The `steamapps/libraryfolders.vdf` uses the same type of formatting as above. The lines with a numeric key point to a Universe folder. These numbers should match up with the `BaseInstallFolder_X` values in `config/config.vdf`.

```text
"LibraryFolders"
{
"TimeNextStatsReport" "1623187700"
"ContentStatsID" "-8832980547670729777"
"1" "F:\\SteamLibrary"
"3" "E:\\SteamLibrary"
}
```

Both `config/config.vdf` and `steamapps/libraryfolders.vdf` are parsed for possible Universe locations. Steam should keep these files synced but there have been some user reports of this not happening.

The `steamapps` subdirectory contains the `appmanifest_*.acf` files we need. `.acf` files have the same KeyValue format as `.vdf` files so parsing is very easy:

```text
"AppState"
Expand Down