Files
2025-07-06 00:23:46 +02:00

165 lines
6.0 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using NitroxModel.Discovery.InstallationFinders.Core;
using NitroxModel.Platforms.OS.Windows;
using static NitroxModel.Discovery.InstallationFinders.Core.GameFinderResult;
namespace NitroxModel.Discovery.InstallationFinders;
/// <summary>
/// Trying to find the path in the Steam installation directory by the appid that contains the game installation directory.
/// By default each game will have a corresponding appmanifest_{appid}.acf file in the steamapps folder.
/// Except for some games that are installed on a different diskdrive, in those case 'libraryfolders.vdf' will give us the real location of the appid folder.
/// </summary>
public sealed class SteamFinder : IGameFinder
{
public GameFinderResult FindGame(GameInfo gameInfo)
{
string steamPath = GetSteamPath();
if (string.IsNullOrEmpty(steamPath))
{
return Error("Steam isn't installed");
}
string path;
string appsPath = Path.Combine(steamPath, "steamapps");
if (File.Exists(Path.Combine(appsPath, $"appmanifest_{gameInfo.SteamAppId}.acf")))
{
path = Path.Combine(appsPath, "common", gameInfo.Name);
}
else
{
path = SearchAllInstallations(Path.Combine(appsPath, "libraryfolders.vdf"), gameInfo.SteamAppId, gameInfo.Name);
if (string.IsNullOrWhiteSpace(path))
{
return NotFound();
}
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
path = Path.Combine(path, $"{gameInfo.Name}.app", "Contents");
}
if (!GameInstallationHelper.HasValidGameFolder(path, gameInfo))
{
return Error($"Path '{path}' known by Steam for '{gameInfo.FullName}' does not point to a valid game file structure");
}
return Ok(path);
}
private static string GetSteamPath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
string steamPath = RegistryEx.Read<string>(@"Software\Valve\Steam\SteamPath");
if (string.IsNullOrWhiteSpace(steamPath))
{
steamPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
"Steam"
);
}
return Directory.Exists(steamPath) ? steamPath : null;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (string.IsNullOrWhiteSpace(homePath))
{
homePath = Environment.GetEnvironmentVariable("HOME");
}
if (!Directory.Exists(homePath))
{
return null;
}
string[] commonSteamPath =
[
// Default install location
// https://github.com/ValveSoftware/steam-for-linux
Path.Combine(homePath, ".local", "share", "Steam"),
// Those symlinks are often use as a backward-compatibility (Debian, Ubuntu, Fedora, ArchLinux)
// https://wiki.archlinux.org/title/steam, https://askubuntu.com/questions/227502/where-are-steam-games-installed
Path.Combine(homePath, ".steam", "steam"),
Path.Combine(homePath, ".steam", "root"),
// Flatpack install
// https://github.com/flathub/com.valvesoftware.Steam/wiki, https://flathub.org/apps/com.valvesoftware.Steam
Path.Combine(homePath, ".var", "app", "com.valvesoftware.Steam", ".local", "share", "Steam"),
Path.Combine(homePath, ".var", "app", "com.valvesoftware.Steam", ".steam", "steam"),
];
foreach (string path in commonSteamPath)
{
if (Directory.Exists(path))
{
return path;
}
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
string homePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (string.IsNullOrWhiteSpace(homePath))
{
homePath = Environment.GetEnvironmentVariable("HOME");
}
if (!Directory.Exists(homePath))
{
return null;
}
// Steam should always be here
string steamPath = Path.Combine(homePath, "Library", "Application Support", "Steam");
if (Directory.Exists(steamPath))
{
return steamPath;
}
}
return null;
}
/// <summary>
/// Finds game install directory by iterating through all the steam game libraries configured, matching the given appid.
/// </summary>
private static string SearchAllInstallations(string libraryFolders, int appid, string gameName)
{
if (!File.Exists(libraryFolders))
{
return null;
}
StreamReader file = new(libraryFolders);
char[] trimChars = [' ', '\t'];
while (file.ReadLine() is { } line)
{
line = Regex.Unescape(line.Trim(trimChars));
Match regMatch = Regex.Match(line, "\"(.*)\"\t*\"(.*)\"");
string key = regMatch.Groups[1].Value;
// New format (about 2021-07-16) uses "path" key instead of steam-library-index as key. If either, it could be steam game path.
if (!key.Equals("path", StringComparison.OrdinalIgnoreCase) && !int.TryParse(key, out _))
{
continue;
}
string value = regMatch.Groups[2].Value;
if (File.Exists(Path.Combine(value, "steamapps", $"appmanifest_{appid}.acf")))
{
return Path.Combine(value, "steamapps", "common", gameName);
}
}
return null;
}
}