From 011f3fac5d82e8f554a1f8e912377e0db99a6e91 Mon Sep 17 00:00:00 2001 From: acidicoala <67734819+acidicoala@users.noreply.github.com> Date: Fri, 6 Jan 2023 05:18:13 +0300 Subject: [PATCH] Refactored DLC unlocking logic --- CMakeLists.txt | 2 + KoalaBox | 2 +- README.md | 6 +- res/SmokeAPI.json | 10 +- src/core/config.cpp | 49 ++++ src/core/config.hpp | 71 ++++++ src/koalageddon/koalageddon.cpp | 9 +- src/smoke_api/smoke_api.cpp | 18 +- src/smoke_api/smoke_api.hpp | 30 --- src/steam_api_exports/steam_api_flat.cpp | 16 +- src/steam_api_virtuals/isteamapps.cpp | 12 +- src/steam_impl/steam_apps.cpp | 222 +++++++++--------- src/steam_impl/steam_apps.hpp | 13 +- src/steam_impl/steam_inventory.cpp | 8 +- src/steam_impl/steam_inventory.hpp | 5 +- src/steam_impl/steam_user.cpp | 6 +- src/steam_types/steam_types.hpp | 127 +--------- .../client_app_manager.cpp | 13 +- src/steamclient_virtuals/client_user.cpp | 8 +- 19 files changed, 310 insertions(+), 317 deletions(-) create mode 100644 src/core/config.cpp create mode 100644 src/core/config.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 74429e4..20ddfb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,8 @@ set( SMOKE_API_SOURCES src/core/cache.cpp src/core/cache.hpp + src/core/config.cpp + src/core/config.hpp src/core/globals.cpp src/core/globals.hpp src/core/macros.hpp diff --git a/KoalaBox b/KoalaBox index 418cb43..53731dd 160000 --- a/KoalaBox +++ b/KoalaBox @@ -1 +1 @@ -Subproject commit 418cb437a7eda9c7e13cdcdbe6e538fa9e4a9281 +Subproject commit 53731ddc6b82281df820884f2d2424ee3a62df54 diff --git a/README.md b/README.md index 8ffbbe5..4399c16 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,9 @@ SmokeAPI does not require any manual configuration. By default, it uses the most | `logging` | Toggles generation of `*.log` file | Boolean | `false` | | `unlock_all` | Toggles whether all DLCs should be unlocked by default | Boolean | `true` | | `override` | When `unlock_all` is `true`, this option serves as a blacklist of DLC IDs, which should remain locked. When `unlock_all` is `false`, this option serves as a whitelist of DLC IDs, which should become unlocked | List of Integers | `[]` | -| `dlc_ids` | When game requests list of all DLCs from Steam and the number of registered DLCs is greater than 64, Steam may not return all of them. In this case, SmokeAPI will fetch all released DLCs from Web API. In some games, however (like Monster Hunter: World), web api also doesn't return all possible DLCs. To address this issue, you can specify the missing DLC IDs¹ in this option. For some games (including MH:W), however, it is not necessary because SmokeAPI will also automatically fetch a [manually maintained list of DLC IDs] that are missing from web api | List of Integers | `[]` | +| `extra_dlc_ids` | When game requests list of all DLCs from Steam and the number of registered DLCs is greater than 64, Steam may not return all of them. In this case, SmokeAPI will fetch all released DLCs from Web API. In some games, however (like Monster Hunter: World), web api also doesn't return all possible DLCs. To address this issue, you can specify the missing DLC IDs¹ in this option. For some games (including MH:W), however, it is not necessary because SmokeAPI will also automatically fetch a [manually maintained list of DLC IDs] that are missing from web api | List of Integers | `[]` | | `auto_inject_inventory` | Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory | Boolean | `true` | -| `inventory_items` | A list of inventory items IDs¹ that will be added in addition to the automatically injected items | List of Integers | `[]` | +| `extra_inventory_items` | A list of inventory items IDs¹ that will be added in addition to the automatically injected items | List of Integers | `[]` | | `koalageddon_config` | An object that specifies patterns and offsets required for koalageddon mode. It can be used to override online config for testing or development purposes. | Object | `null` | ¹ DLC/Item IDs can be obtained from https://steamdb.info. You need to be logged in with your steam account in order to see accurate inventory item IDs. @@ -91,7 +91,7 @@ SmokeAPI does not require any manual configuration. By default, it uses the most ### How SmokeAPI works in games with large number of DLCs -Some games that have a lot of DLCs begin ownership verification by querying the Steamworks API for a list of all available DLCs. Once the game receives the list, it will go over each item and check the ownership. The issue arises from the fact that response from Steamworks SDK may max out at 64, depending on how much unowned DLC the user has. To alleviate this issue, SmokeAPI will make a web request to Steam API for a full list of DLCs, which works well most of the time. Unfortunately, even the web API does not solve all of our problems, because it will only return DLCs that are available in Steam store. This means that DLCs without a dedicated store offer, such as pre-order DLCs will be left out. That's where the `dlc_ids` config option comes into play. You can specify those missing DLC IDs there, and SmokeAPI will make them available to the game. However, this introduces the need for manual configuration, which goes against the ideals of this project. To remedy this issue SmokeAPI will also fetch [this document] stored in a GitHub repository. It contains all the DLC IDs missing from Steam store. The document is hand-crafted using data from https://steamdb.com. This enables SmokeAPI to unlock all DLCs without any config file at all. Feel free to report games that have more than 64 DLCs, +Some games that have a lot of DLCs begin ownership verification by querying the Steamworks API for a list of all available DLCs. Once the game receives the list, it will go over each item and check the ownership. The issue arises from the fact that response from Steamworks SDK may max out at 64, depending on how much unowned DLC the user has. To alleviate this issue, SmokeAPI will make a web request to Steam API for a full list of DLCs, which works well most of the time. Unfortunately, even the web API does not solve all of our problems, because it will only return DLCs that are available in Steam store. This means that DLCs without a dedicated store offer, such as pre-order DLCs will be left out. That's where the `extra_dlc_ids` config option comes into play. You can specify those missing DLC IDs there, and SmokeAPI will make them available to the game. However, this introduces the need for manual configuration, which goes against the ideals of this project. To remedy this issue SmokeAPI will also fetch [this document] stored in a GitHub repository. It contains all the DLC IDs missing from Steam store. The document is hand-crafted using data from https://steamdb.com. This enables SmokeAPI to unlock all DLCs without any config file at all. Feel free to report games that have more than 64 DLCs, *and* have DLCs without a dedicated store page. They will be added to the list of missing DLC IDs to facilitate configless operation. [this document]: https://github.com/acidicoala/public-entitlements/blob/main/steam/v1/dlc.json diff --git a/res/SmokeAPI.json b/res/SmokeAPI.json index 2bd4d80..00ab927 100644 --- a/res/SmokeAPI.json +++ b/res/SmokeAPI.json @@ -2,10 +2,12 @@ "$version": 2, "logging": true, "unlock_family_sharing": true, - "unlock_all": true, - "override": [], - "dlc_ids": [], + "default_app_status": "unlocked", + "default_dlc_status": "unlocked", + "override_app_status": {}, + "override_dlc_status": {}, + "extra_dlc_ids": [], "auto_inject_inventory": true, - "inventory_items": [], + "extra_inventory_items": [], "koalageddon_config": null } diff --git a/src/core/config.cpp b/src/core/config.cpp new file mode 100644 index 0000000..6d9ca90 --- /dev/null +++ b/src/core/config.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +namespace config { + Config instance; // NOLINT(cert-err58-cpp) + + void init() { + instance = config_parser::parse(paths::get_config_path()); + } + + AppStatus get_app_status(uint32_t app_id) { + if (app_id == 0) { + // 0 is a special internal value reserved for cases where we don't know app_id. + // This is typically the case in non-koalageddon modes, hence we treat it as unlocked. + return AppStatus::UNLOCKED; + } + + const auto app_id_key = std::to_string(app_id); + + if (instance.override_app_status.contains(app_id_key)) { + return instance.override_app_status[app_id_key]; + } + + return instance.default_app_status; + } + + DlcStatus get_dlc_status(uint32_t dlc_id) { + const auto dlc_id_key = std::to_string(dlc_id); + + if (instance.override_dlc_status.contains(dlc_id_key)) { + return instance.override_dlc_status[dlc_id_key]; + } + + return instance.default_dlc_status; + } + + bool is_dlc_unlocked(uint32_t app_id, uint32_t dlc_id, const std::function& original_function) { + const auto app_status = config::get_app_status(app_id); + const auto dlc_status = config::get_dlc_status(dlc_id); + + const auto app_unlocked = app_status == config::AppStatus::UNLOCKED; + const auto dlc_unlocked = dlc_status == config::DlcStatus::UNLOCKED || + dlc_status != config::DlcStatus::LOCKED && + original_function(); + + return app_unlocked && dlc_unlocked; + } +} diff --git a/src/core/config.hpp b/src/core/config.hpp new file mode 100644 index 0000000..ddc8995 --- /dev/null +++ b/src/core/config.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include + +namespace config { + using namespace koalabox; + + enum class AppStatus { + LOCKED, + UNLOCKED, + UNDEFINED + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(AppStatus, { + { AppStatus::UNDEFINED, nullptr }, + { AppStatus::LOCKED, "locked" }, + { AppStatus::UNLOCKED, "unlocked" }, + }) + + enum class DlcStatus { + LOCKED, + UNLOCKED, + ORIGINAL, + UNDEFINED + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(DlcStatus, { + { DlcStatus::UNDEFINED, nullptr }, + { DlcStatus::LOCKED, "locked" }, + { DlcStatus::UNLOCKED, "unlocked" }, + { DlcStatus::ORIGINAL, "original" }, + }) + + struct Config { + uint32_t $version = 2; + bool logging = false; + bool unlock_family_sharing = true; + AppStatus default_app_status = AppStatus::UNLOCKED; + DlcStatus default_dlc_status = DlcStatus::UNLOCKED; + Map override_app_status; + Map override_dlc_status; + Vector extra_dlc_ids; + bool auto_inject_inventory = true; + Vector extra_inventory_items; + // We have to use general json type here since the library doesn't support std::optional + nlohmann::json koalageddon_config; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + Config, $version, // NOLINT(misc-const-correctness) + logging, + unlock_family_sharing, + default_app_status, + default_dlc_status, + override_app_status, + override_dlc_status, + extra_dlc_ids, + auto_inject_inventory, + extra_inventory_items, + koalageddon_config + ) + }; + + extern Config instance; + + void init(); + + AppStatus get_app_status(uint32_t app_id); + DlcStatus get_dlc_status(uint32_t dlc_id); + bool is_dlc_unlocked(uint32_t app_id, uint32_t dlc_id, const std::function& original_function); +} diff --git a/src/koalageddon/koalageddon.cpp b/src/koalageddon/koalageddon.cpp index 061db3f..b4b5069 100644 --- a/src/koalageddon/koalageddon.cpp +++ b/src/koalageddon/koalageddon.cpp @@ -1,6 +1,7 @@ -#include #include +#include #include +#include #include #include #include @@ -12,10 +13,10 @@ namespace koalageddon { * @return A string representing the source of the config. */ String init_koalageddon_config() { - if (!smoke_api::config.koalageddon_config.is_null()) { + if (!config::instance.koalageddon_config.is_null()) { try { // First try to read a local config override - config = smoke_api::config.koalageddon_config.get(); + config = config::instance.koalageddon_config.get(); return "local config override"; } catch (const Exception& ex) { @@ -63,7 +64,7 @@ namespace koalageddon { static auto init_count = 0; if (util::strings_are_equal(name, VSTDLIB_DLL)) { // VStdLib DLL handles Family Sharing functions - if (smoke_api::config.unlock_family_sharing) { + if (config::instance.unlock_family_sharing) { init_vstdlib_hooks(); } init_count++; diff --git a/src/smoke_api/smoke_api.cpp b/src/smoke_api/smoke_api.cpp index c973d2c..9d97041 100644 --- a/src/smoke_api/smoke_api.cpp +++ b/src/smoke_api/smoke_api.cpp @@ -1,23 +1,21 @@ #include +#include +#include +#include #include #include -#include - #include #include #include #include #include #include -#include #ifndef _WIN64 #include #endif namespace smoke_api { - Config config = {}; // NOLINT(cert-err58-cpp) - HMODULE original_library = nullptr; HMODULE self_module = nullptr; @@ -85,13 +83,13 @@ namespace smoke_api { koalabox::project_name = PROJECT_NAME; - config = config_parser::parse(paths::get_config_path()); + config::init(); const auto exe_path = Path(win_util::get_module_file_name_or_throw(nullptr)); const auto exe_name = exe_path.filename().string(); const auto exe_bitness = util::is_x64() ? 64 : 32; - if (config.logging) { + if (config::instance.logging) { logger = file_logger::create(paths::get_log_path()); } @@ -138,10 +136,4 @@ namespace smoke_api { logger->error("Shutdown error: {}", ex.what()); } } - - // FIXME: Support for app_id for koalageddon mode - bool should_unlock(uint32_t app_id) { - return config.unlock_all != config.override.contains(app_id); - } - } diff --git a/src/smoke_api/smoke_api.hpp b/src/smoke_api/smoke_api.hpp index a52867d..4df7035 100644 --- a/src/smoke_api/smoke_api.hpp +++ b/src/smoke_api/smoke_api.hpp @@ -24,34 +24,6 @@ namespace smoke_api { using namespace koalabox; - struct Config { - uint32_t $version = 2; - bool logging = false; - bool unlock_family_sharing = true; - bool unlock_all = true; - Set override; - Vector dlc_ids; - bool auto_inject_inventory = true; - Vector inventory_items; - - // Have to use general json type here since library doesn't support std::optional - nlohmann::json koalageddon_config; - - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( - Config, $version, // NOLINT(misc-const-correctness) - logging, - unlock_family_sharing, - unlock_all, - override, - dlc_ids, - auto_inject_inventory, - inventory_items, - koalageddon_config - ) - }; - - extern Config config; - extern HMODULE self_module; extern HMODULE original_library; @@ -62,6 +34,4 @@ namespace smoke_api { void shutdown(); - bool should_unlock(uint32_t app_id); - } diff --git a/src/steam_api_exports/steam_api_flat.cpp b/src/steam_api_exports/steam_api_flat.cpp index 3f96c15..75b7c71 100644 --- a/src/steam_api_exports/steam_api_flat.cpp +++ b/src/steam_api_exports/steam_api_flat.cpp @@ -8,12 +8,20 @@ using namespace smoke_api; // ISteamApps -DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(ISteamApps*, AppId_t appID) { - return steam_apps::IsDlcUnlocked(__func__, 0, appID); +DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(ISteamApps* self, AppId_t appID) { + return steam_apps::IsDlcUnlocked(__func__, 0, appID, [&]() { + GET_ORIGINAL_FUNCTION(SteamAPI_ISteamApps_BIsSubscribedApp) + + return SteamAPI_ISteamApps_BIsSubscribedApp_o(self, appID); + }); } -DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(ISteamApps*, AppId_t appID) { - return steam_apps::IsDlcUnlocked(__func__, 0, appID); +DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(ISteamApps* self, AppId_t appID) { + return steam_apps::IsDlcUnlocked(__func__, 0, appID, [&]() { + GET_ORIGINAL_FUNCTION(SteamAPI_ISteamApps_BIsDlcInstalled) + + return SteamAPI_ISteamApps_BIsDlcInstalled_o(self, appID); + }); } DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(ISteamApps* self) { diff --git a/src/steam_api_virtuals/isteamapps.cpp b/src/steam_api_virtuals/isteamapps.cpp index a875a79..b3cc0f0 100644 --- a/src/steam_api_virtuals/isteamapps.cpp +++ b/src/steam_api_virtuals/isteamapps.cpp @@ -4,11 +4,19 @@ using namespace smoke_api; VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) { // NOLINT(misc-unused-parameters) - return steam_apps::IsDlcUnlocked(__func__, 0, appID); + return steam_apps::IsDlcUnlocked(__func__, 0, appID, [&]() { + GET_ORIGINAL_FUNCTION(ISteamApps_BIsSubscribedApp) + + return ISteamApps_BIsSubscribedApp_o(ARGS(appID)); + }); } VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t appID)) { // NOLINT(misc-unused-parameters) - return steam_apps::IsDlcUnlocked(__func__, 0, appID); + return steam_apps::IsDlcUnlocked(__func__, 0, appID, [&]() { + GET_ORIGINAL_FUNCTION(ISteamApps_BIsDlcInstalled) + + return ISteamApps_BIsDlcInstalled_o(ARGS(appID)); + }); } VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS()) { diff --git a/src/steam_impl/steam_apps.cpp b/src/steam_impl/steam_apps.cpp index 061834c..098040f 100644 --- a/src/steam_impl/steam_apps.cpp +++ b/src/steam_impl/steam_apps.cpp @@ -3,118 +3,120 @@ #include #include #include +#include #include -using namespace smoke_api; - -/// Steamworks may max GetDLCCount value at 64, depending on how much unowned DLCs the user has. -/// Despite this limit, some games with more than 64 DLCs still keep using this method. -/// This means we have to get extra DLC IDs from local config, remote config, or cache. -constexpr auto MAX_DLC = 64; - -// Key: App ID, Value: DLC ID -Map original_dlc_count_map; // NOLINT(cert-err58-cpp) -Vector cached_dlcs; - -/** - * @param app_id - * @return boolean indicating if the function was able to successfully fetch DLC IDs from all sources. - */ -bool fetch_and_cache_dlcs(AppId_t app_id) { - if (not app_id) { - try { - app_id = steam_functions::get_app_id_or_throw(); - // TODO: Check what it returns in koalageddon mode - logger->info("Detected App ID: {}", app_id); - } catch (const Exception& ex) { - logger->error("Failed to get app ID: {}", ex.what()); - return false; - } - } - - auto total_success = true; - const auto app_id_str = std::to_string(app_id); - - const auto fetch_from_steam = [&]() { - Vector dlcs; - - try { - const auto url = fmt::format("https://store.steampowered.com/dlc/{}/ajaxgetdlclist", app_id_str); - const auto json = http_client::fetch_json(url); - - if (json["success"] != 1) { - throw util::exception("Web API responded with 'success' != 1"); - } - - for (const auto& dlc: json["dlcs"]) { - const auto app_id = dlc["appid"].get(); - dlcs.emplace_back(std::stoi(app_id)); - } - } catch (const Exception& e) { - logger->error("Failed to fetch dlc list from steam api: {}", e.what()); - total_success = false; - } - - return dlcs; - }; - - const auto fetch_from_github = [&]() { - Vector dlcs; - - try { - const String url = "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v1/dlc.json"; - const auto json = http_client::fetch_json(url); - - if (json.contains(app_id_str)) { - dlcs = json[app_id_str].get(); - } - } catch (const Exception& e) { - logger->error("Failed to fetch extra dlc list from github api: {}", e.what()); - total_success = false; - } - - return dlcs; - }; - - const auto steam_dlcs = fetch_from_steam(); - const auto github_dlcs = fetch_from_github(); - - // Any of the sources might fail, so we try to get optimal result - // by combining results from all the sources into a single set. - Set combined_dlcs; - combined_dlcs.insert(steam_dlcs.begin(), steam_dlcs.end()); - combined_dlcs.insert(github_dlcs.begin(), github_dlcs.end()); - // There is no need to insert cached entries if both steam and GitHub requests were successful. - if (!total_success) { - const auto cache_dlcs = cache::get_dlc_ids(app_id); - combined_dlcs.insert(cached_dlcs.begin(), cached_dlcs.end()); - } - - // We then transfer that set into a list because we need DLCs to be accessible via index. - cached_dlcs.clear(); - cached_dlcs.insert(cached_dlcs.begin(), combined_dlcs.begin(), combined_dlcs.end()); - - cache::save_dlc_ids(app_id, cached_dlcs); - - return total_success; -} - -String get_app_id_log(const AppId_t app_id) { - return app_id ? fmt::format("App ID: {}, ", app_id) : ""; -} - namespace steam_apps { + using namespace smoke_api; - bool IsDlcUnlocked(const String& function_name, AppId_t app_id, AppId_t dlc_id) { + /// Steamworks may max GetDLCCount value at 64, depending on how much unowned DLCs the user has. + /// Despite this limit, some games with more than 64 DLCs still keep using this method. + /// This means we have to get extra DLC IDs from local config, remote config, or cache. + constexpr auto MAX_DLC = 64; + + // Key: App ID, Value: DLC ID + Map original_dlc_count_map; // NOLINT(cert-err58-cpp) + Vector cached_dlcs; + + /** + * @param app_id + * @return boolean indicating if the function was able to successfully fetch DLC IDs from all sources. + */ + bool fetch_and_cache_dlcs(AppId_t app_id) { + if (not app_id) { + try { + app_id = steam_functions::get_app_id_or_throw(); + // TODO: Check what it returns in koalageddon mode + logger->info("Detected App ID: {}", app_id); + } catch (const Exception& ex) { + logger->error("Failed to get app ID: {}", ex.what()); + return false; + } + } + + auto total_success = true; + const auto app_id_str = std::to_string(app_id); + + const auto fetch_from_steam = [&]() { + Vector dlcs; + + try { + // TODO: Refactor into api namespace + const auto url = fmt::format("https://store.steampowered.com/dlc/{}/ajaxgetdlclist", app_id_str); + const auto json = http_client::fetch_json(url); + + if (json["success"] != 1) { + throw util::exception("Web API responded with 'success' != 1"); + } + + for (const auto& dlc: json["dlcs"]) { + const auto app_id = dlc["appid"].get(); + dlcs.emplace_back(std::stoi(app_id)); + } + } catch (const Exception& e) { + logger->error("Failed to fetch dlc list from steam api: {}", e.what()); + total_success = false; + } + + return dlcs; + }; + + const auto fetch_from_github = [&]() { + Vector dlcs; + + try { + const String url = "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v1/dlc.json"; + const auto json = http_client::fetch_json(url); + + if (json.contains(app_id_str)) { + dlcs = json[app_id_str].get(); + } + } catch (const Exception& e) { + logger->error("Failed to fetch extra dlc list from github api: {}", e.what()); + total_success = false; + } + + return dlcs; + }; + + const auto steam_dlcs = fetch_from_steam(); + const auto github_dlcs = fetch_from_github(); + + // Any of the sources might fail, so we try to get optimal result + // by combining results from all the sources into a single set. + Set combined_dlcs; + combined_dlcs.insert(steam_dlcs.begin(), steam_dlcs.end()); + combined_dlcs.insert(github_dlcs.begin(), github_dlcs.end()); + // There is no need to insert cached entries if both steam and GitHub requests were successful. + if (!total_success) { + const auto cache_dlcs = cache::get_dlc_ids(app_id); + combined_dlcs.insert(cached_dlcs.begin(), cached_dlcs.end()); + } + + // We then transfer that set into a list because we need DLCs to be accessible via index. + cached_dlcs.clear(); + cached_dlcs.insert(cached_dlcs.begin(), combined_dlcs.begin(), combined_dlcs.end()); + + cache::save_dlc_ids(app_id, cached_dlcs); + + return total_success; + } + + String get_app_id_log(const AppId_t app_id) { + return app_id ? fmt::format("App ID: {}, ", app_id) : ""; + } + + bool IsDlcUnlocked( + const String& function_name, + AppId_t app_id, AppId_t dlc_id, + const std::function& original_function + ) { try { - const auto app_id_unlocked = not app_id or should_unlock(app_id); // true if app_id == 0 - const auto dlc_id_unlocked = should_unlock(dlc_id); + const auto unlocked = config::is_dlc_unlocked(app_id, dlc_id, original_function); - const auto installed = app_id_unlocked and dlc_id_unlocked; + logger->info("{} -> {}DLC ID: {}, Unlocked: {}", function_name, get_app_id_log(app_id), dlc_id, unlocked); - logger->info("{} -> {}DLC ID: {}, Unlocked: {}", function_name, get_app_id_log(app_id), dlc_id, installed); - - return installed; + return unlocked; } catch (const Exception& e) { logger->error("{} -> Uncaught exception: {}", function_name, e.what()); return false; @@ -142,7 +144,7 @@ namespace steam_apps { // We need to fetch DLC IDs from all possible sources at this point - const auto injected_count = static_cast(config.dlc_ids.size()); + const auto injected_count = static_cast(config::instance.extra_dlc_ids.size()); logger->debug("{} -> Injected DLC count: {}", function_name, injected_count); // Maintain a list of app_ids for which we have already fetched and cached DLC IDs @@ -191,7 +193,7 @@ namespace steam_apps { // Fill the output pointers *pDlcId = dlc_id; - *pbAvailable = should_unlock(dlc_id); + *pbAvailable = config::is_dlc_unlocked(app_id, dlc_id, []() { return true; }); auto name = fmt::format("DLC #{} with ID: {} ", iDLC, dlc_id); name = name.substr(0, cchNameBufferSize); @@ -218,7 +220,7 @@ namespace steam_apps { const auto success = original_function(); if (success) { - *pbAvailable = should_unlock(*pDlcId); + *pbAvailable = config::is_dlc_unlocked(app_id, *pDlcId, [&]() { return *pbAvailable; }); print_dlc_info("original"); } else { logger->warn("{} -> original call failed for index: {}", function_name, iDLC); @@ -236,9 +238,9 @@ namespace steam_apps { logger->warn("{} -> Out of bounds DLC index: {}", function_name, iDLC); } - const int local_dlc_count = static_cast(config.dlc_ids.size()); + const int local_dlc_count = static_cast(config::instance.extra_dlc_ids.size()); if (iDLC < local_dlc_count) { - return inject_dlc("local config", config.dlc_ids, iDLC); + return inject_dlc("local config", config::instance.extra_dlc_ids, iDLC); } const auto adjusted_index = iDLC - local_dlc_count; diff --git a/src/steam_impl/steam_apps.hpp b/src/steam_impl/steam_apps.hpp index df10cd4..97e93ef 100644 --- a/src/steam_impl/steam_apps.hpp +++ b/src/steam_impl/steam_apps.hpp @@ -3,9 +3,18 @@ namespace steam_apps { using namespace koalabox; - bool IsDlcUnlocked(const String& function_name, AppId_t app_id, AppId_t dlc_id); + bool IsDlcUnlocked( + const String& function_name, + AppId_t app_id, + AppId_t dlc_id, + const std::function& original_function + ); - int GetDLCCount(const String& function_name, AppId_t app_id, const std::function& original_function); + int GetDLCCount( + const String& function_name, + AppId_t app_id, + const std::function& original_function + ); bool GetDLCDataByIndex( const String& dlc_id, diff --git a/src/steam_impl/steam_inventory.cpp b/src/steam_impl/steam_inventory.cpp index 67840a0..c75e1ab 100644 --- a/src/steam_impl/steam_inventory.cpp +++ b/src/steam_impl/steam_inventory.cpp @@ -1,5 +1,5 @@ -#include #include +#include namespace steam_inventory { @@ -51,11 +51,11 @@ namespace steam_inventory { ); static uint32_t original_count = 0; - const auto injected_count = smoke_api::config.inventory_items.size(); + const auto injected_count = config::instance.extra_inventory_items.size(); // Automatically get inventory items from steam static Vector auto_inventory_items; - if (smoke_api::config.auto_inject_inventory) { + if (config::instance.auto_inject_inventory) { static std::once_flag flag; std::call_once(flag, [&]() { uint32_t count = 0; @@ -102,7 +102,7 @@ namespace steam_inventory { for (int i = 0; i < injected_count; i++) { auto& item = pOutItemsArray[original_count + auto_injected_count + i]; - const auto item_def_id = smoke_api::config.inventory_items[i]; + const auto item_def_id = config::instance.extra_inventory_items[i]; item = new_item(item_def_id); diff --git a/src/steam_impl/steam_inventory.hpp b/src/steam_impl/steam_inventory.hpp index 82e2613..2803585 100644 --- a/src/steam_impl/steam_inventory.hpp +++ b/src/steam_impl/steam_inventory.hpp @@ -1,9 +1,8 @@ -#include +#include #include -using namespace smoke_api; - namespace steam_inventory { + using namespace koalabox; EResult GetResultStatus( const String& function_name, diff --git a/src/steam_impl/steam_user.cpp b/src/steam_impl/steam_user.cpp index 2c8e8f5..d8f56f8 100644 --- a/src/steam_impl/steam_user.cpp +++ b/src/steam_impl/steam_user.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace steam_user { @@ -15,7 +15,9 @@ namespace steam_user { return result; } - const auto has_license = smoke_api::should_unlock(appID); + const auto has_license = config::is_dlc_unlocked(0, appID, [&]() { + return result == k_EUserHasLicenseResultHasLicense; + }); logger->info("{} -> App ID: {}, HasLicense: {}", function_name, appID, has_license); diff --git a/src/steam_types/steam_types.hpp b/src/steam_types/steam_types.hpp index a4e0c2b..51d93df 100644 --- a/src/steam_types/steam_types.hpp +++ b/src/steam_types/steam_types.hpp @@ -22,129 +22,4 @@ enum EUserHasLicenseForAppResult { k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated }; -enum EResult { - k_EResultNone = 0, // no result - k_EResultOK = 1, // success - k_EResultFail = 2, // generic failure - k_EResultNoConnection = 3, // no/failed network connection - // k_EResultNoConnectionRetry = 4, // OBSOLETE - removed - k_EResultInvalidPassword = 5, // password/ticket is invalid - k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere - k_EResultInvalidProtocolVer = 7, // protocol version is incorrect - k_EResultInvalidParam = 8, // a parameter is incorrect - k_EResultFileNotFound = 9, // file was not found - k_EResultBusy = 10, // called method busy - action not taken - k_EResultInvalidState = 11, // called object was in an invalid state - k_EResultInvalidName = 12, // name is invalid - k_EResultInvalidEmail = 13, // email is invalid - k_EResultDuplicateName = 14, // name is not unique - k_EResultAccessDenied = 15, // access is denied - k_EResultTimeout = 16, // operation timed out - k_EResultBanned = 17, // VAC2 banned - k_EResultAccountNotFound = 18, // account not found - k_EResultInvalidSteamID = 19, // steamID is invalid - k_EResultServiceUnavailable = 20, // The requested service is currently unavailable - k_EResultNotLoggedOn = 21, // The user is not logged on - k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party) - k_EResultEncryptionFailure = 23, // Encryption or Decryption failed - k_EResultInsufficientPrivilege = 24, // Insufficient privilege - k_EResultLimitExceeded = 25, // Too much of a good thing - k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes) - k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired - k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again - k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time - k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user - k_EResultIPNotFound = 31, // IP address not found - k_EResultPersistFailed = 32, // failed to write change to the data store - k_EResultLockingFailed = 33, // failed to acquire access lock for this operation - k_EResultLogonSessionReplaced = 34, - k_EResultConnectFailed = 35, - k_EResultHandshakeFailed = 36, - k_EResultIOFailure = 37, - k_EResultRemoteDisconnect = 38, - k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested - k_EResultBlocked = 40, // a user didn't allow it - k_EResultIgnored = 41, // target is ignoring sender - k_EResultNoMatch = 42, // nothing matching the request found - k_EResultAccountDisabled = 43, - k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now - k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available - k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin - k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol. - k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another. - k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed. - k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait - k_EResultSuspended = 51, // Long running operation (content download) suspended/paused - k_EResultCancelled = 52, // Operation canceled (typically by user: content download) - k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable - k_EResultDiskFull = 54, // Operation canceled - not enough disk space. - k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed - k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side - k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account - k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid - k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first - k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files - k_EResultIllegalPassword = 61, // The requested new password is not legal - k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer ) - k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure - k_EResultCannotUseOldPassword = 64, // The requested new password is not legal - k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid - k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent - k_EResultHardwareNotCapableOfIPT = 67, // - k_EResultIPTInitError = 68, // - k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user - k_EResultFacebookQueryError = 70, // Facebook query returned an error - k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired - k_EResultIPLoginRestrictionFailed = 72, - k_EResultAccountLockedDown = 73, - k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, - k_EResultNoMatchingURL = 75, - k_EResultBadResponse = 76, // parse failure, missing field, etc. - k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password - k_EResultValueOutOfRange = 78, // the value entered is outside the acceptable range - k_EResultUnexpectedError = 79, // something happened that we didn't expect to ever happen - k_EResultDisabled = 80, // The requested service has been configured to be unavailable - k_EResultInvalidCEGSubmission = 81, // The set of files submitted to the CEG server are not valid ! - k_EResultRestrictedDevice = 82, // The device being used is not allowed to perform this action - k_EResultRegionLocked = 83, // The action could not be complete because it is region restricted - k_EResultRateLimitExceeded = 84, // Temporary rate limit exceeded, try again later, different from k_EResultLimitExceeded which may be permanent - k_EResultAccountLoginDeniedNeedTwoFactor = 85, // Need two-factor code to login - k_EResultItemDeleted = 86, // The thing we're trying to access has been deleted - k_EResultAccountLoginDeniedThrottle = 87, // login attempt failed, try to throttle response to possible attacker - k_EResultTwoFactorCodeMismatch = 88, // two factor code mismatch - k_EResultTwoFactorActivationCodeMismatch = 89, // activation code for two-factor didn't match - k_EResultAccountAssociatedToMultiplePartners = 90, // account has been associated with multiple partners - k_EResultNotModified = 91, // data not modified - k_EResultNoMobileDevice = 92, // the account does not have a mobile device associated with it - k_EResultTimeNotSynced = 93, // the time presented is out of range or tolerance - k_EResultSmsCodeFailed = 94, // SMS code failure (no match, none pending, etc.) - k_EResultAccountLimitExceeded = 95, // Too many accounts access this resource - k_EResultAccountActivityLimitExceeded = 96, // Too many changes to this account - k_EResultPhoneActivityLimitExceeded = 97, // Too many changes to this phone - k_EResultRefundToWallet = 98, // Cannot refund to payment method, must use wallet - k_EResultEmailSendFailure = 99, // Cannot send an email - k_EResultNotSettled = 100, // Can't perform operation till payment has settled - k_EResultNeedCaptcha = 101, // Needs to provide a valid captcha - k_EResultGSLTDenied = 102, // a game server login token owned by this token's owner has been banned - k_EResultGSOwnerDenied = 103, // game server owner is denied for other reason (account lock, community ban, vac ban, missing phone) - k_EResultInvalidItemType = 104, // the type of thing we were requested to act on is invalid - k_EResultIPBanned = 105, // the ip address has been banned from taking this action - k_EResultGSLTExpired = 106, // this token has expired from disuse; can be reset for use - k_EResultInsufficientFunds = 107, // user doesn't have enough wallet funds to complete the action - k_EResultTooManyPending = 108, // There are too many of this thing pending already - k_EResultNoSiteLicensesFound = 109, // No site licenses found - k_EResultWGNetworkSendExceeded = 110, // the WG couldn't send a response because we exceeded max network send size - k_EResultAccountNotFriends = 111, // the user is not mutually friends - k_EResultLimitedUserAccount = 112, // the user is limited - k_EResultCantRemoveItem = 113, // item can't be removed - k_EResultAccountDeleted = 114, // account has been deleted - k_EResultExistingUserCancelledLicense = 115, // A license for this already exists, but cancelled - k_EResultCommunityCooldown = 116, // access is denied because of a community cooldown (probably from support profile data resets) - k_EResultNoLauncherSpecified = 117, // No launcher was specified, but a launcher was needed to choose correct realm for operation. - k_EResultMustAgreeToSSA = 118, // User must agree to china SSA or global SSA before login - k_EResultLauncherMigrated = 119, // The specified launcher type is no longer supported; the user should be directed elsewhere - k_EResultSteamRealmMismatch = 120, // The user's realm does not match the realm of the requested resource - k_EResultInvalidSignature = 121, // signature check did not match - k_EResultParseFailure = 122, // Failed to parse input - k_EResultNoVerifiedPhone = 123, // account does not have a verified phone number -}; +typedef uint32_t EResult; diff --git a/src/steamclient_virtuals/client_app_manager.cpp b/src/steamclient_virtuals/client_app_manager.cpp index cb265f2..98026af 100644 --- a/src/steamclient_virtuals/client_app_manager.cpp +++ b/src/steamclient_virtuals/client_app_manager.cpp @@ -3,11 +3,10 @@ using namespace smoke_api; -VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled( - PARAMS( // NOLINT(misc-unused-parameters) - AppId_t app_id, - AppId_t dlc_id - ) -) { - return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id); +VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t app_id, AppId_t dlc_id)) { + return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id, [&]() { + GET_ORIGINAL_VIRTUAL_FUNCTION(IClientAppManager_IsAppDlcInstalled) + + return IClientAppManager_IsAppDlcInstalled_o(ARGS(app_id, dlc_id)); + }); } diff --git a/src/steamclient_virtuals/client_user.cpp b/src/steamclient_virtuals/client_user.cpp index d2be4c9..bef1764 100644 --- a/src/steamclient_virtuals/client_user.cpp +++ b/src/steamclient_virtuals/client_user.cpp @@ -3,6 +3,10 @@ using namespace smoke_api; -VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t app_id)) { // NOLINT(misc-unused-parameters) - return steam_apps::IsDlcUnlocked(__func__, 0, app_id); +VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t app_id)) { + return steam_apps::IsDlcUnlocked(__func__, 0, app_id, [&]() { + GET_ORIGINAL_VIRTUAL_FUNCTION(IClientUser_BIsSubscribedApp) + + return IClientUser_BIsSubscribedApp_o(ARGS(app_id)); + }); }