Refactored types

This commit is contained in:
acidicoala
2025-08-23 21:26:49 +05:00
parent dc086e40e0
commit e08cf014d1
19 changed files with 243 additions and 89 deletions

View File

@@ -28,6 +28,7 @@ set(SMOKE_API_STATIC_SOURCES
static/smoke_api/config.cpp static/smoke_api/config.cpp
static/smoke_api/types.hpp static/smoke_api/types.hpp
static/smoke_api/types.cpp static/smoke_api/types.cpp
static/smoke_api/steamclient/steamclient.hpp
) )
set(SMOKE_API_SOURCES set(SMOKE_API_SOURCES
@@ -45,8 +46,7 @@ set(SMOKE_API_SOURCES
src/steam_api/steam_client.cpp src/steam_api/steam_client.cpp
src/steam_api/steam_interface.cpp src/steam_api/steam_interface.cpp
src/steam_api/steam_interface.hpp src/steam_api/steam_interface.hpp
src/steamclient/steamclient.cpp src/steamclient.cpp
src/steamclient/steamclient.hpp
src/main.cpp src/main.cpp
src/smoke_api.cpp src/smoke_api.cpp
src/smoke_api.hpp src/smoke_api.hpp
@@ -62,7 +62,7 @@ add_library(SmokeAPI_interface INTERFACE)
#target_compile_options(SmokeAPI_interface PUBLIC /std:c++latest) #target_compile_options(SmokeAPI_interface PUBLIC /std:c++latest)
target_include_directories(SmokeAPI_interface INTERFACE target_include_directories(SmokeAPI_interface INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/static>"
) )
target_link_libraries(SmokeAPI_interface INTERFACE KoalaBox $<TARGET_OBJECTS:KoalaBox>) target_link_libraries(SmokeAPI_interface INTERFACE KoalaBox $<TARGET_OBJECTS:KoalaBox>)
@@ -71,7 +71,7 @@ target_link_libraries(SmokeAPI_interface INTERFACE KoalaBox $<TARGET_OBJECTS:Koa
add_library(SmokeAPI_static STATIC ${SMOKE_API_STATIC_SOURCES}) add_library(SmokeAPI_static STATIC ${SMOKE_API_STATIC_SOURCES})
target_link_libraries(SmokeAPI_static PUBLIC SmokeAPI_interface) target_link_libraries(SmokeAPI_static PUBLIC SmokeAPI_interface)
target_include_directories(SmokeAPI_static PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/static") #target_include_directories(SmokeAPI_static PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/static")
### Shared SmokeAPI ### Shared SmokeAPI
@@ -81,11 +81,13 @@ set_target_properties(SmokeAPI PROPERTIES RUNTIME_OUTPUT_NAME ${STEAMAPI_DLL})
configure_version_resource(SmokeAPI "Free DLC for everyone ʕ ᵔᴥᵔʔ") configure_version_resource(SmokeAPI "Free DLC for everyone ʕ ᵔᴥᵔʔ")
target_include_directories(SmokeAPI PRIVATE target_include_directories(SmokeAPI PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/static" # "${CMAKE_CURRENT_SOURCE_DIR}/static"
"${CMAKE_CURRENT_BINARY_DIR}") "${CMAKE_CURRENT_BINARY_DIR}")
set(B_PRODUCTION_MODE ON) CPMAddPackage(
CPMAddPackage("gh:batterycenter/embed@1.2.19") URI "gh:batterycenter/embed@1.2.19"
OPTIONS "B_PRODUCTION_MODE ON"
)
b_embed(SmokeAPI "res/interface_lookup.json") b_embed(SmokeAPI "res/interface_lookup.json")
configure_linker_exports( configure_linker_exports(

View File

@@ -113,7 +113,7 @@ In this case you can use a configuration file link:res/SmokeAPI.config.json[Smok
To use it, simply place it next to the SmokeAPI DLL. To use it, simply place it next to the SmokeAPI DLL.
It will be read upon each launch of the game. It will be read upon each launch of the game.
In the absence of the config file, default values specified below will be used. In the absence of the config file, default values specified below will be used.
The configuration file is expected to conform to the Json standard. The configuration file is expected to conform to the JSON standard.
`logging`:: Toggles generation of a `SmokeAPI.log.log` file. `logging`:: Toggles generation of a `SmokeAPI.log.log` file.
+ +

View File

@@ -1,3 +0,0 @@
# Building from source
TODO

View File

@@ -1,3 +0,0 @@
# Tools
TODO: describe steamworks_downloader and steamworks_parser tools

View File

@@ -1,4 +1,5 @@
{ {
"$schema": "https://raw.githubusercontent.com/acidicoala/SmokeAPI/refs/heads/master/res/SmokeAPI.schema.json",
"$version": 3, "$version": 3,
"logging": true, "logging": true,
"default_app_status": "unlocked", "default_app_status": "unlocked",

145
res/SmokeAPI.schema.json Normal file
View File

@@ -0,0 +1,145 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/smokeapi-config.schema.json",
"title": "SmokeAPI configuration",
"type": "object",
"additionalProperties": false,
"properties": {
"$version": {
"type": "integer",
"minimum": 0,
"default": 3,
"description": "A technical field reserved for tools like GUI config editors. Do not modify this value."
},
"logging": {
"type": "boolean",
"default": false,
"description": "Toggles generation of a SmokeAPI.log.log file."
},
"default_app_status": {
"description": "Sets the default DLC unlocking behaviour.",
"default": "unlocked",
"$ref": "#/$defs/AppStatus"
},
"override_app_id": {
"type": "integer",
"minimum": 0,
"default": 0,
"description": "Overrides the current app ID (0 means no override)."
},
"override_app_status": {
"type": "object",
"default": {},
"description": "Overrides the status of all DLCs that belong to a specified app ID. Keys are app IDs (as strings).",
"patternProperties": {
"^\\d{1,10}$": {
"$ref": "#/$defs/AppStatus"
}
},
"additionalProperties": false
},
"override_dlc_status": {
"type": "object",
"default": {},
"description": "Overrides the status of individual DLCs, regardless of the corresponding app status. Keys are DLC IDs (as strings).",
"patternProperties": {
"^\\d{1,10}$": {
"$ref": "#/$defs/AppStatus"
}
},
"additionalProperties": false
},
"extra_dlcs": {
"type": "object",
"default": {},
"description": "Map of app IDs to objects containing DLC IDs with their display names.",
"patternProperties": {
"^\\d{1,10}$": {
"$ref": "#/$defs/App"
}
},
"additionalProperties": false
},
"auto_inject_inventory": {
"type": "boolean",
"default": true,
"description": "Whether SmokeAPI should automatically inject a list of all registered inventory items when a game queries user inventory."
},
"extra_inventory_items": {
"type": "array",
"default": [],
"description": "A list of inventory item IDs that will be added in addition to the automatically injected items.",
"items": {
"type": "integer",
"minimum": 0
}
}
},
"$defs": {
"AppStatus": {
"description": "Unlock status for apps or DLCs.",
"anyOf": [
{
"type": "string",
"enum": [
"original",
"unlocked",
"locked"
]
},
{
"type": "null",
"description": "Represents UNDEFINED in the enum."
}
]
},
"App": {
"type": "object",
"additionalProperties": false,
"properties": {
"dlcs": {
"type": "object",
"default": {},
"description": "Map of DLC IDs to human-readable DLC names.",
"patternProperties": {
"^\\d{1,10}$": {
"type": "string"
}
},
"additionalProperties": false
}
}
}
},
"examples": [
{
"$version": 3,
"logging": true,
"default_app_status": "unlocked",
"override_app_status": {
"1234": "original",
"4321": "unlocked"
},
"override_dlc_status": {
"1234": "original",
"4321": "unlocked",
"5678": "locked"
},
"auto_inject_inventory": true,
"extra_inventory_items": [],
"extra_dlcs": {
"1234": {
"dlcs": {
"56789": "Example DLC 1"
}
},
"4321": {
"dlcs": {
"98765": "Example DLC 2",
"98766": "Example DLC 3"
}
}
}
}
]
}

View File

@@ -12,7 +12,7 @@
#include "smoke_api.hpp" #include "smoke_api.hpp"
#include "smoke_api/config.hpp" #include "smoke_api/config.hpp"
#include "steamclient/steamclient.hpp" #include "smoke_api/steamclient/steamclient.hpp"
// Hooking steam_api has shown itself to be less desirable than steamclient // Hooking steam_api has shown itself to be less desirable than steamclient
// for the reasons outlined below: // for the reasons outlined below:

View File

@@ -20,13 +20,6 @@ constexpr auto STEAM_INVENTORY = "STEAMINVENTORY_INTERFACE_V";
#define MODULE_CALL_CLOSURE(FUNC, ...) \ #define MODULE_CALL_CLOSURE(FUNC, ...) \
[&] { MODULE_CALL(FUNC, __VA_ARGS__); } [&] { MODULE_CALL(FUNC, __VA_ARGS__); }
#define HOOKED_CALL(FUNC, ...) \
static const auto _##FUNC = KB_HOOK_GET_HOOKED_FN(smoke_api::steamapi_module, FUNC); \
return _##FUNC(__VA_ARGS__)
#define HOOKED_CALL_CLOSURE(FUNC, ...) \
[&] { HOOKED_CALL(FUNC, __VA_ARGS__); }
namespace smoke_api { namespace smoke_api {
extern HMODULE steamapi_module; extern HMODULE steamapi_module;

View File

@@ -2,41 +2,6 @@
#include "smoke_api/types.hpp" #include "smoke_api/types.hpp"
/**
* By default, virtual functions are declared with __thiscall
* convention, which is normal since they are class members.
* But it presents an issue for us, since we cannot pass *this
* pointer as a function argument. This is because *this
* pointer is passed via register ECX in __thiscall
* convention. Hence, to resolve this issue we declare our
* hooked functions with __fastcall convention, to trick
* the compiler into reading ECX & EDX registers as 1st
* and 2nd function arguments respectively. Similarly, __fastcall
* makes the compiler push the first argument into the ECX register,
* which mimics the __thiscall calling convention. Register EDX
* is not used anywhere in this case, but we still pass it along
* to conform to the __fastcall convention. This all applies
* to the x86 architecture.
*
* In x86-64 however, there is only one calling convention,
* so __fastcall is simply ignored. However, RDX in this case
* will store the 1st actual argument to the function, so we
* have to omit it from the function signature.
*
* The macros below implement the above-mentioned considerations.
*/
#ifdef _WIN64
#define PARAMS(...) void *RCX, __VA_ARGS__
#define ARGS(...) RCX, __VA_ARGS__
#define THIS RCX
#else
#define PARAMS(...) const void *ECX, const void *EDX, __VA_ARGS__
#define ARGS(...) ECX, EDX, __VA_ARGS__
#define THIS ECX
#endif
#define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall // NOLINT(*-macro-parentheses)
// ISteamApps // ISteamApps
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t)); VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t));
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t)); VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t));

16
src/steamclient.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "smoke_api/steamclient/steamclient.hpp"
#include "smoke_api.hpp"
#include "smoke_api/types.hpp"
#include "steam_api/steam_client.hpp"
/**
* SmokeAPI implementation
*/
C_DECL(void*) CreateInterface(const char* interface_version, int* out_result) {
return steam_client::GetGenericInterface(
__func__,
interface_version,
HOOKED_CALL_CLOSURE(CreateInterface, interface_version, out_result)
);
}

View File

@@ -1,15 +0,0 @@
#include <koalabox/hook.hpp>
#include "steamclient.hpp"
#include "smoke_api.hpp"
#include "smoke_api/types.hpp"
#include "steam_api/steam_client.hpp"
C_DECL(void*) CreateInterface(const char* interface_string, int* out_result) {
return steam_client::GetGenericInterface(
__func__,
interface_string,
HOOKED_CALL_CLOSURE(CreateInterface, interface_string, out_result)
);
}

View File

@@ -1,5 +0,0 @@
#pragma once
#define C_DECL(TYPE) extern "C" __declspec(noinline) TYPE __cdecl
C_DECL(void*) CreateInterface(const char* interface_string, int* out_result);

View File

@@ -23,7 +23,7 @@ namespace smoke_api::config {
) )
struct Config { struct Config {
uint32_t $version = 2; uint32_t $version = 3;
bool logging = false; bool logging = false;
AppStatus default_app_status = AppStatus::UNLOCKED; AppStatus default_app_status = AppStatus::UNLOCKED;
uint32_t override_app_id = 0; uint32_t override_app_id = 0;

View File

@@ -64,11 +64,11 @@ namespace smoke_api::steam_inventory {
); );
static uint32_t original_count = 0; static uint32_t original_count = 0;
const auto injected_count = smoke_api::config::instance.extra_inventory_items.size(); const auto injected_count = config::instance.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(smoke_api::config::instance.auto_inject_inventory) { if(config::instance.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,
@@ -120,7 +120,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 = smoke_api::config::instance.extra_inventory_items[i]; const auto item_def_id = config::instance.extra_inventory_items[i];
item = new_item(item_def_id); item = new_item(item_def_id);

View File

@@ -17,7 +17,7 @@ namespace smoke_api::steam_user {
return result; return result;
} }
const auto has_license = smoke_api::config::is_dlc_unlocked( const auto has_license = config::is_dlc_unlocked(
appId, appId,
dlcId, dlcId,
[&] { [&] {

View File

@@ -0,0 +1,5 @@
#pragma once
#include "smoke_api/types.hpp"
C_DECL(void*) CreateInterface(const char* interface_version, int* out_result);

View File

@@ -6,12 +6,52 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
// TODO: Replace with direct call #include <koalabox/hook.hpp>
#define GET_ORIGINAL_HOOKED_FUNCTION(FUNC) \
static const auto FUNC##_o = koalabox::hook::get_original_hooked_function(#FUNC, FUNC);
#define DETOUR_ADDRESS(FUNC, ADDRESS) \ #define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall // NOLINT(*-macro-parentheses)
koalabox::hook::detour_or_warn(ADDRESS, #FUNC, reinterpret_cast<uintptr_t>(FUNC)); #define C_DECL(TYPE) extern "C" __declspec(noinline) TYPE __cdecl
// These macros are meant to be used for callbacks that should return original result
#define HOOKED_CALL(FUNC, ...) \
static const auto _##FUNC = KB_HOOK_GET_HOOKED_FN(FUNC); \
return _##FUNC(__VA_ARGS__)
#define HOOKED_CALL_CLOSURE(FUNC, ...) \
[&] { HOOKED_CALL(FUNC, __VA_ARGS__); }
/**
* By default, virtual functions are declared with __thiscall
* convention, which is normal since they are class members.
* But it presents an issue for us, since we cannot pass *this
* pointer as a function argument. This is because *this
* pointer is passed via register ECX in __thiscall
* convention. Hence, to resolve this issue we declare virtual
* hooked functions with __fastcall convention, to trick
* the compiler into reading ECX & EDX registers as 1st
* and 2nd function arguments respectively. Similarly, __fastcall
* makes the compiler push the first argument into the ECX register,
* which mimics the __thiscall calling convention. Register EDX
* is not used anywhere in this case, but we still pass it along
* to conform to the __fastcall convention. This all applies
* to the x86 architecture.
*
* In x86-64 however, there is only one calling convention,
* so __fastcall is simply ignored. However, RDX in this case
* will store the 1st actual argument to the function, so we
* have to omit it from the function signature.
*
* The macros below implement the above-mentioned considerations.
*/
#ifdef _WIN64
#define PARAMS(...) const void *RCX, __VA_ARGS__
#define ARGS(...) RCX, __VA_ARGS__
#define THIS RCX
#else
#define PARAMS(...) const void *ECX, const void *EDX, __VA_ARGS__
#define ARGS(...) ECX, EDX, __VA_ARGS__
#define THIS ECX
#endif
using AppId_t = uint32_t; using AppId_t = uint32_t;
using HSteamPipe = uint32_t; using HSteamPipe = uint32_t;
@@ -80,4 +120,4 @@ public:
static std::vector<DLC> get_dlcs_from_apps(const AppDlcNameMap& apps, AppId_t app_id); 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); static DlcNameMap get_dlc_map_from_vector(const std::vector<DLC>& dlcs);
}; };

13
tools/README.md Normal file
View File

@@ -0,0 +1,13 @@
# SmokeAPI tools
## Steamworks Downloader
A simple tool for downloading Steamworks SDK archive from the public GitHub repository
[cdn](https://github.com/acidicoala/cdn/tree/main/valve)
and unzipping headers and binaries into the main project for subsequent processing.
## Steamworks Parser
A more sophisticated tool that parses Steamworks SDK C++ headers
in order to build an [interface lookup map](../res/interface_lookup.json),
which is used by SmokeAPI to lookup function ordinals for specific interface versions.