SteamGameHelper.cpp
1 #include "pch.h" 2 #include "SteamHelper.h" 3 #include <fstream> 4 #include <sstream> 5 #include <unordered_map> 6 #include <filesystem> 7 #include <regex> 8 #include <string> 9 10 namespace Utils 11 { 12 13 static std::wstring Utf8ToWide(const std::string& utf8) 14 { 15 if (utf8.empty()) 16 return L""; 17 18 int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast<int>(utf8.size()), nullptr, 0); 19 if (size <= 0) 20 return L""; 21 22 std::wstring wide(size, L'\0'); 23 MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast<int>(utf8.size()), wide.data(), size); 24 return wide; 25 } 26 27 namespace Steam 28 { 29 using namespace std; 30 namespace fs = std::filesystem; 31 32 static std::optional<std::wstring> GetSteamExePathFromRegistry() 33 { 34 static std::optional<std::wstring> cachedPath; 35 if (cachedPath.has_value()) 36 { 37 return cachedPath; 38 } 39 40 const std::vector<HKEY> roots = { HKEY_CLASSES_ROOT, HKEY_LOCAL_MACHINE, HKEY_USERS }; 41 const std::vector<std::wstring> subKeys = { 42 L"steam\\shell\\open\\command", 43 L"Software\\Classes\\steam\\shell\\open\\command", 44 }; 45 46 for (HKEY root : roots) 47 { 48 for (const auto& subKey : subKeys) 49 { 50 HKEY hKey; 51 if (RegOpenKeyExW(root, subKey.c_str(), 0, KEY_READ, &hKey) == ERROR_SUCCESS) 52 { 53 wchar_t value[512]; 54 DWORD size = sizeof(value); 55 DWORD type = 0; 56 57 if (RegQueryValueExW(hKey, nullptr, nullptr, &type, reinterpret_cast<LPBYTE>(value), &size) == ERROR_SUCCESS && 58 (type == REG_SZ || type == REG_EXPAND_SZ)) 59 { 60 std::wregex exeRegex(LR"delim("([^"]+steam\.exe)")delim"); 61 std::wcmatch match; 62 if (std::regex_search(value, match, exeRegex) && match.size() > 1) 63 { 64 RegCloseKey(hKey); 65 cachedPath = match[1].str(); 66 return cachedPath; 67 } 68 } 69 70 RegCloseKey(hKey); 71 } 72 } 73 } 74 75 cachedPath = std::nullopt; 76 return std::nullopt; 77 } 78 79 static fs::path GetSteamBasePath() 80 { 81 auto steamFolderOpt = GetSteamExePathFromRegistry(); 82 if (!steamFolderOpt) 83 { 84 return {}; 85 } 86 87 return fs::path(*steamFolderOpt).parent_path() / L"steamapps"; 88 } 89 90 static fs::path GetAcfFilePath(const std::wstring& gameId) 91 { 92 auto steamFolderOpt = GetSteamExePathFromRegistry(); 93 if (!steamFolderOpt) 94 { 95 return {}; 96 } 97 98 return GetSteamBasePath() / (L"appmanifest_" + gameId + L".acf"); 99 } 100 101 static fs::path GetGameInstallPath(const std::wstring& gameFolderName) 102 { 103 auto steamFolderOpt = GetSteamExePathFromRegistry(); 104 if (!steamFolderOpt) 105 { 106 return {}; 107 } 108 109 return GetSteamBasePath() / L"common" / gameFolderName; 110 } 111 112 static unordered_map<wstring, wstring> ParseAcfFile(const fs::path& acfPath) 113 { 114 unordered_map<wstring, wstring> result; 115 116 ifstream file(acfPath); 117 if (!file.is_open()) 118 return result; 119 120 string line; 121 while (getline(file, line)) 122 { 123 smatch matches; 124 static const regex pattern(R"delim("([^"]+)"\s+"([^"]+)")delim"); 125 126 if (regex_search(line, matches, pattern) && matches.size() == 3) 127 { 128 wstring key = Utf8ToWide(matches[1].str()); 129 wstring value = Utf8ToWide(matches[2].str()); 130 result[key] = value; 131 } 132 } 133 134 return result; 135 } 136 137 std::unique_ptr<Steam::SteamGame> GetSteamGameInfoFromAcfFile(const std::wstring& gameId) 138 { 139 fs::path acfPath = Steam::GetAcfFilePath(gameId); 140 141 if (!fs::exists(acfPath)) 142 return nullptr; 143 144 auto kv = ParseAcfFile(acfPath); 145 if (kv.empty() || kv.find(L"installdir") == kv.end()) 146 return nullptr; 147 148 fs::path gamePath = Steam::GetGameInstallPath(kv[L"installdir"]); 149 if (!fs::exists(gamePath)) 150 return nullptr; 151 152 auto game = std::make_unique<Steam::SteamGame>(); 153 game->gameId = gameId; 154 game->gameInstallationPath = gamePath.wstring(); 155 return game; 156 } 157 158 std::wstring GetGameIdFromUrlProtocolPath(const std::wstring& urlPath) 159 { 160 const std::wstring steamGamePrefix = L"steam://rungameid/"; 161 162 if (urlPath.rfind(steamGamePrefix, 0) == 0) 163 { 164 return urlPath.substr(steamGamePrefix.length()); 165 } 166 167 return L""; 168 } 169 170 } 171 }