13 Commits

Author SHA1 Message Date
acidicoala
3bd112c8f4 Refactored version check 2026-01-17 12:06:19 +05:00
acidicoala
7b54b4bc7b Added null checks 2026-01-14 19:34:00 +05:00
acidicoala
077096b7ed renew steamapi_handle 2026-01-12 04:10:43 +05:00
acidicoala
bc8ee85e19 Fixed proxy mode detection 2026-01-08 23:16:10 +05:00
acidicoala
5aaa9ed151 Added trace logs to proxy exports 2026-01-04 21:41:26 +05:00
acidicoala
e2b126c8b6 Regenerated README [skip ci] 2026-01-04 07:22:14 +05:00
acidicoala
2f6d6cc9aa Regenerated linux proxy exports 2026-01-04 07:12:44 +05:00
acidicoala
8784df5f45 Fix mbedtls include attempt 3 2026-01-04 06:54:06 +05:00
acidicoala
dc12301090 Fix mbedtls include attempt 2 2026-01-04 06:13:58 +05:00
acidicoala
907e939b67 Fixed string section name 2026-01-04 04:49:35 +05:00
acidicoala
7b82994b17 Regenerate linux proxy exports 2026-01-04 04:49:28 +05:00
acidicoala
11bd820921 Sync KoalaBox (brotli, openssl) 2026-01-04 04:49:19 +05:00
acidicoala
9acf7312d3 Fixed static order init fiasco 2026-01-02 21:57:41 +05:00
18 changed files with 5128 additions and 61 deletions

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="steamworks_downloader [prompt]" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="$Prompt$" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/res" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="steamworks_downloader" CONFIG_NAME="Debug [32]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="steamworks_downloader"> <configuration default="false" name="steamworks_downloader [prompt]" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="$Prompt$" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/res" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="steamworks_downloader" CONFIG_NAME="Debug [64]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="steamworks_downloader">
<method v="2"> <method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" /> <option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method> </method>

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="sync" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="sync" CONFIG_NAME="Debug [32]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="sync"> <configuration default="false" name="sync" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="sync" CONFIG_NAME="Debug [64]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="sync">
<method v="2"> <method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" /> <option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method> </method>

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.24) cmake_minimum_required(VERSION 3.24)
project(SmokeAPI VERSION 4.1.0) project(SmokeAPI VERSION 4.1.3)
include(KoalaBox/cmake/KoalaBox.cmake) include(KoalaBox/cmake/KoalaBox.cmake)
add_subdirectory(KoalaBox) add_subdirectory(KoalaBox)

View File

@@ -145,14 +145,15 @@ In such cases, it might be worth trying [Special K], which can inject SmokeAPI a
### ✔️ Requirements ### ✔️ Requirements
Linux builds of SmokeAPI depend on several libraries. Make sure they are installed on your system. Linux builds of SmokeAPI depend on several libraries. These libraries are usually included in the
[Steam Linux Runtime 3.0 (sniper)](https://github.com/ValveSoftware/steam-runtime#readme),
so nothing is required to be installed on the system. But when launching a game directly via its executable
(such as in case of hook mode) your system needs to have the following required libraries installed.
The following list features links in Arch Linux repositories, but if you are using a different distribution, The following list features links in Arch Linux repositories, but if you are using a different distribution,
you should use the equivalent package for your distro. you should use the equivalent package for your distro.
Required libraries: Required libraries:
- [brotli](https://archlinux.org/packages/core/x86_64/brotli/)
[[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-brotli/)]
- [gcc-libs](https://archlinux.org/packages/core/x86_64/gcc-libs/) - [gcc-libs](https://archlinux.org/packages/core/x86_64/gcc-libs/)
[[32-bit](https://archlinux.org/packages/core/x86_64/lib32-gcc-libs/)] [[32-bit](https://archlinux.org/packages/core/x86_64/lib32-gcc-libs/)]
@@ -165,12 +166,6 @@ Required libraries:
- [libnghttp2](https://archlinux.org/packages/core/x86_64/libnghttp2/) - [libnghttp2](https://archlinux.org/packages/core/x86_64/libnghttp2/)
[[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-libnghttp2/)] [[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-libnghttp2/)]
- [libssh2](https://archlinux.org/packages/core/x86_64/libssh2/)
[[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-libssh2/)]
- [openssl](https://archlinux.org/packages/core/x86_64/openssl/)
[[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-openssl/)]
- [zlib](https://archlinux.org/packages/core/x86_64/zlib/) - [zlib](https://archlinux.org/packages/core/x86_64/zlib/)
[[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-zlib/)] [[32-bit](https://archlinux.org/packages/multilib/x86_64/lib32-zlib/)]
@@ -194,7 +189,6 @@ wrappers might cause issues in theory. However, in practice real tests show that
chance of success compared to proxy mode. So, at the end of the day, try both modes to see which one works chance of success compared to proxy mode. So, at the end of the day, try both modes to see which one works
best for you. best for you.
### 🔀 Proxy mode (🐧 Linux) ### 🔀 Proxy mode (🐧 Linux)
Same as on Windows: Same as on Windows:
@@ -214,17 +208,17 @@ For example:
1. Extract and paste the `libsmoke_api32.so` or `libsmoke_api64.so` in the root of game's installation directory. 1. Extract and paste the `libsmoke_api32.so` or `libsmoke_api64.so` in the root of game's installation directory.
2. In Steam _Library_ open game's _Properties_, switch to the _General_ tab, and set the following _LAUNCH OPTIONS_: 2. In Steam _Library_ open game's _Properties_, switch to the _General_ tab, and set the following _LAUNCH OPTIONS_:
| Bitness | Launch Options | | Bitness | Launch Options |
|---------|------------------------------------------------------------------------------------------------------------------------| |---------|---------------------------------------------------------------------------------------------------------------------------------|
| 32-bit | `LD_PRELOAD="./libsmoke_api32.so $HOME/.local/share/Steam/ubuntu12_32/gameoverlayrenderer.so" ./<GameExe32> %command%` | | 32-bit | `LD_PRELOAD="./libsmoke_api32.so $HOME/.local/share/Steam/ubuntu12_32/gameoverlayrenderer.so" ./<GameExe32> ; exit ; %command%` |
| 64-bit | `LD_PRELOAD="./libsmoke_api64.so $HOME/.local/share/Steam/ubuntu12_64/gameoverlayrenderer.so" ./<GameExe64> %command%` | | 64-bit | `LD_PRELOAD="./libsmoke_api64.so $HOME/.local/share/Steam/ubuntu12_64/gameoverlayrenderer.so" ./<GameExe64> ; exit ; %command%` |
Where `<GameExe32>` and `<GameExe64>` correspond to the actual filename of the game executable. For example: Where `<GameExe32>` and `<GameExe64>` correspond to the actual filename of the game executable. For example:
- `TheEscapists2.x86` (32-bit) - `TheEscapists2.x86` (32-bit)
- `TheEscapists2.x86_64` (64-bit) - `TheEscapists2.x86_64` (64-bit)
- `_linux/darkest.bin.x86` (32-bit) - `_linux/darkest.bin.x86` (32-bit)
- `_linux/darkest.bin.x86_64` (64-bit) - `_linux/darkest.bin.x86_64` (64-bit)
- `bin/linux_x64/eurotrucks2` (64-bit) - `eurotrucks2` (64-bit)
- `binaries/victoria3` (64-bit) - `binaries/victoria3` (64-bit)
And so on. Notice that Linux executables do not have `.exe` extension like on Windows, so make sure to copy the entire And so on. Notice that Linux executables do not have `.exe` extension like on Windows, so make sure to copy the entire
@@ -234,6 +228,9 @@ If you have other environment variables, and you don't know how to correctly com
then please make extensive use of search engines and LLMs for guidance and examples then please make extensive use of search engines and LLMs for guidance and examples
before seeking help the official forum topic. before seeking help the official forum topic.
> [!NOTE]
> The `; exit ; %command%` at the end of launch options
> is a trick used to force Steam to directly run the game executable.
## ⚙ Configuration ## ⚙ Configuration
@@ -438,7 +435,8 @@ This project makes use of the following open source projects:
- [p-ranav/glob](https://github.com/p-ranav/glob) - [p-ranav/glob](https://github.com/p-ranav/glob)
- [pantor/inja](https://github.com/pantor/inja) - [pantor/inja](https://github.com/pantor/inja)
- [jarro2783/cxxopts](https://github.com/jarro2783/cxxopts) - [jarro2783/cxxopts](https://github.com/jarro2783/cxxopts)
- [serge1/ELFIO](https://github.com/serge1/ELFIO) - [serge1/ELFIO](https://github.com/serge1/ELFIO)
- [Mbed-TLS/mbedtls](https://github.com/Mbed-TLS/mbedtls)
- [bshoshany/thread-pool](https://github.com/bshoshany/thread-pool) - [bshoshany/thread-pool](https://github.com/bshoshany/thread-pool)
- [batterycenter/embed](https://github.com/batterycenter/embed) - [batterycenter/embed](https://github.com/batterycenter/embed)

View File

@@ -20,8 +20,8 @@
"logging": { "logging": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"x-packaged-default": true,
"description": "Enables logging to SmokeAPI.log.log file.", "description": "Enables logging to SmokeAPI.log.log file.",
"x-packaged-default": true,
"x-valid-values": "`true` or `false`." "x-valid-values": "`true` or `false`."
}, },
"log_steam_http": { "log_steam_http": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
#include <koalabox/config.hpp> #include <koalabox/config.hpp>
#include <koalabox/globals.hpp> #include <koalabox/globals.hpp>
#include <koalabox/hook.hpp> #include <koalabox/hook.hpp>
#include <koalabox/http_client.hpp>
#include <koalabox/lib.hpp> #include <koalabox/lib.hpp>
#include <koalabox/lib_monitor.hpp> #include <koalabox/lib_monitor.hpp>
#include <koalabox/logger.hpp> #include <koalabox/logger.hpp>
@@ -25,7 +26,7 @@
#include "build_config.h" #include "build_config.h"
#if defined(KB_WIN) #ifdef KB_WIN
#include "koalabox/win.hpp" #include "koalabox/win.hpp"
#elif defined(KB_LINUX) && defined(KB_32) #elif defined(KB_LINUX) && defined(KB_32)
#include "generated/32/proxy_exports.hpp" #include "generated/32/proxy_exports.hpp"
@@ -57,6 +58,31 @@ namespace {
void* original_steamapi_handle = nullptr; void* original_steamapi_handle = nullptr;
bool is_hook_mode; bool is_hook_mode;
void check_for_updates() {
try {
const auto latest_release_url = std::format(
"https://api.github.com/repos/acidicoala/{}/releases/latest",
PROJECT_NAME
);
const auto res = kb::http_client::get_json(latest_release_url);
const auto latest_tag = res["tag_name"].get<std::string>();
const auto current_tag = std::format("v{}", PROJECT_VERSION);
if(current_tag == latest_tag) {
LOG_DEBUG("Running the latest version");
} else {
const auto release_page = std::format(
"https://github.com/acidicoala/{}/releases/{}",
PROJECT_NAME, latest_tag
);
LOG_WARN("New version {} available: {}", latest_tag, release_page);
}
} catch(const std::exception& e) {
LOG_ERROR("{} -> Unexpected error: {}", __func__, e.what());
}
}
std::set<std::string> find_steamclient_versions(void* steamapi_handle) { std::set<std::string> find_steamclient_versions(void* steamapi_handle) {
if(!steamapi_handle) { if(!steamapi_handle) {
kb::util::panic("Invalid state. steamapi_handle is null."); kb::util::panic("Invalid state. steamapi_handle is null.");
@@ -64,11 +90,14 @@ namespace {
std::set<std::string> versions; std::set<std::string> versions;
const auto rdata_section = kb::lib::get_section_or_throw(steamapi_handle, kb::lib::CONST_STR_SECTION); // On Linux the section name depends on individual lib file, so we can't use generic constants
const auto rdata = rdata_section.to_string(); // ReSharper disable once CppDFAUnreachableCode
const std::string str_section_name = kb::platform::is_windows ? ".rdata" : ".rodata";
const auto str_section = kb::lib::get_section_or_throw(steamapi_handle, str_section_name);
const auto str_section_str = str_section.to_string();
const std::regex pattern(R"(SteamClient\d{3})"); const std::regex pattern(R"(SteamClient\d{3})");
const auto matches_begin = std::sregex_iterator(rdata.begin(), rdata.end(), pattern); const auto matches_begin = std::sregex_iterator(str_section_str.begin(), str_section_str.end(), pattern);
const auto matches_end = std::sregex_iterator(); const auto matches_end = std::sregex_iterator();
for(std::sregex_iterator i = matches_begin; i != matches_end; ++i) { for(std::sregex_iterator i = matches_begin; i != matches_end; ++i) {
@@ -109,6 +138,15 @@ namespace {
static const auto CreateInterface$ = KB_LIB_GET_FUNC(steamclient_handle, CreateInterface); static const auto CreateInterface$ = KB_LIB_GET_FUNC(steamclient_handle, CreateInterface);
if(auto* steamapi_handle = kb::lib::get_lib_handle(STEAM_API_MODULE)) { if(auto* steamapi_handle = kb::lib::get_lib_handle(STEAM_API_MODULE)) {
if(original_steamapi_handle == nullptr) { // hook mode on Windows
original_steamapi_handle = steamapi_handle;
} else if(steamapi_handle != original_steamapi_handle) {
LOG_WARN(
"{} -> steamapi_handle ({}) != original_steamapi_handle ({})",
__func__, steamapi_handle, original_steamapi_handle
);
}
// SteamAPI might have been initialized. // SteamAPI might have been initialized.
// Hence, we need to query SteamClient interfaces and hook them if needed. // Hence, we need to query SteamClient interfaces and hook them if needed.
const auto steamclient_versions = find_steamclient_versions(steamapi_handle); const auto steamclient_versions = find_steamclient_versions(steamapi_handle);
@@ -123,6 +161,8 @@ namespace {
LOG_INFO("'{}' has not been initialized. Waiting for initialization.", steamclient_version); LOG_INFO("'{}' has not been initialized. Waiting for initialization.", steamclient_version);
} }
} }
} else {
LOG_ERROR("{} -> steamapi_handle is null", __func__);
} }
return true; return true;
@@ -190,7 +230,10 @@ namespace {
if(const auto lib_bitness = kb::lib::get_bitness(lib_path)) { if(const auto lib_bitness = kb::lib::get_bitness(lib_path)) {
if(static_cast<uint8_t>(*lib_bitness) == kb::platform::bitness) { if(static_cast<uint8_t>(*lib_bitness) == kb::platform::bitness) {
if(const auto lib_handle = kb::lib::load(lib_path)) { if(const auto lib_handle = kb::lib::load(lib_path)) {
LOG_INFO("Found original library: {}", kb::path::to_str(lib_path)); LOG_INFO(
"Found & loaded original library '{}' @ {}",
kb::path::to_str(lib_path), *lib_handle
);
original_steamapi_handle = *lib_handle; original_steamapi_handle = *lib_handle;
proxy_exports::init(self_module_handle, original_steamapi_handle); proxy_exports::init(self_module_handle, original_steamapi_handle);
@@ -207,7 +250,7 @@ namespace {
} }
void init_proxy_mode([[maybe_unused]] void* self_module_handle) { void init_proxy_mode([[maybe_unused]] void* self_module_handle) {
is_hook_mode = true; is_hook_mode = false;
original_steamapi_handle = kb::lib::load_original_library(kb::paths::get_self_dir(), STEAM_API_MODULE); original_steamapi_handle = kb::lib::load_original_library(kb::paths::get_self_dir(), STEAM_API_MODULE);
#ifdef KB_LINUX #ifdef KB_LINUX
@@ -229,16 +272,16 @@ namespace smoke_api {
kb::globals::init_globals(self_module_handle, PROJECT_NAME); kb::globals::init_globals(self_module_handle, PROJECT_NAME);
config::instance = kb::config::parse<config::Config>(); config::get() = kb::config::parse<config::Config>();
if(config::instance.logging) { if(config::get().logging) {
kb::logger::init_file_logger(kb::paths::get_log_path()); kb::logger::init_file_logger(kb::paths::get_log_path());
} else { } else {
kb::logger::init_null_logger(); kb::logger::init_null_logger();
} }
LOG_INFO("{} v{}{} | Built at '{}'", PROJECT_NAME, PROJECT_VERSION, VERSION_SUFFIX, __TIMESTAMP__); LOG_INFO("{} v{}{} | Built at '{}'", PROJECT_NAME, PROJECT_VERSION, VERSION_SUFFIX, __TIMESTAMP__);
LOG_DEBUG("Parsed config:\n{}", nlohmann::ordered_json(config::instance).dump(2)); LOG_DEBUG("Parsed config:\n{}", nlohmann::ordered_json(config::get()).dump(2));
const auto exe_path = kb::lib::get_fs_path(nullptr); const auto exe_path = kb::lib::get_fs_path(nullptr);
const auto exe_name = kb::path::to_str(exe_path.filename()); const auto exe_name = kb::path::to_str(exe_path.filename());
@@ -270,6 +313,14 @@ namespace smoke_api {
} }
} }
void post_init() {
#ifdef KB_DEBUG
// TODO: Add config option to toggle this and show native OS notification
// The real reason behind this is for automatic testing of HTTPs dependencies
std::thread(check_for_updates).detach();
#endif
}
void shutdown() { void shutdown() {
try { try {
static bool shutdown_complete = false; static bool shutdown_complete = false;

View File

@@ -4,6 +4,14 @@
namespace smoke_api { namespace smoke_api {
void init(void* self_module_handle); void init(void* self_module_handle);
/**
* Post-initialization procedures that must be done after the module is finished loading.
* Reason being that on Windows we should not start new threads while being in DllMain callback,
* otherwise we would run into deadlocks/race-conditions/undefined behavior.
*/
void post_init();
void shutdown(); void shutdown();
AppId_t get_app_id(); AppId_t get_app_id();

View File

@@ -15,7 +15,9 @@ namespace steam_client {
if(interface_version) { if(interface_version) {
LOG_DEBUG("{} -> '{}' @ {}", function_name, interface_version, interface); LOG_DEBUG("{} -> '{}' @ {}", function_name, interface_version, interface);
steam_interfaces::hook_virtuals(interface, interface_version); if(interface) {
steam_interfaces::hook_virtuals(interface, interface_version);
}
} }
return interface; return interface;

View File

@@ -237,13 +237,10 @@ namespace steam_interfaces {
continue; continue;
} }
const auto* const interface_ptr = ISteamClient_GetISteamGenericInterface( ISteamClient_GetISteamGenericInterface(
ARGS(steam_user, steam_pipe, interface_version.c_str()) ARGS(steam_user, steam_pipe, interface_version.c_str())
); );
if(not interface_ptr) {
LOG_ERROR("Failed to get generic interface: '{}'", interface_version)
}
} }
} catch(const std::exception& e) { } catch(const std::exception& e) {
LOG_ERROR("{} -> Unhandled exception: {}", __func__, e.what()); LOG_ERROR("{} -> Unhandled exception: {}", __func__, e.what());

View File

@@ -4,6 +4,7 @@
#include <koalabox/logger.hpp> #include <koalabox/logger.hpp>
#include "smoke_api/steamclient/steamclient.hpp" #include "smoke_api/steamclient/steamclient.hpp"
#include "smoke_api/smoke_api.hpp"
#include "smoke_api/types.hpp" #include "smoke_api/types.hpp"
#include "steam_api/steam_client.hpp" #include "steam_api/steam_client.hpp"
@@ -16,6 +17,9 @@ C_DECL(void*) CreateInterface(const char* interface_version, create_interface_re
static std::mutex section; static std::mutex section;
const std::lock_guard lock(section); const std::lock_guard lock(section);
static std::once_flag once_flag;
std::call_once(once_flag, smoke_api::post_init);
return steam_client::GetGenericInterface( return steam_client::GetGenericInterface(
__func__, __func__,
interface_version, interface_version,

View File

@@ -1,14 +1,16 @@
#include <koalabox/config.hpp> #include <koalabox/config.hpp>
#include <koalabox/io.hpp>
#include <koalabox/logger.hpp> #include <koalabox/logger.hpp>
#include "smoke_api/config.hpp" #include "smoke_api/config.hpp"
namespace smoke_api::config { namespace smoke_api::config {
Config instance; // NOLINT(cert-err58-cpp) Config& get() noexcept {
static Config config;
return config;
}
std::vector<DLC> get_extra_dlcs(const uint32_t app_id) { std::vector<DLC> get_extra_dlcs(const uint32_t app_id) {
return DLC::get_dlcs_from_apps(instance.extra_dlcs, app_id); return DLC::get_dlcs_from_apps(get().extra_dlcs, app_id);
} }
bool is_dlc_unlocked( bool is_dlc_unlocked(
@@ -16,16 +18,16 @@ namespace smoke_api::config {
const AppId_t dlc_id, const AppId_t dlc_id,
const std::function<bool()>& original_function const std::function<bool()>& original_function
) { ) {
auto status = instance.default_app_status; auto status = get().default_app_status;
const auto app_id_str = std::to_string(app_id); const auto app_id_str = std::to_string(app_id);
if(instance.override_app_status.contains(app_id_str)) { if(get().override_app_status.contains(app_id_str)) {
status = instance.override_app_status[app_id_str]; status = get().override_app_status[app_id_str];
} }
const auto dlc_id_str = std::to_string(dlc_id); const auto dlc_id_str = std::to_string(dlc_id);
if(instance.override_dlc_status.contains(dlc_id_str)) { if(get().override_dlc_status.contains(dlc_id_str)) {
status = instance.override_dlc_status[dlc_id_str]; status = get().override_dlc_status[dlc_id_str];
} }
bool is_unlocked; bool is_unlocked;

View File

@@ -43,7 +43,7 @@ namespace smoke_api::config {
) )
}; };
extern Config instance; Config& get() noexcept;
std::vector<DLC> get_extra_dlcs(AppId_t app_id); std::vector<DLC> get_extra_dlcs(AppId_t app_id);

View File

@@ -14,8 +14,15 @@ namespace {
/// This means we have to get extra DLC IDs from local config, remote config, or cache. /// This means we have to get extra DLC IDs from local config, remote config, or cache.
constexpr auto MAX_DLC = 64; constexpr auto MAX_DLC = 64;
std::map<uint32_t, std::vector<DLC>> app_dlcs; // NOLINT(cert-err58-cpp) auto& get_fully_fetched_apps() {
std::set<uint32_t> fully_fetched; // NOLINT(cert-err58-cpp) static std::set<uint32_t> fully_fetched_apps;
return fully_fetched_apps;
}
auto& get_app_dlc_map() {
static std::map<uint32_t, std::vector<DLC>> app_dlc_map;
return app_dlc_map;
}
std::string get_app_id_log(const uint32_t app_id) { std::string get_app_id_log(const uint32_t app_id) {
return app_id ? std::format("App ID: {:>8}, ", app_id) : ""; return app_id ? std::format("App ID: {:>8}, ", app_id) : "";
@@ -31,13 +38,13 @@ namespace {
if(app_id == 0) { if(app_id == 0) {
LOG_ERROR("{} -> App ID is 0", __func__); LOG_ERROR("{} -> App ID is 0", __func__);
app_dlcs[app_id] = {}; // Dummy value to avoid checking for presence on each access get_app_dlc_map()[app_id] = {}; // Dummy value to avoid checking for presence on each access
return; return;
} }
// We want to fetch data only once. However, if any of the remote sources have failed // We want to fetch data only once. However, if any of the remote sources have failed
// previously, we want to attempt fetching again. // previously, we want to attempt fetching again.
if(fully_fetched.contains(app_id)) { if(get_fully_fetched_apps().contains(app_id)) {
return; return;
} }
@@ -67,13 +74,13 @@ namespace {
} }
if(github_dlcs_opt && steam_dlcs_opt) { if(github_dlcs_opt && steam_dlcs_opt) {
fully_fetched.insert(app_id); get_fully_fetched_apps().insert(app_id);
} else { } else {
append_dlcs(smoke_api::cache::get_dlcs(app_id), "disk cache"); append_dlcs(smoke_api::cache::get_dlcs(app_id), "disk cache");
} }
// Cache DLCs in memory and cache for future use // Cache DLCs in memory and cache for future use
app_dlcs[app_id] = aggregated_dlcs; get_app_dlc_map()[app_id] = aggregated_dlcs;
smoke_api::cache::save_dlcs(app_id, aggregated_dlcs); smoke_api::cache::save_dlcs(app_id, aggregated_dlcs);
} }
@@ -137,12 +144,12 @@ namespace smoke_api::steam_apps {
fetch_and_cache_dlcs(app_id); fetch_and_cache_dlcs(app_id);
if(app_dlcs.empty()) { if(get_app_dlc_map().empty()) {
LOG_DEBUG("{} -> No cached DLCs, responding with original count", function_name); LOG_DEBUG("{} -> No cached DLCs, responding with original count", function_name);
return total_count(original_count); return total_count(original_count);
} }
return total_count(static_cast<int>(app_dlcs[app_id].size())); return total_count(static_cast<int>(get_app_dlc_map()[app_id].size()));
} catch(const std::exception& e) { } catch(const std::exception& e) {
LOG_ERROR("{} -> Uncaught exception: {}", function_name, e.what()); LOG_ERROR("{} -> Uncaught exception: {}", function_name, e.what());
return 0; return 0;
@@ -188,8 +195,8 @@ namespace smoke_api::steam_apps {
pchName[bytes_to_copy] = '\0'; // Ensure null-termination pchName[bytes_to_copy] = '\0'; // Ensure null-termination
}; };
if(!app_dlcs.empty() && app_dlcs.contains(app_id)) { if(!get_app_dlc_map().empty() && get_app_dlc_map().contains(app_id)) {
const auto& dlcs = app_dlcs[app_id]; const auto& dlcs = get_app_dlc_map()[app_id];
if(iDLC >= 0 && iDLC < dlcs.size()) { if(iDLC >= 0 && iDLC < dlcs.size()) {
output_dlc(dlcs[iDLC]); output_dlc(dlcs[iDLC]);

View File

@@ -14,7 +14,7 @@ namespace smoke_api::steam_http {
try { try {
const auto result = original_function(); const auto result = original_function();
if(config::instance.log_steam_http) { if(config::get().log_steam_http) {
const std::string_view buffer = const std::string_view buffer =
pBodyDataBuffer && unBufferSize pBodyDataBuffer && unBufferSize
? std::string_view( ? std::string_view(
@@ -50,7 +50,7 @@ namespace smoke_api::steam_http {
try { try {
const auto result = original_function(); const auto result = original_function();
if(config::instance.log_steam_http) { if(config::get().log_steam_http) {
const std::string_view buffer = const std::string_view buffer =
pBodyDataBuffer && unBufferSize pBodyDataBuffer && unBufferSize
? std::string_view( ? std::string_view(
@@ -87,7 +87,7 @@ namespace smoke_api::steam_http {
try { try {
const auto result = original_function(); const auto result = original_function();
if(config::instance.log_steam_http) { if(config::get().log_steam_http) {
const std::string_view content_type = const std::string_view content_type =
pchContentType ? pchContentType : "smoke_api::N/A"; pchContentType ? pchContentType : "smoke_api::N/A";

View File

@@ -70,11 +70,11 @@ namespace smoke_api::steam_inventory {
); );
static uint32_t original_count = 0; static uint32_t original_count = 0;
const auto injected_count = config::instance.extra_inventory_items.size(); const auto injected_count = config::get().extra_inventory_items.size();
// Automatically get inventory items from steam // Automatically get inventory items from steam
static std::vector<SteamItemDef_t> auto_inventory_items; static std::vector<SteamItemDef_t> auto_inventory_items;
if(config::instance.auto_inject_inventory) { if(config::get().auto_inject_inventory) {
static std::once_flag inventory_inject_flag; static std::once_flag inventory_inject_flag;
std::call_once( std::call_once(
inventory_inject_flag, inventory_inject_flag,
@@ -126,7 +126,7 @@ namespace smoke_api::steam_inventory {
for(int i = 0; i < injected_count; i++) { for(int i = 0; i < injected_count; i++) {
auto& item = pOutItemsArray[original_count + auto_injected_count + i]; auto& item = pOutItemsArray[original_count + auto_injected_count + i];
const auto item_def_id = config::instance.extra_inventory_items[i]; const auto item_def_id = config::get().extra_inventory_items[i];
item = new_item(item_def_id); item = new_item(item_def_id);