diff --git a/Dockerfile b/Dockerfile.build similarity index 100% rename from Dockerfile rename to Dockerfile.build diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 00000000..00c51086 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,21 @@ +FROM --platform=amd64 debian:11.6-slim@sha256:f7d141c1ec6af549958a7a2543365a7829c2cdc4476308ec2e182f8a7c59b519 + +LABEL description="Development environment for bemanitools" + +# mingw-w64-gcc has 32-bit and 64-bit toolchains +RUN apt-get update && apt-get install -y --no-install-recommends \ + mingw-w64 \ + mingw-w64-common \ + make \ + zip \ + git \ + clang-format \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* + +RUN pip3 install mdformat + +RUN mkdir /bemanitools +WORKDIR /bemanitools + +ENV SHELL /bin/bash \ No newline at end of file diff --git a/GNUmakefile b/GNUmakefile index 83289629..9aef0b1b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -13,8 +13,10 @@ BUILDDIR ?= build builddir_docker := $(BUILDDIR)/docker -docker_container_name := "bemanitools-build" -docker_image_name := "bemanitools-build:latest" +docker_build_container_name := "bemanitools-build" +docker_build_image_name := "bemanitools-build:latest" +docker_dev_container_name := "bemanitools-dev" +docker_dev_image_name := "bemanitools-dev:latest" depdir := $(BUILDDIR)/dep objdir := $(BUILDDIR)/obj @@ -41,6 +43,7 @@ FORCE: .PHONY: \ build-docker \ +dev-docker \ clean \ code-format \ doc-format \ @@ -89,21 +92,38 @@ version: $(V)echo "$(gitrev)" > version build-docker: - $(V)docker rm -f $(docker_container_name) 2> /dev/null || true + $(V)docker rm -f $(docker_build_container_name) 2> /dev/null || true $(V)docker \ build \ - -t $(docker_image_name) \ - -f Dockerfile \ + -t $(docker_build_image_name) \ + -f Dockerfile.build \ . $(V)docker \ run \ --volume $(shell pwd):/bemanitools \ - --name $(docker_container_name) \ - $(docker_image_name) + --name $(docker_build_container_name) \ + $(docker_build_image_name) + +dev-docker: + $(V)docker rm -f $(docker_dev_container_name) 2> /dev/null || true + $(V)docker \ + build \ + -t $(docker_dev_image_name) \ + -f Dockerfile.dev \ + . + $(V)docker \ + run \ + --interactive \ + --tty \ + --volume $(shell pwd):/bemanitools \ + --name $(docker_dev_container_name) \ + $(docker_dev_image_name) clean-docker: - $(V)docker rm -f $(docker_container_name) || true - $(V)docker image rm -f $(docker_image_name) || true + $(V)docker rm -f $(docker_dev_container_name) || true + $(V)docker image rm -f $(docker_dev_image_name) || true + $(V)docker rm -f $(docker_build_container_name) || true + $(V)docker image rm -f $(docker_build_image_name) || true $(V)rm -rf $(BUILDDIR) # diff --git a/Module.mk b/Module.mk index 2a948a8d..41d179a1 100644 --- a/Module.mk +++ b/Module.mk @@ -100,6 +100,7 @@ include src/main/bstio/Module.mk include src/main/camhook/Module.mk include src/main/cconfig/Module.mk include src/main/config/Module.mk +include src/main/core/Module.mk include src/main/d3d9-util/Module.mk include src/main/d3d9exhook/Module.mk include src/main/ddrhook-util/Module.mk @@ -710,6 +711,8 @@ $(zipdir)/ddr-14-to-18.zip: \ build/bin/indep-32/eamio.dll \ build/bin/indep-32/geninput.dll \ dist/ddr/config.bat \ + dist/ddr/gamestart-17.bat \ + dist/ddr/gamestart-18.bat \ dist/ddr/gamestart-14.bat \ dist/ddr/gamestart-15.bat \ dist/ddr/gamestart-16.bat \ @@ -728,6 +731,8 @@ $(zipdir)/ddr-16-to-18-x64.zip: \ build/bin/indep-64/eamio.dll \ build/bin/indep-64/geninput.dll \ dist/ddr/config.bat \ + dist/ddr/gamestart-17.bat \ + dist/ddr/gamestart-18.bat \ dist/ddr/gamestart-16.bat \ dist/ddr/gamestart-17.bat \ dist/ddr/gamestart-18.bat \ diff --git a/src/imports/avs.h b/src/imports/avs.h index a2d439ec..ae054e5c 100644 --- a/src/imports/avs.h +++ b/src/imports/avs.h @@ -220,6 +220,9 @@ void property_file_write(struct property *prop, const char *path); int property_set_flag(struct property *prop, int flags, int mask); void property_destroy(struct property *prop); +avs_error property_get_error(struct property *prop); +void property_clear_error(struct property *prop); + int property_psmap_import( struct property *prop, struct property_node *root, diff --git a/src/imports/import_32_0_avs.def b/src/imports/import_32_0_avs.def index 273e5be8..9632ecb4 100644 --- a/src/imports/import_32_0_avs.def +++ b/src/imports/import_32_0_avs.def @@ -25,6 +25,8 @@ EXPORTS property_destroy property_file_write property_insert_read + property_clear_error + property_get_error property_mem_write property_read_query_memsize property_search diff --git a/src/imports/import_32_1002_avs.def b/src/imports/import_32_1002_avs.def index e2ff5ae5..cc4ee29c 100644 --- a/src/imports/import_32_1002_avs.def +++ b/src/imports/import_32_1002_avs.def @@ -28,6 +28,8 @@ EXPORTS property_destroy property_file_write property_insert_read + property_clear_error + property_get_error property_mem_write property_read_query_memsize property_search diff --git a/src/imports/import_32_1101_avs.def b/src/imports/import_32_1101_avs.def index 10c59e4f..776dc712 100644 --- a/src/imports/import_32_1101_avs.def +++ b/src/imports/import_32_1101_avs.def @@ -26,6 +26,8 @@ EXPORTS property_desc_to_buffer @246 NONAME property_destroy @247 NONAME property_insert_read @255 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_create @266 NONAME property_node_datasize @267 NONAME property_node_name @274 NONAME diff --git a/src/imports/import_32_1304_avs.def b/src/imports/import_32_1304_avs.def index f83d5a5f..18dad23a 100644 --- a/src/imports/import_32_1304_avs.def +++ b/src/imports/import_32_1304_avs.def @@ -25,6 +25,8 @@ EXPORTS property_desc_to_buffer @201 NONAME property_destroy @264 NONAME property_insert_read @23 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_create @316 NONAME property_node_datasize @249 NONAME property_node_name @255 NONAME diff --git a/src/imports/import_32_1306_avs.def b/src/imports/import_32_1306_avs.def index f963ec88..f6deed72 100644 --- a/src/imports/import_32_1306_avs.def +++ b/src/imports/import_32_1306_avs.def @@ -25,6 +25,8 @@ EXPORTS property_desc_to_buffer @201 NONAME == XC058ba50000cd property_destroy @264 NONAME == XC058ba500010f property_insert_read @23 NONAME == XC058ba5000016 + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_create @316 NONAME == XC058ba5000143 property_node_datasize @249 NONAME == XC058ba5000100 property_node_name @255 NONAME == XC058ba5000106 diff --git a/src/imports/import_32_1403_avs.def b/src/imports/import_32_1403_avs.def index de171c12..716d0817 100644 --- a/src/imports/import_32_1403_avs.def +++ b/src/imports/import_32_1403_avs.def @@ -24,6 +24,8 @@ EXPORTS property_desc_to_buffer @131 NONAME property_destroy @130 NONAME property_insert_read @133 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_name @573 NONAME == property_node_read @573 NONAME == property_node_remove @148 NONAME diff --git a/src/imports/import_32_1508_avs.def b/src/imports/import_32_1508_avs.def index d1625126..504a303f 100644 --- a/src/imports/import_32_1508_avs.def +++ b/src/imports/import_32_1508_avs.def @@ -26,6 +26,8 @@ EXPORTS property_desc_to_buffer @129 NONAME property_destroy @128 NONAME property_insert_read @131 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_create @145 NONAME property_node_name @150 NONAME property_node_read @154 NONAME == XCd229cc0000f3 diff --git a/src/imports/import_32_1601_avs.def b/src/imports/import_32_1601_avs.def index 74332d48..286806bf 100644 --- a/src/imports/import_32_1601_avs.def +++ b/src/imports/import_32_1601_avs.def @@ -19,6 +19,8 @@ EXPORTS property_destroy @125 NONAME property_desc_to_buffer @126 NONAME property_insert_read @128 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_search @141 NONAME property_node_create @142 NONAME property_node_name @147 NONAME == XCnbrep7000092 diff --git a/src/imports/import_32_1603_avs.def b/src/imports/import_32_1603_avs.def index 21f55c1e..93fd07d2 100644 --- a/src/imports/import_32_1603_avs.def +++ b/src/imports/import_32_1603_avs.def @@ -19,6 +19,8 @@ EXPORTS property_destroy @146 NONAME property_desc_to_buffer @147 NONAME property_insert_read @149 NONAME + property_clear_error @158 NONAME == XCnbrep700009d + property_get_error @159 NONAME == XCnbrep700009e property_search @162 NONAME property_node_create @163 NONAME property_node_name @168 NONAME == XCnbrep70000a7 diff --git a/src/imports/import_32_1700_avs.def b/src/imports/import_32_1700_avs.def index 7dc4eaa8..f01bc391 100644 --- a/src/imports/import_32_1700_avs.def +++ b/src/imports/import_32_1700_avs.def @@ -21,6 +21,8 @@ EXPORTS property_destroy @146 NONAME property_desc_to_buffer @147 NONAME property_insert_read @149 NONAME + property_clear_error @158 NONAME == XCgsqzn000009d + property_get_error @159 NONAME == XCgsqzn000009e property_search @162 NONAME property_node_create @163 NONAME property_node_name @168 NONAME == XCgsqzn00000a7 diff --git a/src/imports/import_32_803_avs.def b/src/imports/import_32_803_avs.def index 37f2b3e6..9340dad5 100644 --- a/src/imports/import_32_803_avs.def +++ b/src/imports/import_32_803_avs.def @@ -25,6 +25,8 @@ EXPORTS property_destroy property_file_write property_insert_read + property_clear_error + property_get_error property_mem_write property_read_query_memsize property_search diff --git a/src/imports/import_64_1508_avs.def b/src/imports/import_64_1508_avs.def index 6aeaba1d..1a93e7e5 100644 --- a/src/imports/import_64_1508_avs.def +++ b/src/imports/import_64_1508_avs.def @@ -26,6 +26,8 @@ EXPORTS property_desc_to_buffer @129 NONAME property_destroy @128 NONAME property_insert_read @131 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_create @145 NONAME property_node_name @150 NONAME property_node_read @154 NONAME == XCd229cc0000f3 diff --git a/src/imports/import_64_1509_avs.def b/src/imports/import_64_1509_avs.def index fde35d65..a596513a 100644 --- a/src/imports/import_64_1509_avs.def +++ b/src/imports/import_64_1509_avs.def @@ -26,6 +26,8 @@ EXPORTS property_desc_to_buffer @129 NONAME property_destroy @128 NONAME property_insert_read @131 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_node_create @145 NONAME property_node_name @573 NONAME == property_node_read @573 NONAME == diff --git a/src/imports/import_64_1601_avs.def b/src/imports/import_64_1601_avs.def index 418395f7..960fb947 100644 --- a/src/imports/import_64_1601_avs.def +++ b/src/imports/import_64_1601_avs.def @@ -19,6 +19,8 @@ EXPORTS property_destroy @125 NONAME property_desc_to_buffer @126 NONAME property_insert_read @128 NONAME + property_clear_error @573 NONAME + property_get_error @573 NONAME property_search @141 NONAME property_node_create @142 NONAME property_node_name @147 NONAME == XCnbrep7000092 diff --git a/src/imports/import_64_1603_avs.def b/src/imports/import_64_1603_avs.def index b106ab1b..14f42b7b 100644 --- a/src/imports/import_64_1603_avs.def +++ b/src/imports/import_64_1603_avs.def @@ -19,12 +19,14 @@ EXPORTS property_destroy @146 NONAME property_desc_to_buffer @147 NONAME property_insert_read @149 NONAME + property_clear_error @158 NONAME == XCnbrep700009d + property_get_error @159 NONAME == XCnbrep700009e property_search @162 NONAME property_node_create @163 NONAME property_node_name @168 NONAME == XCnbrep70000a7 property_node_remove @164 NONAME property_node_type @169 NONAME == XCnbrep70000a8 - property_node_clone @165 NONAME + property_node_clone @165 NONAME == XCnbrep70000a4 property_node_traversal @167 NONAME property_node_refdata @166 NONAME == XCnbrep70000a5 property_node_datasize @171 NONAME == XCnbrep70000aa diff --git a/src/imports/import_64_1700_avs.def b/src/imports/import_64_1700_avs.def index 8cddba8a..3e456b50 100644 --- a/src/imports/import_64_1700_avs.def +++ b/src/imports/import_64_1700_avs.def @@ -21,6 +21,8 @@ EXPORTS property_destroy @146 NONAME property_desc_to_buffer @147 NONAME property_insert_read @149 NONAME + property_clear_error @158 NONAME == XCgsqzn000009d + property_get_error @159 NONAME == XCgsqzn000009e property_search @162 NONAME property_node_create @163 NONAME property_node_name @168 NONAME == XCgsqzn00000a7 diff --git a/src/main/avs-util/Module.mk b/src/main/avs-util/Module.mk index 8db06918..2e59f579 100644 --- a/src/main/avs-util/Module.mk +++ b/src/main/avs-util/Module.mk @@ -3,4 +3,5 @@ libs += avs-util libs_avs-util := \ src_avs-util := \ + core-interop.c \ error.c \ diff --git a/src/main/avs-util/core-interop.c b/src/main/avs-util/core-interop.c new file mode 100644 index 00000000..555930f1 --- /dev/null +++ b/src/main/avs-util/core-interop.c @@ -0,0 +1,16 @@ +#include "core/log.h" +#include "core/thread.h" + +#include "imports/avs.h" + +void avs_util_core_interop_log_avs_impl_set() +{ + core_log_impl_set( + log_body_misc, log_body_info, log_body_warning, log_body_fatal); +} + +void avs_util_core_interop_thread_avs_impl_set() +{ + core_thread_impl_set( + avs_thread_create, avs_thread_join, avs_thread_destroy); +} \ No newline at end of file diff --git a/src/main/avs-util/core-interop.h b/src/main/avs-util/core-interop.h new file mode 100644 index 00000000..51b1e119 --- /dev/null +++ b/src/main/avs-util/core-interop.h @@ -0,0 +1,7 @@ +#ifndef AVS_UTIL_CORE_INTEROP_H +#define AVS_UTIL_CORE_INTEROP_H + +void avs_util_core_interop_log_avs_impl_set(); +void avs_util_core_interop_thread_avs_impl_set(); + +#endif \ No newline at end of file diff --git a/src/main/avs-util/error.c b/src/main/avs-util/error.c index ab09c014..d31b2e18 100644 --- a/src/main/avs-util/error.c +++ b/src/main/avs-util/error.c @@ -96,4 +96,14 @@ const char *avs_util_error_str(avs_error error) } return avs_util_error_unknown; +} + +const char *avs_util_property_error_get_and_clear(struct property *prop) +{ + avs_error error; + + error = property_get_error(prop); + property_clear_error(prop); + + return avs_util_error_str(error); } \ No newline at end of file diff --git a/src/main/avs-util/error.h b/src/main/avs-util/error.h index 35988515..88d68c5b 100644 --- a/src/main/avs-util/error.h +++ b/src/main/avs-util/error.h @@ -5,4 +5,6 @@ const char *avs_util_error_str(avs_error error); -#endif \ No newline at end of file +const char *avs_util_property_error_get_and_clear(struct property *prop); + +#endif diff --git a/src/main/core/Module.mk b/src/main/core/Module.mk new file mode 100644 index 00000000..bd35e6d0 --- /dev/null +++ b/src/main/core/Module.mk @@ -0,0 +1,20 @@ +libs += core + +libs_core := \ + util \ + +src_core := \ + log-bt-ext.c \ + log-bt.c \ + log-sink-async.c \ + log-sink-debug.c \ + log-sink-file.c \ + log-sink-list.c \ + log-sink-mutex.c \ + log-sink-null.c \ + log-sink-std.c \ + log.c \ + thread-crt-ext.c \ + thread-crt.c \ + thread.c \ + diff --git a/src/main/core/log-bt-ext.c b/src/main/core/log-bt-ext.c new file mode 100644 index 00000000..b1aa4f46 --- /dev/null +++ b/src/main/core/log-bt-ext.c @@ -0,0 +1,67 @@ +#include + +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log-sink-file.h" +#include "core/log-sink-list.h" +#include "core/log-sink-mutex.h" +#include "core/log-sink-std.h" +#include "core/log.h" + +void core_log_bt_ext_impl_set() +{ + core_log_impl_set( + core_log_bt_log_misc, + core_log_bt_log_info, + core_log_bt_log_warning, + core_log_bt_log_fatal); +} + +void core_log_bt_ext_init_with_stdout() +{ + struct core_log_sink sink; + + core_log_sink_std_out_open(true, &sink); + core_log_bt_init(&sink); +} + +void core_log_bt_ext_init_with_stderr() +{ + struct core_log_sink sink; + + core_log_sink_std_err_open(true, &sink); + core_log_bt_init(&sink); +} + +void core_log_bt_ext_init_with_debug() +{ + struct core_log_sink sink; + + core_log_sink_debug_open(&sink); + core_log_bt_init(&sink); +} + +void core_log_bt_ext_init_with_file( + const char *path, bool append, bool rotate, uint8_t max_rotations) +{ + struct core_log_sink sink; + + core_log_sink_file_open(path, append, rotate, max_rotations, &sink); + core_log_bt_init(&sink); +} + +void core_log_bt_ext_init_with_stdout_and_file( + const char *path, bool append, bool rotate, uint8_t max_rotations) +{ + struct core_log_sink sinks[2]; + struct core_log_sink sink_composed; + struct core_log_sink sink_mutex; + + core_log_sink_std_out_open(true, &sinks[0]); + core_log_sink_file_open(path, append, rotate, max_rotations, &sinks[1]); + core_log_sink_list_open(sinks, 2, &sink_composed); + + core_log_sink_mutex_open(&sink_composed, &sink_mutex); + + core_log_bt_init(&sink_mutex); +} \ No newline at end of file diff --git a/src/main/core/log-bt-ext.h b/src/main/core/log-bt-ext.h new file mode 100644 index 00000000..dcb9a9a6 --- /dev/null +++ b/src/main/core/log-bt-ext.h @@ -0,0 +1,59 @@ +#ifndef CORE_LOG_BT_EXT_H +#define CORE_LOG_BT_EXT_H + +#include +#include + +/** + * Set the current thread API implementation to use the bemanitools log + * implementation + */ +void core_log_bt_ext_impl_set(); + +/** + * Helper to setup the bemanitools log implementation with a stdout sink. + */ +void core_log_bt_ext_init_with_stdout(); + +/** + * Helper to setup the bemanitools log implementation with a stderr sink. + */ +void core_log_bt_ext_init_with_stderr(); + +/** + * Helper to setup the bemanitools log implementation with a OutputDebugStr + * sink. + */ +void core_log_bt_ext_init_with_debug(); + +/** + * Helper to setup the bemanitools log implementation with a file sink + * + * @param path Path to the log file to write the log output to + * @param append If true, then append to an existing file, false to overwrite + * any existing file + * @param rotate If true, rotates an existing log file and creates a new one + * for this session + * @param max_rotations Max number of rotations for the log files + */ +void core_log_bt_ext_init_with_file( + const char *path, bool append, bool rotate, uint8_t max_rotations); + +/** + * Helper to setup the bemanitools log implementation with a stdout and file + * sink + * + * Important: This combined sink is guarded by a mutex to avoid data races on + * logging to two different sinks. + * + * @param path Path to the log file to write the log output to + * @param append If true, then append to an existing file, false to overwrite + * any existing file + * @param rotate If true, rotates an existing log file and creates a new one + * for this session + * @param max_rotations Max number of rotations for the log files + */ +void core_log_bt_ext_init_with_stdout_and_file( + const char *path, bool append, bool rotate, uint8_t max_rotations); + +#endif \ No newline at end of file diff --git a/src/main/core/log-bt.c b/src/main/core/log-bt.c new file mode 100644 index 00000000..fd868979 --- /dev/null +++ b/src/main/core/log-bt.c @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "core/log-bt.h" +#include "core/log-sink.h" +#include "core/log.h" + +#include "util/mem.h" +#include "util/str.h" + +static enum core_log_bt_log_level _core_log_bt_log_level; +static struct core_log_sink *_core_log_bt_sink; + +static void _core_log_bt_vformat_write( + enum core_log_bt_log_level level, + const char *module, + const char *fmt, + va_list ap) +{ + static const char chars[] = "FFWIM"; + + char timestamp[64]; + /* 64k so we can log data dumps of rs232 without crashing */ + char msg[65536]; + char line[65536]; + int result; + + time_t curtime; + struct tm *tm; + + curtime = 0; + tm = NULL; + + curtime = time(NULL); + tm = localtime(&curtime); + + strftime(timestamp, sizeof(timestamp), "[%Y/%m/%d %H:%M:%S]", tm); + + str_vformat(msg, sizeof(msg), fmt, ap); + + result = str_format( + line, + sizeof(line), + "%s %c:%s: %s\n", + timestamp, + chars[level], + module, + msg); + + _core_log_bt_sink->write(_core_log_bt_sink->ctx, line, result); +} + +void core_log_bt_init(const struct core_log_sink *sink) +{ + if (sink == NULL) { + abort(); + } + + _core_log_bt_sink = xmalloc(sizeof(struct core_log_sink)); + memcpy(_core_log_bt_sink, sink, sizeof(struct core_log_sink)); + + _core_log_bt_log_level = CORE_LOG_BT_LOG_LEVEL_OFF; +} + +void core_log_bt_level_set(enum core_log_bt_log_level level) +{ + _core_log_bt_log_level = level; +} + +void core_log_bt_fini() +{ + log_assert(_core_log_bt_sink); + + _core_log_bt_sink->close(_core_log_bt_sink->ctx); + + free(_core_log_bt_sink); +} + +void core_log_bt_log_fatal(const char *module, const char *fmt, ...) +{ + va_list ap; + + if (_core_log_bt_log_level >= CORE_LOG_BT_LOG_LEVEL_FATAL) { + va_start(ap, fmt); + _core_log_bt_vformat_write( + CORE_LOG_BT_LOG_LEVEL_FATAL, module, fmt, ap); + va_end(ap); + } +} + +void core_log_bt_log_warning(const char *module, const char *fmt, ...) +{ + va_list ap; + + if (_core_log_bt_log_level >= CORE_LOG_BT_LOG_LEVEL_WARNING) { + va_start(ap, fmt); + _core_log_bt_vformat_write( + CORE_LOG_BT_LOG_LEVEL_WARNING, module, fmt, ap); + va_end(ap); + } +} + +void core_log_bt_log_info(const char *module, const char *fmt, ...) +{ + va_list ap; + + if (_core_log_bt_log_level >= CORE_LOG_BT_LOG_LEVEL_INFO) { + va_start(ap, fmt); + _core_log_bt_vformat_write(CORE_LOG_BT_LOG_LEVEL_INFO, module, fmt, ap); + va_end(ap); + } +} + +void core_log_bt_log_misc(const char *module, const char *fmt, ...) +{ + va_list ap; + + if (_core_log_bt_log_level >= CORE_LOG_BT_LOG_LEVEL_MISC) { + va_start(ap, fmt); + _core_log_bt_vformat_write(CORE_LOG_BT_LOG_LEVEL_MISC, module, fmt, ap); + va_end(ap); + } +} + +void core_log_bt_direct_sink_write(const char *chars, size_t nchars) +{ + _core_log_bt_sink->write(_core_log_bt_sink->ctx, chars, nchars); +} \ No newline at end of file diff --git a/src/main/core/log-bt.h b/src/main/core/log-bt.h new file mode 100644 index 00000000..2a8052f3 --- /dev/null +++ b/src/main/core/log-bt.h @@ -0,0 +1,87 @@ +#ifndef CORE_LOG_BT_H +#define CORE_LOG_BT_H + +#include "core/log-sink.h" + +/** + * Log API implementation for games/applications without AVS + */ + +enum core_log_bt_log_level { + CORE_LOG_BT_LOG_LEVEL_OFF = 0, + CORE_LOG_BT_LOG_LEVEL_FATAL = 1, + CORE_LOG_BT_LOG_LEVEL_WARNING = 2, + CORE_LOG_BT_LOG_LEVEL_INFO = 3, + CORE_LOG_BT_LOG_LEVEL_MISC = 4, +}; + +/** + * Initialize the logging backend + * + * This must be called as early as possible in your application to setup + * a logging sink according to your needs. Until this is finished, no + * log output is available. + * + * By default, logging is turned off entirely and must be enabled by setting + * a desired logging level explicitly. + * + * @param sink Pointer to a log sink implementation. The caller owns the memory + * of this. + */ +void core_log_bt_init(const struct core_log_sink *sink); + +/** + * Set the current logging level. This can be changed at any given time, e.g. + * to increase/decrease verbosity. + * + * @param level The logging level to set. + */ +void core_log_bt_level_set(enum core_log_bt_log_level level); + +/** + * Cleanup the logging backend. + * + * Ensure to call this on application exit and cleanup. + */ +void core_log_bt_fini(); + +/** + * Implementation of the log API. + */ +void core_log_bt_log_fatal(const char *module, const char *fmt, ...); + +/** + * Implementation of the log API. + */ +void core_log_bt_log_warning(const char *module, const char *fmt, ...); + +/** + * Implementation of the log API. + */ +void core_log_bt_log_info(const char *module, const char *fmt, ...); + +/** + * Implementation of the log API. + */ +void core_log_bt_log_misc(const char *module, const char *fmt, ...); + +/** + * Allow AVS to by-pass the core log API/engine. + * + * This function must only be called by AVS in an appropriate log callback + * function that is passed to avs_boot. + * + * AVS has it's own logging engine and manages aspects such as async logging, + * log levels and decorating log messages. + * + * Thus, proper interoperability only requires the writer/sink part to be shared + * with AVS. + * + * @param chars Buffer with text data to write to the configured sinks. The + * buffer might contain several log messages separated by newline + * characters. + * @param nchars Number of chars to write to the sink. + */ +void core_log_bt_direct_sink_write(const char *chars, size_t nchars); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-async.c b/src/main/core/log-sink-async.c new file mode 100644 index 00000000..f327aad8 --- /dev/null +++ b/src/main/core/log-sink-async.c @@ -0,0 +1,23 @@ +#include + +#include "core/log-sink.h" + +static void +_core_log_sink_file_write(void *ctx, const char *chars, size_t nchars) +{ + // TODO +} + +static void _core_log_sink_file_close(void *ctx) +{ + // TODO +} + +void core_log_sink_async_open(struct core_log_sink *sink) +{ + // TODO + + sink->ctx = NULL; + sink->write = _core_log_sink_file_write; + sink->close = _core_log_sink_file_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-async.h b/src/main/core/log-sink-async.h new file mode 100644 index 00000000..47aa49e7 --- /dev/null +++ b/src/main/core/log-sink-async.h @@ -0,0 +1,19 @@ +#ifndef CORE_LOG_SINK_ASYNC_H +#define CORE_LOG_SINK_ASYNC_H + +#include +#include + +#include "core/log-sink.h" + +/** + * Open a async log sink + * + * The sink passes data to log to a separate thread which executes the actual + * logging of the data. + * + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_async_open(struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-debug.c b/src/main/core/log-sink-debug.c new file mode 100644 index 00000000..c1dd7a20 --- /dev/null +++ b/src/main/core/log-sink-debug.c @@ -0,0 +1,23 @@ +#include + +#include + +#include "core/log-sink.h" + +static void +_core_log_sink_debug_write(void *ctx, const char *chars, size_t nchars) +{ + OutputDebugStringA(chars); +} + +static void _core_log_sink_debug_close(void *ctx) +{ + // noop +} + +void core_log_sink_debug_open(struct core_log_sink *sink) +{ + sink->ctx = NULL; + sink->write = _core_log_sink_debug_write; + sink->close = _core_log_sink_debug_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-debug.h b/src/main/core/log-sink-debug.h new file mode 100644 index 00000000..4c21d50c --- /dev/null +++ b/src/main/core/log-sink-debug.h @@ -0,0 +1,15 @@ +#ifndef CORE_LOG_SINK_DEBUG_H +#define CORE_LOG_SINK_DEBUG_H + +#include + +#include "core/log-sink.h" + +/** + * Open a log sink that uses OutputDebugStr + * + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_debug_open(struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-file.c b/src/main/core/log-sink-file.c new file mode 100644 index 00000000..f2ca188e --- /dev/null +++ b/src/main/core/log-sink-file.c @@ -0,0 +1,92 @@ +#include + +#include +#include +#include +#include + +#include "core/log-sink.h" + +#include "util/fs.h" +#include "util/str.h" + +static void _core_log_sink_file_rotate(const char *path, uint8_t max_rotations) +{ + uint8_t i; + char rotate_file[MAX_PATH]; + char rotate_file_next[MAX_PATH]; + char version[8]; + char version_next[8]; + + for (i = max_rotations; i > 0; i++) { + str_cpy(rotate_file, sizeof(rotate_file), path); + str_cpy(rotate_file_next, sizeof(rotate_file_next), path); + + if (i - 1 != 0) { + sprintf(version, ".%d", i); + } else { + memset(version, 0, sizeof(version)); + } + + sprintf(version_next, ".%d", i); + + str_cat(rotate_file, sizeof(rotate_file), version); + str_cat(rotate_file_next, sizeof(rotate_file_next), version_next); + + if (path_exists(rotate_file)) { + CopyFile(rotate_file, rotate_file_next, FALSE); + } + } +} + +static void +_core_log_sink_file_write(void *ctx, const char *chars, size_t nchars) +{ + FILE *file; + + file = (FILE *) ctx; + + fwrite(chars, 1, nchars, file); +} + +static void _core_log_sink_file_close(void *ctx) +{ + FILE *file; + + file = (FILE *) ctx; + + fflush(file); + fclose(file); +} + +void core_log_sink_file_open( + const char *path, + bool append, + bool rotate, + uint8_t max_rotations, + struct core_log_sink *sink) +{ + FILE *file; + + if (rotate) { + _core_log_sink_file_rotate(path, max_rotations); + + // Appending doesn't matter when file is rotated anyway + file = fopen(path, "w+"); + } else { + if (append) { + file = fopen(path, "a+"); + } else { + file = fopen(path, "w+"); + } + } + + if (!file) { + printf("Cannot open log file: %s", path); + abort(); + } + + sink->ctx = (void *) file; + sink->write = _core_log_sink_file_write; + sink->close = _core_log_sink_file_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-file.h b/src/main/core/log-sink-file.h new file mode 100644 index 00000000..968a1fed --- /dev/null +++ b/src/main/core/log-sink-file.h @@ -0,0 +1,28 @@ +#ifndef CORE_LOG_SINK_FILE_H +#define CORE_LOG_SINK_FILE_H + +#include +#include +#include + +#include "core/log-sink.h" + +/** + * Open a log sink writing data to a file + * + * @param path Path to the log file to write the log output to + * @param append If true, then append to an existing file, false to overwrite + * any existing file + * @param rotate If true, rotates an existing log file and creates a new one + * for this session + * @param max_rotations Max number of rotations for the log files + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_file_open( + const char *path, + bool append, + bool rotate, + uint8_t max_rotations, + struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-list.c b/src/main/core/log-sink-list.c new file mode 100644 index 00000000..38e32112 --- /dev/null +++ b/src/main/core/log-sink-list.c @@ -0,0 +1,66 @@ +#include +#include + +#include "core/log-sink-list.h" +#include "core/log-sink.h" + +#include "util/mem.h" + +#define MAX_SINKS 8 + +struct core_log_sink_list { + struct core_log_sink entries[MAX_SINKS]; + uint8_t num; +}; + +static void +_core_log_sink_list_write(void *ctx, const char *chars, size_t nchars) +{ + struct core_log_sink_list *sink_list; + int i; + + sink_list = (struct core_log_sink_list *) ctx; + + for (i = 0; i < sink_list->num; i++) { + sink_list->entries[i].write(sink_list->entries[i].ctx, chars, nchars); + } +} + +static void _core_log_sink_list_close(void *ctx) +{ + struct core_log_sink_list *sink_list; + int i; + + sink_list = (struct core_log_sink_list *) ctx; + + for (i = 0; i < sink_list->num; i++) { + sink_list->entries[i].close(sink_list->entries[i].ctx); + } + + free(sink_list); +} + +void core_log_sink_list_open( + const struct core_log_sink *entry, uint8_t num, struct core_log_sink *sink) +{ + struct core_log_sink_list *sink_list; + int i; + + if (num > MAX_SINKS) { + abort(); + } + + sink_list = xmalloc(sizeof(struct core_log_sink_list)); + + for (i = 0; i < num; i++) { + sink_list->entries[i].ctx = entry[i].ctx; + sink_list->entries[i].write = entry[i].write; + sink_list->entries[i].close = entry[i].close; + } + + sink_list->num = num; + + sink->ctx = (void *) sink_list; + sink->write = _core_log_sink_list_write; + sink->close = _core_log_sink_list_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-list.h b/src/main/core/log-sink-list.h new file mode 100644 index 00000000..f1e99f35 --- /dev/null +++ b/src/main/core/log-sink-list.h @@ -0,0 +1,24 @@ +#ifndef CORE_LOG_SINK_LIST_H +#define CORE_LOG_SINK_LIST_H + +#include +#include + +#include "core/log-sink.h" + +/** + * Combine multiple log sinks into a list of sinks. + * + * Upon invoking a list sink, all sinks contained within the list are + * being invoked in the configured order. + * + * @param entry A pointer to allocated memory with a sequence of opened sinks + * that you want to add to the list. Ownership of these sinks + * is transferred, i.e. closing the list sink closes its children. + * @param num The number of elements in the sequence of opened sinks pointed to. + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_list_open( + const struct core_log_sink *entry, uint8_t num, struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-mutex.c b/src/main/core/log-sink-mutex.c new file mode 100644 index 00000000..13c8b207 --- /dev/null +++ b/src/main/core/log-sink-mutex.c @@ -0,0 +1,53 @@ +#include + +#include + +#include "core/log-sink.h" + +#include "util/mem.h" + +struct core_log_sink_mutex_ctx { + struct core_log_sink *child; + HANDLE mutex; +}; + +static void +_core_log_sink_mutex_write(void *ctx_, const char *chars, size_t nchars) +{ + struct core_log_sink_mutex_ctx *ctx; + + ctx = (struct core_log_sink_mutex_ctx *) ctx_; + + WaitForSingleObject(ctx->mutex, INFINITE); + + ctx->child->write(ctx->child->ctx, chars, nchars); + + ReleaseMutex(ctx->mutex); +} + +static void _core_log_sink_mutex_close(void *ctx_) +{ + struct core_log_sink_mutex_ctx *ctx; + + ctx = (struct core_log_sink_mutex_ctx *) ctx_; + + CloseHandle(ctx->mutex); + + ctx->child->close(ctx->child->ctx); + free(ctx); +} + +void core_log_sink_mutex_open( + const struct core_log_sink *child_sink, struct core_log_sink *sink) +{ + struct core_log_sink_mutex_ctx *ctx; + + ctx = xmalloc(sizeof(struct core_log_sink_mutex_ctx)); + + memcpy(ctx->child, child_sink, sizeof(struct core_log_sink)); + ctx->mutex = CreateMutex(NULL, FALSE, NULL); + + sink->ctx = ctx; + sink->write = _core_log_sink_mutex_write; + sink->close = _core_log_sink_mutex_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-mutex.h b/src/main/core/log-sink-mutex.h new file mode 100644 index 00000000..e30357e4 --- /dev/null +++ b/src/main/core/log-sink-mutex.h @@ -0,0 +1,21 @@ +#ifndef CORE_LOG_SINK_MUTEX_H +#define CORE_LOG_SINK_MUTEX_H + +#include + +#include "core/log-sink.h" + +/** + * Create a sink that surrounds another sink with a mutex. + * + * Use this to make other sink implementations thread-safe. + * + * @param child_sink Another opened sink to surround with the mutex. Ownership + * of the sink is transferred, i.e. closing the mutex sink + * also closes the wrapped child sink. + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_mutex_open( + const struct core_log_sink *child_sink, struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-null.c b/src/main/core/log-sink-null.c new file mode 100644 index 00000000..7655a208 --- /dev/null +++ b/src/main/core/log-sink-null.c @@ -0,0 +1,21 @@ +#include + +#include "core/log-sink.h" + +static void +_core_log_sink_null_write(void *ctx, const char *chars, size_t nchars) +{ + // noop +} + +static void _core_log_sink_null_close(void *ctx) +{ + // noop +} + +void core_log_sink_null_open(struct core_log_sink *sink) +{ + sink->ctx = NULL; + sink->write = _core_log_sink_null_write; + sink->close = _core_log_sink_null_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-null.h b/src/main/core/log-sink-null.h new file mode 100644 index 00000000..ebd2babe --- /dev/null +++ b/src/main/core/log-sink-null.h @@ -0,0 +1,17 @@ +#ifndef CORE_LOG_SINK_NULL_H +#define CORE_LOG_SINK_NULL_H + +#include + +#include "core/log-sink.h" + +/** + * Create a null/dummy sink. + * + * Use this to disable any logging entirely. + * + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_null_open(struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink-std.c b/src/main/core/log-sink-std.c new file mode 100644 index 00000000..197fd12a --- /dev/null +++ b/src/main/core/log-sink-std.c @@ -0,0 +1,193 @@ +#include + +#include + +#include "core/log-sink.h" + +#include "util/mem.h" + +struct core_log_sink_std_ctx { + HANDLE handle; + bool color; +}; + +static char _core_log_sink_std_determine_color(const char *str) +{ + /* Add some color to make spotting warnings/errors easier. + Based on debug output level identifier. */ + + /* Avoids colored output on strings like "Windows" */ + if (str[1] != ':') { + return 15; + } + + switch (str[0]) { + /* green */ + case 'M': + return 10; + /* blue */ + case 'I': + return 9; + /* yellow */ + case 'W': + return 14; + /* red */ + case 'F': + return 12; + /* default console color */ + default: + return 15; + } +} + +static size_t _core_log_sink_std_msg_coloring_len(const char *str) +{ + // Expected format example: "I:boot: my log message" + + const char *ptr; + size_t len; + int colon_count; + + ptr = str; + len = 0; + colon_count = 0; + + while (true) { + // End of string = invalid log format + if (*ptr == '\0') { + return 0; + } + + if (*ptr == ':') { + colon_count++; + } + + if (colon_count == 2) { + // Skip current colon, next char is a space + return len + 1; + } + + len++; + ptr++; + } + + return 0; +} + +static void +_core_log_sink_std_write(void *ctx_, const char *chars, size_t nchars) +{ + static const size_t timestamp_len = strlen("[----/--/-- --:--:--]"); + + struct core_log_sink_std_ctx *ctx; + + char color; + size_t color_len; + size_t msg_len; + const char *msg_start; + const char *msg_end; + DWORD written; + DWORD write_pos; + + ctx = (struct core_log_sink_std_ctx *) ctx_; + + if (ctx->color) { + write_pos = 0; + + // Support multiple buffered log messages, e.g. from the AVS logging + // engine + while (write_pos < nchars) { + // Expects the AVS timestamp format + msg_start = chars + timestamp_len + 1; // +1 is the space + + color_len = _core_log_sink_std_msg_coloring_len(msg_start); + + // Check if we could detect which part to color, otherwise just + // write the whole log message without any coloring logic + if (color_len > 0) { + color = _core_log_sink_std_determine_color(msg_start); + + // Timestamp + WriteConsole( + ctx->handle, chars, timestamp_len + 1, &written, NULL); + write_pos += written; + chars += written; + + // Log level + module colored + SetConsoleTextAttribute(ctx->handle, color); + WriteConsole(ctx->handle, chars, color_len, &written, NULL); + write_pos += written; + chars += written; + SetConsoleTextAttribute(ctx->handle, 15); + + msg_end = strchr(chars, '\n'); + + if (msg_end != NULL) { + msg_len = msg_end - chars; + + // Write \n as well + msg_len++; + + // Write actual message non colored + WriteConsole(ctx->handle, chars, msg_len, &written, NULL); + write_pos += written; + chars += written; + } else { + WriteConsole( + ctx->handle, chars, nchars - write_pos, &written, NULL); + write_pos += written; + chars += written; + } + } else { + WriteConsole( + ctx->handle, + chars + write_pos, + nchars - write_pos, + &written, + NULL); + write_pos += written; + } + } + } else { + WriteConsole(ctx->handle, chars, nchars, &written, NULL); + } +} + +static void _core_log_sink_std_close(void *ctx_) +{ + struct core_log_sink_std_ctx *ctx; + + ctx = (struct core_log_sink_std_ctx *) ctx_; + + // Remark: Don't close the ctx->handle, see win API docs + + free(ctx); +} + +void core_log_sink_std_out_open(bool color, struct core_log_sink *sink) +{ + struct core_log_sink_std_ctx *ctx; + + ctx = xmalloc(sizeof(struct core_log_sink_std_ctx)); + + ctx->handle = GetStdHandle(STD_OUTPUT_HANDLE); + ctx->color = color; + + sink->ctx = (void *) ctx; + sink->write = _core_log_sink_std_write; + sink->close = _core_log_sink_std_close; +} + +void core_log_sink_std_err_open(bool color, struct core_log_sink *sink) +{ + struct core_log_sink_std_ctx *ctx; + + ctx = xmalloc(sizeof(struct core_log_sink_std_ctx)); + + ctx->handle = GetStdHandle(STD_ERROR_HANDLE); + ctx->color = color; + + sink->ctx = (void *) ctx; + sink->write = _core_log_sink_std_write; + sink->close = _core_log_sink_std_close; +} \ No newline at end of file diff --git a/src/main/core/log-sink-std.h b/src/main/core/log-sink-std.h new file mode 100644 index 00000000..e0e006bf --- /dev/null +++ b/src/main/core/log-sink-std.h @@ -0,0 +1,24 @@ +#ifndef CORE_LOG_SINK_STD_H +#define CORE_LOG_SINK_STD_H + +#include + +#include "core/log-sink.h" + +/** + * Create a sink that writes to stdout. + * + * @param color If true, messages are colored by log level. + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_std_out_open(bool color, struct core_log_sink *sink); + +/** + * Create a sink that writes to stderr. + * + * @param color If true, messages are colored by log level. + * @param sink Pointer to allocated memory that receives the opened sink + */ +void core_log_sink_std_err_open(bool color, struct core_log_sink *sink); + +#endif \ No newline at end of file diff --git a/src/main/core/log-sink.h b/src/main/core/log-sink.h new file mode 100644 index 00000000..7fdd42fb --- /dev/null +++ b/src/main/core/log-sink.h @@ -0,0 +1,45 @@ +#ifndef CORE_LOG_SINK_H +#define CORE_LOG_SINK_H + +#include + +/** + * Write function for a log sink implementation. + * + * Write the given data to your target output destination. + * + * @param ctx Context defined by the implementation when opening the sink. + * @param chars Buffer with text data to log. This can contain partial data of + * a single log line, a full log line terminated by a newline + * character or multiple log lines (each terminated by a newline + * character). + * @param nchars Number of characters to write. + */ +typedef void (*core_log_sink_write_t)( + void *ctx, const char *chars, size_t nchars); + +/** + * Close your log sink and cleanup resources + * + * Depending on your implementation, you might want to flush any + * outstanding/buffered data. + * + * @param ctx Context defined by the implementation when opening the sink. + */ +typedef void (*core_log_sink_close_t)(void *ctx); + +/** + * Log sink structure. + * + * This must be set-up and populated when opening your log sink implementation. + * The ctx field contains any arbitrary data that you need for your log sink + * to operate, e.g. a file handle, additional buffers etc. Make sure these + * resources are cleaned up upon closing the sink. + */ +struct core_log_sink { + void *ctx; + core_log_sink_write_t write; + core_log_sink_close_t close; +}; + +#endif \ No newline at end of file diff --git a/src/main/core/log.c b/src/main/core/log.c new file mode 100644 index 00000000..9d2d6d76 --- /dev/null +++ b/src/main/core/log.c @@ -0,0 +1,74 @@ +#include + +#include "core/log.h" + +core_log_message_t _core_log_misc_impl; +core_log_message_t _core_log_info_impl; +core_log_message_t _core_log_warning_impl; +core_log_message_t _core_log_fatal_impl; + +void core_log_impl_set( + core_log_message_t misc, + core_log_message_t info, + core_log_message_t warning, + core_log_message_t fatal) +{ + if (misc == NULL || info == NULL || warning == NULL || fatal == NULL) { + abort(); + } + + _core_log_misc_impl = misc; + _core_log_info_impl = info; + _core_log_warning_impl = warning; + _core_log_fatal_impl = fatal; +} + +void core_log_impl_assign(core_log_impl_set_t impl_set) +{ + if (_core_log_misc_impl == NULL || _core_log_info_impl == NULL || + _core_log_warning_impl == NULL || _core_log_fatal_impl == NULL) { + abort(); + } + + impl_set( + _core_log_misc_impl, + _core_log_info_impl, + _core_log_warning_impl, + _core_log_fatal_impl); +} + +core_log_message_t core_log_misc_impl_get() +{ + if (_core_log_misc_impl == NULL) { + abort(); + } + + return _core_log_misc_impl; +} + +core_log_message_t core_log_info_impl_get() +{ + if (_core_log_info_impl == NULL) { + abort(); + } + + return _core_log_info_impl; +} + +core_log_message_t core_log_warning_impl_get() +{ + if (_core_log_warning_impl == NULL) { + abort(); + } + + return _core_log_warning_impl; +} + +core_log_message_t core_log_fatal_impl_get() +{ + if (_core_log_fatal_impl == NULL) { + abort(); + } + + return _core_log_fatal_impl; +} \ No newline at end of file diff --git a/src/main/core/log.h b/src/main/core/log.h new file mode 100644 index 00000000..5b5cd9da --- /dev/null +++ b/src/main/core/log.h @@ -0,0 +1,197 @@ +#ifndef CORE_LOG_H +#define CORE_LOG_H + +#include +#include + +#include "util/defs.h" + +/** + * The core log API of bemanitools. + * + * To a large extent, this reflects the AVS logging API and allows for swapping + * out the backends with different implementations. Most games should have some + * version of the AVS API available while some (legacy) games do not. These + * can use a bemanitools private logging implementation by configuring it + * in the bootstrapping process. + */ + +/* BUILD_MODULE is passed in as a command-line #define by the makefile */ + +#ifndef LOG_MODULE +#define LOG_MODULE STRINGIFY(BUILD_MODULE) +#endif + +/** + * Log a message on misc level + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The macro is required to make things work with varargs. + * The log message is only printed if the log level is set to misc + * + * @param fmt printf format string + * @param ... Additional arguments according to the specified arguments in the + * printf format string + */ +#define log_misc(...) _core_log_misc_impl(LOG_MODULE, __VA_ARGS__) + +/** + * Log a message on info level + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The macro is required to make things work with varargs. + * The log message is only printed if the log level is set to info or lower + * + * @param fmt printf format string + * @param ... Additional arguments according to the specified arguments in the + * printf format string + */ +#define log_info(...) _core_log_info_impl(LOG_MODULE, __VA_ARGS__) + +/** + * Log a message on warning level + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The macro is required to make things work with varargs. + * The log message is only printed if the log level is set to warning or lower + * + * @param fmt printf format string + * @param ... Additional arguments according to the specified arguments in the + * printf format string + */ +#define log_warning(...) _core_log_warning_impl(LOG_MODULE, __VA_ARGS__) + +/** + * Log a message on fatal level + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The macro is required to make things work with varargs. + * The log message is only printed if the log level is set to fatal. + * + * This call will also terminate the application. + * + * @param fmt printf format string + * @param ... Additional arguments according to the specified arguments in the + * printf format string + */ +#define log_fatal(...) \ + do { \ + _core_log_fatal_impl(LOG_MODULE, __VA_ARGS__); \ + abort(); \ + } while (0) + +/** + * Log a message and terminate the application if given condition fails + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The macro is required to make things work with varargs. + * + * @param x Condition to evaluate. If false, the application terminates + */ +#define log_assert(x) \ + do { \ + if (!(x)) { \ + _core_log_fatal_impl( \ + "assert", \ + "%s:%d: function `%s'", \ + __FILE__, \ + __LINE__, \ + __FUNCTION__); \ + abort(); \ + } \ + } while (0) + +/** + * Log a message in an exception handler + * + * Only use this function in an exception handler, e.g. for stack traces. It + * logs the message on fatal level but does not terminate. + * + * @param fmt printf format string + * @param ... Additional arguments according to the specified arguments in the + * printf format string + */ +#define log_exception_handler(...) \ + _core_log_fatal_impl("exception", __VA_ARGS__) + +typedef void (*core_log_message_t)(const char *module, const char *fmt, ...); + +typedef void (*core_log_impl_set_t)( + core_log_message_t misc, + core_log_message_t info, + core_log_message_t warning, + core_log_message_t fatal); + +/** + * Configure the log API implementations + * + * Advised to do this as early in your application/library module as possible + * as calls to the getter functions below will return the currently configured + * implementations. + * + * @param misc Pointer to a function implementing logging on misc level + * @param info Pointer to a function implementing logging on info level + * @param warning Pointer to a function implementing logging on warning level + * @param fatal Pointer to a function implementing logging on fatal level + */ +void core_log_impl_set( + core_log_message_t misc, + core_log_message_t info, + core_log_message_t warning, + core_log_message_t fatal); + +/** + * Supporting function to inject/assign the currently set implementation + * with the given setter function. + * + * @param impl_set Setter function to call with the currently configured log + * function implementations + */ +void core_log_impl_assign(core_log_impl_set_t impl_set); + +/** + * Get the currently configured implementation of the misc level log function + * + * @return Pointer to the currently configured implementation of the function + */ +core_log_message_t core_log_misc_impl_get(); + +/** + * Get the currently configured implementation of the info level log function + * + * @return Pointer to the currently configured implementation of the function + */ +core_log_message_t core_log_info_impl_get(); + +/** + * Get the currently configured implementation of the warning level log function + * + * @return Pointer to the currently configured implementation of the function + */ +core_log_message_t core_log_warning_impl_get(); + +/** + * Get the currently configured implementation of the fatal level log function + * + * @return Pointer to the currently configured implementation of the function + */ +core_log_message_t core_log_fatal_impl_get(); + +// Do not use these directly. +// These are only here to allow usage in the macros above. +extern core_log_message_t _core_log_misc_impl; +extern core_log_message_t _core_log_info_impl; +extern core_log_message_t _core_log_warning_impl; +extern core_log_message_t _core_log_fatal_impl; + +#endif \ No newline at end of file diff --git a/src/main/core/thread-crt-ext.c b/src/main/core/thread-crt-ext.c new file mode 100644 index 00000000..eb403ade --- /dev/null +++ b/src/main/core/thread-crt-ext.c @@ -0,0 +1,8 @@ +#include "core/thread-crt.h" +#include "core/thread.h" + +void core_thread_crt_ext_impl_set() +{ + core_thread_impl_set( + core_thread_crt_create, core_thread_crt_join, core_thread_crt_destroy); +} \ No newline at end of file diff --git a/src/main/core/thread-crt-ext.h b/src/main/core/thread-crt-ext.h new file mode 100644 index 00000000..a17b29b6 --- /dev/null +++ b/src/main/core/thread-crt-ext.h @@ -0,0 +1,9 @@ +#ifndef CORE_THREAD_CRT_EXT_H +#define CORE_THREAD_CRT_EXT_H + +/** + * Set the current thread API implementation to use the C runtime thread API + */ +void core_thread_crt_ext_impl_set(); + +#endif \ No newline at end of file diff --git a/src/main/core/thread-crt.c b/src/main/core/thread-crt.c new file mode 100644 index 00000000..0251cf12 --- /dev/null +++ b/src/main/core/thread-crt.c @@ -0,0 +1,62 @@ +#include +#include + +#include +#include + +#include "core/thread-crt.h" +#include "core/thread.h" + +#include "util/defs.h" + +struct shim_ctx { + HANDLE barrier; + int (*proc)(void *); + void *ctx; +}; + +static unsigned int STDCALL crt_thread_shim(void *outer_ctx) +{ + struct shim_ctx *sctx = outer_ctx; + int (*proc)(void *); + void *inner_ctx; + + proc = sctx->proc; + inner_ctx = sctx->ctx; + + SetEvent(sctx->barrier); + + return proc(inner_ctx); +} + +int core_thread_crt_create( + int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority) +{ + struct shim_ctx sctx; + uintptr_t thread_id; + + sctx.barrier = CreateEvent(NULL, TRUE, FALSE, NULL); + sctx.proc = proc; + sctx.ctx = ctx; + + thread_id = _beginthreadex(NULL, stack_sz, crt_thread_shim, &sctx, 0, NULL); + + WaitForSingleObject(sctx.barrier, INFINITE); + CloseHandle(sctx.barrier); + + return (int) thread_id; +} + +void core_thread_crt_destroy(int thread_id) +{ + CloseHandle((HANDLE) (uintptr_t) thread_id); +} + +void core_thread_crt_join(int thread_id, int *result) +{ + WaitForSingleObject((HANDLE) (uintptr_t) thread_id, INFINITE); + + if (result) { + GetExitCodeThread((HANDLE) (uintptr_t) thread_id, (DWORD *) result); + } +} diff --git a/src/main/core/thread-crt.h b/src/main/core/thread-crt.h new file mode 100644 index 00000000..e9c1e8c0 --- /dev/null +++ b/src/main/core/thread-crt.h @@ -0,0 +1,15 @@ +#ifndef CORE_THREAD_CRT_H +#define CORE_THREAD_CRT_H + +#include + +/** + * Thread API implementation using the C runtime API + */ + +int core_thread_crt_create( + int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority); +void core_thread_crt_join(int thread_id, int *result); +void core_thread_crt_destroy(int thread_id); + +#endif diff --git a/src/main/core/thread.c b/src/main/core/thread.c new file mode 100644 index 00000000..f4e5bfeb --- /dev/null +++ b/src/main/core/thread.c @@ -0,0 +1,78 @@ +#include + +#include "core/log.h" +#include "core/thread.h" + +core_thread_create_t core_thread_create_impl; +core_thread_join_t core_thread_join_impl; +core_thread_destroy_t core_thread_destroy_impl; + +int core_thread_create( + int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority) +{ + log_assert(core_thread_create_impl); + + return core_thread_create_impl(proc, ctx, stack_sz, priority); +} + +void core_thread_join(int thread_id, int *result) +{ + log_assert(core_thread_join_impl); + + core_thread_join_impl(thread_id, result); +} + +void core_thread_destroy(int thread_id) +{ + log_assert(core_thread_destroy_impl); + + core_thread_destroy_impl(thread_id); +} + +void core_thread_impl_set( + core_thread_create_t create, + core_thread_join_t join, + core_thread_destroy_t destroy) +{ + if (create == NULL || join == NULL || destroy == NULL) { + abort(); + } + + core_thread_create_impl = create; + core_thread_join_impl = join; + core_thread_destroy_impl = destroy; +} + +void core_thread_impl_assign(core_thread_impl_set_t impl_set) +{ + if (core_thread_create_impl == NULL || core_thread_join_impl == NULL || + core_thread_destroy_impl == NULL) { + abort(); + } + + impl_set( + core_thread_create_impl, + core_thread_join_impl, + core_thread_destroy_impl); +} + +core_thread_create_t core_thread_create_impl_get() +{ + log_assert(core_thread_create_impl); + + return core_thread_create_impl; +} + +core_thread_join_t core_thread_join_impl_get() +{ + log_assert(core_thread_join_impl); + + return core_thread_join_impl; +} + +core_thread_destroy_t core_thread_destroy_impl_get() +{ + log_assert(core_thread_destroy_impl); + + return core_thread_destroy_impl; +} diff --git a/src/main/core/thread.h b/src/main/core/thread.h new file mode 100644 index 00000000..88115665 --- /dev/null +++ b/src/main/core/thread.h @@ -0,0 +1,117 @@ +#ifndef CORE_THREAD_H +#define CORE_THREAD_H + +#include + +/** + * The core thread API of bemanitools. + * + * This essentially reflects the AVS threading API and allows for swapping out + * the backends with different implementations. Most games should have some + * version of the AVS API available while some (legacy) games do not. These + * can use a bemanitools private threading implementation by configuring it + * in the bootstrapping process. + */ + +/** + * Create a thread + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * @param proc The function to run in a separate thread + * @param ctx Additional data to pass to the function as a parameter + * @param stack_sz The stack size to allocate for the thread in bytes + * @param priority The thread's priority + * @return The ID of the thread once created and started + */ +int core_thread_create( + int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority); + +/** + * Wait for a thread to finish + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The caller of this function blocks until the thread has finished executing. + * + * @param thread_id ID of the thread to wait for + * @param result Pointer to a variable to write the return value of the function + * the thread executed to + */ +void core_thread_join(int thread_id, int *result); + +/** + * Destroy a thread + * + * Always use this interface in your application which hides the currently + * configured implementation. + * + * The thread must have finished execution before calling this. It is advised + * to make threads terminate their execution flow, join them and destroy. + * + * @param thread_id The ID of the thread to destroy. + */ +void core_thread_destroy(int thread_id); + +typedef int (*core_thread_create_t)( + int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority); +typedef void (*core_thread_join_t)(int thread_id, int *result); +typedef void (*core_thread_destroy_t)(int thread_id); + +typedef void (*core_thread_impl_set_t)( + core_thread_create_t create, + core_thread_join_t join, + core_thread_destroy_t destroy); + +/** + * Configure the thread API implementations + * + * Advised to do this as early in your application/library module as possible + * as calls to the getter functions below will return the currently configured + * implementations. + * + * @param create Pointer to a function implementing thread creation + * @param join Pointer to a function implementing joining of a thread + * @param destroy Pointer to a function implementing destroying of a thread + */ +void core_thread_impl_set( + core_thread_create_t create, + core_thread_join_t join, + core_thread_destroy_t destroy); + +/** + * Supporting function to inject/assign the currently set implementation + * with the given setter function. + * + * @param impl_set Setter function to call with the currently configured thread + * function implementations + */ +void core_thread_impl_assign(core_thread_impl_set_t impl_set); + +/** + * Get the currently configured implementation for thread_create + * + * @return Pointer to the currently configured implementation of the + * thread_create function + */ +core_thread_create_t core_thread_create_impl_get(); + +/** + * Get the currently configured implementation for thread_join + * + * @return Pointer to the currently configured implementation of the thread_join + * function + */ +core_thread_join_t core_thread_join_impl_get(); + +/** + * Get the currently configured implementation for thread_destroy + * + * @return Pointer to the currently configured implementation of the + * thread_destroy function + */ +core_thread_destroy_t core_thread_destroy_impl_get(); + +#endif diff --git a/src/main/hook/table.c b/src/main/hook/table.c index ed67b3b6..067ea75f 100644 --- a/src/main/hook/table.c +++ b/src/main/hook/table.c @@ -15,12 +15,21 @@ static const size_t apiset_prefix_len = sizeof(apiset_prefix) - 1; static void hook_table_apply_to_all( const char *depname, const struct hook_symbol *syms, size_t nsyms); +static void hook_table_revert_to_all( + const char *depname, const struct hook_symbol *syms, size_t nsyms); + static void hook_table_apply_to_iid( HMODULE target, const pe_iid_t *iid, const struct hook_symbol *syms, size_t nsyms); +static void hook_table_revert_to_iid( + HMODULE target, + const pe_iid_t *iid, + const struct hook_symbol *syms, + size_t nsyms); + static bool hook_table_match_module( HMODULE target, const char *iid_name, const char *depname); @@ -44,6 +53,23 @@ static void hook_table_apply_to_all( } } +static void hook_table_revert_to_all( + const char *depname, const struct hook_symbol *syms, size_t nsyms) +{ + const peb_dll_t *dll; + HMODULE pe; + + for (dll = peb_dll_get_first(); dll != NULL; dll = peb_dll_get_next(dll)) { + pe = peb_dll_get_base(dll); + + if (pe == NULL) { + continue; /* ?? Happens sometimes. */ + } + + hook_table_revert(pe, depname, syms, nsyms); + } +} + void hook_table_apply( HMODULE target, const char *depname, @@ -73,6 +99,35 @@ void hook_table_apply( } } +void hook_table_revert( + HMODULE target, + const char *depname, + const struct hook_symbol *syms, + size_t nsyms) +{ + const pe_iid_t *iid; + const char *iid_name; + + assert(depname != NULL); + assert(syms != NULL || nsyms == 0); + + if (target == NULL) { + /* Call out, which will then call us back repeatedly. Awkward, but + viewed from the outside it's good for usability. */ + + hook_table_revert_to_all(depname, syms, nsyms); + } else { + for (iid = pe_iid_get_first(target); iid != NULL; + iid = pe_iid_get_next(target, iid)) { + iid_name = pe_iid_get_name(target, iid); + + if (hook_table_match_module(target, iid_name, depname)) { + hook_table_revert_to_iid(target, iid, syms, nsyms); + } + } + } +} + static void hook_table_apply_to_iid( HMODULE target, const pe_iid_t *iid, @@ -101,6 +156,33 @@ static void hook_table_apply_to_iid( } } +static void hook_table_revert_to_iid( + HMODULE target, + const pe_iid_t *iid, + const struct hook_symbol *syms, + size_t nsyms) +{ + struct pe_iat_entry iate; + size_t i; + size_t j; + const struct hook_symbol *sym; + + i = 0; + + while (pe_iid_get_iat_entry(target, iid, i++, &iate) == S_OK) { + for (j = 0; j < nsyms; j++) { + sym = &syms[j]; + + if (hook_table_match_proc(&iate, sym)) { + // Only revert-able if the original pointer was stored previously + if (sym->link != NULL && *sym->link != NULL) { + pe_patch(iate.ppointer, sym->link, sizeof(*sym->link)); + } + } + } + } +} + static bool hook_table_match_module( HMODULE target, const char *iid_name, const char *depname) {