11#include <unordered_set>
29bool isAppUsingGoldSrcEnginePredicate(std::string_view installDir) {
30 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
32 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry){
33 return entry.is_directory(ec) && std::filesystem::exists(entry.path() /
"liblist.gam", ec);
37bool isAppUsingSourceEnginePredicate(std::string_view installDir) {
38 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
40 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry){
41 return entry.is_directory(ec) && std::filesystem::exists(entry.path() /
"gameinfo.txt", ec);
45bool isAppUsingSource2EnginePredicate(std::string_view installDir) {
46 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
48 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry) {
49 if (!entry.is_directory(ec)) {
52 if (std::filesystem::exists(entry.path() /
"gameinfo.gi", ec)) {
55 std::filesystem::directory_iterator subDirIterator{entry.path(), std::filesystem::directory_options::skip_permission_denied};
56 return std::any_of(std::filesystem::begin(subDirIterator), std::filesystem::end(subDirIterator), [&ec](
const auto& entry_) {
57 return entry_.is_directory(ec) && std::filesystem::exists(entry_.path() /
"gameinfo.gi", ec);
63std::unordered_set<AppID> getAppsKnownToUseEngine(
bool(*p)(std::string_view)) {
64 if (p == &::isAppUsingGoldSrcEnginePredicate) {
69 if (p == &::isAppUsingSourceEnginePredicate) {
74 if (p == &::isAppUsingSource2EnginePredicate) {
82template<
bool(*P)(std::
string_view)>
83bool isAppUsingEngine(
const Steam* steam,
AppID appID) {
84 static std::unordered_set<AppID> knownIs = ::getAppsKnownToUseEngine(P);
85 if (knownIs.contains(appID)) {
89 static std::unordered_set<AppID> knownIsNot;
90 if (knownIsNot.contains(appID)) {
99 if (std::error_code ec; !std::filesystem::exists(installDir, ec)) [[unlikely]] {
104 knownIs.emplace(appID);
107 knownIsNot.emplace(appID);
111[[nodiscard]] std::string getAppArtPath(
const KV1Binary& assetCache,
AppID appID, std::string_view steamInstallDir, std::string_view
id) {
114 assetCache[0].getChildCount() != 3 ||
115 !assetCache[0].hasChild(
"cache_version") ||
116 static_cast<KV1BinaryValueType>(assetCache[0][
"cache_version"].getValue().index()) != KV1BinaryValueType::INT32 ||
117 *assetCache[0][
"cache_version"].getValue<int32_t>() != 2
121 const auto idStr = std::format(
"{}", appID);
122 const auto& cache = assetCache[0][2][idStr];
123 if (cache.isInvalid() || !cache.hasChild(
id)) {
126 const auto path = (std::filesystem::path{steamInstallDir} /
"appcache" /
"librarycache" / idStr / *cache[id].getValue<std::string>()).string();
127 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
136 std::filesystem::path steamLocation;
142 static constexpr DWORD STEAM_LOCATION_MAX_SIZE = 16383;
143 std::unique_ptr<char[]> steamLocationData{
new char[STEAM_LOCATION_MAX_SIZE]};
146 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, R
"(SOFTWARE\Valve\Steam)", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &steam) != ERROR_SUCCESS) {
150 DWORD steamLocationSize = STEAM_LOCATION_MAX_SIZE;
151 if (RegQueryValueExA(steam,
"InstallPath",
nullptr,
nullptr,
reinterpret_cast<LPBYTE
>(steamLocationData.get()), &steamLocationSize) != ERROR_SUCCESS) {
156 steamLocation = steamLocationSize > 0 ? std::string(steamLocationData.get(), steamLocationSize - 1) :
"";
160 std::filesystem::path home{std::getenv(
"HOME")};
162 steamLocation = home /
"Library" /
"Application Support" /
"Steam";
165 steamLocation = home /
"snap" /
"steam" /
"common" /
".steam" /
"steam";
167 if (!std::filesystem::exists(steamLocation, ec)) {
169 steamLocation = home /
".steam" /
"steam";
174 if (!std::filesystem::exists(steamLocation, ec)) {
175 std::string location;
176 std::filesystem::path d{
"cwd/steamclient64.dll"};
177 for (
const auto& entry : std::filesystem::directory_iterator{
"/proc/"}) {
178 if (std::filesystem::exists(entry / d, ec)) {
180 const auto s = std::filesystem::read_symlink(entry.path() /
"cwd", ec);
184 location = s.string();
188 if (location.empty()) {
191 steamLocation = location;
195 if (!std::filesystem::exists(steamLocation.string(), ec)) {
198 this->steamInstallDir = steamLocation.string();
200 auto libraryFoldersFilePath = steamLocation /
"steamapps" /
"libraryfolders.vdf";
201 if (!std::filesystem::exists(libraryFoldersFilePath, ec)) {
207 const auto& libraryFoldersValue = libraryFolders[
"libraryfolders"];
208 if (libraryFoldersValue.isInvalid()) {
212 for (uint64_t i = 0; i < libraryFoldersValue.getChildCount(); i++) {
213 const auto& folder = libraryFoldersValue[i];
215 auto folderName = folder.getKey();
216 if (folderName ==
"TimeNextStatsReport" || folderName ==
"ContentStatsID") {
220 const auto& folderPath = folder[
"path"];
221 if (folderPath.isInvalid()) {
225 std::filesystem::path libraryFolderPath{folderPath.getValue()};
227 std::string libraryFolderPathProcessedEscapes;
228 bool hitBackslash =
false;
229 for (
char c : libraryFolderPath.string()) {
230 if (hitBackslash || c !=
'\\') {
231 libraryFolderPathProcessedEscapes += c;
232 hitBackslash =
false;
237 libraryFolderPath = libraryFolderPathProcessedEscapes;
239 libraryFolderPath /=
"steamapps";
241 if (!std::filesystem::exists(libraryFolderPath, ec)) {
244 this->libraryDirs.push_back(libraryFolderPath.string());
246 for (
const auto& entry : std::filesystem::directory_iterator{libraryFolderPath, std::filesystem::directory_options::skip_permission_denied}) {
247 auto entryName = entry.path().filename().string();
248 if (!entryName.starts_with(
"appmanifest_") || !entryName.ends_with(
".acf")) {
254 const auto& appState = appManifest[
"AppState"];
255 if (appState.isInvalid()) {
259 const auto& appName = appState[
"name"];
260 if (appName.isInvalid()) {
263 const auto& appInstallDir = appState[
"installdir"];
264 if (appInstallDir.isInvalid()) {
267 const auto& appID = appState[
"appid"];
268 if (appID.isInvalid()) {
272 this->gameDetails[std::stoi(std::string{appID.getValue()})] = GameInfo{
273 .name = std::string{appName.getValue()},
274 .installDir = std::string{appInstallDir.getValue()},
275 .libraryInstallDirsIndex = this->libraryDirs.size() - 1,
280 const auto assetCacheFilePath = steamLocation /
"appcache" /
"librarycache" /
"assetcache.vdf";
281 if (std::filesystem::exists(assetCacheFilePath, ec)) {
287 return this->steamInstallDir;
291 return this->libraryDirs;
295 return (std::filesystem::path{this->steamInstallDir} /
"steamapps" /
"sourcemods").
string();
299 auto keys = std::views::keys(this->gameDetails);
300 return {keys.begin(), keys.end()};
304 return this->gameDetails.contains(appID);
308 if (!this->gameDetails.contains(appID)) {
311 return this->gameDetails.at(appID).name;
315 if (!this->gameDetails.contains(appID)) {
318 return (std::filesystem::path{this->libraryDirs[this->gameDetails.at(appID).libraryInstallDirsIndex]} /
"common" / this->gameDetails.at(appID).installDir).string();
322 if (!this->gameDetails.contains(appID)) {
325 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"4f"); !cachedPath.empty()) {
328 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"icon.jpg").
string();
329 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
330 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_icon.jpg")).string();
331 if (!std::filesystem::exists(path, ec)) {
339 if (!this->gameDetails.contains(appID)) {
342 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"2f"); !cachedPath.empty()) {
345 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"logo.png").
string();
346 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
347 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_logo.png")).string();
348 if (!std::filesystem::exists(path, ec)) {
356 if (!this->gameDetails.contains(appID)) {
359 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"1f"); !cachedPath.empty()) {
362 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"library_hero.jpg").
string();
363 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
364 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_library_hero.jpg")).string();
365 if (!std::filesystem::exists(path, ec)) {
373 if (!this->gameDetails.contains(appID)) {
376 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"0f"); !cachedPath.empty()) {
379 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"library_600x900.jpg").
string();
380 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
381 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_library_600x900.jpg")).string();
382 if (!std::filesystem::exists(path, ec)) {
390 if (!this->gameDetails.contains(appID)) {
393 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"3f"); !cachedPath.empty()) {
396 auto path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / std::to_string(appID) /
"header.jpg").
string();
397 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
398 path = (std::filesystem::path{this->steamInstallDir} /
"appcache" /
"librarycache" / (std::to_string(appID) +
"_header.jpg")).string();
399 if (!std::filesystem::exists(path, ec)) {
407 return ::isAppUsingEngine<::isAppUsingGoldSrcEnginePredicate>(
this, appID);
411 return ::isAppUsingEngine<::isAppUsingSourceEnginePredicate>(
this, appID);
415 return ::isAppUsingEngine<::isAppUsingSource2EnginePredicate>(
this, appID);
418Steam::operator bool()
const {
419 return !this->gameDetails.empty();
uint64_t getChildCount() const
Get the number of child elements.
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 getAppHeroPath(AppID appID) const
std::string getAppInstallDir(AppID appID) const
std::string_view getInstallDir() const
std::string getAppIconPath(AppID appID) const
std::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
std::string readFileText(const std::string &filepath, std::size_t startOffset=0)
Based on SteamAppPathProvider.