diff --git a/src/main/sdk-hook/Module.mk b/src/main/sdk-hook/Module.mk new file mode 100644 index 0000000..e0d28de --- /dev/null +++ b/src/main/sdk-hook/Module.mk @@ -0,0 +1,13 @@ +libs += sdk-hook + +ldflags_sdk-hook := \ + -lshlwapi \ + +libs_sdk-hook := \ + util \ + +src_sdk-hook := \ + dllentry.c \ + hooks-config.c \ + inject-config.c \ + logger-config.c \ diff --git a/src/main/sdk-hook/dllentry.c b/src/main/sdk-hook/dllentry.c new file mode 100644 index 0000000..3b79c55 --- /dev/null +++ b/src/main/sdk-hook/dllentry.c @@ -0,0 +1,215 @@ +#define LOG_MODULE "hook-dllentry" + +// clang-format off +// Don't format because the order is important here +#include +#include +// clang-format on + +#include + +#include "core/boot.h" +#include "core/config-property-node.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/property-ext.h" +#include "core/property-node.h" + +#include "iface-core/config.h" +#include "iface-core/log.h" +#include "iface-core/thread.h" + +#include "iface/hook.h" + +#include "iidxhook1/iidxhook1.h" + +#include "main/module/core.h" +#include "main/module/hook.h" + +#include "sdk-hook/inject-config.h" + +#include "util/str.h" + +static HMODULE _bt_hook_dllentry_hook_module; +static bt_hook_t *_bt_hook_dllentry_hook; +static bt_hook_inject_config_t _bt_hook_dllentry_inject_config; + +static void _bt_hook_dllentry_current_module_name_get(char *name, size_t len) +{ + char path[MAX_PATH]; + const char *filename; + + log_assert(name); + log_assert(len > 0); + + if (GetModuleFileName(_bt_hook_dllentry_hook_module, path, MAX_PATH) != 0) { + filename = PathFindFileName(path); + + str_cpy(name, len, filename); + } else { + log_fatal("GetModuleFileName to get current module name failed"); + } +} + +static const core_property_t *_bt_hook_dllentry_hook_property_config_get() +{ + char current_module_name[MAX_PATH]; + const struct bt_hook_hooks_hook_config *configs; + const char *filename; + int i; + + _bt_hook_dllentry_current_module_name_get(current_module_name, sizeof(current_module_name)); + + configs = _bt_hook_dllentry_inject_config.hooks.hooks; + + for (i = 0; i < BT_HOOK_HOOKS_CONFIG_MAX_HOOKS; i++) { + filename = PathFindFileName(configs[i].path); + + if (str_eq(current_module_name, filename)) { + log_misc("Property hook config for %s", configs[i].path); + core_property_ext_log(configs[i].config, log_misc_func); + + return configs[i].config; + } + } + + log_fatal("Could not find hook configuration for module %s in inject configuration", current_module_name); +} + +static void _bt_hook_dllentry_config_load() +{ + char inject_config_path[MAX_PATH]; + + // Duct-tape to allow access to config path and load config in hook dlls + GetEnvironmentVariable("INJECT_CONFIG_PATH", inject_config_path, sizeof(inject_config_path)); + + // With DllMain as the only callable entry point from inject, + // the only way to get the configuration for the hook is through + // injects configuration file + command line arguments + // Thus, the whole loading process (that is also done in inject) + // is replicated here (inject config etc. are also copy-pasted from + // the inject module) + bt_hook_inject_config_init(&_bt_hook_dllentry_inject_config); + bt_hook_inject_config_file_load(inject_config_path, &_bt_hook_dllentry_inject_config); +} + +static void _bt_hook_dllentry_logger_reinit() +{ + // Apply same logger configurations as inject + if (!_bt_hook_dllentry_inject_config.logger.enable) { + core_log_bt_fini(); + + core_log_bt_ext_init_with_null(); + } else { + // Logger already initialized previously, just switch log level + core_log_bt_level_set(_bt_hook_dllentry_inject_config.logger.level); + } +} + +void bt_hook_dllentry_init( + HMODULE module, + const char* name, + bt_module_core_config_api_set_t hook_config_api_set, + bt_module_core_log_api_set_t hook_log_api_set, + bt_module_core_thread_api_set_t hook_thread_api_set, + bt_module_hook_api_get_t hook_api_get) +{ + bt_core_config_api_t config_api; + bt_core_log_api_t log_api; + bt_core_thread_api_t thread_api; + bt_hook_api_t hook_api; + + core_boot_dll(name); + + // Debug logging is captured by debugger in inject and actually + // sunk to outputs there, e.g. terminal/file + core_log_bt_ext_init_with_debug(); + core_log_bt_core_api_set(); + // TODO change log level according to how inject is configured, read from config + command line args + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); + + _bt_hook_dllentry_config_load(); + + _bt_hook_dllentry_logger_reinit(); + + // Assert after log init to get visible error output if these fail + log_assert(module); + log_assert(hook_config_api_set); + log_assert(hook_log_api_set); + log_assert(hook_thread_api_set); + log_assert(hook_api_get); + + _bt_hook_dllentry_hook_module = module; + + bt_core_config_api_get(&config_api); + bt_core_log_api_get(&log_api); + bt_core_thread_api_get(&thread_api); + + hook_config_api_set(&config_api); + hook_log_api_set(&log_api); + hook_thread_api_set(&thread_api); + + hook_api_get(&hook_api); + + bt_hook_init(&hook_api, name, &_bt_hook_dllentry_hook); + + log_misc("<<< bt_hook_dllentry_boot_init"); +} + +void bt_hook_dllentry_fini() +{ + log_misc(">>> bt_hook_dllentry_fini"); + + bt_hook_inject_config_fini(&_bt_hook_dllentry_inject_config); + + bt_hook_fini(&_bt_hook_dllentry_hook); + + core_log_bt_fini(); + + bt_core_config_api_clear(); + bt_core_log_api_clear(); + bt_core_thread_api_clear(); +} + +void bt_hook_dllentry_main_init() +{ + bool result; + const core_property_t *property; + core_property_node_t root_node; + bt_core_config_t *config; + core_property_result_t result_prop; + HMODULE game_module; + + log_misc(">>> bt_hook_dllentry_main_init"); + + property = _bt_hook_dllentry_hook_property_config_get(); + + result_prop = core_property_root_node_get(property, &root_node); + core_property_fatal_on_error(result_prop); + + core_config_property_node_init(&root_node, &config); + + // Because this is loaded into the game's process space, this yields the executable module + game_module = GetModuleHandle(NULL); + + result = bt_hook_main_init(_bt_hook_dllentry_hook, game_module, config); + + core_config_property_node_free(&config); + + if (!result) { + log_fatal("Calling hook main init failed"); + } + + log_misc("<<< bt_hook_dllentry_main_init"); +} + +void bt_hook_dllentry_main_fini() +{ + log_misc(">>> bt_hook_dllentry_boot_fini"); + + bt_hook_main_fini(_bt_hook_dllentry_hook); + + bt_hook_fini(&_bt_hook_dllentry_hook); + + log_misc("<<< bt_hook_dllentry_boot_fini"); +} diff --git a/src/main/sdk-hook/dllentry.h b/src/main/sdk-hook/dllentry.h new file mode 100644 index 0000000..079eb54 --- /dev/null +++ b/src/main/sdk-hook/dllentry.h @@ -0,0 +1,23 @@ +#ifndef BT_SDK_HOOK_DLLENTRY_H +#define BT_SDK_HOOK_DLLENTRY_H + +#include + +#include "main/module/core.h" +#include "main/module/hook.h" + +void bt_hook_dllentry_init( + HMODULE module, + const char *name, + bt_module_core_config_api_set_t hook_config_api_set, + bt_module_core_log_api_set_t hook_log_api_set, + bt_module_core_thread_api_set_t hook_thread_api_set, + bt_module_hook_api_get_t hook_api_get); + +void bt_hook_dllentry_fini(); + +void bt_hook_dllentry_main_init(); + +void bt_hook_dllentry_main_fini(); + +#endif \ No newline at end of file diff --git a/src/main/sdk-hook/hooks-config.c b/src/main/sdk-hook/hooks-config.c new file mode 100644 index 0000000..7336eaa --- /dev/null +++ b/src/main/sdk-hook/hooks-config.c @@ -0,0 +1,161 @@ +#define LOG_MODULE "bt-hook-inject-hooks-config" + +#include "core/property-ext.h" +#include "core/property-node-ext.h" + +#include "iface-core/log.h" + +#include "sdk-hook/hooks-config.h" + +#define BT_HOOK_HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES 8 + +static core_property_t * +_bt_hook_hooks_config_layered_config_nodes_load(const core_property_node_t *node) +{ + char kind[64]; + char file[MAX_PATH]; + int cnt; + + core_property_node_t cur; + core_property_node_t tmp; + core_property_t *config_property[BT_HOOK_HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES]; + core_property_t *merged_property; + core_property_node_result_t result; + core_property_result_t prop_result; + + log_assert(node); + + cnt = 0; + result = core_property_node_search(node, "config", &cur); + + if (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) { + core_property_node_fatal_on_error(result); + } + + while (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) { + if (cnt >= BT_HOOK_HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES) { + log_fatal( + "Exceeding max supported config nodes for layering, max is %d", + BT_HOOK_HOOKS_CONFIG_MAX_LAYER_CONFIG_NODES); + } + + result = + core_property_node_attr_read(&cur, "kind", kind, sizeof(kind)); + + if (CORE_PROPERTY_NODE_RESULT_IS_ERROR(result)) { + log_fatal("Failed reading 'kind' attribute value of config node"); + } + + if (!strcmp(kind, "file")) { + core_property_node_str_read(&cur, file, sizeof(file)); + + prop_result = core_property_file_load(file, &config_property[cnt]); + core_property_fatal_on_error(prop_result); + } else if (!strcmp(kind, "inline")) { + // The nested child is the actual root of the inline, not the outer + // node + result = core_property_node_child_get(&cur, &tmp); + memcpy(&cur, &tmp, sizeof(core_property_node_t)); + + if (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) { + core_property_node_fatal_on_error(result); + } + + result = + core_property_node_ext_extract(&cur, &config_property[cnt]); + core_property_node_fatal_on_error(result); + } else { + log_fatal( + "Unsupported 'kind' attribute value '%s' of config node", kind); + } + + cnt++; + result = core_property_node_next_result_search(&cur, &tmp); + memcpy(&cur, &tmp, sizeof(core_property_node_t)); + + if (result != CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) { + core_property_node_fatal_on_error(result); + } + } + + if (cnt == 0) { + prop_result = core_property_str_load("", &merged_property); + core_property_fatal_on_error(prop_result); + } else { + prop_result = + core_property_ext_many_merge(config_property, cnt, &merged_property); + core_property_fatal_on_error(prop_result); + + for (int i = 0; i < cnt; i++) { + core_property_free(&config_property[i]); + } + } + + return merged_property; +} + +static void _bt_hook_hooks_config_hooks_load( + const core_property_node_t *node, + struct bt_hook_hooks_hook_config *configs) +{ + core_property_node_result_t result; + core_property_node_t child; + core_property_node_t tmp; + uint8_t processed_hooks; + + processed_hooks = 0; + + result = core_property_node_search(node, "hook", &child); + + do { + if (processed_hooks >= BT_HOOK_HOOKS_CONFIG_MAX_HOOKS) { + log_fatal("Cannot load more hooks, max supported capacity reached"); + } + + if (result == CORE_PROPERTY_NODE_RESULT_NODE_NOT_FOUND) { + return; + } else { + core_property_node_fatal_on_error(result); + } + + result = core_property_node_ext_bool_read(&child, "enable", &configs[processed_hooks].enable); + core_property_node_fatal_on_error(result); + + result = core_property_node_ext_str_read(&child, "path", configs[processed_hooks].path, sizeof(configs[processed_hooks].path)); + core_property_node_fatal_on_error(result); + + configs[processed_hooks].config = _bt_hook_hooks_config_layered_config_nodes_load(&child); + + result = core_property_node_next_result_search(&child, &tmp); + memcpy(&child, &tmp, sizeof(core_property_node_t)); + + processed_hooks++; + } while (true); +} + +void bt_hook_hooks_config_init(bt_hook_hooks_config_t *config) +{ + log_assert(config); + + memset(config, 0, sizeof(bt_hook_hooks_config_t)); +} + +void bt_hook_hooks_config_load( + const core_property_node_t *node, bt_hook_hooks_config_t *config) +{ + log_assert(node); + log_assert(config); + + _bt_hook_hooks_config_hooks_load(node, config->hooks); +} + +void bt_hook_hooks_config_fini(bt_hook_hooks_config_t *config) +{ + uint32_t i; + + log_assert(config); + + for (i = 0; i < BT_HOOK_HOOKS_CONFIG_MAX_HOOKS; i++) { + core_property_free(&config->hooks[i].config); + } +} \ No newline at end of file diff --git a/src/main/sdk-hook/hooks-config.h b/src/main/sdk-hook/hooks-config.h new file mode 100644 index 0000000..62173d2 --- /dev/null +++ b/src/main/sdk-hook/hooks-config.h @@ -0,0 +1,28 @@ +#ifndef BT_SDK_HOOK_INJECT_HOOKS_CONFIG_H +#define BT_SDK_HOOK_INJECT_HOOKS_CONFIG_H + +#include + +#include + +#include "core/property.h" +#include "core/property-node.h" + +#define BT_HOOK_HOOKS_CONFIG_MAX_HOOKS 16 + +typedef struct bt_hook_hooks_config { + struct bt_hook_hooks_hook_config { + bool enable; + char path[MAX_PATH]; + core_property_t *config; + } hooks[BT_HOOK_HOOKS_CONFIG_MAX_HOOKS]; +} bt_hook_hooks_config_t; + +void bt_hook_hooks_config_init(bt_hook_hooks_config_t *config); + +void bt_hook_hooks_config_load( + const core_property_node_t *node, bt_hook_hooks_config_t *config); + +void bt_hook_hooks_config_fini(bt_hook_hooks_config_t *config); + +#endif \ No newline at end of file diff --git a/src/main/sdk-hook/inject-config.c b/src/main/sdk-hook/inject-config.c new file mode 100644 index 0000000..f828356 --- /dev/null +++ b/src/main/sdk-hook/inject-config.c @@ -0,0 +1,61 @@ +#define LOG_MODULE "bt-hook-inject-config" + +#include "core/property-ext.h" +#include "core/property-node.h" +#include "core/property.h" + +#include "core/property-mxml-internal.h" +#include "core/property-node-ext.h" + +#include "iface-core/log.h" + +#include "sdk-hook/hooks-config.h" +#include "sdk-hook/inject-config.h" +#include "sdk-hook/logger-config.h" + +void bt_hook_inject_config_init(struct bt_hook_inject_config *config) +{ + log_assert(config); + + config->version = 1; + + bt_hook_hooks_config_init(&config->hooks); + bt_hook_logger_config_init(&config->logger); +} + +void bt_hook_inject_config_file_load(const char *path, bt_hook_inject_config_t *config) +{ + core_property_result_t result_prop; + core_property_node_result_t result; + core_property_t *property; + core_property_node_t root_node; + core_property_node_t child_node; + + log_info("Loading configuration file: %s", path); + + result_prop = core_property_file_load(path, &property); + core_property_fatal_on_error(result_prop); + + result = core_property_root_node_get(property, &root_node); + core_property_node_fatal_on_error(result); + + result = core_property_node_search(&root_node, "hooks", &child_node); + core_property_node_fatal_on_error(result); + bt_hook_hooks_config_load(&child_node, &config->hooks); + + result = core_property_node_search(&root_node, "logger", &child_node); + core_property_node_fatal_on_error(result); + bt_hook_logger_config_load(&child_node, &config->logger); + + core_property_free(&property); + + log_misc("Loading done"); +} + +void bt_hook_inject_config_fini(bt_hook_inject_config_t *config) +{ + log_assert(config); + + // Other configs don't have a fini + bt_hook_hooks_config_fini(&config->hooks); +} \ No newline at end of file diff --git a/src/main/sdk-hook/inject-config.h b/src/main/sdk-hook/inject-config.h new file mode 100644 index 0000000..a6d2e55 --- /dev/null +++ b/src/main/sdk-hook/inject-config.h @@ -0,0 +1,22 @@ +#ifndef BT_SDK_HOOK_INJECT_CONFIG_H +#define BT_SDK_HOOK_INJECT_CONFIG_H + +#include + +#include "sdk-hook/hooks-config.h" +#include "sdk-hook/logger-config.h" + +typedef struct bt_hook_inject_config { + uint32_t version; + + bt_hook_hooks_config_t hooks; + bt_hook_logger_config_t logger; +} bt_hook_inject_config_t; + +void bt_hook_inject_config_init(struct bt_hook_inject_config *config); + +void bt_hook_inject_config_file_load(const char *path, bt_hook_inject_config_t *config); + +void bt_hook_inject_config_fini(bt_hook_inject_config_t *config); + +#endif \ No newline at end of file diff --git a/src/main/sdk-hook/logger-config.c b/src/main/sdk-hook/logger-config.c new file mode 100644 index 0000000..62c8e0a --- /dev/null +++ b/src/main/sdk-hook/logger-config.c @@ -0,0 +1,52 @@ +#define LOG_MODULE "bt-hook-inject-logger-config" + +#include "core/property-node-ext.h" + +#include "iface-core/log.h" + +#include "sdk-hook/logger-config.h" + +#include "util/str.h" + +static enum core_log_bt_log_level _bt_hook_logger_config_str_to_loglevel(const char *str) +{ + log_assert(str); + + if (str_eq(str, "off")) { + return CORE_LOG_BT_LOG_LEVEL_OFF; + } else if (str_eq(str, "fatal")) { + return CORE_LOG_BT_LOG_LEVEL_FATAL; + } else if (str_eq(str, "warning")) { + return CORE_LOG_BT_LOG_LEVEL_WARNING; + } else if (str_eq(str, "info")) { + return CORE_LOG_BT_LOG_LEVEL_INFO; + } else if (str_eq(str, "misc")) { + return CORE_LOG_BT_LOG_LEVEL_MISC; + } else { + log_fatal("Invalid log level string in config: %s", str); + } +} + +void bt_hook_logger_config_init(bt_hook_logger_config_t *config) +{ + log_assert(config); + + memset(config, 0, sizeof(bt_hook_logger_config_t)); +} + +void bt_hook_logger_config_load( + const core_property_node_t *node, bt_hook_logger_config_t *config) +{ + core_property_node_result_t result; + char buffer[16]; + + log_assert(node); + log_assert(config); + + result = core_property_node_ext_bool_read(node, "enable", &config->enable); + core_property_node_fatal_on_error(result); + + result = core_property_node_ext_str_read(node, "level", buffer, sizeof(buffer)); + core_property_node_fatal_on_error(result); + config->level = _bt_hook_logger_config_str_to_loglevel(buffer); +} diff --git a/src/main/sdk-hook/logger-config.h b/src/main/sdk-hook/logger-config.h new file mode 100644 index 0000000..6ad89d8 --- /dev/null +++ b/src/main/sdk-hook/logger-config.h @@ -0,0 +1,23 @@ +#ifndef BT_SDK_HOOK_INJECT_LOGGER_CONFIG_H +#define BT_SDK_HOOK_INJECT_LOGGER_CONFIG_H + +#include +#include + +#include + +#include "core/log-bt.h" +#include "core/log-sink-async.h" +#include "core/property-node.h" + +typedef struct bt_hook_logger_config { + bool enable; + enum core_log_bt_log_level level; +} bt_hook_logger_config_t; + +void bt_hook_logger_config_init(bt_hook_logger_config_t *config); + +void bt_hook_logger_config_load( + const core_property_node_t *node, bt_hook_logger_config_t *config); + +#endif \ No newline at end of file