XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
UTF-8
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

namespace SpaceEngineersBlueprintEditor.GameAssemblyLoader;

/// <summary>
/// Locates the installed Steam copy of Space Engineers without loading game code.
/// </summary>
public static class GameInstallationLocator
{
    private const string AppId = "244850";
    private const string DefaultInstallDirectory = "SpaceEngineers";

    /// <summary>
    /// Finds and validates the game root. The preferred path may point to the root,
    /// Bin64 directory, or SpaceEngineers.exe.
    /// </summary>
    public static string? FindGameRootPath(string? preferredPath = null)
    {
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var candidate in EnumerateCandidates(preferredPath))
        {
            var gameRoot = NormalizeAndValidate(candidate);
            if (gameRoot is not null && seen.Add(gameRoot))
            {
                return gameRoot;
            }
        }

        return null;
    }

    /// <summary>
    /// Gets the validated Bin64 directory for the installed game.
    /// </summary>
    public static string? FindGameBinPath(string? preferredPath = null)
    {
        var gameRoot = FindGameRootPath(preferredPath);
        return gameRoot is null ? null : Path.Combine(gameRoot, "Bin64");
    }

    private static IEnumerable<string> 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<string> 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<string> 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+\"(?<path>[^\"]+)\""))
        {
            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+\"(?<name>[^\"]+)\"");
            return match.Success ? match.Groups["name"].Value : null;
        }
        catch (IOException)
        {
            return null;
        }
        catch (UnauthorizedAccessException)
        {
            return null;
        }
    }

    private static IEnumerable<string> ReadRegistryValues(string subKey, params string[] valueNames)
    {
        var values = new List<string>();
        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;
    }
}