/*
* Tuxánci 2 - A first person shooter
* Copyright (C) 2007-2010 Tuxánci Development Team
* Copyright (C) 2025-2026 Connor Thomson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "ta_sdl.h"
#ifdef UNIX_LIKE
#define UNW_LOCAL_ONLY
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LOG_TIMESTAMP_SIZE 30
static Dwfl *ta_dwfl_open_self(void) {
static char *debuginfo_path;
static const Dwfl_Callbacks callbacks = {
.find_elf = dwfl_linux_proc_find_elf,
.find_debuginfo = dwfl_standard_find_debuginfo,
.debuginfo_path = &debuginfo_path,
};
Dwfl *dwfl = dwfl_begin(&callbacks);
if (dwfl == NULL) {
return NULL;
}
if (dwfl_linux_proc_report(dwfl, getpid()) != 0) {
dwfl_end(dwfl);
return NULL;
}
if (dwfl_report_end(dwfl, NULL, NULL) != 0) {
dwfl_end(dwfl);
return NULL;
}
return dwfl;
}
static char *ta_get_proc_name(unw_cursor_t *cursor) {
char *buf = NULL;
size_t cap = 0;
unw_word_t proc_offset = 0;
while (1) {
size_t grow_to = (cap == 0) ? 64 : (cap * 2);
char *grown = realloc(buf, grow_to);
if (grown == NULL) {
free(buf);
return NULL;
}
buf = grown;
cap = grow_to;
int ret = unw_get_proc_name(cursor, buf, cap, &proc_offset);
if (ret == 0) {
return buf;
}
if (ret != -UNW_ENOMEM) {
free(buf);
return NULL;
}
}
}
static void ta_get_timestamp(char *buf, size_t buf_size) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm_info;
localtime_r(&ts.tv_sec, &tm_info);
size_t len = strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S", &tm_info);
snprintf(buf + len, buf_size - len, ".%03ld", ts.tv_nsec / 1000000);
}
static char *ta_format_message(const char *format, va_list args) {
va_list args_copy;
va_copy(args_copy, args);
int needed = vsnprintf(NULL, 0, format, args_copy);
va_end(args_copy);
if (needed < 0) {
return NULL;
}
char *message = malloc((size_t)needed + 1);
if (message == NULL) {
return NULL;
}
vsnprintf(message, (size_t)needed + 1, format, args);
return message;
}
void ta_log(const char *format, ...) {
unw_cursor_t cursor;
unw_context_t context;
int code;
code = unw_getcontext(&context);
if (code != 0) {
SDL_Log("unw_getcontext failed with code %d\n", code);
return;
}
code = unw_init_local(&cursor, &context);
if (code != 0) {
SDL_Log("unw_init_local failed with code %d\n", code);
return;
}
code = unw_step(&cursor);
if (code <= 0) {
SDL_Log("unw_step failed with code %d\n", code);
return;
}
unw_word_t ip = 0;
if (unw_get_reg(&cursor, UNW_REG_IP, &ip) != 0 || ip == 0) {
SDL_Log("unw_get_reg failed to resolve caller IP\n");
return;
}
const char *file = NULL;
int src_line = 0;
const char *func_name = NULL;
char *proc_name_buf = NULL;
Dwfl *dwfl = ta_dwfl_open_self();
if (dwfl != NULL) {
Dwfl_Module *mod = dwfl_addrmodule(dwfl, (Dwarf_Addr)ip);
if (mod != NULL) {
func_name = dwfl_module_addrname(mod, (GElf_Addr)ip);
Dwfl_Line *dwfl_line = dwfl_module_getsrc(mod, (Dwarf_Addr)ip);
if (dwfl_line != NULL) {
Dwarf_Addr src_addr;
file = dwfl_lineinfo(dwfl_line, &src_addr, &src_line, NULL, NULL, NULL);
}
}
}
if (func_name == NULL) {
proc_name_buf = ta_get_proc_name(&cursor);
func_name = proc_name_buf;
}
va_list args;
va_start(args, format);
char *message = ta_format_message(format, args);
va_end(args);
const char *out_message = (message != NULL) ? message : format;
/*
* PATCH:
* Remove leading '/'
* I cannot find a way to make it not do it with the compiler.
* This uses extra cpu cycles and makes the binary larger then it has to be.
* Make a script to trim it from the DWARF info.
*/
if (file != NULL) {
const char *basename = strrchr(file, '/');
if (basename != NULL) {
file = basename + 1;
}
}
char timestamp[LOG_TIMESTAMP_SIZE];
ta_get_timestamp(timestamp, sizeof(timestamp));
if (file != NULL && src_line > 0 && func_name != NULL) {
printf("[%s] %s:%d:%s(): %s\n", timestamp, file, src_line, func_name, out_message);
} else if (func_name != NULL) {
printf("[%s] %s(): %s\n", timestamp, func_name, out_message);
} else {
printf("[%s] 0x%lx: %s\n", timestamp, (unsigned long)ip, out_message);
}
free(message);
free(proc_name_buf);
if (dwfl != NULL) {
dwfl_end(dwfl);
}
}
#else // UNIX_LIKE
void ta_log(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
SDL_LogMessageV(
SDL_LOG_CATEGORY_APPLICATION,
SDL_LOG_PRIORITY_INFO,
fmt,
args
);
va_end(args);
}
#endif // UNIX_LIKE