diff --git a/.clang-tidy b/.clang-tidy index 61f39ef..4d10098 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -70,4 +70,5 @@ misc-*, -readability-function-cognitive-complexity, -*-include-cleaner, -*-lambda-function-name, --*-err58-cpp' +-*-err58-cpp, +-*-misplaced-const' diff --git a/CMakeLists.txt b/CMakeLists.txt index 0974baf..1ecb170 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,11 +7,6 @@ include(KoalaBox/cmake/KoalaBox.cmake) add_subdirectory(KoalaBox) add_subdirectory(tools) -set_32_and_64(STEAMAPI_DLL steam_api) -set_32_and_64(STEAMCLIENT_DLL steamclient) -set_32_and_64(STEAM_API_DLL steam_api steam_api64) -set_32_and_64(SMOKEAPI_DLL SmokeAPI32 SmokeAPI64) - configure_build_config(extra_build_config) set(SMOKE_API_STATIC_SOURCES @@ -50,9 +45,22 @@ set(SMOKE_API_SOURCES src/steam_api/steam_interfaces.cpp src/steam_api/steam_interfaces.hpp src/steamclient/steamclient.cpp - src/main.cpp ) +if(WIN32) + set_32_and_64(STEAM_API_MODULE steam_api) + set_32_and_64(STEAMCLIENT_DLL steamclient) + + list(APPEND SMOKE_API_SOURCES src/main_win.cpp) +else() + set_32_and_64(LINUX_DIR linux32 linux64) + set(STEAM_API_MODULE libsteam_api) + set(STEAMCLIENT_DLL steamclient) + + list(APPEND SMOKE_API_SOURCES src/main_linux.cpp) +endif() +set_32_and_64(SMOKE_API_FILENAME smoke_api32 smoke_api64) + ### SmokeAPI interface add_library(SmokeAPI_common INTERFACE) @@ -73,35 +81,46 @@ target_link_libraries(SmokeAPI_static PUBLIC SmokeAPI::common) ### Shared SmokeAPI -add_library(SmokeAPI SHARED ${SMOKE_API_SOURCES} ${VERSION_RESOURCE}) +add_library(SmokeAPI SHARED ${SMOKE_API_SOURCES}) target_link_libraries(SmokeAPI PUBLIC SmokeAPI::common) -set_target_properties(SmokeAPI PROPERTIES RUNTIME_OUTPUT_NAME ${SMOKEAPI_DLL}) -configure_version_resource( - TARGET SmokeAPI - FILE_DESC "Steamworks DLC unlocker" - ORIG_NAME ${SMOKEAPI_DLL} -) +set_target_properties(SmokeAPI PROPERTIES OUTPUT_NAME ${SMOKE_API_FILENAME}) + configure_include_directories() +if(WIN32) + configure_version_resource( + TARGET SmokeAPI + FILE_DESC "Steamworks DLC unlocker" + ORIG_NAME ${SMOKE_API_FILENAME} + ) + configure_linker_exports( + TARGET SmokeAPI + HEADER_NAME "linker_exports_for_steam_api.h" + FORWARDED_DLL "${STEAM_API_MODULE}_o" + INPUT_SOURCES_DIR "" + DLL_FILES_GLOB "${CMAKE_CURRENT_SOURCE_DIR}/res/steamworks/*/binaries/${STEAM_API_MODULE}.dll" + ) + + configure_linker_exports( + TARGET SmokeAPI + HEADER_NAME "linker_exports_for_version.h" + FORWARDED_DLL "C:/Windows/System32/version.dll" + INPUT_SOURCES_DIR "" + DLL_FILES_GLOB "C:/Windows/System32/version.dll" + ) +else() + configure_linker_exports( + TARGET SmokeAPI + HEADER_NAME "libsteam_api_exports.cpp" + FORWARDED_DLL "${STEAM_API_MODULE}_o.so" + DLL_FILES_GLOB "${CMAKE_CURRENT_SOURCE_DIR}/res/steamworks/*/binaries/${LINUX_DIR}/${STEAM_API_MODULE}.so" + INPUT_SOURCES_DIR "" + ) +endif() + ## https://github.com/batterycenter/embed CPMAddPackage( URI "gh:batterycenter/embed@1.2.19" OPTIONS "B_PRODUCTION_MODE ON" ) b_embed(SmokeAPI "res/interface_lookup.json") - -configure_linker_exports( - TARGET SmokeAPI - HEADER_NAME "linker_exports_for_steam_api" - FORWARDED_DLL "${STEAMAPI_DLL}_o" - INPUT_SOURCES_DIR "" - DLL_FILES_GLOB "${CMAKE_CURRENT_SOURCE_DIR}/res/steamworks/*/binaries/${STEAM_API_DLL}.dll" -) - -configure_linker_exports( - TARGET SmokeAPI - HEADER_NAME "linker_exports_for_version" - FORWARDED_DLL "C:/Windows/System32/version.dll" - INPUT_SOURCES_DIR "" - DLL_FILES_GLOB "C:/Windows/System32/version.dll" -) diff --git a/KoalaBox b/KoalaBox index 9274903..cbf46eb 160000 --- a/KoalaBox +++ b/KoalaBox @@ -1 +1 @@ -Subproject commit 92749037d5bf2695923340fe29dd8b0e651cab7b +Subproject commit cbf46eba34491683839ccd23328f00a14a12baca diff --git a/src/main_linux.cpp b/src/main_linux.cpp new file mode 100644 index 0000000..0e2100b --- /dev/null +++ b/src/main_linux.cpp @@ -0,0 +1,20 @@ +#include + +#include "smoke_api/smoke_api.hpp" + +extern "C" void __attribute__((constructor)) on_loaded() { + // On linux we don't automatically get current module handle, + // hence we get it manually + Dl_info info; + if(dladdr(reinterpret_cast(&on_loaded), &info) && info.dli_fname) { + void* handle = dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD); + smoke_api::init(handle); + } else { + OutputDebugString("Initialization error: failed to get own module handle."); + DebugBreak(); + } +} + +extern "C" void __attribute__((destructor)) on_unloaded() { + smoke_api::shutdown(); +} diff --git a/src/main.cpp b/src/main_win.cpp similarity index 100% rename from src/main.cpp rename to src/main_win.cpp diff --git a/src/smoke_api/smoke_api.cpp b/src/smoke_api/smoke_api.cpp index e73baeb..acb69ee 100644 --- a/src/smoke_api/smoke_api.cpp +++ b/src/smoke_api/smoke_api.cpp @@ -7,10 +7,10 @@ #include #include #include +#include #include #include #include -#include #include "smoke_api.hpp" @@ -41,16 +41,17 @@ namespace { namespace kb = koalabox; - HMODULE original_steamapi_handle = nullptr; + void* original_steamapi_handle = nullptr; std::set find_steamclient_versions(const HMODULE steamapi_handle) { std::set versions; - const auto rdata = kb::win::get_pe_section_or_throw(steamapi_handle, ".rdata").to_string(); + const auto rdata_section = kb::module::get_section_or_throw(steamapi_handle, kb::module::CONST_STR_SECTION); + const auto rdata = rdata_section.to_string(); const std::regex pattern(R"(SteamClient\d{3})"); - auto matches_begin = std::sregex_iterator(rdata.begin(), rdata.end(), pattern); - auto matches_end = std::sregex_iterator(); + const auto matches_begin = std::sregex_iterator(rdata.begin(), rdata.end(), pattern); + const auto matches_end = std::sregex_iterator(); for(std::sregex_iterator i = matches_begin; i != matches_end; ++i) { versions.insert(i->str()); @@ -60,16 +61,16 @@ namespace { } // ReSharper disable once CppDFAConstantFunctionResult - bool on_steamclient_loaded(const HMODULE steamclient_handle) noexcept { + bool on_steamclient_loaded(const HMODULE steamclient_handle) { auto* const steamapi_handle = original_steamapi_handle ? original_steamapi_handle - : GetModuleHandle(TEXT(STEAMAPI_DLL)); + : kb::module::get_library_handle(TEXT(STEAMAPI_DLL)); if(!steamapi_handle) { LOG_ERROR("{} -> {} is not loaded", __func__, STEAMAPI_DLL); return true; } - static const auto CreateInterface$ = KB_WIN_GET_PROC(steamclient_handle, CreateInterface); + static const auto CreateInterface$ = KB_MOD_GET_FUNC(steamclient_handle, CreateInterface); const auto steamclient_versions = find_steamclient_versions(steamapi_handle); for(const auto& steamclient_version : steamclient_versions) { @@ -110,7 +111,7 @@ namespace smoke_api { LOG_INFO("{} v{} | Built at '{}'", PROJECT_NAME, PROJECT_VERSION, __TIMESTAMP__); LOG_DEBUG("Parsed config:\n{}", nlohmann::ordered_json(config::instance).dump(2)); - const auto exe_path = kb::win::get_module_path(nullptr); + const auto exe_path = kb::module::get_fs_path(nullptr); const auto exe_name = kb::path::to_str(exe_path.filename()); LOG_DEBUG("Process name: '{}' [{}-bit]", exe_name, kb::util::BITNESS); @@ -136,14 +137,14 @@ namespace smoke_api { LOG_INFO("Initialization complete"); } catch(const std::exception& e) { - kb::util::panic(fmt::format("Initialization error: {}", e.what())); + kb::util::panic(std::format("Initialization error: {}", e.what())); } } void shutdown() { try { if(original_steamapi_handle != nullptr) { - kb::win::free_library(original_steamapi_handle); + kb::module::unload_library(original_steamapi_handle); original_steamapi_handle = nullptr; } @@ -160,7 +161,7 @@ namespace smoke_api { AppId_t get_app_id() { try { - const auto app_id_str = kb::win::get_env_var("SteamAppId"); + const auto app_id_str = kb::util::get_env_var("SteamAppId"); static auto app_id = std::stoi(app_id_str); return app_id; } catch(const std::exception& e) { diff --git a/src/steam_api/steam_interfaces.cpp b/src/steam_api/steam_interfaces.cpp index 46d493a..e06224d 100644 --- a/src/steam_api/steam_interfaces.cpp +++ b/src/steam_api/steam_interfaces.cpp @@ -1,13 +1,13 @@ -#include #include #include #include #include -#include #include "steam_api/steam_interfaces.hpp" + +#include "koalabox/module.hpp" #include "smoke_api/smoke_api.hpp" #include "smoke_api/steamclient/steamclient.hpp" #include "virtuals/steam_api_virtuals.hpp" @@ -210,7 +210,7 @@ namespace steam_interfaces { const auto prefixes = std::views::keys(virtual_hook_map) | std::ranges::to(); // Prepare HSteamPipe and HSteamUser - const auto CreateInterface$ = KB_WIN_GET_PROC(steamclient_handle, CreateInterface); + const auto CreateInterface$ = KB_MOD_GET_FUNC(steamclient_handle, CreateInterface); const auto* const THIS = CreateInterface$(steam_client_interface_version.c_str(), nullptr); hook_virtuals(THIS, steam_client_interface_version); @@ -223,12 +223,13 @@ namespace steam_interfaces { constexpr auto steam_pipe = 1; constexpr auto steam_user = 1; - const bool should_hook = std::ranges::any_of( - prefixes, - [&](const auto& prefix) { - return std::ranges::starts_with(interface_version, prefix); + bool should_hook = false; + for(const auto& prefix : prefixes) { + if(interface_version.starts_with(prefix)) { + should_hook = true; + break; } - ); + } if(not should_hook) { continue; diff --git a/src/steam_api/virtuals/isteaminventory.cpp b/src/steam_api/virtuals/isteaminventory.cpp index 38145e6..498c7a3 100644 --- a/src/steam_api/virtuals/isteaminventory.cpp +++ b/src/steam_api/virtuals/isteaminventory.cpp @@ -17,7 +17,7 @@ VIRTUAL(bool) ISteamInventory_GetResultItems( SteamItemDetails_t* pOutItemsArray, uint32_t* punOutItemsArraySize ) -) noexcept { +) noexcept { return smoke_api::steam_inventory::GetResultItems( __func__, resultHandle, diff --git a/static/smoke_api/interfaces/steam_apps.cpp b/static/smoke_api/interfaces/steam_apps.cpp index c233aa0..f8c455d 100644 --- a/static/smoke_api/interfaces/steam_apps.cpp +++ b/static/smoke_api/interfaces/steam_apps.cpp @@ -18,7 +18,7 @@ namespace { std::set 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) : ""; + return app_id ? std::format("App ID: {:>8}, ", app_id) : ""; } /** @@ -176,9 +176,11 @@ namespace smoke_api::steam_apps { *pDlcId = dlc.get_id(); *pbAvailable = config::is_dlc_unlocked(app_id, *pDlcId, is_originally_unlocked); - auto name = dlc.get_name(); - name = name.substr(0, cchNameBufferSize + 1); - memcpy_s(pchName, cchNameBufferSize, name.c_str(), name.size()); + const auto& name = dlc.get_name(); + + const auto bytes_to_copy = std::min(static_cast(cchNameBufferSize - 1), name.size()); + std::memcpy(pchName, name.c_str(), bytes_to_copy); + pchName[bytes_to_copy] = '\0'; // Ensure null-termination }; if(app_dlcs.contains(app_id)) { diff --git a/static/smoke_api/interfaces/steam_inventory.cpp b/static/smoke_api/interfaces/steam_inventory.cpp index 708da17..ef4ba51 100644 --- a/static/smoke_api/interfaces/steam_inventory.cpp +++ b/static/smoke_api/interfaces/steam_inventory.cpp @@ -216,7 +216,7 @@ namespace smoke_api::steam_inventory { try { const auto success = original_function(); - LOG_DEBUG("{} -> Handle: {}", function_name, fmt::ptr(pResultHandle)); + LOG_DEBUG("{} -> Handle: {}", function_name, static_cast(pResultHandle)); if(success && pInstanceIDs != nullptr) { for(int i = 0; i < unCountInstanceIDs; i++) { diff --git a/static/smoke_api/types.hpp b/static/smoke_api/types.hpp index 2c36e54..97740d4 100644 --- a/static/smoke_api/types.hpp +++ b/static/smoke_api/types.hpp @@ -42,15 +42,17 @@ * have to omit it from the function signature. * * The macros below implement the above-mentioned considerations. + * Also, note the ## prefix before __VA_ARGS__. This enables a GCC extension + * which strips trailing comma if __VA_ARGS__ is empty. */ #ifdef _WIN64 -#define PARAMS(...) const void* RCX, __VA_ARGS__ -#define ARGS(...) RCX, __VA_ARGS__ +#define PARAMS(...) const void* RCX, ##__VA_ARGS__ +#define ARGS(...) RCX, ##__VA_ARGS__ #define THIS RCX #define DECLARE_EDX() #else -#define PARAMS(...) const void* ECX, const void* EDX, __VA_ARGS__ -#define ARGS(...) ECX, EDX, __VA_ARGS__ +#define PARAMS(...) const void* ECX, const void* EDX, ##__VA_ARGS__ +#define ARGS(...) ECX, EDX, ##__VA_ARGS__ #define THIS ECX #define DECLARE_EDX() const void* EDX = nullptr; #endif diff --git a/tools/src/steamworks_downloader.cpp b/tools/src/steamworks_downloader.cpp index 42a9e08..6534080 100644 --- a/tools/src/steamworks_downloader.cpp +++ b/tools/src/steamworks_downloader.cpp @@ -49,11 +49,23 @@ namespace { return unzip_dir / "headers/steam" / fs::path(name).filename(); } - if(name.starts_with("sdk/redistributable_bin/") && name.ends_with(".dll") && - name.find("steam_api") != std::string::npos) { + // Windows binaries + if( + name.starts_with("sdk/redistributable_bin/") && + name.ends_with(".dll") && + name.contains("steam_api") + ) { return unzip_dir / "binaries" / fs::path(name).filename(); } + // Linux binaries + if( + name.starts_with("sdk/redistributable_bin/linux") && + name.ends_with("libsteam_api.so") + ) { + return unzip_dir / "binaries" / name.substr(name.find("linux")); + } + return fs::path(); } ); @@ -84,7 +96,7 @@ namespace { * A tool for downloading Steamworks SDK and unpacking its headers and binaries * for further processing by other tools. */ -int wmain(const int argc, const wchar_t** argv) { // NOLINT(*-use-internal-linkage) +int MAIN(const int argc, const TCHAR* argv[]) { // NOLINT(*-use-internal-linkage) if(argc == 1) { print_help(); return 0; diff --git a/tools/src/steamworks_parser.cpp b/tools/src/steamworks_parser.cpp index a5168e3..1002a9d 100644 --- a/tools/src/steamworks_parser.cpp +++ b/tools/src/steamworks_parser.cpp @@ -37,9 +37,9 @@ namespace { kb::parser::walk( root, - [&](const auto& current_node) { + [&](const ts::Node& current_node) { const auto current_type = current_node.getType(); - const auto current_value = current_node.getSourceRange(source); + // const auto current_value = current_node.getSourceRange(source); const auto current_sexpr = current_node.getSExpr(); if(current_type == "class_specifier") { @@ -101,7 +101,7 @@ namespace { if(preproc_node.getType() == "preproc_arg") { const auto quoted_version = preproc_node.getSourceRange(source); - const auto trimmed_version = koalabox::str::trim(quoted_version); + const auto trimmed_version = koalabox::str::trim(std::string(quoted_version)); interface_version = unquote_if_quoted(trimmed_version); LOG_DEBUG("Interface version: {}", interface_version); @@ -161,7 +161,7 @@ namespace { // Go over each file in headers directory for(const auto& entry : fs::directory_iterator(headers_dir)) { if(const auto& header_path = entry.path(); header_path.extension() == ".h") { - const auto task = pool.submit_task( + const auto task = pool.submit_task( // NOLINT(*-unused-local-non-trivial-variable) [&, header_path] { try { LOG_DEBUG("Parsing header: {}", kb::path::to_str(header_path)); @@ -227,7 +227,7 @@ namespace { * Optionally accepts a list of folder names that filters which sdk versions will be parsed. * No list means all versions will be parsed. */ -int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-linkage) +int MAIN(const int argc, const TCHAR* argv[]) { // NOLINT(*-use-internal-linkage) try { koalabox::logger::init_console_logger(); @@ -242,7 +242,7 @@ int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-link const auto steamworks_dir = fs::path("steamworks"); if(!fs::exists(steamworks_dir)) { - throw std::exception("Expected to find 'steamworks' in current working directory."); + throw std::runtime_error("Expected to find 'steamworks' in current working directory."); } const auto start = std::chrono::steady_clock::now(); @@ -251,7 +251,7 @@ int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-link const auto elapsed = duration_cast(end - start); LOG_INFO("Finished parsing steamworks in {} seconds", elapsed.count()); - } catch(std::exception& e) { + } catch(const std::exception& e) { LOG_CRITICAL("Error: {}", e.what()); return 1; }