using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace SpaceEngineersBlueprintEditor.GameAssemblyLoader;
///
/// Locates the installed Steam copy of Space Engineers without loading game code.
///
public static class GameInstallationLocator
{
private const string AppId = "244850";
private const string DefaultInstallDirectory = "SpaceEngineers";
///
/// Finds and validates the game root. The preferred path may point to the root,
/// Bin64 directory, or SpaceEngineers.exe.
///
public static string? FindGameRootPath(string? preferredPath = null)
{
var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var candidate in EnumerateCandidates(preferredPath))
{
var gameRoot = NormalizeAndValidate(candidate);
if (gameRoot is not null && seen.Add(gameRoot))
{
return gameRoot;
}
}
return null;
}
///
/// Gets the validated Bin64 directory for the installed game.
///
public static string? FindGameBinPath(string? preferredPath = null)
{
var gameRoot = FindGameRootPath(preferredPath);
return gameRoot is null ? null : Path.Combine(gameRoot, "Bin64");
}
private static IEnumerable EnumerateCandidates(string? preferredPath)
{
if (!string.IsNullOrWhiteSpace(preferredPath))
{
yield return preferredPath!;
}
var environmentBin = Environment.GetEnvironmentVariable("SPACE_ENGINEERS_BIN");
if (!string.IsNullOrWhiteSpace(environmentBin))
{
yield return environmentBin!;
}
var environmentRoot = Environment.GetEnvironmentVariable("SPACE_ENGINEERS_ROOT");
if (!string.IsNullOrWhiteSpace(environmentRoot))
{
yield return environmentRoot!;
}
foreach (var installLocation in ReadRegistryValues(
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App " + AppId,
"InstallLocation"))
{
yield return installLocation;
}
foreach (var installLocation in ReadRegistryValues(
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Steam App " + AppId,
"InstallLocation"))
{
yield return installLocation;
}
foreach (var steamRoot in EnumerateSteamRoots())
{
foreach (var candidate in EnumerateSteamLibraryCandidates(steamRoot))
{
yield return candidate;
}
}
foreach (var drive in DriveInfo.GetDrives())
{
string? root = null;
try
{
if (drive.IsReady && drive.DriveType == DriveType.Fixed)
{
root = drive.RootDirectory.FullName;
}
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}
if (root is not null)
{
yield return Path.Combine(root, "SteamLibrary", "steamapps", "common", DefaultInstallDirectory);
}
}
}
private static IEnumerable EnumerateSteamRoots()
{
foreach (var path in ReadRegistryValues(@"SOFTWARE\Valve\Steam", "SteamPath", "InstallPath"))
{
yield return path;
}
foreach (var path in ReadRegistryValues(@"SOFTWARE\WOW6432Node\Valve\Steam", "SteamPath", "InstallPath"))
{
yield return path;
}
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (!string.IsNullOrWhiteSpace(programFilesX86))
{
yield return Path.Combine(programFilesX86, "Steam");
}
}
private static IEnumerable EnumerateSteamLibraryCandidates(string steamRoot)
{
yield return Path.Combine(steamRoot, "steamapps", "common", DefaultInstallDirectory);
var libraryFile = Path.Combine(steamRoot, "steamapps", "libraryfolders.vdf");
string libraryText;
try
{
if (!File.Exists(libraryFile))
{
yield break;
}
libraryText = File.ReadAllText(libraryFile);
}
catch (IOException)
{
yield break;
}
catch (UnauthorizedAccessException)
{
yield break;
}
foreach (Match match in Regex.Matches(libraryText, "\"(?:path|\\d+)\"\\s+\"(?[^\"]+)\""))
{
var libraryRoot = match.Groups["path"].Value.Replace("\\\\", "\\");
var installDirectory = ReadInstallDirectory(libraryRoot) ?? DefaultInstallDirectory;
yield return Path.Combine(libraryRoot, "steamapps", "common", installDirectory);
}
}
private static string? ReadInstallDirectory(string libraryRoot)
{
var manifest = Path.Combine(libraryRoot, "steamapps", "appmanifest_" + AppId + ".acf");
try
{
if (!File.Exists(manifest))
{
return null;
}
var match = Regex.Match(File.ReadAllText(manifest), "\"installdir\"\\s+\"(?[^\"]+)\"");
return match.Success ? match.Groups["name"].Value : null;
}
catch (IOException)
{
return null;
}
catch (UnauthorizedAccessException)
{
return null;
}
}
private static IEnumerable ReadRegistryValues(string subKey, params string[] valueNames)
{
var values = new List();
foreach (var hive in new[] { Registry.CurrentUser, Registry.LocalMachine })
{
try
{
using (var key = hive.OpenSubKey(subKey, false))
{
if (key is null)
{
continue;
}
foreach (var valueName in valueNames)
{
if (key.GetValue(valueName) is string value && !string.IsNullOrWhiteSpace(value))
{
values.Add(value);
}
}
}
}
catch (UnauthorizedAccessException)
{
}
catch (System.Security.SecurityException)
{
}
}
return values;
}
private static string? NormalizeAndValidate(string candidate)
{
try
{
candidate = Environment.ExpandEnvironmentVariables(candidate.Trim().Trim('"'));
if (string.Equals(Path.GetFileName(candidate), "SpaceEngineers.exe", StringComparison.OrdinalIgnoreCase))
{
candidate = Path.GetDirectoryName(candidate) ?? candidate;
}
if (string.Equals(Path.GetFileName(candidate.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), "Bin64", StringComparison.OrdinalIgnoreCase))
{
candidate = Path.GetDirectoryName(candidate.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)) ?? candidate;
}
var gameRoot = Path.GetFullPath(candidate).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var binPath = Path.Combine(gameRoot, "Bin64");
if (File.Exists(Path.Combine(binPath, "SpaceEngineers.exe")) &&
File.Exists(Path.Combine(binPath, "VRage.Game.dll")) &&
File.Exists(Path.Combine(binPath, "Sandbox.Game.dll")) &&
File.Exists(Path.Combine(binPath, "SpaceEngineers.Game.dll")) &&
Directory.Exists(Path.Combine(gameRoot, "Content")))
{
return gameRoot;
}
}
catch (ArgumentException)
{
}
catch (IOException)
{
}
catch (NotSupportedException)
{
}
catch (UnauthorizedAccessException)
{
}
return null;
}
}