12 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
14 changed files with 5090 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="linux_exports_generator [32]" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="--input_libs_glob $ProjectFileDir$/res/steamworks/*/binaries/linux32/libsteam_api.so --output_path $ProjectFileDir$/src/generated/32/proxy_exports" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="linux_exports_generator" CONFIG_NAME="Debug [32]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="linux_exports_generator">
<configuration default="false" name="linux_exports_generator [32]" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="--input_libs_glob $ProjectFileDir$/res/steamworks/*/binaries/linux32/libsteam_api.so --output_path $ProjectFileDir$/src/generated/32/proxy_exports" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="linux_exports_generator" CONFIG_NAME="Debug [64]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="linux_exports_generator">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>

View File

@@ -1,5 +1,5 @@
<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">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>

View File

@@ -1,5 +1,5 @@
<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">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.24)
project(SmokeAPI VERSION 4.1.0)
project(SmokeAPI VERSION 4.1.3)
include(KoalaBox/cmake/KoalaBox.cmake)
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
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,
you should use the equivalent package for your distro.
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/)
[[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/)
[[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/)
[[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
best for you.
### 🔀 Proxy mode (🐧 Linux)
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.
2. In Steam _Library_ open game's _Properties_, switch to the _General_ tab, and set the following _LAUNCH OPTIONS_:
| Bitness | Launch Options |
|---------|------------------------------------------------------------------------------------------------------------------------|
| 32-bit | `LD_PRELOAD="./libsmoke_api32.so $HOME/.local/share/Steam/ubuntu12_32/gameoverlayrenderer.so" ./<GameExe32> %command%` |
| 64-bit | `LD_PRELOAD="./libsmoke_api64.so $HOME/.local/share/Steam/ubuntu12_64/gameoverlayrenderer.so" ./<GameExe64> %command%` |
| Bitness | Launch Options |
|---------|---------------------------------------------------------------------------------------------------------------------------------|
| 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> ; exit ; %command%` |
Where `<GameExe32>` and `<GameExe64>` correspond to the actual filename of the game executable. For example:
- `TheEscapists2.x86` (32-bit)
- `TheEscapists2.x86_64` (64-bit)
- `_linux/darkest.bin.x86` (32-bit)
- `_linux/darkest.bin.x86_64` (64-bit)
- `bin/linux_x64/eurotrucks2` (64-bit)
- `eurotrucks2` (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
@@ -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
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
@@ -438,7 +435,8 @@ This project makes use of the following open source projects:
- [p-ranav/glob](https://github.com/p-ranav/glob)
- [pantor/inja](https://github.com/pantor/inja)
- [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)
- [batterycenter/embed](https://github.com/batterycenter/embed)

View File

@@ -20,8 +20,8 @@
"logging": {
"type": "boolean",
"default": false,
"x-packaged-default": true,
"description": "Enables logging to SmokeAPI.log.log file.",
"x-packaged-default": true,
"x-valid-values": "`true` or `false`."
},
"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/globals.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/http_client.hpp>
#include <koalabox/lib.hpp>
#include <koalabox/lib_monitor.hpp>
#include <koalabox/logger.hpp>
@@ -57,6 +58,31 @@ namespace {
void* original_steamapi_handle = nullptr;
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) {
if(!steamapi_handle) {
kb::util::panic("Invalid state. steamapi_handle is null.");
@@ -65,12 +91,13 @@ namespace {
std::set<std::string> versions;
// On Linux the section name depends on individual lib file, so we can't use generic constants
const std::string str_section_name = kb::platform::is_windows ? ".text" : ".rodata.str";
const auto rdata_section = kb::lib::get_section_or_throw(steamapi_handle, str_section_name);
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 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();
for(std::sregex_iterator i = matches_begin; i != matches_end; ++i) {
@@ -111,6 +138,15 @@ namespace {
static const auto CreateInterface$ = KB_LIB_GET_FUNC(steamclient_handle, CreateInterface);
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.
// Hence, we need to query SteamClient interfaces and hook them if needed.
const auto steamclient_versions = find_steamclient_versions(steamapi_handle);
@@ -125,6 +161,8 @@ namespace {
LOG_INFO("'{}' has not been initialized. Waiting for initialization.", steamclient_version);
}
}
} else {
LOG_ERROR("{} -> steamapi_handle is null", __func__);
}
return true;
@@ -192,7 +230,10 @@ namespace {
if(const auto lib_bitness = kb::lib::get_bitness(lib_path)) {
if(static_cast<uint8_t>(*lib_bitness) == kb::platform::bitness) {
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;
proxy_exports::init(self_module_handle, original_steamapi_handle);
@@ -209,7 +250,7 @@ namespace {
}
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);
#ifdef KB_LINUX
@@ -272,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() {
try {
static bool shutdown_complete = false;

View File

@@ -4,6 +4,14 @@
namespace smoke_api {
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();
AppId_t get_app_id();

View File

@@ -15,7 +15,9 @@ namespace steam_client {
if(interface_version) {
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;

View File

@@ -237,13 +237,10 @@ namespace steam_interfaces {
continue;
}
const auto* const interface_ptr = ISteamClient_GetISteamGenericInterface(
ISteamClient_GetISteamGenericInterface(
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) {
LOG_ERROR("{} -> Unhandled exception: {}", __func__, e.what());

View File

@@ -4,6 +4,7 @@
#include <koalabox/logger.hpp>
#include "smoke_api/steamclient/steamclient.hpp"
#include "smoke_api/smoke_api.hpp"
#include "smoke_api/types.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;
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(
__func__,
interface_version,