diff --git a/Makefile b/Makefile index 5e598e0..9d09b04 100644 --- a/Makefile +++ b/Makefile @@ -15,9 +15,9 @@ OBJS := $(filter-out src/lindbergh/lindbergh.o, $(OBJS)) all: lindbergh libxdiff.a lindbergh.so libsegaapi.so libkswapapi.so libposixtime.so -lindbergh: src/lindbergh/lindbergh.c src/lindbergh/jvs.c src/lindbergh/jvs.h src/lindbergh/config.h src/lindbergh/config.c src/lindbergh/evdevinput.h src/lindbergh/evdevinput.c +lindbergh: src/lindbergh/lindbergh.c src/lindbergh/log.c src/lindbergh/log.h src/lindbergh/jvs.c src/lindbergh/jvs.h src/lindbergh/config.h src/lindbergh/config.c src/lindbergh/evdevinput.h src/lindbergh/evdevinput.c mkdir -p $(BUILD) - $(CC) src/lindbergh/lindbergh.c src/lindbergh/jvs.h src/lindbergh/jvs.c src/lindbergh/config.h src/lindbergh/config.c src/lindbergh/evdevinput.c src/lindbergh/evdevinput.h -o $(BUILD)/lindbergh -lm + $(CC) src/lindbergh/lindbergh.c src/lindbergh/log.h src/lindbergh/log.c src/lindbergh/jvs.h src/lindbergh/jvs.c src/lindbergh/config.h src/lindbergh/config.c src/lindbergh/evdevinput.c src/lindbergh/evdevinput.h -o $(BUILD)/lindbergh -lm libxdiff.a: $(XDIFF_OBJS) mkdir -p $(BUILD) diff --git a/src/lindbergh/baseboard.c b/src/lindbergh/baseboard.c index a5c3cae..1bfd9e7 100644 --- a/src/lindbergh/baseboard.c +++ b/src/lindbergh/baseboard.c @@ -11,6 +11,7 @@ #include "config.h" #include "jvs.h" #include "passthrough.h" +#include "log.h" #define SERIAL_STRING "FE11-X018012022X" @@ -178,7 +179,7 @@ int baseboardIoctl(int fd, unsigned int request, void *data) case BASEBOARD_WRITE_FLASH: // bcCmdSysFlashWrite { - printf("Warning: The game attempted to write to the baseboard flash\n"); + log_warn("The game attempted to write to the baseboard flash\n"); } break; diff --git a/src/lindbergh/config.c b/src/lindbergh/config.c index 447da67..e80f25d 100644 --- a/src/lindbergh/config.c +++ b/src/lindbergh/config.c @@ -5,6 +5,7 @@ #include "config.h" #include "gpuvendor.h" +#include "log.h" EmulatorConfig config = {0}; @@ -1015,7 +1016,7 @@ int readConfig(FILE *configFile, EmulatorConfig *config) else { // Print a warning and keep the default colour - printf("Warning: Unknown Lindbergh colour '%s'. Keeping default value.\n", colour); + log_warn("Unknown Lindbergh colour '%s'. Keeping default value.\n", colour); } } @@ -1032,7 +1033,7 @@ int readConfig(FILE *configFile, EmulatorConfig *config) else { // Print a warning and keep the default region - printf("Warning: Unknown Region '%s'. Keeping default value.\n", region); + log_warn("Unknown Region '%s'. Keeping default value.\n", region); } } @@ -1341,7 +1342,7 @@ int initConfig() config.skipOutrunCabinetCheck = 0; if (detectGame(config.crc32) != 0) { - printf("Warning: Unsure what game with CRC 0x%X is. Please submit this new game to the GitHub repository: " + log_warn("Unsure what game with CRC 0x%X is. Please submit this new game to the GitHub repository: " "https://github.com/lindbergh-loader/lindbergh-loader/issues/" "new?title=Please+add+new+game+0x%X&body=I+tried+to+launch+the+following+game:\n", config.crc32, config.crc32); @@ -1353,7 +1354,7 @@ int initConfig() if (configFile == NULL) { - printf("Warning: Cannot open %s, using default values.\n", CONFIG_PATH); + log_warn("Cannot open %s, using default values.\n", CONFIG_PATH); return 1; } diff --git a/src/lindbergh/evdevinput.c b/src/lindbergh/evdevinput.c index e19ce21..08e4364 100644 --- a/src/lindbergh/evdevinput.c +++ b/src/lindbergh/evdevinput.c @@ -12,6 +12,7 @@ #include "evdevinput.h" #include "config.h" #include "jvs.h" +#include "log.h" int jvsBits = 10; @@ -1249,7 +1250,7 @@ ControllerStatus startControllerThreads(Controllers *controllers) ControllerStatus status = getArcadeInputByName(mapping, &input); if (status != CONTROLLER_STATUS_SUCCESS) { - printf("Warning: Couldn't find arcade mapping with title %s\n", mapping); + log_warn("Couldn't find arcade mapping with title %s\n", mapping); continue; } diff --git a/src/lindbergh/hook.c b/src/lindbergh/hook.c index bc1869a..42288b7 100644 --- a/src/lindbergh/hook.c +++ b/src/lindbergh/hook.c @@ -47,6 +47,7 @@ #include "shader_patches.h" #include "fps_limiter.h" #include "evdevinput.h" +#include "log.h" #define HOOK_FILE_NAME "/dev/zero" @@ -154,7 +155,8 @@ static void handleSegfault(int signal, siginfo_t *info, void *ptr) break; default: - repeat_printf("Warning: Skipping SEGFAULT %X\n", *code); + // repeat_printf("Skipping SEGFAULT %X\n", *code); + log_warn("Skipping SEGFAULT %X\n", *code); ctx->uc_mcontext.gregs[REG_EIP]++; // abort(); } @@ -217,6 +219,8 @@ void __attribute__((constructor)) hook_init() if (initControllers(&controllers) != 0) exit(1); + + securityBoardSetDipResolution(getConfig()->width, getConfig()->height); printf("\nSEGA Lindbergh Emulator\nBy the Lindbergh Development Team 2025\n\n"); @@ -309,7 +313,7 @@ int open(const char *pathname, int flags, ...) return -1; hooks[SERIAL0] = _open(HOOK_FILE_NAME, flags, mode); - printf("Warning: SERIAL0 Opened %d\n", hooks[SERIAL0]); + log_warn("SERIAL0 Opened %d\n", hooks[SERIAL0]); return hooks[SERIAL0]; } @@ -322,7 +326,7 @@ int open(const char *pathname, int flags, ...) return -1; hooks[SERIAL1] = _open(HOOK_FILE_NAME, flags, mode); - printf("Warning: SERIAL1 opened %d\n", hooks[SERIAL1]); + log_warn("SERIAL1 opened %d\n", hooks[SERIAL1]); return hooks[SERIAL1]; } @@ -1044,104 +1048,3 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) } return _localtime_r(timep, result); } - -/** - * @brief Prints formatted text in a way that avoids spamming identical lines. - * - * This function behaves similarly to printf, but tracks the last printed message. - * If the newly formatted string is identical to the last one, the previous console - * line is overwritten and a repetition counter "(xN)" is appended instead of - * printing a new line. - * - * @details - * Usage: - * 1. Call `repeat_printf("Some message");` as you would call printf(...). - * 2. If the next call uses the exact same message, the console line updates - * to "... (x2)", then "... (x3)", etc. - * 3. If the next call uses a different message, the old message is finalized - * (a newline is printed) and the new message is printed on a fresh line. - * 4. Calling `live_printf("")` or `repeat_printf(NULL)` finalizes the current - * message (if any) and resets the internal state. - * - * If the message ends with a newline ('\n'), the cursor is moved up one line - * after printing. This allows consecutive identical lines that end with '\n' - * to be updated in place, though note it may be visually confusing if your - * terminal is at the top line. - * - * @param format A printf-style format string, or NULL. If NULL or an empty - * string results from formatting, the current tracked message - * is finalized (a newline is printed, and the internal state - * is reset). - * @param ... Variadic arguments for the format string. - * - * @return (void) No return value. - */ -void repeat_printf(const char *format, ...) -{ - static char last_msg[256] = {0}; - static u_int repeat_count = 0; - - // If format is NULL, treat it as an empty string to trigger finalization. - if (format == NULL) { - format = ""; - } - - // Construct the new message using vsnprintf (like printf). - char msg[256]; - va_list args; - va_start(args, format); - vsnprintf(msg, sizeof(msg), format, args); - va_end(args); - - // If the constructed message is empty, finalize and reset. - if (msg[0] == '\0') { - if (repeat_count > 0) { - // Finalize the previous message with a newline. - printf("\n"); - fflush(stdout); - } - // Reset tracking variables. - last_msg[0] = '\0'; - repeat_count = 0; - return; - } - - // Check if the new message is the same as the last one. - if (strcmp(last_msg, msg) == 0) { - // Same message => increment counter, overwrite the line. - repeat_count++; - - // If the message ends with new line, we go up one line - size_t len = strlen(msg); - int ends_with_newline = 0; - if (len > 0 && msg[len - 1] == '\n') { - printf("\033[1A"); - ends_with_newline = 1; - msg[len - 1] = '\0'; // Remove the trailing '\n' - } - // Move cursor to the start of the line and clear it. - printf("\r\033[K"); - // Print the message with the updated repeat count if > 1 - if (repeat_count > 1) { - printf("%s (x%d)", msg, repeat_count); - } else { - // If repeat_count == 1, it means the first time for this message - // on this call. (Normally we shouldn't enter here with == 1 though.) - printf("%s", msg); - } - if (ends_with_newline) { - // Print a newline, then move cursor up one line - printf("\n"); - } - fflush(stdout); - } else { - // Track the new message. - strncpy(last_msg, msg, sizeof(last_msg) - 1); - last_msg[sizeof(last_msg) - 1] = '\0'; - repeat_count = 1; - - // Print the new message on a fresh line. - printf("%s", msg); - fflush(stdout); - } -} diff --git a/src/lindbergh/hook.h b/src/lindbergh/hook.h index 8880774..43801f4 100644 --- a/src/lindbergh/hook.h +++ b/src/lindbergh/hook.h @@ -1,5 +1,4 @@ #include #include -uint32_t get_crc32(const char *s, size_t n); -void repeat_printf(const char *format, ...); +uint32_t get_crc32(const char *s, size_t n); \ No newline at end of file diff --git a/src/lindbergh/lindbergh.c b/src/lindbergh/lindbergh.c index 0e21f38..29b1989 100644 --- a/src/lindbergh/lindbergh.c +++ b/src/lindbergh/lindbergh.c @@ -6,6 +6,7 @@ #include "evdevinput.h" #include "version.h" +#include "log.h" #define LD_LIBRARY_PATH "LD_LIBRARY_PATH" #define LD_PRELOAD "LD_PRELOAD" @@ -183,7 +184,7 @@ int main(int argc, char *argv[]) if (dir == NULL) { - printf("Error: Could not list files in current directory.\n"); + log_error("Could not list files in current directory."); return EXIT_FAILURE; } @@ -237,7 +238,7 @@ int main(int argc, char *argv[]) if (!lindberghSharedObjectFound) { - printf("Error: The preload object lindbergh.so was not found in this directory.\n"); + log_error("The preload object lindbergh.so was not found in this directory."); return EXIT_FAILURE; } @@ -288,7 +289,7 @@ int main(int argc, char *argv[]) { if (game == NULL) { - printf("Error: No lindbergh game found in this directory.\n"); + log_error("No lindbergh game found in this directory."); return EXIT_FAILURE; } strcat(command, game); @@ -305,7 +306,7 @@ int main(int argc, char *argv[]) strcpy(command, temp); } - printf("$ %s\n", command); + log_info("Starting $ %s", command); return system(command); } diff --git a/src/lindbergh/log.c b/src/lindbergh/log.c new file mode 100644 index 0000000..e1adf9b --- /dev/null +++ b/src/lindbergh/log.c @@ -0,0 +1,466 @@ +#include "log.h" +#include +#include +#include +#include + +// Control look (1 = enabled, else 0) +#define LOG_COLOR_ENABLED 1 +#define LOG_TIME_ENABLED 1 + +// Minimum level to output logs, may be nice to replace it with a list in the future +#define LOG_MIN_LEVEL 2 + +// When enabled (1), it counts repeat message instead of spamming +#define LOG_NO_REPEAT 1 +// Buffer size to check if a message is repeated. Too small will miss repeats, but too big take few bytes of memory +#define LOG_REPEAT_BUFFER_SIZE 8192 + + +// Global variable to store the start time of the program +static struct timespec logStartTime = {0, 0}; + +// Hold a counter of message repeat +static unsigned int logRepeatCount = 0; +static char logLastMessage[LOG_REPEAT_BUFFER_SIZE] = {0}; + +static const char *log_style[] = { + "\x1b[0m", // TRACE : Normal default color + "\x1b[36m", // DEBUG : Cyan + "\x1b[0m", // GAME : Normal default color + "\x1b[32m", // INFO : Green + "\x1b[33m", // WARN : Yellow + "\x1b[31m", // ERROR : Red + "\x1b[35m", // FATAL : Magenta +}; + +static const char *log_names[] = { + "TRACE", "DEBUG", "GAME", "INFO", "WARN", "ERROR", "FATAL" +}; + +/** + * @brief Logs a formatted message with metadata using a variadic argument list. + * + * This function facilitates logging operations where variadic arguments (`va_list`) are used + * to dynamically format the message. It performs sanity checks, formats the message, + * appends log metadata (e.g., log level, file, and line number), and writes the log entry + * to the appropriate stream. + * + * @param level The severity level of the log message (e.g., LOG_TRACE, LOG_INFO, LOG_ERROR). + * Must be a valid log level defined in the logging system. + * @param file The name of the source file where the log function was called. + * Typically provided as `__FILE__` in the caller. + * @param line The line number in the source file where the log function was called. + * Typically provided as `__LINE__` in the caller. + * @param message The format string for the log message, similar to printf. + * Must be a valid null-terminated string. + * @param args A `va_list` containing the variadic arguments for the format string. + * + * @return + * - `0`: Logging was skipped (e.g., level below the minimum threshold). + * - `-1`: An error occurred (e.g., invalid log level, null message, or memory allocation failure). + * - `>0`: Number of characters written to the log stream. + * + * Example usage: + * @code + * va_list args; + * va_start(args, format); + * logVA(LOG_INFO, __FILE__, __LINE__, "User %s logged in successfully", args); + * va_end(args); + * @endcode + * + * @warning + * - Ensure that the `level` parameter is valid and within the defined log levels. + * - The `message` parameter must not be NULL. Pass a valid format string. + * - This function dynamically allocates memory for the formatted message. The allocated memory + * is freed internally before the function returns. + * - The `args` parameter must be initialized using `va_start` before calling this function and + * cleaned up with `va_end` after use. + * + * @note + * - The function uses the following helper functions: + * - `logSanityChecks`: Validates the log level and message. + * - `logFormatMessage`: Dynamically formats the message string using `va_list`. + * - `logGetStream`: Determines the appropriate log stream (e.g., stdout, stderr). + * - `logPrintHeader`: Writes log metadata (e.g., timestamp, log level). + * - `logPrintMessage`: Writes the formatted message to the log stream. + * + * @see logSanityChecks, logFormatMessage, logGetStream, logPrintHeader, logPrintMessage + */ + +int logVA(int level, const char *file, int line, const char *message, va_list args) { + + // Init return value and sanity check inputs + int ret = logSanityChecks(level, message); + if (ret < 1) { + // Something went wrong :( + return ret; + } + + // Format the message + LogFormattedMessage formattedMessage = logFormatMessage(message, args); + + FILE * stream = logGetStream(); + logPrintHeader(stream, level); + + ret = logPrintMessage(stream, formattedMessage, level); + + // Cleanup + free(formattedMessage.message); + + return ret; +} + +/** + * @brief Logs a formatted message with metadata to a specific stream. + * + * This function handles logging operations by performing sanity checks, + * formatting the message, and appending log metadata (e.g., log level, file, and line number). + * It writes the formatted log entry to the appropriate stream, which is determined + * based on the log level. + * + * @param level The severity level of the log message (e.g., LOG_TRACE, LOG_INFO, LOG_ERROR). + * Must be a valid log level defined in the logging system. + * @param file The name of the source file where the log function was called. + * Typically provided as `__FILE__` in the caller. + * @param line The line number in the source file where the log function was called. + * Typically provided as `__LINE__` in the caller. + * @param message The format string for the log message, similar to printf. + * Must be a valid null-terminated string. + * @param ... Additional arguments for the format string. + * + * @return + * - `0`: Logging was skipped (e.g., level below the minimum threshold). + * - `-1`: An error occurred (e.g., invalid log level, null message, or memory allocation failure). + * - `>0`: Number of characters written to the log stream. + * + * Example usage: + * @code + * logGeneric(LOG_INFO, __FILE__, __LINE__, "User %s logged in successfully", username); + * @endcode + * + * @warning + * - Ensure that the `level` parameter is valid and within the defined log levels. + * - The `message` parameter must not be NULL. Pass a valid format string. + * - This function allocates memory for the formatted message. The allocated memory + * is freed internally before the function returns. + * + * @note + * - The function uses the following helper functions: + * - `logSanityChecks`: Validates the log level and message. + * - `logFormatMessage`: Formats the message string dynamically. + * - `logGetStream`: Determines the appropriate log stream (e.g., stdout, stderr). + * - `logPrintHeader`: Prints the log metadata (e.g., timestamp, log level). + * - `logPrintMessage`: Writes the formatted message to the stream. + * + * @see logSanityChecks, logFormatMessage, logGetStream, logPrintHeader, logPrintMessage + */ +int logGeneric(int level, const char *file, int line, const char *message, ...) { + + // Init return value and sanity check inputs + int ret = logSanityChecks(level, message); + if (ret < 1) { + // Something went wrong :( + return ret; + } + + + // Format the message + va_list args; + va_start(args, message); + LogFormattedMessage formattedMessage = logFormatMessage(message, args); + va_end(args); + + FILE * stream = logGetStream(); + logPrintHeader(stream, level); + + ret = logPrintMessage(stream, formattedMessage, level); + + // Cleanup + free(formattedMessage.message); + + return ret; +} + +/** + * @brief Prints a formatted log message to the specified stream, handling message repetition. + * + * This function handles the output of log messages, including detecting repeated messages + * and displaying a repeat counter instead of duplicating the same message. It also ensures + * proper formatting of the message based on its content (e.g., whether it ends with a newline). + * + * @param stream The output stream to write the log message to (e.g., stdout, stderr, or a file). + * @param formattedMessage A structure containing the formatted log message, its metadata, and repeat count. + * - `message`: The formatted log message string. + * - `repeat`: The number of times the message has been repeated. + * - `endWithNewLine`: Flag indicating if the message ends with a newline character. + * @param level The log level of the message, used for printing headers if needed. + * + * @return The number of characters written to the stream, or a negative value if an error occurs. + * + * @warning + * - Ensure `stream` is a valid `FILE*` pointer. + * - The `formattedMessage` must be properly initialized with valid data before calling this function. + * + * @note + * - If `LOG_NO_REPEAT` is enabled and the message has been repeated, the function clears the current + * line and rewrites the header with a repeat counter. + * - The `logPrintHeader` function is called to reprint the log metadata when handling repeated messages. + * + * Example usage: + * @code + * LogFormattedMessage msg = {"This is a test message", 0, 1}; + * logPrintMessage(stdout, msg, LOG_INFO); + * @endcode + * + * @see logPrintHeader + */ +int logPrintMessage(FILE *stream, LogFormattedMessage formattedMessage, int level) { + int ret = 0; + if (LOG_NO_REPEAT && formattedMessage.repeat > 0) { + if (formattedMessage.repeat > 1) { + // Not the first repeat + // We clear current line to delete the header + printf("\r\033[K"); + // We go up one line, go first char and clear it + printf("\033[1A\r\033[K"); + // Reprint the header + logPrintHeader(stream, level); + } + // The message is the same as the last one, we clear line and show a counter + ret = fprintf(stream, "(last message repeated %i times)\n", formattedMessage.repeat); + } else { + if (formattedMessage.endWithNewLine == 1) { + ret = fputs(formattedMessage.message, stream); + } else { + ret = fprintf(stream, "%s\n", formattedMessage.message); + } + } + + return ret; +} + + + +/** + * @brief Prints a formatted header to a given output stream. + * + * This function prints a log header that includes styling, a placeholder for + * the current time, the log level, and resets the styling. The function is + * designed to format log messages with consistent headers. + * + * @param stream The output stream to which the header will be printed (e.g., stdout or a file). + * @param level The log level (integer) that determines the style and level printed. + * The `log_style` array must be defined externally and accessible. + * + * @note The `log_style` array must contain strings for each log level, and the + * `level` parameter must be within its bounds to avoid undefined behavior. + * + * Example usage: + * @code + * logPrintHeader(stdout, 2); + * @endcode + */ +int logPrintHeader(FILE *stream, int level) { + // Print the style associated with the log level + if (LOG_COLOR_ENABLED) { + fprintf(stream, "%s", log_style[level]); + } + + if (LOG_TIME_ENABLED) { + /* + // Get the current time + time_t now = time(NULL); + char time_buffer[20]; + struct tm *time_info = localtime(&now); + strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", time_info); + + // Print a placeholder for the current time + fprintf(stream, "%s ", time_buffer); + */ + + // Get elapsed time + long seconds, milliseconds; + logGetElapsedTime(&seconds, &milliseconds); + + // Print a placeholder for the current time + fprintf(stream, "[%04ld.%03ld] ", seconds, milliseconds); + } + + // Print the log level with a separator + fprintf(stream, "%s> ", log_names[level]); + + // Clear styling to ensure subsequent text is not styled + if (LOG_COLOR_ENABLED) { + fprintf(stream, "%s", "\x1b[0m"); + } + + return 0; // Indicate success +} + +/** + * @brief Performs sanity checks on the log parameters before logging. + * + * This function validates the log level and the message string to ensure + * that the logging operation is valid and meets the minimum logging level + * requirements. It prevents invalid or unnecessary logging operations. + * + * @param level The logging level of the message (e.g., LOG_TRACE, LOG_INFO). + * Must be within the range [LOG_TRACE, LOG_FATAL]. + * @param message The log message to validate. Must not be NULL. + * + * @return -1 if the level is invalid or the message is NULL, + * 0 if the level is below the minimum logging threshold, + * 1 if the parameters are valid and logging can proceed. + * + * Example usage: + * @code + * if (logSanityChecks(LOG_INFO, "This is a log message") > 0) { + * // Perform logging + * } + * @endcode + */ +int logSanityChecks(int level, const char *message) { + // Check if the level is within the valid range + if (level < LOG_TRACE || level > LOG_FATAL) { + log_warn("Invalid level in log."); // Log a warning for invalid level + return -1; // Indicate failure + } + + // Suppress logging if the level is below the minimum threshold + if (level < LOG_MIN_LEVEL) { + return 0; // Indicate no logging needed + } + + // Check if the message is NULL (unexpected scenario) + if (!message) { + log_warn("Called log with a NULL message."); // Log a warning for NULL message + return -1; // Indicate failure + } + + // If all checks pass, return success + return 1; +} + +/** + * @brief Returns a FILE* stream initialized to stdout. + * + * This function returns a pointer to the standard output stream (stdout), + * which can be used for logging or other output operations. + * + * @return A FILE* pointing to the stdout stream. + */ +FILE* logGetStream() { + return stdout; +} + + /** + * @brief Formats a message string with variadic arguments and returns both the string and its length. + * + * This function formats a message using a `va_list` of arguments and returns the result + * as a dynamically allocated string along with its length. The caller is responsible for + * freeing the returned string. + * + * @param message The format string, similar to a printf format string. + * Must be a valid null-terminated C string. + * @param args A `va_list` containing the variadic arguments for the format string. + * + * @return A `LogFormattedMessage` containing the dynamically allocated string and its length. + * If memory allocation fails, both fields in the structure will be set to 0 or NULL. + * + * @warning + * - Ensure `message` is a valid format string. Passing NULL or an invalid format string + * may cause undefined behavior. + * - The returned string must be freed by the caller to avoid memory leaks. + * - Do not modify the returned string directly; treat it as read-only. + * - Ensure `va_list` is correctly initialized before calling this function. + * + * @note This function uses `malloc` for memory allocation. Use it carefully in memory-constrained environments. + */ +LogFormattedMessage logFormatMessage(const char *message, va_list args) { + LogFormattedMessage result = {NULL, 0}; // Initialize result with default values + + if (!message) { + return result; // Return empty result if the format string is NULL + } + + va_list args_copy; + va_copy(args_copy, args); + + // Calculate the size of the formatted message (excluding the null terminator) + size_t size = vsnprintf(NULL, 0, message, args_copy); + va_end(args_copy); + + // Allocate memory for the formatted message + char *formatted_message = (char *)malloc(size + 1); // +1 for the null terminator + if (formatted_message == NULL) { + return result; // Return empty result on allocation failure + } + + // Format the message into the allocated memory + vsnprintf(formatted_message, size + 1, message, args); + + // Detect line ending + int ends_with_newline = 0; + if (formatted_message[size - 1] == '\n' || formatted_message[size - 1] == '\r') { + ends_with_newline = 1; + } + + + // Only log message if smaller than buffer + if (LOG_NO_REPEAT && (size + 1) < LOG_REPEAT_BUFFER_SIZE) { + // Compare previous and current messages + if (strcmp(logLastMessage, formatted_message) == 0) { + // Message is the same as the last one ! + logRepeatCount++; + } else { + // Track the new message. + strncpy(logLastMessage, formatted_message, size + 1); + // Force a null terminator - is this needed ? + // logLastMessage[LOG_REPEAT_BUFFER_SIZE - 1] = '\0'; + } + + } else { + // Message bigger than buffer, set counter to 0 + logRepeatCount = 0; + } + + + + // Populate the result structure + result.message = formatted_message; + result.size = size; + result.endWithNewLine = ends_with_newline; + result.repeat = logRepeatCount; + + return result; +} + +/** + * @brief Initializes the timer at the start of the program. + */ +void logInitTimer() { + clock_gettime(CLOCK_MONOTONIC, &logStartTime); +} + +/** + * @brief Gets the elapsed time in seconds and milliseconds since the program started. + * + * @param seconds Pointer to store the seconds part of the elapsed time. + * @param milliseconds Pointer to store the milliseconds part of the elapsed time. + */ +void logGetElapsedTime(long *seconds, long *milliseconds) { + struct timespec now; + if (logStartTime.tv_sec == 0) { + logInitTimer(); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + *seconds = now.tv_sec - logStartTime.tv_sec; + *milliseconds = (now.tv_nsec - logStartTime.tv_nsec) / 1e6; + + if (*milliseconds < 0) { + *milliseconds += 1000; + (*seconds)--; + } +} diff --git a/src/lindbergh/log.h b/src/lindbergh/log.h new file mode 100644 index 0000000..6598d21 --- /dev/null +++ b/src/lindbergh/log.h @@ -0,0 +1,51 @@ +#ifndef LINDBERGH_LOADER_LOG_H +#define LINDBERGH_LOADER_LOG_H + + +#include +#include +#include + + +// Structure to hold the formatted message and its size +typedef struct { + char *message; // The formatted message + size_t size; // The size of the formatted message + int endWithNewLine; // 1 if it ends with a new line + unsigned int repeat; // How many times the message was repeated +} LogFormattedMessage; + +// List of status +enum { LOG_TRACE, // 0 + LOG_DEBUG, // 1 + LOG_GAME, // 2 + LOG_INFO, // 3 + LOG_WARN, // 4 + LOG_ERROR, // 5 + LOG_FATAL // 6 +}; + +// Macro to ease logging +#define log_game(...) logGeneric(LOG_GAME, __FILE__, __LINE__, __VA_ARGS__) +#define log_trace(...) logGeneric(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define log_debug(...) logGeneric(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define log_info(...) logGeneric(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define log_warn(...) logGeneric(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) +#define log_error(...) logGeneric(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define log_fatal(...) logGeneric(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) + +// Macro to ease logging +#define logVA_game(...) logVA(LOG_GAME, __FILE__, __LINE__, __VA_ARGS__) + + +int logGeneric(int level, const char *file, int line, const char *message, ...); +int logVA(int level, const char *file, int line, const char *message, va_list args); +int logSanityChecks(int level, const char *message); +int logPrintHeader(FILE *stream, int level); +int logPrintMessage(FILE *stream, LogFormattedMessage formattedMessage, int level); +LogFormattedMessage logFormatMessage(const char *message, va_list args); +FILE* logGetStream(); +void logInitTimer(); +void logGetElapsedTime(long *seconds, long *milliseconds); + +#endif //LINDBERGH_LOADER_LOG_H \ No newline at end of file diff --git a/src/lindbergh/patch.c b/src/lindbergh/patch.c index 9fec36c..f19f9ac 100644 --- a/src/lindbergh/patch.c +++ b/src/lindbergh/patch.c @@ -22,6 +22,7 @@ #include "securityboard.h" #include "shader_patches.h" #include "glxhooks.h" +#include "log.h" char elfID[4]; void *amDipswContextAddr; @@ -316,6 +317,33 @@ int or2snprintf(char *s, size_t n, const char *format, ...) return ret; } + +// printf hook, used by OR2 +int patchedPrintf(char *format,...) +{ + if (format == NULL) + return 0; + + va_list args; + va_start(args, format); + int ret = logVA_game(format, args); + va_end(args); + + return ret; +} + +// puts hook, used by OR2 +int patchedPuts(char *s) +{ + if (s == NULL) + return 0; + + // Puts appends a new line by default, we add it ourselves + int ret = log_game("%s\n", s); + + return ret; +} + int initPatch() { EmulatorConfig *config = getConfig(); @@ -624,6 +652,9 @@ int initPatch() setVariable(0x0893a4d8, 2); // amSysDataDebugLevel setVariable(0x0893a4e0, 2); // bcLibDebugLevel } + // Output/logs + detourFunction(0x0804c9a8, patchedPuts); + detourFunction(0x0804cfe8, patchedPrintf); // Security detourFunction(0x08190e80, amDongleInit); detourFunction(0x08191201, amDongleIsAvailable); diff --git a/src/lindbergh/patch.h b/src/lindbergh/patch.h index 7ac4c5f..c840054 100644 --- a/src/lindbergh/patch.h +++ b/src/lindbergh/patch.h @@ -8,4 +8,7 @@ void replaceCallAtAddress(size_t address, void *function); void stubReturn(); int stubRetOne(); int stubRetMinusOne(); -char stubRetZeroChar(); \ No newline at end of file +char stubRetZeroChar(); + +int patchedPrintf(char *format,...); +int patchedPuts(char *s); \ No newline at end of file diff --git a/src/lindbergh/securityboard.c b/src/lindbergh/securityboard.c index 2f06a6b..4d4a9ca 100644 --- a/src/lindbergh/securityboard.c +++ b/src/lindbergh/securityboard.c @@ -2,6 +2,7 @@ #include "securityboard.h" #include "config.h" +#include "log.h" #define SECURITY_BOARD_FRONT_PANEL 0x38 #define SECURITY_BOARD_FRONT_PANEL_NON_ROOT 0x1038 @@ -52,7 +53,7 @@ int securityBoardSetDipResolution(int width, int height) else if (width == 1360 && height == 768) setResolutionDips(1, 1, 1); else - printf("Warning: Resolution not compatible, using 640 x 480\n"); + log_warn("Resolution not compatible, using 640 x 480\n"); return 0; }