Add a logging library.
Hook on outrun SPDX as a demo of ingame logging.
This commit is contained in:
parent
24eaa91c63
commit
dfaa52b7e2
4
Makefile
4
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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
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);
|
@ -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);
|
||||
}
|
||||
|
466
src/lindbergh/log.c
Normal file
466
src/lindbergh/log.c
Normal file
@ -0,0 +1,466 @@
|
||||
#include "log.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <time.h>
|
||||
|
||||
// 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)--;
|
||||
}
|
||||
}
|
51
src/lindbergh/log.h
Normal file
51
src/lindbergh/log.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef LINDBERGH_LOADER_LOG_H
|
||||
#define LINDBERGH_LOADER_LOG_H
|
||||
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
// 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
|
@ -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);
|
||||
|
@ -8,4 +8,7 @@ void replaceCallAtAddress(size_t address, void *function);
|
||||
void stubReturn();
|
||||
int stubRetOne();
|
||||
int stubRetMinusOne();
|
||||
char stubRetZeroChar();
|
||||
char stubRetZeroChar();
|
||||
|
||||
int patchedPrintf(char *format,...);
|
||||
int patchedPuts(char *s);
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user