multi-module cleanup: daily logs, luaalloc enable, minimap texture fix

- logsessions: switch from timestamped per-session files to daily rotation
  (WoWCombatLog_YYYY_MM_DD.txt), remove path pointer overwrites, persist
  paths across logout so redirects keep working
- logging: add append file mode (openAppend) for daily log rotation
- minimapicons: clear texture binding after blip render to fix stale
  texture with Silicon d3d9 layer
- weirdperformance: enable luaalloc slab allocator (was disabled for testing)
- worldmarkers: extract FN_SET_UNIT_POSITION to offsets, use lua.typeOf
  helper, update doc comments for renamed API
- delete unused guidcache standalone test module
This commit is contained in:
MarcelineVQ
2026-04-06 17:51:29 -07:00
parent 49f3f2452c
commit d6711901df
7 changed files with 75 additions and 196 deletions
+37 -101
View File
@@ -1,12 +1,13 @@
//! Log session rotation with per-character directories.
//!
//! Redirects combat log, raw combat log, and chat log to per-character
//! directories: `Logs\<realm>\<character>\<Type>_<timestamp>_<PID>.txt`.
//! directories: `Logs\<realm>\<character>\WoW<Type>Log_YYYY_MM_DD.txt`.
//!
//! Features:
//! - Early path setup: hooks HandleCharacterSelection to resolve character/realm
//! from the select screen data before world loading begins
//! - Session continuation: reuses files modified < 60 min ago
//! - Daily rotation: one log file per character per day, no timestamps
//! - Session continuation: appends to today's file on each login (same character)
//! - Session marker: writes `COMBATLOG_SESSION: <char> <realm>` on first combat write
//!
//! All DLL-side - no Lua addon needed.
@@ -118,10 +119,6 @@ var g_raw_marker_written: bool = false;
/// statically; SuperWoW passes it as handle_out.
var g_raw_combat_handle_addr: u32 = 0;
/// Saved original path pointers for restoration.
var g_original_combat_path_ptr: u32 = 0;
var g_original_chat_path_ptr: u32 = 0;
// =============================================================================
// Character / realm identity
// =============================================================================
@@ -196,88 +193,60 @@ fn setupSessionDir(realm: []const u8, char_name: []const u8) bool {
}
// =============================================================================
// Session continuation - find recent file to reuse
// Daily log file lookup
// =============================================================================
/// Scan directory for files matching `<prefix>_*.txt`, return the newest if
/// modified within 60 minutes. Writes full path into result_buf, returns length.
fn findRecentFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
/// Scan directory for today's daily log file matching `<prefix>_YYYY_MM_DD.txt`.
/// Returns full path if found, null if not.
fn findDailyFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
if (g_dir_path_len == 0) return null;
// Build search pattern: <dir><prefix>_*.txt
var search_buf: [300]u8 = undefined;
const pattern = std.fmt.bufPrint(&search_buf, "{s}{s}_*.txt", .{
g_dir_path[0..g_dir_path_len],
var st: SYSTEMTIME = undefined;
GetLocalTime(&st);
// Build exact filename: <dir><prefix>_YYYY_MM_DD.txt
var filename_buf: [260]u8 = undefined;
const filename = std.fmt.bufPrint(&filename_buf, "{s}_{d:0>4}_{d:0>2}_{d:0>2}.txt", .{
prefix,
st.wYear,
st.wMonth,
st.wDay,
}) catch return null;
search_buf[pattern.len] = 0;
// Build full path: <dir><filename>
const total_len = g_dir_path_len + filename.len;
if (total_len >= result_buf.len) return null;
@memcpy(result_buf[0..g_dir_path_len], g_dir_path[0..g_dir_path_len]);
@memcpy(result_buf[g_dir_path_len..total_len], filename);
result_buf[total_len] = 0;
// Check if file exists
var find_data: WIN32_FIND_DATAA = undefined;
const find_handle = FindFirstFileA(@ptrCast(search_buf[0..pattern.len :0]), &find_data);
const find_handle = FindFirstFileA(@ptrCast(result_buf[0..total_len :0]), &find_data);
if (find_handle == INVALID_HANDLE_VALUE) return null;
defer _ = FindClose(find_handle);
_ = FindClose(find_handle);
var newest_time: u64 = 0;
var newest_name: [260]u8 = undefined;
var newest_name_len: usize = 0;
// Iterate all matching files, track the newest by write time
var has_result: bool = true;
while (has_result) {
const ft: u64 = @bitCast(find_data.ftLastWriteTime);
if (ft > newest_time) {
newest_time = ft;
const name_len = std.mem.indexOfScalar(u8, &find_data.cFileName, 0) orelse 0;
if (name_len > 0) {
newest_name_len = name_len;
@memcpy(newest_name[0..name_len], find_data.cFileName[0..name_len]);
}
}
has_result = FindNextFileA(find_handle, &find_data) != 0;
}
if (newest_name_len == 0) return null;
// Compare against current time - both UTC FILETIME (100ns units)
var current_ft: FILETIME = undefined;
GetSystemTimeAsFileTime(&current_ft);
const current: u64 = @bitCast(current_ft);
const threshold: u64 = 60 * 60 * 10_000_000; // 60 minutes
if (current > newest_time and (current - newest_time) < threshold) {
// Build full path: dir + filename
const total_len = g_dir_path_len + newest_name_len;
if (total_len >= result_buf.len) return null;
@memcpy(result_buf[0..g_dir_path_len], g_dir_path[0..g_dir_path_len]);
@memcpy(result_buf[g_dir_path_len..total_len], newest_name[0..newest_name_len]);
result_buf[total_len] = 0;
return total_len;
}
return null;
return total_len;
}
/// Resolve a log file path: reuse recent file or generate new timestamped name.
fn resolveLogPath(prefix: []const u8, result_buf: *[260]u8) usize {
// Try to reuse a recent file (modified < 60 min ago)
if (findRecentFile(prefix, result_buf)) |len| {
/// Resolve a log file path: reuse today's daily file or create new one.
fn resolveDailyLogPath(prefix: []const u8, result_buf: *[260]u8) usize {
// Try to find today's daily file
if (findDailyFile(prefix, result_buf)) |len| {
log.fmt("reusing: {s}\n", .{result_buf[0..len]});
return len;
}
// Generate new timestamped filename
// Generate new daily filename: <dir><prefix>_YYYY_MM_DD.txt
var st: SYSTEMTIME = undefined;
GetLocalTime(&st);
const path = std.fmt.bufPrint(result_buf, "{s}{s}_{d:0>4}{d:0>2}{d:0>2}_{d:0>2}{d:0>2}{d:0>2}.txt", .{
const path = std.fmt.bufPrint(result_buf, "{s}{s}_{d:0>4}_{d:0>2}_{d:0>2}.txt", .{
g_dir_path[0..g_dir_path_len],
prefix,
st.wYear,
st.wMonth,
st.wDay,
st.wHour,
st.wMinute,
st.wSecond,
}) catch return 0;
result_buf[path.len] = 0;
log.fmt("new: {s}\n", .{path});
@@ -310,22 +279,9 @@ fn configureSession(char_span: []const u8, realm_span: []const u8) void {
}
// Resolve paths for all three log types
g_combat_path_len = resolveLogPath("WoWCombatLog", &g_combat_path);
g_raw_combat_path_len = resolveLogPath("WoWRawCombatLog", &g_raw_combat_path);
g_chat_path_len = resolveLogPath("WoWChatLog", &g_chat_path);
// Belt-and-suspenders: overwrite path pointer table for game code paths
// that read the table directly before calling InitializeLogBuffer.
if (g_combat_path_len > 0) {
g_original_combat_path_ptr = hook.readMem(u32, o.COMBAT_LOG_PATH_PTR);
const ptr_bytes: [4]u8 = @bitCast(@intFromPtr(&g_combat_path));
hook.writeMem(o.COMBAT_LOG_PATH_PTR, &ptr_bytes);
}
if (g_chat_path_len > 0) {
g_original_chat_path_ptr = hook.readMem(u32, o.CHAT_LOG_PATH_PTR);
const ptr_bytes: [4]u8 = @bitCast(@intFromPtr(&g_chat_path));
hook.writeMem(o.CHAT_LOG_PATH_PTR, &ptr_bytes);
}
g_combat_path_len = resolveDailyLogPath("WoWCombatLog", &g_combat_path);
g_raw_combat_path_len = resolveDailyLogPath("WoWRawCombatLog", &g_raw_combat_path);
g_chat_path_len = resolveDailyLogPath("WoWChatLog", &g_chat_path);
g_paths_configured = true;
}
@@ -356,19 +312,6 @@ fn enterWorldDetour() callconv(hook.cc.stdcall) void {
enter_world_hook.callOriginal(.{});
}
fn restorePathPointers() void {
if (g_original_combat_path_ptr != 0) {
const ptr_bytes: [4]u8 = @bitCast(g_original_combat_path_ptr);
hook.writeMem(o.COMBAT_LOG_PATH_PTR, &ptr_bytes);
g_original_combat_path_ptr = 0;
}
if (g_original_chat_path_ptr != 0) {
const ptr_bytes: [4]u8 = @bitCast(g_original_chat_path_ptr);
hook.writeMem(o.CHAT_LOG_PATH_PTR, &ptr_bytes);
g_original_chat_path_ptr = 0;
}
}
// =============================================================================
// InitializeLogBuffer hook - lazy setup + path redirect
// =============================================================================
@@ -473,14 +416,9 @@ pub fn onShutdown() void {
g_chat_marker_written = false;
g_raw_marker_written = false;
g_raw_combat_handle_addr = 0;
g_combat_path_len = 0;
g_raw_combat_path_len = 0;
g_chat_path_len = 0;
g_dir_path_len = 0;
// Keep paths set so initLogDetour keeps redirecting on next login
g_session_char_len = 0;
g_session_realm_len = 0;
// Restore original path pointers so next session starts clean
restorePathPointers();
}
// =============================================================================
@@ -512,7 +450,6 @@ pub fn luaGetChatLogPath(L: lua.State) callconv(.c) u32 {
// =============================================================================
pub fn installHooks() void {
const result = mod_mutex.acquire(module_name);
g_mutex = result.handle;
g_is_hook_owner = result.is_owner;
@@ -546,7 +483,6 @@ pub fn removeHooks() void {
write_log_hook.detach();
init_log_hook.detach();
enter_world_hook.detach();
restorePathPointers();
log.close();
mod_mutex.release(&g_mutex);
}