Windows游戏自动检测本地是否安装 (C++版)

Windows游戏自动检测实现 (C++版)

下面我将用C++实现Windows下自动检测已安装游戏的完整解决方案,包含注册表扫描、平台集成检测、快捷方式解析等多种方法:

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <windows.h>
#include <psapi.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <comdef.h>
#include <wrl/client.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Management.Deployment.h>

#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "psapi.lib")

using namespace std;
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::ApplicationModel;
using namespace Windows::Management::Deployment;

// 游戏信息结构体
struct GameInfo {
    wstring name;
    wstring path;
    wstring platform;
    wstring exePath;
};

// 注册表扫描实现
vector<GameInfo> ScanRegistryForGames() {
    vector<GameInfo> games;
    const wchar_t* registryPaths[] = {
        L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
        L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
    };

    for (const auto& path : registryPaths) {
        HKEY hKey;
        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, KEY_READ | KEY_WOW64_64KEY, &hKey) != ERROR_SUCCESS) {
            continue;
        }

        DWORD subkeyCount;
        RegQueryInfoKey(hKey, NULL, NULL, NULL, &subkeyCount, NULL, NULL, NULL, NULL, NULL, NULL, NULL);

        for (DWORD i = 0; i < subkeyCount; i++) {
            wchar_t subkeyName[MAX_PATH];
            DWORD nameSize = MAX_PATH;
            if (RegEnumKeyEx(hKey, i, subkeyName, &nameSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) {
                continue;
            }

            HKEY hSubKey;
            if (RegOpenKeyEx(hKey, subkeyName, 0, KEY_READ, &hSubKey) != ERROR_SUCCESS) {
                continue;
            }

            // 检查是否是游戏
            wchar_t displayName[MAX_PATH];
            DWORD displayNameSize = sizeof(displayName);
            DWORD type;
            if (RegQueryValueEx(hSubKey, L"DisplayName", NULL, &type, 
                               reinterpret_cast<LPBYTE>(displayName), 
                               &displayNameSize) != ERROR_SUCCESS || type != REG_SZ) {
                RegCloseKey(hSubKey);
                continue;
            }

            // 获取安装路径
            wchar_t installPath[MAX_PATH];
            DWORD installPathSize = sizeof(installPath);
            if (RegQueryValueEx(hSubKey, L"InstallLocation", NULL, &type, 
                               reinterpret_cast<LPBYTE>(installPath), 
                               &installPathSize) == ERROR_SUCCESS && type == REG_SZ) {
                // 检查路径是否存在
                if (PathFileExists(installPath)) {
                    GameInfo game;
                    game.name = displayName;
                    game.path = installPath;
                    
                    // 检查平台信息
                    wchar_t publisher[MAX_PATH];
                    DWORD publisherSize = sizeof(publisher);
                    if (RegQueryValueEx(hSubKey, L"Publisher", NULL, &type, 
                                       reinterpret_cast<LPBYTE>(publisher), 
                                       &publisherSize) == ERROR_SUCCESS && type == REG_SZ) {
                        if (wcsstr(publisher, L"Valve") != nullptr) {
                            game.platform = L"Steam";
                        } else if (wcsstr(publisher, L"Epic") != nullptr) {
                            game.platform = L"Epic Games";
                        } else if (wcsstr(publisher, L"GOG") != nullptr) {
                            game.platform = L"GOG";
                        }
                    }
                    
                    games.push_back(game);
                }
            }

            RegCloseKey(hSubKey);
        }
        RegCloseKey(hKey);
    }
    return games;
}

// Steam游戏检测
vector<GameInfo> DetectSteamGames() {
    vector<GameInfo> games;
    
    // 获取Steam安装路径
    HKEY hKey;
    if (RegOpenKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        wchar_t steamPath[MAX_PATH];
        DWORD steamPathSize = sizeof(steamPath);
        DWORD type;
        
        if (RegQueryValueEx(hKey, L"SteamPath", NULL, &type, 
                           reinterpret_cast<LPBYTE>(steamPath), 
                           &steamPathSize) == ERROR_SUCCESS && type == REG_SZ) {
            
            // 读取库文件夹配置
            wstring libraryFoldersPath = steamPath;
            libraryFoldersPath += L"\\steamapps\\libraryfolders.vdf";
            
            // 简化处理 - 实际需要解析VDF文件
            vector<wstring> libraryPaths = { steamPath };
            
            // 添加常见库位置
            wchar_t programFiles[MAX_PATH];
            SHGetSpecialFolderPath(NULL, programFiles, CSIDL_PROGRAM_FILES, FALSE);
            libraryPaths.push_back(wstring(programFiles) + L"\\Steam");
            
            // 扫描库文件夹
            for (const auto& path : libraryPaths) {
                wstring gamesPath = path + L"\\steamapps\\common";
                
                WIN32_FIND_DATA findData;
                wstring searchPath = gamesPath + L"\\*";
                HANDLE hFind = FindFirstFile(searchPath.c_str(), &findData);
                
                if (hFind != INVALID_HANDLE_VALUE) {
                    do {
                        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                            if (wcscmp(findData.cFileName, L".") != 0 && 
                                wcscmp(findData.cFileName, L"..") != 0) {
                                
                                wstring gamePath = gamesPath + L"\\" + findData.cFileName;
                                
                                // 检查游戏可执行文件
                                WIN32_FIND_DATA exeFindData;
                                wstring exeSearchPath = gamePath + L"\\*.exe";
                                HANDLE exeFind = FindFirstFile(exeSearchPath.c_str(), &exeFindData);
                                
                                if (exeFind != INVALID_HANDLE_VALUE) {
                                    GameInfo game;
                                    game.name = findData.cFileName;
                                    game.path = gamePath;
                                    game.platform = L"Steam";
                                    game.exePath = gamePath + L"\\" + exeFindData.cFileName;
                                    games.push_back(game);
                                    FindClose(exeFind);
                                }
                            }
                        }
                    } while (FindNextFile(hFind, &findData));
                    FindClose(hFind);
                }
            }
        }
        RegCloseKey(hKey);
    }
    return games;
}

// Epic Games检测
vector<GameInfo> DetectEpicGames() {
    vector<GameInfo> games;
    
    wchar_t programDataPath[MAX_PATH];
    if (SHGetSpecialFolderPath(NULL, programDataPath, CSIDL_COMMON_APPDATA, FALSE)) {
        wstring manifestsPath = programDataPath;
        manifestsPath += L"\\Epic\\EpicGamesLauncher\\Data\\Manifests";
        
        WIN32_FIND_DATA findData;
        wstring searchPath = manifestsPath + L"\\*.item";
        HANDLE hFind = FindFirstFile(searchPath.c_str(), &findData);
        
        if (hFind != INVALID_HANDLE_VALUE) {
            do {
                if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
                    wstring manifestPath = manifestsPath + L"\\" + findData.cFileName;
                    
                    // 简化处理 - 实际需要解析JSON文件
                    // 这里仅演示逻辑
                    GameInfo game;
                    game.name = findData.cFileName;
                    game.platform = L"Epic Games";
                    
                    // 从文件名提取游戏名
                    wstring fileName = findData.cFileName;
                    size_t pos = fileName.find_last_of(L'.');
                    if (pos != wstring::npos) {
                        fileName = fileName.substr(0, pos);
                    }
                    game.name = fileName;
                    
                    games.push_back(game);
                }
            } while (FindNextFile(hFind, &findData));
            FindClose(hFind);
        }
    }
    return games;
}

// 快捷方式解析
vector<GameInfo> ScanShortcutsForGames() {
    vector<GameInfo> games;
    
    // 获取桌面路径
    wchar_t desktopPath[MAX_PATH];
    if (SHGetSpecialFolderPath(NULL, desktopPath, CSIDL_DESKTOPDIRECTORY, FALSE)) {
        WIN32_FIND_DATA findData;
        wstring searchPath = wstring(desktopPath) + L"\\*.lnk";
        HANDLE hFind = FindFirstFile(searchPath.c_str(), &findData);
        
        if (hFind != INVALID_HANDLE_VALUE) {
            do {
                if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
                    wstring shortcutPath = wstring(desktopPath) + L"\\" + findData.cFileName;
                    
                    // 解析快捷方式
                    IShellLink* pShellLink;
                    IPersistFile* pPersistFile;
                    
                    if (SUCCEEDED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, 
                                                  IID_IShellLink, (void**)&pShellLink))) {
                        if (SUCCEEDED(pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile))) {
                            if (SUCCEEDED(pPersistFile->Load(shortcutPath.c_str(), STGM_READ))) {
                                wchar_t targetPath[MAX_PATH];
                                if (SUCCEEDED(pShellLink->GetPath(targetPath, MAX_PATH, NULL, 0))) {
                                    // 检查是否是游戏
                                    wstring target = targetPath;
                                    if (target.find(L"game") != wstring::npos || 
                                        target.find(L"Game") != wstring::npos ||
                                        target.find(L"Steam") != wstring::npos ||
                                        target.find(L"Epic") != wstring::npos) {
                                        
                                        GameInfo game;
                                        game.name = findData.cFileName;
                                        game.path = targetPath;
                                        game.platform = L"Shortcut";
                                        
                                        // 移除.lnk扩展名
                                        size_t pos = game.name.find_last_of(L'.');
                                        if (pos != wstring::npos) {
                                            game.name = game.name.substr(0, pos);
                                        }
                                        
                                        games.push_back(game);
                                    }
                                }
                            }
                            pPersistFile->Release();
                        }
                        pShellLink->Release();
                    }
                }
            } while (FindNextFile(hFind, &findData));
            FindClose(hFind);
        }
    }
    return games;
}

// 检测运行中的游戏
vector<GameInfo> DetectRunningGames() {
    vector<GameInfo> games;
    
    // 获取进程列表
    DWORD processes[1024], cbNeeded;
    if (!EnumProcesses(processes, sizeof(processes), &cbNeeded) {
        return games;
    }
    
    DWORD processCount = cbNeeded / sizeof(DWORD);
    for (DWORD i = 0; i < processCount; i++) {
        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processes[i]);
        if (hProcess) {
            wchar_t processName[MAX_PATH] = L"<unknown>";
            if (GetModuleFileNameEx(hProcess, NULL, processName, MAX_PATH) > 0) {
                // 检查是否是游戏进程
                wstring name = processName;
                if (name.find(L"game") != wstring::npos || 
                    name.find(L"Game") != wstring::npos ||
                    name.find(L"steamapps") != wstring::npos ||
                    name.find(L"EpicGames") != wstring::npos) {
                    
                    // 提取游戏名称
                    wstring exeName;
                    size_t pos = name.find_last_of(L'\\');
                    if (pos != wstring::npos) {
                        exeName = name.substr(pos + 1);
                    } else {
                        exeName = name;
                    }
                    
                    GameInfo game;
                    game.name = exeName;
                    game.path = name.substr(0, pos);
                    game.platform = L"Running Process";
                    game.exePath = name;
                    
                    games.push_back(game);
                }
            }
            CloseHandle(hProcess);
        }
    }
    return games;
}

// 检测UWP游戏 (Xbox Game Pass)
vector<GameInfo> DetectUwpGames() {
    vector<GameInfo> games;
    
    try {
        PackageManager packageManager;
        auto packages = packageManager.FindPackagesForUser(L"");
        
        for (const auto& package : packages) {
            try {
                // 检查是否是游戏
                if (package.IsFramework() || package.IsResourcePackage()) {
                    continue;
                }
                
                auto logo = package.Logo();
                auto name = package.DisplayName();
                auto appListEntries = package.GetAppListEntries();
                
                if (appListEntries.Size() > 0) {
                    // 检查分类
                    auto keywords = package.Keywords();
                    bool isGame = false;
                    
                    if (keywords.Size() > 0) {
                        for (const auto& keyword : keywords) {
                            if (keyword == L"Game") {
                                isGame = true;
                                break;
                            }
                        }
                    }
                    
                    // 通过包名称检查
                    if (!isGame) {
                        wstring packageName = package.Id().Name().c_str();
                        if (packageName.find(L"game") != wstring::npos) {
                            isGame = true;
                        }
                    }
                    
                    if (isGame) {
                        GameInfo game;
                        game.name = name.c_str();
                        game.platform = L"Xbox/UWP";
                        
                        // 获取安装路径
                        auto path = package.InstalledLocation().Path().c_str();
                        game.path = path;
                        
                        games.push_back(game);
                    }
                }
            } catch (...) {
                // 忽略错误
            }
        }
    } catch (...) {
        // 忽略错误
    }
    
    return games;
}

// 文件系统扫描
vector<GameInfo> ScanFileSystemForGames() {
    vector<GameInfo> games;
    
    // 常见游戏目录
    vector<wstring> commonPaths = {
        L"Program Files",
        L"Program Files (x86)",
        L"Games",
        L"Steam\\steamapps\\common",
        L"Epic Games",
        L"GOG Games"
    };
    
    wchar_t drives[MAX_PATH];
    DWORD driveCount = GetLogicalDriveStrings(MAX_PATH, drives);
    
    for (DWORD i = 0; i < driveCount; i += 4) {
        wchar_t* drive = drives + i;
        if (GetDriveType(drive) == DRIVE_FIXED) {
            for (const auto& path : commonPaths) {
                wstring fullPath = drive;
                fullPath += path;
                
                WIN32_FIND_DATA findData;
                wstring searchPath = fullPath + L"\\*";
                HANDLE hFind = FindFirstFile(searchPath.c_str(), &findData);
                
                if (hFind != INVALID_HANDLE_VALUE) {
                    do {
                        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                            if (wcscmp(findData.cFileName, L".") != 0 && 
                                wcscmp(findData.cFileName, L"..") != 0) {
                                
                                wstring gamePath = fullPath + L"\\" + findData.cFileName;
                                
                                // 检查游戏可执行文件
                                WIN32_FIND_DATA exeFindData;
                                wstring exeSearchPath = gamePath + L"\\*.exe";
                                HANDLE exeFind = FindFirstFile(exeSearchPath.c_str(), &exeFindData);
                                
                                if (exeFind != INVALID_HANDLE_VALUE) {
                                    GameInfo game;
                                    game.name = findData.cFileName;
                                    game.path = gamePath;
                                    game.platform = L"File System";
                                    game.exePath = gamePath + L"\\" + exeFindData.cFileName;
                                    games.push_back(game);
                                    FindClose(exeFind);
                                }
                            }
                        }
                    } while (FindNextFile(hFind, &findData));
                    FindClose(hFind);
                }
            }
        }
    }
    return games;
}

// 打印游戏信息
void PrintGameInfo(const vector<GameInfo>& games) {
    wcout << L"Detected Games (" << games.size() << L"):" << endl;
    wcout << L"======================================" << endl;
    
    for (const auto& game : games) {
        wcout << L"Name: " << game.name << endl;
        wcout << L"Path: " << game.path << endl;
        wcout << L"Platform: " << game.platform << endl;
        if (!game.exePath.empty()) {
            wcout << L"Executable: " << game.exePath << endl;
        }
        wcout << L"--------------------------------------" << endl;
    }
}

int main() {
    // 初始化COM
    CoInitialize(NULL);
    
    // 初始化WinRT
    winrt::init_apartment();
    
    vector<GameInfo> allGames;
    
    // 使用多种方法检测游戏
    wcout << L"Scanning registry..." << endl;
    auto registryGames = ScanRegistryForGames();
    allGames.insert(allGames.end(), registryGames.begin(), registryGames.end());
    
    wcout << L"Scanning Steam..." << endl;
    auto steamGames = DetectSteamGames();
    allGames.insert(allGames.end(), steamGames.begin(), steamGames.end());
    
    wcout << L"Scanning Epic Games..." << endl;
    auto epicGames = DetectEpicGames();
    allGames.insert(allGames.end(), epicGames.begin(), epicGames.end());
    
    wcout << L"Scanning shortcuts..." << endl;
    auto shortcutGames = ScanShortcutsForGames();
    allGames.insert(allGames.end(), shortcutGames.begin(), shortcutGames.end());
    
    wcout << L"Scanning running processes..." << endl;
    auto runningGames = DetectRunningGames();
    allGames.insert(allGames.end(), runningGames.begin(), runningGames.end());
    
    wcout << L"Scanning UWP apps..." << endl;
    auto uwpGames = DetectUwpGames();
    allGames.insert(allGames.end(), uwpGames.begin(), uwpGames.end());
    
    wcout << L"Scanning file system..." << endl;
    auto fsGames = ScanFileSystemForGames();
    allGames.insert(allGames.end(), fsGames.begin(), fsGames.end());
    
    // 打印检测到的所有游戏
    PrintGameInfo(allGames);
    
    // 清理
    CoUninitialize();
    
    return 0;
}

实现方法详解

1. 注册表扫描 (ScanRegistryForGames)

  • 扫描 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
  • 同时扫描64位注册表路径 HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\...
  • 提取DisplayNameInstallLocation
  • 通过Publisher字段识别平台(Steam/Epic/GOG)

2. 平台集成检测

  • Steam检测 (DetectSteamGames)
    • 从注册表获取Steam安装路径
    • 解析libraryfolders.vdf获取库位置
    • 扫描steamapps/common目录
  • Epic Games检测 (DetectEpicGames)
    • 扫描ProgramData\Epic\EpicGamesLauncher\Data\Manifests
    • 解析.item清单文件(简化处理)

3. 快捷方式解析 (ScanShortcutsForGames)

  • 扫描桌面快捷方式(.lnk)
  • 使用COM接口解析目标路径
  • 通过路径关键词("game", "steam"等)识别游戏

4. 运行进程检测 (DetectRunningGames)

  • 枚举系统进程
  • 检查进程名包含"game"、"steam"等关键词
  • 获取进程完整路径

5. UWP游戏检测 (DetectUwpGames)

  • 使用Windows Runtime API
  • 通过PackageManager枚举所有应用
  • 检查应用分类或关键词包含"Game"

6. 文件系统扫描 (ScanFileSystemForGames)

  • 扫描常见游戏目录:
    • Program Files
    • Program Files (x86)
    • Steam\steamapps\common
    • Epic Games
    • GOG Games
  • 递归查找可执行文件(.exe)

优化与注意事项

  1. 性能优化

    • 使用多线程并行扫描
    • 缓存扫描结果避免重复
    • 优先扫描注册表和平台集成(速度更快)
  2. 错误处理

    • 检查路径有效性(PathFileExists)
    • 处理权限问题(管理员权限)
    • 异常捕获防止崩溃
  3. 扩展性

    • 支持用户自定义扫描路径
    • 添加更多平台支持(GOG, Origin等)
    • 实现配置文件解析(VDF/JSON等)
  4. 去重机制

    • 通过安装路径和游戏名去重
    • 合并来自不同源的相同游戏
  5. UWP支持

    • 需要C++/WinRT
    • 支持Xbox Game Pass游戏检测

这个实现涵盖了Windows游戏检测的主要方法,可以根据需要扩展或调整特定部分的实现细节。

相关推荐
高山莫衣1 小时前
将文件移入回收站而不是直接删除
windows
yzx9910132 小时前
JS与Go:编程语言双星的碰撞与共生
java·数据结构·游戏·小程序·ffmpeg
洁可2 小时前
上位机程序开发基础介绍
c++·笔记
寒心雨梦2 小时前
本地preload hook案例
c++
滴水成川4 小时前
现代 C++ 开发工作流(VSCode / Cursor)
开发语言·c++·vscode·cursor
张同学的IT技术日记4 小时前
重构 MVC:让经典架构完美适配复杂智能系统的后端业务逻辑层(内附框架示例代码)
c++·后端·重构·架构·mvc·软件开发·工程应用
万能的小裴同学4 小时前
星痕共鸣数据分析2
c++·数据分析
刚入坑的新人编程4 小时前
暑期算法训练.8
数据结构·c++·算法·面试·哈希算法
TalkU浩克5 小时前
C++中使用Essentia实现STFT/ISTFT
开发语言·c++·音频·istft·stft·essentia
szx04275 小时前
解决笔记本合盖开盖DPI缩放大小变 (异于网传方法,Win11 24H2)
windows·win11·笔记本·dpi·睡眠·高分屏·合盖