mirror of
https://github.com/acidicoala/SmokeAPI.git
synced 2025-12-06 05:25:43 -05:00
Split static & shared lib
This commit is contained in:
53
static/smoke_api/api.cpp
Normal file
53
static/smoke_api/api.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <koalabox/http_client.hpp>
|
||||
#include <koalabox/logger.hpp>
|
||||
|
||||
#include "smoke_api/api.hpp"
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace smoke_api::api {
|
||||
struct SteamResponse {
|
||||
uint32_t success = 0;
|
||||
std::vector<DLC> dlcs;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
|
||||
SteamResponse,
|
||||
success,
|
||||
dlcs
|
||||
)
|
||||
};
|
||||
|
||||
std::optional<std::vector<DLC>> fetch_dlcs_from_github(const AppId_t app_id) {
|
||||
try {
|
||||
constexpr auto url = "https://raw.githubusercontent.com/"
|
||||
"acidicoala/public-entitlements/main/steam/v2/dlc.json";
|
||||
const auto json = koalabox::http_client::get_json(url);
|
||||
const auto response = json.get<AppDlcNameMap>();
|
||||
|
||||
return DLC::get_dlcs_from_apps(response, app_id);
|
||||
} catch(const nlohmann::json::exception& e) {
|
||||
LOG_ERROR("Failed to fetch DLC list from GitHub: {}", e.what());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::vector<DLC>> fetch_dlcs_from_steam(const AppId_t app_id) {
|
||||
try {
|
||||
// TODO: Communicate directly with Steam servers.
|
||||
// ref.: https://github.com/SteamRE/SteamKit
|
||||
const auto url =
|
||||
std::format("https://store.steampowered.com/dlc/{}/ajaxgetdlclist", app_id);
|
||||
const auto json = koalabox::http_client::get_json(url);
|
||||
|
||||
const auto [success, dlcs] = json.get<SteamResponse>();
|
||||
|
||||
if(success != 1) {
|
||||
throw std::runtime_error("Web API responded with 'success' != 1");
|
||||
}
|
||||
|
||||
return dlcs;
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("Failed to fetch dlc list from Steam: {}", e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
9
static/smoke_api/api.hpp
Normal file
9
static/smoke_api/api.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace smoke_api::api {
|
||||
std::optional<std::vector<DLC>> fetch_dlcs_from_github(AppId_t app_id);
|
||||
|
||||
std::optional<std::vector<DLC>> fetch_dlcs_from_steam(AppId_t app_id);
|
||||
}
|
||||
49
static/smoke_api/cache.cpp
Normal file
49
static/smoke_api/cache.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <koalabox/cache.hpp>
|
||||
#include <koalabox/logger.hpp>
|
||||
|
||||
#include "smoke_api/cache.hpp"
|
||||
|
||||
constexpr auto KEY_APPS = "apps";
|
||||
|
||||
namespace {
|
||||
AppDlcNameMap get_cached_apps() {
|
||||
try {
|
||||
return koalabox::cache::get(KEY_APPS).get<AppDlcNameMap>();
|
||||
} catch(const std::exception& e) {
|
||||
LOG_WARN("Failed to get cached apps: {}", e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace smoke_api::cache {
|
||||
std::vector<DLC> get_dlcs(AppId_t app_id) noexcept {
|
||||
try {
|
||||
LOG_DEBUG("Reading cached DLC IDs for the app: {}", app_id);
|
||||
|
||||
const auto apps = get_cached_apps();
|
||||
|
||||
return DLC::get_dlcs_from_apps(apps, app_id);
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("Error reading DLCs from disk cache: ", e.what());
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool save_dlcs(AppId_t app_id, const std::vector<DLC>& dlcs) noexcept {
|
||||
try {
|
||||
LOG_DEBUG("Caching DLC IDs for the app: {}", app_id);
|
||||
|
||||
auto apps = get_cached_apps();
|
||||
|
||||
apps[std::to_string(app_id)] = App{.dlcs = DLC::get_dlc_map_from_vector(dlcs)};
|
||||
|
||||
return koalabox::cache::put(KEY_APPS, nlohmann::json(apps));
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("Error saving DLCs to disk cache: {}", e.what());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
static/smoke_api/cache.hpp
Normal file
9
static/smoke_api/cache.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
namespace smoke_api::cache {
|
||||
std::vector<DLC> get_dlcs(AppId_t app_id) noexcept;
|
||||
|
||||
bool save_dlcs(AppId_t app_id, const std::vector<DLC>& dlcs) noexcept;
|
||||
}
|
||||
58
static/smoke_api/config.cpp
Normal file
58
static/smoke_api/config.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <koalabox/config.hpp>
|
||||
#include <koalabox/io.hpp>
|
||||
#include <koalabox/logger.hpp>
|
||||
#include <koalabox/util.hpp>
|
||||
|
||||
#include "smoke_api/config.hpp"
|
||||
|
||||
namespace smoke_api::config {
|
||||
Config instance; // NOLINT(cert-err58-cpp)
|
||||
|
||||
std::vector<DLC> get_extra_dlcs(const uint32_t app_id) {
|
||||
return DLC::get_dlcs_from_apps(instance.extra_dlcs, app_id);
|
||||
}
|
||||
|
||||
bool is_dlc_unlocked(
|
||||
const AppId_t app_id,
|
||||
const AppId_t dlc_id,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
auto status = instance.default_app_status;
|
||||
|
||||
const auto app_id_str = std::to_string(app_id);
|
||||
if(instance.override_app_status.contains(app_id_str)) {
|
||||
status = instance.override_app_status[app_id_str];
|
||||
}
|
||||
|
||||
const auto dlc_id_str = std::to_string(dlc_id);
|
||||
if(instance.override_dlc_status.contains(dlc_id_str)) {
|
||||
status = instance.override_dlc_status[dlc_id_str];
|
||||
}
|
||||
|
||||
bool is_unlocked;
|
||||
|
||||
switch(status) {
|
||||
case AppStatus::UNLOCKED:
|
||||
is_unlocked = true;
|
||||
break;
|
||||
case AppStatus::LOCKED:
|
||||
is_unlocked = false;
|
||||
break;
|
||||
case AppStatus::ORIGINAL:
|
||||
case AppStatus::UNDEFINED:
|
||||
is_unlocked = original_function();
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_TRACE(
|
||||
"App ID: {}, DLC ID: {}, Status: {}, Original: {}, Unlocked: {}",
|
||||
app_id_str,
|
||||
dlc_id_str,
|
||||
nlohmann::json(status).dump(),
|
||||
original_function(),
|
||||
is_unlocked
|
||||
);
|
||||
|
||||
return is_unlocked;
|
||||
}
|
||||
}
|
||||
60
static/smoke_api/config.hpp
Normal file
60
static/smoke_api/config.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace smoke_api::config {
|
||||
enum class AppStatus {
|
||||
UNDEFINED,
|
||||
ORIGINAL,
|
||||
UNLOCKED,
|
||||
LOCKED,
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(
|
||||
AppStatus,
|
||||
// @formatter:off
|
||||
{
|
||||
{AppStatus::UNDEFINED, nullptr},
|
||||
{AppStatus::ORIGINAL, "original"},
|
||||
{AppStatus::UNLOCKED, "unlocked"},
|
||||
{AppStatus::LOCKED, "locked"},
|
||||
}
|
||||
// @formatter:on
|
||||
)
|
||||
|
||||
struct Config {
|
||||
uint32_t $version = 2;
|
||||
bool logging = false;
|
||||
AppStatus default_app_status = AppStatus::UNLOCKED;
|
||||
uint32_t override_app_id = 0;
|
||||
std::map<std::string, AppStatus> override_app_status;
|
||||
std::map<std::string, AppStatus> override_dlc_status;
|
||||
AppDlcNameMap extra_dlcs;
|
||||
bool auto_inject_inventory = true;
|
||||
std::vector<uint32_t> extra_inventory_items;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(
|
||||
Config,
|
||||
// NOLINT(misc-const-correctness)
|
||||
$version,
|
||||
logging,
|
||||
default_app_status,
|
||||
override_app_id,
|
||||
override_app_status,
|
||||
override_dlc_status,
|
||||
extra_dlcs,
|
||||
auto_inject_inventory,
|
||||
extra_inventory_items
|
||||
)
|
||||
};
|
||||
|
||||
extern Config instance;
|
||||
|
||||
std::vector<DLC> get_extra_dlcs(AppId_t app_id);
|
||||
|
||||
bool is_dlc_unlocked(
|
||||
AppId_t app_id,
|
||||
AppId_t dlc_id,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
}
|
||||
225
static/smoke_api/interfaces/steam_apps.cpp
Normal file
225
static/smoke_api/interfaces/steam_apps.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include <set>
|
||||
|
||||
#include <koalabox/logger.hpp>
|
||||
#include <koalabox/util.hpp>
|
||||
|
||||
#include "smoke_api/interfaces/steam_apps.hpp"
|
||||
#include "smoke_api/api.hpp"
|
||||
#include "smoke_api/cache.hpp"
|
||||
#include "smoke_api/config.hpp"
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace {
|
||||
/// 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;
|
||||
|
||||
std::map<uint32_t, std::vector<DLC>> app_dlcs; // NOLINT(cert-err58-cpp)
|
||||
std::set<uint32_t> fully_fetched; // NOLINT(cert-err58-cpp)
|
||||
|
||||
std::string get_app_id_log(const uint32_t app_id) {
|
||||
return app_id ? fmt::format("App ID: {:>8}, ", app_id) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param app_id
|
||||
* @return boolean indicating if the function was able to successfully fetch DLC IDs from all sources.
|
||||
*/
|
||||
void fetch_and_cache_dlcs(AppId_t app_id) {
|
||||
static std::mutex section;
|
||||
const std::lock_guard lock(section);
|
||||
|
||||
if(app_id == 0) {
|
||||
LOG_ERROR("{} -> App ID is 0", __func__);
|
||||
app_dlcs[app_id] = {}; // Dummy value to avoid checking for presence on each access
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to fetch data only once. However, if any of the remote sources have failed
|
||||
// previously, we want to attempt fetching again.
|
||||
if(fully_fetched.contains(app_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Any of the sources might fail, so we try to get optimal result
|
||||
// by aggregating results from all the sources into a single set.
|
||||
std::vector<DLC> aggregated_dlcs;
|
||||
|
||||
const auto append_dlcs = [&](
|
||||
const std::vector<DLC>& dlc_list,
|
||||
const std::string& source_name
|
||||
) {
|
||||
LOG_DEBUG("App ID {} has {} DLCs defined in {}", app_id, dlc_list.size(), source_name);
|
||||
// Append DLCs to aggregated DLCs
|
||||
std::ranges::copy(dlc_list, std::back_inserter(aggregated_dlcs));
|
||||
};
|
||||
|
||||
append_dlcs(smoke_api::config::get_extra_dlcs(app_id), "local config");
|
||||
|
||||
const auto github_dlcs_opt = smoke_api::api::fetch_dlcs_from_github(app_id);
|
||||
if(github_dlcs_opt) {
|
||||
append_dlcs(*github_dlcs_opt, "GitHub repository");
|
||||
}
|
||||
|
||||
const auto steam_dlcs_opt = smoke_api::api::fetch_dlcs_from_steam(app_id);
|
||||
if(steam_dlcs_opt) {
|
||||
append_dlcs(*steam_dlcs_opt, "Steam API");
|
||||
}
|
||||
|
||||
if(github_dlcs_opt && steam_dlcs_opt) {
|
||||
fully_fetched.insert(app_id);
|
||||
} else {
|
||||
append_dlcs(smoke_api::cache::get_dlcs(app_id), "disk cache");
|
||||
}
|
||||
|
||||
// Cache DLCs in memory and cache for future use
|
||||
app_dlcs[app_id] = aggregated_dlcs;
|
||||
|
||||
smoke_api::cache::save_dlcs(app_id, aggregated_dlcs);
|
||||
}
|
||||
}
|
||||
|
||||
namespace smoke_api::steam_apps {
|
||||
bool IsDlcUnlocked(
|
||||
const std::string& function_name,
|
||||
const AppId_t app_id,
|
||||
const AppId_t dlc_id,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
try {
|
||||
const auto unlocked = smoke_api::config::is_dlc_unlocked(
|
||||
app_id,
|
||||
dlc_id,
|
||||
original_function
|
||||
);
|
||||
|
||||
LOG_INFO(
|
||||
"{} -> {}DLC ID: {:>8}, Unlocked: {}",
|
||||
function_name,
|
||||
get_app_id_log(app_id),
|
||||
dlc_id,
|
||||
unlocked
|
||||
);
|
||||
|
||||
return unlocked;
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("Uncaught exception: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int GetDLCCount(
|
||||
const std::string& function_name,
|
||||
const AppId_t app_id,
|
||||
const std::function<int()>& original_function
|
||||
) {
|
||||
try {
|
||||
const auto total_count = [&](int count) {
|
||||
LOG_INFO("{} -> Responding with DLC count: {}", function_name, count);
|
||||
return count;
|
||||
};
|
||||
|
||||
if(app_id != 0) {
|
||||
LOG_DEBUG("{} -> App ID: {}", function_name, app_id);
|
||||
}
|
||||
|
||||
const auto original_count = original_function();
|
||||
LOG_DEBUG("{} -> Original DLC count: {}", function_name, original_count);
|
||||
|
||||
if(original_count < MAX_DLC) {
|
||||
return total_count(original_count);
|
||||
}
|
||||
|
||||
LOG_DEBUG(
|
||||
"Game has {} or more DLCs. Fetching DLCs from remote sources.",
|
||||
original_count
|
||||
);
|
||||
|
||||
fetch_and_cache_dlcs(app_id);
|
||||
|
||||
return total_count(static_cast<int>(app_dlcs[app_id].size()));
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("Uncaught exception: {}", function_name, e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetDLCDataByIndex(
|
||||
const std::string& function_name,
|
||||
const AppId_t app_id,
|
||||
int iDLC,
|
||||
AppId_t* pDlcId,
|
||||
bool* pbAvailable,
|
||||
char* pchName,
|
||||
const int cchNameBufferSize,
|
||||
const std::function<bool()>& original_function,
|
||||
const std::function<bool(AppId_t)>& is_originally_unlocked
|
||||
) {
|
||||
try {
|
||||
LOG_DEBUG("{} -> {}index: {:>3}", function_name, get_app_id_log(app_id), iDLC);
|
||||
|
||||
const auto print_dlc_info = [&](const std::string& tag) {
|
||||
LOG_INFO(
|
||||
R"({} -> [{:^12}] {}index: {:>3}, DLC ID: {:>8}, available: {:5}, name: "{}")",
|
||||
function_name,
|
||||
tag,
|
||||
get_app_id_log(app_id),
|
||||
iDLC,
|
||||
*pDlcId,
|
||||
*pbAvailable,
|
||||
pchName
|
||||
);
|
||||
};
|
||||
|
||||
const auto inject_dlc = [&](const DLC& dlc) {
|
||||
// Fill the output pointers
|
||||
*pDlcId = dlc.get_id();
|
||||
*pbAvailable = smoke_api::config::is_dlc_unlocked(
|
||||
app_id,
|
||||
*pDlcId,
|
||||
[&] {
|
||||
return is_originally_unlocked(*pDlcId);
|
||||
}
|
||||
);
|
||||
|
||||
auto name = dlc.get_name();
|
||||
name = name.substr(0, cchNameBufferSize + 1);
|
||||
memcpy_s(pchName, cchNameBufferSize, name.c_str(), name.size());
|
||||
};
|
||||
|
||||
if(app_dlcs.contains(app_id)) {
|
||||
const auto& dlcs = app_dlcs[app_id];
|
||||
|
||||
if(iDLC >= 0 && iDLC < dlcs.size()) {
|
||||
inject_dlc(dlcs[iDLC]);
|
||||
print_dlc_info("injected");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_WARN("{} -> Out of bounds DLC index: {}", function_name, iDLC);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto success = original_function();
|
||||
|
||||
if(success) {
|
||||
*pbAvailable = smoke_api::config::is_dlc_unlocked(
|
||||
app_id,
|
||||
*pDlcId,
|
||||
[&] {
|
||||
return *pbAvailable;
|
||||
}
|
||||
);
|
||||
print_dlc_info("original");
|
||||
} else {
|
||||
LOG_WARN("{} -> original call failed for index: {}", function_name, iDLC);
|
||||
}
|
||||
|
||||
return success;
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("{} -> Uncaught exception: {}", function_name, e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
static/smoke_api/interfaces/steam_apps.hpp
Normal file
30
static/smoke_api/interfaces/steam_apps.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace smoke_api::steam_apps {
|
||||
bool IsDlcUnlocked(
|
||||
const std::string& function_name,
|
||||
AppId_t app_id,
|
||||
AppId_t dlc_id,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
|
||||
int GetDLCCount(
|
||||
const std::string& function_name,
|
||||
AppId_t app_id,
|
||||
const std::function<int()>& original_function
|
||||
);
|
||||
|
||||
bool GetDLCDataByIndex(
|
||||
const std::string& function_name,
|
||||
AppId_t app_id,
|
||||
int iDLC,
|
||||
AppId_t* pDlcId,
|
||||
bool* pbAvailable,
|
||||
char* pchName,
|
||||
int cchNameBufferSize,
|
||||
const std::function<bool()>& original_function,
|
||||
const std::function<bool(AppId_t)>& is_originally_unlocked
|
||||
);
|
||||
}
|
||||
270
static/smoke_api/interfaces/steam_inventory.cpp
Normal file
270
static/smoke_api/interfaces/steam_inventory.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
#include <koalabox/logger.hpp>
|
||||
|
||||
#include "smoke_api/interfaces/steam_inventory.hpp"
|
||||
#include "smoke_api/config.hpp"
|
||||
|
||||
namespace smoke_api::steam_inventory {
|
||||
EResult GetResultStatus(
|
||||
const std::string& function_name,
|
||||
const SteamInventoryResult_t resultHandle,
|
||||
const std::function<EResult()>& original_function
|
||||
) {
|
||||
const auto status = original_function();
|
||||
|
||||
LOG_DEBUG(
|
||||
"{} -> handle: {}, status: {}",
|
||||
function_name,
|
||||
resultHandle,
|
||||
static_cast<int>(status)
|
||||
);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool GetResultItems(
|
||||
const std::string& function_name,
|
||||
const SteamInventoryResult_t resultHandle,
|
||||
SteamItemDetails_t* pOutItemsArray,
|
||||
uint32_t* punOutItemsArraySize,
|
||||
const std::function<bool()>& original_function,
|
||||
const std::function<bool(SteamItemDef_t*, uint32_t*)>& get_item_definition_ids
|
||||
) {
|
||||
static std::mutex section;
|
||||
const std::lock_guard guard(section);
|
||||
|
||||
const auto success = original_function();
|
||||
|
||||
auto print_item = [](const std::string& tag, const SteamItemDetails_t& item) {
|
||||
LOG_DEBUG(
|
||||
" [{}] definitionId: {}, itemId: {}, quantity: {}, flags: {}",
|
||||
tag,
|
||||
item.m_iDefinition,
|
||||
item.m_itemId,
|
||||
item.m_unQuantity,
|
||||
item.m_unFlags
|
||||
);
|
||||
};
|
||||
|
||||
if(not success) {
|
||||
LOG_DEBUG("{} -> original result is false", function_name);
|
||||
return success;
|
||||
}
|
||||
|
||||
if(punOutItemsArraySize == nullptr) {
|
||||
LOG_ERROR("{} -> arraySize pointer is null", function_name);
|
||||
return success;
|
||||
}
|
||||
|
||||
LOG_DEBUG(
|
||||
"{} -> handle: {}, pOutItemsArray: {}, arraySize: {}",
|
||||
function_name,
|
||||
resultHandle,
|
||||
fmt::ptr(pOutItemsArray),
|
||||
*punOutItemsArraySize
|
||||
);
|
||||
|
||||
static uint32_t original_count = 0;
|
||||
const auto injected_count = smoke_api::config::instance.extra_inventory_items.size();
|
||||
|
||||
// Automatically get inventory items from steam
|
||||
static std::vector<SteamItemDef_t> auto_inventory_items;
|
||||
if(smoke_api::config::instance.auto_inject_inventory) {
|
||||
static std::once_flag inventory_inject_flag;
|
||||
std::call_once(
|
||||
inventory_inject_flag,
|
||||
[&] {
|
||||
uint32_t count = 0;
|
||||
if(get_item_definition_ids(nullptr, &count)) {
|
||||
auto_inventory_items.resize(count);
|
||||
get_item_definition_ids(auto_inventory_items.data(), &count);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const auto auto_injected_count = auto_inventory_items.size();
|
||||
|
||||
if(not pOutItemsArray) {
|
||||
// If pOutItemsArray is NULL then we must set the array size.
|
||||
original_count = *punOutItemsArraySize;
|
||||
*punOutItemsArraySize += auto_injected_count + injected_count;
|
||||
LOG_DEBUG(
|
||||
"{} -> Original count: {}, Total count: {}",
|
||||
function_name,
|
||||
original_count,
|
||||
*punOutItemsArraySize
|
||||
);
|
||||
} else {
|
||||
// Otherwise, we modify the array
|
||||
for(int i = 0; i < original_count; i++) {
|
||||
print_item("original", pOutItemsArray[i]);
|
||||
}
|
||||
|
||||
static auto new_item = [](SteamItemDef_t id) {
|
||||
return SteamItemDetails_t{
|
||||
.m_itemId = id,
|
||||
.m_iDefinition = id,
|
||||
.m_unQuantity = 1,
|
||||
.m_unFlags = 0,
|
||||
};
|
||||
};
|
||||
|
||||
for(int i = 0; i < auto_injected_count; i++) {
|
||||
auto& item = pOutItemsArray[original_count + i];
|
||||
const auto item_def_id = auto_inventory_items[i];
|
||||
|
||||
item = new_item(item_def_id);
|
||||
|
||||
print_item("auto-injected", item);
|
||||
}
|
||||
|
||||
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::instance.extra_inventory_items[i];
|
||||
|
||||
item = new_item(item_def_id);
|
||||
|
||||
print_item("injected", item);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetResultItemProperty(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
uint32_t unItemIndex,
|
||||
const char* pchPropertyName,
|
||||
const char* pchValueBuffer,
|
||||
const uint32_t* punValueBufferSizeOut,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
const auto common_info = fmt::format(
|
||||
"{} -> Handle: {}, Index: {}, Name: '{}'",
|
||||
function_name,
|
||||
resultHandle,
|
||||
unItemIndex,
|
||||
pchPropertyName
|
||||
);
|
||||
|
||||
const auto success = original_function();
|
||||
|
||||
if(!success) {
|
||||
LOG_WARN("{}, Result is false", common_info);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DEBUG(
|
||||
"{}, Buffer: '{}'",
|
||||
common_info,
|
||||
std::string(pchValueBuffer, *punValueBufferSizeOut - 1)
|
||||
);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetAllItems(
|
||||
const std::string& function_name,
|
||||
const SteamInventoryResult_t* pResultHandle,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
const auto success = original_function();
|
||||
|
||||
LOG_DEBUG("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle));
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetItemsByID(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t* pResultHandle,
|
||||
const SteamItemInstanceID_t* pInstanceIDs,
|
||||
const uint32_t unCountInstanceIDs,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
const auto success = original_function();
|
||||
|
||||
LOG_DEBUG("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle));
|
||||
|
||||
if(success && pInstanceIDs != nullptr) {
|
||||
for(int i = 0; i < unCountInstanceIDs; i++) {
|
||||
LOG_DEBUG(" Index: {}, ItemId: {}", i, pInstanceIDs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeResult(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
void* pOutBuffer,
|
||||
uint32_t* punOutBufferSize,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
const auto success = original_function();
|
||||
|
||||
if(pOutBuffer != nullptr) {
|
||||
std::string buffer(static_cast<char*>(pOutBuffer), *punOutBufferSize);
|
||||
LOG_DEBUG("{} -> Handle: {}, Buffer: '{}'", function_name, resultHandle, buffer);
|
||||
} else {
|
||||
LOG_DEBUG(
|
||||
"{} -> Handle: {}, Size: '{}'",
|
||||
function_name,
|
||||
resultHandle,
|
||||
*punOutBufferSize
|
||||
);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GetItemDefinitionIDs(
|
||||
const std::string& function_name,
|
||||
const SteamItemDef_t* pItemDefIDs,
|
||||
uint32_t* punItemDefIDsArraySize,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
const auto success = original_function();
|
||||
|
||||
if(!success) {
|
||||
LOG_WARN("{} -> Result is false", function_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(punItemDefIDsArraySize) {
|
||||
LOG_DEBUG("{} -> Size: {}", function_name, *punItemDefIDsArraySize);
|
||||
} else {
|
||||
return success;
|
||||
}
|
||||
|
||||
if(pItemDefIDs) { // Definitions were copied
|
||||
for(int i = 0; i < *punItemDefIDsArraySize; i++) {
|
||||
const auto& def = pItemDefIDs[i];
|
||||
LOG_DEBUG(" Index: {}, ID: {}", i, def);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CheckResultSteamID(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
CSteamID steamIDExpected,
|
||||
const std::function<bool()>& original_function
|
||||
) {
|
||||
const auto result = original_function();
|
||||
|
||||
LOG_DEBUG(
|
||||
"{} -> handle: {}, steamID: {}, original result: {}",
|
||||
function_name,
|
||||
resultHandle,
|
||||
steamIDExpected,
|
||||
result
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
66
static/smoke_api/interfaces/steam_inventory.hpp
Normal file
66
static/smoke_api/interfaces/steam_inventory.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace smoke_api::steam_inventory {
|
||||
EResult GetResultStatus(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
const std::function<EResult()>& original_function
|
||||
);
|
||||
|
||||
bool GetResultItems(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
SteamItemDetails_t* pOutItemsArray,
|
||||
uint32_t* punOutItemsArraySize,
|
||||
const std::function<bool()>& original_function,
|
||||
const std::function<bool(SteamItemDef_t*, uint32_t*)>& get_item_definition_ids
|
||||
);
|
||||
|
||||
bool GetResultItemProperty(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
uint32_t unItemIndex,
|
||||
const char* pchPropertyName,
|
||||
const char* pchValueBuffer,
|
||||
const uint32_t* punValueBufferSizeOut,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
|
||||
bool GetAllItems(
|
||||
const std::string& function_name,
|
||||
const SteamInventoryResult_t* pResultHandle,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
|
||||
bool GetItemsByID(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t* pResultHandle,
|
||||
const SteamItemInstanceID_t* pInstanceIDs,
|
||||
uint32_t unCountInstanceIDs,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
|
||||
bool SerializeResult(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
void* pOutBuffer,
|
||||
uint32_t* punOutBufferSize,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
|
||||
bool GetItemDefinitionIDs(
|
||||
const std::string& function_name,
|
||||
const SteamItemDef_t* pItemDefIDs,
|
||||
uint32_t* punItemDefIDsArraySize,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
|
||||
bool CheckResultSteamID(
|
||||
const std::string& function_name,
|
||||
SteamInventoryResult_t resultHandle,
|
||||
CSteamID steamIDExpected,
|
||||
const std::function<bool()>& original_function
|
||||
);
|
||||
}
|
||||
34
static/smoke_api/interfaces/steam_user.cpp
Normal file
34
static/smoke_api/interfaces/steam_user.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <koalabox/logger.hpp>
|
||||
|
||||
#include "smoke_api/interfaces/steam_user.hpp"
|
||||
#include "smoke_api/config.hpp"
|
||||
|
||||
namespace smoke_api::steam_user {
|
||||
EUserHasLicenseForAppResult UserHasLicenseForApp(
|
||||
const std::string& function_name,
|
||||
const AppId_t appId,
|
||||
const AppId_t dlcId,
|
||||
const std::function<EUserHasLicenseForAppResult()>& original_function
|
||||
) {
|
||||
const auto result = original_function();
|
||||
|
||||
if(result == k_EUserHasLicenseResultNoAuth) {
|
||||
LOG_WARN("{} -> App ID: {:>8}, Result: NoAuth", function_name, dlcId);
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto has_license = smoke_api::config::is_dlc_unlocked(
|
||||
appId,
|
||||
dlcId,
|
||||
[&] {
|
||||
return result == k_EUserHasLicenseResultHasLicense;
|
||||
}
|
||||
);
|
||||
|
||||
LOG_INFO("{} -> App ID: {:>8}, HasLicense: {}", function_name, dlcId, has_license);
|
||||
|
||||
return has_license
|
||||
? k_EUserHasLicenseResultHasLicense
|
||||
: k_EUserHasLicenseResultDoesNotHaveLicense;
|
||||
}
|
||||
}
|
||||
12
static/smoke_api/interfaces/steam_user.hpp
Normal file
12
static/smoke_api/interfaces/steam_user.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
namespace smoke_api::steam_user {
|
||||
EUserHasLicenseForAppResult UserHasLicenseForApp(
|
||||
const std::string& function_name,
|
||||
AppId_t appId,
|
||||
AppId_t dlcId,
|
||||
const std::function<EUserHasLicenseForAppResult()>& original_function
|
||||
);
|
||||
}
|
||||
25
static/smoke_api/types.cpp
Normal file
25
static/smoke_api/types.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "smoke_api/types.hpp"
|
||||
|
||||
std::vector<DLC> DLC::get_dlcs_from_apps(const AppDlcNameMap& apps, const AppId_t app_id) {
|
||||
std::vector<DLC> dlcs;
|
||||
|
||||
if(const auto app_id_str = std::to_string(app_id); apps.contains(app_id_str)) {
|
||||
const auto& app = apps.at(app_id_str);
|
||||
|
||||
for(auto const& [id, name] : app.dlcs) {
|
||||
dlcs.emplace_back(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
return dlcs;
|
||||
}
|
||||
|
||||
DlcNameMap DLC::get_dlc_map_from_vector(const std::vector<DLC>& dlcs) {
|
||||
DlcNameMap map;
|
||||
|
||||
for(const auto& dlc : dlcs) {
|
||||
map[dlc.get_id_str()] = dlc.get_name();
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
83
static/smoke_api/types.hpp
Normal file
83
static/smoke_api/types.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// TODO: Replace with direct call
|
||||
#define GET_ORIGINAL_HOOKED_FUNCTION(FUNC) \
|
||||
static const auto FUNC##_o = koalabox::hook::get_original_hooked_function(#FUNC, FUNC);
|
||||
|
||||
#define DETOUR_ADDRESS(FUNC, ADDRESS) \
|
||||
koalabox::hook::detour_or_warn(ADDRESS, #FUNC, reinterpret_cast<uintptr_t>(FUNC));
|
||||
|
||||
using AppId_t = uint32_t;
|
||||
using HSteamPipe = uint32_t;
|
||||
using EResult = uint32_t;
|
||||
using HSteamUser = uint32_t;
|
||||
using SteamInventoryResult_t = uint32_t;
|
||||
using SteamItemInstanceID_t = uint64_t;
|
||||
using SteamItemDef_t = uint32_t;
|
||||
using CSteamID = uint64_t;
|
||||
|
||||
struct SteamItemDetails_t {
|
||||
SteamItemInstanceID_t m_itemId;
|
||||
uint32_t m_iDefinition;
|
||||
uint16_t m_unQuantity;
|
||||
uint16_t m_unFlags; // see ESteamItemFlags
|
||||
};
|
||||
|
||||
// results from UserHasLicenseForApp
|
||||
enum EUserHasLicenseForAppResult {
|
||||
k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app
|
||||
k_EUserHasLicenseResultDoesNotHaveLicense = 1,
|
||||
// User does not have a license for the specified app
|
||||
k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated
|
||||
};
|
||||
|
||||
// These aliases exist solely to increase code readability
|
||||
|
||||
using AppIdKey = std::string;
|
||||
using DlcIdKey = std::string;
|
||||
using DlcNameValue = std::string;
|
||||
using DlcNameMap = std::map<DlcIdKey, DlcNameValue>;
|
||||
|
||||
struct App {
|
||||
DlcNameMap dlcs;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(App, dlcs) // NOLINT(misc-const-correctness)
|
||||
};
|
||||
|
||||
using AppDlcNameMap = std::map<AppIdKey, App>;
|
||||
|
||||
class DLC {
|
||||
// These 2 names must match the property names from Steam API
|
||||
std::string appid;
|
||||
std::string name;
|
||||
|
||||
public:
|
||||
explicit DLC() = default;
|
||||
|
||||
explicit DLC(std::string appid, std::string name) : appid{std::move(appid)},
|
||||
name{std::move(name)} {}
|
||||
|
||||
[[nodiscard]] std::string get_id_str() const {
|
||||
return appid;
|
||||
}
|
||||
|
||||
[[nodiscard]] AppId_t get_id() const {
|
||||
return std::stoi(appid);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string get_name() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(DLC, appid, name)
|
||||
|
||||
static std::vector<DLC> get_dlcs_from_apps(const AppDlcNameMap& apps, AppId_t app_id);
|
||||
|
||||
static DlcNameMap get_dlc_map_from_vector(const std::vector<DLC>& dlcs);
|
||||
};
|
||||
Reference in New Issue
Block a user