13 Commits

Author SHA1 Message Date
acidicoala
4b337a981e Fixed store mode 2023-03-14 15:11:07 +04:00
acidicoala
9af76de3d2 Updated Readme [skip ci] 2023-03-14 00:35:49 +04:00
acidicoala
167701aee1 Allow hooking same interface address 2023-03-13 22:22:56 +04:00
acidicoala
a54279e150 Updated README 2023-03-12 20:40:38 +04:00
acidicoala
d9f477898c Updated KoalaBox 2023-03-12 04:33:04 +04:00
acidicoala
3c707f2474 Fixed koalabox globals 2023-03-11 21:41:59 +04:00
acidicoala
e1987515d8 Fixed koalabox calls 2023-03-11 19:05:48 +04:00
acidicoala
feb212e841 Updated Koalabox 2023-03-11 17:28:14 +04:00
acidicoala
98c61f6e46 Updated Koalabox with hook fixes 2023-03-11 17:11:32 +04:00
acidicoala
6b9cb115c3 Updated README [skip ci] 2023-02-15 18:45:51 +03:00
acidicoala
73a05f1d91 Fixed critical bugs 2023-02-01 01:26:05 +03:00
acidicoala
95ceac3d47 Fixed steam url 2023-01-30 14:43:25 +03:00
acidicoala
aa23be373d Updated README 2023-01-22 18:31:54 +03:00
26 changed files with 342 additions and 245 deletions

View File

@@ -4,7 +4,7 @@ on: push
jobs: jobs:
ci: ci:
name: CI name: CI
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@15d5cfc2e515bc72e47da6c0c563820cff98551f uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@acac7a4450414784f441dc55c52758f550f182ab
permissions: permissions:
contents: write contents: write
with: with:

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.24) cmake_minimum_required(VERSION 3.24)
project(SmokeAPI VERSION 2.0.0) project(SmokeAPI VERSION 2.0.5)
include(KoalaBox/cmake/KoalaBox.cmake) include(KoalaBox/cmake/KoalaBox.cmake)

View File

@@ -60,11 +60,11 @@ Usage of this unlocker entails breaking one or more terms of service, which migh
SmokeAPI supports 2 main modes of installation: *Store* mode and *Game* mode, which are described in the next section. SmokeAPI supports 2 main modes of installation: *Store* mode and *Game* mode, which are described in the next section.
NOTE: It is worth noting that the following instructions describe a _manual_ installation method.
You can benefit from _automatic_ installation and GUI configuration by using Koalageddon v2 (Coming soon).
=== 🛍️ Store mode === 🛍️ Store mode
NOTE: It is worth noting that the following instructions describe a _manual_ installation method.
You can benefit from _automatic_ installation and GUI configuration by using https://github.com/acidicoala/Koalageddon2#readme[Koalageddon v2].
In this installation mode, SmokeAPI is loaded into the Steam process, which makes it able to affect all Steam games. In this installation mode, SmokeAPI is loaded into the Steam process, which makes it able to affect all Steam games.
:steam-dir: the Steam directoryfootnote:fn-steam-dir[The root directory where Steam is installed] :steam-dir: the Steam directoryfootnote:fn-steam-dir[The root directory where Steam is installed]
@@ -140,7 +140,8 @@ The configuration file is expected to conform to the Json standard.
Type::: Boolean Type::: Boolean
Default::: `false` Default::: `false`
`unlock_family_sharing`:: Toggles Family Sharing bypass, which enables the borrower of a shared library to start and continue playing games when library owner is playing as well. `unlock_family_sharing`:: *_Store mode only_*.
Toggles Family Sharing bypass, which enables the borrower of a shared library to start and continue playing games when library owner is playing as well.
+ +
[horizontal] [horizontal]
Type::: Boolean Type::: Boolean
@@ -171,7 +172,7 @@ Default::: `{}`
Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the app status. Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the app status.
Possible app status values are defined in the `default_app_status` option. Possible app status values are defined in the `default_app_status` option.
Furthermore, it is possible to lock even the legitimately locked DLCs by setting the corresponding app status value to `locked`. Furthermore, it is possible to lock even the legitimately locked DLCs by setting the corresponding app status value to `locked`.
Type::: Object (Map of String to String) Type::: Object
Default::: `{}` Default::: `{}`
`auto_inject_inventory`:: Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory `auto_inject_inventory`:: Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory
@@ -200,17 +201,65 @@ Default::: `2`
[horizontal] [horizontal]
Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the object that contains DLC IDs. Possible values::: An object with key-value pairs, where the key corresponds to the app ID, and value to the object that contains DLC IDs.
The format is the same as in the aforementioned GitHub config. The format is the same as in the aforementioned GitHub config.
Type::: Object (Map of String to Object) Type::: Object
Default::: `{}` Default::: `{}`
`store_config`:: An object that specifies offsets required for store mode operation. `store_config`:: *_Store mode only_*.
An object that specifies offsets required for store mode operation.
It will override the config fetched from {steam_config}[remote source] or local cache. It will override the config fetched from {steam_config}[remote source] or local cache.
Do not modify this value unless you know what you are doing. Do not modify this value unless you know what you are doing.
+ +
[horizontal] [horizontal]
Type::: Object (Map of String to Integer) Type::: Object
Default::: See {steam_config}[online config] Default::: See {steam_config}[online config]
.Complete example
[%collapsible]
====
[source,json]
----
{
"$version": 2,
"logging": true,
"unlock_family_sharing": 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"
}
}
},
"store_config": {
"client_engine_steam_client_internal_ordinal": 12,
"steam_client_internal_interface_selector_ordinal": 18,
"vstdlib_callback_address_offset": 20,
"vstdlib_callback_data_offset": 0,
"vstdlib_callback_interceptor_address_offset": 1,
"vstdlib_callback_name_offset": 4
}
}
----
====
== Extra info == Extra info
=== How SmokeAPI works in games with large number of DLCs === How SmokeAPI works in games with large number of DLCs
@@ -261,14 +310,9 @@ For example:
.\build.ps1 32 Debug .\build.ps1 32 Debug
---- ----
== 👋 Acknowledgements == 📚 Open-Source libraries
SmokeAPI makes use of the following open source projects: This project makes use of the open source projects specified in the https://github.com/acidicoala/KoalaBox#-open-source-libraries[KoalaBox Readme]
* https://github.com/libcpr/cpr[C++ Requests]
* https://github.com/nlohmann/json[JSON for Modern C++]
* https://github.com/stevemk14ebr/PolyHook_2_0[PolyHook 2]
* https://github.com/gabime/spdlog[spdlog]
== 📄 License == 📄 License

View File

@@ -3,28 +3,10 @@
"logging": true, "logging": true,
"unlock_family_sharing": true, "unlock_family_sharing": true,
"default_app_status": "unlocked", "default_app_status": "unlocked",
"override_app_status": { "override_app_status": {},
"1234": "original", "override_dlc_status": {},
"4321": "unlocked"
},
"override_dlc_status": {
"1234": "original",
"4321": "unlocked",
"5678": "locked"
},
"auto_inject_inventory": true, "auto_inject_inventory": true,
"extra_inventory_items": [], "extra_inventory_items": [],
"extra_dlcs": { "extra_dlcs": {},
"1234": {
"dlcs": {
"56789": "Example DLC 1"
}
},
"4321": {
"dlcs": {
"98765": "Example DLC 2"
}
}
},
"store_config": null "store_config": null
} }

View File

@@ -7,12 +7,9 @@ constexpr auto KEY_APPS = "apps";
AppDlcNameMap get_cached_apps() noexcept { AppDlcNameMap get_cached_apps() noexcept {
try { try {
const auto cache = koalabox::cache::read_from_cache(KEY_APPS); return koalabox::cache::get(KEY_APPS).get<AppDlcNameMap>();
return cache.get<AppDlcNameMap>();
} catch (const Exception& e) { } catch (const Exception& e) {
LOG_WARN("Failed to get cached apps: {}", e.what()) LOG_WARN("Failed to get cached apps: {}", e.what())
return {}; return {};
} }
} }
@@ -41,7 +38,7 @@ namespace smoke_api::app_cache {
apps[std::to_string(app_id)] = App{.dlcs=DLC::get_dlc_map_from_vector(dlcs)}; apps[std::to_string(app_id)] = App{.dlcs=DLC::get_dlc_map_from_vector(dlcs)};
return koalabox::cache::save_to_cache(KEY_APPS, Json(apps)); return koalabox::cache::put(KEY_APPS, Json(apps));
} catch (const Exception& e) { } catch (const Exception& e) {
LOG_ERROR("Error saving DLCs to disk cache: {}", e.what()) LOG_ERROR("Error saving DLCs to disk cache: {}", e.what())

View File

@@ -15,7 +15,7 @@ namespace api {
try { try {
const auto* url = const auto* url =
"https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v2/dlc.json"; "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v2/dlc.json";
const auto json = koalabox::http_client::fetch_json(url); const auto json = koalabox::http_client::get_json(url);
const auto response = json.get<AppDlcNameMap>(); const auto response = json.get<AppDlcNameMap>();
return DLC::get_dlcs_from_apps(response, app_id); return DLC::get_dlcs_from_apps(response, app_id);
@@ -27,8 +27,10 @@ namespace api {
std::optional<Vector<DLC>> fetch_dlcs_from_steam(AppId_t app_id) noexcept { std::optional<Vector<DLC>> fetch_dlcs_from_steam(AppId_t app_id) noexcept {
try { try {
const auto url = fmt::format("https://store_mode.steampowered.com/dlc/{}/ajaxgetdlclist", app_id); // TODO: Communicate directly with Steam servers.
const auto json = koalabox::http_client::fetch_json(url); // ref.: https://github.com/SteamRE/SteamKit
const auto url = fmt::format("https://store.steampowered.com/dlc/{}/ajaxgetdlclist", app_id);
const auto json = koalabox::http_client::get_json(url);
const auto response = json.get<SteamResponse>(); const auto response = json.get<SteamResponse>();

View File

@@ -1,11 +1,8 @@
#include <core/globals.hpp>
namespace globals { namespace globals {
HMODULE smokeapi_handle = nullptr; HMODULE smokeapi_handle = nullptr;
HMODULE steamapi_module = nullptr; HMODULE steamapi_module = nullptr;
HMODULE vstdlib_module = nullptr; HMODULE vstdlib_module = nullptr;
HMODULE steamclient_module = nullptr; HMODULE steamclient_module = nullptr;
Map<String, uintptr_t> address_map; // NOLINT(cert-err58-cpp)
} }

View File

@@ -8,6 +8,5 @@ namespace globals {
extern HMODULE steamclient_module; extern HMODULE steamclient_module;
extern HMODULE steamapi_module; extern HMODULE steamapi_module;
extern HMODULE vstdlib_module; extern HMODULE vstdlib_module;
extern Map<String, uintptr_t> address_map;
} }

View File

@@ -2,6 +2,7 @@
#include <core/globals.hpp> #include <core/globals.hpp>
#include <koalabox/loader.hpp> #include <koalabox/loader.hpp>
// TODO: Refactor to KoalaBox
namespace paths { namespace paths {
Path get_self_path() { Path get_self_path() {

View File

@@ -45,17 +45,17 @@
#define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall #define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall
#define GET_ORIGINAL_HOOKED_FUNCTION(FUNC) \ #define GET_ORIGINAL_HOOKED_FUNCTION(FUNC) \
static const auto FUNC##_o = koalabox::hook::get_original_hooked_function(globals::address_map, #FUNC, FUNC); static const auto FUNC##_o = koalabox::hook::get_original_hooked_function(#FUNC, FUNC);
#define GET_ORIGINAL_FUNCTION_STEAMAPI(FUNC) \ #define GET_ORIGINAL_FUNCTION_STEAMAPI(FUNC) \
static const auto FUNC##_o = koalabox::hook::get_original_function(globals::steamapi_module, #FUNC, FUNC); static const auto FUNC##_o = koalabox::hook::get_original_function(globals::steamapi_module, #FUNC, FUNC);
#define DETOUR_ADDRESS(FUNC, ADDRESS) \ #define DETOUR_ADDRESS(FUNC, ADDRESS) \
koalabox::hook::detour_or_warn(globals::address_map, ADDRESS, #FUNC, reinterpret_cast<uintptr_t>(FUNC)); koalabox::hook::detour_or_warn(ADDRESS, #FUNC, reinterpret_cast<uintptr_t>(FUNC));
#define $DETOUR(FUNC, NAME, MODULE_HANDLE) \ #define $DETOUR(FUNC, NAME, MODULE_HANDLE) \
koalabox::hook::detour_or_warn(globals::address_map, MODULE_HANDLE, NAME, reinterpret_cast<uintptr_t>(FUNC)); koalabox::hook::detour_or_warn(MODULE_HANDLE, NAME, reinterpret_cast<uintptr_t>(FUNC));
#define DETOUR_STEAMCLIENT(FUNC) $DETOUR(FUNC, #FUNC, globals::steamclient_module) #define DETOUR_STEAMCLIENT(FUNC) $DETOUR(FUNC, #FUNC, globals::steamclient_module)
#define DETOUR_VSTDLIB(FUNC) $DETOUR(vstdlib::FUNC, #FUNC, globals::vstdlib_module) #define DETOUR_VSTDLIB(FUNC) $DETOUR(vstdlib::FUNC, #FUNC, globals::vstdlib_module)

View File

@@ -8,33 +8,51 @@
// ISteamApps // ISteamApps
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(void* self, AppId_t dlcID) { DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(void* self, AppId_t dlcID) {
return steam_apps::IsDlcUnlocked( try {
__func__, 0, dlcID, [&]() { static const auto app_id = steam_impl::get_app_id_or_throw();
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsSubscribedApp) return steam_apps::IsDlcUnlocked(
__func__, app_id, dlcID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsSubscribedApp)
return SteamAPI_ISteamApps_BIsSubscribedApp_o(self, dlcID); return SteamAPI_ISteamApps_BIsSubscribedApp_o(self, dlcID);
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
} }
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(void* self, AppId_t dlcID) { DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsDlcInstalled(void* self, AppId_t dlcID) {
return steam_apps::IsDlcUnlocked( try {
__func__, 0, dlcID, [&]() { static const auto app_id = steam_impl::get_app_id_or_throw();
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsDlcInstalled) return steam_apps::IsDlcUnlocked(
__func__, app_id, dlcID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsDlcInstalled)
return SteamAPI_ISteamApps_BIsDlcInstalled_o(self, dlcID); return SteamAPI_ISteamApps_BIsDlcInstalled_o(self, dlcID);
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
} }
DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(void* self) { DLL_EXPORT(int) SteamAPI_ISteamApps_GetDLCCount(void* self) {
return steam_apps::GetDLCCount( try {
__func__, 0, [&]() { static const auto app_id = steam_impl::get_app_id_or_throw();
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_GetDLCCount) return steam_apps::GetDLCCount(
__func__, app_id, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_GetDLCCount)
return SteamAPI_ISteamApps_GetDLCCount_o(self); return SteamAPI_ISteamApps_GetDLCCount_o(self);
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return 0;
}
} }
DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex( DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(
@@ -45,19 +63,24 @@ DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(
char* pchName, char* pchName,
int cchNameBufferSize int cchNameBufferSize
) { ) {
return steam_apps::GetDLCDataByIndex( try {
__func__, 0, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize, static const auto app_id = steam_impl::get_app_id_or_throw();
[&]() { return steam_apps::GetDLCDataByIndex(
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BGetDLCDataByIndex) __func__, app_id, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
[&]() {
return SteamAPI_ISteamApps_BGetDLCDataByIndex_o( GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BGetDLCDataByIndex)
self, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize return SteamAPI_ISteamApps_BGetDLCDataByIndex_o(
); self, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize
}, );
[&](AppId_t dlc_id) { },
return SteamAPI_ISteamApps_BIsDlcInstalled(self, dlc_id); [&](AppId_t dlc_id) {
} return SteamAPI_ISteamApps_BIsDlcInstalled(self, dlc_id);
); }
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
} }
// ISteamClient // ISteamClient
@@ -68,13 +91,17 @@ DLL_EXPORT(void*) SteamAPI_ISteamClient_GetISteamGenericInterface(
HSteamPipe hSteamPipe, HSteamPipe hSteamPipe,
const char* pchVersion const char* pchVersion
) { ) {
return steam_client::GetGenericInterface( try {
__func__, pchVersion, [&]() { return steam_client::GetGenericInterface(
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamClient_GetISteamGenericInterface) __func__, pchVersion, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamClient_GetISteamGenericInterface)
return SteamAPI_ISteamClient_GetISteamGenericInterface_o(self, hSteamUser, hSteamPipe, pchVersion); return SteamAPI_ISteamClient_GetISteamGenericInterface_o(self, hSteamUser, hSteamPipe, pchVersion);
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
} }
// ISteamInventory // ISteamInventory
@@ -210,18 +237,18 @@ DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp
CSteamID steamID, CSteamID steamID,
AppId_t dlcID AppId_t dlcID
) { ) {
AppId_t app_id;
try { try {
app_id = steam_impl::get_app_id_or_throw(); static const auto app_id = steam_impl::get_app_id_or_throw();
return steam_user::UserHasLicenseForApp(
__func__, app_id, dlcID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamUser_UserHasLicenseForApp)
return SteamAPI_ISteamUser_UserHasLicenseForApp_o(self, steamID, dlcID);
}
);
} catch (const Exception& e) { } catch (const Exception& e) {
LOG_ERROR("{} -> Error getting app id: {}", __func__, e.what()) LOG_ERROR("{} -> Error: {}", __func__, e.what())
return k_EUserHasLicenseResultDoesNotHaveLicense;
} }
return steam_user::UserHasLicenseForApp(
__func__, app_id, dlcID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamUser_UserHasLicenseForApp)
return SteamAPI_ISteamUser_UserHasLicenseForApp_o(self, steamID, dlcID);
}
);
} }

View File

@@ -1,56 +1,81 @@
#include <game_mode/virtuals/steam_api_virtuals.hpp> #include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <koalabox/util.hpp>
#include <steam_impl/steam_apps.hpp> #include <steam_impl/steam_apps.hpp>
#include <steam_impl/steam_impl.hpp>
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) { VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t dlc_id)) {
return steam_apps::IsDlcUnlocked( try {
__func__, 0, appID, [&]() { static const auto app_id = steam_impl::get_app_id_or_throw();
GET_ORIGINAL_FUNCTION_STEAMAPI(ISteamApps_BIsSubscribedApp) return steam_apps::IsDlcUnlocked(
__func__, app_id, dlc_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BIsSubscribedApp)
return ISteamApps_BIsSubscribedApp_o(ARGS(dlc_id));
}
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return ISteamApps_BIsSubscribedApp_o(ARGS(appID)); return false;
} }
);
} }
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t appID)) { VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t dlc_id)) {
return steam_apps::IsDlcUnlocked( try {
__func__, 0, appID, [&]() { static const auto app_id = steam_impl::get_app_id_or_throw();
GET_ORIGINAL_FUNCTION_STEAMAPI(ISteamApps_BIsDlcInstalled) return steam_apps::IsDlcUnlocked(
__func__, app_id, dlc_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BIsDlcInstalled)
return ISteamApps_BIsDlcInstalled_o(ARGS(dlc_id));
}
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return ISteamApps_BIsDlcInstalled_o(ARGS(appID)); return false;
} }
);
} }
VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS()) { VIRTUAL(int) ISteamApps_GetDLCCount(PARAMS()) {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_GetDLCCount) try {
static const auto app_id = steam_impl::get_app_id_or_throw();
return steam_apps::GetDLCCount(
__func__, app_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_GetDLCCount)
return ISteamApps_GetDLCCount_o(ARGS());
}
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return steam_apps::GetDLCCount( return 0;
__func__, 0, [&]() { }
return ISteamApps_GetDLCCount_o(ARGS());
}
);
} }
VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex( VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(
PARAMS( PARAMS(
int iDLC, int iDLC,
AppId_t* pAppID, AppId_t* p_dlc_id,
bool* pbAvailable, bool* pbAvailable,
char* pchName, char* pchName,
int cchNameBufferSize int cchNameBufferSize
) )
) { ) {
return steam_apps::GetDLCDataByIndex( try {
__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize, static const auto app_id = steam_impl::get_app_id_or_throw();
[&]() { return steam_apps::GetDLCDataByIndex(
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BGetDLCDataByIndex) __func__, app_id, iDLC, p_dlc_id, pbAvailable, pchName, cchNameBufferSize,
[&]() {
return ISteamApps_BGetDLCDataByIndex_o( GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BGetDLCDataByIndex)
ARGS(iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize) return ISteamApps_BGetDLCDataByIndex_o(
); ARGS(iDLC, p_dlc_id, pbAvailable, pchName, cchNameBufferSize)
}, );
[&](AppId_t dlc_id) { },
return ISteamApps_BIsDlcInstalled(ARGS(dlc_id)); [&](AppId_t dlc_id) {
} return ISteamApps_BIsDlcInstalled(ARGS(dlc_id));
); }
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
} }

View File

@@ -8,13 +8,17 @@ VIRTUAL(void*) ISteamClient_GetISteamApps(
const char* version const char* version
) )
) { ) {
return steam_client::GetGenericInterface( try {
__func__, version, [&]() { return steam_client::GetGenericInterface(
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamApps) __func__, version, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamApps)
return ISteamClient_GetISteamApps_o(ARGS(hSteamUser, hSteamPipe, version)); return ISteamClient_GetISteamApps_o(ARGS(hSteamUser, hSteamPipe, version));
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
} }
VIRTUAL(void*) ISteamClient_GetISteamUser( VIRTUAL(void*) ISteamClient_GetISteamUser(
@@ -24,13 +28,18 @@ VIRTUAL(void*) ISteamClient_GetISteamUser(
const char* version const char* version
) )
) { ) {
return steam_client::GetGenericInterface( try {
__func__, version, [&]() { return steam_client::GetGenericInterface(
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamUser) __func__, version, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamUser)
return ISteamClient_GetISteamUser_o(ARGS(hSteamUser, hSteamPipe, version)); return ISteamClient_GetISteamUser_o(ARGS(hSteamUser, hSteamPipe, version));
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
} }
VIRTUAL(void*) ISteamClient_GetISteamGenericInterface( VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(
@@ -40,13 +49,18 @@ VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(
const char* pchVersion const char* pchVersion
) )
) { ) {
return steam_client::GetGenericInterface( try {
__func__, pchVersion, [&]() { return steam_client::GetGenericInterface(
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamGenericInterface) __func__, pchVersion, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamGenericInterface)
return ISteamClient_GetISteamGenericInterface_o(ARGS(hSteamUser, hSteamPipe, pchVersion)); return ISteamClient_GetISteamGenericInterface_o(ARGS(hSteamUser, hSteamPipe, pchVersion));
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
} }
VIRTUAL(void*) ISteamClient_GetISteamInventory( VIRTUAL(void*) ISteamClient_GetISteamInventory(
@@ -56,11 +70,16 @@ VIRTUAL(void*) ISteamClient_GetISteamInventory(
const char* pchVersion const char* pchVersion
) )
) { ) {
return steam_client::GetGenericInterface( try {
__func__, pchVersion, [&]() { return steam_client::GetGenericInterface(
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamInventory) __func__, pchVersion, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamInventory)
return ISteamClient_GetISteamInventory_o(ARGS(hSteamUser, hSteamPipe, pchVersion)); return ISteamClient_GetISteamInventory_o(ARGS(hSteamUser, hSteamPipe, pchVersion));
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
} }

View File

@@ -3,19 +3,18 @@
#include <steam_impl/steam_impl.hpp> #include <steam_impl/steam_impl.hpp>
#include <koalabox/logger.hpp> #include <koalabox/logger.hpp>
VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID steamID, AppId_t dlcID)) { VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID steamID, AppId_t dlc_id)) {
AppId_t app_id = 0;
try { try {
app_id = steam_impl::get_app_id_or_throw(); static const auto app_id = steam_impl::get_app_id_or_throw();
return steam_user::UserHasLicenseForApp(
__func__, app_id, dlc_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamUser_UserHasLicenseForApp)
return ISteamUser_UserHasLicenseForApp_o(ARGS(steamID, dlc_id));
}
);
} catch (const Exception& e) { } catch (const Exception& e) {
LOG_ERROR("{} -> Error getting app id: {}", __func__, e.what()) LOG_ERROR("{} -> Error: {}", __func__, e.what())
return k_EUserHasLicenseResultDoesNotHaveLicense;
} }
return steam_user::UserHasLicenseForApp(
__func__, app_id, dlcID, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamUser_UserHasLicenseForApp)
return ISteamUser_UserHasLicenseForApp_o(ARGS(steamID, dlcID));
}
);
} }

View File

@@ -7,6 +7,7 @@
namespace smoke_api::config { namespace smoke_api::config {
Config instance; // NOLINT(cert-err58-cpp) Config instance; // NOLINT(cert-err58-cpp)
// TODO: Refactor to Koalabox
void init_config() { void init_config() {
const auto path = paths::get_config_path(); const auto path = paths::get_config_path();

View File

@@ -4,14 +4,13 @@
#include <core/globals.hpp> #include <core/globals.hpp>
#include <core/paths.hpp> #include <core/paths.hpp>
#include <common/steamclient_exports.hpp> #include <common/steamclient_exports.hpp>
#include <koalabox/globals.hpp>
#include <koalabox/dll_monitor.hpp> #include <koalabox/dll_monitor.hpp>
#include <koalabox/logger.hpp> #include <koalabox/logger.hpp>
#include <koalabox/hook.hpp> #include <koalabox/hook.hpp>
#include <koalabox/cache.hpp>
#include <koalabox/loader.hpp> #include <koalabox/loader.hpp>
#include <koalabox/win_util.hpp> #include <koalabox/win_util.hpp>
#include <koalabox/util.hpp> #include <koalabox/util.hpp>
//#include <steam_api_exports/steam_api_exports.hpp>
#if COMPILE_STORE_MODE #if COMPILE_STORE_MODE
#include <store_mode/store.hpp> #include <store_mode/store.hpp>
@@ -81,6 +80,8 @@ namespace smoke_api {
try { try {
DisableThreadLibraryCalls(module_handle); DisableThreadLibraryCalls(module_handle);
koalabox::globals::init_globals(module_handle, PROJECT_NAME);
globals::smokeapi_handle = module_handle; globals::smokeapi_handle = module_handle;
config::init_config(); config::init_config();
@@ -89,12 +90,10 @@ namespace smoke_api {
koalabox::logger::init_file_logger(paths::get_log_path()); koalabox::logger::init_file_logger(paths::get_log_path());
} }
// This kind of timestamp is reliable on for CI builds, as it will reflect the compilation // This kind of timestamp is reliable only for CI builds, as it will reflect the compilation
// time stamp only when this file gets recompiled. // time stamp only when this file gets recompiled.
LOG_INFO("🐨 {} v{} | Compiled at '{}'", PROJECT_NAME, PROJECT_VERSION, __TIMESTAMP__) LOG_INFO("🐨 {} v{} | Compiled at '{}'", PROJECT_NAME, PROJECT_VERSION, __TIMESTAMP__)
koalabox::cache::init_cache(paths::get_cache_path());
const auto exe_path = Path(koalabox::win_util::get_module_file_name_or_throw(nullptr)); const auto exe_path = Path(koalabox::win_util::get_module_file_name_or_throw(nullptr));
const auto exe_name = exe_path.filename().string(); const auto exe_name = exe_path.filename().string();

View File

@@ -25,20 +25,13 @@ namespace steam_apps {
* @return boolean indicating if the function was able to successfully fetch DLC IDs from all sources. * @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) { void fetch_and_cache_dlcs(AppId_t app_id) {
static std::mutex mutex; static Mutex mutex;
const std::lock_guard<std::mutex> guard(mutex); const MutexLockGuard guard(mutex);
if (not app_id) { if (app_id == 0) {
// No app id means we are operating in game mode. LOG_ERROR("{} -> App ID is 0", __func__)
// Hence, we need to use utility functions to get app id. app_dlcs[app_id] = {}; // Dummy value to avoid checking for presence on each access
try { return;
app_id = steam_impl::get_app_id_or_throw();
LOG_INFO("Detected App ID: {}", app_id)
} catch (const Exception& ex) {
LOG_ERROR("Failed to get app ID: {}", ex.what())
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 // We want to fetch data only once. However, if any of the remote sources have failed
@@ -75,7 +68,6 @@ namespace steam_apps {
} }
// Cache DLCs in memory and cache for future use // Cache DLCs in memory and cache for future use
app_dlcs[app_id] = aggregated_dlcs; app_dlcs[app_id] = aggregated_dlcs;
smoke_api::app_cache::save_dlcs(app_id, aggregated_dlcs); smoke_api::app_cache::save_dlcs(app_id, aggregated_dlcs);

View File

@@ -18,8 +18,8 @@ namespace steam_apps {
); );
bool GetDLCDataByIndex( bool GetDLCDataByIndex(
const String& dlc_id, const String& function_name,
AppId_t dlc_ids, AppId_t app_id,
int iDLC, int iDLC,
AppId_t* pDlcId, AppId_t* pDlcId,
bool* pbAvailable, bool* pbAvailable,

View File

@@ -1,7 +1,7 @@
#include <ranges>
#include <steam_impl/steam_impl.hpp> #include <steam_impl/steam_impl.hpp>
#include <game_mode/virtuals/steam_api_virtuals.hpp> #include <game_mode/virtuals/steam_api_virtuals.hpp>
#include <common/steamclient_exports.hpp> #include <common/steamclient_exports.hpp>
#include <core/globals.hpp>
#include <build_config.h> #include <build_config.h>
#include <koalabox/util.hpp> #include <koalabox/util.hpp>
#include <koalabox/win_util.hpp> #include <koalabox/win_util.hpp>
@@ -21,7 +21,7 @@ namespace steam_impl {
{ {
{6, 16}, {6, 16},
{7, 18}, {7, 18},
{8, 15}, {8, 15},
{9, 16}, {9, 16},
{12, 15}, {12, 15},
} }
@@ -114,9 +114,7 @@ namespace steam_impl {
int get_ordinal(const FunctionOrdinalMap& ordinal_map, const String& function_name, int interface_version) { int get_ordinal(const FunctionOrdinalMap& ordinal_map, const String& function_name, int interface_version) {
const auto& map = ordinal_map.at(function_name); const auto& map = ordinal_map.at(function_name);
for (auto it = map.rbegin(); it != map.rend(); it++) { for (auto [version, ordinal]: std::ranges::reverse_view(map)) {
const auto [version, ordinal] = *it;
if (interface_version >= version) { if (interface_version >= version) {
return ordinal; return ordinal;
} }
@@ -127,7 +125,6 @@ namespace steam_impl {
#define HOOK_VIRTUALS(MAP, FUNC) \ #define HOOK_VIRTUALS(MAP, FUNC) \
koalabox::hook::swap_virtual_func( \ koalabox::hook::swap_virtual_func( \
globals::address_map, \
interface, \ interface, \
#FUNC, \ #FUNC, \
get_ordinal(MAP, #FUNC, version_number), \ get_ordinal(MAP, #FUNC, version_number), \
@@ -148,8 +145,7 @@ namespace steam_impl {
static Set<void*> hooked_interfaces; static Set<void*> hooked_interfaces;
if (hooked_interfaces.contains(interface)) { if (hooked_interfaces.contains(interface)) {
// This interface is already hooked. Skipping it. LOG_DEBUG("Interface {} at {} has already been hooked.", version_string, interface)
return;
} }
static Mutex section; static Mutex section;
@@ -199,7 +195,7 @@ namespace steam_impl {
if (version_number >= 2) { if (version_number >= 2) {
HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItemProperty) HOOK_STEAM_INVENTORY(ISteamInventory_GetResultItemProperty)
} }
} else if (version_string.starts_with(CLIENT_ENGINE)) { } else if (version_string.starts_with(CLIENT_ENGINE) && !hooked_interfaces.contains(interface)) {
#if COMPILE_STORE_MODE #if COMPILE_STORE_MODE
store::steamclient::process_client_engine(reinterpret_cast<uintptr_t>(interface)); store::steamclient::process_client_engine(reinterpret_cast<uintptr_t>(interface));
#endif #endif

View File

@@ -2,11 +2,16 @@
#include <steam_impl/steam_apps.hpp> #include <steam_impl/steam_apps.hpp>
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t app_id, AppId_t dlc_id)) { VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t app_id, AppId_t dlc_id)) {
return steam_apps::IsDlcUnlocked( try {
__func__, app_id, dlc_id, [&]() { return steam_apps::IsDlcUnlocked(
GET_ORIGINAL_HOOKED_FUNCTION(IClientAppManager_IsAppDlcInstalled) __func__, app_id, dlc_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientAppManager_IsAppDlcInstalled)
return IClientAppManager_IsAppDlcInstalled_o(ARGS(app_id, dlc_id)); return IClientAppManager_IsAppDlcInstalled_o(ARGS(app_id, dlc_id));
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
} }

View File

@@ -2,13 +2,18 @@
#include <steam_impl/steam_apps.hpp> #include <steam_impl/steam_apps.hpp>
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) { VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) {
return steam_apps::GetDLCCount( try {
__func__, appId, [&]() { return steam_apps::GetDLCCount(
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_GetDLCCount) __func__, appId, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_GetDLCCount)
return IClientApps_GetDLCCount_o(ARGS(appId)); return IClientApps_GetDLCCount_o(ARGS(appId));
} }
); );
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return 0;
}
} }
VIRTUAL(bool) IClientApps_BGetDLCDataByIndex( VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(
@@ -21,24 +26,29 @@ VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(
int cchNameBufferSize int cchNameBufferSize
) )
) { ) {
return steam_apps::GetDLCDataByIndex( try {
__func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize, return steam_apps::GetDLCDataByIndex(
[&]() { __func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_BGetDLCDataByIndex) [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_BGetDLCDataByIndex)
return IClientApps_BGetDLCDataByIndex_o( return IClientApps_BGetDLCDataByIndex_o(
ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize) ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize)
); );
}, },
[&](AppId_t dlc_id) { [&](AppId_t dlc_id) {
const auto* app_manager_interface = store::steamclient::interface_name_to_address_map["IClientAppManager"]; const auto* app_manager_interface = store::steamclient::interface_name_to_address_map["IClientAppManager"];
if (app_manager_interface) { if (app_manager_interface) {
IClientAppManager_IsAppDlcInstalled(app_manager_interface, EDX, appID, dlc_id); IClientAppManager_IsAppDlcInstalled(app_manager_interface, EDX, appID, dlc_id);
return true; return true;
}
// Would never happen in practice, as the interfaces would be instantiated almost simultaneously
return false;
} }
);
// Would never happen in practice, as the interfaces would be instantiated almost simultaneously } catch (const Exception& e) {
return false; LOG_ERROR("{} -> Error: {}", __func__, e.what())
} return false;
); }
} }

View File

@@ -2,13 +2,18 @@
#include <steam_impl/steam_apps.hpp> #include <steam_impl/steam_apps.hpp>
VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t dlc_id)) { VIRTUAL(bool) IClientUser_BIsSubscribedApp(PARAMS(AppId_t dlc_id)) {
const auto* utils_interface = store::steamclient::interface_name_to_address_map["IClientUtils"]; try {
const auto* utils_interface = store::steamclient::interface_name_to_address_map["IClientUtils"];
const auto app_id = utils_interface ? IClientUtils_GetAppID(utils_interface, EDX) : 0; const auto app_id = utils_interface ? IClientUtils_GetAppID(utils_interface, EDX) : 0;
return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id, [&]() { return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientUser_BIsSubscribedApp) GET_ORIGINAL_HOOKED_FUNCTION(IClientUser_BIsSubscribedApp)
return IClientUser_BIsSubscribedApp_o(ARGS(dlc_id)); return IClientUser_BIsSubscribedApp_o(ARGS(dlc_id));
}); });
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
} }

View File

@@ -35,7 +35,6 @@ namespace store::steamclient {
construct_ordinal_map(#INTERFACE, ordinal_map[#INTERFACE], function_selector_address); construct_ordinal_map(#INTERFACE, ordinal_map[#INTERFACE], function_selector_address);
#define HOOK_FUNCTION(INTERFACE, FUNC) hook::swap_virtual_func_or_throw( \ #define HOOK_FUNCTION(INTERFACE, FUNC) hook::swap_virtual_func_or_throw( \
globals::address_map, \
interface, \ interface, \
#INTERFACE"_"#FUNC, \ #INTERFACE"_"#FUNC, \
ordinal_map[#INTERFACE][#FUNC], \ ordinal_map[#INTERFACE][#FUNC], \

View File

@@ -7,7 +7,7 @@ namespace store::api {
try { try {
const String url = const String url =
"https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v2/store_config.json"; "https://raw.githubusercontent.com/acidicoala/public-entitlements/main/steam/v2/store_config.json";
const auto kg_config_json = koalabox::http_client::fetch_json(url); const auto kg_config_json = koalabox::http_client::get_json(url);
return kg_config_json.get<StoreConfig>(); return kg_config_json.get<StoreConfig>();
} catch (const Exception& e) { } catch (const Exception& e) {

View File

@@ -7,9 +7,7 @@ namespace store::store_cache {
std::optional<StoreConfig> get_store_config() { std::optional<StoreConfig> get_store_config() {
try { try {
const auto config_json = koalabox::cache::read_from_cache(KEY_KG_CONFIG); return koalabox::cache::get(KEY_KG_CONFIG, Json(nullptr)).get<StoreConfig>();
return config_json.get<StoreConfig>();
} catch (const Exception& e) { } catch (const Exception& e) {
LOG_ERROR("Failed to get cached store_mode config: {}", e.what()) LOG_ERROR("Failed to get cached store_mode config: {}", e.what())
@@ -21,7 +19,7 @@ namespace store::store_cache {
try { try {
LOG_DEBUG("Caching store_mode config") LOG_DEBUG("Caching store_mode config")
return koalabox::cache::save_to_cache(KEY_KG_CONFIG, Json(config)); return koalabox::cache::put(KEY_KG_CONFIG, Json(config));
} catch (const Exception& e) { } catch (const Exception& e) {
LOG_ERROR("Failed to cache store_mode config: {}", e.what()) LOG_ERROR("Failed to cache store_mode config: {}", e.what())