13#include <unordered_set>
31bool isAppUsingGoldSrcEnginePredicate(
const std::filesystem::path& installDir) {
32 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
34 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry){
35 return entry.is_directory(ec) && std::filesystem::exists(entry.path() /
"liblist.gam", ec);
39bool isAppUsingSourceEnginePredicate(
const std::filesystem::path& installDir) {
40 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
42 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry){
43 return entry.is_directory(ec) && std::filesystem::exists(entry.path() /
"gameinfo.txt", ec);
47bool isAppUsingSource2EnginePredicate(
const std::filesystem::path& installDir) {
48 std::filesystem::directory_iterator dirIterator{installDir, std::filesystem::directory_options::skip_permission_denied};
50 return std::any_of(std::filesystem::begin(dirIterator), std::filesystem::end(dirIterator), [&ec](
const auto& entry) {
51 if (!entry.is_directory(ec)) {
54 if (std::filesystem::exists(entry.path() /
"gameinfo.gi", ec)) {
57 std::filesystem::directory_iterator subDirIterator{entry.path(), std::filesystem::directory_options::skip_permission_denied};
58 return std::any_of(std::filesystem::begin(subDirIterator), std::filesystem::end(subDirIterator), [&ec](
const auto& entry_) {
59 return entry_.is_directory(ec) && std::filesystem::exists(entry_.path() /
"gameinfo.gi", ec);
65std::unordered_set<AppID> getAppsKnownToUseEngine(
bool(*p)(
const std::filesystem::path&)) {
66 if (p == &::isAppUsingGoldSrcEnginePredicate) {
71 if (p == &::isAppUsingSourceEnginePredicate) {
76 if (p == &::isAppUsingSource2EnginePredicate) {
84template<
bool(*P)(const std::filesystem::path&)>
85bool isAppUsingEngine(
const Steam* steam,
AppID appID) {
86 static std::unordered_set<AppID> knownIs = ::getAppsKnownToUseEngine(P);
87 if (knownIs.contains(appID)) {
91 static std::unordered_set<AppID> knownIsNot;
92 if (knownIsNot.contains(appID)) {
101 if (std::error_code ec; !std::filesystem::exists(installDir, ec)) [[unlikely]] {
106 knownIs.emplace(appID);
109 knownIsNot.emplace(appID);
113[[nodiscard]] std::filesystem::path getAppArtPath(
const KV1Binary& assetCache,
AppID appID,
const std::filesystem::path& steamInstallDir, std::string_view
id) {
115 !assetCache[0].hasChild(
"cache_version") || !assetCache[0].hasChild(
"0") ||
117 *assetCache[0][
"cache_version"].getValue<int32_t>() != 2
121 const auto idStr = std::format(
"{}", appID);
125 const auto& cache = assetCache[0][
"0"][idStr];
126 if (cache.isInvalid() || !cache.hasChild(
id)) {
129 auto path = steamInstallDir /
"appcache" /
"librarycache" / idStr / *cache[id].
getValue<std::string>();
130 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
139 std::filesystem::path steamLocation;
145 static constexpr DWORD STEAM_LOCATION_MAX_SIZE = 16384;
146 std::unique_ptr<wchar_t[]> steamLocationData{
new wchar_t[STEAM_LOCATION_MAX_SIZE] {}};
150 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L
"SOFTWARE\\Valve\\Steam", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &steam) != ERROR_SUCCESS &&
151 RegOpenKeyExW(HKEY_LOCAL_MACHINE, L
"SOFTWARE\\Valve\\Steam", 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, &steam) != ERROR_SUCCESS
156 DWORD steamLocationSize = STEAM_LOCATION_MAX_SIZE *
sizeof(wchar_t);
157 if (RegQueryValueExW(steam, L
"InstallPath",
nullptr,
nullptr,
reinterpret_cast<LPBYTE
>(steamLocationData.get()), &steamLocationSize) != ERROR_SUCCESS) {
163 steamLocation = steamLocationSize > 0 ? std::filesystem::path{steamLocationData.get()} : std::filesystem::path{};
167 std::filesystem::path home{std::getenv(
"HOME")};
169 steamLocation = home /
"Library" /
"Application Support" /
"Steam";
172 steamLocation = home /
"snap" /
"steam" /
"common" /
".steam" /
"steam";
174 if (!std::filesystem::exists(steamLocation, ec)) {
176 steamLocation = home /
".steam" /
"steam";
181 if (!std::filesystem::exists(steamLocation, ec)) {
182 std::filesystem::path location;
183 std::filesystem::path d{
"cwd/steamclient64.dll"};
184 for (
const auto& entry : std::filesystem::directory_iterator{
"/proc/"}) {
185 if (std::filesystem::exists(entry / d, ec)) {
187 location = std::filesystem::read_symlink(entry.path() /
"cwd", ec);
194 if (location.empty()) {
197 steamLocation = location;
201 if (!std::filesystem::exists(steamLocation, ec)) {
204 this->steamInstallDir = steamLocation;
206 auto libraryFoldersFilePath = steamLocation /
"config" /
"libraryfolders.vdf";
207 if (!std::filesystem::exists(libraryFoldersFilePath, ec)) {
208 libraryFoldersFilePath = steamLocation /
"steamapps" /
"libraryfolders.vdf";
209 if (!std::filesystem::exists(libraryFoldersFilePath, ec)) {
216 const auto& libraryFoldersValue = libraryFolders[
"libraryfolders"];
217 if (libraryFoldersValue.isInvalid()) {
221 for (uint64_t i = 0; i < libraryFoldersValue.getChildCount(); i++) {
222 const auto& folder = libraryFoldersValue[i];
224 auto folderName = folder.getKey();
225 if (folderName ==
"TimeNextStatsReport" || folderName ==
"ContentStatsID") {
229 const auto& folderPath = folder[
"path"];
230 if (folderPath.isInvalid()) {
234 std::filesystem::path libraryFolderPath{folderPath.getValue()};
235 libraryFolderPath /=
"steamapps";
237 if (!std::filesystem::exists(libraryFolderPath, ec)) {
240 this->libraryDirs.push_back(libraryFolderPath);
242 for (
const auto& entry : std::filesystem::directory_iterator{libraryFolderPath, std::filesystem::directory_options::skip_permission_denied}) {
243 auto entryName = entry.path().filename().string();
244 if (!entryName.starts_with(
"appmanifest_") || !entryName.ends_with(
".acf")) {
250 const auto& appState = appManifest[
"AppState"];
251 if (appState.isInvalid()) {
255 const auto& appName = appState[
"name"];
256 if (appName.isInvalid()) {
259 const auto& appInstallDir = appState[
"installdir"];
260 if (appInstallDir.isInvalid()) {
263 const auto& appID = appState[
"appid"];
264 if (appID.isInvalid()) {
268 this->gameDetails[std::stoi(std::string{appID.getValue()})] = GameInfo{
269 .name = std::string{appName.getValue()},
270 .installDir = std::string{appInstallDir.getValue()},
271 .libraryInstallDirsIndex = this->libraryDirs.size() - 1,
276 const auto assetCacheFilePath = steamLocation /
"appcache" /
"librarycache" /
"assetcache.vdf";
277 if (std::filesystem::exists(assetCacheFilePath, ec)) {
283 return this->steamInstallDir;
287 return this->libraryDirs;
291 return this->steamInstallDir /
"steamapps" /
"sourcemods";
295 auto keys = std::views::keys(this->gameDetails);
296 return {keys.begin(), keys.end()};
300 return this->gameDetails.contains(appID);
304 if (!this->gameDetails.contains(appID)) {
307 return this->gameDetails.at(appID).name;
311 if (!this->gameDetails.contains(appID)) {
314 return this->libraryDirs[this->gameDetails.at(appID).libraryInstallDirsIndex] /
"common" / this->gameDetails.at(appID).installDir;
318 if (!this->gameDetails.contains(appID)) {
321 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"4f"); !cachedPath.empty()) {
324 auto path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}", appID) /
"icon.jpg";
325 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
326 path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}_icon.jpg", appID);
327 if (!std::filesystem::exists(path, ec)) {
335 if (!this->gameDetails.contains(appID)) {
338 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"2f"); !cachedPath.empty()) {
341 auto path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}", appID) /
"logo.png";
342 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
343 path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}_logo.png", appID);
344 if (!std::filesystem::exists(path, ec)) {
352 if (!this->gameDetails.contains(appID)) {
355 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"1f"); !cachedPath.empty()) {
358 auto path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}", appID) /
"library_hero.jpg";
359 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
360 path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}_library_hero.jpg", appID);
361 if (!std::filesystem::exists(path, ec)) {
369 if (!this->gameDetails.contains(appID)) {
372 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"0f"); !cachedPath.empty()) {
375 auto path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}", appID) /
"library_600x900.jpg";
376 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
377 path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}_library_600x900.jpg", appID);
378 if (!std::filesystem::exists(path, ec)) {
386 if (!this->gameDetails.contains(appID)) {
389 if (
const auto cachedPath = ::getAppArtPath(this->assetCache, appID, this->steamInstallDir,
"3f"); !cachedPath.empty()) {
392 auto path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}", appID) /
"header.jpg";
393 if (std::error_code ec; !std::filesystem::exists(path, ec)) {
394 path = this->steamInstallDir /
"appcache" /
"librarycache" / std::format(
"{}_header.jpg", appID);
395 if (!std::filesystem::exists(path, ec)) {
403 return ::isAppUsingEngine<::isAppUsingGoldSrcEnginePredicate>(
this, appID);
407 return ::isAppUsingEngine<::isAppUsingSourceEnginePredicate>(
this, appID);
411 return ::isAppUsingEngine<::isAppUsingSource2EnginePredicate>(
this, appID);
414Steam::operator bool()
const {
415 return !this->gameDetails.empty();
const KV1BinaryValue & getValue() const
Get the value associated with the element.
std::vector< AppID > getInstalledApps() const
bool isAppUsingSourceEngine(AppID appID) const
bool isAppUsingGoldSrcEngine(AppID appID) const
std::span< const std::filesystem::path > getLibraryDirs() const
const std::filesystem::path & getInstallDir() const
std::filesystem::path getAppLogoPath(AppID appID) const
bool isAppUsingSource2Engine(AppID appID) const
std::string_view getAppName(AppID appID) const
std::filesystem::path getAppStoreArtPath(AppID appID) const
bool isAppInstalled(AppID appID) const
std::filesystem::path getAppHeroPath(AppID appID) const
std::filesystem::path getAppBoxArtPath(AppID appID) const
std::filesystem::path getAppInstallDir(AppID appID) const
std::filesystem::path getSourceModDir() const
std::filesystem::path getAppIconPath(AppID appID) const
std::string readFileText(const std::filesystem::path &filepath, std::size_t startOffset=0)
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
Based on SteamAppPathProvider.