Added koalageddon config

master
acidicoala 1 year ago
parent 941d5d7d8c
commit 636f9186a3
No known key found for this signature in database
GPG Key ID: D24C6065B49C645B

@ -17,6 +17,13 @@
<editorconfig>
<option name="ENABLED" value="false" />
</editorconfig>
<files>
<extensions>
<pair source="cpp" header="hpp" fileNamingConvention="SNAKE_CASE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
<pair source="cu" header="cuh" fileNamingConvention="NONE" />
</extensions>
</files>
<codeStyleSettings language="CMake">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />

@ -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.

@ -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());
}

@ -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<void**>(this)[koalageddon_config.callback_interceptor_address_offset];
}
void* get_callback_address() {
return reinterpret_cast<void**>(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<CallbackData**>(this)[koalageddon_config.callback_data_offset];
}
const char* get_callback_name() {
return reinterpret_cast<const char**>(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;

@ -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<Config>(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<KoalageddonConfig>();
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<Config>(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();

@ -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<uint32_t> override;
@ -33,6 +55,9 @@ namespace smoke_api {
bool auto_inject_inventory = true;
Vector<uint32_t> 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;

@ -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));

@ -1,6 +1,7 @@
#include <steam_impl/steam_impl.hpp>
#include <smoke_api/smoke_api.hpp>
// TODO: Figure out why it doesn't work in koalageddon mode
namespace steam_inventory {
EResult GetResultStatus(

Loading…
Cancel
Save