You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#实现Steam游戏启动程序精准检索的技术问询

Precise Steam Game Executable Detection in C#

Hey there, great question—this is such a common pain point when scraping Steam game files. Recursive searches either miss valid nested exes (like ARK's) or flood you with junk from redist/bin folders. The fix? Use Steam's own metadata instead of guessing via file searches, since Steam tracks exactly which executable is the main game launcher for every title.

The Core Solution: Parse Steam's App Manifest Files

Every Steam game has an appmanifest_<AppID>.acf file stored in the steamapps directory of each library. This file holds official game metadata, including the path to its main executable. Here's how to leverage it:

Step 1: Extract Key Data from ACF Files

ACF files use a simple key-value format (similar to Valve's VDF files). For each game, we need two critical fields:

  • installdir: The name of the game's folder in steamapps/common/
  • Executable: The relative path to the main game executable from the installdir folder

For example, ARK: Survival Evolved's appmanifest will include:

"installdir"		"ARK"
"Launch"
{
	"Executable"		"ShooterGame\\Binaries\\Win64\\ShooterGame.exe"
}

Step 2: Updated C# Code with Metadata Parsing

Here's a revised version of your SearchSteam method that integrates ACF parsing, plus fallback logic for edge cases where manifest data is missing:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Win32;

public class SteamGameFinder
{
    private List<string> steamGameDirs = new List<string>();

    public void SearchSteam()
    {
        steamGameDirs.Clear();
        // Get Steam install paths from registry
        string steam32 = "SOFTWARE\\VALVE\\";
        string steam64 = "SOFTWARE\\Wow6432Node\\Valve\\";
        
        // Process both 32-bit and 64-bit registry entries
        ProcessRegistryKey(Registry.LocalMachine.OpenSubKey(steam32));
        ProcessRegistryKey(Registry.LocalMachine.OpenSubKey(steam64));

        // Parse app manifests from each library's steamapps directory
        foreach (var libraryPath in steamGameDirs)
        {
            string steamAppsDir = Path.Combine(libraryPath, "steamapps");
            if (!Directory.Exists(steamAppsDir)) continue;

            // Grab all appmanifest files
            string[] appManifests = Directory.GetFiles(steamAppsDir, "appmanifest_*.acf");
            foreach (var manifestPath in appManifests)
            {
                ParseAppManifest(manifestPath, libraryPath);
            }
        }
    }

    private void ProcessRegistryKey(RegistryKey key)
    {
        if (key == null) return;
        
        foreach (string subKeyName in key.GetSubKeyNames())
        {
            using (RegistryKey subKey = key.OpenSubKey(subKeyName))
            {
                string installPath = subKey?.GetValue("InstallPath")?.ToString();
                if (string.IsNullOrEmpty(installPath)) continue;

                // Add main Steam library, plus extract additional libraries from libraryfolders.vdf
                steamGameDirs.Add(installPath);
                ExtractAdditionalLibraries(Path.Combine(installPath, "steamapps", "libraryfolders.vdf"));
            }
        }
    }

    private void ExtractAdditionalLibraries(string libraryVdfPath)
    {
        if (!File.Exists(libraryVdfPath)) return;
        
        string[] configLines = File.ReadAllLines(libraryVdfPath);
        string driveRegex = @"[A-Z]:\\\\";
        
        foreach (var line in configLines)
        {
            Match match = Regex.Match(line, driveRegex);
            if (match.Success)
            {
                string libraryPath = line.Substring(match.Index).Replace("\\\\", "\\").Trim('"', ' ', '\t');
                if (!steamGameDirs.Contains(libraryPath))
                {
                    steamGameDirs.Add(libraryPath);
                }
            }
        }
    }

    private void ParseAppManifest(string manifestPath, string libraryPath)
    {
        string[] lines = File.ReadAllLines(manifestPath);
        string installDir = null;
        string executablePath = null;
        bool inLaunchSection = false;

        foreach (var line in lines)
        {
            string trimmedLine = line.Trim('\t', ' ', '"');
            
            // Extract the game's install directory name
            if (trimmedLine.StartsWith("installdir"))
            {
                installDir = trimmedLine.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries)[1];
            }
            // Flag when we enter the Launch section
            else if (trimmedLine == "Launch")
            {
                inLaunchSection = true;
            }
            // Flag when we exit the Launch section
            else if (inLaunchSection && trimmedLine == "}")
            {
                inLaunchSection = false;
            }
            // Extract the main executable path from the Launch section
            else if (inLaunchSection && trimmedLine.StartsWith("Executable"))
            {
                executablePath = trimmedLine.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries)[1];
                break; // Stop searching once we find the main executable
            }
        }

        if (!string.IsNullOrEmpty(installDir) && !string.IsNullOrEmpty(executablePath))
        {
            string gameInstallPath = Path.Combine(libraryPath, "steamapps", "common", installDir);
            string fullExecutablePath = Path.Combine(gameInstallPath, executablePath);
            
            if (File.Exists(fullExecutablePath))
            {
                Console.WriteLine($"Game Title: {installDir}");
                Console.WriteLine($"Main Executable: {fullExecutablePath}");
                Console.WriteLine("---");
            }
            else
            {
                // Fallback: If manifest path is invalid, run a filtered search
                Console.WriteLine($"Warning: Manifest path invalid for {installDir}, falling back to filtered search...");
                FilteredExecutableSearch(gameInstallPath, installDir);
            }
        }
        else
        {
            // Fallback: No manifest data found, run filtered search
            string appId = Path.GetFileNameWithoutExtension(manifestPath).Replace("appmanifest_", "");
            Console.WriteLine($"No manifest data found for AppID {appId}, falling back to filtered search...");
            string possibleGameDir = Directory.EnumerateDirectories(Path.Combine(libraryPath, "steamapps", "common"))
                .FirstOrDefault(d => Path.GetFileName(d).Contains(appId));
            if (possibleGameDir != null)
            {
                FilteredExecutableSearch(possibleGameDir, Path.GetFileName(possibleGameDir));
            }
        }
    }

    private void FilteredExecutableSearch(string gameDir, string gameTitle)
    {
        // Filtered recursive search: avoid redist junk, prioritize exes matching the game title
        var validExes = Directory.GetFiles(gameDir, "*.exe", SearchOption.AllDirectories)
            .Where(exe => 
                !exe.Contains("redist", StringComparison.OrdinalIgnoreCase) &&
                !exe.Contains("_redist", StringComparison.OrdinalIgnoreCase) &&
                // Allow bin/binaries folders only if the exe matches the game title
                (!exe.Contains("bin", StringComparison.OrdinalIgnoreCase) || 
                 Path.GetFileNameWithoutExtension(exe).Equals(gameTitle, StringComparison.OrdinalIgnoreCase)) &&
                // Filter out tiny exes (adjust size threshold as needed)
                new FileInfo(exe).Length > 1024 * 1024 // 1MB minimum
            );

        foreach (var exe in validExes)
        {
            Console.WriteLine($"Fallback Found: {exe}");
        }
        Console.WriteLine("---");
    }
}

Why This Works Better

  • Precision: Uses Steam's official metadata to get the exact main executable, so you never miss games like ARK or pull in redist clutter.
  • Fallback Logic: For rare games with missing manifest data, the filtered search balances completeness and cleanliness.
  • Scalability: Works across all Steam libraries, not just the default installation folder.

Additional Tips

  • Some games have multiple launch options (e.g., editors, dedicated servers). If you need these, modify the ParseAppManifest method to collect all Executable entries in the Launch section instead of breaking after the first one.
  • For more robust ACF parsing (handling deeply nested sections), consider using a dedicated VDF/ACF parser library, but the simple line parsing above works for most cases.

内容的提问来源于stack exchange,提问作者Pete Kemp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:58:11