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:
ci:
name: CI
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@15d5cfc2e515bc72e47da6c0c563820cff98551f
uses: acidicoala/KoalaBox/.github/workflows/build-and-package.yml@acac7a4450414784f441dc55c52758f550f182ab
permissions:
contents: write
with:

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.24)
project(SmokeAPI VERSION 2.0.0)
project(SmokeAPI VERSION 2.0.5)
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.
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
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.
: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
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]
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 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`.
Type::: Object (Map of String to String)
Type::: Object
Default::: `{}`
`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]
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.
Type::: Object (Map of String to Object)
Type::: Object
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.
Do not modify this value unless you know what you are doing.
+
[horizontal]
Type::: Object (Map of String to Integer)
Type::: Object
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
=== How SmokeAPI works in games with large number of DLCs
@@ -261,14 +310,9 @@ For example:
.\build.ps1 32 Debug
----
== 👋 Acknowledgements
== 📚 Open-Source libraries
SmokeAPI makes use of the following open source projects:
* 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]
This project makes use of the open source projects specified in the https://github.com/acidicoala/KoalaBox#-open-source-libraries[KoalaBox Readme]
== 📄 License

View File

@@ -3,28 +3,10 @@
"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"
},
"override_app_status": {},
"override_dlc_status": {},
"auto_inject_inventory": true,
"extra_inventory_items": [],
"extra_dlcs": {
"1234": {
"dlcs": {
"56789": "Example DLC 1"
}
},
"4321": {
"dlcs": {
"98765": "Example DLC 2"
}
}
},
"extra_dlcs": {},
"store_config": null
}

View File

@@ -7,12 +7,9 @@ constexpr auto KEY_APPS = "apps";
AppDlcNameMap get_cached_apps() noexcept {
try {
const auto cache = koalabox::cache::read_from_cache(KEY_APPS);
return cache.get<AppDlcNameMap>();
return koalabox::cache::get(KEY_APPS).get<AppDlcNameMap>();
} catch (const Exception& e) {
LOG_WARN("Failed to get cached apps: {}", e.what())
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)};
return koalabox::cache::save_to_cache(KEY_APPS, Json(apps));
return koalabox::cache::put(KEY_APPS, Json(apps));
} catch (const Exception& e) {
LOG_ERROR("Error saving DLCs to disk cache: {}", e.what())

View File

@@ -15,7 +15,7 @@ namespace api {
try {
const auto* url =
"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>();
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 {
try {
const auto url = fmt::format("https://store_mode.steampowered.com/dlc/{}/ajaxgetdlclist", app_id);
const auto json = koalabox::http_client::fetch_json(url);
// TODO: Communicate directly with Steam servers.
// 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>();

View File

@@ -1,11 +1,8 @@
#include <core/globals.hpp>
namespace globals {
HMODULE smokeapi_handle = nullptr;
HMODULE steamapi_module = nullptr;
HMODULE vstdlib_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 steamapi_module;
extern HMODULE vstdlib_module;
extern Map<String, uintptr_t> address_map;
}

View File

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

View File

@@ -45,17 +45,17 @@
#define VIRTUAL(TYPE) __declspec(noinline) TYPE __fastcall
#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) \
static const auto FUNC##_o = koalabox::hook::get_original_function(globals::steamapi_module, #FUNC, FUNC);
#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) \
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_VSTDLIB(FUNC) $DETOUR(vstdlib::FUNC, #FUNC, globals::vstdlib_module)

View File

@@ -8,33 +8,51 @@
// ISteamApps
DLL_EXPORT(bool) SteamAPI_ISteamApps_BIsSubscribedApp(void* self, AppId_t dlcID) {
return steam_apps::IsDlcUnlocked(
__func__, 0, dlcID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsSubscribedApp)
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
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) {
return steam_apps::IsDlcUnlocked(
__func__, 0, dlcID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BIsDlcInstalled)
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
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) {
return steam_apps::GetDLCCount(
__func__, 0, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_GetDLCCount)
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
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(
@@ -45,19 +63,24 @@ DLL_EXPORT(bool) SteamAPI_ISteamApps_BGetDLCDataByIndex(
char* pchName,
int cchNameBufferSize
) {
return steam_apps::GetDLCDataByIndex(
__func__, 0, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
[&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BGetDLCDataByIndex)
return SteamAPI_ISteamApps_BGetDLCDataByIndex_o(
self, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize
);
},
[&](AppId_t dlc_id) {
return SteamAPI_ISteamApps_BIsDlcInstalled(self, dlc_id);
}
);
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
return steam_apps::GetDLCDataByIndex(
__func__, app_id, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
[&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamApps_BGetDLCDataByIndex)
return SteamAPI_ISteamApps_BGetDLCDataByIndex_o(
self, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize
);
},
[&](AppId_t dlc_id) {
return SteamAPI_ISteamApps_BIsDlcInstalled(self, dlc_id);
}
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
}
// ISteamClient
@@ -68,13 +91,17 @@ DLL_EXPORT(void*) SteamAPI_ISteamClient_GetISteamGenericInterface(
HSteamPipe hSteamPipe,
const char* pchVersion
) {
return steam_client::GetGenericInterface(
__func__, pchVersion, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamClient_GetISteamGenericInterface)
return SteamAPI_ISteamClient_GetISteamGenericInterface_o(self, hSteamUser, hSteamPipe, pchVersion);
}
);
try {
return steam_client::GetGenericInterface(
__func__, pchVersion, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(SteamAPI_ISteamClient_GetISteamGenericInterface)
return SteamAPI_ISteamClient_GetISteamGenericInterface_o(self, hSteamUser, hSteamPipe, pchVersion);
}
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
}
// ISteamInventory
@@ -210,18 +237,18 @@ DLL_EXPORT(EUserHasLicenseForAppResult) SteamAPI_ISteamUser_UserHasLicenseForApp
CSteamID steamID,
AppId_t dlcID
) {
AppId_t app_id;
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) {
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 <koalabox/util.hpp>
#include <steam_impl/steam_apps.hpp>
#include <steam_impl/steam_impl.hpp>
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t appID)) {
return steam_apps::IsDlcUnlocked(
__func__, 0, appID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(ISteamApps_BIsSubscribedApp)
VIRTUAL(bool) ISteamApps_BIsSubscribedApp(PARAMS(AppId_t dlc_id)) {
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
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)) {
return steam_apps::IsDlcUnlocked(
__func__, 0, appID, [&]() {
GET_ORIGINAL_FUNCTION_STEAMAPI(ISteamApps_BIsDlcInstalled)
VIRTUAL(bool) ISteamApps_BIsDlcInstalled(PARAMS(AppId_t dlc_id)) {
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
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()) {
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(
__func__, 0, [&]() {
return ISteamApps_GetDLCCount_o(ARGS());
}
);
return 0;
}
}
VIRTUAL(bool) ISteamApps_BGetDLCDataByIndex(
PARAMS(
int iDLC,
AppId_t* pAppID,
AppId_t* p_dlc_id,
bool* pbAvailable,
char* pchName,
int cchNameBufferSize
)
) {
return steam_apps::GetDLCDataByIndex(
__func__, 0, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize,
[&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BGetDLCDataByIndex)
return ISteamApps_BGetDLCDataByIndex_o(
ARGS(iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize)
);
},
[&](AppId_t dlc_id) {
return ISteamApps_BIsDlcInstalled(ARGS(dlc_id));
}
);
try {
static const auto app_id = steam_impl::get_app_id_or_throw();
return steam_apps::GetDLCDataByIndex(
__func__, app_id, iDLC, p_dlc_id, pbAvailable, pchName, cchNameBufferSize,
[&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamApps_BGetDLCDataByIndex)
return ISteamApps_BGetDLCDataByIndex_o(
ARGS(iDLC, p_dlc_id, pbAvailable, pchName, cchNameBufferSize)
);
},
[&](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
)
) {
return steam_client::GetGenericInterface(
__func__, version, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamApps)
return ISteamClient_GetISteamApps_o(ARGS(hSteamUser, hSteamPipe, version));
}
);
try {
return steam_client::GetGenericInterface(
__func__, version, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamApps)
return ISteamClient_GetISteamApps_o(ARGS(hSteamUser, hSteamPipe, version));
}
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return nullptr;
}
}
VIRTUAL(void*) ISteamClient_GetISteamUser(
@@ -24,13 +28,18 @@ VIRTUAL(void*) ISteamClient_GetISteamUser(
const char* version
)
) {
return steam_client::GetGenericInterface(
__func__, version, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamUser)
try {
return steam_client::GetGenericInterface(
__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(
@@ -40,13 +49,18 @@ VIRTUAL(void*) ISteamClient_GetISteamGenericInterface(
const char* pchVersion
)
) {
return steam_client::GetGenericInterface(
__func__, pchVersion, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamGenericInterface)
try {
return steam_client::GetGenericInterface(
__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(
@@ -56,11 +70,16 @@ VIRTUAL(void*) ISteamClient_GetISteamInventory(
const char* pchVersion
)
) {
return steam_client::GetGenericInterface(
__func__, pchVersion, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(ISteamClient_GetISteamInventory)
try {
return steam_client::GetGenericInterface(
__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 <koalabox/logger.hpp>
VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID steamID, AppId_t dlcID)) {
AppId_t app_id = 0;
VIRTUAL(EUserHasLicenseForAppResult) ISteamUser_UserHasLicenseForApp(PARAMS(CSteamID steamID, AppId_t dlc_id)) {
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) {
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 {
Config instance; // NOLINT(cert-err58-cpp)
// TODO: Refactor to Koalabox
void init_config() {
const auto path = paths::get_config_path();

View File

@@ -4,14 +4,13 @@
#include <core/globals.hpp>
#include <core/paths.hpp>
#include <common/steamclient_exports.hpp>
#include <koalabox/globals.hpp>
#include <koalabox/dll_monitor.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/hook.hpp>
#include <koalabox/cache.hpp>
#include <koalabox/loader.hpp>
#include <koalabox/win_util.hpp>
#include <koalabox/util.hpp>
//#include <steam_api_exports/steam_api_exports.hpp>
#if COMPILE_STORE_MODE
#include <store_mode/store.hpp>
@@ -81,6 +80,8 @@ namespace smoke_api {
try {
DisableThreadLibraryCalls(module_handle);
koalabox::globals::init_globals(module_handle, PROJECT_NAME);
globals::smokeapi_handle = module_handle;
config::init_config();
@@ -89,12 +90,10 @@ namespace smoke_api {
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.
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_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.
*/
void fetch_and_cache_dlcs(AppId_t app_id) {
static std::mutex mutex;
const std::lock_guard<std::mutex> guard(mutex);
static Mutex mutex;
const MutexLockGuard guard(mutex);
if (not app_id) {
// No app id means we are operating in game mode.
// Hence, we need to use utility functions to get app id.
try {
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;
}
if (app_id == 0) {
LOG_ERROR("{} -> App ID is 0", __func__)
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
@@ -75,7 +68,6 @@ namespace steam_apps {
}
// Cache DLCs in memory and cache for future use
app_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(
const String& dlc_id,
AppId_t dlc_ids,
const String& function_name,
AppId_t app_id,
int iDLC,
AppId_t* pDlcId,
bool* pbAvailable,

View File

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

View File

@@ -2,11 +2,16 @@
#include <steam_impl/steam_apps.hpp>
VIRTUAL(bool) IClientAppManager_IsAppDlcInstalled(PARAMS(AppId_t app_id, AppId_t dlc_id)) {
return steam_apps::IsDlcUnlocked(
__func__, app_id, dlc_id, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientAppManager_IsAppDlcInstalled)
try {
return steam_apps::IsDlcUnlocked(
__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>
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t appId)) {
return steam_apps::GetDLCCount(
__func__, appId, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_GetDLCCount)
try {
return steam_apps::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(
@@ -21,24 +26,29 @@ VIRTUAL(bool) IClientApps_BGetDLCDataByIndex(
int cchNameBufferSize
)
) {
return steam_apps::GetDLCDataByIndex(
__func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
[&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_BGetDLCDataByIndex)
try {
return steam_apps::GetDLCDataByIndex(
__func__, appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize,
[&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientApps_BGetDLCDataByIndex)
return IClientApps_BGetDLCDataByIndex_o(
ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize)
);
},
[&](AppId_t dlc_id) {
const auto* app_manager_interface = store::steamclient::interface_name_to_address_map["IClientAppManager"];
if (app_manager_interface) {
IClientAppManager_IsAppDlcInstalled(app_manager_interface, EDX, appID, dlc_id);
return true;
return IClientApps_BGetDLCDataByIndex_o(
ARGS(appID, iDLC, pDlcID, pbAvailable, pchName, cchNameBufferSize)
);
},
[&](AppId_t dlc_id) {
const auto* app_manager_interface = store::steamclient::interface_name_to_address_map["IClientAppManager"];
if (app_manager_interface) {
IClientAppManager_IsAppDlcInstalled(app_manager_interface, EDX, appID, dlc_id);
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
return false;
}
);
);
} catch (const Exception& e) {
LOG_ERROR("{} -> Error: {}", __func__, e.what())
return false;
}
}

View File

@@ -2,13 +2,18 @@
#include <steam_impl/steam_apps.hpp>
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, [&]() {
GET_ORIGINAL_HOOKED_FUNCTION(IClientUser_BIsSubscribedApp)
return steam_apps::IsDlcUnlocked(__func__, app_id, dlc_id, [&]() {
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);
#define HOOK_FUNCTION(INTERFACE, FUNC) hook::swap_virtual_func_or_throw( \
globals::address_map, \
interface, \
#INTERFACE"_"#FUNC, \
ordinal_map[#INTERFACE][#FUNC], \

View File

@@ -7,7 +7,7 @@ namespace store::api {
try {
const String url =
"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>();
} catch (const Exception& e) {

View File

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