10#include <unordered_set>
28bool isAppUsingGoldSrcEnginePredicate(std::string_view installDir) {
29 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
31 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry){
32 return entry.is_directory(ec) && std::filesystem::exists(entry.path() /
"liblist.gam", ec);
36bool isAppUsingSourceEnginePredicate(std::string_view installDir) {
37 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
39 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry){
40 return entry.is_directory(ec) && std::filesystem::exists(entry.path() /
"gameinfo.txt", ec);
44bool isAppUsingSource2EnginePredicate(std::string_view installDir) {
45 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
47 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry) {
48 if (!entry.is_directory(ec)) {
51 if (std::filesystem::exists(entry.path() /
"gameinfo.gi", ec)) {
54 std::filesystem::directory_iterator subDirIterator{entry.path(), std::filesystem::directory_options::skip_permission_denied};
55 return std::any_of(std::filesystem::begin(subDirIterator), std::filesystem::end(subDirIterator), [&ec](
const auto& entry_) {
56 return entry_.is_directory(ec) && std::filesystem::exists(entry_.path() /
"gameinfo.gi", ec);
62std::unordered_set<AppID> getAppsKnownToUseEngine(
bool(*p)(std::string_view)) {
63 if (p == &::isAppUsingGoldSrcEnginePredicate) {
68 if (p == &::isAppUsingSourceEnginePredicate) {
73 if (p == &::isAppUsingSource2EnginePredicate) {
81template<
bool(*P)(std::
string_view)>
82bool isAppUsingEngine(
const Steam* steam,
AppID appID) {
83 static std::unordered_set<AppID> knownIs = ::getAppsKnownToUseEngine(P);
84 if (knownIs.contains(appID)) {
88 static std::unordered_set<AppID> knownIsNot;
89 if (knownIsNot.contains(appID)) {
98 if (std::error_code ec; !std::filesystem::exists(installDir, ec)) [[unlikely]] {
103 knownIs.emplace(appID);
106 knownIsNot.emplace(appID);
113 std::filesystem::path steamLocation;
119 static constexpr DWORD STEAM_LOCATION_MAX_SIZE = 16383;
120 std::unique_ptr<char[]> steamLocationData{
new char[STEAM_LOCATION_MAX_SIZE]};
123 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, R
"(SOFTWARE\Valve\Steam)", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &steam) != ERROR_SUCCESS) {
127 DWORD steamLocationSize = STEAM_LOCATION_MAX_SIZE;
128 if (RegQueryValueExA(steam,
"InstallPath",
nullptr,
nullptr,
reinterpret_cast<LPBYTE
>(steamLocationData.get()), &steamLocationSize) != ERROR_SUCCESS) {
133 steamLocation = steamLocationSize > 0 ? std::string(steamLocationData.get(), steamLocationSize - 1) :
"";
137 std::filesystem::path home{std::getenv(
"HOME")};
139 steamLocation = home /
"Library" /
"Application Support" /
"Steam";
142 steamLocation = home /
"snap" /
"steam" /
"common" /
".steam" /
"steam";
144 if (!std::filesystem::exists(steamLocation, ec)) {
146 steamLocation = home /
".steam" /
"steam";
151 if (!std::filesystem::exists(steamLocation, ec)) {
152 std::string location;
153 std::filesystem::path d{
"cwd/steamclient64.dll"};
154 for (
const auto& entry : std::filesystem::directory_iterator{
"/proc/"}) {
155 if (std::filesystem::exists(entry / d, ec)) {
157 const auto s = std::filesystem::read_symlink(entry.path() /
"cwd", ec);
161 location = s.string();
165 if (location.empty()) {
168 steamLocation = location;
172 if (!std::filesystem::exists(steamLocation.string(), ec)) {
175 this->steamInstallDir = steamLocation.string();
177 auto libraryFoldersFilePath = steamLocation /
"steamapps" /
"libraryfolders.vdf";
178 if (!std::filesystem::exists(libraryFoldersFilePath, ec)) {
184 const auto& libraryFoldersValue = libraryFolders[
"libraryfolders"];
185 if (libraryFoldersValue.isInvalid()) {
189 for (uint64_t i = 0; i < libraryFoldersValue.getChildCount(); i++) {
190 const auto& folder = libraryFoldersValue[i];
192 auto folderName = folder.getKey();
193 if (folderName ==
"TimeNextStatsReport" || folderName ==
"ContentStatsID") {
197 const auto& folderPath = folder[
"path"];
198 if (folderPath.isInvalid()) {
202 std::filesystem::path libraryFolderPath{folderPath.getValue()};
204 std::string libraryFolderPathProcessedEscapes;
205 bool hitBackslash =
false;
206 for (
char c : libraryFolderPath.string()) {
207 if (hitBackslash || c !=
'\\') {
208 libraryFolderPathProcessedEscapes += c;
209 hitBackslash =
false;
214 libraryFolderPath = libraryFolderPathProcessedEscapes;
216 libraryFolderPath /=
"steamapps";
218 if (!std::filesystem::exists(libraryFolderPath, ec)) {
221 this->libraryDirs.push_back(libraryFolderPath.string());
223 for (
const auto& entry : std::filesystem::directory_iterator{libraryFolderPath, std::filesystem::directory_options::skip_permission_denied}) {
224 auto entryName = entry.path().filename().string();
225 if (!entryName.starts_with(
"appmanifest_") || !entryName.ends_with(
".acf")) {
231 const auto& appState = appManifest[
"AppState"];
232 if (appState.isInvalid()) {
236 const auto& appName = appState[
"name"];
237 if (appName.isInvalid()) {
240 const auto& appInstallDir = appState[
"installdir"];
241 if (appInstallDir.isInvalid()) {
244 const auto& appID = appState[
"appid"];
245 if (appID.isInvalid()) {
249 this->gameDetails[std::stoi(std::string{appID.getValue()})] = GameInfo{
250 .name = std::string{appName.getValue()},
251 .installDir = std::string{appInstallDir.getValue()},
252 .libraryInstallDirsIndex = this->libraryDirs.size() - 1,
259 return this->steamInstallDir;
263 return this->libraryDirs;
267 return (std::filesystem::path{this->steamInstallDir} /
"steamapps" /
"sourcemods").
string();
271 auto keys = std::views::keys(this->gameDetails);
272 return {keys.begin(), keys.end()};
276 return this->gameDetails.contains(appID);
280 if (!this->gameDetails.contains(appID)) {
283 return this->gameDetails.at(appID).name;
287 if (!this->gameDetails.contains(appID)) {
290 return (std::filesystem::path{this->libraryDirs[this->gameDetails.at(appID).libraryInstallDirsIndex]} /
"common" / this->gameDetails.at(appID).installDir).string();
294 if (!this->gameDetails.contains(appID)) {
297 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_icon.jpg")).string();
298 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
300 for (
const auto& image : std::filesystem::directory_iterator{std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID), std::filesystem::directory_options::skip_permission_denied, ec}) {
301 if (!image.is_regular_file()) {
305 if (image.path().stem().string().size() == 40) {
306 return image.path().string();
315 if (!this->gameDetails.contains(appID)) {
318 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"logo.png").
string();
319 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
320 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_logo.png")).string();
321 if (!std::filesystem::exists(path, ec)) {
329 if (!this->gameDetails.contains(appID)) {
332 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"library_600x900.jpg").
string();
333 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
334 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_library_600x900.jpg")).string();
335 if (!std::filesystem::exists(path, ec)) {
343 if (!this->gameDetails.contains(appID)) {
346 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"header.jpg").
string();
347 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
348 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_header.jpg")).string();
349 if (!std::filesystem::exists(path, ec)) {
357 return ::isAppUsingEngine<::isAppUsingGoldSrcEnginePredicate>(
this, appID);
361 return ::isAppUsingEngine<::isAppUsingSourceEnginePredicate>(
this, appID);
365 return ::isAppUsingEngine<::isAppUsingSource2EnginePredicate>(
this, appID);
368Steam::operator bool()
const {
369 return !this->gameDetails.empty();
std::vector< AppID > getInstalledApps() const
bool isAppUsingSourceEngine(AppID appID) const
bool isAppUsingGoldSrcEngine(AppID appID) const
std::string getAppBoxArtPath(AppID appID) const
std::string getSourceModDir() const
const std::vector< std::string > & getLibraryDirs() const
bool isAppUsingSource2Engine(AppID appID) const
std::string getAppStoreArtPath(AppID appID) const
std::string_view getAppName(AppID appID) const
bool isAppInstalled(AppID appID) const
std::string getAppLogoPath(AppID appID) const
std::string getAppInstallDir(AppID appID) const
std::string_view getInstallDir() const
std::string getAppIconPath(AppID appID) const
std::string readFileText(const std::string &filepath, std::size_t startOffset=0)
Based on SteamAppPathProvider.