Split static & shared lib

This commit is contained in:
acidicoala
2025-08-23 13:44:17 +05:00
parent b828ecc58a
commit dc086e40e0
48 changed files with 1048 additions and 1318 deletions

View 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;
}
}
}

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

View 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;
}
}

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

View 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;
}
}

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