/ src / modules / Workspaces / WorkspacesLib / SteamGameHelper.cpp
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  }