From 573840aba655f9a146bfa7107197330985fdc69a Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:52:59 +0200 Subject: [PATCH 01/90] feat: Add core module This module contains the "core" (API) of bemanitools which includes an abstraction layer for threads and logging at this time. The threads API is very close to what util/thread already was with some structural enhancements which make it easier to understand and work with the API, I hope. Some additional helpers (*-ext module) support in doing common tasks, e.g. setting up the thread API with other modules. The log(ging) part receives a major overhaul to address known limitations and issues with the util/log module: - Cleaner API layer - Separate sinks from actual logging engine - Sinks are composable - Improved and cleaner compatibility layer with AVS logging API Additional "extensions" (*-ext modules) add various helper functions for common tasks like setting up the logging engine with a file and stdout sink. The sinks also improved significantly with the file sink now supporting proper appending and log rotation. Logging to stdout/stderr supports coloring of log messages which works across logging engines. Overall, this refactored foundation is expected to support future developments and removes known limitations at the current scale of bemanitools such as: - Reducing boiler plate code across hooks - Interop of bemanitools and AVS (and setting the foundation for addressing currently missing interop, e.g. for dealing with property structures without AVS) - Addressing performance issues in the logging engine due to incorrect interop with AVS --- Module.mk | 1 + src/main/core/Module.mk | 20 ++++ src/main/core/log-bt-ext.c | 67 +++++++++++ src/main/core/log-bt-ext.h | 59 ++++++++++ src/main/core/log-bt.c | 129 +++++++++++++++++++++ src/main/core/log-bt.h | 87 +++++++++++++++ src/main/core/log-sink-async.c | 23 ++++ src/main/core/log-sink-async.h | 19 ++++ src/main/core/log-sink-debug.c | 23 ++++ src/main/core/log-sink-debug.h | 15 +++ src/main/core/log-sink-file.c | 92 +++++++++++++++ src/main/core/log-sink-file.h | 28 +++++ src/main/core/log-sink-list.c | 66 +++++++++++ src/main/core/log-sink-list.h | 24 ++++ src/main/core/log-sink-mutex.c | 53 +++++++++ src/main/core/log-sink-mutex.h | 21 ++++ src/main/core/log-sink-null.c | 21 ++++ src/main/core/log-sink-null.h | 17 +++ src/main/core/log-sink-std.c | 193 ++++++++++++++++++++++++++++++++ src/main/core/log-sink-std.h | 24 ++++ src/main/core/log-sink.h | 45 ++++++++ src/main/core/log.c | 74 +++++++++++++ src/main/core/log.h | 197 +++++++++++++++++++++++++++++++++ src/main/core/thread-crt-ext.c | 8 ++ src/main/core/thread-crt-ext.h | 9 ++ src/main/core/thread-crt.c | 62 +++++++++++ src/main/core/thread-crt.h | 15 +++ src/main/core/thread.c | 78 +++++++++++++ src/main/core/thread.h | 117 ++++++++++++++++++++ 29 files changed, 1587 insertions(+) create mode 100644 src/main/core/Module.mk create mode 100644 src/main/core/log-bt-ext.c create mode 100644 src/main/core/log-bt-ext.h create mode 100644 src/main/core/log-bt.c create mode 100644 src/main/core/log-bt.h create mode 100644 src/main/core/log-sink-async.c create mode 100644 src/main/core/log-sink-async.h create mode 100644 src/main/core/log-sink-debug.c create mode 100644 src/main/core/log-sink-debug.h create mode 100644 src/main/core/log-sink-file.c create mode 100644 src/main/core/log-sink-file.h create mode 100644 src/main/core/log-sink-list.c create mode 100644 src/main/core/log-sink-list.h create mode 100644 src/main/core/log-sink-mutex.c create mode 100644 src/main/core/log-sink-mutex.h create mode 100644 src/main/core/log-sink-null.c create mode 100644 src/main/core/log-sink-null.h create mode 100644 src/main/core/log-sink-std.c create mode 100644 src/main/core/log-sink-std.h create mode 100644 src/main/core/log-sink.h create mode 100644 src/main/core/log.c create mode 100644 src/main/core/log.h create mode 100644 src/main/core/thread-crt-ext.c create mode 100644 src/main/core/thread-crt-ext.h create mode 100644 src/main/core/thread-crt.c create mode 100644 src/main/core/thread-crt.h create mode 100644 src/main/core/thread.c create mode 100644 src/main/core/thread.h diff --git a/Module.mk b/Module.mk index 16a1177f..26972251 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 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 From 170277100c10ac6eeacb6b79ef5b2dd1f23d08be Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:52:59 +0200 Subject: [PATCH 02/90] feat: Add helper to set avs implementations Doesn't really reduce boiler plate but adds clarity with a more meaningful function name what the operation does. --- src/main/avs-util/Module.mk | 1 + src/main/avs-util/core-interop.c | 16 ++++++++++++++++ src/main/avs-util/core-interop.h | 7 +++++++ 3 files changed, 24 insertions(+) create mode 100644 src/main/avs-util/core-interop.c create mode 100644 src/main/avs-util/core-interop.h 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 From 82d1a2df5212fe11fa19885e2c0934e770b58152 Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:52:59 +0200 Subject: [PATCH 03/90] refactor(api): Thread and log API Split files and add name spacing. --- src/api/log.h | 31 +++++++++++++++++++++++++++++++ src/api/thread.h | 20 ++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 src/api/log.h create mode 100644 src/api/thread.h diff --git a/src/api/log.h b/src/api/log.h new file mode 100644 index 00000000..638a269a --- /dev/null +++ b/src/api/log.h @@ -0,0 +1,31 @@ +#ifndef BEMANITOOLS_API_THREAD_H +#define BEMANITOOLS_API_THREAD_H + +#include + +#ifdef __GNUC__ +/* Bemanitools is compiled with GCC (MinGW, specifically) as of version 5 */ +#define LOG_CHECK_FMT __attribute__((format(printf, 2, 3))) +#else +/* Compile it out for MSVC plebs */ +#define LOG_CHECK_FMT +#endif + +/* An AVS-style logger function. Comes in four flavors: misc, info, warning, + and fatal, with increasing severity. Fatal loggers do not return, they + abort the running process after writing their message to the log. + + "module" is an arbitrary short string identifying the source of the log + message. The name of the calling DLL is a good default choice for this + string, although you might want to identify a module within your DLL here + instead. + + "fmt" is a printf-style format string. Depending on the context in which + your DLL is running you might end up calling a logger function exported + from libavs, which has its own printf implementation (including a number of + proprietary extensions), so don't use any overly exotic formats. */ + +typedef void (*btapi_log_formatter_t)(const char *module, const char *fmt, ...) + LOG_CHECK_FMT; + +#endif diff --git a/src/api/thread.h b/src/api/thread.h new file mode 100644 index 00000000..0459c9d1 --- /dev/null +++ b/src/api/thread.h @@ -0,0 +1,20 @@ +#ifndef BEMANITOOLS_API_THREAD_H +#define BEMANITOOLS_API_THREAD_H + +#include + +/* An API for spawning threads. This API is defined by libavs, although + Bemanitools itself may supply compatible implementations of these functions + to your DLL, depending on the context in which it runs. + + NOTE: You may only use the logging functions from a thread where Bemanitools + calls you, or a thread that you create using this API. Failure to observe + this restriction will cause the process to crash. This is a limitation of + libavs itself, not Bemanitools. */ + +typedef int (*btapi_thread_create_t)( + int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority); +typedef void (*btapi_thread_join_t)(int thread_id, int *result); +typedef void (*btapi_thread_destroy_t)(int thread_id); + +#endif From 93e0a090bc58ba95f4b97df411ddd0ffc80dffd7 Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 04/90] refactor: Entire code base, thread and log usage Boils down to: - Include headers - Reduce boiler plate with helpers - Swap out explicit usages with core API layer and ensure the right API is configured beforehand --- src/main/aciodrv-proc/Module.mk | 1 + src/main/aciodrv-proc/panb.c | 11 +++--- src/main/aciodrv/device.c | 3 +- src/main/aciodrv/h44b.c | 2 +- src/main/aciodrv/icca.c | 2 +- src/main/aciodrv/kfca.c | 2 +- src/main/aciodrv/panb.c | 3 +- src/main/aciodrv/port.c | 2 +- src/main/aciodrv/rvol.c | 2 +- src/main/acioemu/addr.c | 2 +- src/main/acioemu/emu.c | 3 +- src/main/acioemu/hdxs.c | 2 +- src/main/acioemu/pipe.h | 3 +- src/main/aciomgr/Module.mk | 1 + src/main/aciomgr/manager.c | 7 ++-- src/main/aciotest/Module.mk | 1 + src/main/aciotest/main.c | 14 ++++++- src/main/asio/asio-reghook.c | 7 ++-- src/main/asio/config-asio.c | 4 +- src/main/bio2drv/Module.mk | 5 ++- src/main/bio2drv/bi2a-iidx.c | 2 +- src/main/bio2drv/bi2a-sdvx.c | 2 +- src/main/bio2drv/config-bio2.c | 4 +- src/main/bio2drv/detect.c | 6 +-- src/main/bio2emu/emu.c | 3 +- src/main/bio2emu/setupapi.c | 3 +- src/main/bsthook/Module.mk | 2 + src/main/bsthook/acio.c | 3 +- src/main/bsthook/dllmain.c | 27 ++++++++----- src/main/bsthook/gfx.c | 3 +- src/main/bsthook/settings.c | 3 +- src/main/camhook/cam.c | 8 ++-- src/main/camhook/config-cam.c | 4 +- src/main/cconfig/cconfig-main.c | 7 ++-- src/main/cconfig/cconfig-util.c | 7 ++-- src/main/cconfig/cconfig-util.h | 4 +- src/main/cconfig/cconfig.c | 3 +- src/main/cconfig/cmd.c | 3 +- src/main/cconfig/conf.c | 3 +- src/main/config/Module.mk | 1 + src/main/config/analogs.c | 3 +- src/main/config/buttons.c | 3 +- src/main/config/eam.c | 3 +- src/main/config/gametype.c | 3 +- src/main/config/main.c | 42 +++++++++++++++------ src/main/config/snap.c | 3 +- src/main/config/usages.c | 3 +- src/main/d3d9exhook/config-gfx.c | 4 +- src/main/d3d9exhook/d3d9ex.c | 3 +- src/main/ddrhook-util/_com4.c | 3 +- src/main/ddrhook-util/dinput.c | 3 +- src/main/ddrhook-util/extio.c | 3 +- src/main/ddrhook-util/gfx.c | 3 +- src/main/ddrhook-util/misc.c | 3 +- src/main/ddrhook-util/monitor.c | 3 +- src/main/ddrhook-util/p3io.c | 4 +- src/main/ddrhook-util/spike.c | 3 +- src/main/ddrhook-util/usbmem.c | 3 +- src/main/ddrhook1/Module.mk | 2 + src/main/ddrhook1/avs-boot.c | 11 +++++- src/main/ddrhook1/config-ddrhook1.c | 4 +- src/main/ddrhook1/config-eamuse.c | 3 +- src/main/ddrhook1/config-gfx.c | 4 +- src/main/ddrhook1/config-security.c | 3 +- src/main/ddrhook1/dllmain.c | 42 +++++++++++++++++---- src/main/ddrhook1/filesystem.c | 3 +- src/main/ddrhook1/master.c | 3 +- src/main/ddrhook2/Module.mk | 2 + src/main/ddrhook2/dllmain.c | 28 +++++++++----- src/main/ddrhook2/master.c | 3 +- src/main/ddrio-async/Module.mk | 1 + src/main/ddrio-async/ddrio.c | 9 +++-- src/main/ddrio-mm/Module.mk | 1 + src/main/ddrio-mm/ddrio.c | 5 ++- src/main/ddrio-p3io/Module.mk | 1 + src/main/ddrio-p3io/config.c | 2 +- src/main/ddrio-p3io/ddrio.c | 8 ++-- src/main/ddrio-smx/Module.mk | 1 + src/main/ddrio-smx/ddrio.c | 5 ++- src/main/ddrio/ddrio.c | 2 +- src/main/ddriotest/Module.mk | 1 + src/main/ddriotest/main.c | 33 ++++++++++------ src/main/dinput/dinput.c | 3 +- src/main/eamio-icca/Module.mk | 1 + src/main/eamio-icca/config-icc.c | 2 - src/main/eamio-icca/eamio-icca.c | 6 +-- src/main/eamio/Module.mk | 2 +- src/main/eamio/eam-api.c | 9 +++-- src/main/eamio/eam-impl.c | 3 +- src/main/eamiotest/Module.mk | 1 + src/main/eamiotest/main.c | 21 ++++++++--- src/main/extiodrv/device.c | 2 +- src/main/extiodrv/extio.c | 2 +- src/main/extiotest/Module.mk | 1 + src/main/extiotest/main.c | 12 ++++-- src/main/ezusb-emu/device.c | 3 +- src/main/ezusb-emu/node-coin.c | 4 +- src/main/ezusb-emu/node-eeprom.c | 4 +- src/main/ezusb-emu/node-security-mem.c | 4 +- src/main/ezusb-emu/node-security-plug.c | 4 +- src/main/ezusb-emu/node-sram.c | 3 +- src/main/ezusb-emu/node-wdt.c | 4 +- src/main/ezusb-emu/util.c | 3 +- src/main/ezusb-iidx-16seg-emu/node-16seg.c | 4 +- src/main/ezusb-iidx-emu/Module.mk | 1 + src/main/ezusb-iidx-emu/card-mag.c | 3 +- src/main/ezusb-iidx-emu/msg.c | 3 +- src/main/ezusb-iidx-emu/node-fpga.c | 3 +- src/main/ezusb-iidx-emu/node-serial.c | 7 ++-- src/main/ezusb-iidx-fpga-flash/Module.mk | 1 + src/main/ezusb-iidx-fpga-flash/main.c | 8 +++- src/main/ezusb-iidx-sram-flash/Module.mk | 1 + src/main/ezusb-iidx-sram-flash/main.c | 8 +++- src/main/ezusb-iidx/ezusb-iidx.c | 3 +- src/main/ezusb-iidx/fpga.c | 2 +- src/main/ezusb-iidx/sram.c | 4 +- src/main/ezusb-tool/Module.mk | 1 + src/main/ezusb-tool/main.c | 9 +++-- src/main/ezusb/ezusb.c | 3 +- src/main/ezusb/util.c | 3 +- src/main/ezusb2-dbg-hook/Module.mk | 1 + src/main/ezusb2-dbg-hook/main.c | 11 ++++-- src/main/ezusb2-emu/device.c | 3 +- src/main/ezusb2-emu/util.c | 3 +- src/main/ezusb2-iidx-emu/msg.c | 3 +- src/main/ezusb2-popn-emu/msg.c | 3 +- src/main/ezusb2-popn-shim/Module.mk | 1 + src/main/ezusb2-popn-shim/dllmain.c | 4 +- src/main/ezusb2-popn-shim/proxy.c | 2 +- src/main/ezusb2-tool/Module.mk | 1 + src/main/ezusb2-tool/main.c | 9 +++-- src/main/ezusb2/ezusb2.c | 3 +- src/main/geninput/Module.mk | 1 + src/main/geninput/dev-list.c | 3 +- src/main/geninput/hid-generic.c | 3 +- src/main/geninput/hid-meta-in.c | 3 +- src/main/geninput/hid-meta-out.c | 3 +- src/main/geninput/hid-mgr.c | 3 +- src/main/geninput/hid-report-in.c | 3 +- src/main/geninput/hid-report-out.c | 3 +- src/main/geninput/hid.c | 3 +- src/main/geninput/hotplug.c | 4 +- src/main/geninput/input.c | 9 +++-- src/main/geninput/io-thread.c | 11 +++--- src/main/geninput/kbd.c | 3 +- src/main/geninput/mapper.c | 3 +- src/main/geninput/mouse.c | 3 +- src/main/geninput/msg-thread.c | 3 +- src/main/geninput/pacdrive.c | 3 +- src/main/geninput/ri.c | 3 +- src/main/hook/d3d9.c | 6 +-- src/main/hook/table.c | 5 ++- src/main/hooklib/acp.c | 3 +- src/main/hooklib/adapter.c | 3 +- src/main/hooklib/app.c | 3 +- src/main/hooklib/config-adapter.c | 4 +- src/main/hooklib/memfile.c | 3 +- src/main/hooklib/rs232.c | 3 +- src/main/hooklib/setupapi.c | 3 +- src/main/iidx-bio2-exit-hook/Module.mk | 1 + src/main/iidx-bio2-exit-hook/main.c | 8 +++- src/main/iidx-ezusb-exit-hook/Module.mk | 1 + src/main/iidx-ezusb2-exit-hook/Module.mk | 1 + src/main/iidx-irbeat-patch/Module.mk | 1 + src/main/iidxhook-d3d9/bb-scale-hd.c | 4 +- src/main/iidxhook-d3d9/util.h | 4 +- src/main/iidxhook-util/Module.mk | 1 + src/main/iidxhook-util/acio.c | 3 +- src/main/iidxhook-util/chart-patch.c | 3 +- src/main/iidxhook-util/clock.c | 3 +- src/main/iidxhook-util/config-eamuse.c | 4 +- src/main/iidxhook-util/config-ezusb.c | 3 +- src/main/iidxhook-util/config-gfx.c | 4 +- src/main/iidxhook-util/config-io.c | 4 +- src/main/iidxhook-util/config-misc.c | 4 +- src/main/iidxhook-util/config-sec.c | 3 +- src/main/iidxhook-util/d3d9.c | 3 +- src/main/iidxhook-util/eamuse.c | 3 +- src/main/iidxhook-util/effector.c | 3 +- src/main/iidxhook-util/log-server.c | 7 ++-- src/main/iidxhook-util/settings.c | 3 +- src/main/iidxhook1/Module.mk | 1 + src/main/iidxhook1/config-iidxhook1.c | 4 +- src/main/iidxhook1/dllmain.c | 38 ++++++++++++++----- src/main/iidxhook1/ezusb-mon.c | 3 +- src/main/iidxhook1/log-ezusb.c | 3 +- src/main/iidxhook2/Module.mk | 1 + src/main/iidxhook2/config-iidxhook2.c | 4 +- src/main/iidxhook2/dllmain.c | 39 ++++++++++++++----- src/main/iidxhook3/Module.mk | 1 + src/main/iidxhook3/dllmain.c | 38 ++++++++++++++----- src/main/iidxhook4-cn/Module.mk | 2 + src/main/iidxhook4-cn/avs-boot.c | 12 ++++-- src/main/iidxhook4-cn/dllmain.c | 32 +++++++++++++--- src/main/iidxhook4-cn/path.c | 3 +- src/main/iidxhook4/Module.mk | 2 + src/main/iidxhook4/dllmain.c | 26 ++++++++----- src/main/iidxhook5-cn/Module.mk | 2 + src/main/iidxhook5-cn/avs-boot.c | 12 ++++-- src/main/iidxhook5-cn/dllmain.c | 31 ++++++++++++--- src/main/iidxhook5-cn/path.c | 3 +- src/main/iidxhook5/Module.mk | 2 + src/main/iidxhook5/dllmain.c | 26 ++++++++----- src/main/iidxhook5/ifs-snd-redir.c | 3 +- src/main/iidxhook6/Module.mk | 2 + src/main/iidxhook6/dllmain.c | 26 ++++++++----- src/main/iidxhook7/Module.mk | 2 + src/main/iidxhook7/dllmain.c | 26 ++++++++----- src/main/iidxhook8/Module.mk | 2 + src/main/iidxhook8/config-io.c | 4 +- src/main/iidxhook8/dllmain.c | 26 ++++++++----- src/main/iidxhook9/Module.mk | 2 + src/main/iidxhook9/config-io.c | 4 +- src/main/iidxhook9/dllmain.c | 32 ++++++++++------ src/main/iidxio-bio2/Module.mk | 1 + src/main/iidxio-ezusb/Module.mk | 1 + src/main/iidxio-ezusb2/Module.mk | 1 + src/main/iidxiotest/Module.mk | 1 + src/main/iidxiotest/main.c | 21 ++++++++--- src/main/jbhook-util-p3io/gfx.c | 3 +- src/main/jbhook-util-p3io/mixer.c | 3 +- src/main/jbhook-util-p3io/p3io.c | 4 +- src/main/jbhook-util/acio.c | 3 +- src/main/jbhook-util/eamuse.c | 3 +- src/main/jbhook-util/locale.c | 3 +- src/main/jbhook-util/p4io.c | 4 +- src/main/jbhook1/Module.mk | 1 + src/main/jbhook1/avs-boot.c | 12 ++++-- src/main/jbhook1/config-eamuse.c | 3 +- src/main/jbhook1/config-gfx.c | 4 +- src/main/jbhook1/config-security.c | 3 +- src/main/jbhook1/dllmain.c | 41 +++++++++++++++----- src/main/jbhook1/log-gftools.c | 4 +- src/main/jbhook2/Module.mk | 2 + src/main/jbhook2/dllmain.c | 30 +++++++++------ src/main/jbhook2/options.c | 2 +- src/main/jbhook3/Module.mk | 2 + src/main/jbhook3/dllmain.c | 30 +++++++++------ src/main/jbhook3/gfx.c | 4 +- src/main/jbhook3/options.c | 3 +- src/main/jbio-p4io/Module.mk | 1 + src/main/jbio-p4io/config-h44b.c | 4 +- src/main/jbio-p4io/h44b.c | 2 +- src/main/jbio-p4io/jbio.c | 8 ++-- src/main/jbiotest/Module.mk | 1 + src/main/jbiotest/main.c | 21 ++++++++--- src/main/mempatch-hook/Module.mk | 1 + src/main/mempatch-hook/main.c | 14 ++++--- src/main/mm/mm.c | 3 +- src/main/p3io-ddr-tool/Module.mk | 1 + src/main/p3io-ddr-tool/main.c | 21 ++++++----- src/main/p3io-ddr-tool/mode-test.c | 4 +- src/main/p3io/cmd.c | 4 +- src/main/p3io/frame.c | 3 +- src/main/p3iodrv/ddr.c | 2 +- src/main/p3iodrv/device.c | 3 +- src/main/p3ioemu/devmgr.c | 3 +- src/main/p3ioemu/emu.c | 3 +- src/main/p3ioemu/uart.c | 3 +- src/main/p4iodrv/device.c | 3 +- src/main/p4iodrv/usb.c | 3 +- src/main/p4ioemu/device.c | 3 +- src/main/pcbidgen/Module.mk | 1 + src/main/popnhook-util/acio.c | 3 +- src/main/popnhook-util/mixer.c | 3 +- src/main/popnhook1/Module.mk | 1 + src/main/popnhook1/avs-boot.c | 4 +- src/main/popnhook1/config-eamuse.c | 4 +- src/main/popnhook1/config-gfx.c | 4 +- src/main/popnhook1/config-sec.c | 3 +- src/main/popnhook1/d3d9.c | 3 +- src/main/popnhook1/dllmain.c | 38 ++++++++++++++----- src/main/popnhook1/filesystem.c | 3 +- src/main/sdvxhook/Module.mk | 2 + src/main/sdvxhook/acio.c | 3 +- src/main/sdvxhook/dllmain.c | 27 ++++++++----- src/main/sdvxhook/gfx.c | 3 +- src/main/sdvxhook/kfca.c | 3 +- src/main/sdvxhook/lcd.c | 3 +- src/main/sdvxhook2-cn/Module.mk | 2 + src/main/sdvxhook2-cn/acio.c | 3 +- src/main/sdvxhook2-cn/config-cn.c | 4 +- src/main/sdvxhook2-cn/dllmain.c | 19 ++++++---- src/main/sdvxhook2-cn/kfca.c | 3 +- src/main/sdvxhook2-cn/unis-version.c | 3 +- src/main/sdvxhook2/Module.mk | 2 + src/main/sdvxhook2/acio.c | 3 +- src/main/sdvxhook2/config-io.c | 4 +- src/main/sdvxhook2/dllmain.c | 26 ++++++++----- src/main/sdvxhook2/nvapi.c | 4 +- src/main/sdvxhook2/power.c | 4 +- src/main/sdvxio-bio2/Module.mk | 1 + src/main/sdvxio-kfca/Module.mk | 1 + src/main/sdvxio-kfca/config-kfca.c | 4 +- src/main/security/id.c | 3 +- src/main/security/mcode.c | 3 +- src/main/security/rp-blowfish.c | 4 +- src/main/security/rp.c | 3 +- src/main/security/rp2.c | 3 +- src/main/security/rp3.c | 3 +- src/main/unicorntail/Module.mk | 1 + src/main/unicorntail/dllmain.c | 5 ++- src/main/unicorntail/p3io.c | 3 +- src/main/unicorntail/usbmem.c | 3 +- src/main/util/array.c | 3 +- src/main/util/crc.c | 2 +- src/main/util/crypto.c | 9 +++-- src/main/util/fs.c | 3 +- src/main/util/hex.c | 3 +- src/main/util/iobuf.c | 5 ++- src/main/util/mem.c | 2 +- src/main/util/msg-thread.c | 11 +++--- src/main/util/net.c | 3 +- src/main/util/os.c | 3 +- src/main/util/proc.c | 2 +- src/main/util/signal.c | 3 +- src/main/util/time.c | 3 +- src/main/vigem-ddrio/Module.mk | 1 + src/main/vigem-ddrio/config-vigem-ddrio.c | 4 +- src/main/vigem-ddrio/main.c | 26 ++++++++++--- src/main/vigem-iidxio/Module.mk | 1 + src/main/vigem-iidxio/cab-16seg-sequencer.c | 3 +- src/main/vigem-iidxio/cab-light-sequencer.c | 3 +- src/main/vigem-iidxio/config.c | 4 +- src/main/vigem-iidxio/main.c | 22 ++++++++--- src/main/vigem-sdvxio/Module.mk | 1 + src/main/vigem-sdvxio/config-vigem-sdvxio.c | 4 +- src/main/vigem-sdvxio/main.c | 23 ++++++++--- src/main/vigemstub/helper.c | 3 +- src/test/cconfig/Module.mk | 3 ++ src/test/d3d9hook/Module.mk | 2 + src/test/d3d9hook/dllmain.c | 10 +++-- src/test/iidxhook-util/Module.mk | 4 ++ src/test/iidxhook/Module.mk | 2 + src/test/iidxhook8/Module.mk | 2 + src/test/security/Module.mk | 6 +++ src/test/test/test.h | 15 +++++--- src/test/util/Module.mk | 1 + 338 files changed, 1307 insertions(+), 641 deletions(-) diff --git a/src/main/aciodrv-proc/Module.mk b/src/main/aciodrv-proc/Module.mk index e347f778..9fa6b381 100644 --- a/src/main/aciodrv-proc/Module.mk +++ b/src/main/aciodrv-proc/Module.mk @@ -1,6 +1,7 @@ libs += aciodrv-proc libs_aciodrv-proc := \ + core \ src_aciodrv-proc := \ panb.c \ diff --git a/src/main/aciodrv-proc/panb.c b/src/main/aciodrv-proc/panb.c index c054e21f..3f9d5a4f 100644 --- a/src/main/aciodrv-proc/panb.c +++ b/src/main/aciodrv-proc/panb.c @@ -5,8 +5,9 @@ #include "aciodrv/device.h" #include "aciodrv/panb.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/thread.h" + +#include "core/log.h" static int auto_poll_proc(void *auto_poll_param); static int auto_poll_threadid; @@ -50,7 +51,7 @@ bool aciodrv_proc_panb_init(struct aciodrv_device_ctx *device) InitializeCriticalSection(&keypair_lock); InitializeCriticalSection(&auto_poll_stop_lock); auto_poll_threadid = - thread_create(auto_poll_proc, (void *) device, 0x4000, 0); + core_thread_create(auto_poll_proc, (void *) device, 0x4000, 0); return true; } @@ -80,8 +81,8 @@ void aciodrv_proc_panb_fini(struct aciodrv_device_ctx *device) auto_poll_stop = true; LeaveCriticalSection(&auto_poll_stop_lock); - thread_join(auto_poll_threadid, NULL); - thread_destroy(auto_poll_threadid); + core_thread_join(auto_poll_threadid, NULL); + core_thread_destroy(auto_poll_threadid); DeleteCriticalSection(&keypair_lock); DeleteCriticalSection(&auto_poll_stop_lock); diff --git a/src/main/aciodrv/device.c b/src/main/aciodrv/device.c index 39bf8afc..7584968d 100644 --- a/src/main/aciodrv/device.c +++ b/src/main/aciodrv/device.c @@ -7,8 +7,9 @@ #include "aciodrv/port.h" +#include "core/log.h" + #include "util/hex.h" -#include "util/log.h" #include "util/mem.h" /* Enable to dump all data to the logger */ diff --git a/src/main/aciodrv/h44b.c b/src/main/aciodrv/h44b.c index afbab429..19283f64 100644 --- a/src/main/aciodrv/h44b.c +++ b/src/main/aciodrv/h44b.c @@ -7,7 +7,7 @@ #include "aciodrv/device.h" -#include "util/log.h" +#include "core/log.h" bool aciodrv_h44b_init(struct aciodrv_device_ctx *device, uint8_t node_id) { diff --git a/src/main/aciodrv/icca.c b/src/main/aciodrv/icca.c index ac56d0ea..7bec0c77 100644 --- a/src/main/aciodrv/icca.c +++ b/src/main/aciodrv/icca.c @@ -5,7 +5,7 @@ #include "aciodrv/device.h" #include "aciodrv/icca.h" -#include "util/log.h" +#include "core/log.h" static bool aciodrv_icca_queue_loop_start( struct aciodrv_device_ctx *device, uint8_t node_id) diff --git a/src/main/aciodrv/kfca.c b/src/main/aciodrv/kfca.c index e21a0b84..88093e4b 100644 --- a/src/main/aciodrv/kfca.c +++ b/src/main/aciodrv/kfca.c @@ -5,7 +5,7 @@ #include "aciodrv/device.h" -#include "util/log.h" +#include "core/log.h" static bool aciodrv_kfca_watchdog_start(struct aciodrv_device_ctx *device, uint8_t node_id) diff --git a/src/main/aciodrv/panb.c b/src/main/aciodrv/panb.c index 1412e160..2dbc85ed 100644 --- a/src/main/aciodrv/panb.c +++ b/src/main/aciodrv/panb.c @@ -5,8 +5,7 @@ #include "aciodrv/device.h" #include "aciodrv/panb.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/log.h" bool aciodrv_panb_start_auto_input( struct aciodrv_device_ctx *device, uint8_t node_id, uint8_t node_count) diff --git a/src/main/aciodrv/port.c b/src/main/aciodrv/port.c index b38ad2a7..c3c9c71f 100644 --- a/src/main/aciodrv/port.c +++ b/src/main/aciodrv/port.c @@ -6,7 +6,7 @@ #include -#include "util/log.h" +#include "core/log.h" HANDLE aciodrv_port_open(const char *port_path, int baud) { diff --git a/src/main/aciodrv/rvol.c b/src/main/aciodrv/rvol.c index a9e0c36f..ed7c0360 100644 --- a/src/main/aciodrv/rvol.c +++ b/src/main/aciodrv/rvol.c @@ -5,7 +5,7 @@ #include "aciodrv/device.h" -#include "util/log.h" +#include "core/log.h" static bool aciodrv_rvol_change_expand_mode( struct aciodrv_device_ctx *device, uint8_t node_id, uint8_t mode) diff --git a/src/main/acioemu/addr.c b/src/main/acioemu/addr.c index 8d85487e..3f52fbb5 100644 --- a/src/main/acioemu/addr.c +++ b/src/main/acioemu/addr.c @@ -5,7 +5,7 @@ #include "acioemu/addr.h" #include "acioemu/emu.h" -#include "util/log.h" +#include "core/log.h" void ac_io_emu_cmd_assign_addrs( struct ac_io_emu *emu, const struct ac_io_message *req, uint8_t node_count) diff --git a/src/main/acioemu/emu.c b/src/main/acioemu/emu.c index 11cf35a4..57f74e05 100644 --- a/src/main/acioemu/emu.c +++ b/src/main/acioemu/emu.c @@ -14,9 +14,10 @@ #include "acioemu/emu.h" #include "acioemu/pipe.h" +#include "core/log.h" + #include "hook/iohook.h" -#include "util/log.h" #include "util/str.h" static HRESULT ac_io_emu_open(struct ac_io_emu *emu, struct irp *irp); diff --git a/src/main/acioemu/hdxs.c b/src/main/acioemu/hdxs.c index 9a38beee..01ec19df 100644 --- a/src/main/acioemu/hdxs.c +++ b/src/main/acioemu/hdxs.c @@ -6,7 +6,7 @@ #include "acioemu/emu.h" #include "acioemu/hdxs.h" -#include "util/log.h" +#include "core/log.h" static void ac_io_emu_hdxs_cmd_send_version( struct ac_io_emu_hdxs *hdxs, const struct ac_io_message *req); diff --git a/src/main/acioemu/pipe.h b/src/main/acioemu/pipe.h index ad442fc2..859c3b6f 100644 --- a/src/main/acioemu/pipe.h +++ b/src/main/acioemu/pipe.h @@ -7,9 +7,10 @@ #include "acio/acio.h" +#include "core/log.h" + #include "util/iobuf.h" #include "util/list.h" -#include "util/log.h" /* This uses the USB convention where OUT and IN are from the host's (game's) perspective. So an OUT transaction comes in to us and vice versa. diff --git a/src/main/aciomgr/Module.mk b/src/main/aciomgr/Module.mk index 0a17e8cd..b87f81b6 100644 --- a/src/main/aciomgr/Module.mk +++ b/src/main/aciomgr/Module.mk @@ -1,6 +1,7 @@ dlls += aciomgr libs_aciomgr := \ + core \ aciodrv \ util \ diff --git a/src/main/aciomgr/manager.c b/src/main/aciomgr/manager.c index e73b18e2..d7f03787 100644 --- a/src/main/aciomgr/manager.c +++ b/src/main/aciomgr/manager.c @@ -5,14 +5,15 @@ #include #include "aciomgr/manager-init.h" - #include "aciomgr/manager.h" #include "acio/acio.h" #include "aciodrv/device.h" + +#include "core/log.h" + #include "util/array.h" -#include "util/log.h" #define MAX_PORT_PATH_LENGTH 256 @@ -90,7 +91,7 @@ void aciomgr_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, warning, info, fatal); } struct aciomgr_port_dispatcher *aciomgr_port_init(const char *path, int baud) diff --git a/src/main/aciotest/Module.mk b/src/main/aciotest/Module.mk index af04652b..efeb038f 100644 --- a/src/main/aciotest/Module.mk +++ b/src/main/aciotest/Module.mk @@ -1,6 +1,7 @@ exes += aciotest libs_aciotest := \ + core \ bio2drv \ aciodrv \ aciodrv-proc \ diff --git a/src/main/aciotest/main.c b/src/main/aciotest/main.c index 4a301c41..fdf6df48 100644 --- a/src/main/aciotest/main.c +++ b/src/main/aciotest/main.c @@ -15,7 +15,14 @@ #include "aciotest/panb.h" #include "aciotest/rvol.h" -#include "util/log.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + +#include "core/log.h" static uint8_t aciotest_cnt = 0; static uint8_t bi2a_mode = 255; @@ -112,7 +119,10 @@ int main(int argc, char **argv) } } - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); + + core_log_bt_ext_init_with_stdout(); struct aciodrv_device_ctx *device = aciodrv_device_open_path(argv[1], atoi(argv[2])); diff --git a/src/main/asio/asio-reghook.c b/src/main/asio/asio-reghook.c index 161b13b1..7e058904 100644 --- a/src/main/asio/asio-reghook.c +++ b/src/main/asio/asio-reghook.c @@ -10,13 +10,14 @@ #include +#include "asio/asio-reghook.h" + +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" -#include "asio/asio-reghook.h" - #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/asio/config-asio.c b/src/main/asio/config-asio.c index b29f99c8..2a8675d1 100644 --- a/src/main/asio/config-asio.c +++ b/src/main/asio/config-asio.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "asio/config-asio.h" +#include "core/log.h" -#include "util/log.h" +#include "asio/config-asio.h" #define ASIOHOOK_CONFIG_IO_FORCE_ASIO_KEY "asio.force_asio" #define ASIOHOOK_CONFIG_IO_FORCE_WASAPI_KEY "asio.force_wasapi" diff --git a/src/main/bio2drv/Module.mk b/src/main/bio2drv/Module.mk index f5bdb568..9e5e92b3 100644 --- a/src/main/bio2drv/Module.mk +++ b/src/main/bio2drv/Module.mk @@ -1,10 +1,11 @@ libs += bio2drv libs_bio2drv := \ - aciodrv + core \ + aciodrv \ src_bio2drv := \ detect.c \ config-bio2.c \ bi2a-iidx.c \ - bi2a-sdvx.c + bi2a-sdvx.c \ diff --git a/src/main/bio2drv/bi2a-iidx.c b/src/main/bio2drv/bi2a-iidx.c index 1119876f..bce8c41e 100644 --- a/src/main/bio2drv/bi2a-iidx.c +++ b/src/main/bio2drv/bi2a-iidx.c @@ -7,7 +7,7 @@ #include "aciodrv/device.h" -#include "util/log.h" +#include "core/log.h" // Must be provided on init command. Actual meaning unknown right now. // Not providing this will not initialize the IO correctly resulting diff --git a/src/main/bio2drv/bi2a-sdvx.c b/src/main/bio2drv/bi2a-sdvx.c index 93edf496..14e52395 100644 --- a/src/main/bio2drv/bi2a-sdvx.c +++ b/src/main/bio2drv/bi2a-sdvx.c @@ -7,7 +7,7 @@ #include "aciodrv/device.h" -#include "util/log.h" +#include "core/log.h" static const uint8_t _BIO2DR_BI2A_SDVX_INIT_DATA = 0x3B; diff --git a/src/main/bio2drv/config-bio2.c b/src/main/bio2drv/config-bio2.c index 492cdefd..c016fa94 100644 --- a/src/main/bio2drv/config-bio2.c +++ b/src/main/bio2drv/config-bio2.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "bio2drv/config-bio2.h" +#include "core/log.h" -#include "util/log.h" +#include "bio2drv/config-bio2.h" #define BIO2DRV_CONFIG_BIO2_AUTO_KEY "bio2.autodetect" #define BIO2DRV_CONFIG_BIO2_PORT_KEY "bio2.port" diff --git a/src/main/bio2drv/detect.c b/src/main/bio2drv/detect.c index d559175d..dfdf5013 100644 --- a/src/main/bio2drv/detect.c +++ b/src/main/bio2drv/detect.c @@ -8,11 +8,11 @@ #include "bio2drv/detect.h" +#include "core/log.h" + #include #include -#include "util/log.h" - DEFINE_GUID( GUID_COM_BUS_ENUMERATOR, 0x4D36E978, @@ -142,7 +142,7 @@ void bio2drv_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, warning, info, fatal); } bool bio2drv_detect( diff --git a/src/main/bio2emu/emu.c b/src/main/bio2emu/emu.c index 9fbcb3d8..ea9c7f0d 100644 --- a/src/main/bio2emu/emu.c +++ b/src/main/bio2emu/emu.c @@ -14,6 +14,8 @@ #include "acioemu/addr.h" #include "acioemu/emu.h" +#include "core/log.h" + #include "hook/iohook.h" #include "hooklib/rs232.h" @@ -25,7 +27,6 @@ #include "util/array.h" #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct array bio2_active_ports; diff --git a/src/main/bio2emu/setupapi.c b/src/main/bio2emu/setupapi.c index 5dbfffc7..bb2f32c3 100644 --- a/src/main/bio2emu/setupapi.c +++ b/src/main/bio2emu/setupapi.c @@ -6,13 +6,14 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "bio2emu/emu.h" #include "bio2emu/setupapi.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/bsthook/Module.mk b/src/main/bsthook/Module.mk index ef61d283..92ab3b1c 100644 --- a/src/main/bsthook/Module.mk +++ b/src/main/bsthook/Module.mk @@ -4,6 +4,8 @@ deplibs_bsthook := \ avs \ libs_bsthook := \ + avs-util \ + core \ acioemu \ bstio \ hook \ diff --git a/src/main/bsthook/acio.c b/src/main/bsthook/acio.c index fe5f8cc1..500d4c5b 100644 --- a/src/main/bsthook/acio.c +++ b/src/main/bsthook/acio.c @@ -19,13 +19,14 @@ #include "bsthook/acio.h" #include "bsthook/kfca.h" +#include "core/log.h" + #include "hook/iohook.h" #include "imports/avs.h" #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu ac_io_emu; diff --git a/src/main/bsthook/dllmain.c b/src/main/bsthook/dllmain.c index 57d38e03..e508057c 100644 --- a/src/main/bsthook/dllmain.c +++ b/src/main/bsthook/dllmain.c @@ -2,9 +2,14 @@ #include +#include "avs-util/core-interop.h" + #include "bemanitools/bstio.h" #include "bemanitools/eamio.h" +#include "core/log.h" +#include "core/thread.h" + #include "hook/iohook.h" #include "hooklib/app.h" @@ -18,7 +23,6 @@ #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" static bool my_dll_entry_init(char *sidcode, struct property_node *config); static bool my_dll_entry_main(void); @@ -33,19 +37,23 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *config) log_info("Starting up BeatStream IO backend"); - bst_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(bst_io_set_loggers); - ok = bst_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + ok = bst_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { goto bst_io_fail; } - eam_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(eam_io_set_loggers); - ok = eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + ok = eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { goto eam_io_fail; @@ -91,8 +99,9 @@ BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) return TRUE; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); args_recover(&argc, &argv); diff --git a/src/main/bsthook/gfx.c b/src/main/bsthook/gfx.c index 43d88a2d..23d37e0f 100644 --- a/src/main/bsthook/gfx.c +++ b/src/main/bsthook/gfx.c @@ -3,6 +3,8 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/pe.h" #include "hook/table.h" @@ -10,7 +12,6 @@ #include "sdvxhook/gfx.h" #include "util/defs.h" -#include "util/log.h" static HRESULT STDCALL my_CreateDevice( IDirect3D9 *self, diff --git a/src/main/bsthook/settings.c b/src/main/bsthook/settings.c index 8f48ea8e..c9fb10ae 100644 --- a/src/main/bsthook/settings.c +++ b/src/main/bsthook/settings.c @@ -7,10 +7,11 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* ------------------------------------------------------------------------- */ diff --git a/src/main/camhook/cam.c b/src/main/camhook/cam.c index db380f13..ab40dde3 100644 --- a/src/main/camhook/cam.c +++ b/src/main/camhook/cam.c @@ -17,14 +17,14 @@ #include +#include "camhook/cam.h" + +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" -#include "camhook/cam.h" -#include "camhook/cam-detect.h" - #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #define CAMHOOK_NUM_LAYOUTS 3 diff --git a/src/main/camhook/config-cam.c b/src/main/camhook/config-cam.c index 43a852b6..fc77edd8 100644 --- a/src/main/camhook/config-cam.c +++ b/src/main/camhook/config-cam.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "camhook/config-cam.h" +#include "core/log.h" -#include "util/log.h" +#include "camhook/config-cam.h" #define CAMHOOK_CONFIG_CAM_DISABLE_EMU_KEY "cam.disable_emu" #define CAMHOOK_CONFIG_CAM_DEFAULT_DISABLE_EMU_VALUE false diff --git a/src/main/cconfig/cconfig-main.c b/src/main/cconfig/cconfig-main.c index 342a9120..342d8dfe 100644 --- a/src/main/cconfig/cconfig-main.c +++ b/src/main/cconfig/cconfig-main.c @@ -6,8 +6,9 @@ #include "cconfig/cconfig-main.h" +#include "core/log.h" + #include "util/cmdline.h" -#include "util/log.h" bool cconfig_main_config_init( struct cconfig *config, @@ -84,7 +85,7 @@ bool cconfig_main_config_init( } log_misc("Config state after file loading:"); - cconfig_util_log(config, log_impl_misc); + cconfig_util_log(config, core_log_misc_impl_get()); } log_misc("Parsing override config parameters from cmd"); @@ -96,7 +97,7 @@ bool cconfig_main_config_init( } log_misc("Config state after cmd parameter overrides:"); - cconfig_util_log(config, log_impl_misc); + cconfig_util_log(config, core_log_misc_impl_get()); goto success; diff --git a/src/main/cconfig/cconfig-util.c b/src/main/cconfig/cconfig-util.c index f4ce05f3..35853574 100644 --- a/src/main/cconfig/cconfig-util.c +++ b/src/main/cconfig/cconfig-util.c @@ -5,8 +5,9 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "util/hex.h" -#include "util/log.h" #include "util/mem.h" bool cconfig_util_get_int( @@ -220,10 +221,10 @@ void cconfig_util_set_data( free(str); } -void cconfig_util_log(struct cconfig *config, log_formatter_t log_formatter) +void cconfig_util_log(struct cconfig *config, core_log_message_t log_message) { for (uint32_t i = 0; i < config->nentries; i++) { - log_formatter( + log_message( LOG_MODULE, "%s=%s", config->entries[i].key, diff --git a/src/main/cconfig/cconfig-util.h b/src/main/cconfig/cconfig-util.h index aa9b5004..8f6a3058 100644 --- a/src/main/cconfig/cconfig-util.h +++ b/src/main/cconfig/cconfig-util.h @@ -7,7 +7,7 @@ #include "cconfig/cconfig.h" -#include "util/log.h" +#include "core/log.h" bool cconfig_util_get_int( struct cconfig *config, @@ -57,6 +57,6 @@ void cconfig_util_set_data( size_t len, const char *desc); -void cconfig_util_log(struct cconfig *config, log_formatter_t log_formatter); +void cconfig_util_log(struct cconfig *config, core_log_message_t log_message); #endif \ No newline at end of file diff --git a/src/main/cconfig/cconfig.c b/src/main/cconfig/cconfig.c index 1cfcbedb..6150f66f 100644 --- a/src/main/cconfig/cconfig.c +++ b/src/main/cconfig/cconfig.c @@ -2,7 +2,8 @@ #include "cconfig/cconfig.h" -#include "util/log.h" +#include "core/log.h" + #include "util/mem.h" #include "util/str.h" diff --git a/src/main/cconfig/cmd.c b/src/main/cconfig/cmd.c index 3d4f32fc..3bb1c247 100644 --- a/src/main/cconfig/cmd.c +++ b/src/main/cconfig/cmd.c @@ -7,8 +7,9 @@ #include "cconfig/cmd.h" +#include "core/log.h" + #include "util/hex.h" -#include "util/log.h" #include "util/str.h" static void diff --git a/src/main/cconfig/conf.c b/src/main/cconfig/conf.c index de21f8b9..b23e9d47 100644 --- a/src/main/cconfig/conf.c +++ b/src/main/cconfig/conf.c @@ -6,8 +6,9 @@ #include "cconfig/conf.h" +#include "core/log.h" + #include "util/fs.h" -#include "util/log.h" #include "util/str.h" enum cconfig_conf_error cconfig_conf_load_from_file( diff --git a/src/main/config/Module.mk b/src/main/config/Module.mk index 2f7bac83..fc37a230 100644 --- a/src/main/config/Module.mk +++ b/src/main/config/Module.mk @@ -3,6 +3,7 @@ rc_config := config.rc cppflags_config := -DUNICODE libs_config := \ + core \ eamio \ geninput \ util \ diff --git a/src/main/config/analogs.c b/src/main/config/analogs.c index 5a7bceeb..d6936670 100644 --- a/src/main/config/analogs.c +++ b/src/main/config/analogs.c @@ -11,13 +11,14 @@ #include "config/schema.h" #include "config/usages.h" +#include "core/log.h" + #include "geninput/hid-mgr.h" #include "geninput/input-config.h" #include "geninput/mapper.h" #include "util/array.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/config/buttons.c b/src/main/config/buttons.c index f4a498b8..e083bf3f 100644 --- a/src/main/config/buttons.c +++ b/src/main/config/buttons.c @@ -15,11 +15,12 @@ #include "config/schema.h" #include "config/usages.h" +#include "core/log.h" + #include "geninput/input-config.h" #include "geninput/mapper.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/winres.h" diff --git a/src/main/config/eam.c b/src/main/config/eam.c index e9bb76ce..1d665189 100644 --- a/src/main/config/eam.c +++ b/src/main/config/eam.c @@ -13,6 +13,8 @@ #include "config/resource.h" #include "config/schema.h" +#include "core/log.h" + #include "eamio/eam-config.h" #include "geninput/hid-mgr.h" @@ -21,7 +23,6 @@ #include "util/array.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/config/gametype.c b/src/main/config/gametype.c index e7e7353b..2ec0ea4b 100644 --- a/src/main/config/gametype.c +++ b/src/main/config/gametype.c @@ -6,8 +6,9 @@ #include "config/resource.h" #include "config/schema.h" +#include "core/log.h" + #include "util/defs.h" -#include "util/log.h" static INT_PTR CALLBACK game_type_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); diff --git a/src/main/config/main.c b/src/main/config/main.c index ba515554..8be13700 100644 --- a/src/main/config/main.c +++ b/src/main/config/main.c @@ -14,14 +14,19 @@ #include "config/spinner.h" #include "config/usages.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "eamio/eam-config.h" #include "geninput/input-config.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #include "util/winres.h" HPROPSHEETPAGE @@ -61,11 +66,22 @@ int main(int argc, char **argv) wchar_t text[1024]; int max_light; size_t i; + struct core_log_sink sink; inst = GetModuleHandle(NULL); - log_to_writer(log_writer_debug, NULL); - log_to_external(log_impl_misc, log_impl_info, log_impl_warning, my_fatal); + core_thread_crt_ext_impl_set(); + + core_log_impl_set( + core_log_bt_log_misc, + core_log_bt_log_info, + core_log_bt_log_warning, + my_fatal); + + core_log_sink_debug_open(&sink); + + core_log_bt_init(&sink); + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); usages_init(inst); @@ -96,13 +112,17 @@ int main(int argc, char **argv) } } - input_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); - input_init(crt_thread_create, crt_thread_join, crt_thread_destroy); - - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); - eam_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy); + core_log_impl_assign(input_set_loggers); + input_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); + + core_log_impl_assign(eam_io_set_loggers); + eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); eam_io_config_api = eam_io_get_config_api(); // calculate these and check against the loaded config diff --git a/src/main/config/snap.c b/src/main/config/snap.c index 8981c686..6d8547ab 100644 --- a/src/main/config/snap.c +++ b/src/main/config/snap.c @@ -6,10 +6,11 @@ #include "config/snap.h" +#include "core/log.h" + #include "geninput/hid-mgr.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" enum snap_control_heuristic { CONTROL_CENTERING_AXIS, CONTROL_MULTISWITCH }; diff --git a/src/main/config/usages.c b/src/main/config/usages.c index 5e28ad9b..771772cd 100644 --- a/src/main/config/usages.c +++ b/src/main/config/usages.c @@ -9,8 +9,9 @@ #include "config/resource.h" +#include "core/log.h" + #include "util/array.h" -#include "util/log.h" #include "util/str.h" #include "util/winres.h" diff --git a/src/main/d3d9exhook/config-gfx.c b/src/main/d3d9exhook/config-gfx.c index 6cf57af0..b4a51973 100644 --- a/src/main/d3d9exhook/config-gfx.c +++ b/src/main/d3d9exhook/config-gfx.c @@ -4,9 +4,9 @@ #include "cconfig/cconfig-util.h" -#include "d3d9exhook/config-gfx.h" +#include "core/log.h" -#include "util/log.h" +#include "d3d9exhook/config-gfx.h" #define D3D9EXHOOK_CONFIG_GFX_FRAMED_KEY "gfx.framed" #define D3D9EXHOOK_CONFIG_GFX_WINDOWED_KEY "gfx.windowed" diff --git a/src/main/d3d9exhook/d3d9ex.c b/src/main/d3d9exhook/d3d9ex.c index 88d95b00..80d958c3 100644 --- a/src/main/d3d9exhook/d3d9ex.c +++ b/src/main/d3d9exhook/d3d9ex.c @@ -9,13 +9,14 @@ #include #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" #include "d3d9exhook/d3d9ex.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/ddrhook-util/_com4.c b/src/main/ddrhook-util/_com4.c index 44e01784..4ca08c37 100644 --- a/src/main/ddrhook-util/_com4.c +++ b/src/main/ddrhook-util/_com4.c @@ -19,6 +19,8 @@ #include "bemanitools/ddrio.h" +#include "core/log.h" + #include "ddrhook-util/_com4.h" #include "hook/iohook.h" @@ -27,7 +29,6 @@ #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" static struct ac_io_emu com4_ac_io_emu; static struct ac_io_emu_hdxs com4_hdxs; diff --git a/src/main/ddrhook-util/dinput.c b/src/main/ddrhook-util/dinput.c index b0fdc663..949fb1eb 100644 --- a/src/main/ddrhook-util/dinput.c +++ b/src/main/ddrhook-util/dinput.c @@ -5,12 +5,13 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/pe.h" #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" static HRESULT STDCALL my_DirectInput8Create( HINSTANCE hinst, diff --git a/src/main/ddrhook-util/extio.c b/src/main/ddrhook-util/extio.c index cd2dc84b..661b165f 100644 --- a/src/main/ddrhook-util/extio.c +++ b/src/main/ddrhook-util/extio.c @@ -14,10 +14,11 @@ #include "bemanitools/ddrio.h" +#include "core/log.h" + #include "hook/iohook.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static HRESULT extio_open(struct irp *irp); diff --git a/src/main/ddrhook-util/gfx.c b/src/main/ddrhook-util/gfx.c index 3f5f7736..0bfd0a5c 100644 --- a/src/main/ddrhook-util/gfx.c +++ b/src/main/ddrhook-util/gfx.c @@ -11,13 +11,14 @@ #include #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" #include "ddrhook-util/gfx.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/ddrhook-util/misc.c b/src/main/ddrhook-util/misc.c index b74c8de6..e04390d1 100644 --- a/src/main/ddrhook-util/misc.c +++ b/src/main/ddrhook-util/misc.c @@ -2,13 +2,14 @@ #include +#include "core/log.h" + #include "ddrhook-util/gfx.h" #include "hook/pe.h" #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" static LRESULT(STDCALL *real_SendMessageW)( diff --git a/src/main/ddrhook-util/monitor.c b/src/main/ddrhook-util/monitor.c index 0dfce07f..cba18ccd 100644 --- a/src/main/ddrhook-util/monitor.c +++ b/src/main/ddrhook-util/monitor.c @@ -7,12 +7,13 @@ #include #include +#include "core/log.h" + #include "ddrhook-util/monitor.h" #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* Link pointers */ diff --git a/src/main/ddrhook-util/p3io.c b/src/main/ddrhook-util/p3io.c index 09b8c6f5..af0f1a93 100644 --- a/src/main/ddrhook-util/p3io.c +++ b/src/main/ddrhook-util/p3io.c @@ -7,11 +7,11 @@ #include "ddrhook-util/p3io.h" +#include "core/log.h" + #include "p3ioemu/emu.h" #include "p3ioemu/uart.h" -#include "util/log.h" - extern bool _15khz; extern bool standard_def; diff --git a/src/main/ddrhook-util/spike.c b/src/main/ddrhook-util/spike.c index 0e383d20..37cf8887 100644 --- a/src/main/ddrhook-util/spike.c +++ b/src/main/ddrhook-util/spike.c @@ -15,10 +15,11 @@ #include "acioemu/addr.h" #include "acioemu/emu.h" +#include "core/log.h" + #include "hook/iohook.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu spike_ac_io_emu; diff --git a/src/main/ddrhook-util/usbmem.c b/src/main/ddrhook-util/usbmem.c index f85363f3..c4e12c48 100644 --- a/src/main/ddrhook-util/usbmem.c +++ b/src/main/ddrhook-util/usbmem.c @@ -12,12 +12,13 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" #include "util/crc.h" #include "util/fs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" #define USBMEM_DEVICE_COUNT 2 diff --git a/src/main/ddrhook1/Module.mk b/src/main/ddrhook1/Module.mk index 83782ae8..1e9dc240 100644 --- a/src/main/ddrhook1/Module.mk +++ b/src/main/ddrhook1/Module.mk @@ -7,6 +7,8 @@ deplibs_ddrhook1 := \ avs \ libs_ddrhook1 := \ + avs-util \ + core \ acioemu \ cconfig \ ddrhook-util \ diff --git a/src/main/ddrhook1/avs-boot.c b/src/main/ddrhook1/avs-boot.c index 1b7ad41a..db4445b4 100644 --- a/src/main/ddrhook1/avs-boot.c +++ b/src/main/ddrhook1/avs-boot.c @@ -5,6 +5,9 @@ #include #include +#include "core/log-bt.h" +#include "core/log.h" + #include "hook/iohook.h" #include "hook/table.h" @@ -13,7 +16,6 @@ #include "ddrhook1/avs-boot.h" #include "ddrhook1/filesystem.h" -#include "util/log.h" #include "util/str.h" static void (*real_avs_boot)( @@ -50,6 +52,11 @@ static const struct hook_symbol ddrhook1_avs_ea3_hook_syms[] = { .link = (void **) &real_ea3_boot}, }; +static AVS_LOG_WRITER(_avs_boot_log_writer, chars, nchars, ctx) +{ + core_log_bt_direct_sink_write(chars, nchars); +} + static void avs_boot_replace_property_str( struct property_node *node, const char *name, const char *val) { @@ -115,7 +122,7 @@ static void my_avs_boot( sz_std_heap, avs_heap, sz_avs_heap, - log_writer_debug, + _avs_boot_log_writer, NULL); } diff --git a/src/main/ddrhook1/config-ddrhook1.c b/src/main/ddrhook1/config-ddrhook1.c index 154f3ae3..a5872bf9 100644 --- a/src/main/ddrhook1/config-ddrhook1.c +++ b/src/main/ddrhook1/config-ddrhook1.c @@ -2,9 +2,9 @@ #include "cconfig/cconfig-util.h" -#include "ddrhook1/config-ddrhook1.h" +#include "core/log.h" -#include "util/log.h" +#include "ddrhook1/config-ddrhook1.h" #define DDRHOOK1_CONFIG_DDRHOOK1_USE_COM4_EMU_KEY "ddrhook1.use_com4_emu" #define DDRHOOK1_CONFIG_DDRHOOK1_STANDARD_DEF_KEY "ddrhook1.standard_def" diff --git a/src/main/ddrhook1/config-eamuse.c b/src/main/ddrhook1/config-eamuse.c index 260791d3..dbe44cde 100644 --- a/src/main/ddrhook1/config-eamuse.c +++ b/src/main/ddrhook1/config-eamuse.c @@ -2,9 +2,10 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "ddrhook1/config-eamuse.h" -#include "util/log.h" #include "util/net.h" #define DDRHOOK1_CONFIG_EAMUSE_SERVER_KEY "eamuse.server" diff --git a/src/main/ddrhook1/config-gfx.c b/src/main/ddrhook1/config-gfx.c index 09ccc0c6..c71e74df 100644 --- a/src/main/ddrhook1/config-gfx.c +++ b/src/main/ddrhook1/config-gfx.c @@ -2,9 +2,9 @@ #include "cconfig/cconfig-util.h" -#include "ddrhook1/config-gfx.h" +#include "core/log.h" -#include "util/log.h" +#include "ddrhook1/config-gfx.h" #define DDRHOOK1_CONFIG_GFX_WINDOWED_KEY "gfx.windowed" diff --git a/src/main/ddrhook1/config-security.c b/src/main/ddrhook1/config-security.c index 9489b0a6..b0b7d2b2 100644 --- a/src/main/ddrhook1/config-security.c +++ b/src/main/ddrhook1/config-security.c @@ -2,11 +2,12 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "ddrhook1/config-security.h" #include "security/mcode.h" -#include "util/log.h" #include "util/net.h" #define DDRHOOK1_CONFIG_SECURITY_MCODE_KEY "security.mcode" diff --git a/src/main/ddrhook1/dllmain.c b/src/main/ddrhook1/dllmain.c index 3dcc6c94..3cccb3a7 100644 --- a/src/main/ddrhook1/dllmain.c +++ b/src/main/ddrhook1/dllmain.c @@ -2,11 +2,20 @@ #include +#include "avs-util/core-interop.h" + #include "bemanitools/ddrio.h" #include "bemanitools/eamio.h" #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "ddrhook-util/_com4.h" #include "ddrhook-util/extio.h" #include "ddrhook-util/p3io.h" @@ -37,8 +46,6 @@ #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" -#include "util/thread.h" #define DDRHOOK1_INFO_HEADER \ "ddrhook1 for DDR X" \ @@ -68,6 +75,15 @@ static const struct hook_symbol init_hook_syms[] = { }, }; +static void _ddrhook1_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static DWORD STDCALL my_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) { @@ -155,7 +171,12 @@ my_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) log_info("Initializing DDR IO backend"); - ok = ddr_io_init(thread_create, thread_join, thread_destroy); + core_log_impl_assign(ddr_io_set_loggers); + + ok = ddr_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { log_fatal("Couldn't initialize DDR IO backend"); @@ -165,10 +186,12 @@ my_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) if (config_ddrhook1.use_com4_emu) { log_info("Initializing card reader backend"); - eam_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(eam_io_set_loggers); - ok = eam_io_init(thread_create, thread_join, thread_destroy); + ok = eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { log_fatal("Couldn't initialize card reader backend"); @@ -185,7 +208,12 @@ my_GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - log_to_writer(log_writer_debug, NULL); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + + // TODO init debug logging but with avs available? why not use avs + // logging? + _ddrhook1_log_init(); hook_table_apply( NULL, "kernel32.dll", init_hook_syms, lengthof(init_hook_syms)); diff --git a/src/main/ddrhook1/filesystem.c b/src/main/ddrhook1/filesystem.c index abec921e..9912c4f9 100644 --- a/src/main/ddrhook1/filesystem.c +++ b/src/main/ddrhook1/filesystem.c @@ -6,10 +6,11 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/ddrhook1/master.c b/src/main/ddrhook1/master.c index b30836a5..480d56f7 100644 --- a/src/main/ddrhook1/master.c +++ b/src/main/ddrhook1/master.c @@ -1,3 +1,5 @@ +#include "core/log.h" + #include "ddrhook1/master.h" #include "ddrhook-util/dinput.h" @@ -9,7 +11,6 @@ #include "p3ioemu/devmgr.h" #include "util/defs.h" -#include "util/log.h" static HMODULE(STDCALL *real_LoadLibraryA)(const char *name); static BOOL(STDCALL *real_IsDebuggerPresent)(); diff --git a/src/main/ddrhook2/Module.mk b/src/main/ddrhook2/Module.mk index 0c9fd365..e0416d66 100644 --- a/src/main/ddrhook2/Module.mk +++ b/src/main/ddrhook2/Module.mk @@ -4,6 +4,8 @@ deplibs_ddrhook2 := \ avs \ libs_ddrhook2 := \ + avs-util \ + core \ acioemu \ ddrhook-util \ p3ioemu \ diff --git a/src/main/ddrhook2/dllmain.c b/src/main/ddrhook2/dllmain.c index 90355519..cabbb941 100644 --- a/src/main/ddrhook2/dllmain.c +++ b/src/main/ddrhook2/dllmain.c @@ -2,9 +2,14 @@ #include +#include "avs-util/core-interop.h" + #include "bemanitools/ddrio.h" #include "bemanitools/eamio.h" +#include "core/log.h" +#include "core/thread.h" + #include "ddrhook-util/_com4.h" #include "ddrhook-util/extio.h" #include "ddrhook-util/gfx.h" @@ -25,7 +30,6 @@ #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" static bool my_dll_entry_init(char *sidcode, struct property_node *param); static bool my_dll_entry_main(void); @@ -119,10 +123,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) log_info("Initializing DDR IO backend"); - ddr_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(ddr_io_set_loggers); - ok = ddr_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + ok = ddr_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { return false; @@ -131,11 +137,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (com4) { log_info("Initializing card reader backend"); - eam_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(eam_io_set_loggers); - ok = - eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + ok = eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { return false; @@ -174,8 +181,9 @@ BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/ddrhook2/master.c b/src/main/ddrhook2/master.c index 89f874ff..aba5451d 100644 --- a/src/main/ddrhook2/master.c +++ b/src/main/ddrhook2/master.c @@ -1,3 +1,5 @@ +#include "core/log.h" + #include "ddrhook2/master.h" #include "ddrhook-util/dinput.h" @@ -10,7 +12,6 @@ #include "p3ioemu/devmgr.h" #include "util/defs.h" -#include "util/log.h" static HMODULE(STDCALL *real_LoadLibraryA)(const char *name); diff --git a/src/main/ddrio-async/Module.mk b/src/main/ddrio-async/Module.mk index 62006cfe..2eb806dd 100644 --- a/src/main/ddrio-async/Module.mk +++ b/src/main/ddrio-async/Module.mk @@ -3,6 +3,7 @@ dlls += ddrio-async ldflags_ddrio-async:= \ libs_ddrio-async := \ + core \ util \ src_ddrio-async := \ diff --git a/src/main/ddrio-async/ddrio.c b/src/main/ddrio-async/ddrio.c index fe81f68e..998c59a5 100644 --- a/src/main/ddrio-async/ddrio.c +++ b/src/main/ddrio-async/ddrio.c @@ -11,8 +11,9 @@ #include "bemanitools/ddrio.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/log.h" +#include "core/thread.h" + #include "util/time.h" typedef void (*ddr_io_set_loggers_t)( @@ -154,7 +155,7 @@ void ddr_io_set_loggers( _log_formatter_warning = warning; _log_formatter_fatal = fatal; - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } bool ddr_io_init( @@ -164,6 +165,8 @@ bool ddr_io_init( { log_info("Loading ddrio-async-child.dll as child ddrio library..."); + core_thread_impl_set(thread_create, thread_join, thread_destroy); + _child_ddr_io_module = LoadLibraryA("ddrio-async-child.dll"); if (_child_ddr_io_module == NULL) { diff --git a/src/main/ddrio-mm/Module.mk b/src/main/ddrio-mm/Module.mk index 9368a623..16e3ca87 100644 --- a/src/main/ddrio-mm/Module.mk +++ b/src/main/ddrio-mm/Module.mk @@ -5,6 +5,7 @@ ldflags_ddrio-mm:= \ -lsetupapi \ libs_ddrio-mm := \ + core \ mm \ util \ diff --git a/src/main/ddrio-mm/ddrio.c b/src/main/ddrio-mm/ddrio.c index 08bd1d29..44bdd352 100644 --- a/src/main/ddrio-mm/ddrio.c +++ b/src/main/ddrio-mm/ddrio.c @@ -7,11 +7,12 @@ #include "bemanitools/ddrio.h" +#include "core/log.h" + #include "mm/mm.h" #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" struct ddr_bittrans { @@ -117,7 +118,7 @@ void ddr_io_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } bool ddr_io_init( diff --git a/src/main/ddrio-p3io/Module.mk b/src/main/ddrio-p3io/Module.mk index 43e0fd06..2824bf0e 100644 --- a/src/main/ddrio-p3io/Module.mk +++ b/src/main/ddrio-p3io/Module.mk @@ -4,6 +4,7 @@ ldflags_ddrio-p3io:= \ -lsetupapi \ libs_ddrio-p3io := \ + core \ cconfig \ extio \ extiodrv \ diff --git a/src/main/ddrio-p3io/config.c b/src/main/ddrio-p3io/config.c index c45254e1..54591575 100644 --- a/src/main/ddrio-p3io/config.c +++ b/src/main/ddrio-p3io/config.c @@ -1,6 +1,6 @@ #include "cconfig/cconfig-util.h" -#include "util/log.h" +#include "core/log.h" #include "config.h" diff --git a/src/main/ddrio-p3io/ddrio.c b/src/main/ddrio-p3io/ddrio.c index a38ce09d..6ed7e90c 100644 --- a/src/main/ddrio-p3io/ddrio.c +++ b/src/main/ddrio-p3io/ddrio.c @@ -10,15 +10,15 @@ #include "cconfig/cconfig-main.h" +#include "core/log.h" +#include "core/thread.h" + #include "extiodrv/device.h" #include "extiodrv/extio.h" #include "p3iodrv/ddr.h" #include "p3iodrv/device.h" -#include "util/log.h" -#include "util/thread.h" - #include "config.h" static struct ddrio_p3io_config _ddr_io_p3io_config; @@ -255,7 +255,7 @@ void ddr_io_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } static void _ddr_io_config_init(struct ddrio_p3io_config *config_ddrio_p3io) diff --git a/src/main/ddrio-smx/Module.mk b/src/main/ddrio-smx/Module.mk index cc0563b2..12fd74de 100644 --- a/src/main/ddrio-smx/Module.mk +++ b/src/main/ddrio-smx/Module.mk @@ -5,6 +5,7 @@ deplibs_ddrio-smx := \ SMX \ libs_ddrio-smx := \ + core \ geninput \ util \ diff --git a/src/main/ddrio-smx/ddrio.c b/src/main/ddrio-smx/ddrio.c index a7cda337..334ccaca 100644 --- a/src/main/ddrio-smx/ddrio.c +++ b/src/main/ddrio-smx/ddrio.c @@ -6,11 +6,12 @@ #include "bemanitools/ddrio.h" #include "bemanitools/input.h" +#include "core/log.h" + #include "imports/SMX.h" #include "imports/avs.h" #include "util/defs.h" -#include "util/log.h" struct ddr_io_smx_pad_map { int pad_no; @@ -99,7 +100,7 @@ void ddr_io_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); input_set_loggers(misc, info, warning, fatal); /* We would need a log server thread to accept log messages from SMX, since diff --git a/src/main/ddrio/ddrio.c b/src/main/ddrio/ddrio.c index 167b7c6e..5da99348 100644 --- a/src/main/ddrio/ddrio.c +++ b/src/main/ddrio/ddrio.c @@ -4,7 +4,7 @@ #include "bemanitools/input.h" -#include "util/log.h" +#include "core/log.h" void ddr_io_set_loggers( log_formatter_t misc, diff --git a/src/main/ddriotest/Module.mk b/src/main/ddriotest/Module.mk index 569d9736..b6cba5cb 100644 --- a/src/main/ddriotest/Module.mk +++ b/src/main/ddriotest/Module.mk @@ -1,6 +1,7 @@ exes += ddriotest \ libs_ddriotest := \ + core \ ddrio \ util \ diff --git a/src/main/ddriotest/main.c b/src/main/ddriotest/main.c index e03bc0c4..40453ce0 100644 --- a/src/main/ddriotest/main.c +++ b/src/main/ddriotest/main.c @@ -7,32 +7,41 @@ #include "bemanitools/ddrio.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" int main(int argc, char **argv) { - enum log_level log_level; + enum core_log_bt_log_level log_level; - log_level = LOG_LEVEL_FATAL; + log_level = CORE_LOG_BT_LOG_LEVEL_FATAL; for (int i = 0; i < argc; i++) { if (!strcmp(argv[i], "-v")) { - log_level = LOG_LEVEL_WARNING; + log_level = CORE_LOG_BT_LOG_LEVEL_WARNING; } else if (!strcmp(argv[i], "-vv")) { - log_level = LOG_LEVEL_INFO; + log_level = CORE_LOG_BT_LOG_LEVEL_INFO; } else if (!strcmp(argv[i], "-vvv")) { - log_level = LOG_LEVEL_MISC; + log_level = CORE_LOG_BT_LOG_LEVEL_MISC; } } - log_to_writer(log_writer_stderr, NULL); - log_set_level(log_level); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); - ddr_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_bt_ext_init_with_stderr(); + core_log_bt_level_set(log_level); - if (!ddr_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + core_log_impl_assign(ddr_io_set_loggers); + + if (!ddr_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { fprintf(stderr, "Initializing ddrio failed\n"); return -1; } diff --git a/src/main/dinput/dinput.c b/src/main/dinput/dinput.c index 248ead4a..e65928f7 100644 --- a/src/main/dinput/dinput.c +++ b/src/main/dinput/dinput.c @@ -8,12 +8,13 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/pe.h" #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" static HRESULT STDCALL my_DirectInput8Create( HINSTANCE hinst, diff --git a/src/main/eamio-icca/Module.mk b/src/main/eamio-icca/Module.mk index 28c388e5..bcd4be83 100644 --- a/src/main/eamio-icca/Module.mk +++ b/src/main/eamio-icca/Module.mk @@ -2,6 +2,7 @@ dlls += \ eamio-icca \ libs_eamio-icca := \ + core \ aciodrv \ aciomgr \ cconfig \ diff --git a/src/main/eamio-icca/config-icc.c b/src/main/eamio-icca/config-icc.c index 660c0ee1..963f6882 100644 --- a/src/main/eamio-icca/config-icc.c +++ b/src/main/eamio-icca/config-icc.c @@ -2,8 +2,6 @@ #include "eamio-icca/config-icc.h" -#include "util/log.h" - #define EAMIO_ICCA_CONFIG_ICC_PORT_KEY "icc.port" #define EAMIO_ICCA_CONFIG_ICC_BAUD_KEY "icc.baud" diff --git a/src/main/eamio-icca/eamio-icca.c b/src/main/eamio-icca/eamio-icca.c index c681610e..db0215a9 100644 --- a/src/main/eamio-icca/eamio-icca.c +++ b/src/main/eamio-icca/eamio-icca.c @@ -14,11 +14,11 @@ #include "bemanitools/eamio.h" +#include "core/log.h" + #include "cconfig/cconfig-main.h" #include "eamio-icca/config-icc.h" -#include "util/log.h" - #define IDLE_RESPONSES_BETWEEN_FELICA_POLLS 5 #define NUMBER_OF_EMULATED_READERS 2 #define INVALID_NODE_ID -1 @@ -60,7 +60,7 @@ void eam_io_set_loggers( { aciomgr_set_loggers(misc, info, warning, fatal); - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } // all of these are referred to internally as ICCA diff --git a/src/main/eamio/Module.mk b/src/main/eamio/Module.mk index f7a7409c..9cf82ed3 100644 --- a/src/main/eamio/Module.mk +++ b/src/main/eamio/Module.mk @@ -1,5 +1,5 @@ dlls += eamio -libs_eamio := geninput util +libs_eamio := core geninput util src_eamio := \ eam-api.c \ eam-impl.c \ diff --git a/src/main/eamio/eam-api.c b/src/main/eamio/eam-api.c index 03203173..9d2857d9 100644 --- a/src/main/eamio/eam-api.c +++ b/src/main/eamio/eam-api.c @@ -11,14 +11,15 @@ #include "bemanitools/eamio.h" #include "bemanitools/input.h" +#include "core/log.h" +#include "core/thread.h" + #include "eamio/eam-config.h" #include "eamio/eam-impl.h" #include "eamio/eam-s11n.h" #include "util/fs.h" -#include "util/log.h" #include "util/msg-thread.h" -#include "util/thread.h" static void eam_handle_hotplug_msg(WPARAM wparam, const DEV_BROADCAST_HDR *hdr); static FILE *eam_io_config_open(const char *mode); @@ -108,14 +109,14 @@ void eam_io_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } bool eam_io_init( thread_create_t create, thread_join_t join, thread_destroy_t destroy) { input_init(create, join, destroy); - thread_api_init(create, join, destroy); + core_thread_impl_set(create, join, destroy); eam_io_config_load(); msg_thread_init(eam_hinst); diff --git a/src/main/eamio/eam-impl.c b/src/main/eamio/eam-impl.c index 0c417094..6aca5898 100644 --- a/src/main/eamio/eam-impl.c +++ b/src/main/eamio/eam-impl.c @@ -9,6 +9,8 @@ #include "bemanitools/eamio.h" +#include "core/log.h" + #include "eamio/eam-impl.h" #include "geninput/hid-mgr.h" @@ -16,7 +18,6 @@ #include "util/defs.h" #include "util/fs.h" #include "util/hex.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/eamiotest/Module.mk b/src/main/eamiotest/Module.mk index a54384b7..0b9fd4b9 100644 --- a/src/main/eamiotest/Module.mk +++ b/src/main/eamiotest/Module.mk @@ -1,6 +1,7 @@ exes += eamiotest libs_eamiotest := \ + core \ eamio \ util \ diff --git a/src/main/eamiotest/main.c b/src/main/eamiotest/main.c index 9880af4a..a2f410f4 100644 --- a/src/main/eamiotest/main.c +++ b/src/main/eamiotest/main.c @@ -7,20 +7,29 @@ #include "bemanitools/eamio.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" /** * Tool to test your implementations of eamio. */ int main(int argc, char **argv) { - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_bt_ext_init_with_stdout(); - if (!eam_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + core_log_impl_assign(eam_io_set_loggers); + + if (!eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { printf("Initializing eamio failed\n"); return -1; } diff --git a/src/main/extiodrv/device.c b/src/main/extiodrv/device.c index ba14fa7f..e6bc788f 100644 --- a/src/main/extiodrv/device.c +++ b/src/main/extiodrv/device.c @@ -2,7 +2,7 @@ #include "device.h" -#include "util/log.h" +#include "core/log.h" HRESULT extiodrv_device_open(const char *port, HANDLE *handle) { diff --git a/src/main/extiodrv/extio.c b/src/main/extiodrv/extio.c index 8c50de2f..5b1ae6a4 100644 --- a/src/main/extiodrv/extio.c +++ b/src/main/extiodrv/extio.c @@ -2,7 +2,7 @@ #include "extio.h" -#include "util/log.h" +#include "core/log.h" // static uint8_t _extiodrv_extio_sensor_read_mode_map[5] = { // 1, // all diff --git a/src/main/extiotest/Module.mk b/src/main/extiotest/Module.mk index 73ec1b07..3e05ba08 100644 --- a/src/main/extiotest/Module.mk +++ b/src/main/extiotest/Module.mk @@ -3,6 +3,7 @@ exes += extiotest \ libs_extiotest := \ extiodrv \ extio \ + core \ util \ src_extiotest := \ diff --git a/src/main/extiotest/main.c b/src/main/extiotest/main.c index 3be152b1..c9e48bab 100644 --- a/src/main/extiotest/main.c +++ b/src/main/extiotest/main.c @@ -5,12 +5,15 @@ #include -#include "extiodrv/extio.h" +#include "core/log-bt.h" +#include "core/log-sink-std.h" +#include "core/log.h" -#include "util/log.h" +#include "extiodrv/extio.h" int main(int argc, char **argv) { + struct core_log_sink log_sink; HRESULT hr; const char *port; HANDLE handle; @@ -22,8 +25,9 @@ int main(int argc, char **argv) fprintf(stderr, " COM_PORT: For example COM1\n"); } - log_to_writer(log_writer_stderr, NULL); - log_set_level(LOG_LEVEL_MISC); + core_log_sink_std_err_open(true, &log_sink); + core_log_bt_init(&log_sink); + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); port = argv[1]; diff --git a/src/main/ezusb-emu/device.c b/src/main/ezusb-emu/device.c index 9f1eca82..9cf5c98d 100644 --- a/src/main/ezusb-emu/device.c +++ b/src/main/ezusb-emu/device.c @@ -9,6 +9,8 @@ #include +#include "core/log.h" + #include "ezusb/ezusbsys2.h" #include "ezusb/util.h" @@ -25,7 +27,6 @@ #include "util/fs.h" #include "util/hex.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" // The max buffer size in iidx's ezusb client library is 4096 for the initial diff --git a/src/main/ezusb-emu/node-coin.c b/src/main/ezusb-emu/node-coin.c index 87e75037..6b4aed19 100644 --- a/src/main/ezusb-emu/node-coin.c +++ b/src/main/ezusb-emu/node-coin.c @@ -1,11 +1,11 @@ #define LOG_MODULE "ezusb-emu-node-coin" +#include "core/log.h" + #include "ezusb-emu/node-coin.h" #include "ezusb-iidx/coin-cmd.h" -#include "util/log.h" - static uint8_t ezusb_iidx_emu_node_coin_mode = 0; uint8_t ezusb_iidx_emu_node_coin_process_cmd( diff --git a/src/main/ezusb-emu/node-eeprom.c b/src/main/ezusb-emu/node-eeprom.c index ea6a3fea..f63d866f 100644 --- a/src/main/ezusb-emu/node-eeprom.c +++ b/src/main/ezusb-emu/node-eeprom.c @@ -2,12 +2,12 @@ #include +#include "core/log.h" + #include "ezusb-emu/node-eeprom.h" #include "ezusb-iidx/eeprom-cmd.h" -#include "util/log.h" - /* not verified, but we got calls with 3 pages only so far */ #define EEPROM_NPAGES 3 diff --git a/src/main/ezusb-emu/node-security-mem.c b/src/main/ezusb-emu/node-security-mem.c index 92422c82..8d11c5ff 100644 --- a/src/main/ezusb-emu/node-security-mem.c +++ b/src/main/ezusb-emu/node-security-mem.c @@ -2,11 +2,11 @@ #include +#include "core/log.h" + #include "ezusb-emu/node-security-mem.h" #include "ezusb-iidx/secmem-cmd.h" -#include "util/log.h" - #define SECURITY2_NPAGES 5 /* Starting GOLD with the new IO2 (C02 is not affected), the game does not diff --git a/src/main/ezusb-emu/node-security-plug.c b/src/main/ezusb-emu/node-security-plug.c index 2b05a03a..ed57eed8 100644 --- a/src/main/ezusb-emu/node-security-plug.c +++ b/src/main/ezusb-emu/node-security-plug.c @@ -2,6 +2,8 @@ #include +#include "core/log.h" + #include "ezusb-emu/node-eeprom.h" #include "ezusb-emu/node-security-mem.h" #include "ezusb-emu/node-security-plug.h" @@ -11,8 +13,6 @@ #include "security/rp2.h" #include "security/util.h" -#include "util/log.h" - static struct security_mcode ezusb_iidx_emu_node_security_plug_boot_version; static uint32_t ezusb_iidx_emu_node_security_plug_boot_seeds[3]; diff --git a/src/main/ezusb-emu/node-sram.c b/src/main/ezusb-emu/node-sram.c index 200f1caf..dc3846b4 100644 --- a/src/main/ezusb-emu/node-sram.c +++ b/src/main/ezusb-emu/node-sram.c @@ -2,12 +2,13 @@ #include +#include "core/log.h" + #include "ezusb-emu/conf.h" #include "ezusb-emu/node-sram.h" #include "ezusb-iidx/sram-cmd.h" #include "util/fs.h" -#include "util/log.h" #define SRAM_NPAGES 12 diff --git a/src/main/ezusb-emu/node-wdt.c b/src/main/ezusb-emu/node-wdt.c index 9185bdde..8c5c5416 100644 --- a/src/main/ezusb-emu/node-wdt.c +++ b/src/main/ezusb-emu/node-wdt.c @@ -1,10 +1,10 @@ #define LOG_MODULE "ezusb-emu-node-wdt" +#include "core/log.h" + #include "ezusb-emu/node-wdt.h" #include "ezusb-iidx/wdt-cmd.h" -#include "util/log.h" - uint8_t ezusb_iidx_emu_node_wdt_process_cmd( uint8_t cmd_id, uint8_t cmd_data, uint8_t cmd_data2) { diff --git a/src/main/ezusb-emu/util.c b/src/main/ezusb-emu/util.c index 6e2798ae..30edd425 100644 --- a/src/main/ezusb-emu/util.c +++ b/src/main/ezusb-emu/util.c @@ -6,10 +6,11 @@ #include #include +#include "core/log.h" + #include "ezusb-emu/util.h" #include "util/hex.h" -#include "util/log.h" enum ezusb_pipe { /* This is just the NT driver API. Add 1 to get the actual EP number. */ diff --git a/src/main/ezusb-iidx-16seg-emu/node-16seg.c b/src/main/ezusb-iidx-16seg-emu/node-16seg.c index 89bc5070..2121235e 100644 --- a/src/main/ezusb-iidx-16seg-emu/node-16seg.c +++ b/src/main/ezusb-iidx-16seg-emu/node-16seg.c @@ -6,9 +6,9 @@ #include "bemanitools/iidxio.h" -#include "ezusb-iidx/seg16-cmd.h" +#include "core/log.h" -#include "util/log.h" +#include "ezusb-iidx/seg16-cmd.h" uint8_t ezusb_iidx_emu_node_16seg_process_cmd( uint8_t cmd_id, uint8_t cmd_data, uint8_t cmd_data2) diff --git a/src/main/ezusb-iidx-emu/Module.mk b/src/main/ezusb-iidx-emu/Module.mk index d6253631..16f2c1dd 100644 --- a/src/main/ezusb-iidx-emu/Module.mk +++ b/src/main/ezusb-iidx-emu/Module.mk @@ -1,6 +1,7 @@ libs += ezusb-iidx-emu libs_ezusb-iidx-emu := \ + core \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/ezusb-iidx-emu/card-mag.c b/src/main/ezusb-iidx-emu/card-mag.c index a210fd69..b65414ea 100644 --- a/src/main/ezusb-iidx-emu/card-mag.c +++ b/src/main/ezusb-iidx-emu/card-mag.c @@ -2,10 +2,11 @@ #include +#include "core/log.h" + #include "security/mcode.h" #include "util/crc.h" -#include "util/log.h" static const uint16_t ezusb_iidx_emu_card_mag_checksum_table_payload[256] = { 0x0, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, diff --git a/src/main/ezusb-iidx-emu/msg.c b/src/main/ezusb-iidx-emu/msg.c index bcab6566..76b97423 100644 --- a/src/main/ezusb-iidx-emu/msg.c +++ b/src/main/ezusb-iidx-emu/msg.c @@ -6,6 +6,8 @@ #include "bemanitools/iidxio.h" +#include "core/log.h" + #include "hook/iohook.h" #include "ezusb-emu/msg.h" @@ -21,7 +23,6 @@ #include "ezusb-iidx-16seg-emu/nodes.h" #include "util/hex.h" -#include "util/log.h" /* ------------------------------------------------------------------------ */ diff --git a/src/main/ezusb-iidx-emu/node-fpga.c b/src/main/ezusb-iidx-emu/node-fpga.c index d66c440c..6615b43f 100644 --- a/src/main/ezusb-iidx-emu/node-fpga.c +++ b/src/main/ezusb-iidx-emu/node-fpga.c @@ -2,12 +2,13 @@ #include +#include "core/log.h" + #include "ezusb-iidx-emu/conf.h" #include "ezusb-iidx-emu/node-fpga.h" #include "ezusb-iidx/fpga-cmd.h" #include "util/fs.h" -#include "util/log.h" static uint16_t ezusb_iidx_emu_node_fpga_write_ptr; static uint16_t ezusb_iidx_emu_node_fpga_prog_size; diff --git a/src/main/ezusb-iidx-emu/node-serial.c b/src/main/ezusb-iidx-emu/node-serial.c index 378ab02f..7dc65193 100644 --- a/src/main/ezusb-iidx-emu/node-serial.c +++ b/src/main/ezusb-iidx-emu/node-serial.c @@ -4,6 +4,9 @@ #include "bemanitools/eamio.h" +#include "core/log.h" +#include "core/thread.h" + #include "ezusb-iidx-emu/card-mag.c" #include "ezusb-iidx-emu/node-serial.h" #include "ezusb-iidx/serial-cmd.h" @@ -11,9 +14,7 @@ #include "security/mcode.h" #include "util/hex.h" -#include "util/log.h" #include "util/mem.h" -#include "util/thread.h" #define CARD_ID_LEN 8 @@ -292,7 +293,7 @@ void ezusb_iidx_emu_node_serial_init(void) &ezusb_iidx_emu_node_serial_emulation_state[i].card_cs); } - ezusb_iidx_emu_node_serial_emu_thread = thread_create( + ezusb_iidx_emu_node_serial_emu_thread = core_thread_create( ezusb_iidx_emu_node_serial_emu_thread_proc, NULL, 0x4000, 0); } diff --git a/src/main/ezusb-iidx-fpga-flash/Module.mk b/src/main/ezusb-iidx-fpga-flash/Module.mk index c250b35b..320d6f49 100644 --- a/src/main/ezusb-iidx-fpga-flash/Module.mk +++ b/src/main/ezusb-iidx-fpga-flash/Module.mk @@ -4,6 +4,7 @@ ldflags_ezusb-iidx-fpga-flash := \ -lsetupapi \ libs_ezusb-iidx-fpga-flash := \ + core \ ezusb \ ezusb-iidx \ util \ diff --git a/src/main/ezusb-iidx-fpga-flash/main.c b/src/main/ezusb-iidx-fpga-flash/main.c index a8fb6816..f8dacdd3 100644 --- a/src/main/ezusb-iidx-fpga-flash/main.c +++ b/src/main/ezusb-iidx-fpga-flash/main.c @@ -3,11 +3,14 @@ #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "ezusb-iidx/fpga.h" #include "ezusb/ezusb.h" #include "util/fs.h" -#include "util/log.h" int main(int argc, char **argv) { @@ -24,7 +27,8 @@ int main(int argc, char **argv) return -1; } - log_to_writer(log_writer_stderr, NULL); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stderr(); log_info("Opening ezusb '%s'...", EZUSB_DEVICE_PATH); diff --git a/src/main/ezusb-iidx-sram-flash/Module.mk b/src/main/ezusb-iidx-sram-flash/Module.mk index b6e6a73d..a369b060 100644 --- a/src/main/ezusb-iidx-sram-flash/Module.mk +++ b/src/main/ezusb-iidx-sram-flash/Module.mk @@ -4,6 +4,7 @@ ldflags_ezusb-iidx-sram-flash := \ -lsetupapi \ libs_ezusb-iidx-sram-flash := \ + core \ ezusb \ ezusb-iidx \ util \ diff --git a/src/main/ezusb-iidx-sram-flash/main.c b/src/main/ezusb-iidx-sram-flash/main.c index d22464e0..b4fa94d3 100644 --- a/src/main/ezusb-iidx-sram-flash/main.c +++ b/src/main/ezusb-iidx-sram-flash/main.c @@ -3,11 +3,14 @@ #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "ezusb-iidx/sram.h" #include "ezusb/ezusb.h" #include "util/fs.h" -#include "util/log.h" int main(int argc, char **argv) { @@ -24,7 +27,8 @@ int main(int argc, char **argv) return -1; } - log_to_writer(log_writer_stdout, NULL); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stdout(); log_info("Opening ezusb '%s'...", argv[1]); diff --git a/src/main/ezusb-iidx/ezusb-iidx.c b/src/main/ezusb-iidx/ezusb-iidx.c index e82e2145..ae035b73 100644 --- a/src/main/ezusb-iidx/ezusb-iidx.c +++ b/src/main/ezusb-iidx/ezusb-iidx.c @@ -1,11 +1,12 @@ #define LOG_MODULE "ezusb-iidx" +#include "core/log.h" + #include "ezusb-iidx/ezusb-iidx.h" #include "ezusb/ezusbsys2.h" #include "util/hex.h" -#include "util/log.h" #include "util/time.h" #include "msg.h" diff --git a/src/main/ezusb-iidx/fpga.c b/src/main/ezusb-iidx/fpga.c index 23d5fd05..b4d25500 100644 --- a/src/main/ezusb-iidx/fpga.c +++ b/src/main/ezusb-iidx/fpga.c @@ -4,7 +4,7 @@ #include -#include "util/log.h" +#include "core/log.h" #include "ezusb-iidx.h" #include "fpga-cmd.h" diff --git a/src/main/ezusb-iidx/sram.c b/src/main/ezusb-iidx/sram.c index f816b383..fcce3840 100644 --- a/src/main/ezusb-iidx/sram.c +++ b/src/main/ezusb-iidx/sram.c @@ -1,8 +1,8 @@ #define LOG_MODULE "ezusb-iidx-sram" -#include "ezusb-iidx/sram.h" +#include "core/log.h" -#include "util/log.h" +#include "ezusb-iidx/sram.h" #include "ezusb-iidx.h" #include "sram-cmd.h" diff --git a/src/main/ezusb-tool/Module.mk b/src/main/ezusb-tool/Module.mk index 349bc089..b5e81588 100644 --- a/src/main/ezusb-tool/Module.mk +++ b/src/main/ezusb-tool/Module.mk @@ -4,6 +4,7 @@ ldflags_ezusb-tool := \ -lsetupapi \ libs_ezusb-tool := \ + core \ ezusb \ util \ diff --git a/src/main/ezusb-tool/main.c b/src/main/ezusb-tool/main.c index 115f79c5..ca847815 100644 --- a/src/main/ezusb-tool/main.c +++ b/src/main/ezusb-tool/main.c @@ -3,11 +3,13 @@ #include #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "ezusb/ezusb.h" #include "ezusb/util.h" -#include "util/log.h" - static int info() { HANDLE handle; @@ -103,7 +105,8 @@ int main(int argc, char **argv) arg_pos = 1; - log_to_writer(log_writer_stderr, NULL); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stderr(); if (!strcmp(argv[arg_pos], "info")) { return info(); diff --git a/src/main/ezusb/ezusb.c b/src/main/ezusb/ezusb.c index ce1b828e..7204589a 100644 --- a/src/main/ezusb/ezusb.c +++ b/src/main/ezusb/ezusb.c @@ -4,10 +4,11 @@ #include // clang-format on +#include "core/log.h" + #include "ezusb/ezusb.h" #include "ezusb/ezusbsys2.h" -#include "util/log.h" #include "util/str.h" static bool ezusb_reset(HANDLE handle, bool hold) diff --git a/src/main/ezusb/util.c b/src/main/ezusb/util.c index efc2cd10..5bf18a45 100644 --- a/src/main/ezusb/util.c +++ b/src/main/ezusb/util.c @@ -1,11 +1,12 @@ #include #include +#include "core/log.h" + #include "ezusb/util.h" #include "util/crc.h" #include "util/fs.h" -#include "util/log.h" #include "util/mem.h" struct ezusb_firmware *ezusb_firmware_load(const char *file) diff --git a/src/main/ezusb2-dbg-hook/Module.mk b/src/main/ezusb2-dbg-hook/Module.mk index 4290f5cf..579e9bfb 100644 --- a/src/main/ezusb2-dbg-hook/Module.mk +++ b/src/main/ezusb2-dbg-hook/Module.mk @@ -1,6 +1,7 @@ dlls += ezusb2-dbg-hook libs_ezusb2-dbg-hook := \ + core \ hook \ util \ diff --git a/src/main/ezusb2-dbg-hook/main.c b/src/main/ezusb2-dbg-hook/main.c index e90d3fb0..e1c1bdbb 100644 --- a/src/main/ezusb2-dbg-hook/main.c +++ b/src/main/ezusb2-dbg-hook/main.c @@ -10,13 +10,17 @@ #include #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-file.h" +#include "core/log.h" + #include "ezusb2/cyioctl.h" #include "hook/table.h" #include "util/cmdline.h" #include "util/hex.h" -#include "util/log.h" #include "util/str.h" static HANDLE STDCALL my_CreateFileW( @@ -367,14 +371,13 @@ static void ezusb2_dbg_hook_terminate_process() BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - FILE *file; int argc; char **argv; wchar_t *buffer; uint32_t args_success; - file = fopen("ezusb2_dbg.log", "w+"); - log_to_writer(log_writer_file, file); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_file("ezusb2_dbg.log", false, false, 0); hook_table_apply( NULL, diff --git a/src/main/ezusb2-emu/device.c b/src/main/ezusb2-emu/device.c index f4652798..ecba8163 100644 --- a/src/main/ezusb2-emu/device.c +++ b/src/main/ezusb2-emu/device.c @@ -9,6 +9,8 @@ #include +#include "core/log.h" + #include "ezusb/util.h" #include "ezusb2/cyioctl.h" @@ -27,7 +29,6 @@ #include "util/fs.h" #include "util/hex.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" // The max buffer size in iidx's ezusb client library is 4096 for the initial diff --git a/src/main/ezusb2-emu/util.c b/src/main/ezusb2-emu/util.c index 2abd5134..d5b12b92 100644 --- a/src/main/ezusb2-emu/util.c +++ b/src/main/ezusb2-emu/util.c @@ -6,13 +6,14 @@ #include #include +#include "core/log.h" + #include "ezusb2/cyioctl.h" #include "ezusb2/ezusb2.h" #include "ezusb-emu/util.h" #include "util/hex.h" -#include "util/log.h" void ezusb2_emu_util_log_usb_msg(const char *prefix, const struct irp *irp) { diff --git a/src/main/ezusb2-iidx-emu/msg.c b/src/main/ezusb2-iidx-emu/msg.c index 1279ea9b..bfc3dce8 100644 --- a/src/main/ezusb2-iidx-emu/msg.c +++ b/src/main/ezusb2-iidx-emu/msg.c @@ -6,6 +6,8 @@ #include "bemanitools/iidxio.h" +#include "core/log.h" + #include "hook/iohook.h" #include "ezusb-emu/msg.h" @@ -15,7 +17,6 @@ #include "ezusb2-iidx/msg.h" #include "util/hex.h" -#include "util/log.h" /* ------------------------------------------------------------------------ */ diff --git a/src/main/ezusb2-popn-emu/msg.c b/src/main/ezusb2-popn-emu/msg.c index 6e83f7ac..d696ecc3 100644 --- a/src/main/ezusb2-popn-emu/msg.c +++ b/src/main/ezusb2-popn-emu/msg.c @@ -6,6 +6,8 @@ #include "bemanitools/popnio.h" +#include "core/log.h" + #include "hook/iohook.h" #include "ezusb-emu/msg.h" @@ -19,7 +21,6 @@ #include "ezusb2-popn/msg.h" #include "util/hex.h" -#include "util/log.h" /* ------------------------------------------------------------------------ */ diff --git a/src/main/ezusb2-popn-shim/Module.mk b/src/main/ezusb2-popn-shim/Module.mk index aefc2d40..f4536c47 100644 --- a/src/main/ezusb2-popn-shim/Module.mk +++ b/src/main/ezusb2-popn-shim/Module.mk @@ -4,6 +4,7 @@ ldflags_ezusb2-popn-shim := \ -lsetupapi \ libs_ezusb2-popn-shim := \ + core \ ezusb2-emu \ hook \ hooklib \ diff --git a/src/main/ezusb2-popn-shim/dllmain.c b/src/main/ezusb2-popn-shim/dllmain.c index c8e16fcf..ec7e18f6 100644 --- a/src/main/ezusb2-popn-shim/dllmain.c +++ b/src/main/ezusb2-popn-shim/dllmain.c @@ -4,6 +4,8 @@ #include +#include "core/log.h" + #include "ezusb2-emu/desc.h" #include "ezusb2-emu/device.h" @@ -13,8 +15,6 @@ #include "ezusb2-popn-shim/proxy.h" -#include "util/log.h" - #define EZUSB_REAL_DLL_FILENAME "ezusb.dll" static DWORD(STDCALL *real_entrypoint)(HMODULE self, DWORD reason, void *ctx); diff --git a/src/main/ezusb2-popn-shim/proxy.c b/src/main/ezusb2-popn-shim/proxy.c index fdf4f9f9..ec47002f 100644 --- a/src/main/ezusb2-popn-shim/proxy.c +++ b/src/main/ezusb2-popn-shim/proxy.c @@ -2,7 +2,7 @@ #include #include -#include "util/log.h" +#include "core/log.h" struct CoinParam; struct EEP_HISTORY; diff --git a/src/main/ezusb2-tool/Module.mk b/src/main/ezusb2-tool/Module.mk index 06115652..87e6dff8 100644 --- a/src/main/ezusb2-tool/Module.mk +++ b/src/main/ezusb2-tool/Module.mk @@ -4,6 +4,7 @@ ldflags_ezusb2-tool := \ -lsetupapi \ libs_ezusb2-tool := \ + core \ ezusb2 \ ezusb \ util \ diff --git a/src/main/ezusb2-tool/main.c b/src/main/ezusb2-tool/main.c index cdfd179b..0579c077 100644 --- a/src/main/ezusb2-tool/main.c +++ b/src/main/ezusb2-tool/main.c @@ -3,11 +3,13 @@ #include #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "ezusb/util.h" #include "ezusb2/ezusb2.h" -#include "util/log.h" - static int scan() { char *path; @@ -122,7 +124,8 @@ int main(int argc, char **argv) arg_pos = 1; - log_to_writer(log_writer_stderr, NULL); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stderr(); if (!strcmp(argv[arg_pos], "scan")) { return scan(); diff --git a/src/main/ezusb2/ezusb2.c b/src/main/ezusb2/ezusb2.c index 61fabd73..2c1b7b97 100644 --- a/src/main/ezusb2/ezusb2.c +++ b/src/main/ezusb2/ezusb2.c @@ -4,6 +4,8 @@ #include // clang-format on +#include "core/log.h" + #include "ezusb/ezusb.h" #include "ezusb2/cyioctl.h" @@ -11,7 +13,6 @@ #include "util/crc.h" #include "util/fs.h" -#include "util/log.h" #include "util/str.h" #define REQ_TYPE_HOST_TO_DEV 0x40 diff --git a/src/main/geninput/Module.mk b/src/main/geninput/Module.mk index 1521d806..3115edf0 100644 --- a/src/main/geninput/Module.mk +++ b/src/main/geninput/Module.mk @@ -5,6 +5,7 @@ ldflags_geninput := \ -lsetupapi \ libs_geninput := \ + core \ util \ src_geninput := \ diff --git a/src/main/geninput/dev-list.c b/src/main/geninput/dev-list.c index 2bfcb4e8..ce6d93ac 100644 --- a/src/main/geninput/dev-list.c +++ b/src/main/geninput/dev-list.c @@ -9,9 +9,10 @@ #include #include +#include "core/log.h" + #include "geninput/dev-list.h" -#include "util/log.h" #include "util/mem.h" void dev_list_init(struct dev_list *devs, const GUID *class_guid) diff --git a/src/main/geninput/hid-generic.c b/src/main/geninput/hid-generic.c index 41e73828..46c2924b 100644 --- a/src/main/geninput/hid-generic.c +++ b/src/main/geninput/hid-generic.c @@ -12,6 +12,8 @@ #include // clang-format on +#include "core/log.h" + #include "geninput/hid-generic-strings.h" #include "geninput/hid-generic.h" #include "geninput/hid-meta-in.h" @@ -19,7 +21,6 @@ #include "geninput/hid.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/geninput/hid-meta-in.c b/src/main/geninput/hid-meta-in.c index bbaf44f1..3a324947 100644 --- a/src/main/geninput/hid-meta-in.c +++ b/src/main/geninput/hid-meta-in.c @@ -11,10 +11,11 @@ #include #include +#include "core/log.h" + #include "geninput/hid-meta-in.h" #include "geninput/hid-report-in.h" -#include "util/log.h" #include "util/mem.h" static bool diff --git a/src/main/geninput/hid-meta-out.c b/src/main/geninput/hid-meta-out.c index 4f87602a..19037851 100644 --- a/src/main/geninput/hid-meta-out.c +++ b/src/main/geninput/hid-meta-out.c @@ -12,10 +12,11 @@ #include #include +#include "core/log.h" + #include "geninput/hid-meta-out.h" #include "geninput/hid-report-out.h" -#include "util/log.h" #include "util/mem.h" static bool diff --git a/src/main/geninput/hid-mgr.c b/src/main/geninput/hid-mgr.c index f22d3a78..2f8a7c13 100644 --- a/src/main/geninput/hid-mgr.c +++ b/src/main/geninput/hid-mgr.c @@ -2,9 +2,10 @@ #include +#include "core/log.h" + #include "geninput/hid-mgr.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/geninput/hid-report-in.c b/src/main/geninput/hid-report-in.c index 721a721d..aec213de 100644 --- a/src/main/geninput/hid-report-in.c +++ b/src/main/geninput/hid-report-in.c @@ -10,9 +10,10 @@ #include #include +#include "core/log.h" + #include "geninput/hid-report-in.h" -#include "util/log.h" #include "util/mem.h" void hid_report_in_init( diff --git a/src/main/geninput/hid-report-out.c b/src/main/geninput/hid-report-out.c index 5469e3a3..892cd13f 100644 --- a/src/main/geninput/hid-report-out.c +++ b/src/main/geninput/hid-report-out.c @@ -11,9 +11,10 @@ #include #include +#include "core/log.h" + #include "geninput/hid-report-out.h" -#include "util/log.h" #include "util/mem.h" bool hid_report_out_init( diff --git a/src/main/geninput/hid.c b/src/main/geninput/hid.c index 57685b02..889ee80f 100644 --- a/src/main/geninput/hid.c +++ b/src/main/geninput/hid.c @@ -1,10 +1,11 @@ #include #include +#include "core/log.h" + #include "geninput/dev-list.h" #include "geninput/hid.h" -#include "util/log.h" #include "util/str.h" wchar_t *hid_ri_init_name(const GUID *class_guid, const char *dev_node) diff --git a/src/main/geninput/hotplug.c b/src/main/geninput/hotplug.c index 54dad425..d9954d37 100644 --- a/src/main/geninput/hotplug.c +++ b/src/main/geninput/hotplug.c @@ -6,13 +6,13 @@ #include +#include "core/log.h" + #include "geninput/hid.h" #include "geninput/hotplug.h" #include "geninput/io-thread.h" #include "geninput/ri.h" -#include "util/log.h" - static HDEVNOTIFY hotplug_handle; void hotplug_init(HWND wnd) diff --git a/src/main/geninput/input.c b/src/main/geninput/input.c index 60c94c26..e67df3d7 100644 --- a/src/main/geninput/input.c +++ b/src/main/geninput/input.c @@ -7,6 +7,9 @@ #include "bemanitools/glue.h" #include "bemanitools/input.h" +#include "core/log.h" +#include "core/thread.h" + #include "geninput/hid-mgr.h" #include "geninput/hid.h" #include "geninput/io-thread.h" @@ -14,10 +17,8 @@ #include "geninput/mapper.h" #include "util/fs.h" -#include "util/log.h" #include "util/msg-thread.h" #include "util/str.h" -#include "util/thread.h" static HINSTANCE input_hinst; static volatile long input_init_count; @@ -47,7 +48,7 @@ void input_set_loggers( log_formatter_t warning, log_formatter_t fatal) { - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } void input_init( @@ -63,7 +64,7 @@ void input_init( mapper_inst = mapper_impl_create(); - thread_api_init(create, join, destroy); + core_thread_impl_set(create, join, destroy); msg_thread_init(input_hinst); io_thread_init(); } diff --git a/src/main/geninput/io-thread.c b/src/main/geninput/io-thread.c index ca663ff3..18f5ccca 100644 --- a/src/main/geninput/io-thread.c +++ b/src/main/geninput/io-thread.c @@ -2,6 +2,9 @@ #include +#include "core/log.h" +#include "core/thread.h" + #include "geninput/dev-list.h" #include "geninput/hid-generic.h" #include "geninput/hid-mgr.h" @@ -10,9 +13,7 @@ #include "geninput/pacdrive.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" -#include "util/thread.h" enum io_thread_cmd { IO_THREAD_CMD_STOP, @@ -172,7 +173,7 @@ void io_thread_init(void) barrier = CreateEvent(NULL, TRUE, FALSE, NULL); - io_thread_id = thread_create(io_thread_proc, barrier, 16384, 0); + io_thread_id = core_thread_create(io_thread_proc, barrier, 16384, 0); WaitForSingleObject(barrier, INFINITE); CloseHandle(barrier); @@ -200,6 +201,6 @@ void io_thread_fini(void) PostQueuedCompletionStatus(io_thread_cp, 0, (uintptr_t) &msg, NULL); - thread_join(io_thread_id, NULL); - thread_destroy(io_thread_id); + core_thread_join(io_thread_id, NULL); + core_thread_destroy(io_thread_id); } diff --git a/src/main/geninput/kbd.c b/src/main/geninput/kbd.c index 9ae8e5ed..191890b0 100644 --- a/src/main/geninput/kbd.c +++ b/src/main/geninput/kbd.c @@ -11,12 +11,13 @@ #include #include +#include "core/log.h" + #include "geninput/hid.h" #include "geninput/kbd-data.h" #include "geninput/kbd.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/geninput/mapper.c b/src/main/geninput/mapper.c index c3d02143..3c66c19c 100644 --- a/src/main/geninput/mapper.c +++ b/src/main/geninput/mapper.c @@ -1,11 +1,12 @@ #include #include +#include "core/log.h" + #include "geninput/hid-mgr.h" #include "geninput/mapper.h" #include "util/array.h" -#include "util/log.h" #include "util/mem.h" struct action_iter { diff --git a/src/main/geninput/mouse.c b/src/main/geninput/mouse.c index 2b24d1c8..f1c54bfd 100644 --- a/src/main/geninput/mouse.c +++ b/src/main/geninput/mouse.c @@ -12,11 +12,12 @@ #include #include +#include "core/log.h" + #include "geninput/hid.h" #include "geninput/mouse.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/geninput/msg-thread.c b/src/main/geninput/msg-thread.c index cc21ae06..7ca47f32 100644 --- a/src/main/geninput/msg-thread.c +++ b/src/main/geninput/msg-thread.c @@ -6,10 +6,11 @@ #include +#include "core/log.h" + #include "geninput/hotplug.h" #include "geninput/ri.h" -#include "util/log.h" #include "util/msg-thread.h" void msg_window_setup(HWND hwnd) diff --git a/src/main/geninput/pacdrive.c b/src/main/geninput/pacdrive.c index 2a5ab36f..b388036f 100644 --- a/src/main/geninput/pacdrive.c +++ b/src/main/geninput/pacdrive.c @@ -11,12 +11,13 @@ #include #include +#include "core/log.h" + #include "geninput/hid.h" #include "geninput/io-thread.h" #include "geninput/pacdrive.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" /* The PacDrive appears to have a malformed descriptor for its OUT report. diff --git a/src/main/geninput/ri.c b/src/main/geninput/ri.c index b3befee9..45b6d65b 100644 --- a/src/main/geninput/ri.c +++ b/src/main/geninput/ri.c @@ -1,5 +1,7 @@ #include +#include "core/log.h" + #include "geninput/hid-mgr.h" #include "geninput/hid.h" #include "geninput/kbd.h" @@ -7,7 +9,6 @@ #include "geninput/ri.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" struct ri_handle { diff --git a/src/main/hook/d3d9.c b/src/main/hook/d3d9.c index 45e1fd14..0f09ec68 100644 --- a/src/main/hook/d3d9.c +++ b/src/main/hook/d3d9.c @@ -1,11 +1,11 @@ #define LOG_MODULE "hook-d3d9" -#include "hook/d3d9.h" +#include "core/log.h" + #include "hook/com-proxy.h" +#include "hook/d3d9.h" #include "hook/table.h" -#include "util/log.h" - /* ------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/main/hook/table.c b/src/main/hook/table.c index 067ea75f..285a43d3 100644 --- a/src/main/hook/table.c +++ b/src/main/hook/table.c @@ -174,10 +174,11 @@ static void hook_table_revert_to_iid( sym = &syms[j]; if (hook_table_match_proc(&iate, sym)) { - // Only revert-able if the original pointer was stored previously + // 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)); - } + } } } } diff --git a/src/main/hooklib/acp.c b/src/main/hooklib/acp.c index 52675b2d..81faa738 100644 --- a/src/main/hooklib/acp.c +++ b/src/main/hooklib/acp.c @@ -6,13 +6,14 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "hooklib/acp.h" #include "util/codepage.h" #include "util/defs.h" -#include "util/log.h" static NTSTATUS NTAPI my_RtlMultiByteToUnicodeN( wchar_t *dest, diff --git a/src/main/hooklib/adapter.c b/src/main/hooklib/adapter.c index 0b94c125..8b3b6d9f 100644 --- a/src/main/hooklib/adapter.c +++ b/src/main/hooklib/adapter.c @@ -6,13 +6,14 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "hooklib/adapter.h" #include "util/codepage.h" #include "util/defs.h" -#include "util/log.h" static DWORD WINAPI my_GetAdaptersInfo(PIP_ADAPTER_INFO adapter_info, PULONG out_buf_len); diff --git a/src/main/hooklib/app.c b/src/main/hooklib/app.c index 7ccd441d..11fb8243 100644 --- a/src/main/hooklib/app.c +++ b/src/main/hooklib/app.c @@ -1,5 +1,7 @@ #include +#include "core/log.h" + #include "hook/table.h" #include "hooklib/app.h" @@ -7,7 +9,6 @@ #include "imports/avs.h" #include "imports/eapki.h" -#include "util/log.h" #include "util/str.h" static dll_entry_init_t hook_dll_entry_init; diff --git a/src/main/hooklib/config-adapter.c b/src/main/hooklib/config-adapter.c index 49c24505..0ddb6806 100644 --- a/src/main/hooklib/config-adapter.c +++ b/src/main/hooklib/config-adapter.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "hooklib/config-adapter.h" +#include "core/log.h" -#include "util/log.h" +#include "hooklib/config-adapter.h" #define HOOKLIB_CONFIG_ADAPTER_OVERRIDE_IP_KEY "adapter.override_ip" diff --git a/src/main/hooklib/memfile.c b/src/main/hooklib/memfile.c index 08dbeba1..09c05878 100644 --- a/src/main/hooklib/memfile.c +++ b/src/main/hooklib/memfile.c @@ -3,12 +3,13 @@ #include #include +#include "core/log.h" + #include "hook/hr.h" #include "hook/iohook.h" #include "hook/table.h" #include "util/array.h" -#include "util/log.h" #include "util/str.h" struct file_entry { diff --git a/src/main/hooklib/rs232.c b/src/main/hooklib/rs232.c index 8d6d371f..591c98f4 100644 --- a/src/main/hooklib/rs232.c +++ b/src/main/hooklib/rs232.c @@ -14,12 +14,13 @@ #include #include +#include "core/log.h" + #include "hook/hr.h" #include "hook/iohook.h" #include "hook/table.h" #include "util/array.h" -#include "util/log.h" /* RS232 API hooks */ diff --git a/src/main/hooklib/setupapi.c b/src/main/hooklib/setupapi.c index 07dc72cf..5363c52a 100644 --- a/src/main/hooklib/setupapi.c +++ b/src/main/hooklib/setupapi.c @@ -4,12 +4,13 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "hooklib/setupapi.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* my hooks */ diff --git a/src/main/iidx-bio2-exit-hook/Module.mk b/src/main/iidx-bio2-exit-hook/Module.mk index dcf7289b..085af3a8 100644 --- a/src/main/iidx-bio2-exit-hook/Module.mk +++ b/src/main/iidx-bio2-exit-hook/Module.mk @@ -4,6 +4,7 @@ ldflags_iidx-bio2-exit-hook := \ -lsetupapi \ libs_iidx-bio2-exit-hook := \ + core \ bio2drv \ hook \ util \ diff --git a/src/main/iidx-bio2-exit-hook/main.c b/src/main/iidx-bio2-exit-hook/main.c index 13ac8251..2718717c 100644 --- a/src/main/iidx-bio2-exit-hook/main.c +++ b/src/main/iidx-bio2-exit-hook/main.c @@ -11,10 +11,13 @@ #include "bio2/bi2a-iidx.h" #include "bio2drv/detect.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "hook/iobuf.h" #include "hook/iohook.h" -#include "util/log.h" #include "util/mem.h" #include "util/proc.h" #include "util/str.h" @@ -224,7 +227,8 @@ static HRESULT _iohook_handler(struct irp *irp) BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { - log_to_writer(log_writer_stdout, NULL); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stdout(); if (reason != DLL_PROCESS_ATTACH) { return TRUE; diff --git a/src/main/iidx-ezusb-exit-hook/Module.mk b/src/main/iidx-ezusb-exit-hook/Module.mk index 4050ebe1..500bd156 100644 --- a/src/main/iidx-ezusb-exit-hook/Module.mk +++ b/src/main/iidx-ezusb-exit-hook/Module.mk @@ -1,6 +1,7 @@ dlls += iidx-ezusb-exit-hook libs_iidx-ezusb-exit-hook := \ + core \ ezusb-iidx \ hook \ util \ diff --git a/src/main/iidx-ezusb2-exit-hook/Module.mk b/src/main/iidx-ezusb2-exit-hook/Module.mk index 7828a97b..f449f383 100644 --- a/src/main/iidx-ezusb2-exit-hook/Module.mk +++ b/src/main/iidx-ezusb2-exit-hook/Module.mk @@ -1,6 +1,7 @@ dlls += iidx-ezusb2-exit-hook libs_iidx-ezusb2-exit-hook := \ + core \ hook \ util \ diff --git a/src/main/iidx-irbeat-patch/Module.mk b/src/main/iidx-irbeat-patch/Module.mk index df42cd3d..d01645fa 100644 --- a/src/main/iidx-irbeat-patch/Module.mk +++ b/src/main/iidx-irbeat-patch/Module.mk @@ -2,6 +2,7 @@ exes += iidx-irbeat-patch libs_iidx-irbeat-patch := \ util \ + core \ src_iidx-irbeat-patch := \ main.c \ diff --git a/src/main/iidxhook-d3d9/bb-scale-hd.c b/src/main/iidxhook-d3d9/bb-scale-hd.c index 60dae6be..84afe7c8 100644 --- a/src/main/iidxhook-d3d9/bb-scale-hd.c +++ b/src/main/iidxhook-d3d9/bb-scale-hd.c @@ -2,13 +2,13 @@ #include +#include "core/log.h" + #include "d3d9-util/vertex.h" #include "bb-scale-hd.h" #include "util.h" -#include "util/log.h" - static bool iidxhook_d3d9_bb_scale_initialized; static uint16_t iidxhook_d3d9_bb_scale_hd_width; static uint16_t iidxhook_d3d9_bb_scale_hd_height; diff --git a/src/main/iidxhook-d3d9/util.h b/src/main/iidxhook-d3d9/util.h index 3a77e99b..96b489e0 100644 --- a/src/main/iidxhook-d3d9/util.h +++ b/src/main/iidxhook-d3d9/util.h @@ -3,9 +3,9 @@ #include -#include "d3d9-util/dxerr9.h" +#include "core/log.h" -#include "util/log.h" +#include "d3d9-util/dxerr9.h" inline void iidxhook_d3d9_util_check_and_handle_failure(HRESULT hr, const char *msg) diff --git a/src/main/iidxhook-util/Module.mk b/src/main/iidxhook-util/Module.mk index 92fe44d8..50dd18dc 100644 --- a/src/main/iidxhook-util/Module.mk +++ b/src/main/iidxhook-util/Module.mk @@ -1,6 +1,7 @@ libs += iidxhook-util libs_iidxhook-util := \ + core \ util \ src_iidxhook-util := \ diff --git a/src/main/iidxhook-util/acio.c b/src/main/iidxhook-util/acio.c index 62913361..a52fa766 100644 --- a/src/main/iidxhook-util/acio.c +++ b/src/main/iidxhook-util/acio.c @@ -15,6 +15,8 @@ #include "acioemu/emu.h" #include "acioemu/icca.h" +#include "core/log.h" + #include "hook/iohook.h" #include "hooklib/rs232.h" @@ -24,7 +26,6 @@ #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu iidxhook_util_acio_emu; diff --git a/src/main/iidxhook-util/chart-patch.c b/src/main/iidxhook-util/chart-patch.c index 4b2970ec..b6cc290d 100644 --- a/src/main/iidxhook-util/chart-patch.c +++ b/src/main/iidxhook-util/chart-patch.c @@ -4,12 +4,13 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" #include "util/crc.h" #include "util/defs.h" #include "util/fs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/iidxhook-util/clock.c b/src/main/iidxhook-util/clock.c index 8c870fff..dc0e2f2f 100644 --- a/src/main/iidxhook-util/clock.c +++ b/src/main/iidxhook-util/clock.c @@ -2,10 +2,11 @@ #include +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" static BOOL STDCALL my_SetLocalTime(const SYSTEMTIME *lpSystemTime); static BOOL(STDCALL *real_SetLocalTime)(const SYSTEMTIME *lpSystemTime); diff --git a/src/main/iidxhook-util/config-eamuse.c b/src/main/iidxhook-util/config-eamuse.c index f59b03e8..c75bd1a7 100644 --- a/src/main/iidxhook-util/config-eamuse.c +++ b/src/main/iidxhook-util/config-eamuse.c @@ -2,12 +2,12 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "iidxhook-util/config-eamuse.h" #include "security/mcode.h" -#include "util/log.h" - #define IIDXHOOK_CONFIG_EAMUSE_CARD_TYPE_KEY "eamuse.card_type" #define IIDXHOOK_CONFIG_EAMUSE_SERVER_KEY "eamuse.server" #define IIDXHOOK_CONFIG_EAMUSE_PCBID_KEY "eamuse.pcbid" diff --git a/src/main/iidxhook-util/config-ezusb.c b/src/main/iidxhook-util/config-ezusb.c index e860a1d5..14d953d2 100644 --- a/src/main/iidxhook-util/config-ezusb.c +++ b/src/main/iidxhook-util/config-ezusb.c @@ -3,9 +3,10 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "iidxhook-util/config-ezusb.h" -#include "util/log.h" #include "util/mem.h" #define IIDXHOOK_UTIL_CONFIG_EZUSB_API_CALL_MONITORING_KEY \ diff --git a/src/main/iidxhook-util/config-gfx.c b/src/main/iidxhook-util/config-gfx.c index e591f480..1be281fe 100644 --- a/src/main/iidxhook-util/config-gfx.c +++ b/src/main/iidxhook-util/config-gfx.c @@ -2,9 +2,9 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook-util/config-gfx.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook-util/config-gfx.h" #define IIDXHOOK_CONFIG_GFX_BGVIDEO_UV_FIX_KEY "gfx.bgvideo_uv_fix" #define IIDXHOOK_CONFIG_GFX_FRAMED_KEY "gfx.framed" diff --git a/src/main/iidxhook-util/config-io.c b/src/main/iidxhook-util/config-io.c index 3055ac6e..13c59f3c 100644 --- a/src/main/iidxhook-util/config-io.c +++ b/src/main/iidxhook-util/config-io.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook-util/config-io.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook-util/config-io.h" #define IIDXHOOK_UTIL_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY \ "io.disable_card_reader_emu" diff --git a/src/main/iidxhook-util/config-misc.c b/src/main/iidxhook-util/config-misc.c index 7a18499e..fca5ea5a 100644 --- a/src/main/iidxhook-util/config-misc.c +++ b/src/main/iidxhook-util/config-misc.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook-util/config-misc.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook-util/config-misc.h" #define IIDXHOOK_CONFIG_MISC_DISABLE_CLOCK_SET_KEY "misc.disable_clock_set" #define IIDXHOOK_CONFIG_MISC_RTEFFECT_STUB_KEY "misc.rteffect_stub" diff --git a/src/main/iidxhook-util/config-sec.c b/src/main/iidxhook-util/config-sec.c index 54cb0eea..a2e9a573 100644 --- a/src/main/iidxhook-util/config-sec.c +++ b/src/main/iidxhook-util/config-sec.c @@ -3,12 +3,13 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "iidxhook-util/config-sec.h" #include "security/mcode.h" #include "security/rp.h" -#include "util/log.h" #include "util/mem.h" #define IIDXHOOK_CONFIG_SEC_BOOT_VERSION_KEY "sec.boot_version" diff --git a/src/main/iidxhook-util/d3d9.c b/src/main/iidxhook-util/d3d9.c index 9c877199..eeb5081f 100644 --- a/src/main/iidxhook-util/d3d9.c +++ b/src/main/iidxhook-util/d3d9.c @@ -9,6 +9,8 @@ #include #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" @@ -16,7 +18,6 @@ #include "iidxhook-util/vertex-shader.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/iidxhook-util/eamuse.c b/src/main/iidxhook-util/eamuse.c index bd56c775..bbdcbe13 100644 --- a/src/main/iidxhook-util/eamuse.c +++ b/src/main/iidxhook-util/eamuse.c @@ -8,12 +8,13 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "iidxhook-util/eamuse.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* ------------------------------------------------------------------------- */ diff --git a/src/main/iidxhook-util/effector.c b/src/main/iidxhook-util/effector.c index 4f30e279..f313c602 100644 --- a/src/main/iidxhook-util/effector.c +++ b/src/main/iidxhook-util/effector.c @@ -2,12 +2,13 @@ #include +#include "core/log.h" + #include "hook/table.h" #include "iidxhook-util/effector.h" #include "util/defs.h" -#include "util/log.h" static BOOL my_EnableEqualizer(int a1); static BOOL my_GetEqualizerStatus(LPVOID buffer); diff --git a/src/main/iidxhook-util/log-server.c b/src/main/iidxhook-util/log-server.c index 1a7f687c..5a2fe3d2 100644 --- a/src/main/iidxhook-util/log-server.c +++ b/src/main/iidxhook-util/log-server.c @@ -5,6 +5,9 @@ #include #include +#include "core/log.h" +#include "core/thread.h" + #include "hook/table.h" #include "iidxhook-util/log-server.h" @@ -12,9 +15,7 @@ #include "imports/avs.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" static int log_thread_proc(void *ctx); static void @@ -39,7 +40,7 @@ void log_server_init(void) log_rv_consumer = CreateSemaphore(NULL, 0, 1, NULL); ready = CreateEvent(NULL, TRUE, FALSE, NULL); - log_to_external( + core_log_impl_set( log_post_misc, log_post_info, log_post_warning, log_post_fatal); log_thread_id = avs_thread_create(log_thread_proc, ready, 16384, 0); diff --git a/src/main/iidxhook-util/settings.c b/src/main/iidxhook-util/settings.c index b4f2ce3f..5402f695 100644 --- a/src/main/iidxhook-util/settings.c +++ b/src/main/iidxhook-util/settings.c @@ -7,12 +7,13 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" #include "hook/table.h" #include "util/defs.h" #include "util/fs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/iidxhook1/Module.mk b/src/main/iidxhook1/Module.mk index fcfad98b..61bb5ff4 100644 --- a/src/main/iidxhook1/Module.mk +++ b/src/main/iidxhook1/Module.mk @@ -5,6 +5,7 @@ ldflags_iidxhook1 := \ -liphlpapi \ libs_iidxhook1 := \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook1/config-iidxhook1.c b/src/main/iidxhook1/config-iidxhook1.c index 5a02c95c..b88d4718 100644 --- a/src/main/iidxhook1/config-iidxhook1.c +++ b/src/main/iidxhook1/config-iidxhook1.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook1/config-iidxhook1.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook1/config-iidxhook1.h" #define IIDXHOOK_CONFIG_MISC_HAPPY_SKY_MS_BG_FIX_KEY "misc.happy_sky_ms_bg_fix" diff --git a/src/main/iidxhook1/dllmain.c b/src/main/iidxhook1/dllmain.c index a3a4a357..60c61a75 100644 --- a/src/main/iidxhook1/dllmain.c +++ b/src/main/iidxhook1/dllmain.c @@ -9,6 +9,14 @@ #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "ezusb-emu/desc.h" #include "ezusb-emu/device.h" #include "ezusb-emu/node-security-plug.h" @@ -42,8 +50,6 @@ #include "iidxhook1/log-ezusb.h" #include "util/defs.h" -#include "util/log.h" -#include "util/thread.h" #define IIDXHOOK1_INFO_HEADER \ "iidxhook for 9th Style, 10th Style, RED and HAPPY SKY" \ @@ -68,6 +74,14 @@ static const struct hook_symbol init_hook_syms[] = { }, }; +static void _iidxhook1_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static void iidxhook1_setup_d3d9_hooks( const struct iidxhook_config_gfx *config_gfx, const struct iidxhook_config_iidxhook1 *config_iidxhook1) @@ -215,20 +229,24 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) /* Start up IIDXIO.DLL */ log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); - if (!iidx_io_init(thread_create, thread_join, thread_destroy)) { + if (!iidx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } /* Start up EAMIO.DLL */ log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + if (!eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } @@ -261,7 +279,9 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - log_to_writer(log_writer_debug, NULL); + core_thread_crt_ext_impl_set(); + + _iidxhook1_log_init(); /* Bootstrap hook for further init tasks (see above) */ diff --git a/src/main/iidxhook1/ezusb-mon.c b/src/main/iidxhook1/ezusb-mon.c index b76418a6..11f10f9e 100644 --- a/src/main/iidxhook1/ezusb-mon.c +++ b/src/main/iidxhook1/ezusb-mon.c @@ -6,12 +6,13 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "iidxhook1/ezusb-mon.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* ------------------------------------------------------------------------- */ diff --git a/src/main/iidxhook1/log-ezusb.c b/src/main/iidxhook1/log-ezusb.c index ac71907b..28a9c6a1 100644 --- a/src/main/iidxhook1/log-ezusb.c +++ b/src/main/iidxhook1/log-ezusb.c @@ -6,12 +6,13 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "iidxhook1/log-ezusb.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* ------------------------------------------------------------------------- */ diff --git a/src/main/iidxhook2/Module.mk b/src/main/iidxhook2/Module.mk index c9fd4389..46124426 100644 --- a/src/main/iidxhook2/Module.mk +++ b/src/main/iidxhook2/Module.mk @@ -5,6 +5,7 @@ ldflags_iidxhook2 := \ -liphlpapi \ libs_iidxhook2 := \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook2/config-iidxhook2.c b/src/main/iidxhook2/config-iidxhook2.c index 6e3ac6bd..0db10f3b 100644 --- a/src/main/iidxhook2/config-iidxhook2.c +++ b/src/main/iidxhook2/config-iidxhook2.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook2/config-iidxhook2.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook2/config-iidxhook2.h" #define IIDXHOOK_CONFIG_MISC_DISTORTED_MS_BG_FIX_KEY "misc.distorted_ms_bg_fix" diff --git a/src/main/iidxhook2/dllmain.c b/src/main/iidxhook2/dllmain.c index 25a76ed9..de8a9160 100644 --- a/src/main/iidxhook2/dllmain.c +++ b/src/main/iidxhook2/dllmain.c @@ -9,6 +9,14 @@ #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "ezusb-emu/desc.h" #include "ezusb-emu/device.h" #include "ezusb-emu/node-security-plug.h" @@ -41,9 +49,6 @@ #include "iidxhook2/config-iidxhook2.h" -#include "util/log.h" -#include "util/thread.h" - #define IIDXHOOK2_INFO_HEADER \ "iidxhook for DistorteD" \ ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) @@ -65,6 +70,14 @@ static const struct hook_symbol init_hook_syms[] = { .link = (void **) &real_OpenProcess}, }; +static void _iidxhook2_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static void iidxhook2_setup_d3d9_hooks( const struct iidxhook_config_gfx *config_gfx, const struct iidxhook_config_iidxhook2 *config_iidxhook2) @@ -207,20 +220,24 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) /* Start up IIDXIO.DLL */ log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); - if (!iidx_io_init(thread_create, thread_join, thread_destroy)) { + if (!iidx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } /* Start up EAMIO.DLL */ log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + if (!eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } @@ -254,7 +271,9 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - log_to_writer(log_writer_debug, NULL); + core_thread_crt_ext_impl_set(); + + _iidxhook2_log_init(); /* Bootstrap hook for further init tasks (see above) */ diff --git a/src/main/iidxhook3/Module.mk b/src/main/iidxhook3/Module.mk index a575a13b..ff698f24 100644 --- a/src/main/iidxhook3/Module.mk +++ b/src/main/iidxhook3/Module.mk @@ -8,6 +8,7 @@ deplibs_iidxhook3 := \ avs \ libs_iidxhook3 := \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook3/dllmain.c b/src/main/iidxhook3/dllmain.c index 49882b85..a62b4e34 100644 --- a/src/main/iidxhook3/dllmain.c +++ b/src/main/iidxhook3/dllmain.c @@ -9,6 +9,14 @@ #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "ezusb-emu/node-security-plug.h" #include "ezusb-iidx-emu/node-serial.h" #include "ezusb-iidx-emu/nodes.h" @@ -41,9 +49,7 @@ #include "security/rp-sign-key.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define IIDXHOOK3_INFO_HEADER \ "iidxhook for Gold, DJTroopers, Empress and Sirius" \ @@ -65,6 +71,14 @@ static const struct hook_symbol init_hook_syms[] = { .link = (void **) &real_OpenProcess}, }; +static void _iidxhook3_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static void iidxhook3_setup_d3d9_hooks(const struct iidxhook_config_gfx *config_gfx) { @@ -199,20 +213,24 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) /* Start up IIDXIO.DLL */ log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); - if (!iidx_io_init(thread_create, thread_join, thread_destroy)) { + if (!iidx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } /* Start up EAMIO.DLL */ log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + if (!eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } @@ -245,7 +263,9 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - log_to_writer(log_writer_debug, NULL); + core_thread_crt_ext_impl_set(); + + _iidxhook3_log_init(); /* Bootstrap hook for further init tasks (see above) */ diff --git a/src/main/iidxhook4-cn/Module.mk b/src/main/iidxhook4-cn/Module.mk index 0f7bceea..8cc34cb1 100644 --- a/src/main/iidxhook4-cn/Module.mk +++ b/src/main/iidxhook4-cn/Module.mk @@ -8,6 +8,8 @@ deplibs_iidxhook4-cn := \ avs \ libs_iidxhook4-cn := \ + avs-util \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook4-cn/avs-boot.c b/src/main/iidxhook4-cn/avs-boot.c index 378d80cf..4c498c95 100644 --- a/src/main/iidxhook4-cn/avs-boot.c +++ b/src/main/iidxhook4-cn/avs-boot.c @@ -3,14 +3,15 @@ #include #include +#include "core/log-bt.h" +#include "core/log.h" + #include "hook/table.h" #include "imports/avs.h" #include "iidxhook4-cn/avs-boot.h" -#include "util/log.h" - static void (*real_avs_boot)( struct property_node *config, void *std_heap, @@ -35,6 +36,11 @@ static const struct hook_symbol iidxhook4_cn_log_hook_syms[] = { .link = (void **) &real_avs_boot}, }; +static AVS_LOG_WRITER(_avs_boot_log_writer, chars, nchars, ctx) +{ + core_log_bt_direct_sink_write(chars, nchars); +} + static void avs_boot_replace_property_uint32( struct property_node *node, const char *name, uint32_t val) { @@ -87,7 +93,7 @@ static void my_avs_boot( sz_std_heap, avs_heap, sz_avs_heap, - log_writer_debug, + _avs_boot_log_writer, NULL); } diff --git a/src/main/iidxhook4-cn/dllmain.c b/src/main/iidxhook4-cn/dllmain.c index ea2f42ab..9fa61ba3 100644 --- a/src/main/iidxhook4-cn/dllmain.c +++ b/src/main/iidxhook4-cn/dllmain.c @@ -5,10 +5,19 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "ezusb-emu/node-security-plug.h" #include "ezusb2-emu/desc.h" @@ -37,8 +46,6 @@ #include "imports/avs.h" -#include "util/log.h" - #define IIDXHOOK4_CN_INFO_HEADER \ "iidxhook for Resort Anthem CN" \ ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) @@ -61,6 +68,14 @@ static const struct hook_symbol init_hook_syms[] = { static struct iidxhook_config_io config_io; +static void _iidxhook4_cn_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static void iidxhook4_cn_setup_d3d9_hooks(const struct iidxhook_config_gfx *config_gfx) { @@ -183,11 +198,12 @@ my_OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId) if (!config_io.disable_io_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } else { @@ -221,7 +237,11 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) return TRUE; } - log_to_writer(log_writer_debug, NULL); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + + // TODO init debug logging but with avs available? why not use avs logging? + _iidxhook4_cn_log_init(); hook_table_apply( NULL, "kernel32.dll", init_hook_syms, lengthof(init_hook_syms)); diff --git a/src/main/iidxhook4-cn/path.c b/src/main/iidxhook4-cn/path.c index 4ab6c5a3..5d4ef1dd 100644 --- a/src/main/iidxhook4-cn/path.c +++ b/src/main/iidxhook4-cn/path.c @@ -4,11 +4,12 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "iidxhook4-cn/path.h" -#include "util/log.h" #include "util/str.h" #define PATH_A "D:/JDZ-001/contents/" diff --git a/src/main/iidxhook4/Module.mk b/src/main/iidxhook4/Module.mk index 40f40b02..6546ac19 100644 --- a/src/main/iidxhook4/Module.mk +++ b/src/main/iidxhook4/Module.mk @@ -7,6 +7,8 @@ deplibs_iidxhook4 := \ avs \ libs_iidxhook4 := \ + avs-util \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook4/dllmain.c b/src/main/iidxhook4/dllmain.c index d7993dd7..c52336f3 100644 --- a/src/main/iidxhook4/dllmain.c +++ b/src/main/iidxhook4/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "ezusb-iidx-emu/nodes.h" #include "ezusb2-emu/desc.h" @@ -36,9 +41,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define IIDXHOOK4_INFO_HEADER \ "iidxhook for Resort Anthem" \ @@ -142,11 +145,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_io_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } else { @@ -156,11 +160,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } else { @@ -227,8 +232,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/iidxhook5-cn/Module.mk b/src/main/iidxhook5-cn/Module.mk index feea3d48..5addce9b 100644 --- a/src/main/iidxhook5-cn/Module.mk +++ b/src/main/iidxhook5-cn/Module.mk @@ -8,6 +8,8 @@ deplibs_iidxhook5-cn := \ avs \ libs_iidxhook5-cn := \ + avs-util \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook5-cn/avs-boot.c b/src/main/iidxhook5-cn/avs-boot.c index 772832ab..eaa1f459 100644 --- a/src/main/iidxhook5-cn/avs-boot.c +++ b/src/main/iidxhook5-cn/avs-boot.c @@ -3,14 +3,15 @@ #include #include +#include "core/log-bt.h" +#include "core/log.h" + #include "hook/table.h" #include "imports/avs.h" #include "iidxhook5-cn/avs-boot.h" -#include "util/log.h" - static void (*real_avs_boot)( struct property_node *config, void *std_heap, @@ -35,6 +36,11 @@ static const struct hook_symbol iidxhook5_cn_log_hook_syms[] = { .link = (void **) &real_avs_boot}, }; +static AVS_LOG_WRITER(_avs_boot_log_writer, chars, nchars, ctx) +{ + core_log_bt_direct_sink_write(chars, nchars); +} + static void avs_boot_replace_property_uint32( struct property_node *node, const char *name, uint32_t val) { @@ -87,7 +93,7 @@ static void my_avs_boot( sz_std_heap, avs_heap, sz_avs_heap, - log_writer_debug, + _avs_boot_log_writer, NULL); } diff --git a/src/main/iidxhook5-cn/dllmain.c b/src/main/iidxhook5-cn/dllmain.c index 711c906f..1d327dcf 100644 --- a/src/main/iidxhook5-cn/dllmain.c +++ b/src/main/iidxhook5-cn/dllmain.c @@ -5,10 +5,18 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread.h" + #include "ezusb-emu/node-security-plug.h" #include "ezusb2-emu/desc.h" @@ -37,8 +45,6 @@ #include "imports/avs.h" -#include "util/log.h" - #define IIDXHOOK5_CN_INFO_HEADER \ "iidxhook for tricoro CN" \ ", build " __DATE__ " " __TIME__ ", gitrev " STRINGIFY(GITREV) @@ -61,6 +67,14 @@ static const struct hook_symbol init_hook_user32_syms[] = { static struct iidxhook_config_io config_io; +static void _iidxhook5_cn_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static void iidxhook5_cn_setup_d3d9_hooks(const struct iidxhook_config_gfx *config_gfx) { @@ -163,11 +177,12 @@ static ATOM WINAPI my_RegisterClassA(const WNDCLASSA *lpWndClass) if (!config_io.disable_io_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } else { @@ -200,7 +215,11 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) return TRUE; } - log_to_writer(log_writer_debug, NULL); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + + // TODO init debug logging but with avs available? why not use avs logging? + _iidxhook5_cn_log_init(); hook_table_apply( NULL, diff --git a/src/main/iidxhook5-cn/path.c b/src/main/iidxhook5-cn/path.c index d45bf227..bb4f81d1 100644 --- a/src/main/iidxhook5-cn/path.c +++ b/src/main/iidxhook5-cn/path.c @@ -4,11 +4,12 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "iidxhook5-cn/path.h" -#include "util/log.h" #include "util/str.h" #define PATH_A "D:/JDZ-001/contents/" diff --git a/src/main/iidxhook5/Module.mk b/src/main/iidxhook5/Module.mk index 78ea3077..28cb7fe4 100644 --- a/src/main/iidxhook5/Module.mk +++ b/src/main/iidxhook5/Module.mk @@ -7,6 +7,8 @@ deplibs_iidxhook5 := \ avs \ libs_iidxhook5 := \ + avs-util \ + core \ iidxhook-util \ ezusb-emu \ ezusb-iidx-16seg-emu \ diff --git a/src/main/iidxhook5/dllmain.c b/src/main/iidxhook5/dllmain.c index 268bf395..0912c381 100644 --- a/src/main/iidxhook5/dllmain.c +++ b/src/main/iidxhook5/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "ezusb-iidx-emu/nodes.h" #include "ezusb2-emu/desc.h" @@ -36,9 +41,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #include "ifs-snd-redir.h" @@ -144,11 +147,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_io_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } else { @@ -158,11 +162,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } else { @@ -228,8 +233,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/iidxhook5/ifs-snd-redir.c b/src/main/iidxhook5/ifs-snd-redir.c index e73dd5ca..639d16fd 100644 --- a/src/main/iidxhook5/ifs-snd-redir.c +++ b/src/main/iidxhook5/ifs-snd-redir.c @@ -3,13 +3,14 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "imports/avs.h" #include "iidxhook5/ifs-snd-redir.h" -#include "util/log.h" #include "util/str.h" static void *(*real_avs_fs_open)(const char *path, int mode, int flags); diff --git a/src/main/iidxhook6/Module.mk b/src/main/iidxhook6/Module.mk index 7fda87b1..fe7e955e 100644 --- a/src/main/iidxhook6/Module.mk +++ b/src/main/iidxhook6/Module.mk @@ -7,6 +7,8 @@ deplibs_iidxhook6 := \ avs \ libs_iidxhook6 := \ + avs-util \ + core \ iidxhook-d3d9 \ iidxhook-util \ ezusb-emu \ diff --git a/src/main/iidxhook6/dllmain.c b/src/main/iidxhook6/dllmain.c index b4ca65aa..4b614a48 100644 --- a/src/main/iidxhook6/dllmain.c +++ b/src/main/iidxhook6/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "ezusb-iidx-emu/nodes.h" #include "ezusb2-emu/desc.h" @@ -35,9 +40,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define IIDXHOOK6_INFO_HEADER \ "iidxhook for Tricoro" \ @@ -121,11 +124,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_io_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } else { @@ -135,11 +139,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } else { @@ -203,8 +208,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/iidxhook7/Module.mk b/src/main/iidxhook7/Module.mk index a40366af..b1b6d937 100644 --- a/src/main/iidxhook7/Module.mk +++ b/src/main/iidxhook7/Module.mk @@ -8,6 +8,8 @@ deplibs_iidxhook7 := \ avs \ libs_iidxhook7 := \ + avs-util \ + core \ iidxhook-d3d9 \ iidxhook-util \ cconfig \ diff --git a/src/main/iidxhook7/dllmain.c b/src/main/iidxhook7/dllmain.c index af948f61..0823e854 100644 --- a/src/main/iidxhook7/dllmain.c +++ b/src/main/iidxhook7/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "ezusb-iidx-emu/nodes.h" #include "ezusb2-emu/desc.h" @@ -35,9 +40,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define IIDXHOOK7_INFO_HEADER \ "iidxhook for SPADA, PENDUAL, copula and SINOBUZ" \ @@ -121,11 +124,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_io_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } else { @@ -135,11 +139,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } else { @@ -203,8 +208,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/iidxhook8/Module.mk b/src/main/iidxhook8/Module.mk index e71c8c25..faebbbb9 100644 --- a/src/main/iidxhook8/Module.mk +++ b/src/main/iidxhook8/Module.mk @@ -12,6 +12,8 @@ deplibs_iidxhook8 := \ avs \ libs_iidxhook8 := \ + avs-util \ + core \ iidxhook-d3d9 \ iidxhook-util \ acioemu \ diff --git a/src/main/iidxhook8/config-io.c b/src/main/iidxhook8/config-io.c index 8cbb9b24..9f61e130 100644 --- a/src/main/iidxhook8/config-io.c +++ b/src/main/iidxhook8/config-io.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook8/config-io.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook8/config-io.h" #define IIDXHOOK8_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY \ "io.disable_card_reader_emu" diff --git a/src/main/iidxhook8/dllmain.c b/src/main/iidxhook8/dllmain.c index 7bf7e98c..64b8a0e5 100644 --- a/src/main/iidxhook8/dllmain.c +++ b/src/main/iidxhook8/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "hook/d3d9.h" #include "hooklib/acp.h" @@ -34,9 +39,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define IIDXHOOK8_INFO_HEADER \ "iidxhook for Cannon Ballers/Rootage" \ @@ -131,11 +134,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up IIDXIO.DLL */ if (!iidxhook8_config_io.disable_bio2_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } @@ -143,11 +147,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up EAMIO.DLL */ if (!iidxhook8_config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } @@ -217,8 +222,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/iidxhook9/Module.mk b/src/main/iidxhook9/Module.mk index 09e56a8c..33cd1955 100644 --- a/src/main/iidxhook9/Module.mk +++ b/src/main/iidxhook9/Module.mk @@ -12,6 +12,8 @@ deplibs_iidxhook9 := \ avs \ libs_iidxhook9 := \ + avs-util \ + core \ iidxhook-util \ acioemu \ asio \ diff --git a/src/main/iidxhook9/config-io.c b/src/main/iidxhook9/config-io.c index ba7ff45a..34deebfb 100644 --- a/src/main/iidxhook9/config-io.c +++ b/src/main/iidxhook9/config-io.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "iidxhook9/config-io.h" +#include "core/log.h" -#include "util/log.h" +#include "iidxhook9/config-io.h" #define IIDXHOOK9_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY \ "io.disable_card_reader_emu" diff --git a/src/main/iidxhook9/dllmain.c b/src/main/iidxhook9/dllmain.c index 7044d215..e93d1b4d 100644 --- a/src/main/iidxhook9/dllmain.c +++ b/src/main/iidxhook9/dllmain.c @@ -5,11 +5,18 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/iidxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread.h" + #include "hooklib/acp.h" #include "hooklib/adapter.h" #include "hooklib/app.h" @@ -39,9 +46,7 @@ #include "imports/avs.h" #include "util/cmdline.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define IIDXHOOK9_INFO_HEADER \ "iidxhook for Heroic Verse" \ @@ -98,6 +103,9 @@ static bool load_configs() static bool my_dll_entry_init(char *sidcode, struct property_node *param) { + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + // log_server_init is required due to IO occuring in a non avs_thread log_server_init(); @@ -125,11 +133,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up IIDXIO.DLL */ if (!iidxhook9_config_io.disable_bio2_emu) { log_info("Starting IIDX IO backend"); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); if (!iidx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing IIDX IO backend failed"); } } @@ -137,11 +146,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up EAMIO.DLL */ if (!iidxhook9_config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } @@ -276,8 +286,8 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) // if AVS is loaded, we're likely too late to be a prehook // so we warn the user // and switch the current logging context to AVS so it shows up in logs - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + avs_util_core_interop_log_avs_impl_set(); + log_warning("iidxhook9 is designed to be used as a prehook"); log_warning("please ensure that it is being loaded with -B"); log_fatal("cya l8r in the prehook :3"); @@ -285,7 +295,7 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) // we can't log to external in DllMain (AVS) as we're a prehook // later during my_dll_entry_init, log_server_init is called // which sets swaps the main log write to that instead - log_to_writer(log_writer_file, stdout); + core_log_bt_ext_impl_set(); } pre_hook(); diff --git a/src/main/iidxio-bio2/Module.mk b/src/main/iidxio-bio2/Module.mk index 09632c21..e91d76a1 100644 --- a/src/main/iidxio-bio2/Module.mk +++ b/src/main/iidxio-bio2/Module.mk @@ -4,6 +4,7 @@ ldflags_iidxio-bio2 := \ -lsetupapi \ libs_iidxio-bio2 := \ + core \ aciodrv \ bio2drv \ cconfig \ diff --git a/src/main/iidxio-ezusb/Module.mk b/src/main/iidxio-ezusb/Module.mk index ce9982c2..cc64914e 100644 --- a/src/main/iidxio-ezusb/Module.mk +++ b/src/main/iidxio-ezusb/Module.mk @@ -5,6 +5,7 @@ ldflags_iidxio-ezusb := \ -lsetupapi \ libs_iidxio-ezusb := \ + core \ ezusb \ ezusb2 \ ezusb-iidx \ diff --git a/src/main/iidxio-ezusb2/Module.mk b/src/main/iidxio-ezusb2/Module.mk index f1f19631..3371a785 100644 --- a/src/main/iidxio-ezusb2/Module.mk +++ b/src/main/iidxio-ezusb2/Module.mk @@ -5,6 +5,7 @@ ldflags_iidxio-ezusb2 := \ -lsetupapi \ libs_iidxio-ezusb2 := \ + core \ ezusb2 \ ezusb \ ezusb2-iidx \ diff --git a/src/main/iidxiotest/Module.mk b/src/main/iidxiotest/Module.mk index ce0536cf..94eca380 100644 --- a/src/main/iidxiotest/Module.mk +++ b/src/main/iidxiotest/Module.mk @@ -1,6 +1,7 @@ exes += iidxiotest \ libs_iidxiotest := \ + core \ iidxio \ util \ diff --git a/src/main/iidxiotest/main.c b/src/main/iidxiotest/main.c index ddd57cf4..a9b7daaa 100644 --- a/src/main/iidxiotest/main.c +++ b/src/main/iidxiotest/main.c @@ -7,8 +7,12 @@ #include "bemanitools/iidxio.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" static uint8_t _fix_top_lamps_order(uint8_t top_lamps) { @@ -55,12 +59,17 @@ static void _all_lights_off_shutdown() */ int main(int argc, char **argv) { - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_bt_ext_init_with_stdout(); - if (!iidx_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + core_log_impl_assign(iidx_io_set_loggers); + + if (!iidx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { printf("Initializing iidxio failed\n"); return -1; } diff --git a/src/main/jbhook-util-p3io/gfx.c b/src/main/jbhook-util-p3io/gfx.c index 63607fd8..0574b1f0 100644 --- a/src/main/jbhook-util-p3io/gfx.c +++ b/src/main/jbhook-util-p3io/gfx.c @@ -9,6 +9,8 @@ #include #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" @@ -17,7 +19,6 @@ #include "jbhook-util-p3io/gfx.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/jbhook-util-p3io/mixer.c b/src/main/jbhook-util-p3io/mixer.c index 30dcace9..ba52a638 100644 --- a/src/main/jbhook-util-p3io/mixer.c +++ b/src/main/jbhook-util-p3io/mixer.c @@ -6,10 +6,11 @@ #include // clang-format on +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" MMRESULT STDCALL hook_mixerGetLineControlsA( HMIXEROBJ hmxobj, LPMIXERLINECONTROLSA pmxlc, DWORD fdwControls); diff --git a/src/main/jbhook-util-p3io/p3io.c b/src/main/jbhook-util-p3io/p3io.c index 25b3364f..dd04607a 100644 --- a/src/main/jbhook-util-p3io/p3io.c +++ b/src/main/jbhook-util-p3io/p3io.c @@ -5,6 +5,8 @@ #include "bemanitools/jbio.h" +#include "core/log.h" + #include "jbhook-util-p3io/p3io.h" #include "p3ioemu/emu.h" @@ -13,8 +15,6 @@ #include "security/rp-sign-key.h" #include "security/rp3.h" -#include "util/log.h" - static HRESULT jbhook_p3io_read_jamma(void *ctx, uint32_t *state); static HRESULT jbhook_p3io_get_roundplug( void *ctx, uint8_t plug_id, uint8_t *rom, uint8_t *eeprom); diff --git a/src/main/jbhook-util/acio.c b/src/main/jbhook-util/acio.c index a1f5492e..8a1ed023 100644 --- a/src/main/jbhook-util/acio.c +++ b/src/main/jbhook-util/acio.c @@ -16,6 +16,8 @@ #include "acioemu/h44b.h" #include "acioemu/icca.h" +#include "core/log.h" + #include "hook/iohook.h" #include "jbhook-util/acio.h" @@ -25,7 +27,6 @@ #include "util/defs.h" #include "util/hex.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu ac_io_emu; diff --git a/src/main/jbhook-util/eamuse.c b/src/main/jbhook-util/eamuse.c index 0a796259..c9d1d906 100644 --- a/src/main/jbhook-util/eamuse.c +++ b/src/main/jbhook-util/eamuse.c @@ -9,10 +9,11 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/net.h" #include "util/str.h" diff --git a/src/main/jbhook-util/locale.c b/src/main/jbhook-util/locale.c index 272bbc64..a6b6d17a 100644 --- a/src/main/jbhook-util/locale.c +++ b/src/main/jbhook-util/locale.c @@ -2,10 +2,11 @@ #include +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" // ANSI/OEM Japanese; Japanese (Shift-JIS) #define CODEPAGE_SHIFT_JIS 932 diff --git a/src/main/jbhook-util/p4io.c b/src/main/jbhook-util/p4io.c index ab77d5dc..824ab803 100644 --- a/src/main/jbhook-util/p4io.c +++ b/src/main/jbhook-util/p4io.c @@ -4,14 +4,14 @@ #include "bemanitools/jbio.h" +#include "core/log.h" + #include "imports/avs.h" #include "jbhook-util/p4io.h" #include "p4io/cmd.h" -#include "util/log.h" - static void jbhook_io_jamma2_read(void *resp, uint32_t nbytes); static uint32_t jbhook_command_handle( uint8_t cmd, diff --git a/src/main/jbhook1/Module.mk b/src/main/jbhook1/Module.mk index b5e47776..c434f217 100644 --- a/src/main/jbhook1/Module.mk +++ b/src/main/jbhook1/Module.mk @@ -11,6 +11,7 @@ ldflags_jbhook1 := \ -lopengl32 \ libs_jbhook1 := \ + core \ acioemu \ cconfig \ eamio \ diff --git a/src/main/jbhook1/avs-boot.c b/src/main/jbhook1/avs-boot.c index dccba692..62d67733 100644 --- a/src/main/jbhook1/avs-boot.c +++ b/src/main/jbhook1/avs-boot.c @@ -5,14 +5,15 @@ #include #include +#include "core/log-bt.h" +#include "core/log.h" + #include "hook/table.h" #include "imports/avs.h" #include "jbhook1/avs-boot.h" -#include "util/log.h" - static void (*real_avs_boot)( struct property_node *config, void *std_heap, @@ -52,6 +53,11 @@ static const struct hook_symbol jbhook1_log_gftools_hook_syms2[] = { .link = (void **) &real_ea3_boot}, }; +static AVS_LOG_WRITER(_avs_boot_log_writer, chars, nchars, ctx) +{ + core_log_bt_direct_sink_write(chars, nchars); +} + static void avs_boot_create_property_str( struct property *config, const char *name, const char *val) { @@ -139,7 +145,7 @@ static void my_avs_boot( sz_std_heap, avs_heap, sz_avs_heap, - log_writer_debug, + _avs_boot_log_writer, NULL); } diff --git a/src/main/jbhook1/config-eamuse.c b/src/main/jbhook1/config-eamuse.c index dba512eb..77170e2c 100644 --- a/src/main/jbhook1/config-eamuse.c +++ b/src/main/jbhook1/config-eamuse.c @@ -2,9 +2,10 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "jbhook1/config-eamuse.h" -#include "util/log.h" #include "util/net.h" #define JBHOOK1_CONFIG_EAMUSE_SERVER_KEY "eamuse.server" diff --git a/src/main/jbhook1/config-gfx.c b/src/main/jbhook1/config-gfx.c index 5215659c..10102fa7 100644 --- a/src/main/jbhook1/config-gfx.c +++ b/src/main/jbhook1/config-gfx.c @@ -2,9 +2,9 @@ #include "cconfig/cconfig-util.h" -#include "jbhook1/config-gfx.h" +#include "core/log.h" -#include "util/log.h" +#include "jbhook1/config-gfx.h" #define JBHOOK1_CONFIG_GFX_WINDOWED_KEY "gfx.windowed" #define JBHOOK1_CONFIG_GFX_VERTICAL_KEY "gfx.vertical" diff --git a/src/main/jbhook1/config-security.c b/src/main/jbhook1/config-security.c index 565081e4..d6c56b05 100644 --- a/src/main/jbhook1/config-security.c +++ b/src/main/jbhook1/config-security.c @@ -2,11 +2,12 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "jbhook1/config-security.h" #include "security/mcode.h" -#include "util/log.h" #include "util/net.h" #define JBHOOK1_CONFIG_SECURITY_MCODE_KEY "security.mcode" diff --git a/src/main/jbhook1/dllmain.c b/src/main/jbhook1/dllmain.c index 42f8dc9d..f058d0f3 100644 --- a/src/main/jbhook1/dllmain.c +++ b/src/main/jbhook1/dllmain.c @@ -10,6 +10,14 @@ #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "hook/table.h" #include "hooklib/acp.h" @@ -33,8 +41,6 @@ #include "p3ioemu/emu.h" #include "util/defs.h" -#include "util/log.h" -#include "util/thread.h" #define JBHOOK1_INFO_HEADER \ "jbhook1 for jubeat" \ @@ -89,6 +95,14 @@ static const struct hook_symbol kernel32_hook_syms[] = { // so our CreateProcessA hook can check static bool vertical; +static void _jbhook1_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + /** * This seems to be a good entry point to intercept before the game calls * anything important (very close to the start of WinMain). @@ -153,19 +167,23 @@ static HWND CDECL my_mwindow_create( log_info("Starting up jubeat IO backend"); - jb_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(jb_io_set_loggers); - if (!jb_io_init(thread_create, thread_join, thread_destroy)) { + if (!jb_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing jb IO backend failed"); } log_info("Starting up card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + if (!eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } @@ -201,7 +219,12 @@ static HWND CDECL my_mwindow_create( BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - log_to_writer(log_writer_debug, NULL); + // TODO why not use AVS threads? + core_thread_crt_ext_impl_set(); + + // TODO init debug logging but with avs available? why not use avs + // logging? + _jbhook1_log_init(); /* Bootstrap hook for further init tasks (see above) */ diff --git a/src/main/jbhook1/log-gftools.c b/src/main/jbhook1/log-gftools.c index 97721736..f0dbf854 100644 --- a/src/main/jbhook1/log-gftools.c +++ b/src/main/jbhook1/log-gftools.c @@ -5,9 +5,9 @@ #include #include -#include "hook/table.h" +#include "core/log.h" -#include "util/log.h" +#include "hook/table.h" static int CDECL my_GFReportPuts( int level, diff --git a/src/main/jbhook2/Module.mk b/src/main/jbhook2/Module.mk index 2573867f..7ecebdb9 100644 --- a/src/main/jbhook2/Module.mk +++ b/src/main/jbhook2/Module.mk @@ -10,6 +10,8 @@ ldflags_jbhook2 := \ -lopengl32 \ libs_jbhook2 := \ + avs-util \ + core \ acioemu \ eamio \ jbio \ diff --git a/src/main/jbhook2/dllmain.c b/src/main/jbhook2/dllmain.c index a8523ad9..4c06e4dc 100644 --- a/src/main/jbhook2/dllmain.c +++ b/src/main/jbhook2/dllmain.c @@ -5,9 +5,14 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/jbio.h" +#include "core/log.h" +#include "core/thread.h" + #include "hook/iohook.h" #include "hook/table.h" @@ -36,8 +41,6 @@ #include "security/mcode.h" #include "util/defs.h" -#include "util/log.h" -#include "util/thread.h" static struct options options; @@ -80,11 +83,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) log_info("Starting up jubeat IO backend"); - jb_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(jb_io_set_loggers); - jb_io_ok = - jb_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + jb_io_ok = jb_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!jb_io_ok) { goto fail; @@ -99,11 +103,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) log_info("Starting up card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - eam_io_ok = - eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + eam_io_ok = eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!eam_io_ok) { goto fail; @@ -224,8 +229,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) return TRUE; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); options_init_from_cmdline(&options); diff --git a/src/main/jbhook2/options.c b/src/main/jbhook2/options.c index 9e1987e4..63ee6d45 100644 --- a/src/main/jbhook2/options.c +++ b/src/main/jbhook2/options.c @@ -9,7 +9,7 @@ #include "util/cmdline.h" #include "util/defs.h" #include "util/hex.h" -#include "util/log.h" + #include "util/str.h" void options_init_from_cmdline(struct options *options) diff --git a/src/main/jbhook3/Module.mk b/src/main/jbhook3/Module.mk index a82c4cc9..ca36a439 100644 --- a/src/main/jbhook3/Module.mk +++ b/src/main/jbhook3/Module.mk @@ -8,6 +8,8 @@ ldflags_jbhook3 := \ -liphlpapi \ libs_jbhook3 := \ + avs-util \ + core \ acioemu \ eamio \ jbio \ diff --git a/src/main/jbhook3/dllmain.c b/src/main/jbhook3/dllmain.c index b4eada83..30a0eb3d 100644 --- a/src/main/jbhook3/dllmain.c +++ b/src/main/jbhook3/dllmain.c @@ -5,9 +5,14 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/jbio.h" +#include "core/log.h" +#include "core/thread.h" + #include "hook/iohook.h" #include "hook/table.h" @@ -31,8 +36,6 @@ #include "security/id.h" #include "util/defs.h" -#include "util/log.h" -#include "util/thread.h" static struct options options; @@ -62,11 +65,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!options.disable_p4ioemu) { log_info("Starting up jubeat IO backend"); - jb_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(jb_io_set_loggers); - jb_io_ok = - jb_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + jb_io_ok = jb_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!jb_io_ok) { goto fail; @@ -79,11 +83,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) if (!options.disable_cardemu) { log_info("Starting up card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - eam_io_ok = - eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + eam_io_ok = eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!eam_io_ok) { goto fail; @@ -143,8 +148,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) return TRUE; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); options_init_from_cmdline(&options); diff --git a/src/main/jbhook3/gfx.c b/src/main/jbhook3/gfx.c index ee3b5ff5..827ac55f 100644 --- a/src/main/jbhook3/gfx.c +++ b/src/main/jbhook3/gfx.c @@ -5,12 +5,12 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "jbhook3/options.h" -#include "util/log.h" - static bool jbhook3_gfx_windowed; static bool jbhook3_gfx_show_cursor; diff --git a/src/main/jbhook3/options.c b/src/main/jbhook3/options.c index 59ffc1eb..8bb544b8 100644 --- a/src/main/jbhook3/options.c +++ b/src/main/jbhook3/options.c @@ -6,10 +6,11 @@ #include #include +#include "core/log.h" + #include "util/cmdline.h" #include "util/defs.h" #include "util/hex.h" -#include "util/log.h" #include "util/str.h" void options_init_from_cmdline(struct options *options) diff --git a/src/main/jbio-p4io/Module.mk b/src/main/jbio-p4io/Module.mk index b31604df..ca9895e3 100644 --- a/src/main/jbio-p4io/Module.mk +++ b/src/main/jbio-p4io/Module.mk @@ -9,6 +9,7 @@ src_jbio-p4io := \ jbio.c \ libs_jbio-p4io := \ + core \ aciodrv \ aciomgr \ cconfig \ diff --git a/src/main/jbio-p4io/config-h44b.c b/src/main/jbio-p4io/config-h44b.c index 91d37cb8..98a5f4f2 100644 --- a/src/main/jbio-p4io/config-h44b.c +++ b/src/main/jbio-p4io/config-h44b.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "jbio-p4io/config-h44b.h" +#include "core/log.h" -#include "util/log.h" +#include "jbio-p4io/config-h44b.h" #define JBIO_CONFIG_H44B_PORT_KEY "h44b.port" #define JBIO_CONFIG_H44B_BAUD_KEY "h44b.baud" diff --git a/src/main/jbio-p4io/h44b.c b/src/main/jbio-p4io/h44b.c index 1d9b77a0..f26aa2a1 100644 --- a/src/main/jbio-p4io/h44b.c +++ b/src/main/jbio-p4io/h44b.c @@ -10,7 +10,7 @@ #include "aciomgr/manager.h" -#include "util/log.h" +#include "core/log.h" static int16_t h44b_node_id; diff --git a/src/main/jbio-p4io/jbio.c b/src/main/jbio-p4io/jbio.c index 202187fe..58f4db9a 100644 --- a/src/main/jbio-p4io/jbio.c +++ b/src/main/jbio-p4io/jbio.c @@ -7,17 +7,17 @@ #include "aciomgr/manager.h" +#include "bemanitools/jbio.h" + #include "cconfig/cconfig-main.h" -#include "bemanitools/jbio.h" +#include "core/log.h" #include "jbio-p4io/config-h44b.h" #include "jbio-p4io/h44b.h" #include "p4iodrv/device.h" -#include "util/log.h" - static struct p4iodrv_ctx *p4io_ctx; static uint16_t jb_io_panels; static uint8_t jb_io_sys_buttons; @@ -36,7 +36,7 @@ void jb_io_set_loggers( { aciomgr_set_loggers(misc, info, warning, fatal); - log_to_external(misc, info, warning, fatal); + core_log_impl_set(misc, info, warning, fatal); } bool jb_io_init( diff --git a/src/main/jbiotest/Module.mk b/src/main/jbiotest/Module.mk index f2cf618b..eba9c0b1 100644 --- a/src/main/jbiotest/Module.mk +++ b/src/main/jbiotest/Module.mk @@ -1,6 +1,7 @@ exes += jbiotest \ libs_jbiotest := \ + core \ jbio \ util \ diff --git a/src/main/jbiotest/main.c b/src/main/jbiotest/main.c index 416b3251..0a6270b4 100644 --- a/src/main/jbiotest/main.c +++ b/src/main/jbiotest/main.c @@ -7,8 +7,12 @@ #include "bemanitools/jbio.h" -#include "util/log.h" -#include "util/thread.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" typedef struct { uint8_t r, g, b; @@ -23,12 +27,17 @@ enum jbio_light_mode { LIGHTS_OFF, LIGHTS_ON, LIGHTS_INPUT }; */ int main(int argc, char **argv) { - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); - jb_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_bt_ext_init_with_stdout(); - if (!jb_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + core_log_impl_assign(jb_io_set_loggers); + + if (!jb_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { printf("Initializing jbio failed\n"); return -1; } diff --git a/src/main/mempatch-hook/Module.mk b/src/main/mempatch-hook/Module.mk index 9785efa9..1dedd43e 100644 --- a/src/main/mempatch-hook/Module.mk +++ b/src/main/mempatch-hook/Module.mk @@ -2,6 +2,7 @@ dlls += mempatch-hook libs_mempatch-hook := \ util \ + core \ src_mempatch-hook := \ main.c \ diff --git a/src/main/mempatch-hook/main.c b/src/main/mempatch-hook/main.c index 43c1dcd9..856b962a 100644 --- a/src/main/mempatch-hook/main.c +++ b/src/main/mempatch-hook/main.c @@ -4,10 +4,13 @@ #include #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "util/cmdline.h" #include "util/fs.h" #include "util/hex.h" -#include "util/log.h" static bool patch_memory_check_data( uintptr_t base_address, @@ -283,12 +286,12 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) int argc; char **argv; char *filepath; - FILE *logfile; bool patched; patched = false; - logfile = fopen("mempatch.log", "w+"); - log_to_writer(log_writer_file, logfile); + + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_file("mempatch.log", false, false, 0); args_recover(&argc, &argv); @@ -319,8 +322,7 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) log_info("Patching done"); } - fflush(logfile); - fclose(logfile); + core_log_bt_fini(); } return TRUE; diff --git a/src/main/mm/mm.c b/src/main/mm/mm.c index db543a62..2c89957d 100644 --- a/src/main/mm/mm.c +++ b/src/main/mm/mm.c @@ -9,11 +9,12 @@ #include #include +#include "core/log.h" + #include "mm/mm.h" #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" DEFINE_GUID( diff --git a/src/main/p3io-ddr-tool/Module.mk b/src/main/p3io-ddr-tool/Module.mk index 2c76b428..f6e3a166 100644 --- a/src/main/p3io-ddr-tool/Module.mk +++ b/src/main/p3io-ddr-tool/Module.mk @@ -4,6 +4,7 @@ ldflags_p3io-ddr-tool := \ -lsetupapi \ libs_p3io-ddr-tool := \ + core \ extiodrv \ extio \ p3iodrv \ diff --git a/src/main/p3io-ddr-tool/main.c b/src/main/p3io-ddr-tool/main.c index 939e1e77..4a9bac3a 100644 --- a/src/main/p3io-ddr-tool/main.c +++ b/src/main/p3io-ddr-tool/main.c @@ -7,15 +7,17 @@ #include #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-std.h" +#include "core/log.h" + #include "extiodrv/extio.h" #include "p3io/ddr.h" #include "p3iodrv/ddr.h" #include "p3iodrv/device.h" -#include "util/log.h" -#include "util/thread.h" - #include "mode-test.h" enum mode { @@ -70,7 +72,7 @@ static mode_proc _mode_procs[MODE_TOTAL_COUNT] = { _mode_sensores, }; -static enum log_level _log_level = LOG_LEVEL_FATAL; +static enum core_log_bt_log_level _log_level = CORE_LOG_BT_LOG_LEVEL_FATAL; static bool _extio_enabled = true; static const char *_p3io_device_path = ""; static const char *_extio_com_port = "COM1"; @@ -196,11 +198,11 @@ static bool _process_cmd_args(int argc, char **argv) for (int i = 0; i < argc; i++) { if (!strcmp(argv[i], "-v")) { - _log_level = LOG_LEVEL_WARNING; + _log_level = CORE_LOG_BT_LOG_LEVEL_WARNING; } else if (!strcmp(argv[i], "-vv")) { - _log_level = LOG_LEVEL_INFO; + _log_level = CORE_LOG_BT_LOG_LEVEL_INFO; } else if (!strcmp(argv[i], "-vvv")) { - _log_level = LOG_LEVEL_MISC; + _log_level = CORE_LOG_BT_LOG_LEVEL_MISC; } else if (!strcmp(argv[i], "-noextio")) { _extio_enabled = false; } else if (!strcmp(argv[i], "-p3io")) { @@ -256,8 +258,9 @@ static bool _process_cmd_args(int argc, char **argv) static void _init_logging() { - log_to_writer(log_writer_stderr, NULL); - log_set_level(_log_level); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stderr(); + core_log_bt_level_set(_log_level); } static bool _mode_invalid(HANDLE handle) diff --git a/src/main/p3io-ddr-tool/mode-test.c b/src/main/p3io-ddr-tool/mode-test.c index 791e6d2c..0c3f5a9d 100644 --- a/src/main/p3io-ddr-tool/mode-test.c +++ b/src/main/p3io-ddr-tool/mode-test.c @@ -3,14 +3,14 @@ #include #include +#include "core/log.h" + #include "extiodrv/extio.h" #include "p3io/ddr.h" #include "p3iodrv/ddr.h" #include "p3iodrv/device.h" -#include "util/log.h" - #include "mode-test.h" struct mode_test_output_state { diff --git a/src/main/p3io/cmd.c b/src/main/p3io/cmd.c index 78c8b020..b62958ff 100644 --- a/src/main/p3io/cmd.c +++ b/src/main/p3io/cmd.c @@ -1,8 +1,8 @@ #include -#include "p3io/cmd.h" +#include "core/log.h" -#include "util/log.h" +#include "p3io/cmd.h" uint8_t p3io_get_full_req_size(const union p3io_req_any *req) { diff --git a/src/main/p3io/frame.c b/src/main/p3io/frame.c index bc50dd45..1fc6d2ce 100644 --- a/src/main/p3io/frame.c +++ b/src/main/p3io/frame.c @@ -3,10 +3,11 @@ #include #include +#include "core/log.h" + #include "p3io/frame.h" #include "util/iobuf.h" -#include "util/log.h" #define P3IO_FRAME_SOF 0xAA #define P3IO_FRAME_ESCAPE 0xFF diff --git a/src/main/p3iodrv/ddr.c b/src/main/p3iodrv/ddr.c index 2cc45261..57ebfab6 100644 --- a/src/main/p3iodrv/ddr.c +++ b/src/main/p3iodrv/ddr.c @@ -2,7 +2,7 @@ #include "p3io/cmd.h" -#include "util/log.h" +#include "core/log.h" #include "ddr.h" #include "device.h" diff --git a/src/main/p3iodrv/device.c b/src/main/p3iodrv/device.c index dbbb04af..afa2c512 100644 --- a/src/main/p3iodrv/device.c +++ b/src/main/p3iodrv/device.c @@ -10,13 +10,14 @@ #include // clang-format on +#include "core/log.h" + #include "p3io/cmd.h" #include "p3io/guid.h" #include "p3io/ioctl.h" #include "p3iodrv/device.h" -#include "util/log.h" #include "util/str.h" #define P3IO_DEVICE_FILENMAME "\\p3io" diff --git a/src/main/p3ioemu/devmgr.c b/src/main/p3ioemu/devmgr.c index fc729b35..ac26233d 100644 --- a/src/main/p3ioemu/devmgr.c +++ b/src/main/p3ioemu/devmgr.c @@ -7,6 +7,8 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "p3io/guid.h" @@ -14,7 +16,6 @@ #include "p3ioemu/devmgr.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" /* Link pointers */ diff --git a/src/main/p3ioemu/emu.c b/src/main/p3ioemu/emu.c index 6486d595..7fc21230 100644 --- a/src/main/p3ioemu/emu.c +++ b/src/main/p3ioemu/emu.c @@ -7,6 +7,8 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" #include "p3io/cmd.h" @@ -18,7 +20,6 @@ #include "p3ioemu/uart.h" #include "util/iobuf.h" -#include "util/log.h" static HANDLE p3io_emu_fd; static uint8_t p3io_emu_resp_bytes[256]; diff --git a/src/main/p3ioemu/uart.c b/src/main/p3ioemu/uart.c index a4a9f735..cbceb5a2 100644 --- a/src/main/p3ioemu/uart.c +++ b/src/main/p3ioemu/uart.c @@ -10,6 +10,8 @@ #include +#include "core/log.h" + #include "hook/iohook.h" #include "p3io/cmd.h" @@ -17,7 +19,6 @@ #include "p3ioemu/uart.h" #include "util/iobuf.h" -#include "util/log.h" static HRESULT p3io_uart_open(const wchar_t *path, uint32_t baud_rate, HANDLE *fd); diff --git a/src/main/p4iodrv/device.c b/src/main/p4iodrv/device.c index 67dd56e3..da65f7bb 100644 --- a/src/main/p4iodrv/device.c +++ b/src/main/p4iodrv/device.c @@ -2,12 +2,13 @@ #include +#include "core/log.h" + #include "p4io/cmd.h" #include "p4iodrv/device.h" #include "p4iodrv/usb.h" -#include "util/log.h" #include "util/mem.h" struct p4iodrv_ctx { diff --git a/src/main/p4iodrv/usb.c b/src/main/p4iodrv/usb.c index 8c99fc42..243adae5 100644 --- a/src/main/p4iodrv/usb.c +++ b/src/main/p4iodrv/usb.c @@ -10,13 +10,14 @@ #include // clang-format on +#include "core/log.h" + #include "p4io/cmd.h" #include "p4io/guid.h" #include "p4io/ioctl.h" #include "p4iodrv/usb.h" -#include "util/log.h" #include "util/str.h" HANDLE p4io_usb_open(void) diff --git a/src/main/p4ioemu/device.c b/src/main/p4ioemu/device.c index e18e8910..4e7498fc 100644 --- a/src/main/p4ioemu/device.c +++ b/src/main/p4ioemu/device.c @@ -6,11 +6,12 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" #include "p4io/cmd.h" #include "p4io/ioctl.h" #include "util/hex.h" -#include "util/log.h" #include "util/str.h" // #define P4IOEMU_DEBUG_DUMP diff --git a/src/main/pcbidgen/Module.mk b/src/main/pcbidgen/Module.mk index 852a4d00..612510f4 100644 --- a/src/main/pcbidgen/Module.mk +++ b/src/main/pcbidgen/Module.mk @@ -3,6 +3,7 @@ exes += pcbidgen libs_pcbidgen := \ security \ util \ + core \ src_pcbidgen := \ main.c \ diff --git a/src/main/popnhook-util/acio.c b/src/main/popnhook-util/acio.c index 60d66b84..465bdcdb 100644 --- a/src/main/popnhook-util/acio.c +++ b/src/main/popnhook-util/acio.c @@ -15,6 +15,8 @@ #include "acioemu/emu.h" #include "acioemu/icca.h" +#include "core/log.h" + #include "hook/iohook.h" #include "hooklib/rs232.h" @@ -24,7 +26,6 @@ #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu popnhook_acio_emu; diff --git a/src/main/popnhook-util/mixer.c b/src/main/popnhook-util/mixer.c index 91d3c759..904a008f 100644 --- a/src/main/popnhook-util/mixer.c +++ b/src/main/popnhook-util/mixer.c @@ -6,10 +6,11 @@ #include // clang-format on +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" MMRESULT STDCALL hook_mixerGetLineControlsA( HMIXEROBJ hmxobj, LPMIXERLINECONTROLSA pmxlc, DWORD fdwControls); diff --git a/src/main/popnhook1/Module.mk b/src/main/popnhook1/Module.mk index c2c15107..f7447737 100644 --- a/src/main/popnhook1/Module.mk +++ b/src/main/popnhook1/Module.mk @@ -8,6 +8,7 @@ ldflags_popnhook1 := \ -liphlpapi \ libs_popnhook1 := \ + core \ iidxhook-util \ ezusb-emu \ ezusb2-popn-emu \ diff --git a/src/main/popnhook1/avs-boot.c b/src/main/popnhook1/avs-boot.c index f9833a7b..aae46167 100644 --- a/src/main/popnhook1/avs-boot.c +++ b/src/main/popnhook1/avs-boot.c @@ -5,14 +5,14 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "imports/avs.h" #include "popnhook1/avs-boot.h" -#include "util/log.h" - static int (*real_ea3_boot_avs)(struct property_node *config); static int (*real_ea3_boot)(struct property_node *config); diff --git a/src/main/popnhook1/config-eamuse.c b/src/main/popnhook1/config-eamuse.c index 98e406b8..14864ba2 100644 --- a/src/main/popnhook1/config-eamuse.c +++ b/src/main/popnhook1/config-eamuse.c @@ -2,12 +2,12 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "popnhook1/config-eamuse.h" #include "security/mcode.h" -#include "util/log.h" - #define POPNHOOK1_CONFIG_EAMUSE_SERVER_KEY "eamuse.server" #define POPNHOOK1_CONFIG_EAMUSE_PCBID_KEY "eamuse.pcbid" #define POPNHOOK1_CONFIG_EAMUSE_EAMID_KEY "eamuse.eamid" diff --git a/src/main/popnhook1/config-gfx.c b/src/main/popnhook1/config-gfx.c index 8e231d11..8bc6b100 100644 --- a/src/main/popnhook1/config-gfx.c +++ b/src/main/popnhook1/config-gfx.c @@ -2,9 +2,9 @@ #include "cconfig/cconfig-util.h" -#include "popnhook1/config-gfx.h" +#include "core/log.h" -#include "util/log.h" +#include "popnhook1/config-gfx.h" #define POPNHOOK1_CONFIG_GFX_WINDOWED_KEY "gfx.windowed" #define POPNHOOK1_CONFIG_GFX_FRAMED_KEY "gfx.framed" diff --git a/src/main/popnhook1/config-sec.c b/src/main/popnhook1/config-sec.c index f1555200..99342b28 100644 --- a/src/main/popnhook1/config-sec.c +++ b/src/main/popnhook1/config-sec.c @@ -3,12 +3,13 @@ #include "cconfig/cconfig-util.h" +#include "core/log.h" + #include "popnhook1/config-sec.h" #include "security/mcode.h" #include "security/rp.h" -#include "util/log.h" #include "util/mem.h" #define POPNHOOK1_CONFIG_SEC_BLACK_PLUG_MCODE_KEY "sec.black_plug_mcode" diff --git a/src/main/popnhook1/d3d9.c b/src/main/popnhook1/d3d9.c index 1d67262c..44f9e1c1 100644 --- a/src/main/popnhook1/d3d9.c +++ b/src/main/popnhook1/d3d9.c @@ -5,6 +5,8 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/pe.h" #include "hook/table.h" @@ -12,7 +14,6 @@ #include "popnhook1/d3d9.h" #include "util/defs.h" -#include "util/log.h" #include "hook/d3d9.h" diff --git a/src/main/popnhook1/dllmain.c b/src/main/popnhook1/dllmain.c index 5995e222..36c232fb 100644 --- a/src/main/popnhook1/dllmain.c +++ b/src/main/popnhook1/dllmain.c @@ -10,6 +10,14 @@ #include "cconfig/cconfig-hook.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-debug.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "ezusb-emu/node-security-plug.h" #include "hook/d3d9.h" @@ -29,8 +37,6 @@ #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" -#include "util/thread.h" #include "ezusb2-emu/desc.h" #include "ezusb2-emu/device.h" @@ -62,6 +68,14 @@ static const struct hook_symbol init_hook_syms[] = { }, }; +static void _popnhook1_log_init() +{ + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_debug(); + // TODO change log level support + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_MISC); +} + static void popnhook_setup_d3d9_hooks( const struct popnhook1_config_gfx *config_gfx, const bool texture_usage_fix) { @@ -145,20 +159,24 @@ static DWORD STDCALL my_GetStartupInfoA(LPSTARTUPINFOA lpStartupInfo) /* Start up POPNIO.DLL */ log_info("Starting pop'n IO backend"); - popn_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(popn_io_set_loggers); - if (!popn_io_init(thread_create, thread_join, thread_destroy)) { + if (!popn_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing pop'n IO backend failed"); } /* Start up EAMIO.DLL */ log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); - if (!eam_io_init(thread_create, thread_join, thread_destroy)) { + if (!eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } @@ -190,7 +208,9 @@ BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) return TRUE; } - log_to_writer(log_writer_debug, NULL); + core_thread_crt_ext_impl_set(); + + _popnhook1_log_init(); hook_table_apply( NULL, "kernel32.dll", init_hook_syms, lengthof(init_hook_syms)); diff --git a/src/main/popnhook1/filesystem.c b/src/main/popnhook1/filesystem.c index 3d7fd09a..d5584864 100644 --- a/src/main/popnhook1/filesystem.c +++ b/src/main/popnhook1/filesystem.c @@ -7,10 +7,11 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/sdvxhook/Module.mk b/src/main/sdvxhook/Module.mk index c1a8c5b0..cc6d31c2 100644 --- a/src/main/sdvxhook/Module.mk +++ b/src/main/sdvxhook/Module.mk @@ -4,6 +4,8 @@ deplibs_sdvxhook := \ avs \ libs_sdvxhook := \ + avs-util \ + core \ acioemu \ hook \ hooklib \ diff --git a/src/main/sdvxhook/acio.c b/src/main/sdvxhook/acio.c index 6596ee41..e8d7dfda 100644 --- a/src/main/sdvxhook/acio.c +++ b/src/main/sdvxhook/acio.c @@ -15,6 +15,8 @@ #include "acioemu/emu.h" #include "acioemu/icca.h" +#include "core/log.h" + #include "hook/iohook.h" #include "imports/avs.h" @@ -24,7 +26,6 @@ #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu ac_io_emu; diff --git a/src/main/sdvxhook/dllmain.c b/src/main/sdvxhook/dllmain.c index 391f4c99..50e20637 100644 --- a/src/main/sdvxhook/dllmain.c +++ b/src/main/sdvxhook/dllmain.c @@ -3,9 +3,14 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/sdvxio.h" +#include "core/log.h" +#include "core/thread.h" + #include "hook/iohook.h" #include "hooklib/app.h" @@ -17,7 +22,6 @@ #include "util/cmdline.h" #include "util/defs.h" -#include "util/log.h" static bool my_dll_entry_init(char *sidcode, struct property_node *config); static bool my_dll_entry_main(void); @@ -32,10 +36,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *config) log_info("Starting up SDVX IO backend"); - sdvx_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(sdvx_io_set_loggers); - ok = sdvx_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + ok = sdvx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); if (!ok) { goto sdvx_io_fail; @@ -43,10 +49,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *config) log_info("Starting up card reader backend"); - eam_io_set_loggers( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + core_log_impl_assign(eam_io_set_loggers); - ok = eam_io_init(avs_thread_create, avs_thread_join, avs_thread_destroy); + ok = eam_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get()); /* Set up IO emulation hooks _after_ IO API setup to allow API implementations with real IO devices */ @@ -100,8 +108,9 @@ BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) return TRUE; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); args_recover(&argc, &argv); diff --git a/src/main/sdvxhook/gfx.c b/src/main/sdvxhook/gfx.c index 16cf7330..2d367590 100644 --- a/src/main/sdvxhook/gfx.c +++ b/src/main/sdvxhook/gfx.c @@ -3,6 +3,8 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/pe.h" #include "hook/table.h" @@ -10,7 +12,6 @@ #include "sdvxhook/gfx.h" #include "util/defs.h" -#include "util/log.h" static LRESULT CALLBACK my_WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); diff --git a/src/main/sdvxhook/kfca.c b/src/main/sdvxhook/kfca.c index 5c89686e..82ac111f 100644 --- a/src/main/sdvxhook/kfca.c +++ b/src/main/sdvxhook/kfca.c @@ -9,8 +9,9 @@ #include "bemanitools/sdvxio.h" +#include "core/log.h" + #include "util/defs.h" -#include "util/log.h" #include "util/time.h" static void kfca_send_version(const struct ac_io_message *req); diff --git a/src/main/sdvxhook/lcd.c b/src/main/sdvxhook/lcd.c index 06ea1e6c..6d942bbc 100644 --- a/src/main/sdvxhook/lcd.c +++ b/src/main/sdvxhook/lcd.c @@ -12,13 +12,14 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" #include "sdvxhook/lcd.h" #include "util/hex.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static HRESULT lcd_open(struct irp *irp); diff --git a/src/main/sdvxhook2-cn/Module.mk b/src/main/sdvxhook2-cn/Module.mk index fd00fc62..2bb715ef 100644 --- a/src/main/sdvxhook2-cn/Module.mk +++ b/src/main/sdvxhook2-cn/Module.mk @@ -12,6 +12,8 @@ deplibs_sdvxhook2-cn := \ avs \ libs_sdvxhook2-cn := \ + avs-util \ + core \ acioemu \ camhook \ d3d9exhook \ diff --git a/src/main/sdvxhook2-cn/acio.c b/src/main/sdvxhook2-cn/acio.c index f7116e28..4507333c 100644 --- a/src/main/sdvxhook2-cn/acio.c +++ b/src/main/sdvxhook2-cn/acio.c @@ -14,6 +14,8 @@ #include "acioemu/addr.h" #include "acioemu/emu.h" +#include "core/log.h" + #include "hook/iohook.h" #include "hooklib/rs232.h" @@ -24,7 +26,6 @@ #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu ac_io_emu; diff --git a/src/main/sdvxhook2-cn/config-cn.c b/src/main/sdvxhook2-cn/config-cn.c index c492603e..650b5282 100644 --- a/src/main/sdvxhook2-cn/config-cn.c +++ b/src/main/sdvxhook2-cn/config-cn.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "sdvxhook2-cn/config-cn.h" +#include "core/log.h" -#include "util/log.h" +#include "sdvxhook2-cn/config-cn.h" #define SDVXHOOK2_CN_CONFIG_DISABLE_IO_EMU_KEY "io.disable_io_emu" #define SDVXHOOK2_CN_CONFIG_UNIS_PATH_KEY "cn.unis_path" diff --git a/src/main/sdvxhook2-cn/dllmain.c b/src/main/sdvxhook2-cn/dllmain.c index d94cade5..ffcb36a4 100644 --- a/src/main/sdvxhook2-cn/dllmain.c +++ b/src/main/sdvxhook2-cn/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/sdvxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "hooklib/acp.h" #include "hooklib/adapter.h" #include "hooklib/app.h" @@ -27,9 +32,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define SDVXHOOK2_CN_INFO_HEADER \ "sdvxhook for VW CN" \ @@ -75,11 +78,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up sdvxio.DLL */ if (!config_cn.disable_io_emu) { log_info("Starting sdvx IO backend"); - sdvx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(sdvx_io_set_loggers); if (!sdvx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing sdvx IO backend failed"); } } @@ -134,8 +138,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/sdvxhook2-cn/kfca.c b/src/main/sdvxhook2-cn/kfca.c index f4d88948..02c72973 100644 --- a/src/main/sdvxhook2-cn/kfca.c +++ b/src/main/sdvxhook2-cn/kfca.c @@ -9,8 +9,9 @@ #include "bemanitools/sdvxio.h" +#include "core/log.h" + #include "util/defs.h" -#include "util/log.h" #include "util/time.h" static void kfca_send_version(const struct ac_io_message *req); diff --git a/src/main/sdvxhook2-cn/unis-version.c b/src/main/sdvxhook2-cn/unis-version.c index d6609992..3ed7e385 100644 --- a/src/main/sdvxhook2-cn/unis-version.c +++ b/src/main/sdvxhook2-cn/unis-version.c @@ -8,11 +8,12 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" #include "util/defs.h" -#include "util/log.h" #include "util/str.h" #include "util/time.h" diff --git a/src/main/sdvxhook2/Module.mk b/src/main/sdvxhook2/Module.mk index aa38c669..f813557b 100644 --- a/src/main/sdvxhook2/Module.mk +++ b/src/main/sdvxhook2/Module.mk @@ -12,6 +12,8 @@ deplibs_sdvxhook2 := \ avs \ libs_sdvxhook2 := \ + avs-util \ + core \ acioemu \ bio2emu \ camhook \ diff --git a/src/main/sdvxhook2/acio.c b/src/main/sdvxhook2/acio.c index 6ac0cdca..8db25df5 100644 --- a/src/main/sdvxhook2/acio.c +++ b/src/main/sdvxhook2/acio.c @@ -15,6 +15,8 @@ #include "acioemu/emu.h" #include "acioemu/icca.h" +#include "core/log.h" + #include "hook/iohook.h" #include "hooklib/rs232.h" @@ -24,7 +26,6 @@ #include "util/defs.h" #include "util/iobuf.h" -#include "util/log.h" #include "util/str.h" static struct ac_io_emu ac_io_emu; diff --git a/src/main/sdvxhook2/config-io.c b/src/main/sdvxhook2/config-io.c index 6d87bc0b..421f3af7 100644 --- a/src/main/sdvxhook2/config-io.c +++ b/src/main/sdvxhook2/config-io.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "sdvxhook2/config-io.h" +#include "core/log.h" -#include "util/log.h" +#include "sdvxhook2/config-io.h" #define SDVXHOOK2_CONFIG_IO_DISABLE_CARD_READER_EMU_KEY \ "io.disable_card_reader_emu" diff --git a/src/main/sdvxhook2/dllmain.c b/src/main/sdvxhook2/dllmain.c index f19142b0..bcf29ac1 100644 --- a/src/main/sdvxhook2/dllmain.c +++ b/src/main/sdvxhook2/dllmain.c @@ -5,11 +5,16 @@ #include #include +#include "avs-util/core-interop.h" + #include "bemanitools/eamio.h" #include "bemanitools/sdvxio.h" #include "cconfig/cconfig-hook.h" +#include "core/log.h" +#include "core/thread.h" + #include "hooklib/acp.h" #include "hooklib/adapter.h" #include "hooklib/app.h" @@ -33,9 +38,7 @@ #include "imports/avs.h" -#include "util/log.h" #include "util/str.h" -#include "util/thread.h" #define SDVXHOOK2_INFO_HEADER \ "sdvxhook for VW" \ @@ -105,11 +108,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up sdvxio.DLL */ if (!config_io.disable_bio2_emu) { log_info("Starting sdvx IO backend"); - sdvx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(sdvx_io_set_loggers); if (!sdvx_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing sdvx IO backend failed"); } } @@ -117,11 +121,12 @@ static bool my_dll_entry_init(char *sidcode, struct property_node *param) /* Start up EAMIO.DLL */ if (!config_io.disable_card_reader_emu) { log_misc("Initializing card reader backend"); - eam_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(eam_io_set_loggers); if (!eam_io_init( - avs_thread_create, avs_thread_join, avs_thread_destroy)) { + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_fatal("Initializing card reader backend failed"); } } @@ -207,8 +212,9 @@ BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) goto end; } - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); + // Use AVS APIs + avs_util_core_interop_thread_avs_impl_set(); + avs_util_core_interop_log_avs_impl_set(); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/sdvxhook2/nvapi.c b/src/main/sdvxhook2/nvapi.c index fbe14d83..e5f244a4 100644 --- a/src/main/sdvxhook2/nvapi.c +++ b/src/main/sdvxhook2/nvapi.c @@ -10,13 +10,13 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" #include "sdvxhook2/nvapi.h" -#include "util/log.h" - static void *my_GetProcAddress(HMODULE dll, const char *name); static void *(*real_GetProcAddress)(HMODULE dll, const char *name); diff --git a/src/main/sdvxhook2/power.c b/src/main/sdvxhook2/power.c index f860c1b9..e6d0d33a 100644 --- a/src/main/sdvxhook2/power.c +++ b/src/main/sdvxhook2/power.c @@ -10,13 +10,13 @@ #include +#include "core/log.h" + #include "hook/com-proxy.h" #include "hook/table.h" #include "sdvxhook2/power.h" -#include "util/log.h" - static DWORD my_PowerSetActiveScheme(HKEY UserRootPowerKey, const GUID *SchemeGuid); static DWORD my_PowerWriteACValueIndex( diff --git a/src/main/sdvxio-bio2/Module.mk b/src/main/sdvxio-bio2/Module.mk index bf16a3be..04ea0fde 100644 --- a/src/main/sdvxio-bio2/Module.mk +++ b/src/main/sdvxio-bio2/Module.mk @@ -4,6 +4,7 @@ ldflags_sdvxio-bio2 := \ -lsetupapi \ libs_sdvxio-bio2 := \ + core \ aciodrv \ bio2drv \ cconfig \ diff --git a/src/main/sdvxio-kfca/Module.mk b/src/main/sdvxio-kfca/Module.mk index cb10b225..5127c16a 100644 --- a/src/main/sdvxio-kfca/Module.mk +++ b/src/main/sdvxio-kfca/Module.mk @@ -1,6 +1,7 @@ dlls += sdvxio-kfca libs_sdvxio-kfca := \ + core \ geninput \ aciodrv \ aciomgr \ diff --git a/src/main/sdvxio-kfca/config-kfca.c b/src/main/sdvxio-kfca/config-kfca.c index 91814e67..6b95e24e 100644 --- a/src/main/sdvxio-kfca/config-kfca.c +++ b/src/main/sdvxio-kfca/config-kfca.c @@ -1,8 +1,8 @@ #include "cconfig/cconfig-util.h" -#include "sdvxio-kfca/config-kfca.h" +#include "core/log.h" -#include "util/log.h" +#include "sdvxio-kfca/config-kfca.h" #define SDVXIO_KFCA_CONFIG_KFCA_PORT_KEY "kfca.port" #define SDVXIO_KFCA_CONFIG_KFCA_BAUD_KEY "kfca.baud" diff --git a/src/main/security/id.c b/src/main/security/id.c index b591fb2d..93699aae 100644 --- a/src/main/security/id.c +++ b/src/main/security/id.c @@ -1,9 +1,10 @@ #include +#include "core/log.h" + #include "security/id.h" #include "util/hex.h" -#include "util/log.h" #include "util/mem.h" const struct security_id security_id_default = { diff --git a/src/main/security/mcode.c b/src/main/security/mcode.c index 5bc93fa9..88176473 100644 --- a/src/main/security/mcode.c +++ b/src/main/security/mcode.c @@ -1,8 +1,9 @@ #include +#include "core/log.h" + #include "security/mcode.h" -#include "util/log.h" #include "util/mem.h" const struct security_mcode security_mcode_eamuse = { diff --git a/src/main/security/rp-blowfish.c b/src/main/security/rp-blowfish.c index 9aeb6965..f45a0a99 100644 --- a/src/main/security/rp-blowfish.c +++ b/src/main/security/rp-blowfish.c @@ -1,10 +1,10 @@ #include +#include "core/log.h" + #include "security/rp-blowfish-table.h" #include "security/rp-blowfish.h" -#include "util/log.h" - static int security_rp_blowfish_enc_sub(int a1) { int result; // eax@1 diff --git a/src/main/security/rp.c b/src/main/security/rp.c index ba828644..5e80670b 100644 --- a/src/main/security/rp.c +++ b/src/main/security/rp.c @@ -1,3 +1,5 @@ +#include "core/log.h" + #include "security/rp.h" #include "security/rp-blowfish.h" @@ -5,7 +7,6 @@ #include "security/util.h" #include "util/crypto.h" -#include "util/log.h" static uint32_t security_rp_get_len_mcode(const struct security_mcode *mcode) { diff --git a/src/main/security/rp2.c b/src/main/security/rp2.c index 51480a69..92b3acb4 100644 --- a/src/main/security/rp2.c +++ b/src/main/security/rp2.c @@ -1,11 +1,12 @@ #include +#include "core/log.h" + #include "security/rp-util.h" #include "security/rp2.h" #include "security/util.h" #include "util/crypto.h" -#include "util/log.h" static uint8_t security_rp2_signature_scramble_table[16] = { 0x0C, diff --git a/src/main/security/rp3.c b/src/main/security/rp3.c index a6dd5bf5..6e83f683 100644 --- a/src/main/security/rp3.c +++ b/src/main/security/rp3.c @@ -1,12 +1,13 @@ #include +#include "core/log.h" + #include "security/rp-util.h" #include "security/rp2.h" #include "security/rp3.h" #include "security/util.h" #include "util/crc.h" -#include "util/log.h" void security_rp3_generate_signed_eeprom_data( enum security_rp_util_rp_type type, diff --git a/src/main/unicorntail/Module.mk b/src/main/unicorntail/Module.mk index bd6d3606..d2d96be1 100644 --- a/src/main/unicorntail/Module.mk +++ b/src/main/unicorntail/Module.mk @@ -4,6 +4,7 @@ deplibs_unicorntail := \ avs \ libs_unicorntail := \ + core \ p3io \ p3ioemu \ hook \ diff --git a/src/main/unicorntail/dllmain.c b/src/main/unicorntail/dllmain.c index 59f862de..b195edf5 100644 --- a/src/main/unicorntail/dllmain.c +++ b/src/main/unicorntail/dllmain.c @@ -2,6 +2,8 @@ #include +#include "core/log.h" + #include "hook/iohook.h" #include "hooklib/app.h" @@ -12,7 +14,6 @@ #include "unicorntail/usbmem.h" #include "util/defs.h" -#include "util/log.h" static bool my_dll_entry_init(char *sidcode, struct property_node *param); static bool my_dll_entry_main(void); @@ -50,7 +51,7 @@ BOOL WINAPI DllMain(HMODULE self, DWORD reason, void *ctx) goto end; } - log_to_external( + core_log_impl_set( log_body_misc, log_body_info, log_body_warning, log_body_fatal); app_hook_init(my_dll_entry_init, my_dll_entry_main); diff --git a/src/main/unicorntail/p3io.c b/src/main/unicorntail/p3io.c index fd0a85c4..b4a49b17 100644 --- a/src/main/unicorntail/p3io.c +++ b/src/main/unicorntail/p3io.c @@ -3,6 +3,8 @@ #include #include +#include "core/log.h" + #include "p3io/cmd.h" #include "p3io/frame.h" @@ -11,7 +13,6 @@ #include "unicorntail/p3io.h" #include "util/array.h" -#include "util/log.h" #include "util/str.h" static bool p3io_match_irp_locked(const struct irp *irp); diff --git a/src/main/unicorntail/usbmem.c b/src/main/unicorntail/usbmem.c index 50b496e6..ef820699 100644 --- a/src/main/unicorntail/usbmem.c +++ b/src/main/unicorntail/usbmem.c @@ -3,9 +3,10 @@ #include #include +#include "core/log.h" + #include "hook/iohook.h" -#include "util/log.h" #include "util/str.h" static bool usbmem_match_irp(const struct irp *irp); diff --git a/src/main/util/array.c b/src/main/util/array.c index 900fba17..9421fffa 100644 --- a/src/main/util/array.c +++ b/src/main/util/array.c @@ -2,8 +2,9 @@ #include #include +#include "core/log.h" + #include "util/array.h" -#include "util/log.h" #include "util/mem.h" void array_init(struct array *array) diff --git a/src/main/util/crc.c b/src/main/util/crc.c index c49af451..7ab06b84 100644 --- a/src/main/util/crc.c +++ b/src/main/util/crc.c @@ -1,7 +1,7 @@ #include #include -#include "util/log.h" +#include "core/log.h" uint8_t crc8(const void *ptr, size_t nbytes, uint8_t in) { diff --git a/src/main/util/crypto.c b/src/main/util/crypto.c index 110dd99e..db1702e8 100644 --- a/src/main/util/crypto.c +++ b/src/main/util/crypto.c @@ -1,12 +1,13 @@ #define LOG_MODULE "crypto" -#include "util/crypto.h" -#include "util/log.h" -#include "util/mem.h" - #include #include +#include "core/log.h" + +#include "util/crypto.h" +#include "util/mem.h" + static const char vista_prov[] = "Microsoft Enhanced RSA and AES Cryptographic Provider"; static const char winxp_prov[] = diff --git a/src/main/util/fs.c b/src/main/util/fs.c index 731fdf93..8c4c7c68 100644 --- a/src/main/util/fs.c +++ b/src/main/util/fs.c @@ -6,9 +6,10 @@ #include #include +#include "core/log.h" + #include "util/defs.h" #include "util/fs.h" -#include "util/log.h" #include "util/mem.h" #include "util/str.h" diff --git a/src/main/util/hex.c b/src/main/util/hex.c index 1ac6b4d6..de08286d 100644 --- a/src/main/util/hex.c +++ b/src/main/util/hex.c @@ -2,8 +2,9 @@ #include #include +#include "core/log.h" + #include "util/hex.h" -#include "util/log.h" static bool hex_decode_nibble(char c, uint8_t *nibble) { diff --git a/src/main/util/iobuf.c b/src/main/util/iobuf.c index 45884ead..7638cb50 100644 --- a/src/main/util/iobuf.c +++ b/src/main/util/iobuf.c @@ -1,8 +1,9 @@ #define LOG_MODULE "util-iobuf" -#include "util/iobuf.h" +#include "core/log.h" + #include "util/hex.h" -#include "util/log.h" +#include "util/iobuf.h" #include "util/mem.h" void iobuf_log(struct iobuf *buffer, const char *tag) diff --git a/src/main/util/mem.c b/src/main/util/mem.c index 53e84ffb..f03ebafc 100644 --- a/src/main/util/mem.c +++ b/src/main/util/mem.c @@ -7,7 +7,7 @@ #include #include -#include "util/log.h" +#include "core/log.h" void *xcalloc(size_t nbytes) { diff --git a/src/main/util/msg-thread.c b/src/main/util/msg-thread.c index 6b5623d8..04d498d3 100644 --- a/src/main/util/msg-thread.c +++ b/src/main/util/msg-thread.c @@ -5,9 +5,10 @@ #include #include -#include "util/log.h" +#include "core/log.h" +#include "core/thread.h" + #include "util/msg-thread.h" -#include "util/thread.h" static bool msg_thread_step(HWND hwnd); @@ -102,7 +103,7 @@ void msg_thread_init(HINSTANCE inst) { msg_thread_ready = CreateEvent(NULL, TRUE, FALSE, NULL); msg_thread_stop = CreateEvent(NULL, TRUE, FALSE, NULL); - msg_thread_id = thread_create(msg_thread_proc, inst, 0x4000, 0); + msg_thread_id = core_thread_create(msg_thread_proc, inst, 0x4000, 0); WaitForSingleObject(msg_thread_ready, INFINITE); CloseHandle(msg_thread_ready); @@ -112,8 +113,8 @@ void msg_thread_fini(void) { SetEvent(msg_thread_stop); - thread_join(msg_thread_id, NULL); - thread_destroy(msg_thread_id); + core_thread_join(msg_thread_id, NULL); + core_thread_destroy(msg_thread_id); CloseHandle(msg_thread_stop); } diff --git a/src/main/util/net.c b/src/main/util/net.c index 492e3ef1..d2c9d7d5 100644 --- a/src/main/util/net.c +++ b/src/main/util/net.c @@ -6,7 +6,8 @@ #include #include -#include "util/log.h" +#include "core/log.h" + #include "util/mem.h" #include "util/net.h" #include "util/str.h" diff --git a/src/main/util/os.c b/src/main/util/os.c index c63de00d..e6aad5dc 100644 --- a/src/main/util/os.c +++ b/src/main/util/os.c @@ -6,7 +6,8 @@ #include -#include "util/log.h" +#include "core/log.h" + #include "util/os.h" #include "util/str.h" diff --git a/src/main/util/proc.c b/src/main/util/proc.c index 84731133..f1905d32 100644 --- a/src/main/util/proc.c +++ b/src/main/util/proc.c @@ -6,7 +6,7 @@ #include #include -#include "util/log.h" +#include "core/log.h" bool proc_is_running_as_admin_user() { diff --git a/src/main/util/signal.c b/src/main/util/signal.c index 4c15acd5..9851c920 100644 --- a/src/main/util/signal.c +++ b/src/main/util/signal.c @@ -1,8 +1,9 @@ #include #include +#include "core/log.h" + #include "util/hex.h" -#include "util/log.h" #include "util/signal.h" static signal_shutdown_handler_t shutdown_handler; diff --git a/src/main/util/time.c b/src/main/util/time.c index 0412b29c..feb29045 100644 --- a/src/main/util/time.c +++ b/src/main/util/time.c @@ -1,6 +1,7 @@ #include -#include "util/log.h" +#include "core/log.h" + #include "util/time.h" static uint64_t counter_freq_ns; diff --git a/src/main/vigem-ddrio/Module.mk b/src/main/vigem-ddrio/Module.mk index 8aae8a25..82e0f51f 100644 --- a/src/main/vigem-ddrio/Module.mk +++ b/src/main/vigem-ddrio/Module.mk @@ -10,6 +10,7 @@ ldflags_vigem-ddrio := \ -lsetupapi \ libs_vigem-ddrio := \ + core \ cconfig \ ddrio \ util \ diff --git a/src/main/vigem-ddrio/config-vigem-ddrio.c b/src/main/vigem-ddrio/config-vigem-ddrio.c index 59e8251f..0ff44f23 100644 --- a/src/main/vigem-ddrio/config-vigem-ddrio.c +++ b/src/main/vigem-ddrio/config-vigem-ddrio.c @@ -1,9 +1,9 @@ #include "cconfig/cconfig-main.h" #include "cconfig/cconfig-util.h" -#include "vigem-ddrio/config-vigem-ddrio.h" +#include "core/log.h" -#include "util/log.h" +#include "vigem-ddrio/config-vigem-ddrio.h" #define VIGEM_DDRIO_CONFIG_ENABLE_REACTIVE_LIGHT_KEY \ "ddrio.enable_reactive_light" diff --git a/src/main/vigem-ddrio/main.c b/src/main/vigem-ddrio/main.c index 62cc7b18..7d7fafa7 100644 --- a/src/main/vigem-ddrio/main.c +++ b/src/main/vigem-ddrio/main.c @@ -8,9 +8,17 @@ #include "ViGEm/Client.h" #include "bemanitools/ddrio.h" -#include "util/log.h" + +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-std.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "util/math.h" -#include "util/thread.h" + #include "vigemstub/helper.h" #include "vigem-ddrio/config-vigem-ddrio.h" @@ -112,17 +120,23 @@ void set_reactive_lights(uint32_t input_state) int main(int argc, char **argv) { - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); + + core_log_bt_ext_init_with_stdout(); + core_log_bt_level_set(CORE_LOG_BT_LOG_LEVEL_INFO); struct vigem_ddrio_config config; if (!get_vigem_ddrio_config(&config)) { exit(EXIT_FAILURE); } - ddr_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(ddr_io_set_loggers); - if (!ddr_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + if (!ddr_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_warning("Initializing ddrio failed"); return -1; } diff --git a/src/main/vigem-iidxio/Module.mk b/src/main/vigem-iidxio/Module.mk index 887a70f0..4fae200c 100644 --- a/src/main/vigem-iidxio/Module.mk +++ b/src/main/vigem-iidxio/Module.mk @@ -10,6 +10,7 @@ ldflags_vigem-iidxio := \ -lsetupapi \ libs_vigem-iidxio := \ + core \ cconfig \ iidxio \ util \ diff --git a/src/main/vigem-iidxio/cab-16seg-sequencer.c b/src/main/vigem-iidxio/cab-16seg-sequencer.c index 01ea12bd..f7e6e05e 100644 --- a/src/main/vigem-iidxio/cab-16seg-sequencer.c +++ b/src/main/vigem-iidxio/cab-16seg-sequencer.c @@ -4,7 +4,8 @@ #include #include -#include "util/log.h" +#include "core/log.h" + #include "util/time.h" static const uint8_t _MAX_LEN_16SEG = 9; diff --git a/src/main/vigem-iidxio/cab-light-sequencer.c b/src/main/vigem-iidxio/cab-light-sequencer.c index 95214f90..6b345b75 100644 --- a/src/main/vigem-iidxio/cab-light-sequencer.c +++ b/src/main/vigem-iidxio/cab-light-sequencer.c @@ -4,9 +4,10 @@ #include #include +#include "core/log.h" + #include "vigem-iidxio/cab-light-sequencer.h" -#include "util/log.h" #include "util/math.h" #include "util/time.h" diff --git a/src/main/vigem-iidxio/config.c b/src/main/vigem-iidxio/config.c index 40eeae56..acf52359 100644 --- a/src/main/vigem-iidxio/config.c +++ b/src/main/vigem-iidxio/config.c @@ -1,9 +1,9 @@ #include "cconfig/cconfig-main.h" #include "cconfig/cconfig-util.h" -#include "vigem-iidxio/config.h" +#include "core/log.h" -#include "util/log.h" +#include "vigem-iidxio/config.h" #define VIGEM_IIDXIO_CONFIG_TT_ANALOG_RELATIVE_KEY \ "vigem.iidxio.tt.anlog.relative" diff --git a/src/main/vigem-iidxio/main.c b/src/main/vigem-iidxio/main.c index 3527545a..51b0b147 100644 --- a/src/main/vigem-iidxio/main.c +++ b/src/main/vigem-iidxio/main.c @@ -9,9 +9,14 @@ #include "bemanitools/iidxio.h" -#include "util/log.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "util/math.h" -#include "util/thread.h" #include "util/time.h" #include "vigem-iidxio/cab-16seg-sequencer.h" @@ -259,7 +264,10 @@ static void _all_lights_off() int main(int argc, char **argv) { - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); + + core_log_bt_ext_init_with_stdout(); struct vigem_iidxio_config config; @@ -267,10 +275,12 @@ int main(int argc, char **argv) return -1; } - iidx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(iidx_io_set_loggers); - if (!iidx_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + if (!iidx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_warning("Initializing iidxio failed"); return -1; } diff --git a/src/main/vigem-sdvxio/Module.mk b/src/main/vigem-sdvxio/Module.mk index 6907fbf8..523dd69f 100644 --- a/src/main/vigem-sdvxio/Module.mk +++ b/src/main/vigem-sdvxio/Module.mk @@ -10,6 +10,7 @@ ldflags_vigem-sdvxio := \ -lsetupapi \ libs_vigem-sdvxio := \ + core \ cconfig \ sdvxio \ util \ diff --git a/src/main/vigem-sdvxio/config-vigem-sdvxio.c b/src/main/vigem-sdvxio/config-vigem-sdvxio.c index 768a514c..faae9e29 100644 --- a/src/main/vigem-sdvxio/config-vigem-sdvxio.c +++ b/src/main/vigem-sdvxio/config-vigem-sdvxio.c @@ -1,9 +1,9 @@ #include "cconfig/cconfig-main.h" #include "cconfig/cconfig-util.h" -#include "vigem-sdvxio/config-vigem-sdvxio.h" +#include "core/log.h" -#include "util/log.h" +#include "vigem-sdvxio/config-vigem-sdvxio.h" #define VIGEM_SDVXIO_CONFIG_ENABLE_KEYLIGHT_KEY "sdvxio.enable_keylight" #define VIGEM_SDVXIO_CONFIG_RELATIVE_ANALOG_KEY "sdvxio.use_relative_analog" diff --git a/src/main/vigem-sdvxio/main.c b/src/main/vigem-sdvxio/main.c index cf4b4e22..c87a4361 100644 --- a/src/main/vigem-sdvxio/main.c +++ b/src/main/vigem-sdvxio/main.c @@ -8,9 +8,16 @@ #include "ViGEm/Client.h" #include "bemanitools/sdvxio.h" -#include "util/log.h" + +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "util/math.h" -#include "util/thread.h" + #include "vigemstub/helper.h" #include "vigem-sdvxio/config-vigem-sdvxio.h" @@ -129,17 +136,21 @@ void set_pwm_brightness(uint8_t wing_pwm, uint8_t controller_pwm) int main(int argc, char **argv) { - log_to_writer(log_writer_stdout, NULL); + core_thread_crt_ext_impl_set(); + core_log_bt_ext_impl_set(); + core_log_bt_ext_init_with_stdout(); struct vigem_sdvxio_config config; if (!get_vigem_sdvxio_config(&config)) { exit(EXIT_FAILURE); } - sdvx_io_set_loggers( - log_impl_misc, log_impl_info, log_impl_warning, log_impl_fatal); + core_log_impl_assign(sdvx_io_set_loggers); - if (!sdvx_io_init(crt_thread_create, crt_thread_join, crt_thread_destroy)) { + if (!sdvx_io_init( + core_thread_create_impl_get(), + core_thread_join_impl_get(), + core_thread_destroy_impl_get())) { log_warning("Initializing sdvxio failed"); return -1; } diff --git a/src/main/vigemstub/helper.c b/src/main/vigemstub/helper.c index 4aeb5192..a92510b2 100644 --- a/src/main/vigemstub/helper.c +++ b/src/main/vigemstub/helper.c @@ -2,9 +2,10 @@ #include #include +#include "core/log.h" + #include "ViGEm/Client.h" -#include "util/log.h" #include "vigemstub/helper.h" PVIGEM_CLIENT vigem_helper_setup(void) diff --git a/src/test/cconfig/Module.mk b/src/test/cconfig/Module.mk index a61e8d9b..81433890 100644 --- a/src/test/cconfig/Module.mk +++ b/src/test/cconfig/Module.mk @@ -3,6 +3,7 @@ testexes += cconfig-test srcdir_cconfig-test := src/test/cconfig libs_cconfig-test := \ + core \ cconfig \ test \ util \ @@ -17,6 +18,7 @@ testexes += cconfig-util-test srcdir_cconfig-util-test := src/test/cconfig libs_cconfig-util-test := \ + core \ cconfig \ test \ util \ @@ -31,6 +33,7 @@ testexes += cconfig-cmd-test srcdir_cconfig-cmd-test := src/test/cconfig libs_cconfig-cmd-test := \ + core \ cconfig \ test \ util \ diff --git a/src/test/d3d9hook/Module.mk b/src/test/d3d9hook/Module.mk index 585139c5..3ab336b0 100644 --- a/src/test/d3d9hook/Module.mk +++ b/src/test/d3d9hook/Module.mk @@ -5,6 +5,7 @@ srcdir_d3d9hook := src/test/d3d9hook ldflags_d3d9hook := \ libs_d3d9hook := \ + core \ hook \ test \ util \ @@ -25,6 +26,7 @@ libs_d3d9hook-test := \ hook \ test \ util \ + core \ src_d3d9hook-test := \ main.c \ diff --git a/src/test/d3d9hook/dllmain.c b/src/test/d3d9hook/dllmain.c index 88060bb5..e7052df3 100644 --- a/src/test/d3d9hook/dllmain.c +++ b/src/test/d3d9hook/dllmain.c @@ -2,12 +2,14 @@ #include #include +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" + #include "hook/d3d9.h" #include "test/check.h" -#include "util/log.h" - #define debug_print(...) fprintf(stderr, __VA_ARGS__) static HRESULT my_d3d9_handler(struct hook_d3d9_irp *irp); @@ -298,7 +300,9 @@ static HRESULT my_d3d9_handler(struct hook_d3d9_irp *irp) BOOL WINAPI DllMain(HMODULE mod, DWORD reason, void *ctx) { if (reason == DLL_PROCESS_ATTACH) { - log_to_writer(log_writer_stderr, NULL); + core_log_bt_ext_init_with_debug(); + core_log_bt_ext_init_with_stderr(); + debug_print("Initializing d3d9 hook module...\n"); hook_d3d9_init(d3d9_handlers, lengthof(d3d9_handlers)); diff --git a/src/test/iidxhook-util/Module.mk b/src/test/iidxhook-util/Module.mk index be4883eb..49fdda92 100644 --- a/src/test/iidxhook-util/Module.mk +++ b/src/test/iidxhook-util/Module.mk @@ -6,6 +6,7 @@ ldflags_iidxhook-util-config-eamuse-test := \ -lws2_32 \ libs_iidxhook-util-config-eamuse-test := \ + core \ security \ iidxhook-util \ cconfig \ @@ -22,6 +23,7 @@ testexes += iidxhook-util-config-gfx-test srcdir_iidxhook-util-config-gfx-test := src/test/iidxhook-util libs_iidxhook-util-config-gfx-test := \ + core \ security \ iidxhook-util \ cconfig \ @@ -38,6 +40,7 @@ testexes += iidxhook-util-config-misc-test srcdir_iidxhook-util-config-misc-test := src/test/iidxhook-util libs_iidxhook-util-config-misc-test := \ + core \ security \ iidxhook-util \ cconfig \ @@ -54,6 +57,7 @@ testexes += iidxhook-util-config-sec-test srcdir_iidxhook-util-config-sec-test := src/test/iidxhook-util libs_iidxhook-util-config-sec-test := \ + core \ security \ iidxhook-util \ cconfig \ diff --git a/src/test/iidxhook/Module.mk b/src/test/iidxhook/Module.mk index 8c086423..9172fa56 100644 --- a/src/test/iidxhook/Module.mk +++ b/src/test/iidxhook/Module.mk @@ -3,6 +3,7 @@ testexes += iidxhook-config-iidxhook1-test srcdir_iidxhook-config-iidxhook1-test := src/test/iidxhook libs_iidxhook-config-iidxhook1-test := \ + core \ cconfig \ iidxhook1 \ test \ @@ -18,6 +19,7 @@ testexes += iidxhook-config-iidxhook2-test srcdir_iidxhook-config-iidxhook2-test := src/test/iidxhook libs_iidxhook-config-iidxhook2-test := \ + core \ iidxhook2 \ cconfig \ test \ diff --git a/src/test/iidxhook8/Module.mk b/src/test/iidxhook8/Module.mk index 57731d54..3cccd421 100644 --- a/src/test/iidxhook8/Module.mk +++ b/src/test/iidxhook8/Module.mk @@ -3,6 +3,7 @@ testexes += iidxhook8-config-cam-test srcdir_iidxhook8-config-cam-test := src/test/iidxhook8 libs_iidxhook8-config-cam-test := \ + core \ camhook \ cconfig \ test \ @@ -18,6 +19,7 @@ testexes += iidxhook8-config-io-test srcdir_iidxhook8-config-io-test := src/test/iidxhook8 libs_iidxhook8-config-io-test := \ + core \ cconfig \ test \ util \ diff --git a/src/test/security/Module.mk b/src/test/security/Module.mk index 3bdef674..7ac51d71 100644 --- a/src/test/security/Module.mk +++ b/src/test/security/Module.mk @@ -3,6 +3,7 @@ testexes += security-id-test srcdir_security-id-test := src/test/security libs_security-id-test := \ + core \ security \ test \ util \ @@ -17,6 +18,7 @@ testexes += security-mcode-test srcdir_security-mcode-test := src/test/security libs_security-mcode-test := \ + core \ security \ test \ util \ @@ -31,6 +33,7 @@ testexes += security-util-test srcdir_security-util-test := src/test/security libs_security-util-test := \ + core \ security \ test \ util \ @@ -45,6 +48,7 @@ testexes += security-rp-test srcdir_security-rp-test := src/test/security libs_security-rp-test := \ + core \ security \ test \ util \ @@ -59,6 +63,7 @@ testexes += security-rp2-test srcdir_security-rp2-test := src/test/security libs_security-rp2-test := \ + core \ security \ test \ util \ @@ -73,6 +78,7 @@ testexes += security-rp3-test srcdir_security-rp3-test := src/test/security libs_security-rp3-test := \ + core \ security \ test \ util \ diff --git a/src/test/test/test.h b/src/test/test/test.h index 91fb01bb..7d0dda04 100644 --- a/src/test/test/test.h +++ b/src/test/test/test.h @@ -3,12 +3,15 @@ #include -#include "util/log.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log.h" -#define TEST_MODULE_BEGIN(name) \ - int main(int argc, char **argv) \ - { \ - log_to_writer(log_writer_stderr, NULL); \ +#define TEST_MODULE_BEGIN(name) \ + int main(int argc, char **argv) \ + { \ + core_log_bt_ext_impl_set(); \ + core_log_bt_ext_init_with_stderr(); \ fprintf(stderr, "Executing test module '%s'...\n", #name); #define TEST_MODULE_TEST(func) \ @@ -19,6 +22,8 @@ #define TEST_MODULE_END() \ fprintf(stderr, "Finished execution of test module\n"); \ + core_log_bt_fini(); \ + \ return 0; \ } diff --git a/src/test/util/Module.mk b/src/test/util/Module.mk index ca13ecc6..90f34245 100644 --- a/src/test/util/Module.mk +++ b/src/test/util/Module.mk @@ -7,6 +7,7 @@ ldflags_util-net-test := \ -liphlpapi \ libs_util-net-test := \ + core \ test \ util \ From 1b38c3d34639fab02e92be775e712c162004e6de Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 05/90] chore: Delete old log and thread modules in util The log API stopped scaling already a while ago and needs considerable refactoring to consider the various use-cases that emerged since it was first created on alpha versions of bemanitools. --- src/main/util/Module.mk | 2 - src/main/util/log.c | 134 ---------------------------------------- src/main/util/log.h | 90 --------------------------- src/main/util/thread.c | 92 --------------------------- src/main/util/thread.h | 20 ------ 5 files changed, 338 deletions(-) delete mode 100644 src/main/util/log.c delete mode 100644 src/main/util/log.h delete mode 100644 src/main/util/thread.c delete mode 100644 src/main/util/thread.h diff --git a/src/main/util/Module.mk b/src/main/util/Module.mk index ab8d4956..e6dc7c41 100644 --- a/src/main/util/Module.mk +++ b/src/main/util/Module.mk @@ -9,7 +9,6 @@ src_util := \ hex.c \ iobuf.c \ list.c \ - log.c \ math.c \ mem.c \ msg-thread.c \ @@ -18,7 +17,6 @@ src_util := \ proc.c \ signal.c \ str.c \ - thread.c \ time.c \ winres.c \ diff --git a/src/main/util/log.c b/src/main/util/log.c deleted file mode 100644 index 7da1d63a..00000000 --- a/src/main/util/log.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "util/log.h" -#include "util/str.h" - -#include - -#include -#include -#include -#include - -static log_writer_t log_writer; -static void *log_writer_ctx; -static enum log_level log_level; - -static void log_builtin_fatal(const char *module, const char *fmt, ...); -static void log_builtin_info(const char *module, const char *fmt, ...); -static void log_builtin_misc(const char *module, const char *fmt, ...); -static void log_builtin_warning(const char *module, const char *fmt, ...); -static void log_builtin_format( - enum log_level msg_level, const char *module, const char *fmt, va_list ap); - -#define IMPLEMENT_SINK(name, msg_level) \ - static void name(const char *module, const char *fmt, ...) \ - { \ - va_list ap; \ - \ - va_start(ap, fmt); \ - log_builtin_format(msg_level, module, fmt, ap); \ - va_end(ap); \ - } - -IMPLEMENT_SINK(log_builtin_info, LOG_LEVEL_INFO) -IMPLEMENT_SINK(log_builtin_misc, LOG_LEVEL_MISC) -IMPLEMENT_SINK(log_builtin_warning, LOG_LEVEL_WARNING) - -static void log_builtin_fatal(const char *module, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - log_builtin_format(LOG_LEVEL_FATAL, module, fmt, ap); - va_end(ap); - - DebugBreak(); - ExitProcess(EXIT_FAILURE); -} - -static void log_builtin_format( - enum log_level msg_level, const char *module, const char *fmt, va_list ap) -{ - static const char chars[] = "FWIM"; - - /* 64k so we can log data dumps of rs232 without crashing */ - char line[65536]; - char msg[65536]; - int result; - - if (msg_level <= log_level) { - str_vformat(msg, sizeof(msg), fmt, ap); - result = str_format( - line, sizeof(line), "%c:%s: %s\n", chars[msg_level], module, msg); - - log_writer(log_writer_ctx, line, result); - } -} - -void log_assert_body(const char *file, int line, const char *function) -{ - log_impl_fatal("assert", "%s:%d: function `%s'", file, line, function); -} - -void log_to_external( - log_formatter_t misc, - log_formatter_t info, - log_formatter_t warning, - log_formatter_t fatal) -{ - log_impl_misc = misc; - log_impl_info = info; - log_impl_warning = warning; - log_impl_fatal = fatal; -} - -void log_to_writer(log_writer_t writer, void *ctx) -{ - log_impl_misc = log_builtin_misc; - log_impl_info = log_builtin_info; - log_impl_warning = log_builtin_warning; - log_impl_fatal = log_builtin_fatal; - - if (writer != NULL) { - log_writer = writer; - log_writer_ctx = ctx; - } else { - log_writer = log_writer_null; - } -} - -void log_set_level(enum log_level new_level) -{ - log_level = new_level; -} - -void log_writer_debug(void *ctx, const char *chars, size_t nchars) -{ - OutputDebugStringA(chars); -} - -void log_writer_stdout(void *ctx, const char *chars, size_t nchars) -{ - printf("%s", chars); -} - -void log_writer_stderr(void *ctx, const char *chars, size_t nchars) -{ - fprintf(stderr, "%s", chars); -} - -void log_writer_file(void *ctx, const char *chars, size_t nchars) -{ - fwrite(chars, 1, nchars, (FILE *) ctx); - fflush((FILE *) ctx); -} - -void log_writer_null(void *ctx, const char *chars, size_t nchars) -{ -} - -log_formatter_t log_impl_misc = log_builtin_misc; -log_formatter_t log_impl_info = log_builtin_info; -log_formatter_t log_impl_warning = log_builtin_warning; -log_formatter_t log_impl_fatal = log_builtin_fatal; -static log_writer_t log_writer = log_writer_null; -static enum log_level log_level = LOG_LEVEL_MISC; diff --git a/src/main/util/log.h b/src/main/util/log.h deleted file mode 100644 index 47603fa1..00000000 --- a/src/main/util/log.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef UTIL_LOG_H -#define UTIL_LOG_H - -#include -#include - -#include "bemanitools/glue.h" - -#include "util/defs.h" - -/* Dynamically retargetable logging system modeled on (and potentially - integrateable with) the one found in AVS2 */ - -/* BUILD_MODULE is passed in as a command-line #define by the makefile */ - -#ifndef LOG_MODULE -#define LOG_MODULE STRINGIFY(BUILD_MODULE) -#endif - -#ifndef LOG_SUPPRESS - -#define log_misc(...) log_impl_misc(LOG_MODULE, __VA_ARGS__) -#define log_info(...) log_impl_info(LOG_MODULE, __VA_ARGS__) -#define log_warning(...) log_impl_warning(LOG_MODULE, __VA_ARGS__) - -/* This doesn't really belong here, but it's what libavs does so w/e */ - -#define log_assert(x) \ - do { \ - if (!(x)) { \ - log_assert_body(__FILE__, __LINE__, __FUNCTION__); \ - } \ - } while (0) - -#else - -#define log_misc(...) -#define log_info(...) -#define log_warning(...) -#define log_assert(x) \ - do { \ - if (!(x)) { \ - abort(); \ - } \ - } while (0) - -#endif - -#define log_fatal(...) \ - do { \ - log_impl_fatal(LOG_MODULE, __VA_ARGS__); \ - abort(); \ - } while (0) - -typedef void (*log_writer_t)(void *ctx, const char *chars, size_t nchars); - -extern log_formatter_t log_impl_misc; -extern log_formatter_t log_impl_info; -extern log_formatter_t log_impl_warning; -extern log_formatter_t log_impl_fatal; - -enum log_level { - LOG_LEVEL_FATAL = 0, - LOG_LEVEL_WARNING = 1, - LOG_LEVEL_INFO = 2, - LOG_LEVEL_MISC = 3, -}; - -void log_assert_body(const char *file, int line, const char *function); -void log_to_external( - log_formatter_t misc, - log_formatter_t info, - log_formatter_t warning, - log_formatter_t fatal); -void log_to_writer(log_writer_t writer, void *ctx); - -void log_set_level(enum log_level new_level); - -/* I tried to make this API match the function signature of the AVS log writer - callback, but then the signature changed and the explicit line breaks - being passed to that callback went away. So we don't try to track that API - any more. Launcher defines its own custom writer anyway. */ - -void log_writer_debug(void *ctx, const char *chars, size_t nchars); -void log_writer_stdout(void *ctx, const char *chars, size_t nchars); -void log_writer_stderr(void *ctx, const char *chars, size_t nchars); -void log_writer_file(void *ctx, const char *chars, size_t nchars); -void log_writer_null(void *ctx, const char *chars, size_t nchars); - -#endif diff --git a/src/main/util/thread.c b/src/main/util/thread.c deleted file mode 100644 index daa82677..00000000 --- a/src/main/util/thread.c +++ /dev/null @@ -1,92 +0,0 @@ -#include -#include - -#include -#include - -#include "util/defs.h" -#include "util/thread.h" - -struct shim_ctx { - HANDLE barrier; - int (*proc)(void *); - void *ctx; -}; - -thread_create_t thread_impl_create = crt_thread_create; -thread_join_t thread_impl_join = crt_thread_join; -thread_destroy_t thread_impl_destroy = crt_thread_destroy; - -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 crt_thread_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 crt_thread_destroy(int thread_id) -{ - CloseHandle((HANDLE) (uintptr_t) thread_id); -} - -void crt_thread_join(int thread_id, int *result) -{ - WaitForSingleObject((HANDLE) (uintptr_t) thread_id, INFINITE); - - if (result) { - GetExitCodeThread((HANDLE) (uintptr_t) thread_id, (DWORD *) result); - } -} - -void thread_api_init( - thread_create_t create, thread_join_t join, thread_destroy_t destroy) -{ - if (create == NULL || join == NULL || destroy == NULL) { - abort(); - } - - thread_impl_create = create; - thread_impl_join = join; - thread_impl_destroy = destroy; -} - -int thread_create( - int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority) -{ - return thread_impl_create(proc, ctx, stack_sz, priority); -} - -void thread_join(int thread_id, int *result) -{ - thread_impl_join(thread_id, result); -} - -void thread_destroy(int thread_id) -{ - thread_impl_destroy(thread_id); -} diff --git a/src/main/util/thread.h b/src/main/util/thread.h deleted file mode 100644 index dca396b7..00000000 --- a/src/main/util/thread.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef UTIL_THREAD_H -#define UTIL_THREAD_H - -#include - -#include "bemanitools/glue.h" - -int crt_thread_create( - int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority); -void crt_thread_join(int thread_id, int *result); -void crt_thread_destroy(int thread_id); - -void thread_api_init( - thread_create_t create, thread_join_t join, thread_destroy_t destroy); -int thread_create( - int (*proc)(void *), void *ctx, uint32_t stack_sz, unsigned int priority); -void thread_join(int thread_id, int *result); -void thread_destroy(int thread_id); - -#endif From 947050d0c89dded8fbd89ca6fc277df1546f88bf Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 06/90] refactor(inject): Use new core thread and log modules Keep this a separate commit because this also removes inject's own logging engine and replaces it with the streamlined core API. The core API provides all the features of inject's own logging engine which also performed horribly. The entire logging operation was locked which included expensive operations that formatted the log messages and required memory allocations and copying around data. The core API's implementation at least only synchronizes the actual IO operations (though this can be improved further with an actual async logging sink, TBD) --- src/main/inject/Module.mk | 2 +- src/main/inject/debugger.c | 10 +- src/main/inject/logger.c | 217 ------------------------------------- src/main/inject/logger.h | 28 ----- src/main/inject/main.c | 72 ++++++++++-- 5 files changed, 72 insertions(+), 257 deletions(-) delete mode 100644 src/main/inject/logger.c delete mode 100644 src/main/inject/logger.h diff --git a/src/main/inject/Module.mk b/src/main/inject/Module.mk index 549f1792..fd868ef4 100644 --- a/src/main/inject/Module.mk +++ b/src/main/inject/Module.mk @@ -5,12 +5,12 @@ ldflags_inject := \ -lpsapi \ libs_inject := \ + core \ util \ src_inject := \ main.c \ debugger.c \ - logger.c \ options.c \ version.c \ diff --git a/src/main/inject/debugger.c b/src/main/inject/debugger.c index 724ed9ba..e8e7068c 100644 --- a/src/main/inject/debugger.c +++ b/src/main/inject/debugger.c @@ -8,10 +8,11 @@ #include #include +#include "core/log-bt.h" +#include "core/log.h" + #include "inject/debugger.h" -#include "inject/logger.h" -#include "util/log.h" #include "util/mem.h" #include "util/proc.h" #include "util/signal.h" @@ -178,6 +179,7 @@ static bool log_debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi) log_assert(odsi); char *debug_str; + size_t debug_str_len; if (odsi->fUnicode) { debug_str = read_debug_wstr(process, odsi); @@ -186,7 +188,9 @@ static bool log_debug_str(HANDLE process, const OUTPUT_DEBUG_STRING_INFO *odsi) } if (debug_str) { - logger_log(debug_str); + debug_str_len = strlen(debug_str); + + core_log_bt_direct_sink_write(debug_str, debug_str_len); free(debug_str); return true; diff --git a/src/main/inject/logger.c b/src/main/inject/logger.c deleted file mode 100644 index 8f63aa9c..00000000 --- a/src/main/inject/logger.c +++ /dev/null @@ -1,217 +0,0 @@ -#define LOG_MODULE "inject-logger" - -#include -#include -#include -#include -#include -#include - -#include "inject/logger.h" -#include "inject/version.h" - -#include "util/log.h" - -static FILE *log_file; -static HANDLE log_mutex; - -static const char *logger_get_formatted_timestamp(void) -{ - static char buffer[64]; - time_t cur = 0; - struct tm *tm = NULL; - - cur = time(NULL); - tm = localtime(&cur); - - strftime(buffer, sizeof(buffer), "[%Y/%m/%d %H:%M:%S] ", tm); - - return buffer; -} - -static char logger_console_determine_color(const char *str) -{ - log_assert(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 logger_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 logger_console( - void *ctx, const char *chars, size_t nchars, const char *timestamp_str) -{ - char color; - size_t color_len; - // See "util/log.c", has to align - char buffer[65536]; - char tmp; - - color_len = logger_msg_coloring_len(chars); - - // 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 = logger_console_determine_color(chars); - - strcpy(buffer, chars); - - // Mask start of log message for coloring - tmp = buffer[color_len]; - buffer[color_len] = '\0'; - - printf("%s", timestamp_str); - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color); - printf("%s", buffer); - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15); - - // Write actual message non colored - buffer[color_len] = tmp; - printf("%s", buffer + color_len); - } else { - printf("%s", chars); - } -} - -static void logger_file( - void *ctx, const char *chars, size_t nchars, const char *timestamp_str) -{ - if (ctx) { - fwrite(timestamp_str, 1, strlen(timestamp_str), (FILE *) ctx); - fwrite(chars, 1, nchars, (FILE *) ctx); - fflush((FILE *) ctx); - } -} - -static void logger_writer(void *ctx, const char *chars, size_t nchars) -{ - const char *timestamp_str; - - // Different threads logging the same destination, e.g. debugger thread, - // main thread - - WaitForSingleObject(log_mutex, INFINITE); - - timestamp_str = logger_get_formatted_timestamp(); - - logger_console(ctx, chars, nchars, timestamp_str); - logger_file(ctx, chars, nchars, timestamp_str); - - ReleaseMutex(log_mutex); -} - -static void logger_log_header() -{ - log_info( - "\n" - " _ _ _ \n" - " (_)_ __ (_) ___ ___| |_ \n" - " | | '_ \\ | |/ _ \\/ __| __|\n" - " | | | | || | __/ (__| |_ \n" - " |_|_| |_|/ |\\___|\\___|\\__|\n" - " |__/ "); - - log_info( - "Inject build date %s, gitrev %s", inject_build_date, inject_gitrev); -} - -bool logger_init(const char *log_file_path) -{ - if (log_file_path) { - log_file = fopen(log_file_path, "w+"); - } else { - log_file = NULL; - } - - log_to_writer(logger_writer, log_file); - - logger_log_header(); - - if (log_file_path) { - log_info("Log file: %s", log_file_path); - - if (!log_file) { - log_warning( - "ERROR: Opening log file %s failed: %s", - log_file_path, - strerror(errno)); - return false; - } - } - - log_mutex = CreateMutex(NULL, FALSE, NULL); - - return true; -} - -void logger_log(const char *str) -{ - logger_writer(log_file, str, strlen(str)); -} - -void logger_finit() -{ - log_misc("Logger finit"); - - if (log_file) { - fclose(log_file); - } - - CloseHandle(log_mutex); -} \ No newline at end of file diff --git a/src/main/inject/logger.h b/src/main/inject/logger.h deleted file mode 100644 index 45a8b770..00000000 --- a/src/main/inject/logger.h +++ /dev/null @@ -1,28 +0,0 @@ -#include - -/** - * Initialize inject's logger backend. - * - * This takes care of hooking and merging the different log - * streams, e.g. inject's local logging and inject's debugger - * receiving remote logging events. - * - * @param log_file_path Path to the file to log to or NULL to - * disable. - */ -bool logger_init(const char *log_file_path); - -/** - * Write a message to the logging backend. - * - * This is used by inject's debugger to redirect log messages - * recevied from the remote process. - * - * @param str String to log - */ -void logger_log(const char *str); - -/** - * Shutdown and cleanup the logging backend. - */ -void logger_finit(); \ No newline at end of file diff --git a/src/main/inject/main.c b/src/main/inject/main.c index bfb6c1be..07026dcb 100644 --- a/src/main/inject/main.c +++ b/src/main/inject/main.c @@ -1,3 +1,5 @@ +#define LOG_MODULE "inject" + #include #include @@ -10,18 +12,67 @@ #include "cconfig/cconfig-util.h" #include "cconfig/cmd.h" +#include "core/log-bt-ext.h" +#include "core/log-bt.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" +#include "core/thread-crt-ext.h" +#include "core/thread-crt.h" +#include "core/thread.h" + #include "inject/debugger.h" -#include "inject/logger.h" #include "inject/options.h" #include "inject/version.h" #include "util/cmdline.h" -#include "util/log.h" #include "util/mem.h" #include "util/os.h" #include "util/signal.h" #include "util/str.h" +static void _inject_log_header() +{ + log_info( + "\n" + " _ _ _ \n" + " (_)_ __ (_) ___ ___| |_ \n" + " | | '_ \\ | |/ _ \\/ __| __|\n" + " | | | | || | __/ (__| |_ \n" + " |_|_| |_|/ |\\___|\\___|\\__|\n" + " |__/ "); + + log_info( + "inject build date %s, gitrev %s", inject_build_date, inject_gitrev); +} + +void _inject_log_init( + const char *log_file_path, enum core_log_bt_log_level level) +{ + struct core_log_sink sinks[2]; + struct core_log_sink sink_composed; + struct core_log_sink sink_mutex; + + core_log_bt_ext_impl_set(); + + if (log_file_path) { + core_log_sink_std_out_open(true, &sinks[0]); + core_log_sink_file_open(log_file_path, false, true, 10, &sinks[1]); + core_log_sink_list_open(sinks, 2, &sink_composed); + } else { + core_log_sink_std_out_open(true, &sink_composed); + } + + // Different threads logging the same destination, e.g. debugger thread, + // main thread + core_log_sink_mutex_open(&sink_composed, &sink_mutex); + + core_log_bt_init(&sink_mutex); + core_log_bt_level_set(level); +} + static bool init_options(int argc, char **argv, struct options *options) { options_init(options); @@ -145,7 +196,7 @@ static bool inject_hook_dlls(uint32_t hooks, char **argv) static void signal_shutdown_handler() { debugger_finit(true); - logger_finit(); + core_log_bt_fini(); } int main(int argc, char **argv) @@ -160,9 +211,14 @@ int main(int argc, char **argv) goto init_options_fail; } - if (!logger_init(strlen(options.log_file) > 0 ? options.log_file : NULL)) { - goto init_logger_fail; - } + core_thread_crt_ext_impl_set(); + // TODO expose log level + + _inject_log_init( + strlen(options.log_file) > 0 ? options.log_file : NULL, + CORE_LOG_BT_LOG_LEVEL_MISC); + + _inject_log_header(); os_version_log(); @@ -214,7 +270,7 @@ int main(int argc, char **argv) debugger_finit(false); - logger_finit(); + core_log_bt_fini(); return EXIT_SUCCESS; @@ -226,7 +282,7 @@ int main(int argc, char **argv) debugger_init_fail: verify_2_fail: verify_fail: - logger_finit(); + core_log_bt_fini(); init_logger_fail: init_options_fail: From 35ca49fe7e896090fbfbe7ce4534158a6b52b47f Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 07/90] refactor(launcher): Major re-work of launcher Kudos to Shiz for providing the groundwork for this. Fundamentally re-think how launcher operates and bootstrapping the games is managed and configured. This brings it significantly closer to how the original bootstrap is doing the job: launcher now utilizes the data (structures) provided by the bootstrap.xml configuration file. This creates compatibility with vanilla data dumps and original stock images. Note that bemanitools does not include any code or means to run DRM'd data, only decrypted. But, this allows users to keep decrypted dumps as stock as possible which means: * No copying around of property files anymore * Keep the modules/ folder with the binaries * Have bemanitools binaries separate in the data * No need to edit/customize the original configuration files A list of key features of the "new" launcher: * Boostrap games by following the configuration provided by stock game's bootstrap.xml files * Custom launcher.xml configuration file that adds further launcher configurable features, composability of bootstrap.xml configuration(s) as well as configuration overriding/stacking of selected types of configurations, e.g. eamuse config, avs-config. The latter eliminates the need for modifying stock config files in the prop/ folder * Unified logging system: launcher and AVS logging uses the same logger, all output can now be in a single file * Original features such as various hook types still available Due to the significant architectural changes, this also breaks with any backwards compatibility to existing launcher setups. Thus, users need to migrate by re-applying the new configuration format and migrating their config parameters accordingly. Further migration instructions and updated documentation will be provided upon release. Co-authored-by: Shiz --- src/main/launcher/Module.mk | 18 +- src/main/launcher/avs-config.c | 773 +++++++++++++++++++++++ src/main/launcher/avs-config.h | 57 ++ src/main/launcher/avs-context.c | 72 --- src/main/launcher/avs-context.h | 20 - src/main/launcher/avs.c | 270 ++++++++ src/main/launcher/avs.h | 16 + src/main/launcher/bootstrap-config.c | 548 ++++++++++++++++ src/main/launcher/bootstrap-config.h | 139 +++++ src/main/launcher/bootstrap.c | 347 +++++++++++ src/main/launcher/bootstrap.h | 32 + src/main/launcher/debug.c | 33 + src/main/launcher/debug.h | 6 + src/main/launcher/ea3-config.c | 190 ------ src/main/launcher/ea3-config.h | 46 -- src/main/launcher/ea3-ident-config.c | 104 ++++ src/main/launcher/ea3-ident-config.h | 37 ++ src/main/launcher/eamuse-config.c | 125 ++++ src/main/launcher/eamuse-config.h | 23 + src/main/launcher/eamuse.c | 25 + src/main/launcher/eamuse.h | 9 + src/main/launcher/hook.c | 34 + src/main/launcher/hook.h | 6 + src/main/launcher/launcher-config.c | 371 +++++++++++ src/main/launcher/launcher-config.h | 56 ++ src/main/launcher/launcher.c | 587 ++++++++++++++++++ src/main/launcher/launcher.h | 8 + src/main/launcher/main.c | 323 +--------- src/main/launcher/module.c | 159 ++++- src/main/launcher/module.h | 18 +- src/main/launcher/options.c | 202 +++--- src/main/launcher/options.h | 60 +- src/main/launcher/property-util.c | 891 +++++++++++++++++++++++++++ src/main/launcher/property-util.h | 99 +++ src/main/launcher/property.c | 135 ---- src/main/launcher/property.h | 10 - src/main/launcher/stubs.c | 7 +- 37 files changed, 4915 insertions(+), 941 deletions(-) create mode 100644 src/main/launcher/avs-config.c create mode 100644 src/main/launcher/avs-config.h delete mode 100644 src/main/launcher/avs-context.c delete mode 100644 src/main/launcher/avs-context.h create mode 100644 src/main/launcher/avs.c create mode 100644 src/main/launcher/avs.h create mode 100644 src/main/launcher/bootstrap-config.c create mode 100644 src/main/launcher/bootstrap-config.h create mode 100644 src/main/launcher/bootstrap.c create mode 100644 src/main/launcher/bootstrap.h create mode 100644 src/main/launcher/debug.c create mode 100644 src/main/launcher/debug.h delete mode 100644 src/main/launcher/ea3-config.c delete mode 100644 src/main/launcher/ea3-config.h create mode 100644 src/main/launcher/ea3-ident-config.c create mode 100644 src/main/launcher/ea3-ident-config.h create mode 100644 src/main/launcher/eamuse-config.c create mode 100644 src/main/launcher/eamuse-config.h create mode 100644 src/main/launcher/eamuse.c create mode 100644 src/main/launcher/eamuse.h create mode 100644 src/main/launcher/hook.c create mode 100644 src/main/launcher/hook.h create mode 100644 src/main/launcher/launcher-config.c create mode 100644 src/main/launcher/launcher-config.h create mode 100644 src/main/launcher/launcher.c create mode 100644 src/main/launcher/launcher.h create mode 100644 src/main/launcher/property-util.c create mode 100644 src/main/launcher/property-util.h delete mode 100644 src/main/launcher/property.c delete mode 100644 src/main/launcher/property.h diff --git a/src/main/launcher/Module.mk b/src/main/launcher/Module.mk index c9dfd078..39daace5 100644 --- a/src/main/launcher/Module.mk +++ b/src/main/launcher/Module.mk @@ -3,22 +3,34 @@ rc_launcher := launcher.rc ldflags_launcher := \ -mconsole \ + -ldbghelp \ deplibs_launcher := \ avs \ avs-ea3 \ libs_launcher := \ + avs-util \ + core \ hook \ util \ src_launcher := \ - avs-context.c \ - ea3-config.c \ + avs-config.c \ + avs.c \ + bootstrap-config.c \ + bootstrap.c \ + debug.c \ + ea3-ident-config.c \ + eamuse-config.c \ + eamuse.c \ + hook.c \ + launcher-config.c \ + launcher.c \ main.c \ module.c \ options.c \ - property.c \ + property-util.c \ stubs.c \ version.c \ diff --git a/src/main/launcher/avs-config.c b/src/main/launcher/avs-config.c new file mode 100644 index 00000000..8669f3d4 --- /dev/null +++ b/src/main/launcher/avs-config.c @@ -0,0 +1,773 @@ +#define LOG_MODULE "avs-config" + +#include + +#include "avs-util/error.h" + +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/avs-config.h" +#include "launcher/property-util.h" + +#include "util/str.h" + +#define AVS_CONFIG_ROOT_NODE "/config" + +static const char *_avs_config_property_mounttable_path = + "/config/fs/mounttable"; + +static void _avs_config_node_vfs_copy( + struct property *parent_property, + struct property_node *parent, + struct property_node *source) +{ + // Use max path size to fit dst and src fs paths + char data[MAX_PATH]; + + // Remark: Using property_node_clone doesn't work here + // Cloning non-deep only clones the vfs node. Cloning deep doesn't seem + // to work with arbitrary attributes that don't follow the general + // design of a property structure. This seems to require clear typing for + // nodes in order to allow property_node_clone to work + + // Ignore errors and default to empty + memset(data, 0, sizeof(data)); + property_node_refer( + NULL, source, "name@", PROPERTY_TYPE_ATTR, data, sizeof(data)); + property_util_node_attribute_replace( + parent_property, parent, "name@", data); + + memset(data, 0, sizeof(data)); + property_node_refer( + NULL, source, "fstype@", PROPERTY_TYPE_ATTR, data, sizeof(data)); + property_util_node_attribute_replace( + parent_property, parent, "fstype@", data); + + memset(data, 0, sizeof(data)); + property_node_refer( + NULL, source, "src@", PROPERTY_TYPE_ATTR, data, sizeof(data)); + property_util_node_attribute_replace(parent_property, parent, "src@", data); + + memset(data, 0, sizeof(data)); + property_node_refer( + NULL, source, "dst@", PROPERTY_TYPE_ATTR, data, sizeof(data)); + property_util_node_attribute_replace(parent_property, parent, "dst@", data); + + memset(data, 0, sizeof(data)); + property_node_refer( + NULL, source, "opt@", PROPERTY_TYPE_ATTR, data, sizeof(data)); + property_util_node_attribute_replace(parent_property, parent, "opt@", data); +} + +static bool _avs_config_mounttable_vfs_nodes_merge_strategy_do( + struct property *parent_property, + struct property_node *parent, + struct property_node *source, + void *ctx, + property_util_node_merge_recursion_do_t node_merge_recursion_do) +{ + struct property_node *parent_child; + struct property_node *source_child; + + char parent_child_name[PROPERTY_NODE_NAME_SIZE_MAX]; + char name_parent[PROPERTY_NODE_ATTR_NAME_SIZE_MAX]; + char dst_parent[PROPERTY_NODE_ATTR_NAME_SIZE_MAX]; + + char source_child_name[PROPERTY_NODE_NAME_SIZE_MAX]; + char name_source[PROPERTY_NODE_ATTR_NAME_SIZE_MAX]; + char dst_source[PROPERTY_NODE_ATTR_NAME_SIZE_MAX]; + + bool node_consumed; + bool found_parent; + + source_child = property_node_traversal(source, TRAVERSE_FIRST_CHILD); + + node_consumed = false; + + while (source_child) { + property_node_name( + source_child, source_child_name, sizeof(source_child_name)); + + if (str_eq(source_child_name, "vfs")) { + node_consumed = true; + + parent_child = + property_node_traversal(parent, TRAVERSE_FIRST_CHILD); + + found_parent = false; + + while (parent_child) { + property_node_name( + parent_child, parent_child_name, sizeof(parent_child_name)); + + if (str_eq(parent_child_name, "vfs")) { + if (AVS_IS_ERROR(property_node_refer( + NULL, + source_child, + "name@", + PROPERTY_TYPE_ATTR, + name_source, + sizeof(name_source)))) { + log_fatal( + "Missing 'name' attribute on avs config mounttable " + "vfs source node"); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + source_child, + "dst@", + PROPERTY_TYPE_ATTR, + dst_source, + sizeof(dst_source)))) { + log_fatal( + "Missing 'dst' attribute on avs config mounttable " + "vfs source node"); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + parent_child, + "name@", + PROPERTY_TYPE_ATTR, + name_parent, + sizeof(name_parent)))) { + log_fatal( + "Missing 'name' attribute on avs config mounttable " + "vfs parent node"); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + parent_child, + "dst@", + PROPERTY_TYPE_ATTR, + dst_parent, + sizeof(dst_parent)))) { + log_fatal( + "Missing 'dst' attribute on avs config mounttable " + "vfs parent node"); + } + + // Found existing matching node on parent, replace it + if (str_eq(name_source, name_parent) && + str_eq(dst_source, dst_parent)) { + _avs_config_node_vfs_copy( + parent_property, parent_child, source_child); + + found_parent = true; + break; + } + } + + parent_child = property_node_traversal( + parent_child, TRAVERSE_NEXT_SIBLING); + } + + // Not found an existing node that got replaced, insert/merge new + // data + if (!found_parent) { + parent_child = property_node_create( + parent_property, parent, PROPERTY_TYPE_VOID, "vfs"); + + _avs_config_node_vfs_copy( + parent_property, parent_child, source_child); + } + } + + source_child = + property_node_traversal(source_child, TRAVERSE_NEXT_SIBLING); + } + + return node_consumed; +} + +struct property *avs_config_load(const char *filepath) +{ + struct property *property; + + log_assert(filepath); + + log_info("Loading from file path: %s", filepath); + + property = property_util_load(filepath); + + // Check if root node exists, call already errors if not + avs_config_root_get(property); + + return property; +} + +struct property_node *avs_config_root_get(struct property *property) +{ + struct property_node *node; + + log_assert(property); + + node = property_search(property, 0, AVS_CONFIG_ROOT_NODE); + + if (node == NULL) { + log_fatal("Root node " AVS_CONFIG_ROOT_NODE " in AVS config missing"); + } + + return node; +} + +struct property * +avs_config_property_merge(struct property *parent, struct property *source) +{ + struct property_util_node_merge_strategies strategies; + + log_assert(parent); + log_assert(source); + + strategies.num = 2; + + strategies.entry[0].path = _avs_config_property_mounttable_path; + strategies.entry[0].merge_strategy_do = + _avs_config_mounttable_vfs_nodes_merge_strategy_do; + + strategies.entry[1].path = ""; + strategies.entry[1].merge_strategy_do = + property_util_node_merge_default_strategy_do; + + return property_util_merge_with_strategies(parent, source, &strategies); +} + +void avs_config_fs_root_device_get( + struct property_node *node, char *buffer, size_t size) +{ + struct property_node *device_node; + avs_error error; + + log_assert(node); + + device_node = property_search(NULL, node, "fs/root/device"); + + if (device_node == NULL) { + log_fatal("Could not find node fs/root/device AVS config"); + } + + error = property_node_read(device_node, PROPERTY_TYPE_STR, buffer, size); + + if (AVS_IS_ERROR(error)) { + log_fatal( + "fs/root/device, property read failed: %s", + avs_util_error_str(error)); + } +} + +void avs_config_mode_product_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace(NULL, node, "mode/product", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "mode/product", enable); +#endif +} + +void avs_config_net_raw_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace(NULL, node, "net/enable_raw", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "net/enable_raw", enable); +#endif +} + +void avs_config_net_eaudp_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace( + NULL, node, "net/eaudp/enable", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "net/eaudp/enable", enable); +#endif +} + +void avs_config_sntp_ea_set(struct property_node *node, bool on) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace(NULL, node, "sntp/ea_on", on ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "sntp/ea_on", on); +#endif +} + +void avs_config_log_level_set(struct property_node *node, const char *level) +{ + log_assert(node); + log_assert(level); + +#if AVS_VERSION <= 1306 + uint32_t level_value; + + if (str_eq(level, "fatal")) { + level_value = 1; + } else if (str_eq(level, "warning")) { + level_value = 2; + } else if (str_eq(level, "info")) { + level_value = 3; + } else if (str_eq(level, "misc")) { + level_value = 4; + } else if (str_eq(level, "all")) { + level_value = 4; + } else if (str_eq(level, "disable")) { + level_value = 0; + } else if (str_eq(level, "default")) { + level_value = 4; + } else { + log_fatal("Unknown log level string %s", level); + } + + property_util_node_u32_replace(NULL, node, "log/level", level_value); +#else + property_util_node_str_replace(NULL, node, "log/level", level); +#endif +} + +void avs_config_log_name_set(struct property_node *node, const char *name) +{ + log_assert(node); + log_assert(name); + + property_util_node_str_replace(NULL, node, "log/name", name); +} + +void avs_config_log_file_set(struct property_node *node, const char *file) +{ + log_assert(node); + log_assert(file); + + property_util_node_str_replace(NULL, node, "log/file", file); +} + +void avs_config_log_buffer_size_set(struct property_node *node, uint32_t size) +{ + log_assert(node); + + property_util_node_u32_replace(NULL, node, "log/sz_buf", size); +} + +void avs_config_log_output_delay_set( + struct property_node *node, uint16_t delay_ms) +{ + log_assert(node); + + property_util_node_u16_replace(NULL, node, "log/output_delay", delay_ms); +} + +void avs_config_log_enable_console_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace( + NULL, node, "log/enable_console", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "log/enable_console", enable); +#endif +} + +void avs_config_log_enable_sci_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace( + NULL, node, "log/enable_netsci", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "log/enable_netsci", enable); +#endif +} + +void avs_config_log_enable_net_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace( + NULL, node, "log/enable_netlog", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "log/enable_netlog", enable); +#endif +} + +void avs_config_log_enable_file_set(struct property_node *node, bool enable) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace( + NULL, node, "log/enable_file", enable ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "log/enable_file", enable); +#endif +} + +void avs_config_log_rotate_set(struct property_node *node, bool rotate) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace(NULL, node, "log/rotate", rotate ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "log/rotate", rotate); +#endif +} + +void avs_config_log_append_set(struct property_node *node, bool append) +{ + log_assert(node); + +#if AVS_VERSION <= 1306 + property_util_node_u8_replace(NULL, node, "log/append", append ? 1 : 0); +#else + property_util_node_bool_replace(NULL, node, "log/append", append); +#endif +} + +void avs_config_log_count_set(struct property_node *node, uint16_t count) +{ + log_assert(node); + + property_util_node_u16_replace(NULL, node, "log/gen", count); +} + +void avs_config_set_log_level( + struct property_node *node, enum core_log_bt_log_level loglevel) +{ + const char *str; + + log_assert(node); + + switch (loglevel) { + case CORE_LOG_BT_LOG_LEVEL_OFF: + str = "disable"; + break; + + case CORE_LOG_BT_LOG_LEVEL_FATAL: + str = "fatal"; + break; + + case CORE_LOG_BT_LOG_LEVEL_WARNING: + str = "warn"; + break; + + case CORE_LOG_BT_LOG_LEVEL_INFO: + str = "info"; + break; + + case CORE_LOG_BT_LOG_LEVEL_MISC: + str = "misc"; + break; + + default: + log_fatal("Unsupported log level: %d", loglevel); + break; + } + + avs_config_log_level_set(node, str); +} + +void avs_config_local_fs_path_dev_nvram_and_raw_set( + struct property_node *node, const char *dev_nvram_raw_path) +{ + char path_dev_raw[MAX_PATH]; + char path_dev_nvram[MAX_PATH]; + + struct property_node *fs_node; + struct property_node *mounttable_node; + struct property_node *vfs_node; + + log_assert(node); + log_assert(dev_nvram_raw_path); + + str_cpy(path_dev_raw, sizeof(path_dev_raw), dev_nvram_raw_path); + str_cat(path_dev_raw, sizeof(path_dev_raw), "/dev/raw"); + + str_cpy(path_dev_nvram, sizeof(path_dev_nvram), dev_nvram_raw_path); + str_cat(path_dev_nvram, sizeof(path_dev_nvram), "/dev/nvram"); + + fs_node = property_search(NULL, node, "fs"); + + if (!fs_node) { + log_fatal("Cannot find 'fs' node in avs config"); + } + + // Check if "new" mounttable config is used for dev/nvram and dev/raw or + // legacy config + if (property_search(NULL, fs_node, "mounttable")) { + property_remove(NULL, fs_node, "mounttable"); + + mounttable_node = property_node_create( + NULL, fs_node, PROPERTY_TYPE_VOID, "mounttable"); + + vfs_node = property_node_create( + NULL, mounttable_node, PROPERTY_TYPE_VOID, "vfs"); + + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "name", "boot"); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "fstype", "fs"); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "src", path_dev_raw); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "dest", "/dev/raw"); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "opt", "vf=1,posix=1"); + + vfs_node = property_node_create( + NULL, mounttable_node, PROPERTY_TYPE_VOID, "vfs"); + + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "name", "boot"); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "fstype", "fs"); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "src", path_dev_nvram); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "dest", "/dev/nvram"); + property_node_create( + NULL, vfs_node, PROPERTY_TYPE_ATTR, "opt", "vf=1,posix=1"); + } else { + property_util_node_str_replace( + NULL, fs_node, "nvram/device", path_dev_raw); + property_util_node_str_replace(NULL, fs_node, "nvram/fstype", "fs"); + property_util_node_str_replace( + NULL, fs_node, "nvram/option", "vf=1,posix=1"); + + property_util_node_str_replace( + NULL, fs_node, "raw/device", path_dev_nvram); + property_util_node_str_replace(NULL, fs_node, "raw/fstype", "fs"); + property_util_node_str_replace( + NULL, fs_node, "raw/option", "vf=1,posix=1"); + } +} + +void avs_config_vfs_mounttable_get( + struct property_node *node, struct avs_config_vfs_mounttable *mounttable) +{ + struct property_node *fs_node; + struct property_node *mounttable_node; + struct property_node *cur; + char mounttable_selector[128]; + char name[128]; + uint8_t pos; + + log_assert(node); + log_assert(mounttable); + + fs_node = property_search(NULL, node, "fs"); + + if (!fs_node) { + log_fatal("Cannot find 'fs' node in avs config"); + } + + // Check if new mounttable config is used for dev/nvram and dev/raw or + // legacy config + mounttable_node = property_search(NULL, fs_node, "mounttable"); + + memset(mounttable, 0, sizeof(*mounttable)); + pos = 0; + + if (mounttable_node) { + cur = property_search(NULL, fs_node, "mounttable_selector"); + + if (!cur) { + log_fatal("Missing 'mounttable_selector' on mounttable"); + } + + if (AVS_IS_ERROR(property_node_read( + cur, + PROPERTY_TYPE_STR, + mounttable_selector, + sizeof(mounttable_selector)))) { + log_fatal("Reading 'mounttable_selector' failed"); + } + + log_misc("Mounttable selector: %s", mounttable_selector); + + cur = property_node_traversal(mounttable_node, TRAVERSE_FIRST_CHILD); + + while (cur) { + property_node_name(cur, name, sizeof(name)); + + if (str_eq(name, "vfs")) { + if (pos >= AVS_CONFIG_MOUNTTABLE_MAX_ENTRIES) { + log_warning( + "Exceeding max number of supported mounttable entries " + "(%d), ignoring remaining", + pos); + break; + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "name@", + PROPERTY_TYPE_ATTR, + name, + sizeof(name)))) { + log_fatal("Missing 'name' attribute on vfs node"); + } + + if (str_eq(name, mounttable_selector)) { + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "fstype@", + PROPERTY_TYPE_ATTR, + mounttable->entry[pos].fstype, + sizeof(mounttable->entry[pos].fstype)))) { + // default + str_cpy( + mounttable->entry[pos].fstype, + sizeof(mounttable->entry[pos].fstype), + "fs"); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "src@", + PROPERTY_TYPE_ATTR, + mounttable->entry[pos].src, + sizeof(mounttable->entry[pos].src)))) { + log_fatal( + "Missing 'src' attribute on vfs node, name: %s", + name); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "dst@", + PROPERTY_TYPE_ATTR, + mounttable->entry[pos].dst, + sizeof(mounttable->entry[pos].dst)))) { + log_fatal( + "Missing 'dst' attribute on vfs node, name: %s", + name); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "opt@", + PROPERTY_TYPE_ATTR, + mounttable->entry[pos].opt, + sizeof(mounttable->entry[pos].opt)))) { + // optional + } + + pos++; + } + } + + cur = property_node_traversal(cur, TRAVERSE_NEXT_SIBLING); + } + } else { + cur = property_search(NULL, fs_node, "nvram"); + + if (cur) { + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "fstype", + PROPERTY_TYPE_STR, + mounttable->entry[pos].fstype, + sizeof(mounttable->entry[pos].fstype)))) { + // default + str_cpy( + mounttable->entry[pos].fstype, + sizeof(mounttable->entry[pos].fstype), + "fs"); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "device", + PROPERTY_TYPE_STR, + mounttable->entry[pos].src, + sizeof(mounttable->entry[pos].src)))) { + log_fatal("Missing 'device' attribute on nvram node"); + } + + str_cpy( + mounttable->entry[pos].dst, + sizeof(mounttable->entry[pos].dst), + "/dev/nvram"); + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "opt", + PROPERTY_TYPE_STR, + mounttable->entry[pos].opt, + sizeof(mounttable->entry[pos].opt)))) { + // optional + } + + pos++; + } + + cur = property_search(NULL, fs_node, "raw"); + + if (cur) { + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "fstype", + PROPERTY_TYPE_STR, + mounttable->entry[pos].fstype, + sizeof(mounttable->entry[pos].fstype)))) { + // default + str_cpy( + mounttable->entry[pos].fstype, + sizeof(mounttable->entry[pos].fstype), + "fs"); + } + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "device", + PROPERTY_TYPE_STR, + mounttable->entry[pos].src, + sizeof(mounttable->entry[pos].src)))) { + log_fatal("Missing 'device' attribute on raw node"); + } + + str_cpy( + mounttable->entry[pos].dst, + sizeof(mounttable->entry[pos].dst), + "/dev/raw"); + + if (AVS_IS_ERROR(property_node_refer( + NULL, + cur, + "opt", + PROPERTY_TYPE_STR, + mounttable->entry[pos].opt, + sizeof(mounttable->entry[pos].opt)))) { + // optional + } + + pos++; + } + } + + mounttable->num_entries = pos; +} \ No newline at end of file diff --git a/src/main/launcher/avs-config.h b/src/main/launcher/avs-config.h new file mode 100644 index 00000000..692643d3 --- /dev/null +++ b/src/main/launcher/avs-config.h @@ -0,0 +1,57 @@ +#ifndef LAUNCHER_AVS_CONFIG_H +#define LAUNCHER_AVS_CONFIG_H + +#include "core/log-bt.h" + +#include "imports/avs.h" + +#include "launcher/bootstrap-config.h" + +#define AVS_CONFIG_MOUNTTABLE_MAX_ENTRIES 16 + +struct avs_config_vfs_mounttable { + struct { + char fstype[64]; + char src[512]; + char dst[512]; + char opt[256]; + } entry[AVS_CONFIG_MOUNTTABLE_MAX_ENTRIES]; + + uint8_t num_entries; +}; + +struct property *avs_config_load(const char *filepath); +struct property_node *avs_config_root_get(struct property *property); +struct property * +avs_config_property_merge(struct property *parent, struct property *source); + +void avs_config_fs_root_device_get( + struct property_node *node, char *buffer, size_t size); + +void avs_config_mode_product_set(struct property_node *node, bool enable); +void avs_config_net_raw_set(struct property_node *node, bool enable); +void avs_config_net_eaudp_set(struct property_node *node, bool enable); +void avs_config_sntp_ea_set(struct property_node *node, bool on); +void avs_config_log_level_set(struct property_node *node, const char *level); +void avs_config_log_name_set(struct property_node *node, const char *name); +void avs_config_log_file_set(struct property_node *node, const char *file); +void avs_config_log_buffer_size_set(struct property_node *node, uint32_t size); +void avs_config_log_output_delay_set( + struct property_node *node, uint16_t delay_ms); +void avs_config_log_enable_console_set(struct property_node *node, bool enable); +void avs_config_log_enable_sci_set(struct property_node *node, bool enable); +void avs_config_log_enable_net_set(struct property_node *node, bool enable); +void avs_config_log_enable_file_set(struct property_node *node, bool enable); +void avs_config_log_rotate_set(struct property_node *node, bool rotate); +void avs_config_log_append_set(struct property_node *node, bool append); +void avs_config_log_count_set(struct property_node *node, uint16_t count); + +void avs_config_set_log_level( + struct property_node *node, enum core_log_bt_log_level loglevel); +void avs_config_local_fs_path_dev_nvram_and_raw_set( + struct property_node *node, const char *dev_nvram_raw_path); + +void avs_config_vfs_mounttable_get( + struct property_node *node, struct avs_config_vfs_mounttable *mounttable); + +#endif \ No newline at end of file diff --git a/src/main/launcher/avs-context.c b/src/main/launcher/avs-context.c deleted file mode 100644 index 1e391db1..00000000 --- a/src/main/launcher/avs-context.c +++ /dev/null @@ -1,72 +0,0 @@ -#include - -#include -#include -#include - -#include "imports/avs.h" - -#include "launcher/avs-context.h" - -#include "util/log.h" - -static void *avs_heap; - -#ifdef AVS_HAS_STD_HEAP -static void *std_heap; -#endif - -void avs_context_init( - struct property_node *config, - uint32_t avs_heap_size, - uint32_t std_heap_size, - avs_log_writer_t log_writer, - void *log_writer_ctx) -{ - avs_heap = VirtualAlloc( - NULL, avs_heap_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - - if (avs_heap == NULL) { - log_fatal( - "Failed to VirtualAlloc %d byte AVS heap: %08x", - avs_heap_size, - (unsigned int) GetLastError()); - } - -#ifdef AVS_HAS_STD_HEAP - std_heap = VirtualAlloc( - NULL, std_heap_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - - if (std_heap == NULL) { - log_fatal( - "Failed to VirtualAlloc %d byte \"std\" heap: %08x", - std_heap_size, - (unsigned int) GetLastError()); - } -#endif - -#ifdef AVS_HAS_STD_HEAP - avs_boot( - config, - std_heap, - std_heap_size, - avs_heap, - avs_heap_size, - log_writer, - log_writer_ctx); -#else - /* AVS v2.16.xx and I suppose onward uses a unified heap */ - avs_boot(config, avs_heap, avs_heap_size, NULL, log_writer, log_writer_ctx); -#endif -} - -void avs_context_fini(void) -{ - avs_shutdown(); - -#ifdef AVS_HAS_STD_HEAP - VirtualFree(std_heap, 0, MEM_RELEASE); -#endif - - VirtualFree(avs_heap, 0, MEM_RELEASE); -} diff --git a/src/main/launcher/avs-context.h b/src/main/launcher/avs-context.h deleted file mode 100644 index d4532eb4..00000000 --- a/src/main/launcher/avs-context.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef LAUNCHER_AVS_CONTEXT_H -#define LAUNCHER_AVS_CONTEXT_H - -#include - -#include "imports/avs.h" - -#if AVS_VERSION < 1600 -#define AVS_HAS_STD_HEAP -#endif - -void avs_context_init( - struct property_node *config, - uint32_t avs_heap_size, - uint32_t std_heap_size, - avs_log_writer_t log_writer, - void *log_writer_ctx); -void avs_context_fini(void); - -#endif diff --git a/src/main/launcher/avs.c b/src/main/launcher/avs.c new file mode 100644 index 00000000..4ff0730a --- /dev/null +++ b/src/main/launcher/avs.c @@ -0,0 +1,270 @@ +#define LOG_MODULE "avs" + +#include + +#include +#include +#include + +#include "core/log-bt.h" +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/avs-config.h" +#include "launcher/avs.h" +#include "launcher/property-util.h" + +#include "util/codepage.h" +#include "util/fs.h" +#include "util/mem.h" +#include "util/str.h" + +#if AVS_VERSION < 1600 +#define AVS_HAS_STD_HEAP +#endif + +static void *avs_heap; + +#ifdef AVS_HAS_STD_HEAP +static void *std_heap; +#endif + +/* Gratuitous API changes orz */ +static AVS_LOG_WRITER(_avs_context_log_writer, chars, nchars, ctx) +{ + wchar_t *utf16; + char *utf8; + int utf16_len; + int utf8_len; + int result; + + /* Ignore existing NUL terminator */ + + nchars--; + + /* Transcode shit_jis to UTF-8 */ + + utf16_len = MultiByteToWideChar(CP_SHIFT_JIS, 0, chars, nchars, NULL, 0); + + if (utf16_len == 0) { + abort(); + } + + utf16 = xmalloc(sizeof(*utf16) * utf16_len); + result = + MultiByteToWideChar(CP_SHIFT_JIS, 0, chars, nchars, utf16, utf16_len); + + if (result == 0) { + abort(); + } + + utf8_len = + WideCharToMultiByte(CP_UTF8, 0, utf16, utf16_len, NULL, 0, NULL, NULL); + + if (utf8_len == 0) { + abort(); + } + + utf8 = xmalloc(utf8_len + 3); + result = WideCharToMultiByte( + CP_UTF8, 0, utf16, utf16_len, utf8, utf8_len, NULL, NULL); + + if (result == 0) { + abort(); + } + +#if AVS_VERSION >= 1500 + utf8[utf8_len + 0] = '\r'; + utf8[utf8_len + 1] = '\n'; + + utf8_len += 2; +#endif + + // Clean string terminate + utf8[utf8_len] = '\0'; + + // Write to launcher's dedicated logging backend + core_log_bt_direct_sink_write(utf8, utf8_len); + + /* Clean up */ + + free(utf8); + free(utf16); +} + +static void _avs_switch_log_engine() +{ + // Switch the logging backend now that AVS is booted to use a single logging + // engine which avoids concurrency issues as AVS runs it's own async logger + // thread + core_log_impl_set( + log_body_misc, log_body_info, log_body_warning, log_body_fatal); + + log_misc("Switched logging engine to AVS"); +} + +void avs_fs_assert_root_device_exists(struct property_node *node) +{ + char root_device_path[PATH_MAX]; + char cwd_path[PATH_MAX]; + + avs_config_fs_root_device_get( + node, root_device_path, sizeof(root_device_path)); + getcwd(cwd_path, sizeof(cwd_path)); + + if (!path_exists(root_device_path)) { + log_fatal( + "Root device path '%s' does not exist in current working dir '%s'", + root_device_path, + cwd_path); + } +} + +void avs_fs_mountpoints_fs_dirs_create(struct property_node *node) +{ + struct avs_config_vfs_mounttable mounttable; + uint8_t i; + + avs_config_vfs_mounttable_get(node, &mounttable); + + if (mounttable.num_entries == 0) { + log_warning("No mountpoints found in mounttable"); + } + + for (i = 0; i < mounttable.num_entries; i++) { + if (str_eq(mounttable.entry[i].fstype, "fs")) { + log_misc( + "Creating avs fs directory '%s' for destination/device '%s'...", + mounttable.entry[i].src, + mounttable.entry[i].dst); + + if (!path_exists(mounttable.entry[i].src)) { + if (!path_mkdir(mounttable.entry[i].src)) { + log_fatal( + "Creating fs directory %s failed", + mounttable.entry[i].src); + } + } + } + } +} + +void avs_init( + struct property_node *node, uint32_t avs_heap_size, uint32_t std_heap_size) +{ + log_assert(node); + log_assert(avs_heap_size > 0); + // Modern games don't have a separate std heap anymore + log_assert(std_heap_size >= 0); + + log_info("init"); + + log_misc("Allocating avs heap: %d", avs_heap_size); + + avs_heap = VirtualAlloc( + NULL, avs_heap_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + if (avs_heap == NULL) { + log_fatal( + "Failed to VirtualAlloc %d byte AVS heap: %08x", + avs_heap_size, + (unsigned int) GetLastError()); + } + +#ifdef AVS_HAS_STD_HEAP + log_misc("Allocating std heap: %d", std_heap_size); + + std_heap = VirtualAlloc( + NULL, std_heap_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + if (std_heap == NULL) { + log_fatal( + "Failed to VirtualAlloc %d byte \"std\" heap: %08x", + std_heap_size, + (unsigned int) GetLastError()); + } +#endif + + log_info("Calling avs_boot"); + +#ifdef AVS_HAS_STD_HEAP + avs_boot( + node, + std_heap, + std_heap_size, + avs_heap, + avs_heap_size, + _avs_context_log_writer, + NULL); +#else + /* AVS v2.16.xx and I suppose onward uses a unified heap */ + avs_boot( + node, avs_heap, avs_heap_size, NULL, _avs_context_log_writer, NULL); +#endif + + _avs_switch_log_engine(); + + log_misc("init done"); +} + +void avs_fs_file_copy(const char *src, const char *dst) +{ + struct avs_stat st; + + log_assert(src); + log_assert(dst); + + log_misc("Copying %s to %s...", src, dst); + + if (!avs_fs_lstat(src, &st)) { + log_fatal("File source %s does not exist or is not accessible", src); + } + + if (avs_fs_copy(src, dst) < 0) { + log_fatal("Failed copying file %s to %s", src, dst); + } +} + +void avs_fs_dir_log(const char *path) +{ + const char *name; + + log_assert(path); + + avs_desc dir = avs_fs_opendir(path); + + if (dir < 0) { + log_warning( + "Opening avs dir %s failed, skipping logging contents", path); + } + + log_misc("Contents of %s:", path); + + do { + name = avs_fs_readdir(dir); + + if (name == NULL) { + break; + } + + log_misc("%s", name); + } while (name != NULL); + + avs_fs_closedir(dir); +} + +void avs_fini(void) +{ + log_info("fini"); + + avs_shutdown(); + +#ifdef AVS_HAS_STD_HEAP + VirtualFree(std_heap, 0, MEM_RELEASE); +#endif + + VirtualFree(avs_heap, 0, MEM_RELEASE); + + log_misc("fini done"); +} diff --git a/src/main/launcher/avs.h b/src/main/launcher/avs.h new file mode 100644 index 00000000..ad2233ac --- /dev/null +++ b/src/main/launcher/avs.h @@ -0,0 +1,16 @@ +#ifndef LAUNCHER_AVS_H +#define LAUNCHER_AVS_H + +#include + +#include "imports/avs.h" + +void avs_fs_assert_root_device_exists(struct property_node *node); +void avs_fs_mountpoints_fs_dirs_create(struct property_node *node); +void avs_init( + struct property_node *node, uint32_t avs_heap_size, uint32_t std_heap_size); +void avs_fs_file_copy(const char *src, const char *dst); +void avs_fs_dir_log(const char *path); +void avs_fini(void); + +#endif diff --git a/src/main/launcher/bootstrap-config.c b/src/main/launcher/bootstrap-config.c new file mode 100644 index 00000000..35915c65 --- /dev/null +++ b/src/main/launcher/bootstrap-config.c @@ -0,0 +1,548 @@ +#define LOG_MODULE "bootstrap-config" + +#include + +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/avs-config.h" +#include "launcher/bootstrap-config.h" +#include "launcher/property-util.h" + +#include "util/defs.h" +#include "util/hex.h" +#include "util/str.h" + +// clang-format off +PSMAP_BEGIN(bootstrap_startup_boot_psmap) +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct bootstrap_boot_config, config_file, + "boot/file") +PSMAP_REQUIRED(PSMAP_TYPE_U32, struct bootstrap_boot_config, avs_heap_size, + "boot/heap_avs") +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_boot_config, std_heap_size, + "boot/heap_std", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_boot_config, mount_table_selector, + "boot/mounttable_selector", "boot") +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_boot_config, watcher_enable, + "boot/watcher", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_boot_config, timemachine_enable, + "boot/timemachine", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_boot_config, launch_config_file, + "boot/launch_path", "/dev/raw/launch.xml") +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_log_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_log_config, level, + "log/level", "all") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_log_config, name, + "log/name", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_log_config, file, + "log/file", "") +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_log_config, bufsz, + "log/sz_buf", 4096) +PSMAP_OPTIONAL(PSMAP_TYPE_U16, struct bootstrap_log_config, output_delay_ms, + "log/output_delay", 10) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_log_config, enable_console, + "log/enable_console", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_log_config, enable_sci, + "log/enable_netsci", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_log_config, enable_net, + "log/enable_netlog", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_log_config, enable_file, + "log/enable_file", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_log_config, rotate, + "log/rotate", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_log_config, append, + "log/append", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_U16, struct bootstrap_log_config, count, + "log/gen", 10) +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_minidump_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_U8, struct bootstrap_minidump_config, count, + "minidump/gen", 10) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_minidump_config, continue_, + "minidump/cont_debug", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_minidump_config, log, + "minidump/echo_log", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_U8, struct bootstrap_minidump_config, type, + "minidump/dump_type", 2) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_minidump_config, path, + "minidump/path", "/dev/raw/minidump") +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_minidump_config, symbufsz, + "minidump/sz_symbuf", 32768) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_minidump_config, search_path, + "minidump/search", ".") +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_module_psmap) +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct bootstrap_module_config, file, + "component/file") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_module_config, load_type, + "component/load_type", "MEMORY") +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_dlm_psmap) +/* disabled until we implement PSMAP_TYPE_BIN + PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_startup_config, ntdll_digest, + "dlml/ntdll/hash", "") + */ +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_dlm_config, size, + "dlml/ntdll/size", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_dlm_config, ift_table, + "dlml/ntdll/ift_table", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_dlm_config, ift_insert, + "dlml/ntdll/insert_ift", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_dlm_config, ift_remove, + "dlml/ntdll/remove_ift", 0) +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_shield_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_shield_config, enable, + "shield/enable", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_shield_config, verbose, + "shield/verbose", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_shield_config, use_loadlibrary, + "shield/use_loadlibrary", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_shield_config, logger, + "shield/logger", "") +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_shield_config, sleep_min, + "shield/sleepmin", 10) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_shield_config, sleep_blur, + "shield/sleepblur", 90) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_shield_config, whitelist_file, + "shield/whitelist", "prop/whitelist.csv") +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_shield_config, tick_sleep, + "shield/ticksleep", 100) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_shield_config, tick_error, + "shield/tickerror", 1000) +PSMAP_OPTIONAL(PSMAP_TYPE_U8, struct bootstrap_shield_config, overwork_threshold, + "shield/overwork_threshold", 50) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_shield_config, overwork_delay, + "shield/overwork_delay", 100) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_shield_config, pause_delay, + "shield/pause_delay", 1000) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_shield_config, unlimited_key, + "shield/unlimited_key", "") +PSMAP_OPTIONAL(PSMAP_TYPE_U16, struct bootstrap_shield_config, killer_port, + "shield_killer/port", 5001) +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_dongle_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_dongle_config, license_cn, + "dongle/license", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_dongle_config, account_cn, + "dongle/account", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_dongle_config, driver_dll, + "dongle/pkcs11_driver", "eTPKCS11.dll") +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_dongle_config, disable_gc, + "dongle/disable_gc", 0) +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_drm_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_drm_config, dll, + "drm/dll", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_drm_config, fstype, + "drm/fstype", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_drm_config, device, + "drm/device", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_drm_config, mount, + "drm/dst", "/") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_drm_config, options, + "drm/option", "") +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_lte_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_lte_config, enable, + "lte/enable", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_lte_config, config_file, + "lte/file", "/dev/nvram/lte-config.xml") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_lte_config, unlimited_key, + "lte/unlimited_key", "") +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_ssl_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_ssl_config, options, + "ssl/option", "") +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_esign_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_esign_config, enable, + "esign/enable", 0) +PSMAP_END + +PSMAP_BEGIN(bootstrap_startup_eamuse_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_eamuse_config, enable, + "eamuse/enable", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_eamuse_config, sync, + "eamuse/sync", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_eamuse_config, enable_model, + "eamuse/enable_model", 0) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_eamuse_config, config_file, + "eamuse/file", "/dev/nvram/ea3-config.xml") +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct bootstrap_eamuse_config, updatecert_enable, + "eamuse/updatecert_enable", 1) +PSMAP_OPTIONAL(PSMAP_TYPE_U32, struct bootstrap_eamuse_config, updatecert_interval, + "eamuse/updatecert_interval", 0) +PSMAP_END + +PSMAP_BEGIN(bootstrap_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct bootstrap_config, release_code, "/release_code", "") +PSMAP_END +// clang-format on + +#define ROOT_NODE "/config" +#define MODULE_PATH_PREFIX "modules/" + +#define NODE_MISSING_FATAL(subnode) \ + log_fatal("%s/%s: Node missing", ROOT_NODE, subnode); +#define NODE_STARTUP_MISSING_FATAL(profile) \ + log_fatal("%s/startup/%s: Node missing", ROOT_NODE, profile); +#define NODE_PROFILE_MISSING_FATAL(profile, subnode) \ + log_fatal("%s/%s/%s: Node missing", ROOT_NODE, profile, subnode); +#define NODE_PROFILE_LOADING_FATAL(profile, subnode) \ + log_fatal("%s/startup/%s/%s: Node loading", ROOT_NODE, profile, subnode); + +#define DEFAULT_HEAP_SIZE 16777216 + +const char *const inherited_nodes[] = { + "develop", + "default", + "log", + "minidump", + "boot", + "drm", + "ssl", + "eamuse", + "shield", + "esign", + "dongle", + "lte", +}; + +static void _bootstrap_config_profile_node_verify( + struct property_node *node, const char *profile) +{ + struct property_node *profile_node; + + log_assert(node); + log_assert(profile); + + profile_node = property_search(NULL, node, profile); + + if (!profile_node) { + NODE_STARTUP_MISSING_FATAL(profile); + } +} + +static struct property_node * +_bootstrap_config_root_node_get(struct property *property) +{ + struct property_node *root_node; + + log_assert(property); + + root_node = property_search(property, NULL, ROOT_NODE); + + if (!root_node) { + NODE_MISSING_FATAL(""); + } + + return root_node; +} + +static struct property_node * +_bootstrap_config_startup_node_get(struct property_node *node) +{ + struct property_node *startup_node; + + log_assert(node); + + startup_node = property_search(NULL, node, "startup"); + + if (!startup_node) { + NODE_MISSING_FATAL("startup"); + } + + return startup_node; +} + +static void _bootstrap_config_inheritance_resolve( + struct property_node *startup_node, const char *profile_name) +{ + struct property_node *startup_parent_node; + struct property_node *startup_profile_node; + struct property_node *tmp_node; + + char inherit_name[64]; + avs_error error; + struct property_node *result; + + startup_profile_node = property_search(NULL, startup_node, profile_name); + + if (!startup_profile_node) { + log_fatal(ROOT_NODE "/startup/%s: missing", profile_name); + } + + startup_parent_node = startup_profile_node; + + for (;;) { + error = property_node_refer( + NULL, + startup_parent_node, + "inherit@", + PROPERTY_TYPE_ATTR, + inherit_name, + sizeof(inherit_name)); + + if (AVS_IS_ERROR(error)) { + break; + } + + startup_parent_node = property_search(NULL, startup_node, inherit_name); + + if (!startup_parent_node) { + NODE_STARTUP_MISSING_FATAL(inherit_name); + } + + for (int i = 0; i < _countof(inherited_nodes); i++) { + if (property_search(NULL, startup_node, inherited_nodes[i])) { + continue; + } + + tmp_node = + property_search(NULL, startup_parent_node, inherited_nodes[i]); + + if (tmp_node) { + log_misc( + ROOT_NODE "/startup/%s: merging %s...", + inherit_name, + inherited_nodes[i]); + + result = property_node_clone( + NULL, startup_profile_node, tmp_node, true); + + if (!result) { + log_fatal( + "Merging '%s' into '%s' failed", + inherited_nodes[i], + inherit_name); + } + } + } + } +} + +static void _bootstrap_config_load_bootstrap_module_app_config( + struct property_node *profile_node, struct bootstrap_module_config *config) +{ + struct property_node *app_node; + + log_assert(profile_node); + log_assert(config); + + app_node = property_search(NULL, profile_node, "component/param"); + + config->app_config = property_util_node_extract(app_node); +} + +static void _bootstrap_config_load_bootstrap_default_files_config( + const char *profile_name, + struct property_node *profile_node, + struct bootstrap_default_file_config *config) +{ + int i; + int result; + struct property_node *child; + + log_assert(profile_node); + log_assert(config); + + child = property_search(NULL, profile_node, "default/file"); + i = 0; + + while (child) { + if (i >= DEFAULT_FILE_MAX) { + log_warning( + "Currently not supporting more than %d default files, skipping " + "remaining", + i); + break; + } + + result = property_node_refer( + NULL, + child, + "src@", + PROPERTY_TYPE_ATTR, + &config->file[i].src, + sizeof(config->file[i].src)); + + if (result < 0) { + log_fatal( + "Missing src attribute on default file node of profile %s", + profile_name); + } + + result = property_node_refer( + NULL, + child, + "dst@", + PROPERTY_TYPE_ATTR, + &config->file[i].dst, + sizeof(config->file[i].dst)); + + if (result < 0) { + log_fatal( + "Missing dst attribute on default file node of profile %s", + profile_name); + } + + child = property_node_traversal(child, TRAVERSE_NEXT_SEARCH_RESULT); + i++; + } +} + +static void _bootstrap_config_load_bootstrap( + struct property_node *startup_node, + const char *profile, + struct bootstrap_startup_config *config) +{ + struct property_node *profile_node; + + profile_node = property_search(NULL, startup_node, profile); + + if (!profile_node) { + NODE_PROFILE_LOADING_FATAL(profile, ""); + } + + _bootstrap_config_load_bootstrap_default_files_config( + profile, profile_node, &config->default_file); + + if (!property_psmap_import( + NULL, profile_node, &config->boot, bootstrap_startup_boot_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "boot"); + } + + if (!property_psmap_import( + NULL, profile_node, &config->log, bootstrap_startup_log_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "log"); + } + + if (!property_psmap_import( + NULL, + profile_node, + &config->minidump, + bootstrap_startup_minidump_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "minidump"); + } + + if (!property_psmap_import( + NULL, + profile_node, + &config->module, + bootstrap_startup_module_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "component"); + } + + _bootstrap_config_load_bootstrap_module_app_config( + profile_node, &config->module); + + if (!property_psmap_import( + NULL, + profile_node, + &config->dlm_ntdll, + bootstrap_startup_dlm_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "dlm/ntdll"); + } + + if (!property_psmap_import( + NULL, + profile_node, + &config->shield, + bootstrap_startup_shield_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "shield"); + } + + if (!property_psmap_import( + NULL, + profile_node, + &config->dongle, + bootstrap_startup_dongle_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "dongle"); + } + + if (!property_psmap_import( + NULL, profile_node, &config->drm, bootstrap_startup_drm_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "drm"); + } + + if (!property_psmap_import( + NULL, profile_node, &config->lte, bootstrap_startup_lte_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "lte"); + } + + if (!property_psmap_import( + NULL, profile_node, &config->ssl, bootstrap_startup_ssl_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "ssl"); + } + + if (!property_psmap_import( + NULL, + profile_node, + &config->esign, + bootstrap_startup_esign_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "esign"); + } + + if (!property_psmap_import( + NULL, + profile_node, + &config->eamuse, + bootstrap_startup_eamuse_psmap)) { + NODE_PROFILE_LOADING_FATAL(profile, "eamuse"); + } +} + +void bootstrap_config_init(struct bootstrap_config *config) +{ + log_assert(config); + + memset(config, 0, sizeof(*config)); +} + +void bootstrap_config_load( + struct property *property, + const char *profile, + struct bootstrap_config *config) +{ + struct property_node *root_node; + struct property_node *startup_node; + + log_assert(property); + log_assert(profile); + log_assert(config); + + log_info(ROOT_NODE ": loading..."); + + root_node = _bootstrap_config_root_node_get(property); + + if (!property_psmap_import(NULL, root_node, config, bootstrap_psmap)) { + log_fatal(ROOT_NODE ": loading failed"); + } + + startup_node = _bootstrap_config_startup_node_get(root_node); + + _bootstrap_config_profile_node_verify(startup_node, profile); + + _bootstrap_config_inheritance_resolve(startup_node, profile); + + log_misc(ROOT_NODE "/startup/%s: loading merged result...", profile); + + property_util_node_log(startup_node); + + _bootstrap_config_load_bootstrap(startup_node, profile, &config->startup); + + log_misc("Loading finished"); +} \ No newline at end of file diff --git a/src/main/launcher/bootstrap-config.h b/src/main/launcher/bootstrap-config.h new file mode 100644 index 00000000..596bf93f --- /dev/null +++ b/src/main/launcher/bootstrap-config.h @@ -0,0 +1,139 @@ +#ifndef LAUNCHER_BOOTSTRAP_CONFIG_H +#define LAUNCHER_BOOTSTRAP_CONFIG_H + +#include +#include + +#include "imports/avs.h" + +// should be enough for a while +#define DEFAULT_FILE_MAX 16 + +struct bootstrap_startup_config { + struct bootstrap_default_file_config { + struct bootstrap_default_file { + char src[64]; + char dst[64]; + } file[DEFAULT_FILE_MAX]; + } default_file; + + struct bootstrap_boot_config { + char config_file[64]; + uint32_t avs_heap_size; + uint32_t std_heap_size; + char launch_config_file[64]; + char mount_table_selector[16]; + bool watcher_enable; + bool timemachine_enable; + } boot; + + struct bootstrap_log_config { + char level[8]; + char name[64]; + char file[64]; + uint32_t bufsz; + uint16_t output_delay_ms; + bool enable_console; + bool enable_sci; + bool enable_net; + bool enable_file; + bool rotate; + bool append; + uint16_t count; + } log; + + struct bootstrap_minidump_config { + uint8_t count; + bool continue_; + bool log; + uint8_t type; + char path[64]; + uint32_t symbufsz; + char search_path[64]; + } minidump; + + struct bootstrap_module_config { + char file[64]; + char load_type[64]; + struct property *app_config; + } module; + + struct bootstrap_dlm_config { + char digest[16]; + uint32_t size; + uint32_t ift_table; + uint32_t ift_insert; + uint32_t ift_remove; + }; + + struct bootstrap_dlm_config dlm_ntdll; + + struct bootstrap_shield_config { + bool enable; + bool verbose; + bool use_loadlibrary; + char logger[64]; + uint32_t sleep_min; + uint32_t sleep_blur; + uint32_t tick_sleep; + uint32_t tick_error; + uint8_t overwork_threshold; + uint32_t overwork_delay; + uint32_t pause_delay; + char whitelist_file[64]; + char unlimited_key[10]; + uint16_t killer_port; + } shield; + + struct bootstrap_dongle_config { + char license_cn[32]; + char account_cn[32]; + char driver_dll[16]; + bool disable_gc; + } dongle; + + struct bootstrap_drm_config { + char dll[64]; + char device[64]; + char mount[64]; + char fstype[64]; + char options[64]; + } drm; + + struct bootstrap_lte_config { + bool enable; + char config_file[64]; + char unlimited_key[10]; + } lte; + + struct bootstrap_ssl_config { + char options[64]; + } ssl; + + struct bootstrap_esign_config { + bool enable; + } esign; + + struct bootstrap_eamuse_config { + bool enable; + bool sync; + bool enable_model; + char config_file[64]; + bool updatecert_enable; + uint32_t updatecert_interval; + } eamuse; +}; + +struct bootstrap_config { + char release_code[16]; + struct bootstrap_startup_config startup; +}; + +void bootstrap_config_init(struct bootstrap_config *config); + +void bootstrap_config_load( + struct property *property, + const char *profile, + struct bootstrap_config *config); + +#endif /* LAUNCHER_BOOTSTRAP_CONFIG_H */ diff --git a/src/main/launcher/bootstrap.c b/src/main/launcher/bootstrap.c new file mode 100644 index 00000000..a9e52bbe --- /dev/null +++ b/src/main/launcher/bootstrap.c @@ -0,0 +1,347 @@ +#define LOG_MODULE "bootstrap" + +#include "core/log-bt.h" +#include "core/log-sink-file.h" +#include "core/log-sink-list.h" +#include "core/log-sink-null.h" +#include "core/log-sink-std.h" +#include "core/log.h" + +#include "launcher/avs-config.h" +#include "launcher/avs.h" +#include "launcher/bootstrap-config.h" +#include "launcher/ea3-ident-config.h" +#include "launcher/eamuse-config.h" +#include "launcher/eamuse.h" +#include "launcher/launcher-config.h" +#include "launcher/module.h" +#include "launcher/property-util.h" + +#include "util/str.h" + +static bool _bootstrap_log_property_configs; +static struct module_context _bootstrap_module_context; + +static void _bootstrap_eamuse_ea3_ident_config_inject( + struct property_node *node, const struct ea3_ident_config *ea3_ident_config) +{ + eamuse_config_id_softid_set(node, ea3_ident_config->softid); + eamuse_config_id_hardid_set(node, ea3_ident_config->hardid); + eamuse_config_id_pcbid_set(node, ea3_ident_config->pcbid); + eamuse_config_soft_model_set(node, ea3_ident_config->model); + eamuse_config_soft_dest_set(node, ea3_ident_config->dest); + eamuse_config_soft_spec_set(node, ea3_ident_config->spec); + eamuse_config_soft_rev_set(node, ea3_ident_config->rev); + eamuse_config_soft_ext_set(node, ea3_ident_config->ext); +} + +static void +_bootstrap_avs_config_force_overrides_apply(struct property_node *node) +{ + log_assert(node); + + avs_config_mode_product_set(node, true); + avs_config_net_raw_set(node, true); + avs_config_net_eaudp_set(node, true); + avs_config_sntp_ea_set(node, true); +} + +static void _bootstrap_avs_config_log_overrides_apply( + struct property_node *node, const struct bootstrap_log_config *log_config) +{ + log_assert(node); + log_assert(log_config); + + avs_config_log_level_set(node, log_config->level); + avs_config_log_name_set(node, log_config->name); + avs_config_log_file_set(node, log_config->file); + avs_config_log_buffer_size_set(node, log_config->bufsz); + avs_config_log_output_delay_set(node, log_config->output_delay_ms); + avs_config_log_enable_console_set(node, log_config->enable_console); + avs_config_log_enable_sci_set(node, log_config->enable_sci); + avs_config_log_enable_net_set(node, log_config->enable_net); + avs_config_log_enable_file_set(node, log_config->enable_file); + avs_config_log_rotate_set(node, log_config->rotate); + avs_config_log_append_set(node, log_config->append); + avs_config_log_count_set(node, log_config->count); +} + +static enum core_log_bt_log_level _bootstrap_log_map_level(const char *level) +{ + if (str_eq(level, "fatal")) { + return CORE_LOG_BT_LOG_LEVEL_FATAL; + } else if (str_eq(level, "warning")) { + return CORE_LOG_BT_LOG_LEVEL_WARNING; + } else if (str_eq(level, "info")) { + return CORE_LOG_BT_LOG_LEVEL_INFO; + } else if (str_eq(level, "misc")) { + return CORE_LOG_BT_LOG_LEVEL_MISC; + } else if (str_eq(level, "all")) { + return CORE_LOG_BT_LOG_LEVEL_MISC; + } else if (str_eq(level, "disable")) { + return CORE_LOG_BT_LOG_LEVEL_OFF; + } else if (str_eq(level, "default")) { + return CORE_LOG_BT_LOG_LEVEL_WARNING; + } else { + log_fatal("Unknown log level string %s", level); + } +} + +void bootstrap_init(bool log_property_configs) +{ + log_info("init"); + + _bootstrap_log_property_configs = log_property_configs; + + log_misc("init done"); +} + +void bootstrap_log_init(const struct bootstrap_log_config *config) +{ + struct core_log_sink sinks[2]; + struct core_log_sink sink_composed; + enum core_log_bt_log_level level; + + log_assert(config); + + log_info("log init"); + + // Shutdown old setup + core_log_bt_fini(); + + if (config->enable_file && strlen(config->file) > 0 && + config->enable_console) { + core_log_sink_std_out_open(true, &sinks[0]); + core_log_sink_file_open( + config->file, + config->append, + config->rotate, + config->count, + &sinks[1]); + core_log_sink_list_open(sinks, 2, &sink_composed); + } else if (config->enable_file && strlen(config->file) > 0) { + core_log_sink_file_open( + config->file, + config->append, + config->rotate, + config->count, + &sink_composed); + } else if (config->enable_console) { + core_log_sink_std_out_open(true, &sink_composed); + } else { + core_log_sink_null_open(&sink_composed); + } + + core_log_bt_init(&sink_composed); + + level = _bootstrap_log_map_level(config->level); + core_log_bt_level_set(level); + + log_misc("log init done"); +} + +void bootstrap_default_files_create( + const struct bootstrap_default_file_config *config) +{ + log_assert(config); + + log_info("default files create"); + + for (int i = 0; i < DEFAULT_FILE_MAX; i++) { + if (strlen(config->file[i].src) > 0 && + strlen(config->file[i].dst) > 0) { + avs_fs_file_copy(config->file[i].src, config->file[i].dst); + } + } + + log_misc("default files create done"); +} + +void bootstrap_avs_init( + const struct bootstrap_boot_config *config, + const struct bootstrap_log_config *log_config, + struct property *override_property) +{ + struct property *file_property; + struct property *merged_property; + struct property_node *root_node; + + log_assert(config); + log_assert(log_config); + log_assert(override_property); + + log_info("avs init"); + + file_property = avs_config_load(config->config_file); + + if (_bootstrap_log_property_configs) { + log_misc("avs-config from file: %s", config->config_file); + property_util_log(file_property); + } + + merged_property = + avs_config_property_merge(file_property, override_property); + + property_util_free(file_property); + + if (_bootstrap_log_property_configs) { + log_misc("avs-config merged with overrides"); + property_util_log(merged_property); + } + + root_node = avs_config_root_get(merged_property); + + _bootstrap_avs_config_force_overrides_apply(root_node); + _bootstrap_avs_config_log_overrides_apply(root_node, log_config); + + if (_bootstrap_log_property_configs) { + log_misc("avs-config final"); + property_util_log(merged_property); + } + + avs_fs_assert_root_device_exists(root_node); + + log_misc("Creating AVS file system directories..."); + + avs_fs_mountpoints_fs_dirs_create(root_node); + + avs_init(root_node, config->avs_heap_size, config->std_heap_size); + + property_util_free(merged_property); + + log_misc("avs init done"); +} + +void bootstrap_eamuse_init( + const struct bootstrap_eamuse_config *config, + const struct ea3_ident_config *ea3_ident_config, + struct property *override_property) +{ + struct property *file_property; + struct property *merged_property; + struct property_node *root_node; + + log_assert(config); + log_assert(ea3_ident_config); + log_assert(override_property); + + log_info("eamuse init"); + + if (config->enable) { + file_property = eamuse_config_avs_load(config->config_file); + + if (_bootstrap_log_property_configs) { + log_misc("eamuse-config from file: %s", config->config_file); + property_util_log(file_property); + } + + merged_property = property_util_merge(file_property, override_property); + + property_util_free(file_property); + + if (_bootstrap_log_property_configs) { + log_misc("eamuse-config merged with overrides"); + property_util_log(merged_property); + } + + root_node = eamuse_config_root_get(merged_property); + + _bootstrap_eamuse_ea3_ident_config_inject(root_node, ea3_ident_config); + + if (_bootstrap_log_property_configs) { + log_misc("eamuse-config final"); + property_util_log(merged_property); + } + + eamuse_init(root_node); + + property_util_free(merged_property); + } else { + log_warning("Eamuse disabled"); + } + + log_misc("eamuse init done"); +} + +void bootstrap_module_init( + const struct bootstrap_module_config *module_config, + const struct array *iat_hook_dlls) +{ + log_assert(module_config); + log_assert(iat_hook_dlls); + + log_info("module init"); + + if (iat_hook_dlls->nitems > 0) { + log_info( + "Load game DLL with IAT hooks (%d): %s", + (uint32_t) iat_hook_dlls->nitems, + module_config->file); + + module_with_iat_hooks_init( + &_bootstrap_module_context, module_config->file, iat_hook_dlls); + } else { + log_info("Load game DLL: %s", module_config->file); + + module_init(&_bootstrap_module_context, module_config->file); + } + + log_misc("module init done"); +} + +void bootstrap_module_game_init( + const struct bootstrap_module_config *module_config, + struct ea3_ident_config *ea3_ident_config) +{ + struct property_node *node; + + log_assert(module_config); + log_assert(ea3_ident_config); + + log_info("module game init"); + + node = property_search(module_config->app_config, NULL, "/param"); + + if (!node) { + log_fatal("Missing param node on app-config"); + } + + if (_bootstrap_log_property_configs) { + log_misc("app-config"); + property_util_node_log(node); + } + + module_init_invoke(&_bootstrap_module_context, ea3_ident_config, node); + + log_misc("module game init done"); +} + +void bootstrap_module_game_run() +{ + log_info("module game run"); + + module_main_invoke(&_bootstrap_module_context); +} + +void bootstrap_module_game_fini() +{ + log_info("module game fini"); + + module_fini(&_bootstrap_module_context); +} + +void bootstrap_avs_fini() +{ + log_info("avs fini"); + + avs_fini(); +} + +void bootstrap_eamuse_fini(const struct bootstrap_eamuse_config *config) +{ + log_info("eamuse fini"); + + if (config->enable) { + eamuse_fini(); + } +} \ No newline at end of file diff --git a/src/main/launcher/bootstrap.h b/src/main/launcher/bootstrap.h new file mode 100644 index 00000000..4deab8f2 --- /dev/null +++ b/src/main/launcher/bootstrap.h @@ -0,0 +1,32 @@ +#ifndef LAUNCHER_BOOTSTRAP_H +#define LAUNCHER_BOOTSTRAP_H + +#include "launcher/bootstrap-config.h" +#include "launcher/ea3-ident-config.h" + +#include "util/array.h" + +void bootstrap_init(bool log_property_configs); +void bootstrap_log_init(const struct bootstrap_log_config *config); +void bootstrap_default_files_create( + const struct bootstrap_default_file_config *config); +void bootstrap_avs_init( + const struct bootstrap_boot_config *config, + const struct bootstrap_log_config *log_config, + struct property *override_property); +void bootstrap_eamuse_init( + const struct bootstrap_eamuse_config *config, + const struct ea3_ident_config *ea3_ident_config, + struct property *override_property); +void bootstrap_module_init( + const struct bootstrap_module_config *module_config, + const struct array *iat_hook_dlls); +void bootstrap_module_game_init( + const struct bootstrap_module_config *module_config, + struct ea3_ident_config *ea3_ident_config); +void bootstrap_module_game_run(); +void bootstrap_module_game_fini(); +void bootstrap_avs_fini(); +void bootstrap_eamuse_fini(const struct bootstrap_eamuse_config *config); + +#endif \ No newline at end of file diff --git a/src/main/launcher/debug.c b/src/main/launcher/debug.c new file mode 100644 index 00000000..2f40e338 --- /dev/null +++ b/src/main/launcher/debug.c @@ -0,0 +1,33 @@ + +#define LOG_MODULE "debug" + +#include +#include + +#include "core/log.h" + +#include "launcher/debug.h" + +void debug_remote_debugger_trap() +{ + BOOL res; + + log_info("Waiting until debugger attaches to remote process..."); + + while (true) { + res = FALSE; + + if (!CheckRemoteDebuggerPresent(GetCurrentProcess(), &res)) { + log_fatal( + "CheckRemoteDebuggerPresent failed: %08x", + (unsigned int) GetLastError()); + } + + if (res) { + log_info("Debugger attached, resuming"); + break; + } + + Sleep(1000); + } +} \ No newline at end of file diff --git a/src/main/launcher/debug.h b/src/main/launcher/debug.h new file mode 100644 index 00000000..f57ca8ba --- /dev/null +++ b/src/main/launcher/debug.h @@ -0,0 +1,6 @@ +#ifndef LAUNCHER_DEBUG_H +#define LAUNCHER_DEBUG_H + +void debug_remote_debugger_trap(); + +#endif \ No newline at end of file diff --git a/src/main/launcher/ea3-config.c b/src/main/launcher/ea3-config.c deleted file mode 100644 index 684ba466..00000000 --- a/src/main/launcher/ea3-config.c +++ /dev/null @@ -1,190 +0,0 @@ -#include - -#include "imports/avs.h" - -#include "launcher/ea3-config.h" -#include "launcher/module.h" - -#include "util/defs.h" -#include "util/hex.h" -#include "util/log.h" -#include "util/str.h" - -PSMAP_BEGIN(ea3_ident_psmap) -PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, softid, "/ea3/id/softid", "") -PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, hardid, "/ea3/id/hardid", "") -PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, pcbid, "/ea3/id/pcbid", "") -PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, model, "/ea3/soft/model") -PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, dest, "/ea3/soft/dest") -PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, spec, "/ea3/soft/spec") -PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, rev, "/ea3/soft/rev") -PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, ext, "/ea3/soft/ext") -PSMAP_END - -void ea3_ident_init(struct ea3_ident *ident) -{ - memset(ident, 0, sizeof(*ident)); -} - -bool ea3_ident_from_property( - struct ea3_ident *ident, struct property *ea3_config) -{ - return property_psmap_import(ea3_config, NULL, ident, ea3_ident_psmap); -} - -void ea3_ident_hardid_from_ethernet(struct ea3_ident *ident) -{ - struct avs_net_interface netif; - int result; - - result = avs_net_ctrl(1, &netif, sizeof(netif)); - - if (result < 0) { - log_fatal( - "avs_net_ctrl call to get MAC address returned error: %d", result); - } - - ident->hardid[0] = '0'; - ident->hardid[1] = '1'; - ident->hardid[2] = '0'; - ident->hardid[3] = '0'; - - hex_encode_uc( - netif.mac_addr, - sizeof(netif.mac_addr), - ident->hardid + 4, - sizeof(ident->hardid) - 4); -} - -bool ea3_ident_invoke_module_init( - struct ea3_ident *ident, - const struct module_context *module, - struct property_node *app_config) -{ - char sidcode_short[17]; - char sidcode_long[21]; - char security_code[9]; - bool ok; - - /* Set up security env vars */ - - str_format( - security_code, - lengthof(security_code), - "G*%s%s%s%s", - ident->model, - ident->dest, - ident->spec, - ident->rev); - - std_setenv("/env/boot/version", "0.0.0"); - std_setenv("/env/profile/security_code", security_code); - std_setenv("/env/profile/system_id", ident->pcbid); - std_setenv("/env/profile/account_id", ident->pcbid); - std_setenv("/env/profile/license_id", ident->softid); - std_setenv("/env/profile/software_id", ident->softid); - std_setenv("/env/profile/hardware_id", ident->hardid); - - /* Set up the short sidcode string, let dll_entry_init mangle it */ - - str_format( - sidcode_short, - lengthof(sidcode_short), - "%s%s%s%s%s", - ident->model, - ident->dest, - ident->spec, - ident->rev, - ident->ext); - - /* Set up long-form sidcode env var */ - - str_format( - sidcode_long, - lengthof(sidcode_long), - "%s:%s:%s:%s:%s", - ident->model, - ident->dest, - ident->spec, - ident->rev, - ident->ext); - - /* Set this up beforehand, as certain games require it in dll_entry_init */ - - std_setenv("/env/profile/soft_id_code", sidcode_long); - - ok = module_context_invoke_init(module, sidcode_short, app_config); - - if (!ok) { - return false; - } - - /* Back-propagate sidcode, as some games modify it during init */ - - memcpy(ident->model, sidcode_short + 0, sizeof(ident->model) - 1); - ident->dest[0] = sidcode_short[3]; - ident->spec[0] = sidcode_short[4]; - ident->rev[0] = sidcode_short[5]; - memcpy(ident->ext, sidcode_short + 6, sizeof(ident->ext)); - - /* Set up long-form sidcode env var again */ - - str_format( - sidcode_long, - lengthof(sidcode_long), - "%s:%s:%s:%s:%s", - ident->model, - ident->dest, - ident->spec, - ident->rev, - ident->ext); - - std_setenv("/env/profile/soft_id_code", sidcode_long); - - return true; -} - -void ea3_ident_to_property( - const struct ea3_ident *ident, struct property *ea3_config) -{ - struct property_node *node; - int i; - - for (i = 0; ea3_ident_psmap[i].type != 0xFF; i++) { - node = property_search(ea3_config, 0, ea3_ident_psmap[i].path); - - if (node != NULL) { - property_node_remove(node); - } - } - - property_psmap_export(ea3_config, NULL, ident, ea3_ident_psmap); -} - -void ea3_ident_replace_property_bool( - struct property_node *node, const char *name, uint8_t val) -{ - struct property_node *tmp; - - tmp = property_search(NULL, node, name); - - if (tmp) { - property_node_remove(tmp); - } - - property_node_create(NULL, node, PROPERTY_TYPE_BOOL, name, val); -} - -void ea3_ident_replace_property_str( - struct property_node *node, const char *name, const char *val) -{ - struct property_node *tmp; - - tmp = property_search(NULL, node, name); - - if (tmp) { - property_node_remove(tmp); - } - - tmp = property_node_create(NULL, node, PROPERTY_TYPE_STR, name, val); -} diff --git a/src/main/launcher/ea3-config.h b/src/main/launcher/ea3-config.h deleted file mode 100644 index ada44008..00000000 --- a/src/main/launcher/ea3-config.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef LAUNCHER_EA3_CONFIG_H -#define LAUNCHER_EA3_CONFIG_H - -#include "imports/avs.h" - -#include "launcher/module.h" - -/* N.B. even though this might look like a Konami ABI, this is purely an - internal data structure. */ - -struct ea3_ident { - /* psmapped structure offset can't be zero for some stupid reason */ - - uint32_t dummy; - - /* Initialized from ea3-config.xml, then fed back from sidcode_short */ - - char model[4]; - char dest[4]; - char spec[4]; - char rev[4]; - char ext[11]; - - /* Initialized from ea3-config.xml (hardware_id defaults to MAC addr) */ - - char softid[24]; - char hardid[24]; - char pcbid[24]; -}; - -void ea3_ident_init(struct ea3_ident *ident); -bool ea3_ident_from_property( - struct ea3_ident *ident, struct property *ea3_config); -void ea3_ident_hardid_from_ethernet(struct ea3_ident *ident); -bool ea3_ident_invoke_module_init( - struct ea3_ident *ident, - const struct module_context *module, - struct property_node *app_config); -void ea3_ident_to_property( - const struct ea3_ident *ident, struct property *ea3_config); -void ea3_ident_replace_property_bool( - struct property_node *node, const char *name, uint8_t val); -void ea3_ident_replace_property_str( - struct property_node *node, const char *name, const char *val); - -#endif diff --git a/src/main/launcher/ea3-ident-config.c b/src/main/launcher/ea3-ident-config.c new file mode 100644 index 00000000..e8c5c4ff --- /dev/null +++ b/src/main/launcher/ea3-ident-config.c @@ -0,0 +1,104 @@ +#define LOG_MODULE "ea3-ident-config" + +#include + +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/ea3-ident-config.h" +#include "launcher/property-util.h" + +#include "util/defs.h" +#include "util/hex.h" +#include "util/str.h" + +#define ROOT_NODE "/ea3_conf" + +PSMAP_BEGIN(ea3_ident_config_psmap) +PSMAP_OPTIONAL( + PSMAP_TYPE_STR, struct ea3_ident_config, softid, "/id/softid", "") +PSMAP_OPTIONAL( + PSMAP_TYPE_STR, struct ea3_ident_config, hardid, "/id/hardid", "") +PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident_config, pcbid, "/id/pcbid", "") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident_config, model, "/soft/model") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident_config, dest, "/soft/dest") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident_config, spec, "/soft/spec") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident_config, rev, "/soft/rev") +PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident_config, ext, "/soft/ext") +PSMAP_END + +void ea3_ident_config_init(struct ea3_ident_config *config) +{ + memset(config, 0, sizeof(*config)); +} + +void ea3_ident_config_from_file_load( + const char *path, struct ea3_ident_config *config) +{ + struct property *property; + + log_assert(path); + log_assert(config); + + log_info("Loading from file path: %s", path); + + property = property_util_load(path); + + ea3_ident_config_load(property, config); + + property_util_free(property); +} + +void ea3_ident_config_load( + struct property *property, struct ea3_ident_config *config) +{ + struct property_node *node; + + log_assert(property); + log_assert(config); + + node = property_search(property, NULL, ROOT_NODE); + + if (node == NULL) { + log_fatal("Root node '" ROOT_NODE "' missing"); + } + + if (!property_psmap_import( + property, node, config, ea3_ident_config_psmap)) { + log_fatal("Error reading config file"); + } +} + +bool ea3_ident_config_hardid_is_defined(struct ea3_ident_config *config) +{ + log_assert(config); + + return strlen(config->hardid) > 0; +} + +void ea3_ident_config_hardid_from_ethernet_set(struct ea3_ident_config *config) +{ + struct avs_net_interface netif; + int result; + + log_assert(config); + + result = avs_net_ctrl(1, &netif, sizeof(netif)); + + if (result < 0) { + log_fatal( + "avs_net_ctrl call to get MAC address returned error: %d", result); + } + + config->hardid[0] = '0'; + config->hardid[1] = '1'; + config->hardid[2] = '0'; + config->hardid[3] = '0'; + + hex_encode_uc( + netif.mac_addr, + sizeof(netif.mac_addr), + config->hardid + 4, + sizeof(config->hardid) - 4); +} \ No newline at end of file diff --git a/src/main/launcher/ea3-ident-config.h b/src/main/launcher/ea3-ident-config.h new file mode 100644 index 00000000..1a6dc7f2 --- /dev/null +++ b/src/main/launcher/ea3-ident-config.h @@ -0,0 +1,37 @@ +#ifndef LAUNCHER_EA3_IDENT_CONFIG_H +#define LAUNCHER_EA3_IDENT_CONFIG_H + +#include "imports/avs.h" + +/* N.B. even though this might look like a Konami ABI, this is purely an + internal data structure. */ + +struct ea3_ident_config { + /* psmapped structure offset can't be zero for some stupid reason */ + + uint32_t dummy; + + /* Initialized from ea3-config.xml, then fed back from sidcode_short */ + + char model[4]; + char dest[4]; + char spec[4]; + char rev[4]; + char ext[11]; + + /* Initialized from ea3-config.xml (hardware_id defaults to MAC addr) */ + + char softid[24]; + char hardid[24]; + char pcbid[24]; +}; + +void ea3_ident_config_init(struct ea3_ident_config *config); +void ea3_ident_config_from_file_load( + const char *path, struct ea3_ident_config *config); +void ea3_ident_config_load( + struct property *property, struct ea3_ident_config *config); +bool ea3_ident_config_hardid_is_defined(struct ea3_ident_config *config); +void ea3_ident_config_hardid_from_ethernet_set(struct ea3_ident_config *config); + +#endif diff --git a/src/main/launcher/eamuse-config.c b/src/main/launcher/eamuse-config.c new file mode 100644 index 00000000..020e17a9 --- /dev/null +++ b/src/main/launcher/eamuse-config.c @@ -0,0 +1,125 @@ +#define LOG_MODULE "eamuse-config" + +#include + +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/ea3-ident-config.h" +#include "launcher/eamuse-config.h" +#include "launcher/property-util.h" + +#define EAMUSE_CONFIG_ROOT_NODE "/ea3" + +struct property *eamuse_config_avs_load(const char *path) +{ + struct property *property; + + log_assert(path); + + log_misc("Loading from avs path: %s", path); + + property = property_util_avs_fs_load(path); + + // Check if root node exists, call already errors if not + eamuse_config_root_get(property); + + return property; +} + +struct property_node *eamuse_config_root_get(struct property *property) +{ + struct property_node *node; + + log_assert(property); + + node = property_search(property, 0, EAMUSE_CONFIG_ROOT_NODE); + + if (node == NULL) { + log_fatal("Root node " EAMUSE_CONFIG_ROOT_NODE + " in eamuse config missing"); + } + + return node; +} + +void eamuse_config_id_softid_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "id/softid", value); +} + +void eamuse_config_id_hardid_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "id/hardid", value); +} + +void eamuse_config_id_pcbid_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "id/pcbid", value); +} + +void eamuse_config_soft_model_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "soft/model", value); +} + +void eamuse_config_soft_dest_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "soft/dest", value); +} + +void eamuse_config_soft_spec_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "soft/spec", value); +} + +void eamuse_config_soft_rev_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "soft/rev", value); +} + +void eamuse_config_soft_ext_set(struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "soft/ext", value); +} + +void eamuse_config_network_url_slash_set(struct property_node *node, bool value) +{ + log_assert(node); + + property_util_node_bool_replace(NULL, node, "network/url_slash", value); +} + +void eamuse_config_network_service_url_set( + struct property_node *node, const char *value) +{ + log_assert(node); + log_assert(value); + + property_util_node_str_replace(NULL, node, "network/services", value); +} \ No newline at end of file diff --git a/src/main/launcher/eamuse-config.h b/src/main/launcher/eamuse-config.h new file mode 100644 index 00000000..2cba227a --- /dev/null +++ b/src/main/launcher/eamuse-config.h @@ -0,0 +1,23 @@ +#ifndef LAUNCHER_EAMUSE_CONFIG_H +#define LAUNCHER_EAMUSE_CONFIG_H + +#include "imports/avs.h" + +struct property *eamuse_config_avs_load(const char *path); +struct property_node *eamuse_config_root_get(struct property *property); + +void eamuse_config_id_softid_set(struct property_node *node, const char *value); +void eamuse_config_id_hardid_set(struct property_node *node, const char *value); +void eamuse_config_id_pcbid_set(struct property_node *node, const char *value); +void eamuse_config_soft_model_set( + struct property_node *node, const char *value); +void eamuse_config_soft_dest_set(struct property_node *node, const char *value); +void eamuse_config_soft_spec_set(struct property_node *node, const char *value); +void eamuse_config_soft_rev_set(struct property_node *node, const char *value); +void eamuse_config_soft_ext_set(struct property_node *node, const char *value); +void eamuse_config_network_url_slash_set( + struct property_node *node, bool value); +void eamuse_config_network_service_url_set( + struct property_node *node, const char *value); + +#endif \ No newline at end of file diff --git a/src/main/launcher/eamuse.c b/src/main/launcher/eamuse.c new file mode 100644 index 00000000..39e560d7 --- /dev/null +++ b/src/main/launcher/eamuse.c @@ -0,0 +1,25 @@ +#define LOG_MODULE "eamuse" + +#include "core/log.h" + +#include "imports/avs-ea3.h" + +void eamuse_init(struct property_node *node) +{ + log_assert(node); + + log_info("init"); + + ea3_boot(node); + + log_misc("init done"); +} + +void eamuse_fini() +{ + log_info("fini"); + + ea3_shutdown(); + + log_misc("fini done"); +} \ No newline at end of file diff --git a/src/main/launcher/eamuse.h b/src/main/launcher/eamuse.h new file mode 100644 index 00000000..1cd1f7e7 --- /dev/null +++ b/src/main/launcher/eamuse.h @@ -0,0 +1,9 @@ +#ifndef LAUNCHER_EAMUSE_H +#define LAUNCHER_EAMUSE_H + +#include "imports/avs.h" + +void eamuse_init(struct property_node *node); +void eamuse_fini(); + +#endif \ No newline at end of file diff --git a/src/main/launcher/hook.c b/src/main/launcher/hook.c new file mode 100644 index 00000000..d7be37a7 --- /dev/null +++ b/src/main/launcher/hook.c @@ -0,0 +1,34 @@ +#define LOG_MODULE "hook" + +#include + +#include "core/log.h" + +#include "launcher/hook.h" + +void hook_load_dll(const char *path) +{ + log_assert(path); + + log_info("Load hook dll: %s", path); + + if (LoadLibraryA(path) == NULL) { + LPSTR buffer; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR) &buffer, + 0, + NULL); + + log_fatal("%s: Failed to load hook DLL: %s", path, buffer); + + LocalFree(buffer); + } + + log_misc("Load hook dll done"); +} \ No newline at end of file diff --git a/src/main/launcher/hook.h b/src/main/launcher/hook.h new file mode 100644 index 00000000..9d0c5d42 --- /dev/null +++ b/src/main/launcher/hook.h @@ -0,0 +1,6 @@ +#ifndef LAUNCHER_HOOK_H +#define LAUNCHER_HOOK_H + +void hook_load_dll(const char *path); + +#endif \ No newline at end of file diff --git a/src/main/launcher/launcher-config.c b/src/main/launcher/launcher-config.c new file mode 100644 index 00000000..c71abe56 --- /dev/null +++ b/src/main/launcher/launcher-config.c @@ -0,0 +1,371 @@ +#define LOG_MODULE "launcher-config" + +#include + +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/launcher-config.h" +#include "launcher/property-util.h" + +#include "util/mem.h" +#include "util/str.h" + +// clang-format off +PSMAP_BEGIN(launcher_debug_psmap) +PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct launcher_debug_config, remote_debugger, + "debug/remote_debugger", false) + PSMAP_OPTIONAL(PSMAP_TYPE_BOOL, struct launcher_debug_config, log_property_configs, + "debug/log_property_configs", false) +PSMAP_END +// clang-format on + +#define ROOT_NODE "/launcher" +#define MAX_LAYER_CONFIG_NODES 8 + +#define NODE_MISSING_FATAL(subnode) \ + log_fatal("%s/%s: Node missing", ROOT_NODE, subnode); +#define NODE_LOADING_FATAL(subnode) \ + log_fatal("%s/%s: Node loading", ROOT_NODE, subnode); + +static struct property * +_launcher_config_layered_config_nodes_load(struct property_node *node) +{ + char kind[64]; + char file[MAX_PATH]; + int res; + int cnt; + + struct property_node *cur; + struct property *config_property[MAX_LAYER_CONFIG_NODES]; + struct property *merged_property; + + log_assert(node); + + cnt = 0; + cur = property_search(NULL, node, "config"); + + while (cur) { + if (cnt >= MAX_LAYER_CONFIG_NODES) { + log_fatal( + "Exceeding max supported config nodes for layering, max is %d", + MAX_LAYER_CONFIG_NODES); + } + + res = property_node_refer( + NULL, cur, "kind@", PROPERTY_TYPE_ATTR, kind, sizeof(kind)); + + if (res < 0) { + log_fatal("Failed reading 'kind' attribute value of config node"); + } + + if (!strcmp(kind, "file")) { + property_node_read(cur, PROPERTY_TYPE_STR, file, sizeof(file)); + + config_property[cnt] = property_util_load(file); + } else if (!strcmp(kind, "inline")) { + // The nested child is the actual root of the inline, not the outer + // node + cur = property_node_traversal(cur, TRAVERSE_FIRST_CHILD); + + config_property[cnt] = property_util_node_extract(cur); + } else { + log_fatal( + "Unsupported 'kind' attribute value '%s' of config node", kind); + } + + cnt++; + cur = property_node_traversal(cur, TRAVERSE_NEXT_SEARCH_RESULT); + } + + if (cnt == 0) { + return NULL; + } + + merged_property = property_util_many_merge(config_property, cnt); + + for (int i = 0; i < cnt; i++) { + property_util_free(config_property[i]); + } + + return merged_property; +} + +static void _launcher_config_hook_dlls_parse( + struct property_node *node, + const char *node_path, + char dlls[LAUNCHER_CONFIG_MAX_HOOK_DLL][MAX_PATH]) +{ + int cnt; + struct property_node *cur; + + cnt = 0; + cur = property_search(NULL, node, node_path); + + while (cur) { + if (cnt >= LAUNCHER_CONFIG_MAX_HOOK_DLL) { + log_warning( + "Currently not supporting more than %d dlls, skipping " + "remaining", + cnt); + break; + } + + property_node_read(cur, PROPERTY_TYPE_STR, dlls[cnt], MAX_PATH); + + cnt++; + cur = property_node_traversal(cur, TRAVERSE_NEXT_SEARCH_RESULT); + } +} + +static void _launcher_config_bootstrap_load( + struct property_node *node, struct launcher_bootstrap_config *config) +{ + int res; + + log_assert(node); + log_assert(config); + + res = property_node_refer( + NULL, + node, + "selector", + PROPERTY_TYPE_STR, + config->selector, + sizeof(config->selector)); + + if (res < 0) { + NODE_MISSING_FATAL("bootstrap/selector"); + } + + config->property = _launcher_config_layered_config_nodes_load(node); + + if (config->property == NULL) { + NODE_MISSING_FATAL("bootstrap/config"); + } +} + +static void _launcher_config_hook_load( + struct property_node *node, struct launcher_hook_config *config) +{ + log_assert(node); + log_assert(config); + + _launcher_config_hook_dlls_parse(node, "hook_dlls/dll", config->hook_dlls); + _launcher_config_hook_dlls_parse( + node, "before_hook_dlls/dll", config->before_hook_dlls); + _launcher_config_hook_dlls_parse( + node, "iat_hook_dlls/dll", config->iat_hook_dlls); +} + +static void _launcher_config_debug_load( + struct property_node *node, struct launcher_debug_config *config) +{ + log_assert(node); + log_assert(config); + + if (!property_psmap_import(NULL, node, config, launcher_debug_psmap)) { + NODE_LOADING_FATAL("debug"); + } +} + +void launcher_config_init(struct launcher_config *config) +{ + log_assert(config); + + memset(config->bootstrap.selector, 0, sizeof(config->bootstrap.selector)); + config->bootstrap.property = NULL; + + config->avs.property = NULL; + + config->ea3_ident.property = NULL; + + config->eamuse.property = NULL; + + memset(config->hook.hook_dlls, 0, sizeof(config->hook.hook_dlls)); + memset( + config->hook.before_hook_dlls, + 0, + sizeof(config->hook.before_hook_dlls)); + memset(config->hook.iat_hook_dlls, 0, sizeof(config->hook.iat_hook_dlls)); + + config->debug.remote_debugger = false; + config->debug.log_property_configs = false; +} + +void launcher_config_load( + struct property *property, struct launcher_config *config) +{ + struct property_node *root_node; + struct property_node *node; + avs_error error; + + log_assert(property); + log_assert(config); + + root_node = property_search(property, NULL, ROOT_NODE); + + if (root_node == NULL) { + NODE_MISSING_FATAL(""); + } + + error = property_node_refer( + NULL, + root_node, + "version@", + PROPERTY_TYPE_ATTR, + &config->version, + sizeof(uint32_t)); + + if (AVS_IS_ERROR(error)) { + log_fatal("Missing version attribute on root node"); + } + + // if (config->version != 1) { + // log_fatal("Unsupported version of launcher configuration: %d", + // config->version); + // } + + node = property_search(NULL, root_node, "bootstrap"); + + if (node == NULL) { + NODE_MISSING_FATAL("bootstrap"); + } + + _launcher_config_bootstrap_load(node, &config->bootstrap); + + node = property_search(NULL, root_node, "avs"); + + if (node) { + config->avs.property = _launcher_config_layered_config_nodes_load(node); + } + + node = property_search(NULL, root_node, "ea3_ident"); + + if (node) { + config->ea3_ident.property = + _launcher_config_layered_config_nodes_load(node); + } + + node = property_search(NULL, root_node, "eamuse"); + + if (node) { + config->eamuse.property = + _launcher_config_layered_config_nodes_load(node); + } + + node = property_search(NULL, root_node, "hook"); + + if (node) { + _launcher_config_hook_load(node, &config->hook); + } + + _launcher_config_debug_load(root_node, &config->debug); +} + +bool launcher_config_add_hook_dll( + struct launcher_config *config, const char *path) +{ + int i; + + log_assert(config); + log_assert(path); + + i = 0; + + while (i < LAUNCHER_CONFIG_MAX_HOOK_DLL) { + if (strlen(config->hook.hook_dlls[i]) == 0) { + break; + } + + i++; + } + + if (i >= LAUNCHER_CONFIG_MAX_HOOK_DLL) { + return false; + } + + str_cpy(config->hook.hook_dlls[i], sizeof(config->hook.hook_dlls[i]), path); + + return true; +} + +bool launcher_config_add_before_hook_dll( + struct launcher_config *config, const char *path) +{ + int i; + + log_assert(config); + log_assert(path); + + i = 0; + + while (i < LAUNCHER_CONFIG_MAX_HOOK_DLL) { + if (strlen(config->hook.before_hook_dlls[i]) == 0) { + break; + } + + i++; + } + + if (i >= LAUNCHER_CONFIG_MAX_HOOK_DLL) { + return false; + } + + str_cpy( + config->hook.before_hook_dlls[i], + sizeof(config->hook.before_hook_dlls[i]), + path); + + return true; +} + +bool launcher_config_add_iat_hook_dll( + struct launcher_config *config, const char *path) +{ + int i; + + log_assert(config); + log_assert(path); + + i = 0; + + while (i < LAUNCHER_CONFIG_MAX_HOOK_DLL) { + if (strlen(config->hook.iat_hook_dlls[i]) == 0) { + break; + } + + i++; + } + + if (i >= LAUNCHER_CONFIG_MAX_HOOK_DLL) { + return false; + } + + str_cpy( + config->hook.iat_hook_dlls[i], + sizeof(config->hook.iat_hook_dlls[i]), + path); + + return true; +} + +void launcher_config_fini(struct launcher_config *config) +{ + log_assert(config); + + property_util_free(config->bootstrap.property); + + if (config->avs.property) { + property_util_free(config->avs.property); + } + + if (config->ea3_ident.property) { + property_util_free(config->ea3_ident.property); + } + + if (config->eamuse.property) { + property_util_free(config->eamuse.property); + } +} \ No newline at end of file diff --git a/src/main/launcher/launcher-config.h b/src/main/launcher/launcher-config.h new file mode 100644 index 00000000..7d6b8a6e --- /dev/null +++ b/src/main/launcher/launcher-config.h @@ -0,0 +1,56 @@ +#ifndef LAUNCHER_CONFIG_H +#define LAUNCHER_CONFIG_H + +#include + +#include "util/array.h" + +#define LAUNCHER_CONFIG_MAX_HOOK_DLL 16 + +struct launcher_config { + uint32_t version; + + struct launcher_bootstrap_config { + char selector[128]; + struct property *property; + } bootstrap; + + struct launcher_avs_config { + struct property *property; + } avs; + + struct launcher_ea3_ident_config { + struct property *property; + } ea3_ident; + + struct launcher_eamuse_config { + struct property *property; + } eamuse; + + struct launcher_hook_config { + char hook_dlls[LAUNCHER_CONFIG_MAX_HOOK_DLL][MAX_PATH]; + char before_hook_dlls[LAUNCHER_CONFIG_MAX_HOOK_DLL][MAX_PATH]; + char iat_hook_dlls[LAUNCHER_CONFIG_MAX_HOOK_DLL][MAX_PATH]; + } hook; + + struct launcher_debug_config { + bool remote_debugger; + bool log_property_configs; + } debug; +}; + +void launcher_config_init(struct launcher_config *config); + +void launcher_config_load( + struct property *property, struct launcher_config *config); + +bool launcher_config_add_hook_dll( + struct launcher_config *config, const char *path); +bool launcher_config_add_before_hook_dll( + struct launcher_config *config, const char *path); +bool launcher_config_add_iat_hook_dll( + struct launcher_config *config, const char *path); + +void launcher_config_fini(struct launcher_config *config); + +#endif \ No newline at end of file diff --git a/src/main/launcher/launcher.c b/src/main/launcher/launcher.c new file mode 100644 index 00000000..60f32cf2 --- /dev/null +++ b/src/main/launcher/launcher.c @@ -0,0 +1,587 @@ +#define LOG_MODULE "launcher" + +#include + +#include +#include +#include +#include + +#include "core/log-bt-ext.h" +#include "core/log-bt.h" +#include "core/log-sink-file.h" +#include "core/log-sink-list.h" +#include "core/log-sink-std.h" +#include "core/log.h" + +#include "imports/avs-ea3.h" +#include "imports/avs.h" + +#include "launcher/avs-config.h" +#include "launcher/avs.h" +#include "launcher/bootstrap-config.h" +#include "launcher/bootstrap.h" +#include "launcher/debug.h" +#include "launcher/ea3-ident-config.h" +#include "launcher/eamuse-config.h" +#include "launcher/eamuse.h" +#include "launcher/hook.h" +#include "launcher/launcher-config.h" +#include "launcher/module.h" +#include "launcher/options.h" +#include "launcher/property-util.h" +#include "launcher/stubs.h" +#include "launcher/version.h" + +#include "util/defs.h" +#include "util/fs.h" +#include "util/os.h" +#include "util/proc.h" +#include "util/signal.h" +#include "util/str.h" + +static void _launcher_log_header() +{ + log_info( + "\n" + " .__ .__ \n" + " | | _____ __ __ ____ ____ | |__ ___________ \n" + " | | \\__ \\ | | \\/ \\_/ ___\\| | \\_/ __ \\_ __ \\ \n" + " | |__/ __ \\| | / | \\ \\___| Y \\ ___/| | \\/ \n" + " |____(____ /____/|___| /\\___ >___| /\\___ >__| \n" + " \\/ \\/ \\/ \\/ \\/ "); + + log_info( + "launcher build date %s, gitrev %s", + launcher_build_date, + launcher_gitrev); +} + +void _launcher_log_init( + const char *log_file_path, enum core_log_bt_log_level level) +{ + struct core_log_sink sinks[2]; + struct core_log_sink sink_composed; + + core_log_bt_ext_impl_set(); + + if (log_file_path) { + core_log_sink_std_out_open(true, &sinks[0]); + core_log_sink_file_open(log_file_path, false, true, 10, &sinks[1]); + core_log_sink_list_open(sinks, 2, &sink_composed); + } else { + core_log_sink_std_out_open(true, &sink_composed); + } + + core_log_bt_init(&sink_composed); + core_log_bt_level_set(level); +} + +static void _launcher_signal_shutdown_handler() +{ + core_log_bt_fini(); + ExitProcess(EXIT_FAILURE); +} + +static void _launcher_env_game_dir_verify() +{ + char cwd[MAX_PATH]; + char modules_dir[MAX_PATH]; + char prop_dir[MAX_PATH]; + + getcwd(cwd, sizeof(cwd)); + + log_info("Current working directory: %s", cwd); + + str_cpy(modules_dir, sizeof(modules_dir), cwd); + str_cpy(prop_dir, sizeof(prop_dir), cwd); + + str_cat(modules_dir, sizeof(modules_dir), "/modules"); + str_cat(prop_dir, sizeof(prop_dir), "/prop"); + + if (!path_exists(modules_dir)) { + log_fatal( + "Cannot find 'modules' directory in current working directory: %s", + cwd); + } + + if (!path_exists(prop_dir)) { + log_fatal( + "Cannot find 'prop' directory in current working directory: %s", + cwd); + } +} + +static void _launcher_bootstrap_config_options_override( + struct launcher_bootstrap_config *config, + const struct options_bootstrap *options) +{ + log_assert(config); + log_assert(options); + + if (options->config_path) { + log_misc( + "Command line override bootstrap configuration from file: %s", + options->config_path); + + property_util_free(config->property); + config->property = property_util_load(options->config_path); + } + + if (options->selector) { + log_misc( + "Command line override bootstrap selector: %s", options->selector); + + str_cpy(config->selector, sizeof(config->selector), options->selector); + } +} + +static void _launcher_ea3_ident_config_options_override( + struct ea3_ident_config *config, const struct options_eamuse *options) +{ + log_assert(config); + log_assert(options); + + if (options->softid) { + str_cpy(config->softid, sizeof(config->softid), options->softid); + } + + if (options->pcbid) { + str_cpy(config->pcbid, sizeof(config->pcbid), options->pcbid); + } +} + +static void _launcher_hook_config_options_override( + struct launcher_config *config, const struct options_hook *options) +{ + size_t i; + const char *dll; + + log_assert(config); + log_assert(options); + + for (i = 0; i < options->hook_dlls.nitems; i++) { + dll = *array_item(const char *, &options->hook_dlls, i); + + if (!launcher_config_add_hook_dll(config, dll)) { + log_warning( + "Adding override hook dll '%s' failed (max supported limit " + "exceeded), ignored", + dll); + } + } + + for (i = 0; i < options->before_hook_dlls.nitems; i++) { + dll = *array_item(const char *, &options->before_hook_dlls, i); + + if (!launcher_config_add_before_hook_dll(config, dll)) { + log_warning( + "Adding override before hook dll '%s' failed (max supported " + "limit exceeded), ignored", + dll); + } + } + + for (i = 0; i < options->iat_hook_dlls.nitems; i++) { + dll = *array_item(const char *, &options->iat_hook_dlls, i); + + if (!launcher_config_add_iat_hook_dll(config, dll)) { + log_warning( + "Adding override iat hook dll '%s' failed (max supported limit " + "exceeded), ignored", + dll); + } + } +} + +static void _launcher_debug_config_options_override( + struct launcher_debug_config *config, const struct options_debug *options) +{ + log_assert(config); + log_assert(options); + + if (options->remote_debugger) { + log_misc("Command line override, enable remote debugger"); + + config->remote_debugger = true; + } + + if (options->log_property_configs) { + log_misc("Command line override, log property configs"); + + config->log_property_configs = true; + } +} + +static void _launcher_config_options_override( + struct launcher_config *config, const struct options *options) +{ + log_assert(config); + log_assert(options); + + // Apply command line overrides on all launcher owned configuration + // parameters + _launcher_bootstrap_config_options_override( + &config->bootstrap, &options->bootstrap); + _launcher_hook_config_options_override(config, &options->hook); + _launcher_debug_config_options_override(&config->debug, &options->debug); +} + +static void +_launcher_config_full_resolved_log(const struct launcher_config *config) +{ + if (config->debug.log_property_configs) { + log_misc("launcher-config resolved properties"); + log_misc("bootstrap-config"); + property_util_log(config->bootstrap.property); + + log_misc("avs-config"); + property_util_log(config->avs.property); + + log_misc("ea3-ident-config"); + property_util_log(config->ea3_ident.property); + + log_misc("eamuse-config"); + property_util_log(config->eamuse.property); + } +} + +static void +_launcher_remote_debugger_trap(const struct launcher_debug_config *config) +{ + log_assert(config); + + /* If enabled, wait for a remote debugger to attach as early as possible. + Spawning launcher with a debugger crashes it for some reason + (e.g. on jubeat08). However, starting the launcher separately and + attaching a remote debugger works */ + + if (config->remote_debugger) { + debug_remote_debugger_trap(); + } +} + +static void _launcher_bootstrap_config_load( + const struct launcher_bootstrap_config *launcher_bootstrap_config, + struct bootstrap_config *config) +{ + bootstrap_config_init(config); + + bootstrap_config_load( + launcher_bootstrap_config->property, + launcher_bootstrap_config->selector, + config); +} + +static void _launcher_bootstrap_log_config_options_override( + struct bootstrap_log_config *config, const struct options_log *options) +{ + log_assert(config); + log_assert(options); + + if (options->level) { + log_misc( + "Command line override bootstrap log level: %d", *(options->level)); + + switch (*(options->level)) { + case CORE_LOG_BT_LOG_LEVEL_OFF: + str_cpy(config->level, sizeof(config->level), "disable"); + break; + + case CORE_LOG_BT_LOG_LEVEL_FATAL: + str_cpy(config->level, sizeof(config->level), "fatal"); + break; + + case CORE_LOG_BT_LOG_LEVEL_WARNING: + str_cpy(config->level, sizeof(config->level), "warn"); + break; + + case CORE_LOG_BT_LOG_LEVEL_INFO: + str_cpy(config->level, sizeof(config->level), "info"); + break; + + case CORE_LOG_BT_LOG_LEVEL_MISC: + str_cpy(config->level, sizeof(config->level), "misc"); + break; + + default: + log_assert(false); + } + } + + if (options->file_path) { + log_misc( + "Command line override bootstrap log file: %s", options->file_path); + str_cpy(config->file, sizeof(config->file), options->file_path); + } +} + +static void _launcher_bootstrap_log_config_verify( + const struct launcher_config *launcher_config, + const struct bootstrap_config *bootstrap_config) +{ + log_assert(launcher_config); + log_assert(bootstrap_config); + + if (!str_eq(bootstrap_config->startup.log.level, "misc")) { + if (launcher_config->debug.log_property_configs) { + log_warning( + "Logging of property configs enabled, but requires misc log " + "level, current log level: %s", + bootstrap_config->startup.log.level); + } + } +} + +static void +_launcher_before_hook_dlls_load(const struct launcher_hook_config *config) +{ + int i; + + log_assert(config); + + log_misc("Loading before hook dlls..."); + + for (i = 0; i < LAUNCHER_CONFIG_MAX_HOOK_DLL; i++) { + if (strlen(config->before_hook_dlls[i]) > 0) { + hook_load_dll(config->before_hook_dlls[i]); + } + } +} + +static void _launcher_ea3_ident_config_load( + const struct launcher_ea3_ident_config *launcher_config, + struct ea3_ident_config *config, + bool log_property_configs) +{ + log_assert(launcher_config); + log_assert(config); + + ea3_ident_config_init(config); + ea3_ident_config_load(launcher_config->property, config); + + if (log_property_configs) { + log_misc("Property ea3-ident-config"); + + property_util_log(launcher_config->property); + } + + if (!ea3_ident_config_hardid_is_defined(config)) { + log_misc( + "No no hardid defined in ea3-ident-config, derive from ethernet"); + + ea3_ident_config_hardid_from_ethernet_set(config); + } +} + +static void _launcher_bootstrap_module_init( + const struct bootstrap_module_config *module_config, + const struct launcher_hook_config *hook_config) +{ + int i; + struct array iat_hook_dlls; + + log_assert(module_config); + log_assert(hook_config); + + array_init(&iat_hook_dlls); + + for (i = 0; i < LAUNCHER_CONFIG_MAX_HOOK_DLL; i++) { + if (strlen(hook_config->before_hook_dlls[i]) > 0) { + *array_append(const char *, &iat_hook_dlls) = + (const char *) &hook_config->before_hook_dlls[i]; + } + } + + bootstrap_module_init(module_config, &iat_hook_dlls); + + array_fini(&iat_hook_dlls); +} + +static void _launcher_hook_dlls_load(const struct launcher_hook_config *config) +{ + int i; + + log_assert(config); + + log_misc("Loading hook dlls..."); + + for (i = 0; i < LAUNCHER_CONFIG_MAX_HOOK_DLL; i++) { + if (strlen(config->hook_dlls[i]) > 0) { + hook_load_dll(config->hook_dlls[i]); + } + } +} + +static void _launcher_dongle_stubs_init() +{ + stubs_init(); +} + +static void _launcher_debugger_break() +{ + /* Opportunity for breakpoint setup etc */ + if (IsDebuggerPresent()) { + DebugBreak(); + } +} + +void _launcher_log_reinit() +{ + core_log_bt_ext_impl_set(); +} + +void _launcher_init( + const struct options *options, + struct launcher_config *launcher_config, + struct bootstrap_config *bootstrap_config, + struct ea3_ident_config *ea3_ident_config) +{ + struct property *launcher_property; + + log_assert(options); + log_assert(launcher_config); + log_assert(bootstrap_config); + log_assert(ea3_ident_config); + + // Early logging pre AVS setup depend entirely on command args + // We don't even have the bootstrap configuration loaded at this point + _launcher_log_init(options->log.file_path, *(options->log.level)); + _launcher_log_header(); + + signal_exception_handler_init(_launcher_signal_shutdown_handler); + signal_register_shutdown_handler(&_launcher_signal_shutdown_handler); + + os_version_log(); + _launcher_env_game_dir_verify(); + + if (proc_is_running_as_admin_user()) { + log_warning( + "Not running as admin user. Launcher and games require elevated " + "privileges to run correctly"); + } + + launcher_config_init(launcher_config); + + if (options->launcher.config_path) { + log_info( + "Loading launcher configuration from file: %s", + options->launcher.config_path); + + launcher_property = property_util_load(options->launcher.config_path); + launcher_config_load(launcher_property, launcher_config); + + _launcher_config_options_override(launcher_config, options); + + if (launcher_config->debug.log_property_configs) { + log_misc("launcher-config"); + property_util_log(launcher_property); + } + + property_util_free(launcher_property); + } else { + _launcher_config_options_override(launcher_config, options); + } + + // Not really fully resolved, but have an early debug dump because there are + // still several more steps that can fail before having the entire + // configuration resolved + _launcher_config_full_resolved_log(launcher_config); + + _launcher_remote_debugger_trap(&launcher_config->debug); + + _launcher_bootstrap_config_load( + &launcher_config->bootstrap, bootstrap_config); + _launcher_bootstrap_log_config_options_override( + &bootstrap_config->startup.log, &options->log); + _launcher_bootstrap_log_config_verify(launcher_config, bootstrap_config); + + bootstrap_init(launcher_config->debug.log_property_configs); + bootstrap_log_init(&bootstrap_config->startup.log); + + _launcher_before_hook_dlls_load(&launcher_config->hook); + + bootstrap_avs_init( + &bootstrap_config->startup.boot, + &bootstrap_config->startup.log, + launcher_config->avs.property); + bootstrap_default_files_create(&bootstrap_config->startup.default_file); + + _launcher_ea3_ident_config_load( + &launcher_config->ea3_ident, + ea3_ident_config, + launcher_config->debug.log_property_configs); + _launcher_ea3_ident_config_options_override( + ea3_ident_config, &options->eamuse); + + // Execute another one which is now actually final. No more configuration + // changes from this point on + _launcher_config_full_resolved_log(launcher_config); +} + +void _launcher_run( + const struct launcher_config *launcher_config, + const struct bootstrap_config *bootstrap_config, + struct ea3_ident_config *ea3_ident_config) +{ + log_assert(launcher_config); + log_assert(bootstrap_config); + log_assert(ea3_ident_config); + + _launcher_bootstrap_module_init( + &bootstrap_config->startup.module, &launcher_config->hook); + + _launcher_hook_dlls_load(&launcher_config->hook); + + _launcher_dongle_stubs_init(); + + _launcher_debugger_break(); + + bootstrap_module_game_init( + &bootstrap_config->startup.module, ea3_ident_config); + + bootstrap_eamuse_init( + &bootstrap_config->startup.eamuse, + ea3_ident_config, + launcher_config->eamuse.property); + + bootstrap_module_game_run(); +} + +void _launcher_fini( + struct launcher_config *launcher_config, + const struct bootstrap_config *bootstrap_config) +{ + log_assert(launcher_config); + log_assert(bootstrap_config); + + bootstrap_eamuse_fini(&bootstrap_config->startup.eamuse); + + bootstrap_avs_fini(); + + _launcher_log_reinit(); + + bootstrap_module_game_fini(); + + launcher_config_fini(launcher_config); + + log_info("Shutdown complete"); + + core_log_bt_fini(); +} + +void launcher_main(const struct options *options) +{ + struct launcher_config launcher_config; + struct bootstrap_config bootstrap_config; + struct ea3_ident_config ea3_ident_config; + + log_assert(options); + + _launcher_init( + options, &launcher_config, &bootstrap_config, &ea3_ident_config); + + _launcher_run(&launcher_config, &bootstrap_config, &ea3_ident_config); + + _launcher_fini(&launcher_config, &bootstrap_config); +} \ No newline at end of file diff --git a/src/main/launcher/launcher.h b/src/main/launcher/launcher.h new file mode 100644 index 00000000..da1995cc --- /dev/null +++ b/src/main/launcher/launcher.h @@ -0,0 +1,8 @@ +#ifndef LAUNCHER_LAUNCHER_H +#define LAUNCHER_LAUNCHER_H + +#include "launcher/options.h" + +void launcher_main(const struct options *options); + +#endif \ No newline at end of file diff --git a/src/main/launcher/main.c b/src/main/launcher/main.c index 70c5184d..4b1b68a0 100644 --- a/src/main/launcher/main.c +++ b/src/main/launcher/main.c @@ -1,338 +1,23 @@ -#include - -#include -#include -#include #include -#include "imports/avs-ea3.h" -#include "imports/avs.h" - -#include "launcher/avs-context.h" -#include "launcher/ea3-config.h" -#include "launcher/module.h" +#include "launcher/launcher.h" #include "launcher/options.h" -#include "launcher/property.h" -#include "launcher/stubs.h" -#include "launcher/version.h" - -#include "util/codepage.h" -#include "util/defs.h" -#include "util/fs.h" -#include "util/log.h" -#include "util/mem.h" -#include "util/os.h" -#include "util/str.h" - -/* Gratuitous API changes orz */ -static AVS_LOG_WRITER(log_callback, chars, nchars, ctx) -{ - wchar_t *utf16; - char *utf8; - int utf16_len; - int utf8_len; - int result; - DWORD nwritten; - HANDLE console; - HANDLE file; - - /* Ignore existing NUL terminator */ - - nchars--; - - /* Transcode shit_jis to UTF-8 */ - - utf16_len = MultiByteToWideChar(CP_SHIFT_JIS, 0, chars, nchars, NULL, 0); - - if (utf16_len == 0) { - abort(); - } - - utf16 = xmalloc(sizeof(*utf16) * utf16_len); - result = - MultiByteToWideChar(CP_SHIFT_JIS, 0, chars, nchars, utf16, utf16_len); - - if (result == 0) { - abort(); - } - - utf8_len = - WideCharToMultiByte(CP_UTF8, 0, utf16, utf16_len, NULL, 0, NULL, NULL); - - if (utf8_len == 0) { - abort(); - } - - utf8 = xmalloc(utf8_len + 2); - result = WideCharToMultiByte( - CP_UTF8, 0, utf16, utf16_len, utf8, utf8_len, NULL, NULL); - - if (result == 0) { - abort(); - } - -#if AVS_VERSION >= 1500 - utf8[utf8_len + 0] = '\r'; - utf8[utf8_len + 1] = '\n'; - - utf8_len += 2; -#endif - - /* Write to console and log file */ - - file = (HANDLE) ctx; - console = GetStdHandle(STD_OUTPUT_HANDLE); - - if (ctx != INVALID_HANDLE_VALUE) { - WriteFile(file, utf8, utf8_len, &nwritten, NULL); - } - - WriteFile(console, utf8, utf8_len, &nwritten, NULL); - - /* Clean up */ - - free(utf8); - free(utf16); -} - -static void load_hook_dlls(struct array *hook_dlls) -{ - const char *hook_dll; - - for (size_t i = 0; i < hook_dlls->nitems; i++) { - hook_dll = *array_item(char *, hook_dlls, i); - - if (LoadLibraryA(hook_dll) == NULL) { - LPSTR buffer; - - FormatMessageA( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR) &buffer, - 0, - NULL); - - log_fatal("%s: Failed to load hook DLL: %s", hook_dll, buffer); - - LocalFree(buffer); - } - } -} int main(int argc, const char **argv) { - bool ok; - HANDLE logfile; - - struct ea3_ident ea3; - struct module_context module; struct options options; - struct property *app_config; - struct property *avs_config; - struct property *ea3_config; - - struct property_node *app_config_root; - struct property_node *avs_config_root; - struct property_node *ea3_config_root; - - log_to_writer(log_writer_file, stdout); - log_info( - "launcher build date %s, gitrev %s", - launcher_build_date, - launcher_gitrev); - - /* Read command line */ - options_init(&options); if (!options_read_cmdline(&options, argc, argv)) { options_print_usage(); - return EXIT_FAILURE; - } - - /* If enabled, wait for a remote debugger to attach. Spawning launcher - with a debugger crashes it for some reason (e.g. on jubeat08). However, - starting the launcher separately and attaching a remote debugger works */ - - if (options.remote_debugger) { - log_info("Waiting until debugger attaches to remote process..."); - - while (true) { - BOOL res = FALSE; - if (!CheckRemoteDebuggerPresent(GetCurrentProcess(), &res)) { - log_fatal( - "CheckRemoteDebuggerPresent failed: %08x", - (unsigned int) GetLastError()); - } - - if (res) { - log_info("Debugger attached, resuming"); - break; - } - - Sleep(1000); - } - } - - /* Start up AVS */ - - if (options.logfile != NULL) { - logfile = CreateFileA( - options.logfile, - GENERIC_WRITE, - FILE_SHARE_READ, - NULL, - CREATE_ALWAYS, - 0, - NULL); - } else { - logfile = INVALID_HANDLE_VALUE; - } - - avs_config = boot_property_load(options.avs_config_path); - avs_config_root = property_search(avs_config, 0, "/config"); - - if (avs_config_root == NULL) { - log_fatal("%s: /config missing", options.avs_config_path); - } - - load_hook_dlls(&options.before_hook_dlls); - - avs_context_init( - avs_config_root, - options.avs_heap_size, - options.std_heap_size, - log_callback, - logfile); - - boot_property_free(avs_config); - - log_to_external( - log_body_misc, log_body_info, log_body_warning, log_body_fatal); - - os_version_log(); - - /* Load game DLL */ - - if (options.iat_hook_dlls.nitems > 0) { - module_context_init_with_iat_hooks( - &module, options.module, &options.iat_hook_dlls); - } else { - module_context_init(&module, options.module); + exit(EXIT_FAILURE); } - /* Load hook DLLs */ - - load_hook_dlls(&options.hook_dlls); - - /* Inject GetModuleHandle hooks */ - - stubs_init(); - - /* Prepare ea3 config */ - - ea3_config = boot_property_load(options.ea3_config_path); - ea3_config_root = property_search(ea3_config, 0, "/ea3"); - - if (ea3_config_root == NULL) { - log_fatal("%s: /ea3 missing", options.ea3_config_path); - } - - ea3_ident_init(&ea3); - - if (!ea3_ident_from_property(&ea3, ea3_config)) { - log_fatal( - "%s: Error reading IDs from config file", options.ea3_config_path); - } - - if (options.softid != NULL) { - str_cpy(ea3.softid, lengthof(ea3.softid), options.softid); - } - - if (options.pcbid != NULL) { - str_cpy(ea3.pcbid, lengthof(ea3.pcbid), options.pcbid); - } - - if (!ea3.hardid[0]) { - ea3_ident_hardid_from_ethernet(&ea3); - } - - /* Invoke dll_entry_init */ - - if (path_exists(options.app_config_path)) { - app_config = boot_property_load(options.app_config_path); - } else { - log_warning( - "%s: app config file missing, using empty", - options.app_config_path); - app_config = boot_property_load_cstring("dummy"); - } - - app_config_root = property_search(app_config, 0, "/param"); - - if (app_config_root == NULL) { - log_fatal("%s: /param missing", options.app_config_path); - } - - if (IsDebuggerPresent()) { - /* Opportunity for breakpoint setup etc */ - DebugBreak(); - } - - ok = ea3_ident_invoke_module_init(&ea3, &module, app_config_root); - - if (!ok) { - log_fatal("%s: dll_module_init() returned failure", options.module); - } - - boot_property_free(app_config); - - ea3_ident_to_property(&ea3, ea3_config); - - if (options.override_urlslash_enabled) { - log_info( - "Overriding url_slash to: %d", options.override_urlslash_value); - - ea3_ident_replace_property_bool( - ea3_config_root, - "/network/url_slash", - options.override_urlslash_value); - } - - if (options.override_service != NULL) { - log_info("Overriding service url to: %s", options.override_service); - - ea3_ident_replace_property_str( - ea3_config_root, "/network/services", options.override_service); - } - - /* Start up e-Amusement client */ - - ea3_boot(ea3_config_root); - boot_property_free(ea3_config); - - /* Run application */ - - module_context_invoke_main(&module); - - /* Shut down */ - - ea3_shutdown(); - - log_to_writer(log_writer_file, stdout); - avs_context_fini(); - - if (logfile != INVALID_HANDLE_VALUE) { - CloseHandle(logfile); - } + launcher_main(&options); - module_context_fini(&module); options_fini(&options); return EXIT_SUCCESS; -} +} \ No newline at end of file diff --git a/src/main/launcher/module.c b/src/main/launcher/module.c index b3beff26..c0b9ef4f 100644 --- a/src/main/launcher/module.c +++ b/src/main/launcher/module.c @@ -1,22 +1,29 @@ +#define LOG_MODULE "module" + #include +#include "core/log.h" + #include "hook/pe.h" #include "imports/avs.h" #include "imports/eapki.h" #include "launcher/module.h" +#include "launcher/property-util.h" -#include "util/log.h" #include "util/str.h" #define MM_ALLOCATION_GRANULARITY 0x10000 -static bool module_replace_dll_iat(HMODULE hModule, struct array *iat_hook_dlls) +static bool +module_replace_dll_iat(HMODULE hModule, const struct array *iat_hook_dlls) { log_assert(hModule); log_assert(iat_hook_dlls); + log_misc("replace dll iat: %p", hModule); + if (iat_hook_dlls->nitems == 0) return true; @@ -127,11 +134,13 @@ static bool module_replace_dll_iat(HMODULE hModule, struct array *iat_hook_dlls) return false; } -void module_context_init(struct module_context *module, const char *path) +void module_init(struct module_context *module, const char *path) { log_assert(module != NULL); log_assert(path != NULL); + log_info("init: %s", path); + module->dll = LoadLibrary(path); if (module->dll == NULL) { @@ -159,16 +168,20 @@ void module_context_init(struct module_context *module, const char *path) } module->path = str_dup(path); + + log_misc("init done"); } -void module_context_init_with_iat_hooks( +void module_with_iat_hooks_init( struct module_context *module, const char *path, - struct array *iat_hook_dlls) + const struct array *iat_hook_dlls) { log_assert(module != NULL); log_assert(path != NULL); + log_info("init iat hooks: %s", path); + module->dll = LoadLibraryExA(path, NULL, DONT_RESOLVE_DLL_REFERENCES); if (module->dll == NULL) { @@ -211,16 +224,71 @@ void module_context_init_with_iat_hooks( module->path = str_dup(path); } -bool module_context_invoke_init( +void module_init_invoke( const struct module_context *module, - char *sidcode, - struct property_node *app_config) + struct ea3_ident_config *ea3_ident_config, + struct property_node *app_params_node) { + char sidcode_short[17]; + char sidcode_long[21]; + char security_code[9]; dll_entry_init_t init; + bool ok; - log_assert(module != NULL); - log_assert(sidcode != NULL); - log_assert(app_config != NULL); + log_info("init invoke"); + + /* Set up security env vars */ + + str_format( + security_code, + lengthof(security_code), + "G*%s%s%s%s", + ea3_ident_config->model, + ea3_ident_config->dest, + ea3_ident_config->spec, + ea3_ident_config->rev); + + log_misc("security code: %s", security_code); + + std_setenv("/env/boot/version", "0.0.0"); + std_setenv("/env/profile/security_code", security_code); + std_setenv("/env/profile/system_id", ea3_ident_config->pcbid); + std_setenv("/env/profile/account_id", ea3_ident_config->pcbid); + std_setenv("/env/profile/license_id", ea3_ident_config->softid); + std_setenv("/env/profile/software_id", ea3_ident_config->softid); + std_setenv("/env/profile/hardware_id", ea3_ident_config->hardid); + + /* Set up the short sidcode string, let dll_entry_init mangle it */ + + str_format( + sidcode_short, + lengthof(sidcode_short), + "%s%s%s%s%s", + ea3_ident_config->model, + ea3_ident_config->dest, + ea3_ident_config->spec, + ea3_ident_config->rev, + ea3_ident_config->ext); + + log_misc("sidcode short: %s", sidcode_short); + + /* Set up long-form sidcode env var */ + + str_format( + sidcode_long, + lengthof(sidcode_long), + "%s:%s:%s:%s:%s", + ea3_ident_config->model, + ea3_ident_config->dest, + ea3_ident_config->spec, + ea3_ident_config->rev, + ea3_ident_config->ext); + + log_misc("sidecode long: %s", sidcode_long); + + /* Set this up beforehand, as certain games require it in dll_entry_init */ + + std_setenv("/env/profile/soft_id_code", sidcode_long); init = (void *) GetProcAddress(module->dll, "dll_entry_init"); @@ -229,16 +297,67 @@ bool module_context_invoke_init( "%s: dll_entry_init not found. Is this a game DLL?", module->path); } - return init(sidcode, app_config); + log_info("Invoking game init..."); + + struct property *prop = + property_util_cstring_load("p3io"); + + struct property_node *prop_node = property_search(prop, NULL, "/param"); + + property_util_log(prop); + property_util_node_log(prop_node); + + ok = init(sidcode_short, prop_node); + + if (!ok) { + log_fatal("%s: dll_module_init() returned failure", module->path); + } else { + log_info("Game init done"); + } + + /* Back-propagate sidcode, as some games modify it during init */ + + memcpy( + ea3_ident_config->model, + sidcode_short + 0, + sizeof(ea3_ident_config->model) - 1); + ea3_ident_config->dest[0] = sidcode_short[3]; + ea3_ident_config->spec[0] = sidcode_short[4]; + ea3_ident_config->rev[0] = sidcode_short[5]; + memcpy( + ea3_ident_config->ext, + sidcode_short + 6, + sizeof(ea3_ident_config->ext)); + + /* Set up long-form sidcode env var again */ + + str_format( + sidcode_long, + lengthof(sidcode_long), + "%s:%s:%s:%s:%s", + ea3_ident_config->model, + ea3_ident_config->dest, + ea3_ident_config->spec, + ea3_ident_config->rev, + ea3_ident_config->ext); + + std_setenv("/env/profile/soft_id_code", sidcode_long); + + log_misc("back-propagated sidcode long: %s", sidcode_long); + + log_misc("init invoke done"); } -bool module_context_invoke_main(const struct module_context *module) +bool module_main_invoke(const struct module_context *module) { /* GCC warns if you call a variable "main" */ dll_entry_main_t main_; + bool result; log_assert(module != NULL); + log_info("main invoke"); + main_ = (void *) GetProcAddress(module->dll, "dll_entry_main"); if (main_ == NULL) { @@ -246,11 +365,19 @@ bool module_context_invoke_main(const struct module_context *module) "%s: dll_entry_main not found. Is this a game DLL?", module->path); } - return main_(); + log_info("Invoking game's main function..."); + + result = main_(); + + log_info("Main terminated, result: %d", result); + + return result; } -void module_context_fini(struct module_context *module) +void module_fini(struct module_context *module) { + log_info("fini"); + if (module == NULL) { return; } @@ -260,4 +387,6 @@ void module_context_fini(struct module_context *module) if (module->dll != NULL) { FreeLibrary(module->dll); } + + log_misc("fini done"); } diff --git a/src/main/launcher/module.h b/src/main/launcher/module.h index f2a851eb..ed9b7327 100644 --- a/src/main/launcher/module.h +++ b/src/main/launcher/module.h @@ -5,6 +5,8 @@ #include "imports/avs.h" +#include "launcher/ea3-ident-config.h" + #include "util/array.h" struct module_context { @@ -12,16 +14,16 @@ struct module_context { char *path; }; -void module_context_init(struct module_context *module, const char *path); -void module_context_init_with_iat_hooks( +void module_init(struct module_context *module, const char *path); +void module_with_iat_hooks_init( struct module_context *module, const char *path, - struct array *iat_hook_dlls); -bool module_context_invoke_init( + const struct array *iat_hook_dlls); +void module_init_invoke( const struct module_context *module, - char *sidcode, - struct property_node *app_config); -bool module_context_invoke_main(const struct module_context *module); -void module_context_fini(struct module_context *module); + struct ea3_ident_config *ea3_ident_config, + struct property_node *app_params_node); +bool module_main_invoke(const struct module_context *module); +void module_fini(struct module_context *module); #endif diff --git a/src/main/launcher/options.c b/src/main/launcher/options.c index 757555ec..e116849e 100644 --- a/src/main/launcher/options.c +++ b/src/main/launcher/options.c @@ -1,3 +1,5 @@ +#define LOG_MODULE "options" + #include #include #include @@ -6,62 +8,42 @@ #include "launcher/options.h" -#include "util/array.h" -#include "util/log.h" +#include "util/mem.h" #include "util/str.h" -#define DEFAULT_HEAP_SIZE 16777216 - void options_init(struct options *options) { - options->std_heap_size = DEFAULT_HEAP_SIZE; - options->avs_heap_size = DEFAULT_HEAP_SIZE; - options->app_config_path = "prop/app-config.xml"; - options->avs_config_path = "prop/avs-config.xml"; - options->ea3_config_path = "prop/ea3-config.xml"; - options->softid = NULL; - options->pcbid = NULL; - options->module = NULL; - options->logfile = NULL; - options->remote_debugger = false; - array_init(&options->hook_dlls); - array_init(&options->before_hook_dlls); - array_init(&options->iat_hook_dlls); - - options->override_service = NULL; - options->override_urlslash_enabled = false; - options->override_urlslash_value = false; + memset(options, 0, sizeof(struct options)); + + array_init(&options->hook.hook_dlls); + array_init(&options->hook.before_hook_dlls); + array_init(&options->hook.iat_hook_dlls); } bool options_read_cmdline(struct options *options, int argc, const char **argv) { + bool got_launcher_config; + + got_launcher_config = false; + for (int i = 1; i < argc; i++) { if (argv[i][0] == '-') { switch (argv[i][1]) { - case 'A': - if (i + 1 >= argc) { - return false; - } - - options->app_config_path = argv[++i]; - - break; - - case 'E': + case 'T': if (i + 1 >= argc) { return false; } - options->ea3_config_path = argv[++i]; + options->bootstrap.config_path = argv[++i]; break; - case 'V': + case 'Z': if (i + 1 >= argc) { return false; } - options->avs_config_path = argv[++i]; + options->bootstrap.selector = argv[++i]; break; @@ -70,7 +52,7 @@ bool options_read_cmdline(struct options *options, int argc, const char **argv) return false; } - options->pcbid = argv[++i]; + options->eamuse.pcbid = argv[++i]; break; @@ -79,7 +61,7 @@ bool options_read_cmdline(struct options *options, int argc, const char **argv) return false; } - options->softid = argv[++i]; + options->eamuse.softid = argv[++i]; break; @@ -88,70 +70,65 @@ bool options_read_cmdline(struct options *options, int argc, const char **argv) return false; } - options->avs_heap_size = - (size_t) strtol(argv[++i], NULL, 0); - - if (options->avs_heap_size == 0) { - return false; - } + *array_append(const char *, &options->hook.hook_dlls) = + argv[++i]; break; -#ifdef AVS_HAS_STD_HEAP - case 'T': + case 'B': if (i + 1 >= argc) { return false; } - options->std_heap_size = - (size_t) strtol(argv[++i], NULL, 0); - - if (options->std_heap_size == 0) { - return false; - } + *array_append( + const char *, &options->hook.before_hook_dlls) = + argv[++i]; break; -#endif - case 'K': + case 'I': { if (i + 1 >= argc) { return false; } - *array_append(const char *, &options->hook_dlls) = - argv[++i]; + const char *dll = argv[++i]; + log_assert(strstr(dll, "=") != NULL); + + *array_append(const char *, &options->hook.iat_hook_dlls) = + dll; break; + } - case 'B': + case 'L': if (i + 1 >= argc) { return false; } - *array_append(const char *, &options->before_hook_dlls) = - argv[++i]; + long tmp = strtol(argv[++i], NULL, 0); - break; - - case 'I': { - if (i + 1 >= argc) { + if (tmp < CORE_LOG_BT_LOG_LEVEL_OFF || + tmp > CORE_LOG_BT_LOG_LEVEL_MISC) { return false; } - const char *dll = argv[++i]; - log_assert(strstr(dll, "=") != NULL); - - *array_append(const char *, &options->iat_hook_dlls) = dll; + options->log.level = + xmalloc(sizeof(enum core_log_bt_log_level)); + *(options->log.level) = (enum core_log_bt_log_level) tmp; break; - } case 'Y': if (i + 1 >= argc) { return false; } - options->logfile = argv[++i]; + options->log.file_path = argv[++i]; + + break; + + case 'C': + options->debug.log_property_configs = true; break; @@ -160,7 +137,7 @@ bool options_read_cmdline(struct options *options, int argc, const char **argv) return false; } - options->override_service = argv[++i]; + options->eamuse.service_url = argv[++i]; break; @@ -169,22 +146,23 @@ bool options_read_cmdline(struct options *options, int argc, const char **argv) return false; } - options->override_urlslash_enabled = true; + options->eamuse.urlslash = xmalloc(sizeof(bool)); const char *urlslash_value = argv[++i]; - options->override_urlslash_value = false; + *(options->eamuse.urlslash) = false; + if (_stricmp(urlslash_value, "1") == 0) { - options->override_urlslash_value = true; + *(options->eamuse.urlslash) = true; } if (_stricmp(urlslash_value, "true") == 0) { - options->override_urlslash_value = true; + *(options->eamuse.urlslash) = true; } break; case 'D': - options->remote_debugger = true; + options->debug.remote_debugger = true; break; @@ -192,59 +170,71 @@ bool options_read_cmdline(struct options *options, int argc, const char **argv) break; } } else { - if (!options->module) { - options->module = argv[i]; + if (!got_launcher_config) { + options->launcher.config_path = argv[i]; + got_launcher_config = true; } } } - if (options->module) { - return true; - } else { - return false; - } + return got_launcher_config; } void options_print_usage(void) { fprintf( stderr, - "Usage: launcher.exe [launcher options...] [hooks " - "options...] \n" + "Usage:\n" + " launcher.exe [launcher options as overrides...] " + "[further options, e.g. for hook libraries to pick up...]\n" "\n" - " The following options can be specified before the app DLL " - "path:\n" + " The following options can be specified before the launcher.xml " + "configuration file:\n" "\n" - " -A [filename] App configuration file (default: " - "prop/app-config.xml)\n" - " -V [filename] AVS configuration file (default: " - "prop/avs-config.xml)\n" - " -E [filename] ea3 configuration file (default: " - "prop/ea3-config.xml)\n" - " -H [bytes] AVS heap size (default: 16777216)\n" -#ifdef AVS_HAS_STD_HEAP - " -T [bytes] 'std' heap size (default 16777216)\n" -#endif - " -P [pcbid] Specify PCBID (default: use ea3 config)\n" - " -R [pcbid] Specify Soft ID (default: use ea3 config)\n" - " -S [url] Specify service url (default: use ea3 config)\n" - " -U [0/1] Specify url_slash (default: use ea3 config)\n" - " -K [filename] Load hook DLL (can be specified multiple " + " Bootstrap\n" + " -T [filename] Bootstrap configuration file\n" + " -Z [selector] Bootstrap selector used in configuration\n" + "\n" + " Eamuse\n" + " -P [pcbid] Specify PCBID\n" + " -R [softid] Specify Soft ID\n" + " -S [url] Specify service url\n" + " -U [0/1] Specify url_slash enabled/disabled\n" + "\n" + " Hook\n" + " -H [filename] Load hook DLL (can be specified multiple " "times)\n" " -B [filename] Load pre-hook DLL loaded before avs boot " "(can be specified multiple times)\n" " -I [filename] Load pre-hook DLL that overrides IAT reference " - "before execution" - "(can be specified multiple times)\n" + "before execution (can be specified multiple times)\n" + "\n" + " Logging\n" + " -L [0/1/2/3] Log level for both console and file with " + "increasing verbosity (0 = fatal, 1 = warn, 2 = info, 3 = misc)\n" " -Y [filename] Log to a file in addition to the console\n" + "\n" + " Debug\n" " -D Halt the launcher before bootstrapping AVS " - "until a" - " remote debugger is attached\n"); + "until a remote debugger is attached\n" + " -C Log all loaded and final (property) " + "configuration that launcher uses for bootstrapping. IMPORTANT: DO NOT " + "ENABLE unless you know what you are doing. This prints sensitive data " + "and credentials to the console and logfile. BE CAUTIOUS not to share " + "this information before redaction."); } void options_fini(struct options *options) { - array_fini(&options->hook_dlls); - array_fini(&options->before_hook_dlls); - array_fini(&options->iat_hook_dlls); + array_fini(&options->hook.hook_dlls); + array_fini(&options->hook.before_hook_dlls); + array_fini(&options->hook.iat_hook_dlls); + + if (options->log.level) { + free(options->log.level); + } + + if (options->eamuse.urlslash) { + free(options->eamuse.urlslash); + } } diff --git a/src/main/launcher/options.h b/src/main/launcher/options.h index 13f1c0fb..c0bdfaf2 100644 --- a/src/main/launcher/options.h +++ b/src/main/launcher/options.h @@ -4,25 +4,53 @@ #include #include +#include "core/log-bt.h" +#include "core/log.h" + +#include "launcher/bootstrap-config.h" + #include "util/array.h" +// Launcher options (cmd params) are limited to: +// - Options to run a (vanilla) game without additional launcher features, e.g. +// hooking +// - Options that are handy to have for development/debugging purpose, e.g. +// quickly switching on/off +// logging levels +// +// Everything else is driven by a composable configuration file (launcher.xml) struct options { - size_t std_heap_size; - size_t avs_heap_size; - const char *app_config_path; - const char *avs_config_path; - const char *ea3_config_path; - const char *softid; - const char *pcbid; - const char *module; - const char *logfile; - struct array hook_dlls; - struct array before_hook_dlls; - struct array iat_hook_dlls; - bool remote_debugger; - const char *override_service; - bool override_urlslash_enabled; - bool override_urlslash_value; + struct options_launcher { + const char *config_path; + } launcher; + + struct options_bootstrap { + const char *config_path; + const char *selector; + } bootstrap; + + struct options_log { + enum core_log_bt_log_level *level; + const char *file_path; + } log; + + struct options_eamuse { + const char *softid; + const char *pcbid; + const char *service_url; + bool *urlslash; + } eamuse; + + struct options_hook { + struct array hook_dlls; + struct array before_hook_dlls; + struct array iat_hook_dlls; + } hook; + + struct options_debug { + bool remote_debugger; + bool log_property_configs; + } debug; }; void options_init(struct options *options); diff --git a/src/main/launcher/property-util.c b/src/main/launcher/property-util.c new file mode 100644 index 00000000..20a7f917 --- /dev/null +++ b/src/main/launcher/property-util.c @@ -0,0 +1,891 @@ +#define LOG_MODULE "property-util" + +#include + +#include +#include +#include +#include + +#include "avs-util/error.h" + +#include "core/log.h" + +#include "imports/avs.h" + +#include "launcher/property-util.h" + +#include "util/mem.h" +#include "util/str.h" + +#define PROPERTY_STRUCTURE_META_SIZE 576 + +typedef void (*rewinder)(uint32_t context); + +struct cstring_read_handle { + const char *buffer; + size_t buffer_len; + size_t offset; +}; + +struct property_util_node_merge_ctx { + const char *path; + const struct property_util_node_merge_strategies *strategies; +}; + +static struct property *property_util_do_load( + avs_reader_t reader, rewinder rewinder, uint32_t context, const char *name) +{ + struct property *prop; + void *buffer; + int nbytes; + + nbytes = property_read_query_memsize(reader, context, 0, 0); + + if (nbytes < 0) { + log_fatal("%s: Error querying configuration file", name); + } + + buffer = xmalloc(nbytes); + prop = property_create( + PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE | PROPERTY_FLAG_CREATE | + PROPERTY_FLAG_APPEND, + buffer, + nbytes); + + if (!prop) { + log_fatal( + "%s: Creating property failed: %s", + name, + avs_util_property_error_get_and_clear(prop)); + } + + rewinder(context); + + if (!property_insert_read(prop, 0, reader, context)) { + log_fatal( + "%s: Error reading configuration file: %s", + name, + avs_util_property_error_get_and_clear(prop)); + } + + return prop; +} + +static int property_util_fread(uint32_t context, void *bytes, size_t nbytes) +{ + FILE *f; + + f = TlsGetValue(context); + + return fread(bytes, 1, nbytes, f); +} + +static void property_util_frewind(uint32_t context) +{ + FILE *f = TlsGetValue(context); + rewind(f); +} + +static void property_util_log_node_tree_rec( + struct property_node *parent_node, const char *parent_path) +{ + char cur_path[PROPERTY_NODE_PATH_LEN_MAX]; + char cur_node_name[PROPERTY_NODE_NAME_SIZE_MAX]; + + struct property_node *child_node; + enum property_type property_type; + + int8_t value_s8; + int16_t value_s16; + int32_t value_s32; + int64_t value_s64; + uint8_t value_u8; + uint16_t value_u16; + uint32_t value_u32; + uint64_t value_u64; + char value_str[4096]; + bool value_bool; + + avs_error error; + + // Carry on the full root path down the node tree + property_node_name(parent_node, cur_node_name, sizeof(cur_node_name)); + + str_cpy(cur_path, sizeof(cur_path), parent_path); + str_cat(cur_path, sizeof(cur_path), "/"); + str_cat(cur_path, sizeof(cur_path), cur_node_name); + + child_node = property_node_traversal(parent_node, TRAVERSE_FIRST_CHILD); + + // parent node is a leaf node, print all data of it + if (child_node == NULL) { + property_type = property_node_type(parent_node); + + switch (property_type) { + case PROPERTY_TYPE_VOID: + log_misc("%s: ", cur_path); + break; + + case PROPERTY_TYPE_S8: + property_node_read( + parent_node, property_type, &value_s8, sizeof(value_s8)); + log_misc("%s: %" PRId8, cur_path, value_s8); + break; + + case PROPERTY_TYPE_S16: + property_node_read( + parent_node, property_type, &value_s16, sizeof(value_s16)); + log_misc("%s: %" PRId16, cur_path, value_s16); + break; + + case PROPERTY_TYPE_S32: + property_node_read( + parent_node, property_type, &value_s32, sizeof(value_s32)); + log_misc("%s: %" PRId32, cur_path, value_s32); + break; + + case PROPERTY_TYPE_S64: + property_node_read( + parent_node, property_type, &value_s64, sizeof(value_s64)); + log_misc("%s: %" PRId64, cur_path, value_s64); + break; + + case PROPERTY_TYPE_U8: + property_node_read( + parent_node, property_type, &value_u8, sizeof(value_u8)); + log_misc("%s: %" PRIu8, cur_path, value_u8); + break; + + case PROPERTY_TYPE_U16: + property_node_read( + parent_node, property_type, &value_u16, sizeof(value_u16)); + log_misc("%s: %" PRIu16, cur_path, value_u16); + break; + + case PROPERTY_TYPE_U32: + property_node_read( + parent_node, property_type, &value_u32, sizeof(value_u32)); + log_misc("%s: %" PRIu32, cur_path, value_u32); + break; + + case PROPERTY_TYPE_U64: + property_node_read( + parent_node, property_type, &value_u64, sizeof(value_u64)); + log_misc("%s: %" PRIu64, cur_path, value_u64); + break; + + case PROPERTY_TYPE_STR: + property_node_read( + parent_node, property_type, value_str, sizeof(value_str)); + log_misc("%s: %s", cur_path, value_str); + + break; + + case PROPERTY_TYPE_BOOL: + property_node_read( + parent_node, + property_type, + &value_bool, + sizeof(value_bool)); + log_misc("%s: %d", cur_path, value_bool); + + break; + + case PROPERTY_TYPE_BIN: + log_misc("%s: ", cur_path); + break; + + case PROPERTY_TYPE_ATTR: + error = property_node_read( + parent_node, property_type, value_str, sizeof(value_str)); + + if (AVS_IS_ERROR(error)) { + log_fatal( + "%s, property read failed: %s", + cur_path, + avs_util_error_str(error)); + } + + log_misc("%s@: %s", cur_path, value_str); + + break; + + case PROPERTY_TYPE_VOID_WITH_ATTRIBUTES: + log_misc("%s: ", cur_path); + + child_node = + property_node_traversal(parent_node, TRAVERSE_FIRST_ATTR); + + while (child_node) { + property_util_log_node_tree_rec(child_node, cur_path); + + child_node = property_node_traversal( + child_node, TRAVERSE_NEXT_SIBLING); + } + + break; + + case PROPERTY_TYPE_STR_WITH_ATTRIBUTES: + error = property_node_read( + parent_node, property_type, value_str, sizeof(value_str)); + + if (AVS_IS_ERROR(error)) { + log_fatal( + "%s, property read failed: %s", + cur_path, + avs_util_error_str(error)); + } + + log_misc("%s: %s", cur_path, value_str); + + child_node = + property_node_traversal(parent_node, TRAVERSE_FIRST_ATTR); + + while (child_node) { + property_util_log_node_tree_rec(child_node, cur_path); + + child_node = property_node_traversal( + child_node, TRAVERSE_NEXT_SIBLING); + } + + break; + + default: + log_misc("%s: (%d)", cur_path, property_type); + break; + } + } else { + while (child_node) { + property_util_log_node_tree_rec(child_node, cur_path); + + child_node = + property_node_traversal(child_node, TRAVERSE_NEXT_SIBLING); + } + } +} + +static int +property_util_cstring_read(uint32_t context, void *bytes, size_t nbytes) +{ + int result = 0; + struct cstring_read_handle *h = TlsGetValue(context); + + if (h->offset < h->buffer_len) { + result = min(nbytes, h->buffer_len - h->offset); + memcpy(bytes, (const void *) (h->buffer + h->offset), result); + h->offset += result; + } + return result; +} + +static void property_util_cstring_rewind(uint32_t context) +{ + struct cstring_read_handle *h = TlsGetValue(context); + h->offset = 0; +} + +static int property_util_avs_read(uint32_t context, void *bytes, size_t nbytes) +{ + avs_desc desc = (avs_desc) context; + return avs_fs_read(desc, bytes, nbytes); +} + +static void property_util_avs_rewind(uint32_t context) +{ + avs_desc desc = (avs_desc) context; + avs_fs_lseek(desc, 0, AVS_SEEK_SET); +} + +static void _property_util_node_merge_recursive( + struct property *parent_property, + struct property_node *parent, + struct property_node *source, + void *ctx) +{ + uint8_t i; + bool consumed; + struct property_node *result; + + const struct property_util_node_merge_ctx *ctx_; + struct property_util_node_merge_ctx ctx_next; + + char parent_name[PROPERTY_NODE_NAME_SIZE_MAX]; + char parent_path[PROPERTY_NODE_PATH_LEN_MAX]; + + log_assert(source); + log_assert(ctx); + + ctx_ = (const struct property_util_node_merge_ctx *) ctx; + + log_assert(ctx_->path); + log_assert(ctx_->strategies); + log_assert(ctx_->strategies->num > 0); + + // Default to copying to an empty node + if (!parent) { + result = property_node_clone(parent_property, NULL, source, true); + + if (!result) { + log_fatal("Copying '%s' into empty parent failed", ctx_->path); + } + + return; + } + + property_node_name(parent, parent_name, sizeof(parent_name)); + + str_cpy(parent_path, sizeof(parent_path), ctx_->path); + str_cat(parent_path, sizeof(parent_path), "/"); + str_cat(parent_path, sizeof(parent_path), parent_name); + + ctx_next.path = parent_path; + ctx_next.strategies = ctx_->strategies; + + consumed = false; + + // Apply given strategies, one MUST consume + for (i = 0; i < ctx_->strategies->num; i++) { + log_assert(ctx_->strategies->entry[i].path); + + // path == "" matches everything + if (str_eq(ctx_->strategies->entry[i].path, "") || + str_eq(ctx_->strategies->entry[i].path, parent_path)) { + + consumed = ctx_->strategies->entry[i].merge_strategy_do( + parent_property, + parent, + source, + &ctx_next, + _property_util_node_merge_recursive); + + log_misc( + "Merge strategy for '%s' consumed: %d", + ctx_->strategies->entry[i].path, + consumed); + + if (consumed) { + break; + } + } + } + + log_assert(consumed); +} + +void property_util_log(struct property *property) +{ + property_util_log_node_tree_rec(property_search(property, NULL, "/"), ""); +} + +void property_util_node_log(struct property_node *node) +{ + property_util_log_node_tree_rec(node, ""); +} + +struct property *property_util_load(const char *filename) +{ + FILE *f; + uint32_t f_keyhole; + struct property *prop; + + log_assert(filename); + + /* AVS callbacks are only given a 32-bit context parameter, even in 64-bit + builds of AVS. We allocate a 32-bit TLS key and pass the context in this + manner instead. Inefficient, but it works. */ + + f = fopen(filename, "r"); + + f_keyhole = TlsAlloc(); + TlsSetValue(f_keyhole, f); + + if (f == NULL) { + log_fatal("%s: Error opening configuration file", filename); + } + + prop = property_util_do_load( + property_util_fread, property_util_frewind, f_keyhole, filename); + + TlsFree(f_keyhole); + + fclose(f); + + return prop; +} + +struct property *property_util_avs_fs_load(const char *filename) +{ + avs_desc desc; + struct property *prop; + + log_assert(filename); + + desc = avs_fs_open(filename, AVS_FILE_READ, AVS_FILE_FLAG_SHARE_READ); + + if (!desc) { + log_fatal("%s: Error opening configuration file", filename); + } + + prop = property_util_do_load( + property_util_avs_read, property_util_avs_rewind, desc, filename); + + avs_fs_close(desc); + + return prop; +} + +struct property *property_util_cstring_load(const char *cstring) +{ + uint32_t s_keyhole; + struct property *prop; + + log_assert(cstring); + + // see above + struct cstring_read_handle read_handle; + read_handle.buffer = cstring; + read_handle.buffer_len = strlen(cstring); + read_handle.offset = 0; + + s_keyhole = TlsAlloc(); + TlsSetValue(s_keyhole, &read_handle); + + prop = property_util_do_load( + property_util_cstring_read, + property_util_cstring_rewind, + s_keyhole, + ""); + + TlsFree(s_keyhole); + + return prop; +} + +struct property *property_util_clone(struct property *property) +{ + struct property *clone; + size_t size; + void *buffer; + + struct property_node *node_property; + struct property_node *node_clone; + + log_assert(property); + + size = property_util_property_query_real_size(property); + + buffer = xmalloc(size); + + clone = property_create( + PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE | PROPERTY_FLAG_CREATE | + PROPERTY_FLAG_APPEND, + buffer, + size); + + if (!clone) { + log_fatal("Creating property failed"); + } + + node_property = property_search(property, NULL, "/"); + node_clone = property_search(clone, NULL, "/"); + + if (!property_node_clone(clone, node_clone, node_property, true)) { + log_fatal( + "Cloning property data failed: %s", + avs_util_property_error_get_and_clear(clone)); + } + + return clone; +} + +void property_util_free(struct property *prop) +{ + void *buffer; + + buffer = property_desc_to_buffer(prop); + property_destroy(prop); + free(buffer); +} + +uint32_t property_util_property_query_real_size(struct property *property) +{ + avs_error size; + + log_assert(property); + + // Returns the size of the actual data in the property structure only + // Hence, using that size only, allocating another buffer for a copy + // of this might fail or copying the data will fail because the buffer + // is too small + size = property_query_size(property); + + if (AVS_IS_ERROR(size)) { + log_fatal( + "Querying property size failed: %s", avs_util_error_str(size)); + } + + // Hack: *2 to have enough space and not cut off data when cloning/copying + // property data because...reasons? I haven't figured this one out and + // there doesn't seem to be an actual API call for that to return the + // "true" size that allows the caller to figure out how much memory + // they have to allocate to create a copy of the property structure + // with property_create and + return (PROPERTY_STRUCTURE_META_SIZE + size) * 2; +} + +void property_util_node_u8_replace( + struct property *property, + struct property_node *node, + const char *name, + uint8_t val) +{ + struct property_node *tmp; + + log_assert(node); + log_assert(name); + + tmp = property_search(property, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(property, node, PROPERTY_TYPE_U8, name, val); + + if (!tmp) { + log_fatal( + "Creating node '%s' failed: %s", + name, + property ? avs_util_property_error_get_and_clear(property) : + "unknown"); + } +} + +void property_util_node_u16_replace( + struct property *property, + struct property_node *node, + const char *name, + uint16_t val) +{ + struct property_node *tmp; + + log_assert(node); + log_assert(name); + + tmp = property_search(property, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(property, node, PROPERTY_TYPE_U16, name, val); + + if (!tmp) { + log_fatal( + "Creating node '%s' failed: %s", + name, + property ? avs_util_property_error_get_and_clear(property) : + "unknown"); + } +} + +void property_util_node_u32_replace( + struct property *property, + struct property_node *node, + const char *name, + uint32_t val) +{ + struct property_node *tmp; + + log_assert(node); + log_assert(name); + + tmp = property_search(property, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(property, node, PROPERTY_TYPE_U32, name, val); + + if (!tmp) { + log_fatal( + "Creating node '%s' failed: %s", + name, + property ? avs_util_property_error_get_and_clear(property) : + "unknown"); + } +} + +void property_util_node_str_replace( + struct property *property, + struct property_node *node, + const char *name, + const char *val) +{ + struct property_node *tmp; + + log_assert(node); + log_assert(name); + + tmp = property_search(property, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(property, node, PROPERTY_TYPE_STR, name, val); + + if (!tmp) { + log_fatal( + "Creating node '%s' failed: %s", + name, + property ? avs_util_property_error_get_and_clear(property) : + "unknown"); + } +} + +void property_util_node_bool_replace( + struct property *property, + struct property_node *node, + const char *name, + bool val) +{ + struct property_node *tmp; + + log_assert(node); + log_assert(name); + + tmp = property_search(property, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(property, node, PROPERTY_TYPE_BOOL, name, val); + + if (!tmp) { + log_fatal( + "Creating node '%s' failed: %s", + name, + property ? avs_util_property_error_get_and_clear(property) : + "unknown"); + } +} + +void property_util_node_attribute_replace( + struct property *property, + struct property_node *node, + const char *name, + const char *val) +{ + struct property_node *tmp; + + log_assert(node); + log_assert(name); + + tmp = property_search(property, node, name); + + if (tmp) { + property_node_remove(tmp); + } + + tmp = property_node_create(property, node, PROPERTY_TYPE_ATTR, name, val); +} + +struct property * +property_util_many_merge(struct property **properties, size_t count) +{ + struct property *merged_property; + struct property *tmp; + int i; + + log_assert(properties); + log_assert(count > 0); + + merged_property = property_util_clone(properties[0]); + + if (count == 1) { + return merged_property; + } + + for (i = 1; i < count; i++) { + tmp = property_util_merge(merged_property, properties[i]); + + property_util_free(merged_property); + merged_property = tmp; + } + + return merged_property; +} + +struct property *property_util_node_extract(struct property_node *node) +{ + struct property *property; + struct property_node *root_node; + uint32_t size; + void *buffer; + struct property_node *result; + + if (!node) { + return NULL; + } + + // Hack: Is it even possible to get the size of a (sub-) node without + // the property? 256kb should be fine for now, even for larger + // configurations. Obviously, this scales horribly and wastes a lot of + // memory for most smaller sub-nodes + size = 1024 * 256; + + buffer = xmalloc(size); + property = property_create( + PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE | PROPERTY_FLAG_CREATE | + PROPERTY_FLAG_APPEND, + buffer, + size); + root_node = property_search(property, NULL, ""); + + result = property_node_clone(property, root_node, node, true); + + if (!result) { + log_fatal("Cloning node into empty property failed"); + } + + return property; +} + +struct property * +property_util_merge(struct property *parent, struct property *source) +{ + struct property_util_node_merge_strategies strategies; + + log_assert(parent); + log_assert(source); + + strategies.num = 1; + + strategies.entry[0].path = ""; + strategies.entry[0].merge_strategy_do = + property_util_node_merge_default_strategy_do; + + return property_util_merge_with_strategies(parent, source, &strategies); +} + +struct property *property_util_merge_with_strategies( + struct property *parent, + struct property *source, + const struct property_util_node_merge_strategies *strategies) +{ + struct property_util_node_merge_ctx ctx; + size_t total_size; + void *buffer; + struct property *merged; + struct property_node *parent_node; + struct property_node *source_node; + + log_assert(parent); + log_assert(source); + log_assert(strategies); + + // We can't estimate how these two are being merged as in how much new + // data is being inserted from source into parent. Therefore, worse-case + // estimate memory requirement for no overlap + total_size = 0; + total_size += property_util_property_query_real_size(parent); + total_size += property_util_property_query_real_size(source); + + buffer = xmalloc(total_size); + + merged = property_create( + PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE | PROPERTY_FLAG_CREATE | + PROPERTY_FLAG_APPEND, + buffer, + total_size); + + ctx.path = ""; + ctx.strategies = strategies; + + parent_node = property_search(parent, NULL, "/"); + + if (!property_node_clone(merged, NULL, parent_node, true)) { + log_fatal( + "Copying parent base failed: %s", + avs_util_property_error_get_and_clear(merged)); + } + + // Grab parent_node from merged property which is the target one to merge + // into + parent_node = property_search(merged, NULL, "/"); + source_node = property_search(source, NULL, "/"); + + _property_util_node_merge_recursive(merged, parent_node, source_node, &ctx); + + return merged; +} + +bool property_util_node_merge_default_strategy_do( + struct property *parent_property, + struct property_node *parent, + struct property_node *source, + void *ctx, + property_util_node_merge_recursion_do_t node_merge_recursion_do) +{ + struct property_node *result; + + struct property_node *parent_child; + struct property_node *source_child; + struct property_node *source_child_child; + + char child_node_name[PROPERTY_NODE_NAME_SIZE_MAX]; + + log_assert(parent); + log_assert(source); + + source_child = property_node_traversal(source, TRAVERSE_FIRST_CHILD); + + while (source_child) { + property_node_name( + source_child, child_node_name, sizeof(child_node_name)); + + parent_child = property_search(NULL, parent, child_node_name); + + if (parent_child) { + source_child_child = + property_node_traversal(source_child, TRAVERSE_FIRST_CHILD); + + if (source_child_child) { + // Continue recursion if there are actually more children + node_merge_recursion_do( + parent_property, parent_child, source_child, ctx); + } else { + // Found identical leaf node, remove the matching parent's child + // and copy the source child over to the parent and terminate + // the recursion + property_node_remove(parent_child); + result = property_node_clone( + parent_property, parent, source_child, true); + + if (!result) { + log_fatal( + "Replacing leaf node '%s' failed", child_node_name); + } + } + } else { + // Could not find an identical child on parent, copy source + // recursively to parent + result = property_node_clone( + parent_property, parent, source_child, true); + + if (!result) { + log_fatal("Deep copying child '%s' failed", child_node_name); + } + } + + source_child = + property_node_traversal(source_child, TRAVERSE_NEXT_SIBLING); + } + + // Default strategy always consumes + return true; +} diff --git a/src/main/launcher/property-util.h b/src/main/launcher/property-util.h new file mode 100644 index 00000000..ba59479f --- /dev/null +++ b/src/main/launcher/property-util.h @@ -0,0 +1,99 @@ +#ifndef PROPERTY_UTIL_H +#define PROPERTY_UTIL_H + +#include + +#include "imports/avs.h" + +// Guestimate, should be long enough, I hope? +#define PROPERTY_NODE_PATH_LEN_MAX 4096 +// 256 found in AVS code as size used on property_node_name +#define PROPERTY_NODE_NAME_SIZE_MAX 256 +// Guestimate, should be enough, I hope? +#define PROPERTY_NODE_ATTR_NAME_SIZE_MAX 128 + +#define PROPERTY_UTIL_MAX_NODE_NAME_RESOLVERS 4 + +typedef void (*property_util_node_merge_recursion_do_t)( + struct property *parent_property, + struct property_node *parent, + struct property_node *source, + void *ctx); + +typedef bool (*property_util_node_merge_strategy_do_t)( + struct property *parent_property, + struct property_node *parent, + struct property_node *source, + void *ctx, + property_util_node_merge_recursion_do_t node_merge_recursion_do); + +struct property_util_node_merge_strategies { + struct { + const char *path; + property_util_node_merge_strategy_do_t merge_strategy_do; + } entry[PROPERTY_UTIL_MAX_NODE_NAME_RESOLVERS]; + uint8_t num; +}; + +void property_util_log(struct property *property); +void property_util_node_log(struct property_node *node); +struct property *property_util_load(const char *filename); +struct property *property_util_avs_fs_load(const char *filename); +struct property *property_util_cstring_load(const char *cstring); +struct property *property_util_clone(struct property *property); +void property_util_free(struct property *prop); +uint32_t property_util_property_query_real_size(struct property *property); +void property_util_node_u8_replace( + struct property *property, + struct property_node *node, + const char *name, + uint8_t val); +void property_util_node_u16_replace( + struct property *property, + struct property_node *node, + const char *name, + uint16_t val); +void property_util_node_u32_replace( + struct property *property, + struct property_node *node, + const char *name, + uint32_t val); +void property_util_node_str_replace( + struct property *property, + struct property_node *node, + const char *name, + const char *val); +void property_util_node_bool_replace( + struct property *property, + struct property_node *node, + const char *name, + bool val); +void property_util_node_attribute_replace( + struct property *property, + struct property_node *node, + const char *name, + const char *val); + +struct property * +property_util_many_merge(struct property **properties, size_t count); +struct property *property_util_node_extract(struct property_node *node); + +struct property * +property_util_merge(struct property *parent, struct property *source); + +// Strategies are applied in order and first consumer terminates +// applying further strategies Typically, you want to include the default +// strategy after your custom strategies for special cases +struct property *property_util_merge_with_strategies( + struct property *parent, + struct property *source, + const struct property_util_node_merge_strategies *strategies); + +bool property_util_node_merge_default_strategy_do( + struct property *parent_property, + struct property_node *parent, + struct property_node *source, + void *ctx, + property_util_node_merge_recursion_do_t node_merge_recursion_do); + +#endif diff --git a/src/main/launcher/property.c b/src/main/launcher/property.c deleted file mode 100644 index 2f5ded94..00000000 --- a/src/main/launcher/property.c +++ /dev/null @@ -1,135 +0,0 @@ -#include - -#include -#include -#include - -#include "imports/avs.h" - -#include "launcher/property.h" - -#include "util/log.h" -#include "util/mem.h" - -static int boot_property_fread(uint32_t context, void *bytes, size_t nbytes) -{ - FILE *f; - - f = TlsGetValue(context); - - return fread(bytes, 1, nbytes, f); -} - -struct cstring_read_handle { - const char *buffer; - size_t buffer_len; - size_t offset; -}; - -static int -boot_property_cstring_read(uint32_t context, void *bytes, size_t nbytes) -{ - int result = 0; - struct cstring_read_handle *h = TlsGetValue(context); - - if (h->offset < h->buffer_len) { - result = min(nbytes, h->buffer_len - h->offset); - memcpy(bytes, (const void *) (h->buffer + h->offset), result); - h->offset += result; - } - return result; -} - -struct property *boot_property_load(const char *filename) -{ - struct property *prop; - void *buffer; - int nbytes; - FILE *f; - uint32_t f_keyhole; - - /* AVS callbacks are only given a 32-bit context parameter, even in 64-bit - builds of AVS. We allocate a 32-bit TLS key and pass the context in this - manner instead. Inefficient, but it works. */ - - f = fopen(filename, "r"); - - f_keyhole = TlsAlloc(); - TlsSetValue(f_keyhole, f); - - if (f == NULL) { - log_fatal("%s: Error opening configuration file", filename); - } - - nbytes = property_read_query_memsize(boot_property_fread, f_keyhole, 0, 0); - - if (nbytes < 0) { - log_fatal("%s: Error querying configuration file", filename); - } - - buffer = xmalloc(nbytes); - prop = property_create( - PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE | PROPERTY_FLAG_CREATE | - PROPERTY_FLAG_APPEND, - buffer, - nbytes); - rewind(f); - - if (!property_insert_read(prop, 0, boot_property_fread, f_keyhole)) { - log_fatal("%s: Error reading configuration file", filename); - } - - TlsFree(f_keyhole); - - fclose(f); - - return prop; -} -struct property *boot_property_load_cstring(const char *cstring) -{ - struct property *prop; - void *buffer; - int nbytes; - uint32_t s_keyhole; - - // see above - struct cstring_read_handle read_handle; - read_handle.buffer = cstring; - read_handle.buffer_len = strlen(cstring); - read_handle.offset = 0; - - s_keyhole = TlsAlloc(); - TlsSetValue(s_keyhole, &read_handle); - - nbytes = property_read_query_memsize( - boot_property_cstring_read, s_keyhole, 0, 0); - - if (nbytes < 0) { - log_fatal("Error querying configuration string"); - } - - buffer = xmalloc(nbytes); - prop = property_create( - PROPERTY_FLAG_READ | PROPERTY_FLAG_WRITE | PROPERTY_FLAG_CREATE | - PROPERTY_FLAG_APPEND, - buffer, - nbytes); - - read_handle.offset = 0; - if (!property_insert_read(prop, 0, boot_property_cstring_read, s_keyhole)) { - log_fatal("Error inserting configuration string"); - } - - TlsFree(s_keyhole); - - return prop; -} - -void boot_property_free(struct property *prop) -{ - void *buffer; - - buffer = property_desc_to_buffer(prop); - property_destroy(prop); - free(buffer); -} diff --git a/src/main/launcher/property.h b/src/main/launcher/property.h deleted file mode 100644 index c3f14d0e..00000000 --- a/src/main/launcher/property.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef LAUNCHER_PROPERTY_H -#define LAUNCHER_PROPERTY_H - -#include "imports/avs.h" - -struct property *boot_property_load(const char *filename); -struct property *boot_property_load_cstring(const char *cstring); -void boot_property_free(struct property *prop); - -#endif diff --git a/src/main/launcher/stubs.c b/src/main/launcher/stubs.c index a524273b..4c0b253a 100644 --- a/src/main/launcher/stubs.c +++ b/src/main/launcher/stubs.c @@ -1,3 +1,5 @@ +#define LOG_MODULE "stubs" + #include #include @@ -5,12 +7,13 @@ #include #include +#include "core/log.h" + #include "hook/table.h" #include "launcher/stubs.h" #include "util/defs.h" -#include "util/log.h" struct ikey_status { uint32_t field_0; @@ -116,6 +119,8 @@ static void *STDCALL my_GetProcAddress(HMODULE dll, const char *name) void stubs_init(void) { + log_info("Init"); + hook_table_apply( NULL, "kernel32.dll", stub_hook_syms, lengthof(stub_hook_syms)); } From 8fde17d24afcfe6f7388a57c6e1ef322c8ef2f46 Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 08/90] refactor(dist): Config and .bat files Move everything to new launcher.xml configuration files. Adjust the bootstrapping of launcher in the .bat files. Features such as copying the default props/ files to nvram are now handled by launcher. Using the PATH variable, bemanitools binaries can live in their own dedicated bemanitools/ subfolder next to props/ and modules/ now. All original binaries are expected to be kept in a modules/ folder like on stock data. --- Module.mk | 86 ++++++++++++++++++++++++++++++++++++ dist/bst/gamestart1.bat | 34 +++++++++++--- dist/bst/gamestart2.bat | 34 +++++++++++--- dist/bst/launcher-01.xml | 75 +++++++++++++++++++++++++++++++ dist/bst/launcher-02.xml | 75 +++++++++++++++++++++++++++++++ dist/ddr/gamestart-12.bat | 41 ++++++++++++++--- dist/ddr/gamestart-13.bat | 41 ++++++++++++++--- dist/ddr/gamestart-14.bat | 46 +++++++++++++++---- dist/ddr/gamestart-15.bat | 46 +++++++++++++++---- dist/ddr/gamestart-16.bat | 47 ++++++++++++++++---- dist/ddr/gamestart-17.bat | 47 ++++++++++++++++---- dist/ddr/gamestart-18.bat | 47 ++++++++++++++++---- dist/ddr/launcher-12.xml | 75 +++++++++++++++++++++++++++++++ dist/ddr/launcher-13.xml | 75 +++++++++++++++++++++++++++++++ dist/ddr/launcher-14.xml | 75 +++++++++++++++++++++++++++++++ dist/ddr/launcher-15.xml | 75 +++++++++++++++++++++++++++++++ dist/ddr/launcher-16.xml | 76 +++++++++++++++++++++++++++++++ dist/ddr/launcher-17.xml | 76 +++++++++++++++++++++++++++++++ dist/ddr/launcher-18.xml | 76 +++++++++++++++++++++++++++++++ dist/iidx/ea3-ident.xml | 10 +++++ dist/iidx/eamuse-server.xml | 6 +++ dist/iidx/gamestart-18.bat | 36 +++++++++++---- dist/iidx/gamestart-19.bat | 36 +++++++++++---- dist/iidx/gamestart-20.bat | 34 +++++++++++--- dist/iidx/gamestart-21.bat | 34 +++++++++++--- dist/iidx/gamestart-22.bat | 34 +++++++++++--- dist/iidx/gamestart-23.bat | 34 +++++++++++--- dist/iidx/gamestart-24.bat | 34 +++++++++++--- dist/iidx/gamestart-25.bat | 40 ++++++++++++----- dist/iidx/gamestart-26.bat | 40 ++++++++++++----- dist/iidx/gamestart-27.bat | 40 ++++++++++++----- dist/iidx/gamestart-28.bat | 40 ++++++++++++----- dist/iidx/gamestart-29.bat | 40 ++++++++++++----- dist/iidx/gamestart-30.bat | 40 ++++++++++++----- dist/iidx/launcher-18.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-19.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-20.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-21.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-22.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-23.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-24.xml | 75 +++++++++++++++++++++++++++++++ dist/iidx/launcher-25.xml | 74 +++++++++++++++++++++++++++++++ dist/iidx/launcher-26.xml | 74 +++++++++++++++++++++++++++++++ dist/iidx/launcher-27.xml | 69 +++++++++++++++++++++++++++++ dist/iidx/launcher-28.xml | 69 +++++++++++++++++++++++++++++ dist/iidx/launcher-29.xml | 69 +++++++++++++++++++++++++++++ dist/iidx/launcher-30.xml | 69 +++++++++++++++++++++++++++++ dist/iidx/pcbid.xml | 7 +++ dist/jb/ea3-ident.xml | 10 +++++ dist/jb/eamuse-server.xml | 6 +++ dist/jb/gamestart-03.bat | 35 ++++++++++++--- dist/jb/gamestart-04.bat | 35 ++++++++++++--- dist/jb/launcher-03.xml | 75 +++++++++++++++++++++++++++++++ dist/jb/launcher-04.xml | 75 +++++++++++++++++++++++++++++++ dist/jb/pcbid.xml | 7 +++ dist/sdvx/ea3-ident.xml | 10 +++++ dist/sdvx/eamuse-server.xml | 6 +++ dist/sdvx/gamestart.bat | 35 +++++++++++++++ dist/sdvx/launcher.xml | 75 +++++++++++++++++++++++++++++++ dist/sdvx/pcbid.xml | 7 +++ dist/sdvx5/ea3-ident.xml | 10 +++++ dist/sdvx5/eamuse-server.xml | 6 +++ dist/sdvx5/gamestart-cn.bat | 41 ++++++++++++----- dist/sdvx5/gamestart.bat | 40 ++++++++++++----- dist/sdvx5/launcher-cn.xml | 74 +++++++++++++++++++++++++++++++ dist/sdvx5/launcher.xml | 74 +++++++++++++++++++++++++++++++ dist/sdvx5/pcbid.xml | 7 +++ dist/shared/ea3-ident.xml | 11 +++++ dist/shared/ea3-license.xml | 8 ++++ dist/shared/ea3-service.xml | 7 +++ 70 files changed, 3050 insertions(+), 205 deletions(-) create mode 100644 dist/bst/launcher-01.xml create mode 100644 dist/bst/launcher-02.xml create mode 100644 dist/ddr/launcher-12.xml create mode 100644 dist/ddr/launcher-13.xml create mode 100644 dist/ddr/launcher-14.xml create mode 100644 dist/ddr/launcher-15.xml create mode 100644 dist/ddr/launcher-16.xml create mode 100644 dist/ddr/launcher-17.xml create mode 100644 dist/ddr/launcher-18.xml create mode 100644 dist/iidx/ea3-ident.xml create mode 100644 dist/iidx/eamuse-server.xml create mode 100644 dist/iidx/launcher-18.xml create mode 100644 dist/iidx/launcher-19.xml create mode 100644 dist/iidx/launcher-20.xml create mode 100644 dist/iidx/launcher-21.xml create mode 100644 dist/iidx/launcher-22.xml create mode 100644 dist/iidx/launcher-23.xml create mode 100644 dist/iidx/launcher-24.xml create mode 100644 dist/iidx/launcher-25.xml create mode 100644 dist/iidx/launcher-26.xml create mode 100644 dist/iidx/launcher-27.xml create mode 100644 dist/iidx/launcher-28.xml create mode 100644 dist/iidx/launcher-29.xml create mode 100644 dist/iidx/launcher-30.xml create mode 100644 dist/iidx/pcbid.xml create mode 100644 dist/jb/ea3-ident.xml create mode 100644 dist/jb/eamuse-server.xml create mode 100644 dist/jb/launcher-03.xml create mode 100644 dist/jb/launcher-04.xml create mode 100644 dist/jb/pcbid.xml create mode 100644 dist/sdvx/ea3-ident.xml create mode 100644 dist/sdvx/eamuse-server.xml create mode 100644 dist/sdvx/launcher.xml create mode 100644 dist/sdvx/pcbid.xml create mode 100644 dist/sdvx5/ea3-ident.xml create mode 100644 dist/sdvx5/eamuse-server.xml create mode 100644 dist/sdvx5/launcher-cn.xml create mode 100644 dist/sdvx5/launcher.xml create mode 100644 dist/sdvx5/pcbid.xml create mode 100644 dist/shared/ea3-ident.xml create mode 100644 dist/shared/ea3-license.xml create mode 100644 dist/shared/ea3-service.xml diff --git a/Module.mk b/Module.mk index 26972251..de08e82b 100644 --- a/Module.mk +++ b/Module.mk @@ -325,6 +325,10 @@ $(zipdir)/iidx-18.zip: \ dist/iidx/config.bat \ dist/iidx/gamestart-18.bat \ dist/iidx/iidxhook-18.conf \ + dist/iidx/launcher-18.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ dist/iidx/vefx.txt \ | $(zipdir)/ $(V)echo ... $@ @@ -356,6 +360,10 @@ $(zipdir)/iidx-19.zip: \ dist/iidx/config.bat \ dist/iidx/gamestart-19.bat \ dist/iidx/iidxhook-19.conf \ + dist/iidx/launcher-19.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ dist/iidx/vefx.txt \ | $(zipdir)/ $(V)echo ... $@ @@ -372,6 +380,10 @@ $(zipdir)/iidx-20.zip: \ dist/iidx/config.bat \ dist/iidx/gamestart-20.bat \ dist/iidx/iidxhook-20.conf \ + dist/iidx/launcher-20.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ dist/iidx/vefx.txt \ | $(zipdir)/ $(V)echo ... $@ @@ -409,6 +421,13 @@ $(zipdir)/iidx-21-to-24.zip: \ dist/iidx/iidxhook-22.conf \ dist/iidx/iidxhook-23.conf \ dist/iidx/iidxhook-24.conf \ + dist/iidx/launcher-21.xml \ + dist/iidx/launcher-22.xml \ + dist/iidx/launcher-23.xml \ + dist/iidx/launcher-24.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ dist/iidx/vefx.txt \ | $(zipdir)/ $(V)echo ... $@ @@ -427,6 +446,11 @@ $(zipdir)/iidx-25-to-26.zip: \ dist/iidx/gamestart-26.bat \ dist/iidx/iidxhook-25.conf \ dist/iidx/iidxhook-26.conf \ + dist/iidx/launcher-25.xml \ + dist/iidx/launcher-26.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ dist/iidx/vefx.txt \ | $(zipdir)/ $(V)echo ... $@ @@ -449,6 +473,13 @@ $(zipdir)/iidx-27-to-30.zip: \ dist/iidx/iidxhook-28.conf \ dist/iidx/iidxhook-29.conf \ dist/iidx/iidxhook-30.conf \ + dist/iidx/launcher-27.xml \ + dist/iidx/launcher-28.xml \ + dist/iidx/launcher-29.xml \ + dist/iidx/launcher-30.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ dist/iidx/vefx.txt \ | $(zipdir)/ $(V)echo ... $@ @@ -517,6 +548,10 @@ $(zipdir)/jb-03.zip: \ build/bin/indep-32/jbio.dll \ dist/jb/config.bat \ dist/jb/gamestart-03.bat \ + dist/jb/launcher-03.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -530,6 +565,10 @@ $(zipdir)/jb-04.zip: \ build/bin/indep-32/jbio.dll \ dist/jb/config.bat \ dist/jb/gamestart-03.bat \ + dist/jb/launcher-03.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -543,6 +582,10 @@ $(zipdir)/jb-05-to-07.zip: \ build/bin/indep-32/jbio.dll \ dist/jb/config.bat \ dist/jb/gamestart-04.bat \ + dist/jb/launcher-04.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -556,6 +599,10 @@ $(zipdir)/jb-08.zip: \ build/bin/indep-32/jbio.dll \ dist/jb/config.bat \ dist/jb/gamestart-04.bat \ + dist/jb/launcher-04.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -580,6 +627,10 @@ $(zipdir)/sdvx-01-to-04.zip: \ build/bin/indep-32/sdvxio.dll \ dist/sdvx/config.bat \ dist/sdvx/gamestart.bat \ + dist/sdvx/launcher.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -594,6 +645,10 @@ $(zipdir)/sdvx-05-to-06.zip: \ dist/sdvx5/config.bat \ dist/sdvx5/gamestart.bat \ dist/sdvx5/sdvxhook2.conf \ + dist/sdvx5/launcher.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -608,6 +663,10 @@ $(zipdir)/sdvx-05-cn.zip: \ dist/sdvx5/config.bat \ dist/sdvx5/gamestart-cn.bat \ dist/sdvx5/sdvxhook2-cn.conf \ + dist/sdvx5/launcher-cn.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -682,6 +741,10 @@ $(zipdir)/ddr-12.zip: \ build/bin/indep-32/geninput.dll \ dist/ddr/config.bat \ dist/ddr/gamestart-12.bat \ + dist/ddr/launcher-12.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -698,6 +761,10 @@ $(zipdir)/ddr-13.zip: \ build/bin/indep-32/geninput.dll \ dist/ddr/config.bat \ dist/ddr/gamestart-13.bat \ + dist/ddr/launcher-13.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -718,6 +785,14 @@ $(zipdir)/ddr-14-to-18.zip: \ dist/ddr/gamestart-16.bat \ dist/ddr/gamestart-17.bat \ dist/ddr/gamestart-18.bat \ + dist/ddr/launcher-14.xml \ + dist/ddr/launcher-15.xml \ + dist/ddr/launcher-16.xml \ + dist/ddr/launcher-17.xml \ + dist/ddr/launcher-18.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -736,6 +811,12 @@ $(zipdir)/ddr-16-to-18-x64.zip: \ dist/ddr/gamestart-16.bat \ dist/ddr/gamestart-17.bat \ dist/ddr/gamestart-18.bat \ + dist/ddr/launcher-16.xml \ + dist/ddr/launcher-17.xml \ + dist/ddr/launcher-18.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ @@ -774,6 +855,11 @@ $(zipdir)/bst.zip: \ dist/bst/config.bat \ dist/bst/gamestart1.bat \ dist/bst/gamestart2.bat \ + dist/bst/launcher-01.xml \ + dist/bst/launcher-02.xml \ + dist/shared/ea3-ident.xml \ + dist/shared/ea3-license.xml \ + dist/shared/ea3-service.xml \ | $(zipdir)/ $(V)echo ... $@ $(V)zip -j $@ $^ diff --git a/dist/bst/gamestart1.bat b/dist/bst/gamestart1.bat index 5a3a9155..5bca8eb1 100644 --- a/dist/bst/gamestart1.bat +++ b/dist/bst/gamestart1.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K bsthook.dll -E prop/ea3-config-1.xml beatstream1.dll %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-01.xml %* \ No newline at end of file diff --git a/dist/bst/gamestart2.bat b/dist/bst/gamestart2.bat index 8b6f53f8..55340c37 100644 --- a/dist/bst/gamestart2.bat +++ b/dist/bst/gamestart2.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K bsthook.dll -E prop/ea3-config-2.xml beatstream2.dll %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-02.xml %* \ No newline at end of file diff --git a/dist/bst/launcher-01.xml b/dist/bst/launcher-01.xml new file mode 100644 index 00000000..b62630ef --- /dev/null +++ b/dist/bst/launcher-01.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + ∂ + + info + + + /dev/nvram/ea3-config.xml + + + beatstream1.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-server.xml + + + + bsthook.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/bst/launcher-02.xml b/dist/bst/launcher-02.xml new file mode 100644 index 00000000..fdad81d9 --- /dev/null +++ b/dist/bst/launcher-02.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + beatstream2.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + bsthook.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/gamestart-12.bat b/dist/ddr/gamestart-12.bat index 7df3fa33..03daa9c3 100644 --- a/dist/ddr/gamestart-12.bat +++ b/dist/ddr/gamestart-12.bat @@ -1,11 +1,42 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s k-clvsd.dll -regsvr32 /s xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-13.xml %* -.\launcher.exe -K .\ddrhook2.dll .\ddr.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll \ No newline at end of file diff --git a/dist/ddr/gamestart-13.bat b/dist/ddr/gamestart-13.bat index 7df3fa33..03daa9c3 100644 --- a/dist/ddr/gamestart-13.bat +++ b/dist/ddr/gamestart-13.bat @@ -1,11 +1,42 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s k-clvsd.dll -regsvr32 /s xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-13.xml %* -.\launcher.exe -K .\ddrhook2.dll .\ddr.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll \ No newline at end of file diff --git a/dist/ddr/gamestart-14.bat b/dist/ddr/gamestart-14.bat index 2ad52121..2364f521 100644 --- a/dist/ddr/gamestart-14.bat +++ b/dist/ddr/gamestart-14.bat @@ -1,14 +1,44 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml -if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml -if not exist conf\nvram\share-config.xml copy prop\share-config.xml conf\nvram\share-config.xml -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll +regsvr32 /s %COM_DIR%\xactengine2_10.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s k-clvsd.dll -regsvr32 /s xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-14.xml %* -.\launcher.exe -K .\ddrhook2.dll .\mdxja_945.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll +regsvr32 /u /s %COM_DIR%\xactengine2_10.dll \ No newline at end of file diff --git a/dist/ddr/gamestart-15.bat b/dist/ddr/gamestart-15.bat index 2ad52121..97301177 100644 --- a/dist/ddr/gamestart-15.bat +++ b/dist/ddr/gamestart-15.bat @@ -1,14 +1,44 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml -if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml -if not exist conf\nvram\share-config.xml copy prop\share-config.xml conf\nvram\share-config.xml -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll +regsvr32 /s %COM_DIR%\xactengine2_10.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s k-clvsd.dll -regsvr32 /s xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-15.xml %* -.\launcher.exe -K .\ddrhook2.dll .\mdxja_945.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll +regsvr32 /u /s %COM_DIR%\xactengine2_10.dll \ No newline at end of file diff --git a/dist/ddr/gamestart-16.bat b/dist/ddr/gamestart-16.bat index d108118b..b176cb3a 100644 --- a/dist/ddr/gamestart-16.bat +++ b/dist/ddr/gamestart-16.bat @@ -1,15 +1,44 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\nvram\ea3-config.xml copy prop\eamuse-config.xml conf\nvram\ea3-config.xml -if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml -if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml -if not exist conf\nvram\testmode-v.xml copy prop\testmode-v.xml conf\nvram\testmode-v.xml -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll +regsvr32 /s %COM_DIR%\xactengine2_10.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s com\k-clvsd.dll -regsvr32 /s com\xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-17.xml %* -.\launcher.exe -H 33554432 -K .\ddrhook2.dll .\arkmdxp3.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll +regsvr32 /u /s %COM_DIR%\xactengine2_10.dll \ No newline at end of file diff --git a/dist/ddr/gamestart-17.bat b/dist/ddr/gamestart-17.bat index d108118b..89c15da0 100644 --- a/dist/ddr/gamestart-17.bat +++ b/dist/ddr/gamestart-17.bat @@ -1,15 +1,44 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\nvram\ea3-config.xml copy prop\eamuse-config.xml conf\nvram\ea3-config.xml -if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml -if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml -if not exist conf\nvram\testmode-v.xml copy prop\testmode-v.xml conf\nvram\testmode-v.xml -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll +regsvr32 /s %COM_DIR%\xactengine2_10.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s com\k-clvsd.dll -regsvr32 /s com\xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-17.xml %* -.\launcher.exe -H 33554432 -K .\ddrhook2.dll .\arkmdxp3.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll +regsvr32 /u /s %COM_DIR%\xactengine2_10.dll diff --git a/dist/ddr/gamestart-18.bat b/dist/ddr/gamestart-18.bat index d108118b..d73ddf34 100644 --- a/dist/ddr/gamestart-18.bat +++ b/dist/ddr/gamestart-18.bat @@ -1,15 +1,44 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist conf\nvram mkdir conf\nvram -if not exist conf\nvram\ea3-config.xml copy prop\eamuse-config.xml conf\nvram\ea3-config.xml -if not exist conf\nvram\coin.xml copy prop\coin.xml conf\nvram\coin.xml -if not exist conf\nvram\eacoin.xml copy prop\eacoin.xml conf\nvram\eacoin.xml -if not exist conf\nvram\testmode-v.xml copy prop\testmode-v.xml conf\nvram\testmode-v.xml -if not exist conf\raw mkdir conf\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set COM_DIR=%CONTENT_DIR%\com +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Register video codecs, e.g. required for background videos +regsvr32 /s %COM_DIR%\k-clvsd.dll +regsvr32 /s %COM_DIR%\xactengine2_10.dll + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -regsvr32 /s com\k-clvsd.dll -regsvr32 /s com\xactengine2_10.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-18.xml %* -.\launcher.exe -H 33554432 -K .\ddrhook2.dll .\arkmdxp3.dll %* +:: Unregister video codec to avoid conflicts with other/older video codecs crashing different +:: game versions, e.g. DDR X +regsvr32 /u /s %COM_DIR%\k-clvsd.dll +regsvr32 /u /s %COM_DIR%\xactengine2_10.dll diff --git a/dist/ddr/launcher-12.xml b/dist/ddr/launcher-12.xml new file mode 100644 index 00000000..54c0284b --- /dev/null +++ b/dist/ddr/launcher-12.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 31457280 + 31457280 + + + info + + + /dev/nvram/ea3-config.xml + + + ddr.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/launcher-13.xml b/dist/ddr/launcher-13.xml new file mode 100644 index 00000000..54c0284b --- /dev/null +++ b/dist/ddr/launcher-13.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 31457280 + 31457280 + + + info + + + /dev/nvram/ea3-config.xml + + + ddr.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/launcher-14.xml b/dist/ddr/launcher-14.xml new file mode 100644 index 00000000..9ae27ac3 --- /dev/null +++ b/dist/ddr/launcher-14.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 31457280 + 31457280 + + + info + + + /dev/nvram/ea3-config.xml + + + mdxja_945.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/launcher-15.xml b/dist/ddr/launcher-15.xml new file mode 100644 index 00000000..9ae27ac3 --- /dev/null +++ b/dist/ddr/launcher-15.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 31457280 + 31457280 + + + info + + + /dev/nvram/ea3-config.xml + + + mdxja_945.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/launcher-16.xml b/dist/ddr/launcher-16.xml new file mode 100644 index 00000000..cdf16d55 --- /dev/null +++ b/dist/ddr/launcher-16.xml @@ -0,0 +1,76 @@ + + + + bemanitools_local_fs + + + + + + + + + + + + prop/avs-config.xml + 33554432 + 1048576 + + + info + + + /dev/nvram/ea3-config.xml + + + arkmdxp3.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/launcher-17.xml b/dist/ddr/launcher-17.xml new file mode 100644 index 00000000..cdf16d55 --- /dev/null +++ b/dist/ddr/launcher-17.xml @@ -0,0 +1,76 @@ + + + + bemanitools_local_fs + + + + + + + + + + + + prop/avs-config.xml + 33554432 + 1048576 + + + info + + + /dev/nvram/ea3-config.xml + + + arkmdxp3.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/ddr/launcher-18.xml b/dist/ddr/launcher-18.xml new file mode 100644 index 00000000..cdf16d55 --- /dev/null +++ b/dist/ddr/launcher-18.xml @@ -0,0 +1,76 @@ + + + + bemanitools_local_fs + + + + + + + + + + + + prop/avs-config.xml + 33554432 + 1048576 + + + info + + + /dev/nvram/ea3-config.xml + + + arkmdxp3.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + ddrhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/ea3-ident.xml b/dist/iidx/ea3-ident.xml new file mode 100644 index 00000000..e24a9b92 --- /dev/null +++ b/dist/iidx/ea3-ident.xml @@ -0,0 +1,10 @@ + + + + LDJ + J + A + A + 2022082400 + + \ No newline at end of file diff --git a/dist/iidx/eamuse-server.xml b/dist/iidx/eamuse-server.xml new file mode 100644 index 00000000..bc706444 --- /dev/null +++ b/dist/iidx/eamuse-server.xml @@ -0,0 +1,6 @@ + + + + http://localhost + + \ No newline at end of file diff --git a/dist/iidx/gamestart-18.bat b/dist/iidx/gamestart-18.bat index cce99b03..4777dd9c 100755 --- a/dist/iidx/gamestart-18.bat +++ b/dist/iidx/gamestart-18.bat @@ -1,14 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist d mkdir d -if not exist e mkdir e -if not exist f mkdir f +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook4.dll bm2dx.dll --config iidxhook-18.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-18.xml --config %BEMANITOOLS_DIR%\iidxhook-18.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-19.bat b/dist/iidx/gamestart-19.bat index 8bfd5803..03c22b08 100755 --- a/dist/iidx/gamestart-19.bat +++ b/dist/iidx/gamestart-19.bat @@ -1,14 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist d mkdir d -if not exist e mkdir e -if not exist f mkdir f +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook5.dll bm2dx.dll --config iidxhook-19.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-19.xml --config %BEMANITOOLS_DIR%\iidxhook-19.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-20.bat b/dist/iidx/gamestart-20.bat index 2dac80ea..f579f2ef 100755 --- a/dist/iidx/gamestart-20.bat +++ b/dist/iidx/gamestart-20.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook6.dll bm2dx.dll --config iidxhook-20.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-20.xml --config %BEMANITOOLS_DIR%\iidxhook-20.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-21.bat b/dist/iidx/gamestart-21.bat index b79d6416..9ec039dd 100755 --- a/dist/iidx/gamestart-21.bat +++ b/dist/iidx/gamestart-21.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-21.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-21.xml --config %BEMANITOOLS_DIR%\iidxhook-21.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-22.bat b/dist/iidx/gamestart-22.bat index d10a945a..d2847863 100755 --- a/dist/iidx/gamestart-22.bat +++ b/dist/iidx/gamestart-22.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-22.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-22.xml --config %BEMANITOOLS_DIR%\iidxhook-22.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-23.bat b/dist/iidx/gamestart-23.bat index 656e1fe2..33cceb12 100755 --- a/dist/iidx/gamestart-23.bat +++ b/dist/iidx/gamestart-23.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-23.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-23.xml --config %BEMANITOOLS_DIR%\iidxhook-23.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-24.bat b/dist/iidx/gamestart-24.bat index 283020c4..94fa8b2b 100755 --- a/dist/iidx/gamestart-24.bat +++ b/dist/iidx/gamestart-24.bat @@ -1,10 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -K iidxhook7.dll bm2dx.dll --config iidxhook-24.conf %* +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-24.xml --config %BEMANITOOLS_DIR%\iidxhook-24.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-25.bat b/dist/iidx/gamestart-25.bat index edff2940..2d6b6c7c 100644 --- a/dist/iidx/gamestart-25.bat +++ b/dist/iidx/gamestart-25.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 134217728 -K iidxhook8.dll bm2dx.dll --config iidxhook-25.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-25.xml --config %BEMANITOOLS_DIR%\iidxhook-25.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-26.bat b/dist/iidx/gamestart-26.bat index d41b72b3..dc06460f 100644 --- a/dist/iidx/gamestart-26.bat +++ b/dist/iidx/gamestart-26.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 134217728 -K iidxhook8.dll bm2dx.dll --config iidxhook-26.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-26.xml --config %BEMANITOOLS_DIR%\iidxhook-26.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-27.bat b/dist/iidx/gamestart-27.bat index 04a39091..d034f84f 100644 --- a/dist/iidx/gamestart-27.bat +++ b/dist/iidx/gamestart-27.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 134217728 -B iidxhook9.dll bm2dx.dll --config iidxhook-27.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-27.xml --config %BEMANITOOLS_DIR%\iidxhook-27.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-28.bat b/dist/iidx/gamestart-28.bat index 1abb520d..cfc18e45 100644 --- a/dist/iidx/gamestart-28.bat +++ b/dist/iidx/gamestart-28.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 134217728 -B iidxhook9.dll bm2dx.dll --config iidxhook-28.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-28.xml --config %BEMANITOOLS_DIR%\iidxhook-28.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-29.bat b/dist/iidx/gamestart-29.bat index 1029b8c5..64a02a5b 100644 --- a/dist/iidx/gamestart-29.bat +++ b/dist/iidx/gamestart-29.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 134217728 -B iidxhook9.dll bm2dx.dll --config iidxhook-29.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-29.xml --config %BEMANITOOLS_DIR%\iidxhook-29.conf %* \ No newline at end of file diff --git a/dist/iidx/gamestart-30.bat b/dist/iidx/gamestart-30.bat index 776d7e84..61f77205 100644 --- a/dist/iidx/gamestart-30.bat +++ b/dist/iidx/gamestart-30.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -modules\launcher -H 134217728 -B iidxhook9.dll bm2dx.dll --config iidxhook-30.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-30.xml --config %BEMANITOOLS_DIR%\iidxhook-30.conf %* \ No newline at end of file diff --git a/dist/iidx/launcher-18.xml b/dist/iidx/launcher-18.xml new file mode 100644 index 00000000..74bf35e3 --- /dev/null +++ b/dist/iidx/launcher-18.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook4.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-19.xml b/dist/iidx/launcher-19.xml new file mode 100644 index 00000000..1749cfbd --- /dev/null +++ b/dist/iidx/launcher-19.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook5.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-20.xml b/dist/iidx/launcher-20.xml new file mode 100644 index 00000000..7af7ce4f --- /dev/null +++ b/dist/iidx/launcher-20.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook6.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-21.xml b/dist/iidx/launcher-21.xml new file mode 100644 index 00000000..502407b7 --- /dev/null +++ b/dist/iidx/launcher-21.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook7.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-22.xml b/dist/iidx/launcher-22.xml new file mode 100644 index 00000000..502407b7 --- /dev/null +++ b/dist/iidx/launcher-22.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook7.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-23.xml b/dist/iidx/launcher-23.xml new file mode 100644 index 00000000..502407b7 --- /dev/null +++ b/dist/iidx/launcher-23.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook7.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-24.xml b/dist/iidx/launcher-24.xml new file mode 100644 index 00000000..502407b7 --- /dev/null +++ b/dist/iidx/launcher-24.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook7.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-25.xml b/dist/iidx/launcher-25.xml new file mode 100644 index 00000000..733a8807 --- /dev/null +++ b/dist/iidx/launcher-25.xml @@ -0,0 +1,74 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 134217728 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook8.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-26.xml b/dist/iidx/launcher-26.xml new file mode 100644 index 00000000..733a8807 --- /dev/null +++ b/dist/iidx/launcher-26.xml @@ -0,0 +1,74 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 134217728 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + iidxhook8.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-27.xml b/dist/iidx/launcher-27.xml new file mode 100644 index 00000000..736ee635 --- /dev/null +++ b/dist/iidx/launcher-27.xml @@ -0,0 +1,69 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 134217728 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + boot + + + + + + + + + + bemanitools/ea3-service.xml + + + + + + iidxhook9.dll + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-28.xml b/dist/iidx/launcher-28.xml new file mode 100644 index 00000000..736ee635 --- /dev/null +++ b/dist/iidx/launcher-28.xml @@ -0,0 +1,69 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 134217728 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + boot + + + + + + + + + + bemanitools/ea3-service.xml + + + + + + iidxhook9.dll + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-29.xml b/dist/iidx/launcher-29.xml new file mode 100644 index 00000000..736ee635 --- /dev/null +++ b/dist/iidx/launcher-29.xml @@ -0,0 +1,69 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 134217728 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + boot + + + + + + + + + + bemanitools/ea3-service.xml + + + + + + iidxhook9.dll + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/launcher-30.xml b/dist/iidx/launcher-30.xml new file mode 100644 index 00000000..736ee635 --- /dev/null +++ b/dist/iidx/launcher-30.xml @@ -0,0 +1,69 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 134217728 + + + info + + + /dev/nvram/ea3-config.xml + + + bm2dx.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + boot + + + + + + + + + + bemanitools/ea3-service.xml + + + + + + iidxhook9.dll + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/iidx/pcbid.xml b/dist/iidx/pcbid.xml new file mode 100644 index 00000000..bc50cdee --- /dev/null +++ b/dist/iidx/pcbid.xml @@ -0,0 +1,7 @@ + + + + 0101020304050607083F + 0101020304050607083F + + \ No newline at end of file diff --git a/dist/jb/ea3-ident.xml b/dist/jb/ea3-ident.xml new file mode 100644 index 00000000..e24a9b92 --- /dev/null +++ b/dist/jb/ea3-ident.xml @@ -0,0 +1,10 @@ + + + + LDJ + J + A + A + 2022082400 + + \ No newline at end of file diff --git a/dist/jb/eamuse-server.xml b/dist/jb/eamuse-server.xml new file mode 100644 index 00000000..bc706444 --- /dev/null +++ b/dist/jb/eamuse-server.xml @@ -0,0 +1,6 @@ + + + + http://localhost + + \ No newline at end of file diff --git a/dist/jb/gamestart-03.bat b/dist/jb/gamestart-03.bat index 76acc782..39e1b8fd 100644 --- a/dist/jb/gamestart-03.bat +++ b/dist/jb/gamestart-03.bat @@ -1,11 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\ea3-config.xml copy prop\ea3-config.xml dev\nvram\ea3-config.xml -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -H 33554432 -K jbhook2.dll jubeat.dll -v +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-03.xml -v %* \ No newline at end of file diff --git a/dist/jb/gamestart-04.bat b/dist/jb/gamestart-04.bat index 245e314d..14d95e78 100644 --- a/dist/jb/gamestart-04.bat +++ b/dist/jb/gamestart-04.bat @@ -1,11 +1,34 @@ @echo off +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. cd /d %~dp0 -if not exist dev\nvram mkdir dev\nvram -if not exist dev\nvram\ea3-config.xml copy prop\ea3-config.xml dev\nvram\ea3-config.xml -if not exist dev\nvram\coin.xml copy prop\defaults\coin.xml dev\nvram\coin.xml -if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin.xml -if not exist dev\raw mkdir dev\raw +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% -launcher -H 33554432 -K jbhook3.dll jubeat.dll +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-04.xml %* \ No newline at end of file diff --git a/dist/jb/launcher-03.xml b/dist/jb/launcher-03.xml new file mode 100644 index 00000000..095b2f62 --- /dev/null +++ b/dist/jb/launcher-03.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 33554432 + 33554432 + + + info + + + /dev/nvram/ea3-config.xml + + + jubeat.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + jbhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/jb/launcher-04.xml b/dist/jb/launcher-04.xml new file mode 100644 index 00000000..1c85a3c8 --- /dev/null +++ b/dist/jb/launcher-04.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 33554432 + 33554432 + + + info + + + /dev/nvram/ea3-config.xml + + + jubeat.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + jbhook3.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/jb/pcbid.xml b/dist/jb/pcbid.xml new file mode 100644 index 00000000..bc50cdee --- /dev/null +++ b/dist/jb/pcbid.xml @@ -0,0 +1,7 @@ + + + + 0101020304050607083F + 0101020304050607083F + + \ No newline at end of file diff --git a/dist/sdvx/ea3-ident.xml b/dist/sdvx/ea3-ident.xml new file mode 100644 index 00000000..e24a9b92 --- /dev/null +++ b/dist/sdvx/ea3-ident.xml @@ -0,0 +1,10 @@ + + + + LDJ + J + A + A + 2022082400 + + \ No newline at end of file diff --git a/dist/sdvx/eamuse-server.xml b/dist/sdvx/eamuse-server.xml new file mode 100644 index 00000000..bc706444 --- /dev/null +++ b/dist/sdvx/eamuse-server.xml @@ -0,0 +1,6 @@ + + + + http://localhost + + \ No newline at end of file diff --git a/dist/sdvx/gamestart.bat b/dist/sdvx/gamestart.bat index a286325c..4e97b924 100644 --- a/dist/sdvx/gamestart.bat +++ b/dist/sdvx/gamestart.bat @@ -8,3 +8,38 @@ if not exist dev\nvram\eacoin.xml copy prop\defaults\eacoin.xml dev\nvram\eacoin if not exist dev\raw mkdir dev\raw launcher -K sdvxhook.dll soundvoltex.dll %* + +@echo off + +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session + +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 +) + +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher.xml %* \ No newline at end of file diff --git a/dist/sdvx/launcher.xml b/dist/sdvx/launcher.xml new file mode 100644 index 00000000..4876ca76 --- /dev/null +++ b/dist/sdvx/launcher.xml @@ -0,0 +1,75 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 16777216 + 16777216 + + + info + + + /dev/nvram/ea3-config.xml + + + soundvoltex.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + sdvxhook.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/sdvx/pcbid.xml b/dist/sdvx/pcbid.xml new file mode 100644 index 00000000..bc50cdee --- /dev/null +++ b/dist/sdvx/pcbid.xml @@ -0,0 +1,7 @@ + + + + 0101020304050607083F + 0101020304050607083F + + \ No newline at end of file diff --git a/dist/sdvx5/ea3-ident.xml b/dist/sdvx5/ea3-ident.xml new file mode 100644 index 00000000..e24a9b92 --- /dev/null +++ b/dist/sdvx5/ea3-ident.xml @@ -0,0 +1,10 @@ + + + + LDJ + J + A + A + 2022082400 + + \ No newline at end of file diff --git a/dist/sdvx5/eamuse-server.xml b/dist/sdvx5/eamuse-server.xml new file mode 100644 index 00000000..bc706444 --- /dev/null +++ b/dist/sdvx5/eamuse-server.xml @@ -0,0 +1,6 @@ + + + + http://localhost + + \ No newline at end of file diff --git a/dist/sdvx5/gamestart-cn.bat b/dist/sdvx5/gamestart-cn.bat index 8435bd73..265fce19 100644 --- a/dist/sdvx5/gamestart-cn.bat +++ b/dist/sdvx5/gamestart-cn.bat @@ -1,17 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\c.dest echo $null >> dev\raw\c.dest -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 268435456 -K sdvxhook2-cn.dll soundvoltex.dll --config sdvxhook2-cn.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher-cn.xml --config %BEMANITOOLS_DIR%\sdvxhook2-cn.conf %* \ No newline at end of file diff --git a/dist/sdvx5/gamestart.bat b/dist/sdvx5/gamestart.bat index c1b41ddd..2db92894 100644 --- a/dist/sdvx5/gamestart.bat +++ b/dist/sdvx5/gamestart.bat @@ -1,16 +1,34 @@ @echo off -cd /d %~dp0 -if not exist dev mkdir dev -if not exist dev\e mkdir dev\e -if not exist dev\g mkdir dev\g -if not exist dev\nvram mkdir dev\nvram -if not exist dev\raw mkdir dev\raw -if not exist dev\raw\log mkdir dev\raw\log -if not exist dev\raw\fscache mkdir dev\raw\fscache +:: Game doesn't work properly when not run with administrator privileges +>nul 2>&1 net session -for /R prop\defaults %%D in (*.*) do ( - if not exist dev\nvram\%%~nxD copy /y prop\defaults\%%~nxD dev\nvram +if %errorlevel% neq 0 ( + echo This script requires administrative privileges. + echo Please run the script as an administrator. + pause + exit 1 ) -launcher -H 268435456 -K sdvxhook2.dll soundvoltex.dll --config sdvxhook2.conf %* +:: Script expects to be located in a subfolder "bemanitools" in the root folder +:: (contents/) next to the folders modules, data etc. +cd /d %~dp0 + +:: Script expects to be located in the root folder (contents/) next to the +:: folders modules, data etc. +set CONTENT_DIR=%CD%\.. +set BEMANITOOLS_DIR=%CONTENT_DIR%\bemanitools +set MODULES_DIR=%CONTENT_DIR%\modules + +:: Keep that data vanilla, no need to copy these around anymore +:: Just add them to the env PATH so launcher can find the libs and game executable +:: Remark: This also requires admin privileges to propage correctly to launcher +set PATH=^ +%MODULES_DIR%;^ +%BEMANITOOLS_DIR%;^ +%PATH% + +:: Current working dir is the game's root folder +cd /d %CONTENT_DIR% + +%BEMANITOOLS_DIR%\launcher %BEMANITOOLS_DIR%\launcher.xml --config %BEMANITOOLS_DIR%\sdvxhook2.conf %* \ No newline at end of file diff --git a/dist/sdvx5/launcher-cn.xml b/dist/sdvx5/launcher-cn.xml new file mode 100644 index 00000000..4ec2939f --- /dev/null +++ b/dist/sdvx5/launcher-cn.xml @@ -0,0 +1,74 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 268435456 + + + info + + + /dev/nvram/ea3-config.xml + + + soundvoltex.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + sdvxhook2-cn.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/sdvx5/launcher.xml b/dist/sdvx5/launcher.xml new file mode 100644 index 00000000..3ae97c1e --- /dev/null +++ b/dist/sdvx5/launcher.xml @@ -0,0 +1,74 @@ + + + + bemanitools_local_fs + + + + + + + + + + + prop/avs-config.xml + 268435456 + + + info + + + /dev/nvram/ea3-config.xml + + + soundvoltex.dll + + + + + + + + + bemanitools/ea3-ident.xml + bemanitools/ea3-license.xml + + + + + + + . + + + dev/nvram + fs + + + + dev/raw + fs + + + + + + + + bemanitools/ea3-service.xml + + + + sdvxhook2.dll + + + + + + + + 0 + 0 + + \ No newline at end of file diff --git a/dist/sdvx5/pcbid.xml b/dist/sdvx5/pcbid.xml new file mode 100644 index 00000000..bc50cdee --- /dev/null +++ b/dist/sdvx5/pcbid.xml @@ -0,0 +1,7 @@ + + + + 0101020304050607083F + 0101020304050607083F + + \ No newline at end of file diff --git a/dist/shared/ea3-ident.xml b/dist/shared/ea3-ident.xml new file mode 100644 index 00000000..ae4161ef --- /dev/null +++ b/dist/shared/ea3-ident.xml @@ -0,0 +1,11 @@ + + + + + 000 + A + A + A + 1969032100 + + \ No newline at end of file diff --git a/dist/shared/ea3-license.xml b/dist/shared/ea3-license.xml new file mode 100644 index 00000000..fef35346 --- /dev/null +++ b/dist/shared/ea3-license.xml @@ -0,0 +1,8 @@ + + + + + 0101020304050607083F + 0101020304050607083F + + \ No newline at end of file diff --git a/dist/shared/ea3-service.xml b/dist/shared/ea3-service.xml new file mode 100644 index 00000000..ffaaa437 --- /dev/null +++ b/dist/shared/ea3-service.xml @@ -0,0 +1,7 @@ + + + + + http://localhost + + \ No newline at end of file From 4b38f50a3964c23b8fd638c44a7ec3eb6e531b13 Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 09/90] fix(mingw): Version upgrade caused this to error HRESULT not defined fixed by including windows header --- src/main/hook/com-proxy.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/hook/com-proxy.h b/src/main/hook/com-proxy.h index 94671b7c..71d29538 100644 --- a/src/main/hook/com-proxy.h +++ b/src/main/hook/com-proxy.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include From 4b352ed080de956623b9f8abf2ef47ce687f22d5 Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 10/90] feat(inject): Use new exception handler with stacktraces Adjust inject to utilize the new feature. This also requires including the dwarfstack.dll in all distribution packages. --- Module.mk | 8 ++++++++ src/main/inject/Module.mk | 3 +++ src/main/inject/debugger.c | 5 +++-- src/main/inject/main.c | 3 +++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Module.mk b/Module.mk index de08e82b..eb28f826 100644 --- a/Module.mk +++ b/Module.mk @@ -259,6 +259,7 @@ $(zipdir)/iidx-09-to-12.zip: \ build/bin/indep-32/iidxio.dll \ build/bin/indep-32/vefxio.dll \ build/bin/indep-32/inject.exe \ + dist/dwarfstack/32/dwarfstack.dll \ dist/iidx/config.bat \ dist/iidx/gamestart-09.bat \ dist/iidx/gamestart-10.bat \ @@ -284,6 +285,7 @@ $(zipdir)/iidx-13.zip: \ build/bin/indep-32/iidxio.dll \ build/bin/indep-32/vefxio.dll \ build/bin/indep-32/inject.exe \ + dist/dwarfstack/32/dwarfstack.dll \ dist/iidx/config.bat \ dist/iidx/gamestart-13.bat \ dist/iidx/iidxhook-13.conf \ @@ -300,6 +302,7 @@ $(zipdir)/iidx-14-to-17.zip: \ build/bin/indep-32/iidxio.dll \ build/bin/indep-32/vefxio.dll \ build/bin/indep-32/inject.exe \ + dist/dwarfstack/32/dwarfstack.dll \ dist/iidx/config.bat \ dist/iidx/gamestart-14.bat \ dist/iidx/gamestart-15.bat \ @@ -518,6 +521,7 @@ $(zipdir)/jb-01.zip: \ build/bin/indep-32/eamio.dll \ build/bin/indep-32/geninput.dll \ build/bin/indep-32/jbio.dll \ + dist/dwarfstack/32/dwarfstack.dll \ dist/jb/config.bat \ dist/jb/gamestart-01.bat \ dist/jb/jbhook-01.conf \ @@ -532,6 +536,7 @@ $(zipdir)/jb-02.zip: \ build/bin/indep-32/eamio.dll \ build/bin/indep-32/geninput.dll \ build/bin/indep-32/jbio.dll \ + dist/dwarfstack/32/dwarfstack.dll \ dist/jb/config.bat \ dist/jb/gamestart-02.bat \ dist/jb/jbhook-02.conf \ @@ -720,6 +725,7 @@ $(zipdir)/ddr-12-us.zip: \ build/bin/indep-32/ddrio-smx.dll \ build/bin/indep-32/eamio.dll \ build/bin/indep-32/geninput.dll \ + dist/dwarfstack/32/dwarfstack.dll \ dist/ddr/config.bat \ dist/ddr/gamestart-12-us.bat \ dist/ddr/gamestart-12-eu.bat \ @@ -872,6 +878,7 @@ $(zipdir)/popn-15-to-18.zip: \ build/bin/indep-32/eamio.dll \ build/bin/indep-32/popnio.dll \ build/bin/indep-32/ezusb2-popn-shim.dll \ + dist/dwarfstack/32/dwarfstack.dll \ dist/popn/config.bat \ dist/popn/gamestart-15.bat \ dist/popn/gamestart-16.bat \ @@ -903,6 +910,7 @@ $(BUILDDIR)/tests.zip: \ build/bin/indep-32/iidxhook-util-config-gfx-test.exe \ build/bin/indep-32/iidxhook-util-config-misc-test.exe \ build/bin/indep-32/iidxhook-util-config-sec-test.exe \ + dist/dwarfstack/32/dwarfstack.dll \ build/bin/indep-32/inject.exe \ build/bin/indep-32/security-id-test.exe \ build/bin/indep-32/security-mcode-test.exe \ diff --git a/src/main/inject/Module.mk b/src/main/inject/Module.mk index fd868ef4..5fb93dc8 100644 --- a/src/main/inject/Module.mk +++ b/src/main/inject/Module.mk @@ -1,12 +1,15 @@ exes += inject +imps += dwarfstack ldflags_inject := \ -mconsole \ -lpsapi \ + -ldbghelp \ libs_inject := \ core \ util \ + dwarfstack \ src_inject := \ main.c \ diff --git a/src/main/inject/debugger.c b/src/main/inject/debugger.c index e8e7068c..69f0a74d 100644 --- a/src/main/inject/debugger.c +++ b/src/main/inject/debugger.c @@ -13,9 +13,10 @@ #include "inject/debugger.h" +#include "util/debug.h" +#include "util/log.h" #include "util/mem.h" #include "util/proc.h" -#include "util/signal.h" #include "util/str.h" #define MM_ALLOCATION_GRANULARITY 0x10000 @@ -275,7 +276,7 @@ static uint32_t debugger_loop() "EXCEPTION_DEBUG_EVENT(pid %ld, tid %ld): x%s 0x%p", de.dwProcessId, de.dwThreadId, - signal_exception_code_to_str( + debug_exception_code_to_str( de.u.Exception.ExceptionRecord.ExceptionCode), de.u.Exception.ExceptionRecord.ExceptionAddress); diff --git a/src/main/inject/main.c b/src/main/inject/main.c index 07026dcb..da80a8b5 100644 --- a/src/main/inject/main.c +++ b/src/main/inject/main.c @@ -28,6 +28,8 @@ #include "inject/version.h" #include "util/cmdline.h" +#include "util/debug.h" +#include "util/log.h" #include "util/mem.h" #include "util/os.h" #include "util/signal.h" @@ -222,6 +224,7 @@ int main(int argc, char **argv) os_version_log(); + debug_init(); signal_exception_handler_init(); // Cleanup remote process on CTRL+C signal_register_shutdown_handler(signal_shutdown_handler); From 1b8bf7b2459d8b5ed0a433225583d8e1f1cc9be5 Mon Sep 17 00:00:00 2001 From: icex2 Date: Wed, 14 Aug 2024 17:53:51 +0200 Subject: [PATCH 11/90] feat(util): Separate debug module with proper stacktrace printing Because we are using mingw, we can't just use window's dbghelp library as the symbols created are in dwarf format. Fortunately, the dwarfstack library already provides all the facilities to easily print very descriptive stacktraces, including function names, file names and line numbers, when dwarf symbols are available. This moves the incomplete exception handling portion from signal to a separate module as well to improve scoping. --- dist/dwarfstack/32/dwarfstack.dll | Bin 0 -> 555008 bytes dist/dwarfstack/64/dwarfstack.dll | Bin 0 -> 519168 bytes dist/dwarfstack/readme.md | 1 + src/imports/dwarfstack.h | 162 +++++++++++++++++ src/imports/import_32_indep_dwarfstack.def | 13 ++ src/imports/import_64_indep_dwarfstack.def | 13 ++ src/main/util/Module.mk | 1 + src/main/util/debug.c | 193 +++++++++++++++++++++ src/main/util/debug.h | 12 ++ src/main/util/signal.c | 83 --------- 10 files changed, 395 insertions(+), 83 deletions(-) create mode 100644 dist/dwarfstack/32/dwarfstack.dll create mode 100644 dist/dwarfstack/64/dwarfstack.dll create mode 100644 dist/dwarfstack/readme.md create mode 100644 src/imports/dwarfstack.h create mode 100644 src/imports/import_32_indep_dwarfstack.def create mode 100644 src/imports/import_64_indep_dwarfstack.def create mode 100644 src/main/util/debug.c create mode 100644 src/main/util/debug.h diff --git a/dist/dwarfstack/32/dwarfstack.dll b/dist/dwarfstack/32/dwarfstack.dll new file mode 100644 index 0000000000000000000000000000000000000000..74b9796d0740d80ab1771704689e9e960ca6db70 GIT binary patch literal 555008 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

KBB7MRsob6CzwA@ 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≦ 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