diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 4c2251c..ed9566e 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -17,6 +17,13 @@
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index bf80207..758175f 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,6 @@ SmokeAPI aims to support all released SteamAPI versions. When it encountered a n
Steam inventory does not work in all games with steam inventory because of custom implementation, and online checks.
The list of games where inventory emulation has been shown to work is as follows:
-- Hero Siege
- Project Winter
- Euro Truck Simulator 2
- Bloons TD 6
@@ -80,6 +79,7 @@ SmokeAPI does not require any manual configuration. By default, it uses the most
| `dlc_ids` | When game requests list of all DLCs from Steam and the number of registered DLCs is greater than 64, Steam may not return all of them. In this case, SmokeAPI will fetch all released DLCs from Web API. In some games, however (like Monster Hunter: World), web api also doesn't return all possible DLCs. To address this issue, you can specify the missing DLC IDs¹ in this option. For some games (including MH:W), however, it is not necessary because SmokeAPI will also automatically fetch a [manually maintained list of DLC IDs] that are missing from web api | List of Integers | `[]` |
| `auto_inject_inventory` | Toggles whether SmokeAPI should automatically inject a list of all registered inventory items, when a game queries user inventory | Boolean | `true` |
| `inventory_items` | A list of inventory items IDs¹ that will be added in addition to the automatically injected items | List of Integers | `[]` |
+| `koalageddon_config` | An object that specifies patterns and offsets required for koalageddon mode. It can be used to override online config for testing or development purposes. | Object | `null` |
¹ DLC/Item IDs can be obtained from https://steamdb.info. You need to be logged in with your steam account in order to see accurate inventory item IDs.
diff --git a/src/koalageddon/steamclient.cpp b/src/koalageddon/steamclient.cpp
index a4e5610..ac0570c 100644
--- a/src/koalageddon/steamclient.cpp
+++ b/src/koalageddon/steamclient.cpp
@@ -5,8 +5,7 @@
using namespace smoke_api;
-
-DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_name) {
+DLL_EXPORT(void) SteamClient_Interface_Interceptor(const char* interface_name, const char* function_name) {
try {
void**** parent_ebp;
@@ -29,7 +28,7 @@ DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_
const auto compound_name = interface_name + String("::") + function_name;
#define HOOK(FUNC, ORDINAL) hook_function(FUNC, #FUNC, ORDINAL);
-
+ // TODO: Parametrize ordinals
if (util::strings_are_equal(interface_name, "IClientAppManager")) {
HOOK(IClientAppManager_IsAppDlcInstalled, 8)
} else if (util::strings_are_equal(interface_name, "IClientApps")) {
@@ -46,8 +45,8 @@ DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_
HOOK(IClientInventory_GetItemDefinitionIDs, 19)
}
- GET_ORIGINAL_FUNCTION(Log_Interface)
- Log_Interface_o(interface_name, function_name);
+ GET_ORIGINAL_FUNCTION(SteamClient_Interface_Interceptor)
+ SteamClient_Interface_Interceptor_o(interface_name, function_name);
} catch (const Exception& ex) {
logger->error("{} -> Error: {}", __func__, ex.what());
}
diff --git a/src/koalageddon/vstdlib.cpp b/src/koalageddon/vstdlib.cpp
index dae08a9..7b7afa1 100644
--- a/src/koalageddon/vstdlib.cpp
+++ b/src/koalageddon/vstdlib.cpp
@@ -18,22 +18,29 @@ VIRTUAL(bool) SharedLibraryStopPlaying(PARAMS(void* arg)) { // NOLINT(misc-unuse
}
struct CallbackData {
- [[maybe_unused]] void* pad1[1];
- void* set_callback_name_address; // to_do: fetch online
- [[maybe_unused]] void* pad19[17];
- void* callback_address; // to_do: fetch online
+ void* get_callback_intercept_address() {
+ return reinterpret_cast(this)[koalageddon_config.callback_interceptor_address_offset];
+ }
+
+ void* get_callback_address() {
+ return reinterpret_cast(this)[koalageddon_config.callback_address_offset];
+ }
};
struct CoroutineData {
- CallbackData* callback_data; // to_do: fetch online
- [[maybe_unused]] uint32_t pad4[3];
- const char* callback_name; // to_do: fetch online
+ CallbackData* get_callback_data() {
+ return reinterpret_cast(this)[koalageddon_config.callback_data_offset];
+ }
+
+ const char* get_callback_name() {
+ return reinterpret_cast(this)[koalageddon_config.callback_name_offset];
+ }
};
-VIRTUAL(void) set_callback_name(PARAMS(const char** p_name)) {
- GET_ORIGINAL_FUNCTION(set_callback_name)
+VIRTUAL(void) VStdLib_Callback_Interceptor(PARAMS(const char** p_name)) {
+ GET_ORIGINAL_FUNCTION(VStdLib_Callback_Interceptor)
- set_callback_name_o(ARGS(p_name));
+ VStdLib_Callback_Interceptor_o(ARGS(p_name));
static auto hooked_functions = 0;
@@ -43,20 +50,20 @@ VIRTUAL(void) set_callback_name(PARAMS(const char** p_name)) {
auto* const data = (CoroutineData*) THIS;
- if (data && data->callback_name) {
- const auto name = String(data->callback_name);
+ if (data && data->get_callback_name()) {
+ const auto name = String(data->get_callback_name());
// logger->trace("{} -> instance: {}, name: '{}'", __func__, fmt::ptr(THIS), name);
if (name == "SharedLicensesLockStatus") {
static std::once_flag flag;
std::call_once(flag, [&]() {
- DETOUR(SharedLicensesLockStatus, data->callback_data->callback_address)
+ DETOUR(SharedLicensesLockStatus, data->get_callback_data()->get_callback_address())
hooked_functions++;
});
} else if (name == "SharedLibraryStopPlaying") {
static std::once_flag flag;
std::call_once(flag, [&]() {
- DETOUR(SharedLibraryStopPlaying, data->callback_data->callback_address)
+ DETOUR(SharedLibraryStopPlaying, data->get_callback_data()->get_callback_address())
hooked_functions++;
});
}
@@ -76,7 +83,9 @@ DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, CoroutineData* d
static std::once_flag flag;
std::call_once(flag, [&]() {
logger->debug("Coroutine_Create -> callback: {}, data: {}", callback_address, fmt::ptr(data));
- DETOUR(set_callback_name, data->callback_data->set_callback_name_address)
+
+
+ DETOUR(VStdLib_Callback_Interceptor, data->get_callback_data()->get_callback_intercept_address())
});
return result;
diff --git a/src/smoke_api/smoke_api.cpp b/src/smoke_api/smoke_api.cpp
index eaad4ad..22ba16a 100644
--- a/src/smoke_api/smoke_api.cpp
+++ b/src/smoke_api/smoke_api.cpp
@@ -18,16 +18,44 @@
namespace smoke_api {
Config config = {}; // NOLINT(cert-err58-cpp)
+ KoalageddonConfig koalageddon_config = {};
+
HMODULE original_library = nullptr;
bool is_hook_mode = false;
Path self_directory;
+ void init_config() {
+ // TODO: Detect koalageddon mode first, and then fetch config from corresponding directory
+ config = config_parser::parse(self_directory / PROJECT_NAME".json");
+ }
+
+ /**
+ * @return A string representing the source of the config.
+ */
+ String init_koalageddon_config() {
+ try {
+ // First try to read a local config override
+ koalageddon_config = config.koalageddon_config.get();
+ return "local config override";
+ } catch (const Exception& ex) {
+ logger->debug("Local config parse exception: {}", ex.what());
+ }
+
+ // TODO: Remote source with local cache
+
+ // Finally, fallback on the default config
+ return "default config bundled in the binary";
+ }
+
void init_koalageddon_mode() {
#ifndef _WIN64
logger->info("🐨 Detected Koalageddon mode 💥");
+ const auto kg_config_source = init_koalageddon_config();
+ logger->info("Loaded Koalageddon config from the {}", kg_config_source);
+
dll_monitor::init({VSTDLIB_DLL, STEAMCLIENT_DLL}, [](const HMODULE& library, const String& name) {
original_library = library; // TODO: Is this necessary?
@@ -36,13 +64,14 @@ namespace smoke_api {
DETOUR(Coroutine_Create)
} else if (name == STEAMCLIENT_DLL) {
// Unlocking functions
- // TODO: Un-hardcode the pattern
- const String pattern("55 8B EC 8B ?? ?? ?? ?? ?? 81 EC ?? ?? ?? ?? 53 FF 15");
- auto Log_Interface_address = (FunctionAddress) patcher::find_pattern_address(
- win_util::get_module_info(library), "Log_Interface", pattern
+ auto interface_interceptor_address = (FunctionAddress) patcher::find_pattern_address(
+ win_util::get_module_info(library),
+ "SteamClient_Interface_Interceptor",
+ koalageddon_config.interface_interceptor_pattern
);
- if (Log_Interface_address) {
- DETOUR_EX(Log_Interface, Log_Interface_address)
+
+ if (interface_interceptor_address) {
+ DETOUR_EX(SteamClient_Interface_Interceptor, interface_interceptor_address)
}
}
});
@@ -91,7 +120,7 @@ namespace smoke_api {
self_directory = loader::get_module_dir(self_module);
- config = config_parser::parse(self_directory / PROJECT_NAME".json");
+ init_config();
const auto exe_path = Path(win_util::get_module_file_name_or_throw(nullptr));
const auto exe_name = exe_path.filename().string();
diff --git a/src/smoke_api/smoke_api.hpp b/src/smoke_api/smoke_api.hpp
index 39a615f..e10b1bc 100644
--- a/src/smoke_api/smoke_api.hpp
+++ b/src/smoke_api/smoke_api.hpp
@@ -24,8 +24,30 @@
namespace smoke_api {
using namespace koalabox;
+ struct KoalageddonConfig {
+ String interface_interceptor_pattern = "55 8B EC 8B ?? ?? ?? ?? ?? 81 EC ?? ?? ?? ?? 53 FF 15";
+
+ // Offset values are interpreted according to pointer arithmetic rules, i.e.
+ // 1 unit offset represents 4 and 8 bytes in 32-bit and 64-bit architectures respectively.
+ uint32_t callback_interceptor_address_offset = 1;
+ uint32_t callback_address_offset = 20;
+ uint32_t callback_data_offset = 0;
+ uint32_t callback_name_offset = 4;
+
+ // We do not use *_WITH_DEFAULT macro to ensure that overriding
+ // the koalageddon config requires definition of all keys
+ NLOHMANN_DEFINE_TYPE_INTRUSIVE(
+ KoalageddonConfig, // NOLINT(misc-const-correctness)
+ interface_interceptor_pattern,
+ callback_interceptor_address_offset,
+ callback_address_offset,
+ callback_data_offset,
+ callback_name_offset
+ )
+ };
+
struct Config {
- uint32_t $version = 1;
+ uint32_t $version = 2;
bool logging = false;
bool unlock_all = true;
Set override;
@@ -33,6 +55,9 @@ namespace smoke_api {
bool auto_inject_inventory = true;
Vector inventory_items;
+ // Have to use general json type here since library doesn't support std::optional
+ nlohmann::json koalageddon_config;
+
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
Config, $version, // NOLINT(misc-const-correctness)
logging,
@@ -40,12 +65,15 @@ namespace smoke_api {
override,
dlc_ids,
auto_inject_inventory,
- inventory_items
+ inventory_items,
+ koalageddon_config
)
};
extern Config config;
+ extern KoalageddonConfig koalageddon_config;
+
extern HMODULE original_library;
extern bool is_hook_mode;
diff --git a/src/steam_functions/steam_functions.hpp b/src/steam_functions/steam_functions.hpp
index 09442b7..1e452d4 100644
--- a/src/steam_functions/steam_functions.hpp
+++ b/src/steam_functions/steam_functions.hpp
@@ -117,7 +117,7 @@ DLL_EXPORT(bool) SteamAPI_ISteamInventory_GetItemDefinitionIDs(ISteamInventory*,
// Koalageddon mode
DLL_EXPORT(HCoroutine) Coroutine_Create(void* callback_address, struct CoroutineData* data);
-DLL_EXPORT(void) Log_Interface(const char* interface_name, const char* function_name);
+DLL_EXPORT(void) SteamClient_Interface_Interceptor(const char* interface_name, const char* function_name);
// IClientApps
VIRTUAL(int) IClientApps_GetDLCCount(PARAMS(AppId_t));
diff --git a/src/steam_impl/steam_inventory.cpp b/src/steam_impl/steam_inventory.cpp
index 975560c..63da524 100644
--- a/src/steam_impl/steam_inventory.cpp
+++ b/src/steam_impl/steam_inventory.cpp
@@ -1,6 +1,7 @@
#include
#include
+// TODO: Figure out why it doesn't work in koalageddon mode
namespace steam_inventory {
EResult GetResultStatus(