Add bigcursor module stub, rename combatlog to logsessions, bump session timer to 60min

- New bigcursor module skeleton with build integration and mutex
- Rename combatlog module to logsessions (directory, files, build options,
  imports, console prefixes, mutex name, DLL variant, docs)
- Increase log session continuation threshold from 30 to 60 minutes
- Update DLL_README log sessions section
This commit is contained in:
MarcelineVQ
2026-03-04 15:14:04 -08:00
parent 1bbf334958
commit 00749d5745
9 changed files with 141 additions and 64 deletions
+51
View File
@@ -0,0 +1,51 @@
//! Big cursor module.
//!
//! Increases the hardware cursor render size for improved visibility.
//!
//! TODO: Research cursor rendering pipeline and hook implementation.
const std = @import("std");
const con = @import("../console.zig");
const WINAPI = std.builtin.CallingConvention.winapi;
extern "kernel32" fn CreateMutexA(lpMutexAttributes: ?*anyopaque, bInitialOwner: i32, lpName: [*:0]const u8) callconv(WINAPI) ?*anyopaque;
extern "kernel32" fn ReleaseMutex(hMutex: *anyopaque) callconv(WINAPI) i32;
extern "kernel32" fn CloseHandle(hObject: *anyopaque) callconv(WINAPI) i32;
extern "kernel32" fn GetLastError() callconv(WINAPI) u32;
extern "kernel32" fn GetCurrentProcessId() callconv(WINAPI) u32;
const ERROR_ALREADY_EXISTS: u32 = 183;
var g_mutex: ?*anyopaque = null;
var g_is_hook_owner: bool = false;
pub fn installHooks() void {
con.print("[bigcursor] Module loaded (stub)\n");
// Multi-DLL safety: only one instance per process should hook
var mutex_name_buf: [64]u8 = undefined;
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_BigcursorHook_{d}", .{GetCurrentProcessId()}) catch return;
mutex_name_buf[mutex_name.len] = 0;
g_mutex = CreateMutexA(null, 1, @ptrCast(mutex_name_buf[0..mutex_name.len :0]));
if (g_mutex == null) return;
if (GetLastError() == ERROR_ALREADY_EXISTS) {
_ = CloseHandle(g_mutex.?);
g_mutex = null;
g_is_hook_owner = false;
con.print("[bigcursor] Another DLL owns hooks (mutex taken), skipping\n");
return;
}
g_is_hook_owner = true;
}
pub fn removeHooks() void {
if (g_is_hook_owner) {
if (g_mutex) |m| {
_ = ReleaseMutex(m);
_ = CloseHandle(m);
g_mutex = null;
}
}
g_is_hook_owner = false;
}
@@ -1,4 +1,4 @@
//! Combat log session rotation with per-character directories.
//! 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`.
@@ -6,7 +6,7 @@
//! Features:
//! - Early path setup: hooks HandleCharacterSelection to resolve character/realm
//! from the select screen data before world loading begins
//! - Session continuation: reuses files modified < 30 min ago
//! - Session continuation: reuses files modified < 60 min ago
//! - Session marker: writes `COMBATLOG_SESSION: <char> <realm>` on first combat write
//!
//! All DLL-side — no Lua addon needed.
@@ -184,7 +184,7 @@ fn setupSessionDir(realm: []const u8, char_name: []const u8) bool {
g_dir_path[dir_path.len] = 0;
g_dir_path_len = dir_path.len;
con.fmt("[combatlog] dir: {s}\n", .{g_dir_path[0..g_dir_path_len]});
con.fmt("[logsessions] dir: {s}\n", .{g_dir_path[0..g_dir_path_len]});
return true;
}
@@ -193,7 +193,7 @@ fn setupSessionDir(realm: []const u8, char_name: []const u8) bool {
// =============================================================================
/// Scan directory for files matching `<prefix>_*.txt`, return the newest if
/// modified within 30 minutes. Writes full path into result_buf, returns length.
/// modified within 60 minutes. Writes full path into result_buf, returns length.
fn findRecentFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
if (g_dir_path_len == 0) return null;
@@ -235,7 +235,7 @@ fn findRecentFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
var current_ft: FILETIME = undefined;
GetSystemTimeAsFileTime(&current_ft);
const current: u64 = @bitCast(current_ft);
const threshold: u64 = 30 * 60 * 10_000_000; // 30 minutes
const threshold: u64 = 60 * 60 * 10_000_000; // 60 minutes
if (current > newest_time and (current - newest_time) < threshold) {
// Build full path: dir + filename
@@ -252,9 +252,9 @@ fn findRecentFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
/// 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 < 30 min ago)
// Try to reuse a recent file (modified < 60 min ago)
if (findRecentFile(prefix, result_buf)) |len| {
con.fmt("[combatlog] reusing: {s}\n", .{result_buf[0..len]});
con.fmt("[logsessions] reusing: {s}\n", .{result_buf[0..len]});
return len;
}
@@ -273,7 +273,7 @@ fn resolveLogPath(prefix: []const u8, result_buf: *[260]u8) usize {
st.wSecond,
}) catch return 0;
result_buf[path.len] = 0;
con.fmt("[combatlog] new: {s}\n", .{path});
con.fmt("[logsessions] new: {s}\n", .{path});
return path.len;
}
@@ -288,7 +288,7 @@ fn configureSession(char_span: []const u8, realm_span: []const u8) void {
g_session_char_len = sanitizeName(char_span, &g_session_char);
g_session_realm_len = sanitizeName(realm_span, &g_session_realm);
con.fmt("[combatlog] session: {s} on {s}\n", .{
con.fmt("[logsessions] session: {s} on {s}\n", .{
g_session_char[0..g_session_char_len],
g_session_realm[0..g_session_realm_len],
});
@@ -298,7 +298,7 @@ fn configureSession(char_span: []const u8, realm_span: []const u8) void {
g_session_realm[0..g_session_realm_len],
g_session_char[0..g_session_char_len],
)) {
con.print("[combatlog] setup: failed to create directories\n");
con.print("[logsessions] setup: failed to create directories\n");
return;
}
@@ -342,7 +342,7 @@ fn enterWorldDetour() callconv(sc) void {
if (char_name != null and realm_name != null) {
configureSession(std.mem.span(char_name.?), std.mem.span(realm_name.?));
} else {
con.print("[combatlog] enter world: char/realm not available\n");
con.print("[logsessions] enter world: char/realm not available\n");
}
}
@@ -376,18 +376,18 @@ fn initLogDetour(file_path: u32, flags: u32, handle_out: u32) callconv(sc) u32 {
// Redirect based on path suffix
if (g_combat_path_len > 0 and std.mem.endsWith(u8, path_span, "WoWCombatLog.txt")) {
con.fmt("[combatlog] redirect: {s} -> {s}\n", .{ path_span, g_combat_path[0..g_combat_path_len] });
con.fmt("[logsessions] redirect: {s} -> {s}\n", .{ path_span, g_combat_path[0..g_combat_path_len] });
return init_log_hook.callOriginal(.{ @intFromPtr(&g_combat_path), flags, handle_out });
}
if (g_raw_combat_path_len > 0 and std.mem.endsWith(u8, path_span, "WoWRawCombatLog.txt")) {
g_raw_combat_handle_addr = handle_out; // capture SuperWoW's handle address
con.fmt("[combatlog] redirect: {s} -> {s}\n", .{ path_span, g_raw_combat_path[0..g_raw_combat_path_len] });
con.fmt("[logsessions] redirect: {s} -> {s}\n", .{ path_span, g_raw_combat_path[0..g_raw_combat_path_len] });
return init_log_hook.callOriginal(.{ @intFromPtr(&g_raw_combat_path), flags, handle_out });
}
if (g_chat_path_len > 0 and std.mem.endsWith(u8, path_span, "WoWChatLog.txt")) {
con.fmt("[combatlog] redirect: {s} -> {s}\n", .{ path_span, g_chat_path[0..g_chat_path_len] });
con.fmt("[logsessions] redirect: {s} -> {s}\n", .{ path_span, g_chat_path[0..g_chat_path_len] });
return init_log_hook.callOriginal(.{ @intFromPtr(&g_chat_path), flags, handle_out });
}
@@ -448,7 +448,7 @@ fn writeSessionMarker(handle: u32, fmt_str: [*:0]const u8) void {
@intFromPtr(&marker_ptr),
});
con.fmt("[combatlog] marker: {s}\n", .{marker_str});
con.fmt("[logsessions] marker: {s}\n", .{marker_str});
}
// =============================================================================
@@ -459,7 +459,7 @@ fn writeSessionMarker(handle: u32, fmt_str: [*:0]const u8) void {
/// Called from logoutDetour/shutdownDetour in main.zig.
pub fn onShutdown() void {
if (g_paths_configured) {
con.print("[combatlog] session reset\n");
con.print("[logsessions] session reset\n");
}
g_paths_configured = false;
g_combat_marker_written = false;
@@ -505,11 +505,11 @@ pub fn luaGetChatLogPath(L: lua.State) callconv(.c) u32 {
// =============================================================================
pub fn installHooks() void {
con.print("[combatlog] Module loaded\n");
con.print("[logsessions] Module loaded\n");
// Multi-DLL safety: only one instance per process should hook
var mutex_name_buf: [64]u8 = undefined;
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_CombatlogHook_{d}", .{GetCurrentProcessId()}) catch return;
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_LogSessionsHook_{d}", .{GetCurrentProcessId()}) catch return;
mutex_name_buf[mutex_name.len] = 0;
g_mutex = CreateMutexA(null, 1, @ptrCast(mutex_name_buf[0..mutex_name.len :0]));
@@ -519,7 +519,7 @@ pub fn installHooks() void {
_ = CloseHandle(g_mutex.?);
g_mutex = null;
g_is_hook_owner = false;
con.print("[combatlog] Another DLL owns hooks (mutex taken), skipping\n");
con.print("[logsessions] Another DLL owns hooks (mutex taken), skipping\n");
return;
}
g_is_hook_owner = true;
@@ -527,21 +527,21 @@ pub fn installHooks() void {
// Hook HandleCharacterSelection — sets up paths when player clicks Enter World,
// before the world loading sequence calls InitializeLogBuffer.
if (enter_world_hook.attach(o.FN_HANDLE_CHAR_SELECT, &enterWorldDetour) != .ok) {
con.print("[combatlog] FAILED to hook HandleCharacterSelection!\n");
con.print("[logsessions] FAILED to hook HandleCharacterSelection!\n");
} else {
con.print("[combatlog] hooked HandleCharacterSelection OK\n");
con.print("[logsessions] hooked HandleCharacterSelection OK\n");
}
if (init_log_hook.attach(o.FN_INIT_LOG_BUFFER, &initLogDetour) != .ok) {
con.print("[combatlog] FAILED to hook InitializeLogBuffer!\n");
con.print("[logsessions] FAILED to hook InitializeLogBuffer!\n");
} else {
con.print("[combatlog] hooked InitializeLogBuffer OK\n");
con.print("[logsessions] hooked InitializeLogBuffer OK\n");
}
if (write_log_hook.attach(o.FN_WRITE_FMT_LOG_MSG, &writeLogDetour) != .ok) {
con.print("[combatlog] FAILED to hook WriteFormattedLogMessage!\n");
con.print("[logsessions] FAILED to hook WriteFormattedLogMessage!\n");
} else {
con.print("[combatlog] hooked WriteFormattedLogMessage OK\n");
con.print("[logsessions] hooked WriteFormattedLogMessage OK\n");
}
}
@@ -1,4 +1,4 @@
//! Address constants for combat log session rotation.
//! Address constants for log session rotation.
// =============================================================================
// Combat log path and state
+10 -7
View File
@@ -9,11 +9,12 @@ const build_opts = struct {
const outline = @import("build_options").enable_outline;
const worldmarkers = @import("build_options").enable_worldmarkers;
const framecrash = @import("build_options").enable_framecrash;
const combatlog = @import("build_options").enable_combatlog;
const logsessions = @import("build_options").enable_logsessions;
const minimapicons = @import("build_options").enable_minimapicons;
const transmogfix = @import("build_options").enable_transmogfix;
const customassets = @import("build_options").enable_customassets;
const healtextfix = @import("build_options").enable_healtextfix;
const bigcursor = @import("build_options").enable_bigcursor;
};
// Conditional module imports
@@ -22,11 +23,12 @@ const interact = if (build_opts.interact) @import("interact/interact.zig") else
const outline = if (build_opts.outline) @import("outline/api.zig") else struct {};
const markers = if (build_opts.worldmarkers) @import("markers/markers.zig") else struct {};
const framecrash = if (build_opts.framecrash) @import("framecrash/framecrash.zig") else struct {};
const combatlog = if (build_opts.combatlog) @import("combatlog/combatlog.zig") else struct {};
const logsessions = if (build_opts.logsessions) @import("logsessions/logsessions.zig") else struct {};
const minimapicons = if (build_opts.minimapicons) @import("minimapicons/minimapicons.zig") else struct {};
const transmogfix = if (build_opts.transmogfix) @import("transmogfix/transmogfix.zig") else struct {};
const customassets = if (build_opts.customassets) @import("customassets/customassets.zig") else struct {};
const healtextfix = if (build_opts.healtextfix) @import("healtextfix/healtextfix.zig") else struct {};
const bigcursor = if (build_opts.bigcursor) @import("bigcursor/bigcursor.zig") else struct {};
const WINAPI = std.builtin.CallingConvention.winapi;
const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} };
@@ -108,9 +110,9 @@ fn registerLuaFunctions() void {
if (build_opts.outline) {
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
}
if (build_opts.combatlog) {
registerFunction("GetCombatLogPath", @intFromPtr(&combatlog.luaGetCombatLogPath));
registerFunction("GetChatLogPath", @intFromPtr(&combatlog.luaGetChatLogPath));
if (build_opts.logsessions) {
registerFunction("GetCombatLogPath", @intFromPtr(&logsessions.luaGetCombatLogPath));
registerFunction("GetChatLogPath", @intFromPtr(&logsessions.luaGetChatLogPath));
}
if (build_opts.worldmarkers and markers.isActive()) {
// User-facing functions stay global
@@ -795,7 +797,7 @@ fn logoutDetour() callconv(sc) void {
// Reset per-session state — only on real logout/disconnect, not /reload.
if (build_opts.worldmarkers) markers.onShutdown();
if (build_opts.combatlog) combatlog.onShutdown();
if (build_opts.logsessions) logsessions.onShutdown();
// Clean up world objects BEFORE game teardown — modules with
// remove_on_shutdown must destroy while game systems are alive.
@@ -836,10 +838,11 @@ const ModuleHooks = struct {
const modules = [_]ModuleHooks{
if (build_opts.customassets) .{ .install = customassets.installHooks, .remove = customassets.removeHooks } else .{},
if (build_opts.framecrash) .{ .install = framecrash.installHooks, .remove = framecrash.removeHooks } else .{},
if (build_opts.combatlog) .{ .install = combatlog.installHooks, .remove = combatlog.removeHooks } else .{},
if (build_opts.logsessions) .{ .install = logsessions.installHooks, .remove = logsessions.removeHooks } else .{},
if (build_opts.transmogfix) .{ .install = transmogfix.installHooks, .remove = transmogfix.removeHooks } else .{},
if (build_opts.minimapicons) .{ .install = minimapicons.installHooks, .remove = minimapicons.removeHooks } else .{},
if (build_opts.healtextfix) .{ .install = healtextfix.installHooks, .remove = healtextfix.removeHooks } else .{},
if (build_opts.bigcursor) .{ .install = bigcursor.installHooks, .remove = bigcursor.removeHooks } else .{},
if (build_opts.worldmarkers) .{ .install = markers.installHooks, .remove = markers.removeHooks } else .{},
if (build_opts.interact) .{ .install = interact.installHooks, .remove = interact.removeHooks } else .{},
if (build_opts.outline) .{ .remove = outline.cleanup } else .{},
+4 -6
View File
@@ -70,8 +70,7 @@ fn unitGUID(unit_id: [*:0]const u8) u64 {
[hi] "={edx}" (hi),
: [_] "{ecx}" (unit_id),
[func] "r" (@as(u32, ADDR_UnitGUID)),
: .{ .memory = true, .cc = true }
);
: .{ .memory = true, .cc = true });
return @as(u64, hi) << 32 | lo;
}
@@ -86,8 +85,7 @@ fn getObjectByGUID(guid: u64) u32 {
: [lo] "r" (lo),
[hi] "r" (hi),
[func] "r" (@as(u32, ADDR_GetObjectByGUID)),
: .{ .ecx = true, .edx = true, .memory = true, .cc = true }
);
: .{ .ecx = true, .edx = true, .memory = true, .cc = true });
}
fn updateInventoryAlertStates() void {
@@ -765,7 +763,6 @@ fn hookRefreshVisualAppearance(unit: u32, event_data: u32, extra_data: u32, forc
// =============================================================================
fn hookSceneEnd(device: u32) callconv(tc) void {
if (g_enabled and (g_local_pending_count > 0 or g_other_pending_count > 0)) {
processTimeouts(GetTickCount());
}
@@ -782,7 +779,8 @@ pub fn installHooks() void {
// Multi-DLL safety: only one instance per process should hook
var mutex_name_buf: [64]u8 = undefined;
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_TransmogCoalesceHook_{d}", .{GetCurrentProcessId()}) catch return;
// muted name scheme not quite the same because this dll exists in the wild and we want to match it
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\TransmogCoalesceHook_{d}", .{GetCurrentProcessId()}) catch return;
mutex_name_buf[mutex_name.len] = 0;
g_mutex = CreateMutexA(null, 1, @ptrCast(mutex_name_buf[0..mutex_name.len :0]));