Rename dataassets→customassets, Markers→WorldMarkers, prefix all mutexes
- dataassets module renamed to customassets everywhere (build flag, source, DLL variant name, docs) - Markers addon renamed to WorldMarkers (addon path, .toc, .lua, Bindings.xml header, Lua globals, debug log prefix, mutex name) - All 9 module mutexes now use WeirdUtils_ prefix to avoid collisions with other DLLs in the same process
This commit is contained in:
+9
-1
@@ -102,7 +102,7 @@ Patch archives are sorted case-insensitively by filename — last in the sort ge
|
||||
|
||||
No configuration needed, install and forget.
|
||||
|
||||
**DLL:** `dataassets.dll`
|
||||
**DLL:** `customassets.dll`
|
||||
|
||||
---
|
||||
|
||||
@@ -111,3 +111,11 @@ No configuration needed, install and forget.
|
||||
Fixes duplicate floating heal numbers caused by SuperWoW 1.5. Only relevant if you use SuperWoW. No configuration needed, install and forget.
|
||||
|
||||
**DLL:** `healtextfix.dll`
|
||||
|
||||
---
|
||||
|
||||
## Why No Source Code?
|
||||
|
||||
This project is distributed as pre-built DLLs only. The source code is not and will not be made publicly available.
|
||||
|
||||
These DLLs work by hooking deeply into the game client's internals — memory layout, function addresses, rendering pipeline, input handling, and more. While every feature here is built for legitimate quality-of-life use, the underlying techniques touch on too many core mechanisms that are trivially abusable. Publishing the source would be handing a candy store to bad actors: the same hooks and patterns used to render a raid marker or fix a crash can be repurposed for cheats, exploits, and in particular automation with minimal effort.
|
||||
|
||||
@@ -41,7 +41,7 @@ Users can pick the full package or grab only the features they want.
|
||||
|
||||
```zig
|
||||
// build.zig options (planned)
|
||||
const enable_dataassets = b.option(bool, "dataassets", "Enable loose file loading & permissive patch glob") orelse true;
|
||||
const enable_customassets = b.option(bool, "customassets", "Enable loose file loading & permissive patch glob") orelse true;
|
||||
const enable_transmogfix = b.option(bool, "transmogfix", "Enable transmog coalesce fix") orelse true;
|
||||
const enable_interact = b.option(bool, "interact", "Enable interact helpers") orelse true;
|
||||
const enable_outline = b.option(bool, "outline", "Enable outline rendering") orelse true;
|
||||
@@ -52,14 +52,14 @@ const enable_outline = b.option(bool, "outline", "Enable outline rendering") ore
|
||||
zig build
|
||||
|
||||
# Single-feature builds — one DLL per feature for individual distribution
|
||||
zig build -Ddataassets=true -Dtransmogfix=false -Dinteract=false -Doutline=false
|
||||
zig build -Ddataassets=false -Dtransmogfix=true -Dinteract=false -Doutline=false
|
||||
zig build -Dcustomassets=true -Dtransmogfix=false -Dinteract=false -Doutline=false
|
||||
zig build -Dcustomassets=false -Dtransmogfix=true -Dinteract=false -Doutline=false
|
||||
# etc.
|
||||
```
|
||||
|
||||
Release artifacts:
|
||||
- `weirdutils.dll` — everything
|
||||
- `dataassets.dll` — just asset/MPQ fixes
|
||||
- `customassets.dll` — just asset/MPQ fixes
|
||||
- `transmogfix.dll` — just transmog coalesce
|
||||
- `interact.dll` — just interact/loot helpers
|
||||
- `outline.dll` — just outline rendering
|
||||
@@ -69,14 +69,14 @@ All built from this repo, all sharing the same hook library and codebase.
|
||||
### Per-Feature Named Mutex
|
||||
|
||||
A user might load the full DLL alongside one of the smaller single-feature DLLs
|
||||
(e.g. they use `weirdutils.dll` for everything but also have `dataassets.dll` from
|
||||
(e.g. they use `weirdutils.dll` for everything but also have `customassets.dll` from
|
||||
before they switched). Each feature module claims a **named mutex** on load — if
|
||||
it's already held, that module skips hook installation. This way any combination
|
||||
of DLLs coexists safely with no duplicate hooks.
|
||||
|
||||
```zig
|
||||
// Each module creates a process-specific named mutex on init
|
||||
const mutex = CreateMutexA(null, 1, "Local\\WeirdUtils_DataAssets_{pid}");
|
||||
const mutex = CreateMutexA(null, 1, "Local\\WeirdUtils_CustomAssetsHook_{pid}");
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
// Another DLL already owns this feature's hooks — skip
|
||||
CloseHandle(mutex);
|
||||
@@ -127,7 +127,7 @@ gh release create v1.0 --repo YourName/WeirdUtils \
|
||||
--title "v1.0" --notes "Release notes" \
|
||||
./zig-out/lib/weirdutils.dll \
|
||||
./builds/outline.dll \
|
||||
./builds/dataassets.dll
|
||||
./builds/customassets.dll
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
# Releasing WeirdUtils
|
||||
|
||||
> **Remote:** https://codeberg.org/MarcelineVQ/WeirdUtils
|
||||
|
||||
## How the remote repo works
|
||||
|
||||
This project is developed entirely locally. The remote repo is **only** a
|
||||
distribution point for releases — no source code is pushed.
|
||||
|
||||
The remote `main` branch contains a single file: `README.md` (built from the
|
||||
local `DLL_README.md`). This must be set up once when creating the repo:
|
||||
|
||||
```sh
|
||||
tea repo create --name WeirdUtils --description "Vanilla WoW 1.12.1 utility DLLs" --login MarcelineVQ
|
||||
```
|
||||
|
||||
Codeberg disables releases on new repos by default. Enable via API:
|
||||
|
||||
```sh
|
||||
curl -s -X PATCH \
|
||||
-H "Authorization: token <your-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"has_releases":true}' \
|
||||
"https://codeberg.org/api/v1/repos/MarcelineVQ/WeirdUtils"
|
||||
```
|
||||
|
||||
Then push the initial README:
|
||||
|
||||
```sh
|
||||
# In a temporary directory:
|
||||
git init && git remote add origin ssh://git@codeberg.org/MarcelineVQ/WeirdUtils.git && git checkout -b main
|
||||
cp /path/to/weirdutils/DLL_README.md README.md
|
||||
git add README.md
|
||||
git commit -m "Add README"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
After that, the remote `main` only needs updating when `DLL_README.md` changes.
|
||||
|
||||
## 1. Build DLLs
|
||||
|
||||
Decide which modules to include in this release. Check `build.zig` for the
|
||||
current list of module flags (`b.option(bool, ...)` declarations) and their
|
||||
defaults. List them with:
|
||||
|
||||
```sh
|
||||
zig build --help 2>&1 | grep 'Enable'
|
||||
```
|
||||
|
||||
### Combined DLL
|
||||
|
||||
Build `weirdutils.dll` with only the modules for this release. Explicitly
|
||||
disable everything not being included — defaults may enable modules you don't
|
||||
want:
|
||||
|
||||
```sh
|
||||
# Example: only transmogfix + customassets + healtextfix
|
||||
zig build -Doptimize=ReleaseSmall \
|
||||
-Dscreenshot=false -Dinteract=false -Doutline=false \
|
||||
-Dworldmarkers=false -Dframecrash=false -Dcombatlog=false \
|
||||
-Dminimapicons=false \
|
||||
-Dtransmogfix=true -Dcustomassets=true -Dhealtextfix=true
|
||||
```
|
||||
|
||||
### Individual variant DLLs
|
||||
|
||||
```sh
|
||||
zig build all-variants -Doptimize=ReleaseSmall
|
||||
```
|
||||
|
||||
This builds all variants — you only attach the ones for this release.
|
||||
|
||||
### Output locations
|
||||
|
||||
| Artifact | Path |
|
||||
|----------|------|
|
||||
| Combined DLL | `zig-out/bin/weirdutils.dll` |
|
||||
| Variant DLLs | `zig-out/variants/<name>.dll` |
|
||||
|
||||
Verify:
|
||||
|
||||
```sh
|
||||
ls -lh zig-out/bin/weirdutils.dll zig-out/variants/*.dll
|
||||
```
|
||||
|
||||
## 2. Update the remote README
|
||||
|
||||
The remote README should match the features in this release. Start from
|
||||
`DLL_README.md` and remove the sections for modules not being released —
|
||||
keep the header, install instructions, and included feature sections exactly
|
||||
as they are.
|
||||
|
||||
```sh
|
||||
# from a clone or worktree of the remote repo
|
||||
# edit README.md: remove sections for modules not in this release
|
||||
git add README.md
|
||||
git commit -m "Update README for vX.Y.Z"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## 3. Write the release notes
|
||||
|
||||
Use this template — fill in the sections that apply, delete the rest:
|
||||
|
||||
```markdown
|
||||
## What's New
|
||||
|
||||
- ...
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
## Module Changes
|
||||
|
||||
- ...
|
||||
|
||||
## Notes
|
||||
|
||||
- Place DLLs next to `WoW.exe` and add to `dlls.txt`
|
||||
- `weirdutils.dll` includes all features; individual DLLs are also provided
|
||||
```
|
||||
|
||||
## 4. Create the release and upload DLLs
|
||||
|
||||
Uses `tea` (Gitea/Forgejo CLI) which handles release creation, tagging, and
|
||||
asset upload in one command. The tag is created on the remote automatically.
|
||||
|
||||
Run from a clone of the remote repo, or specify `--repo` explicitly.
|
||||
Only attach the combined DLL and the variant DLLs for modules in this release:
|
||||
|
||||
```sh
|
||||
# Example: transmogfix + customassets + healtextfix release
|
||||
tea release create \
|
||||
--repo MarcelineVQ/WeirdUtils \
|
||||
--tag vX.Y.Z \
|
||||
--target main \
|
||||
--title "vX.Y.Z" \
|
||||
--note-file release-notes.md \
|
||||
--asset zig-out/bin/weirdutils.dll \
|
||||
--asset zig-out/variants/transmogfix.dll \
|
||||
--asset zig-out/variants/customassets.dll \
|
||||
--asset zig-out/variants/healtextfix.dll
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Built with `ReleaseSmall` (both default and `all-variants`)
|
||||
- [ ] Remote README updated (if `DLL_README.md` changed)
|
||||
- [ ] Release created and DLLs uploaded via `tea`
|
||||
@@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void {
|
||||
const enable_combatlog = b.option(bool, "combatlog", "Enable combat log freshness") orelse true;
|
||||
const enable_minimapicons = b.option(bool, "minimapicons", "Enable custom minimap icons") orelse true;
|
||||
const enable_transmogfix = b.option(bool, "transmogfix", "Enable transmog update coalescing") orelse true;
|
||||
const enable_dataassets = b.option(bool, "dataassets", "Enable loose file loading & permissive patch glob") orelse true;
|
||||
const enable_customassets = b.option(bool, "customassets", "Enable loose file loading & permissive patch glob") orelse true;
|
||||
const enable_healtextfix = b.option(bool, "healtextfix", "Enable SuperWoW heal text fix") orelse true;
|
||||
|
||||
// Create build options module
|
||||
@@ -30,7 +30,7 @@ pub fn build(b: *std.Build) void {
|
||||
build_options.addOption(bool, "enable_combatlog", enable_combatlog);
|
||||
build_options.addOption(bool, "enable_minimapicons", enable_minimapicons);
|
||||
build_options.addOption(bool, "enable_transmogfix", enable_transmogfix);
|
||||
build_options.addOption(bool, "enable_dataassets", enable_dataassets);
|
||||
build_options.addOption(bool, "enable_customassets", enable_customassets);
|
||||
build_options.addOption(bool, "enable_healtextfix", enable_healtextfix);
|
||||
const build_options_module = build_options.createModule();
|
||||
|
||||
@@ -60,19 +60,19 @@ pub fn build(b: *std.Build) void {
|
||||
const build_all_step = b.step("all-variants", "Build all DLL variants");
|
||||
|
||||
// Helper to create a single-module build
|
||||
const Variant = struct { name: []const u8, screenshot: bool, interact: bool, outline: bool, worldmarkers: bool, framecrash: bool, combatlog: bool, minimapicons: bool, transmogfix: bool, dataassets: bool, healtextfix: bool };
|
||||
const Variant = struct { name: []const u8, screenshot: bool, interact: bool, outline: bool, worldmarkers: bool, framecrash: bool, combatlog: bool, minimapicons: bool, transmogfix: bool, customassets: bool, healtextfix: bool };
|
||||
inline for (&[_]Variant{
|
||||
.{ .name = "full", .screenshot = true, .interact = true, .outline = true, .worldmarkers = true, .framecrash = true, .combatlog = true, .minimapicons = true, .transmogfix = true, .dataassets = true, .healtextfix = true },
|
||||
.{ .name = "screenshot", .screenshot = true, .interact = false, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "interact", .screenshot = false, .interact = true, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "outline", .screenshot = false, .interact = false, .outline = true, .worldmarkers = false, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "worldmarkers", .screenshot = false, .interact = false, .outline = false, .worldmarkers = true, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "framecrash", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = false, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "combatlog", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = true, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "minimapicons", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = false, .minimapicons = true, .transmogfix = false, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "transmogfix", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = false, .minimapicons = false, .transmogfix = true, .dataassets = false, .healtextfix = false },
|
||||
.{ .name = "dataassets", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = false, .minimapicons = false, .transmogfix = false, .dataassets = true, .healtextfix = false },
|
||||
.{ .name = "healtextfix", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = false, .minimapicons = false, .transmogfix = false, .dataassets = false, .healtextfix = true },
|
||||
.{ .name = "full", .screenshot = true, .interact = true, .outline = true, .worldmarkers = true, .framecrash = true, .combatlog = true, .minimapicons = true, .transmogfix = true, .customassets = true, .healtextfix = true },
|
||||
.{ .name = "screenshot", .screenshot = true, .interact = false, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "interact", .screenshot = false, .interact = true, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "outline", .screenshot = false, .interact = false, .outline = true, .worldmarkers = false, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "worldmarkers", .screenshot = false, .interact = false, .outline = false, .worldmarkers = true, .framecrash = true, .combatlog = true, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "framecrash", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = false, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "combatlog", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = true, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "minimapicons", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = true, .combatlog = false, .minimapicons = true, .transmogfix = false, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "transmogfix", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = false, .minimapicons = false, .transmogfix = true, .customassets = false, .healtextfix = false },
|
||||
.{ .name = "customassets", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = false, .minimapicons = false, .transmogfix = false, .customassets = true, .healtextfix = false },
|
||||
.{ .name = "healtextfix", .screenshot = false, .interact = false, .outline = false, .worldmarkers = false, .framecrash = false, .combatlog = false, .minimapicons = false, .transmogfix = false, .customassets = false, .healtextfix = true },
|
||||
}) |variant| {
|
||||
const opts = b.addOptions();
|
||||
opts.addOption(bool, "enable_screenshot", variant.screenshot);
|
||||
@@ -83,7 +83,7 @@ pub fn build(b: *std.Build) void {
|
||||
opts.addOption(bool, "enable_combatlog", variant.combatlog);
|
||||
opts.addOption(bool, "enable_minimapicons", variant.minimapicons);
|
||||
opts.addOption(bool, "enable_transmogfix", variant.transmogfix);
|
||||
opts.addOption(bool, "enable_dataassets", variant.dataassets);
|
||||
opts.addOption(bool, "enable_customassets", variant.customassets);
|
||||
opts.addOption(bool, "enable_healtextfix", variant.healtextfix);
|
||||
|
||||
const variant_lib = b.addLibrary(.{
|
||||
|
||||
+193
-6
@@ -1,12 +1,15 @@
|
||||
//! Combat log freshness module.
|
||||
//! Combat log session rotation.
|
||||
//!
|
||||
//! Appends a timestamp to the combat log filename so each client session
|
||||
//! gets a fresh log file (e.g. WoWCombatLog_20260227_123456.txt).
|
||||
//! Redirects the combat log to a per-session file named with timestamp + PID
|
||||
//! (e.g. `Logs\WoWCombatLog_20260303_193045_1234.txt`), and writes a
|
||||
//! `COMBATLOG_SESSION,<PlayerName>` marker line when combat logging is enabled.
|
||||
//!
|
||||
//! TODO: Find and hook the combat log open/create function.
|
||||
//! All DLL-side — no Lua addon needed.
|
||||
|
||||
const std = @import("std");
|
||||
const hook = @import("zhook");
|
||||
const con = @import("../console.zig");
|
||||
const o = @import("offsets.zig");
|
||||
|
||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
||||
extern "kernel32" fn CreateMutexA(lpMutexAttributes: ?*anyopaque, bInitialOwner: i32, lpName: [*:0]const u8) callconv(WINAPI) ?*anyopaque;
|
||||
@@ -16,15 +19,186 @@ extern "kernel32" fn GetLastError() callconv(WINAPI) u32;
|
||||
extern "kernel32" fn GetCurrentProcessId() callconv(WINAPI) u32;
|
||||
const ERROR_ALREADY_EXISTS: u32 = 183;
|
||||
|
||||
const SYSTEMTIME = extern struct {
|
||||
wYear: u16,
|
||||
wMonth: u16,
|
||||
wDayOfWeek: u16,
|
||||
wDay: u16,
|
||||
wHour: u16,
|
||||
wMinute: u16,
|
||||
wSecond: u16,
|
||||
wMilliseconds: u16,
|
||||
};
|
||||
extern "kernel32" fn GetLocalTime(lpSystemTime: *SYSTEMTIME) callconv(WINAPI) void;
|
||||
|
||||
var g_mutex: ?*anyopaque = null;
|
||||
var g_is_hook_owner: bool = false;
|
||||
|
||||
// =============================================================================
|
||||
// Path redirect
|
||||
// =============================================================================
|
||||
|
||||
/// Static buffer for the redirected combat log path. Must outlive the process.
|
||||
var g_path_buf: [260]u8 = undefined;
|
||||
|
||||
/// Saved original path pointer so we can restore on unload.
|
||||
var g_original_path_ptr: u32 = 0;
|
||||
|
||||
fn setupPathRedirect() void {
|
||||
// Save the original pointer value
|
||||
g_original_path_ptr = hook.readMem(u32, o.COMBAT_LOG_PATH_PTR);
|
||||
|
||||
var st: SYSTEMTIME = undefined;
|
||||
GetLocalTime(&st);
|
||||
const pid = GetCurrentProcessId();
|
||||
|
||||
const path = std.fmt.bufPrint(&g_path_buf, "Logs\\WoWCombatLog_{d:0>4}{d:0>2}{d:0>2}_{d:0>2}{d:0>2}{d:0>2}_{d}.txt", .{
|
||||
st.wYear, st.wMonth, st.wDay,
|
||||
st.wHour, st.wMinute, st.wSecond,
|
||||
pid,
|
||||
}) catch {
|
||||
con.print("[combatlog] path format error\n");
|
||||
return;
|
||||
};
|
||||
g_path_buf[path.len] = 0;
|
||||
|
||||
// Overwrite the pointer at 0x00843610 to point to our buffer.
|
||||
// .data section is RW, no VirtualProtect needed.
|
||||
const ptr_bytes: [4]u8 = @bitCast(@intFromPtr(&g_path_buf));
|
||||
hook.writeMem(o.COMBAT_LOG_PATH_PTR, &ptr_bytes);
|
||||
|
||||
con.fmt("[combatlog] path: {s}\n", .{path});
|
||||
}
|
||||
|
||||
fn restorePathPointer() void {
|
||||
if (g_original_path_ptr != 0) {
|
||||
const ptr_bytes: [4]u8 = @bitCast(g_original_path_ptr);
|
||||
hook.writeMem(o.COMBAT_LOG_PATH_PTR, &ptr_bytes);
|
||||
g_original_path_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Player identity (same inline asm patterns as markers module)
|
||||
// =============================================================================
|
||||
|
||||
/// Get local player GUID via GetPlayerGUID (0x468550).
|
||||
/// __fastcall(), no params, returns EAX(low):EDX(high).
|
||||
fn getPlayerGUID() u64 {
|
||||
var lo: u32 = undefined;
|
||||
var hi: u32 = undefined;
|
||||
asm volatile ("call *%[func]"
|
||||
: [_] "={eax}" (lo),
|
||||
[_] "={edx}" (hi),
|
||||
: [func] "r" (o.FN_GET_PLAYER_GUID),
|
||||
: .{ .ecx = true, .memory = true, .cc = true });
|
||||
return (@as(u64, hi) << 32) | lo;
|
||||
}
|
||||
|
||||
/// Look up a player name from the name cache by GUID.
|
||||
/// Calls RetrieveNPCDataFromCache — __thiscall(ECX=cache), 6 stack params, RET 0x18.
|
||||
fn getNameFromGUID(guid_lo: u32, guid_hi: u32) ?[*:0]const u8 {
|
||||
if (guid_lo == 0 and guid_hi == 0) return null;
|
||||
var name_buf: [2]u32 = .{ 0, 0 };
|
||||
const stack_args = [6]u32{
|
||||
guid_lo,
|
||||
guid_hi,
|
||||
@intFromPtr(&name_buf),
|
||||
0, 0, 0,
|
||||
};
|
||||
const result: u32 = asm volatile (
|
||||
\\ push 20(%[a])
|
||||
\\ push 16(%[a])
|
||||
\\ push 12(%[a])
|
||||
\\ push 8(%[a])
|
||||
\\ push 4(%[a])
|
||||
\\ push (%[a])
|
||||
\\ call *%[func]
|
||||
: [ret] "={eax}" (-> u32),
|
||||
: [_] "{ecx}" (@as(u32, o.NAME_CACHE_OBJ)),
|
||||
[a] "r" (&stack_args),
|
||||
[func] "r" (@as(u32, o.FN_NAME_CACHE_LOOKUP)),
|
||||
: .{ .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
return if (result != 0) @ptrFromInt(result) else null;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Session marker
|
||||
// =============================================================================
|
||||
|
||||
var g_session_marker_written: bool = false;
|
||||
|
||||
/// Write a COMBATLOG_SESSION line with the player's name to the combat log.
|
||||
/// Called once after combat logging is first enabled for this session.
|
||||
fn maybeWriteSessionMarker() void {
|
||||
// Read combat log handle — 0 means log not active yet
|
||||
const combat_handle = hook.readMem(u32, o.COMBAT_LOG_HANDLE);
|
||||
if (combat_handle == 0) return;
|
||||
|
||||
const player_guid = getPlayerGUID();
|
||||
if (player_guid == 0) return;
|
||||
|
||||
const guid_lo: u32 = @truncate(player_guid);
|
||||
const guid_hi: u32 = @truncate(player_guid >> 32);
|
||||
const name = getNameFromGUID(guid_lo, guid_hi) orelse {
|
||||
// TODO: If player name is unavailable at this point (e.g.
|
||||
// LoggingCombat enabled before login), could hook OnWorldUpdate for a
|
||||
// per-frame retry until name is available.
|
||||
con.print("[combatlog] session marker: player name not yet available\n");
|
||||
return;
|
||||
};
|
||||
|
||||
// WriteFormattedLogMessage — __cdecl(handle, fmt, va_list)
|
||||
// Third arg is a va_list (pointer to the arg list), NOT the arg itself.
|
||||
const fmt_str: [*:0]const u8 = "COMBATLOG_SESSION,%s";
|
||||
var va_args = [1]u32{@intFromPtr(name)};
|
||||
const cdecl_args = [3]u32{
|
||||
combat_handle,
|
||||
@intFromPtr(fmt_str),
|
||||
@intFromPtr(&va_args),
|
||||
};
|
||||
asm volatile (
|
||||
\\ push 8(%[a])
|
||||
\\ push 4(%[a])
|
||||
\\ push (%[a])
|
||||
\\ call *%[func]
|
||||
\\ add $12, %%esp
|
||||
:
|
||||
: [a] "r" (&cdecl_args),
|
||||
[func] "r" (@as(u32, o.FN_WRITE_FMT_LOG_MSG)),
|
||||
: .{ .eax = true, .ecx = true, .edx = true, .memory = true, .cc = true });
|
||||
|
||||
g_session_marker_written = true;
|
||||
con.fmt("[combatlog] session: {s}\n", .{std.mem.span(name)});
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EnableChatLogging hook
|
||||
// =============================================================================
|
||||
|
||||
const fc = std.builtin.CallingConvention{ .x86_fastcall = .{} };
|
||||
|
||||
var enable_logging_hook: hook.Detour(fn (u32, u32) callconv(fc) u32) = .{};
|
||||
|
||||
fn enableChatLoggingDetour(lua_state: u32, index: u32) callconv(fc) u32 {
|
||||
const result = enable_logging_hook.callOriginal(.{ lua_state, index });
|
||||
// index 1 = combat log
|
||||
if (index == 1 and !g_session_marker_written) {
|
||||
maybeWriteSessionMarker();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Install / remove hooks
|
||||
// =============================================================================
|
||||
|
||||
pub fn installHooks() void {
|
||||
con.print("[combatlog] Module loaded (stub)\n");
|
||||
con.print("[combatlog] 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\\CombatlogHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_CombatlogHook_{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]));
|
||||
@@ -38,10 +212,23 @@ pub fn installHooks() void {
|
||||
return;
|
||||
}
|
||||
g_is_hook_owner = true;
|
||||
|
||||
// Redirect combat log path to timestamped+PID filename
|
||||
setupPathRedirect();
|
||||
|
||||
// Hook EnableChatLogging to inject session marker on combat log enable
|
||||
if (enable_logging_hook.attach(o.FN_ENABLE_CHAT_LOGGING, &enableChatLoggingDetour) != .ok) {
|
||||
con.print("[combatlog] FAILED to hook EnableChatLogging!\n");
|
||||
} else {
|
||||
con.print("[combatlog] hooked EnableChatLogging OK\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn removeHooks() void {
|
||||
if (g_is_hook_owner) {
|
||||
enable_logging_hook.detach();
|
||||
restorePathPointer();
|
||||
|
||||
if (g_mutex) |m| {
|
||||
_ = ReleaseMutex(m);
|
||||
_ = CloseHandle(m);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// =============================================================================
|
||||
// dataassets - Loose file loading & permissive patch glob
|
||||
// customassets - Loose file loading & permissive patch glob
|
||||
// =============================================================================
|
||||
//
|
||||
// 1. Patches patch-?.MPQ → patch-*.MPQ so multi-char patch names work
|
||||
@@ -192,7 +192,7 @@ fn checkFileExistenceDetour(filename_ptr: u32, flags: u32, output_buffer_ptr: u3
|
||||
if (filename_ptr != 0) {
|
||||
if (looseFilesLookup(filename_ptr)) |disk_path| {
|
||||
const raw: [*]const u8 = @ptrFromInt(filename_ptr);
|
||||
con.fmt("[dataassets] loose hit: \"{s}\"\n", .{raw[0..cStrLen(raw)]});
|
||||
con.fmt("[customassets] loose hit: \"{s}\"\n", .{raw[0..cStrLen(raw)]});
|
||||
|
||||
if (output_buffer_ptr != 0) {
|
||||
const disk_len = cStrLen(disk_path);
|
||||
@@ -281,11 +281,11 @@ var g_mutex: ?*anyopaque = null;
|
||||
var g_is_hook_owner: bool = false;
|
||||
|
||||
pub fn installHooks() void {
|
||||
con.print("[dataassets] Module loaded\n");
|
||||
con.print("[customassets] 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\\DataAssetsHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_CustomAssetsHook_{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]));
|
||||
@@ -295,7 +295,7 @@ pub fn installHooks() void {
|
||||
_ = CloseHandle(g_mutex.?);
|
||||
g_mutex = null;
|
||||
g_is_hook_owner = false;
|
||||
con.print("[dataassets] Another DLL owns hooks (mutex taken), skipping\n");
|
||||
con.print("[customassets] Another DLL owns hooks (mutex taken), skipping\n");
|
||||
return;
|
||||
}
|
||||
g_is_hook_owner = true;
|
||||
@@ -627,7 +627,7 @@ fn restoreVtableSlot(slot_addr: usize, saved: *usize) void {
|
||||
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\\FramecrashHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_FramecrashHook_{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]));
|
||||
|
||||
@@ -406,7 +406,7 @@ 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\\InteractHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_InteractHook_{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]));
|
||||
|
||||
+8
-8
@@ -12,7 +12,7 @@ const build_opts = struct {
|
||||
const combatlog = @import("build_options").enable_combatlog;
|
||||
const minimapicons = @import("build_options").enable_minimapicons;
|
||||
const transmogfix = @import("build_options").enable_transmogfix;
|
||||
const dataassets = @import("build_options").enable_dataassets;
|
||||
const customassets = @import("build_options").enable_customassets;
|
||||
const healtextfix = @import("build_options").enable_healtextfix;
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const framecrash = if (build_opts.framecrash) @import("framecrash/framecrash.zig
|
||||
const combatlog = if (build_opts.combatlog) @import("combatlog/combatlog.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 dataassets = if (build_opts.dataassets) @import("dataassets/dataassets.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 WINAPI = std.builtin.CallingConvention.winapi;
|
||||
@@ -168,8 +168,8 @@ const outline_files = if (build_opts.outline) [_]FileEntry{
|
||||
} else [_]FileEntry{};
|
||||
|
||||
const markers_files = if (build_opts.worldmarkers) [_]FileEntry{
|
||||
.{ .name = "Markers.toc", .data = @embedFile("markers/addon/Markers.toc") },
|
||||
.{ .name = "Markers.lua", .data = @embedFile("markers/addon/Markers.lua") },
|
||||
.{ .name = "WorldMarkers.toc", .data = @embedFile("markers/addon/Markers.toc") },
|
||||
.{ .name = "WorldMarkers.lua", .data = @embedFile("markers/addon/Markers.lua") },
|
||||
.{ .name = "Bindings.xml", .data = @embedFile("markers/addon/Bindings.xml") },
|
||||
} else [_]FileEntry{};
|
||||
|
||||
@@ -216,7 +216,7 @@ const addon_prefixes = [_]AddonPrefix{
|
||||
.{ .prefix = "Interface\\AddOns\\Screenshot\\", .files = &screenshot_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Interact\\", .files = &interact_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Outline\\", .files = &outline_files },
|
||||
.{ .prefix = "Interface\\AddOns\\Markers\\", .files = &markers_files },
|
||||
.{ .prefix = "Interface\\AddOns\\WorldMarkers\\", .files = &markers_files },
|
||||
.{ .prefix = "Spells\\", .files = &markers_spells_assets },
|
||||
.{ .prefix = "Spells\\", .files = &markers_xyz_model },
|
||||
.{ .prefix = "World\\Expansion01\\Doodads\\Zulaman\\Doors\\", .files = &markers_world_assets },
|
||||
@@ -718,12 +718,12 @@ fn loadAddonsDetour(error_handler: u32) callconv(fc) void {
|
||||
}
|
||||
if (build_opts.worldmarkers and markers.isActive()) {
|
||||
callLoadFileListWithIncludes(
|
||||
"Interface\\AddOns\\Markers\\Markers.toc",
|
||||
"Interface\\AddOns\\WorldMarkers\\WorldMarkers.toc",
|
||||
&md5ctx,
|
||||
error_handler,
|
||||
);
|
||||
callLoadUIBindingsFromFile(
|
||||
"Interface\\AddOns\\Markers\\Bindings.xml",
|
||||
"Interface\\AddOns\\WorldMarkers\\Bindings.xml",
|
||||
&md5ctx,
|
||||
error_handler,
|
||||
);
|
||||
@@ -803,7 +803,7 @@ const ModuleHooks = struct {
|
||||
/// Order matters: modules are installed top-to-bottom, removed bottom-to-top.
|
||||
/// Modules with remove_on_shutdown run their remove during shutdownDetour too.
|
||||
const modules = [_]ModuleHooks{
|
||||
if (build_opts.dataassets) .{ .install = dataassets.installHooks, .remove = dataassets.removeHooks } else .{},
|
||||
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.transmogfix) .{ .install = transmogfix.installHooks, .remove = transmogfix.removeHooks } else .{},
|
||||
|
||||
@@ -410,7 +410,7 @@ The hash table uses a specific encryption algorithm (documented in wowdev.wiki a
|
||||
|
||||
### Data Assets Synergy
|
||||
|
||||
The [dataassets project](/media/storage/projects/zig/dataassets/) already implements the glob pattern patch (`0x82edc2: '?' → '*'`) for multi-character patch names. If combined with dataassets, the temp file could use any name like `patch-weirdutils.MPQ`.
|
||||
The [customassets project](/media/storage/projects/zig/customassets/) already implements the glob pattern patch (`0x82edc2: '?' → '*'`) for multi-character patch names. If combined with customassets, the temp file could use any name like `patch-weirdutils.MPQ`.
|
||||
|
||||
---
|
||||
|
||||
@@ -532,11 +532,11 @@ If we could insert entries into this hash table mapping our asset paths to disk
|
||||
|
||||
---
|
||||
|
||||
## Approach D: CheckFileExistence Hook (dataassets Pattern)
|
||||
## Approach D: CheckFileExistence Hook (customassets Pattern)
|
||||
|
||||
### Concept
|
||||
|
||||
The [dataassets project](/media/storage/projects/zig/dataassets/) takes a different approach entirely:
|
||||
The [customassets project](/media/storage/projects/zig/customassets/) takes a different approach entirely:
|
||||
|
||||
1. **NOP two gates** in `File_FindInArchive` (0x654b5c, 0x654b6a) that restrict `CheckFileExistence` to only "Interface/AddOns" paths
|
||||
2. **Hook `CheckFileExistence`** (0x654DD0) to check a hash map of loose disk files
|
||||
@@ -544,7 +544,7 @@ The [dataassets project](/media/storage/projects/zig/dataassets/) takes a differ
|
||||
|
||||
This works for **disk-based** loose files but not for in-memory embedded data. However, combined with writing temp files to disk, it provides a clean single-hook solution.
|
||||
|
||||
### Key Addresses (from dataassets)
|
||||
### Key Addresses (from customassets)
|
||||
|
||||
| Target | Address | Patch |
|
||||
|--------|---------|-------|
|
||||
@@ -564,7 +564,7 @@ This works for **disk-based** loose files but not for in-memory embedded data. H
|
||||
| Fake contexts | ~20 | **0** | 0 | 1 | 0 | 0 |
|
||||
| Async handling | Manual | **Native** | Native | Uncertain | Native | Native |
|
||||
| Build complexity | Low | **Low** | Medium (MPQ builder) | Medium (MPQ builder) | High (struct RE) | Low |
|
||||
| RE work needed | Done | **Minimal** (verify path redirect) | Archive_OpenUnified conv | Same as A + async verify | Full struct layout | Already done (dataassets) |
|
||||
| RE work needed | Done | **Minimal** (verify path redirect) | Archive_OpenUnified conv | Same as A + async verify | Full struct layout | Already done (customassets) |
|
||||
| Risk | Proven but fragile | **Low** (real OS handles) | Low (native I/O) | Medium (async path?) | High (struct mismatch) | Low |
|
||||
| Cleanup needed | None | **Auto** (DELETE_ON_CLOSE) | Delete temp file | None | Remove from array | Delete temp files |
|
||||
| Purely in-memory | Yes | Mostly (cache-backed) | No | Yes | Yes | No |
|
||||
@@ -597,7 +597,7 @@ If a single-file solution is preferred over ~20 temp files, building a real MPQ
|
||||
|
||||
### Worth Combining With: Data Assets Integration
|
||||
|
||||
The dataassets project's `CheckFileExistence` hook and glob pattern patch could complement either approach, especially for supporting user-provided loose asset files alongside our embedded ones.
|
||||
The customassets project's `CheckFileExistence` hook and glob pattern patch could complement either approach, especially for supporting user-provided loose asset files alongside our embedded ones.
|
||||
|
||||
---
|
||||
|
||||
@@ -687,6 +687,6 @@ The dataassets project's `CheckFileExistence` hook and glob pattern patch could
|
||||
| Archive search critsec | 0xc54008 | Critical section for archive ops |
|
||||
| Archive search state | 0xc53ff0 | Used by File_FindInArchive |
|
||||
| Patch glob "patch-?.MPQ" | 0x82edbc | Glob pattern for patch discovery |
|
||||
| Patch glob char | 0x82edc2 | The '?' byte (dataassets patches to '*') |
|
||||
| Patch glob char | 0x82edc2 | The '?' byte (customassets patches to '*') |
|
||||
| Data path format | 0x82edc8 | `"Data\%s"` format string |
|
||||
| SArchive RTTI | 0x82e248 | `".PAVSArchive@@"` |
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<Bindings>
|
||||
<Binding name="Place Blue Square" header="MARKERS">
|
||||
<Binding name="Place Blue Square" header="WORLDMARKERS">
|
||||
WorldMarker(1)
|
||||
</Binding>
|
||||
<Binding name="Place Green Triangle" header="MARKERS">
|
||||
<Binding name="Place Green Triangle" header="WORLDMARKERS">
|
||||
WorldMarker(2)
|
||||
</Binding>
|
||||
<Binding name="Place Purple Diamond" header="MARKERS">
|
||||
<Binding name="Place Purple Diamond" header="WORLDMARKERS">
|
||||
WorldMarker(3)
|
||||
</Binding>
|
||||
<Binding name="Place Red Cross" header="MARKERS">
|
||||
<Binding name="Place Red Cross" header="WORLDMARKERS">
|
||||
WorldMarker(4)
|
||||
</Binding>
|
||||
<Binding name="Place Yellow Star" header="MARKERS">
|
||||
<Binding name="Place Yellow Star" header="WORLDMARKERS">
|
||||
WorldMarker(5)
|
||||
</Binding>
|
||||
<Binding name="Clear All Markers" header="MARKERS">
|
||||
<Binding name="Clear All Markers" header="WORLDMARKERS">
|
||||
ClearWorldMarker()
|
||||
</Binding>
|
||||
</Bindings>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
-- Markers addon (embedded in DLL, loaded from memory)
|
||||
-- Part of WeirdUtils - only loaded when markers module is compiled
|
||||
-- WorldMarkers addon (embedded in DLL, loaded from memory)
|
||||
-- Part of WeirdUtils - only loaded when worldmarkers module is compiled
|
||||
|
||||
MARKERS_VERSION = 4
|
||||
WORLDMARKERS_VERSION = 4
|
||||
|
||||
BINDING_HEADER_MARKERS = "Markers"
|
||||
BINDING_HEADER_WORLDMARKERS = "World Markers"
|
||||
|
||||
-- =============================================================================
|
||||
-- Debug logging
|
||||
@@ -343,7 +343,7 @@ frame:RegisterEvent("RAID_ROSTER_UPDATE")
|
||||
|
||||
frame:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_LOGIN" then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers|r v" .. MARKERS_VERSION .. " loaded")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00WorldMarkers|r v" .. WORLDMARKERS_VERSION .. " loaded")
|
||||
lastGroupSize = getGroupSize()
|
||||
scheduleSyncRequest()
|
||||
elseif event == "CHAT_MSG_ADDON" then
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Interface: 11200
|
||||
## Title: Markers
|
||||
## Notes: Client-side raid markers and indicators
|
||||
## Title: WorldMarkers
|
||||
## Notes: Client-side world markers
|
||||
## Version: 1.0
|
||||
## Dependencies: WeirdUtils
|
||||
|
||||
Markers.lua
|
||||
WorldMarkers.lua
|
||||
|
||||
+34
-34
@@ -38,7 +38,7 @@ const ERROR_ALREADY_EXISTS: u32 = 183;
|
||||
var g_mutex: ?*anyopaque = null;
|
||||
var g_is_hook_owner: bool = false;
|
||||
|
||||
/// True if this DLL instance owns the markers hooks and Lua API is safe to use.
|
||||
/// True if this DLL instance owns the world markers hooks and Lua API is safe to use.
|
||||
pub fn isActive() bool {
|
||||
return g_is_hook_owner;
|
||||
}
|
||||
@@ -466,7 +466,7 @@ fn spawnEntity(index: usize, pos: Vec3) bool {
|
||||
var position = [3]f32{ pos.x, pos.y, pos.z + MARKER_Z_OFFSET };
|
||||
|
||||
const obj = createEntityInstance(MODEL_PATHS[index], &position, 0.0, 0, 1) orelse {
|
||||
con.fmt("[markers] failed to create marker {d}\n", .{index + 1});
|
||||
con.fmt("[worldmarkers] failed to create marker {d}\n", .{index + 1});
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -474,7 +474,7 @@ fn spawnEntity(index: usize, pos: Vec3) bool {
|
||||
marker_created_tick[index] = GetTickCount();
|
||||
hold_queued[index] = false;
|
||||
|
||||
con.fmt("[markers] marker {d} spawned at {d:.1}, {d:.1}, {d:.1} @0x{x}\n", .{
|
||||
con.fmt("[worldmarkers] marker {d} spawned at {d:.1}, {d:.1}, {d:.1} @0x{x}\n", .{
|
||||
index + 1, pos.x, pos.y, pos.z, @intFromPtr(obj),
|
||||
});
|
||||
return true;
|
||||
@@ -511,7 +511,7 @@ fn clearAllMarkers() void {
|
||||
}
|
||||
marker_defs[i] = EMPTY_DEF;
|
||||
}
|
||||
if (any) con.print("[markers] all markers cleared\n");
|
||||
if (any) con.print("[worldmarkers] all markers cleared\n");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -522,20 +522,20 @@ fn clearAllMarkers() void {
|
||||
/// Returns 1 on success, nil on permission denied.
|
||||
pub fn luaWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
if (!canSetMarkers()) {
|
||||
con.print("[markers] WorldMarker: no permission\n");
|
||||
con.print("[worldmarkers] WorldMarker: no permission\n");
|
||||
return 0; // nil — addon shows user message
|
||||
}
|
||||
|
||||
const nargs = lua.gettop(L);
|
||||
|
||||
if (nargs < 1 or !lua.isnumber(L, 1)) {
|
||||
con.print("[markers] WorldMarker: expected index (1-5)\n");
|
||||
con.print("[worldmarkers] WorldMarker: expected index (1-5)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) {
|
||||
con.print("[markers] WorldMarker: index must be 1-5\n");
|
||||
con.print("[worldmarkers] WorldMarker: index must be 1-5\n");
|
||||
return 0;
|
||||
}
|
||||
const index: usize = @intCast(raw_index - 1);
|
||||
@@ -547,17 +547,17 @@ pub fn luaWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
_ = placeMarker(index, .{ .x = x, .y = y, .z = z });
|
||||
} else if (nargs >= 2 and lua.isstring(L, 2)) {
|
||||
const unit_id = lua.tostring(L, 2) orelse {
|
||||
con.print("[markers] WorldMarker: invalid unit string\n");
|
||||
con.print("[worldmarkers] WorldMarker: invalid unit string\n");
|
||||
return 0;
|
||||
};
|
||||
const pos = resolveUnitPosition(unit_id) orelse {
|
||||
con.fmt("[markers] WorldMarker: unit '{s}' not found\n", .{std.mem.span(unit_id)});
|
||||
con.fmt("[worldmarkers] WorldMarker: unit '{s}' not found\n", .{std.mem.span(unit_id)});
|
||||
return 0;
|
||||
};
|
||||
_ = placeMarker(index, pos);
|
||||
} else {
|
||||
const pos = getCursorTerrainPosition() orelse {
|
||||
con.print("[markers] no terrain under cursor\n");
|
||||
con.print("[worldmarkers] no terrain under cursor\n");
|
||||
return 0;
|
||||
};
|
||||
_ = placeMarker(index, pos);
|
||||
@@ -571,7 +571,7 @@ pub fn luaWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
/// Returns 1 on success, nil on permission denied.
|
||||
pub fn luaClearWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
if (!canSetMarkers()) {
|
||||
con.print("[markers] ClearWorldMarker: no permission\n");
|
||||
con.print("[worldmarkers] ClearWorldMarker: no permission\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -586,13 +586,13 @@ pub fn luaClearWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
}
|
||||
|
||||
if (!lua.isnumber(L, 1)) {
|
||||
con.print("[markers] ClearWorldMarker: expected index (1-5) or nil\n");
|
||||
con.print("[worldmarkers] ClearWorldMarker: expected index (1-5) or nil\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) {
|
||||
con.print("[markers] ClearWorldMarker: index must be 1-5\n");
|
||||
con.print("[worldmarkers] ClearWorldMarker: index must be 1-5\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ fn tickAnimations() void {
|
||||
const addr = @intFromPtr(entity);
|
||||
const refcount = hook.readMem(u16, addr + 0x0E);
|
||||
if (refcount <= 1) {
|
||||
con.fmt("[markers] zombie detected [{d}] @0x{x} rc={d}, destroying\n", .{ i + 1, addr, refcount });
|
||||
con.fmt("[worldmarkers] zombie detected [{d}] @0x{x} rc={d}, destroying\n", .{ i + 1, addr, refcount });
|
||||
cleanupEntity(entity);
|
||||
marker_entities[i] = null;
|
||||
hold_queued[i] = false;
|
||||
@@ -664,7 +664,7 @@ fn tickAnimations() void {
|
||||
const dist_sq = dx * dx + dy * dy + dz * dz;
|
||||
|
||||
if (dist_sq < RESPAWN_DISTANCE_SQ) {
|
||||
con.fmt("[markers] respawning [{d}] dist={d:.0}\n", .{ i + 1, @sqrt(dist_sq) });
|
||||
con.fmt("[worldmarkers] respawning [{d}] dist={d:.0}\n", .{ i + 1, @sqrt(dist_sq) });
|
||||
_ = spawnEntity(i, marker_defs[i].pos);
|
||||
}
|
||||
}
|
||||
@@ -684,7 +684,7 @@ pub fn luaSetMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
|
||||
const sender = lua.tostring(L, 6) orelse return 0;
|
||||
if (!senderHasPermission(sender)) {
|
||||
con.fmt("[markers] SetMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
con.fmt("[worldmarkers] SetMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ pub fn luaSetMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
.active = true,
|
||||
};
|
||||
|
||||
con.fmt("[markers] SetMarkerDef [{d}] at {d:.1},{d:.1},{d:.1} area={d}\n", .{ index + 1, x, y, z, area_id });
|
||||
con.fmt("[worldmarkers] SetMarkerDef [{d}] at {d:.1},{d:.1},{d:.1} area={d}\n", .{ index + 1, x, y, z, area_id });
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -720,13 +720,13 @@ pub fn luaSetMarkerDefSync(L: lua.State) callconv(.c) u32 {
|
||||
if (!lua.isnumber(L, 1) or !lua.isnumber(L, 2) or !lua.isnumber(L, 3) or !lua.isnumber(L, 4) or !lua.isnumber(L, 5) or !lua.isstring(L, 6)) return 0;
|
||||
|
||||
if (!canSetMarkers()) {
|
||||
con.print("[markers] SetMarkerDefSync: local player not leader/assist\n");
|
||||
con.print("[worldmarkers] SetMarkerDefSync: local player not leader/assist\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const sender = lua.tostring(L, 6) orelse return 0;
|
||||
if (!senderInGroup(sender)) {
|
||||
con.fmt("[markers] SetMarkerDefSync: sender '{s}' not in group\n", .{std.mem.span(sender)});
|
||||
con.fmt("[worldmarkers] SetMarkerDefSync: sender '{s}' not in group\n", .{std.mem.span(sender)});
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -747,7 +747,7 @@ pub fn luaSetMarkerDefSync(L: lua.State) callconv(.c) u32 {
|
||||
.active = true,
|
||||
};
|
||||
|
||||
con.fmt("[markers] SetMarkerDefSync [{d}] at {d:.1},{d:.1},{d:.1} area={d}\n", .{ index + 1, x, y, z, area_id });
|
||||
con.fmt("[worldmarkers] SetMarkerDefSync [{d}] at {d:.1},{d:.1},{d:.1} area={d}\n", .{ index + 1, x, y, z, area_id });
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -762,7 +762,7 @@ pub fn luaClearMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
// ClearMarkerDef(senderName) — clear all
|
||||
const sender = lua.tostring(L, 1) orelse return 0;
|
||||
if (!senderHasPermission(sender)) {
|
||||
con.fmt("[markers] ClearMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
con.fmt("[worldmarkers] ClearMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
return 0;
|
||||
}
|
||||
clearAllMarkers();
|
||||
@@ -773,7 +773,7 @@ pub fn luaClearMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
// ClearMarkerDef(index, senderName) — clear one
|
||||
const sender = lua.tostring(L, 2) orelse return 0;
|
||||
if (!senderHasPermission(sender)) {
|
||||
con.fmt("[markers] ClearMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
con.fmt("[worldmarkers] ClearMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
return 0;
|
||||
}
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
@@ -837,7 +837,7 @@ fn worldUpdateDetour(frame: u32) callconv(fc) void {
|
||||
/// processCinematicExit, DestroyPathObjectIfPresent). This unlinks them from
|
||||
/// the WDOODADDEF hash table so the atexit handler never touches freed memory.
|
||||
fn worldCleanupDetour() callconv(sc) void {
|
||||
con.print("[markers] >>> worldCleanupDetour FIRING <<<\n");
|
||||
con.print("[worldmarkers] >>> worldCleanupDetour FIRING <<<\n");
|
||||
destroyAllEntities();
|
||||
world_cleanup_hook.callOriginal(.{});
|
||||
}
|
||||
@@ -850,7 +850,7 @@ fn destroyAllEntities() void {
|
||||
for (&marker_entities, 0..) |*slot, i| {
|
||||
if (slot.*) |existing| {
|
||||
const addr = @intFromPtr(existing);
|
||||
con.fmt("[markers] destroying marker[{d}] @0x{x}\n", .{ i, addr });
|
||||
con.fmt("[worldmarkers] destroying marker[{d}] @0x{x}\n", .{ i, addr });
|
||||
cleanupEntity(existing);
|
||||
slot.* = null;
|
||||
count += 1;
|
||||
@@ -862,7 +862,7 @@ fn destroyAllEntities() void {
|
||||
for (&despawning, 0..) |*slot, i| {
|
||||
if (slot.*) |d| {
|
||||
const addr = @intFromPtr(d.entity);
|
||||
con.fmt("[markers] destroying despawn[{d}] @0x{x}\n", .{ i, addr });
|
||||
con.fmt("[worldmarkers] destroying despawn[{d}] @0x{x}\n", .{ i, addr });
|
||||
cleanupEntity(d.entity);
|
||||
slot.* = null;
|
||||
count += 1;
|
||||
@@ -870,7 +870,7 @@ fn destroyAllEntities() void {
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
con.fmt("[markers] world cleanup: destroyed {d} entities\n", .{count});
|
||||
con.fmt("[worldmarkers] world cleanup: destroyed {d} entities\n", .{count});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,10 +879,10 @@ fn destroyAllEntities() void {
|
||||
// =============================================================================
|
||||
|
||||
pub fn installHooks() void {
|
||||
con.print("[markers] Module loaded\n");
|
||||
con.print("[worldmarkers] Module loaded\n");
|
||||
|
||||
var mutex_name_buf: [64]u8 = undefined;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\MarkersHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_WorldMarkersHook_{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]));
|
||||
@@ -892,24 +892,24 @@ pub fn installHooks() void {
|
||||
_ = CloseHandle(g_mutex.?);
|
||||
g_mutex = null;
|
||||
g_is_hook_owner = false;
|
||||
con.print("[markers] Another DLL owns markers (mutex taken), skipping\n");
|
||||
con.print("[worldmarkers] Another DLL owns world markers (mutex taken), skipping\n");
|
||||
return;
|
||||
}
|
||||
g_is_hook_owner = true;
|
||||
|
||||
// Hook OnWorldUpdate for per-frame animation tick (runs every frame while world is active).
|
||||
if (world_update_hook.attach(o.FN_ON_WORLD_UPDATE, &worldUpdateDetour) != .ok) {
|
||||
con.print("[markers] FAILED to hook OnWorldUpdate!\n");
|
||||
con.print("[worldmarkers] FAILED to hook OnWorldUpdate!\n");
|
||||
} else {
|
||||
con.print("[markers] hooked OnWorldUpdate OK\n");
|
||||
con.print("[worldmarkers] hooked OnWorldUpdate OK\n");
|
||||
}
|
||||
|
||||
// Hook CleanupWorldAndEntities to destroy our entities before world teardown.
|
||||
// This fires on map change, logout, AND exit — before heaps are destroyed.
|
||||
if (world_cleanup_hook.attach(o.FN_CLEANUP_WORLD_AND_ENTITIES, &worldCleanupDetour) != .ok) {
|
||||
con.print("[markers] FAILED to hook CleanupWorldAndEntities!\n");
|
||||
con.print("[worldmarkers] FAILED to hook CleanupWorldAndEntities!\n");
|
||||
} else {
|
||||
con.print("[markers] hooked CleanupWorldAndEntities OK\n");
|
||||
con.print("[worldmarkers] hooked CleanupWorldAndEntities OK\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,7 +918,7 @@ pub fn installHooks() void {
|
||||
/// worldCleanupDetour which fires after shutdown.
|
||||
pub fn onShutdown() void {
|
||||
for (&marker_defs) |*d| d.* = EMPTY_DEF;
|
||||
con.print("[markers] defs cleared (shutdown)\n");
|
||||
con.print("[worldmarkers] defs cleared (shutdown)\n");
|
||||
}
|
||||
|
||||
pub fn removeHooks() void {
|
||||
|
||||
@@ -27,7 +27,7 @@ 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\\MinimapiconsHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_MinimapiconsHook_{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]));
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ pub fn init() bool {
|
||||
|
||||
// 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\\OutlineHook_{d}", .{GetCurrentProcessId()}) catch return false;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_OutlineHook_{d}", .{GetCurrentProcessId()}) catch return false;
|
||||
mutex_name_buf[mutex_name.len] = 0;
|
||||
|
||||
g_mutex = CreateMutexA(null, 1, @ptrCast(mutex_name_buf[0..mutex_name.len :0]));
|
||||
|
||||
@@ -345,7 +345,7 @@ pub fn installHook() 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\\ScreenshotHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_ScreenshotHook_{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]));
|
||||
|
||||
@@ -782,7 +782,7 @@ 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\\TransmogCoalesceHook_{d}", .{GetCurrentProcessId()}) catch return;
|
||||
const mutex_name = std.fmt.bufPrint(&mutex_name_buf, "Local\\WeirdUtils_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]));
|
||||
|
||||
Reference in New Issue
Block a user