Refactored DLC unlocking logic

This commit is contained in:
acidicoala
2023-01-06 05:18:13 +03:00
parent b04c96a36d
commit 011f3fac5d
19 changed files with 310 additions and 317 deletions

49
src/core/config.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include <core/config.hpp>
#include <core/paths.hpp>
#include <koalabox/config_parser.hpp>
namespace config {
Config instance; // NOLINT(cert-err58-cpp)
void init() {
instance = config_parser::parse<Config>(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<bool()>& 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;
}
}

71
src/core/config.hpp Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include <cstdint>
#include <nlohmann/json.hpp>
#include <koalabox/koalabox.hpp>
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<String, AppStatus> override_app_status;
Map<String, DlcStatus> override_dlc_status;
Vector<uint32_t> extra_dlc_ids;
bool auto_inject_inventory = true;
Vector<uint32_t> 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<bool()>& original_function);
}

View File

@@ -1,6 +1,7 @@
#include <build_config.h>
#include <koalageddon/koalageddon.hpp>
#include <build_config.h>
#include <core/cache.hpp>
#include <core/config.hpp>
#include <smoke_api/smoke_api.hpp>
#include <koalabox/dll_monitor.hpp>
#include <koalabox/http_client.hpp>
@@ -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<decltype(config)>();
config = config::instance.koalageddon_config.get<decltype(config)>();
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++;

View File

@@ -1,23 +1,21 @@
#include <smoke_api/smoke_api.hpp>
#include <build_config.h>
#include <core/config.hpp>
#include <core/globals.hpp>
#include <core/paths.hpp>
#include <steam_functions/steam_functions.hpp>
#include <build_config.h>
#include <koalabox/config_parser.hpp>
#include <koalabox/dll_monitor.hpp>
#include <koalabox/file_logger.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/loader.hpp>
#include <koalabox/win_util.hpp>
#include <core/globals.hpp>
#ifndef _WIN64
#include <koalageddon/koalageddon.hpp>
#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<Config>(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);
}
}

View File

@@ -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<uint32_t> override;
Vector<uint32_t> dlc_ids;
bool auto_inject_inventory = true;
Vector<uint32_t> 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);
}

View File

@@ -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) {

View File

@@ -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()) {

View File

@@ -3,118 +3,120 @@
#include <koalabox/io.hpp>
#include <koalabox/http_client.hpp>
#include <core/cache.hpp>
#include <core/config.hpp>
#include <smoke_api/smoke_api.hpp>
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<AppId_t, int> original_dlc_count_map; // NOLINT(cert-err58-cpp)
Vector<AppId_t> 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<AppId_t> 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<String>();
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<AppId_t> 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<decltype(dlcs)>();
}
} 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<AppId_t> 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<AppId_t, int> original_dlc_count_map; // NOLINT(cert-err58-cpp)
Vector<AppId_t> 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<AppId_t> 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<String>();
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<AppId_t> 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<decltype(dlcs)>();
}
} 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<AppId_t> 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<bool()>& 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<int>(config.dlc_ids.size());
const auto injected_count = static_cast<int>(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<int>(config.dlc_ids.size());
const int local_dlc_count = static_cast<int>(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;

View File

@@ -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<bool()>& original_function
);
int GetDLCCount(const String& function_name, AppId_t app_id, const std::function<int()>& original_function);
int GetDLCCount(
const String& function_name,
AppId_t app_id,
const std::function<int()>& original_function
);
bool GetDLCDataByIndex(
const String& dlc_id,

View File

@@ -1,5 +1,5 @@
#include <smoke_api/smoke_api.hpp>
#include <steam_impl/steam_inventory.hpp>
#include <core/config.hpp>
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<SteamItemDef_t> 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);

View File

@@ -1,9 +1,8 @@
#include <smoke_api/smoke_api.hpp>
#include <koalabox/koalabox.hpp>
#include <steam_functions/steam_functions.hpp>
using namespace smoke_api;
namespace steam_inventory {
using namespace koalabox;
EResult GetResultStatus(
const String& function_name,

View File

@@ -1,5 +1,5 @@
#include <steam_impl/steam_user.hpp>
#include <smoke_api/smoke_api.hpp>
#include <core/config.hpp>
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);

View File

@@ -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;

View File

@@ -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));
});
}

View File

@@ -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));
});
}