1
0
mirror of synced 2025-01-19 01:14:04 +01:00

Add a logging library.

Hook on outrun SPDX as a demo of ingame logging.
This commit is contained in:
Rolel 2025-01-02 18:37:33 +01:00 committed by Bobby Dilley
parent 24eaa91c63
commit dfaa52b7e2
12 changed files with 578 additions and 120 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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
View 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
View 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

View File

@ -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);

View File

@ -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);

View File

@ -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;
}