Move permission checks DLL-side: canSetMarkers, senderHasPermission, senderInGroup
Replace Lua-side permission checks with direct memory reads in the DLL. All marker mutation functions now verify sender identity against the raid roster / party leader GUID without touching Lua state. - Add getPlayerGUID (0x468550), getNameFromGUID (name cache at 0xc0e228) - Add canSetMarkers: checks local player is leader/officer via roster - Add senderHasPermission: verifies sender name against roster ranks - Add senderInGroup: weaker check for sync relay (any rank) - WorldMarker/ClearWorldMarker return 1/nil for addon feedback - SetMarkerDef/ClearMarkerDef take sender name param, verify DLL-side - SetMarkerDefSync: dual check (local=leader + sender in group) - CanSetMarkers() Lua function for addon broadcast decisions - Remove Lua-side canSetMarkers/senderHasPermission from Markers.lua - Add offsets: LEADER_GUID, RAID_ROSTER_ARRAY, RAID_MEMBER_COUNT, etc.
This commit is contained in:
+2
-2
@@ -111,12 +111,12 @@ fn registerLuaFunctions() void {
|
||||
if (build_opts.markers and markers.isActive()) {
|
||||
registerFunction("WorldMarker", @intFromPtr(&markers.luaWorldMarker));
|
||||
registerFunction("ClearWorldMarker", @intFromPtr(&markers.luaClearWorldMarker));
|
||||
registerFunction("GetPlayerPosition", @intFromPtr(&markers.luaGetPlayerPosition));
|
||||
registerFunction("DistanceToMark", @intFromPtr(&markers.luaDistanceToMark));
|
||||
registerFunction("SetMarkerDef", @intFromPtr(&markers.luaSetMarkerDef));
|
||||
registerFunction("SetMarkerDefSync", @intFromPtr(&markers.luaSetMarkerDefSync));
|
||||
registerFunction("ClearMarkerDef", @intFromPtr(&markers.luaClearMarkerDef));
|
||||
registerFunction("GetMarkerDef", @intFromPtr(&markers.luaGetMarkerDef));
|
||||
registerFunction("GetCurrentAreaId", @intFromPtr(&markers.luaGetCurrentAreaId));
|
||||
registerFunction("CanSetMarkers", @intFromPtr(&markers.luaCanSetMarkers));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- Markers addon (embedded in DLL, loaded from memory)
|
||||
-- Part of WeirdUtils - only loaded when markers module is compiled
|
||||
|
||||
MARKERS_VERSION = 3
|
||||
MARKERS_VERSION = 4
|
||||
|
||||
BINDING_HEADER_MARKERS = "Markers"
|
||||
|
||||
@@ -16,6 +16,11 @@ end
|
||||
-- =============================================================================
|
||||
-- Addon message protocol
|
||||
-- Delimiter is ":" (pipe "|" is WoW's escape char for color codes)
|
||||
--
|
||||
-- Permission model: ALL permission checks are enforced DLL-side.
|
||||
-- WorldMarker/ClearWorldMarker: DLL checks local player is leader/assist.
|
||||
-- SetMarkerDef/ClearMarkerDef: DLL checks sender name against roster.
|
||||
-- CanSetMarkers(): DLL returns 1 if local player has permission.
|
||||
-- =============================================================================
|
||||
|
||||
local MSG_PREFIX = "WMark"
|
||||
@@ -30,59 +35,9 @@ local function getChannel()
|
||||
return nil
|
||||
end
|
||||
|
||||
local function canSetMarkers()
|
||||
if GetNumRaidMembers() > 0 then
|
||||
for i = 1, 40 do
|
||||
local name, rank = GetRaidRosterInfo(i)
|
||||
if name == UnitName("player") then
|
||||
log("canSet: raid rank=" .. rank .. (rank >= 1 and " YES" or " NO"))
|
||||
return rank >= 1 -- 1=assist, 2=leader
|
||||
end
|
||||
end
|
||||
log("canSet: not in raid roster")
|
||||
return false
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
local result = IsPartyLeader()
|
||||
log("canSet: party leader=" .. tostring(result))
|
||||
return result
|
||||
end
|
||||
log("canSet: solo YES")
|
||||
return true -- solo: always allowed
|
||||
end
|
||||
|
||||
local function senderHasPermission(sender)
|
||||
if GetNumRaidMembers() > 0 then
|
||||
for i = 1, 40 do
|
||||
local name, rank = GetRaidRosterInfo(i)
|
||||
if name == sender then
|
||||
log("perm: " .. sender .. " rank=" .. rank .. (rank >= 1 and " OK" or " NO"))
|
||||
return rank >= 1
|
||||
end
|
||||
end
|
||||
log("perm: " .. sender .. " not in roster")
|
||||
return false
|
||||
elseif GetNumPartyMembers() > 0 then
|
||||
-- Find the sender's unit ID to check if they're party leader
|
||||
for i = 1, GetNumPartyMembers() do
|
||||
if UnitName("party" .. i) == sender then
|
||||
local isLeader = UnitIsPartyLeader("party" .. i)
|
||||
log("perm: " .. sender .. "=party" .. i .. " ldr=" .. tostring(isLeader))
|
||||
return isLeader == 1
|
||||
end
|
||||
end
|
||||
log("perm: " .. sender .. " not in party")
|
||||
return false
|
||||
end
|
||||
log("perm: no group")
|
||||
return false
|
||||
end
|
||||
|
||||
local function broadcastPlace(index, x, y, z, areaId)
|
||||
local ch = getChannel()
|
||||
if not ch then
|
||||
log("send: no channel")
|
||||
return
|
||||
end
|
||||
if not ch then return end
|
||||
local msg = "P:" .. index .. ":" .. x .. ":" .. y .. ":" .. z .. ":" .. areaId
|
||||
log("SEND [" .. ch .. "] " .. msg)
|
||||
SendAddonMessage(MSG_PREFIX, msg, ch)
|
||||
@@ -193,6 +148,7 @@ end
|
||||
|
||||
-- =============================================================================
|
||||
-- Addon message handler
|
||||
-- All mutation commands pass sender name to the DLL for permission check.
|
||||
-- =============================================================================
|
||||
|
||||
local function parseMarkerFields(parts)
|
||||
@@ -226,12 +182,10 @@ local function onAddonMessage(prefix, message, channel, sender)
|
||||
local cmd = parts[1]
|
||||
log(" cmd=" .. tostring(cmd) .. " n=" .. table.getn(parts))
|
||||
|
||||
-- SR/LSR/SF don't mutate state — handled before permission check.
|
||||
-- P, C, CA mutate state and require sender to have leader/assist permission.
|
||||
if cmd == "SR" then
|
||||
-- Normal sync request: only leader/assist responds
|
||||
log(" sync request from " .. tostring(sender))
|
||||
if canSetMarkers() then
|
||||
if CanSetMarkers() then
|
||||
broadcastAllDefs()
|
||||
end
|
||||
return
|
||||
@@ -251,37 +205,28 @@ local function onAddonMessage(prefix, message, channel, sender)
|
||||
end
|
||||
local idx, x, y, z, areaId = parseMarkerFields(parts)
|
||||
if idx then
|
||||
log(" SetMarkerDef(" .. idx .. "," .. x .. "," .. y .. "," .. z .. "," .. areaId .. ")")
|
||||
SetMarkerDef(idx, x, y, z, areaId)
|
||||
SetMarkerDefSync(idx, x, y, z, areaId, sender)
|
||||
else
|
||||
log(" PARSE FAIL")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Mutation commands require permission
|
||||
if not senderHasPermission(sender) then
|
||||
log(" no perm, skip")
|
||||
return
|
||||
end
|
||||
|
||||
-- P, C, CA — DLL checks sender permission via SetMarkerDef/ClearMarkerDef
|
||||
if cmd == "P" then
|
||||
local idx, x, y, z, areaId = parseMarkerFields(parts)
|
||||
if idx then
|
||||
log(" SetMarkerDef(" .. idx .. "," .. x .. "," .. y .. "," .. z .. "," .. areaId .. ")")
|
||||
SetMarkerDef(idx, x, y, z, areaId)
|
||||
SetMarkerDef(idx, x, y, z, areaId, sender)
|
||||
else
|
||||
log(" PARSE FAIL")
|
||||
end
|
||||
elseif cmd == "C" then
|
||||
local idx = tonumber(parts[2])
|
||||
if idx then
|
||||
log(" ClearMarkerDef(" .. idx .. ")")
|
||||
ClearMarkerDef(idx)
|
||||
ClearMarkerDef(idx, sender)
|
||||
end
|
||||
elseif cmd == "CA" then
|
||||
log(" ClearAll")
|
||||
ClearMarkerDef()
|
||||
ClearMarkerDef(sender)
|
||||
else
|
||||
log(" unknown: " .. tostring(cmd))
|
||||
end
|
||||
@@ -299,7 +244,7 @@ local function broadcastSyncRequest()
|
||||
end
|
||||
-- Reset first-responder lock before requesting
|
||||
syncSender = nil
|
||||
local cmd = canSetMarkers() and "LSR" or "SR"
|
||||
local cmd = CanSetMarkers() and "LSR" or "SR"
|
||||
log("SEND [" .. ch .. "] " .. cmd)
|
||||
SendAddonMessage(MSG_PREFIX, cmd, ch)
|
||||
end
|
||||
@@ -357,7 +302,7 @@ rosterTimer:SetScript("OnUpdate", function()
|
||||
rosterTimer.pending = false
|
||||
rosterTimer.extensions = 0
|
||||
log("roster timer fired, broadcasting")
|
||||
if canSetMarkers() then
|
||||
if CanSetMarkers() then
|
||||
broadcastAllDefs()
|
||||
end
|
||||
end
|
||||
|
||||
+224
-68
@@ -115,27 +115,147 @@ const sc = std.builtin.CallingConvention{ .x86_stdcall = .{} };
|
||||
// Permission check — leader or raid officer required
|
||||
// =============================================================================
|
||||
|
||||
/// WoW Lua C functions: ECX=L, plain ret, return count of pushed values.
|
||||
const LuaCFn = *const fn (lua.State) callconv(fc) u32;
|
||||
const FN_IS_PARTY_LEADER: LuaCFn = @ptrFromInt(0x004e9130);
|
||||
const FN_IS_RAID_OFFICER: LuaCFn = @ptrFromInt(0x004bb910);
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// Call a WoW Lua C function that pushes 1 value, check if it pushed
|
||||
/// a truthy value (non-nil), then pop the result.
|
||||
fn luaBoolCheck(L: lua.State, func: LuaCFn) bool {
|
||||
const before = lua.gettop(L);
|
||||
_ = func(L);
|
||||
const after = lua.gettop(L);
|
||||
if (after <= before) return false;
|
||||
const is_nil = lua.typeOf(L, -1) == 0;
|
||||
lua.settop(L, before);
|
||||
return !is_nil;
|
||||
/// 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)),
|
||||
: .{ .edx = true, .memory = true, .cc = true });
|
||||
return if (result != 0) @ptrFromInt(result) else null;
|
||||
}
|
||||
|
||||
/// Check if the local player has permission to place/clear markers.
|
||||
/// Uses direct memory reads — no Lua state required.
|
||||
/// Requires: party leader, raid leader, or raid officer (assist).
|
||||
fn canSetMarkers(L: lua.State) bool {
|
||||
return luaBoolCheck(L, FN_IS_PARTY_LEADER) or luaBoolCheck(L, FN_IS_RAID_OFFICER);
|
||||
fn canSetMarkers() bool {
|
||||
const player_guid = getPlayerGUID();
|
||||
if (player_guid == 0) return false;
|
||||
const player_lo: u32 = @truncate(player_guid);
|
||||
const player_hi: u32 = @truncate(player_guid >> 32);
|
||||
|
||||
const raid_count = hook.readMem(u32, o.RAID_MEMBER_COUNT);
|
||||
if (raid_count > 0) {
|
||||
// Raid: check if player is leader or has rank > 0 (officer/assist)
|
||||
const count = @min(raid_count, 40);
|
||||
for (0..count) |i| {
|
||||
const entry = hook.readMem(u32, o.RAID_ROSTER_ARRAY + i * 4);
|
||||
if (entry == 0 or entry < 0x10000) continue;
|
||||
const guid_lo = hook.readMem(u32, entry);
|
||||
const guid_hi = hook.readMem(u32, entry + 4);
|
||||
if (guid_lo == player_lo and guid_hi == player_hi) {
|
||||
return hook.readMem(i32, entry + o.ROSTER_ENTRY_RANK) > 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Party: check if player is the leader
|
||||
const leader_lo = hook.readMem(u32, o.LEADER_GUID);
|
||||
const leader_hi = hook.readMem(u32, o.LEADER_GUID + 4);
|
||||
if (leader_lo == 0 and leader_hi == 0) return false;
|
||||
return player_lo == leader_lo and player_hi == leader_hi;
|
||||
}
|
||||
|
||||
/// Check if a named sender has permission (leader or raid officer).
|
||||
/// Used to authenticate incoming addon messages — sender name comes from
|
||||
/// CHAT_MSG_ADDON arg4 (server-verified, can't be spoofed).
|
||||
fn senderHasPermission(sender: [*:0]const u8) bool {
|
||||
const sender_span = std.mem.span(sender);
|
||||
if (sender_span.len == 0) return false;
|
||||
|
||||
const raid_count = hook.readMem(u32, o.RAID_MEMBER_COUNT);
|
||||
if (raid_count > 0) {
|
||||
// Raid: find sender in roster, check rank > 0
|
||||
const count = @min(raid_count, 40);
|
||||
for (0..count) |i| {
|
||||
const entry = hook.readMem(u32, o.RAID_ROSTER_ARRAY + i * 4);
|
||||
if (entry == 0 or entry < 0x10000) continue;
|
||||
const rank = hook.readMem(i32, entry + o.ROSTER_ENTRY_RANK);
|
||||
if (rank <= 0) continue; // skip non-officers
|
||||
const guid_lo = hook.readMem(u32, entry);
|
||||
const guid_hi = hook.readMem(u32, entry + 4);
|
||||
const name = getNameFromGUID(guid_lo, guid_hi) orelse continue;
|
||||
if (std.mem.eql(u8, std.mem.span(name), sender_span)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Party: check if sender is the leader
|
||||
const leader_lo = hook.readMem(u32, o.LEADER_GUID);
|
||||
const leader_hi = hook.readMem(u32, o.LEADER_GUID + 4);
|
||||
if (leader_lo == 0 and leader_hi == 0) return false;
|
||||
const leader_name = getNameFromGUID(leader_lo, leader_hi) orelse return false;
|
||||
return std.mem.eql(u8, std.mem.span(leader_name), sender_span);
|
||||
}
|
||||
|
||||
/// Check if a named sender is in the group (any rank).
|
||||
/// Weaker than senderHasPermission — used for sync relay (SF) where the
|
||||
/// sender is just echoing stored data, not issuing a command.
|
||||
fn senderInGroup(sender: [*:0]const u8) bool {
|
||||
const sender_span = std.mem.span(sender);
|
||||
if (sender_span.len == 0) return false;
|
||||
|
||||
const raid_count = hook.readMem(u32, o.RAID_MEMBER_COUNT);
|
||||
if (raid_count > 0) {
|
||||
// Raid: find sender anywhere in roster (any rank)
|
||||
const count = @min(raid_count, 40);
|
||||
for (0..count) |i| {
|
||||
const entry = hook.readMem(u32, o.RAID_ROSTER_ARRAY + i * 4);
|
||||
if (entry == 0 or entry < 0x10000) continue;
|
||||
const guid_lo = hook.readMem(u32, entry);
|
||||
const guid_hi = hook.readMem(u32, entry + 4);
|
||||
const name = getNameFromGUID(guid_lo, guid_hi) orelse continue;
|
||||
if (std.mem.eql(u8, std.mem.span(name), sender_span)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Party: check if sender is any party member
|
||||
for (0..4) |i| {
|
||||
const guid_lo = hook.readMem(u32, o.PARTY_MEMBER_GUIDS + i * 8);
|
||||
const guid_hi = hook.readMem(u32, o.PARTY_MEMBER_GUIDS + i * 8 + 4);
|
||||
if (guid_lo == 0 and guid_hi == 0) continue;
|
||||
const name = getNameFromGUID(guid_lo, guid_hi) orelse continue;
|
||||
if (std.mem.eql(u8, std.mem.span(name), sender_span)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -397,7 +517,7 @@ fn clearAllMarkers() void {
|
||||
/// Lua: local ok = WorldMarker(index [, x, y, z | "unitId"])
|
||||
/// Returns 1 on success, nil on permission denied.
|
||||
pub fn luaWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
if (!canSetMarkers(L)) {
|
||||
if (!canSetMarkers()) {
|
||||
con.print("[markers] WorldMarker: no permission\n");
|
||||
return 0; // nil — addon shows user message
|
||||
}
|
||||
@@ -446,7 +566,7 @@ pub fn luaWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
/// Lua: local ok = ClearWorldMarker([index])
|
||||
/// Returns 1 on success, nil on permission denied.
|
||||
pub fn luaClearWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
if (!canSetMarkers(L)) {
|
||||
if (!canSetMarkers()) {
|
||||
con.print("[markers] ClearWorldMarker: no permission\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -549,53 +669,20 @@ fn tickAnimations() void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lua: local x, y, z = GetPlayerPosition()
|
||||
pub fn luaGetPlayerPosition(L: lua.State) callconv(.c) u32 {
|
||||
const player = wow.getLocalPlayer();
|
||||
if (player == 0) return 0;
|
||||
|
||||
const pos = getUnitPosition(player);
|
||||
lua.pushnumber(L, @floatCast(pos.x));
|
||||
lua.pushnumber(L, @floatCast(pos.y));
|
||||
lua.pushnumber(L, @floatCast(pos.z));
|
||||
return 3;
|
||||
}
|
||||
|
||||
/// Lua: local dist = DistanceToMark(index)
|
||||
/// Returns distance in yards from player to the stored marker position,
|
||||
/// or nil if the marker doesn't exist or there's no player.
|
||||
pub fn luaDistanceToMark(L: lua.State) callconv(.c) u32 {
|
||||
const nargs = lua.gettop(L);
|
||||
if (nargs < 1 or !lua.isnumber(L, 1)) return 0;
|
||||
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
|
||||
const index: usize = @intCast(raw_index - 1);
|
||||
|
||||
if (!marker_defs[index].active) return 0;
|
||||
const mark_pos = marker_defs[index].pos;
|
||||
|
||||
const player = wow.getLocalPlayer();
|
||||
if (player == 0) return 0;
|
||||
const player_pos = getUnitPosition(player);
|
||||
if (player_pos.x == 0 and player_pos.y == 0 and player_pos.z == 0) return 0;
|
||||
|
||||
const dx = player_pos.x - mark_pos.x;
|
||||
const dy = player_pos.y - mark_pos.y;
|
||||
const dz = player_pos.z - mark_pos.z;
|
||||
const dist = @sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
lua.pushnumber(L, @floatCast(dist));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Lua: SetMarkerDef(index, x, y, z, areaId)
|
||||
/// Lua: SetMarkerDef(index, x, y, z, areaId, senderName)
|
||||
/// Store a marker definition without immediately spawning. Used by the addon
|
||||
/// when receiving remote marker data — proximity respawn handles entity creation.
|
||||
/// senderName is verified against the group roster for leader/officer permission.
|
||||
pub fn luaSetMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
const nargs = lua.gettop(L);
|
||||
if (nargs < 5) return 0;
|
||||
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)) return 0;
|
||||
if (nargs < 6) return 0;
|
||||
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;
|
||||
|
||||
const sender = lua.tostring(L, 6) orelse return 0;
|
||||
if (!senderHasPermission(sender)) {
|
||||
con.fmt("[markers] SetMarkerDef: sender '{s}' denied\n", .{std.mem.span(sender)});
|
||||
return 0;
|
||||
}
|
||||
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
|
||||
@@ -619,20 +706,78 @@ pub fn luaSetMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Lua: ClearMarkerDef([index])
|
||||
/// Clear a definition (and its entity if alive). No args = clear all.
|
||||
/// Lua: SetMarkerDefSync(index, x, y, z, areaId, senderName)
|
||||
/// Like SetMarkerDef but for sync relay (SF messages). Two checks:
|
||||
/// 1. Local player must be leader/assist (only they request syncs)
|
||||
/// 2. Sender must be in the group (any rank — they're just relaying data)
|
||||
pub fn luaSetMarkerDefSync(L: lua.State) callconv(.c) u32 {
|
||||
const nargs = lua.gettop(L);
|
||||
if (nargs < 6) return 0;
|
||||
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");
|
||||
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)});
|
||||
return 0;
|
||||
}
|
||||
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
|
||||
const index: usize = @intCast(raw_index - 1);
|
||||
|
||||
const x: f32 = @floatCast(lua.tonumber(L, 2));
|
||||
const y: f32 = @floatCast(lua.tonumber(L, 3));
|
||||
const z: f32 = @floatCast(lua.tonumber(L, 4));
|
||||
const area_id: u32 = @intFromFloat(lua.tonumber(L, 5));
|
||||
|
||||
clearEntity(index);
|
||||
|
||||
marker_defs[index] = .{
|
||||
.pos = .{ .x = x, .y = y, .z = z },
|
||||
.area_id = area_id,
|
||||
.active = true,
|
||||
};
|
||||
|
||||
con.fmt("[markers] SetMarkerDefSync [{d}] at {d:.1},{d:.1},{d:.1} area={d}\n", .{ index + 1, x, y, z, area_id });
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Lua: ClearMarkerDef(senderName) — clear all
|
||||
/// Lua: ClearMarkerDef(index, senderName) — clear one
|
||||
/// senderName is verified against the group roster for leader/officer permission.
|
||||
pub fn luaClearMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
const nargs = lua.gettop(L);
|
||||
if (nargs == 0) {
|
||||
if (nargs < 1) return 0;
|
||||
|
||||
if (lua.isstring(L, 1) and (nargs == 1 or !lua.isnumber(L, 1))) {
|
||||
// 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)});
|
||||
return 0;
|
||||
}
|
||||
clearAllMarkers();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lua.isnumber(L, 1)) return 0;
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
|
||||
if (nargs >= 2 and lua.isnumber(L, 1) and lua.isstring(L, 2)) {
|
||||
// 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)});
|
||||
return 0;
|
||||
}
|
||||
const raw_index = @as(i32, @intFromFloat(lua.tonumber(L, 1)));
|
||||
if (raw_index < 1 or raw_index > NUM_MARKERS) return 0;
|
||||
clearMarker(@intCast(raw_index - 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
clearMarker(@intCast(raw_index - 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -655,6 +800,17 @@ pub fn luaGetMarkerDef(L: lua.State) callconv(.c) u32 {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/// Lua: local ok = CanSetMarkers()
|
||||
/// Returns 1 if the local player has permission (leader/assist), nil otherwise.
|
||||
/// Used by the addon for broadcast/sync decisions.
|
||||
pub fn luaCanSetMarkers(L: lua.State) callconv(.c) u32 {
|
||||
if (canSetMarkers()) {
|
||||
lua.pushnumber(L, 1.0);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Lua: local areaId = GetCurrentAreaId()
|
||||
/// Returns the current zone area ID from the game global.
|
||||
pub fn luaGetCurrentAreaId(L: lua.State) callconv(.c) u32 {
|
||||
|
||||
@@ -197,3 +197,38 @@ pub const FN_LOAD_MODEL_ASYNC: usize = 0x0071d4e0;
|
||||
/// Parses model header from buffer at model+0x130 (ptr) / model+0x134 (size),
|
||||
/// initializes model resources, sets bit 0 of model+8 when done.
|
||||
pub const FN_PROCESS_LOADED_MODEL_DATA: usize = 0x0071d640;
|
||||
|
||||
// =============================================================================
|
||||
// Permission check — leader / raid officer
|
||||
// =============================================================================
|
||||
|
||||
/// Group leader GUID (64-bit): low u32 at +0, high u32 at +4.
|
||||
/// Valid for both party leader and raid leader.
|
||||
pub const LEADER_GUID: usize = 0x00bc75f8;
|
||||
|
||||
/// Raid roster — array of 40 pointers to roster entry structs.
|
||||
/// Entry layout: +0x00/+0x04 = GUID (u64), +0x08 = subgroup, +0x0C = rank.
|
||||
/// Rank: 0 = member, 1 = assistant, 2 = leader.
|
||||
pub const RAID_ROSTER_ARRAY: usize = 0x00b712a8;
|
||||
|
||||
/// Number of raid members (u32). 0 when not in a raid.
|
||||
pub const RAID_MEMBER_COUNT: usize = 0x00b713e0;
|
||||
|
||||
/// Offset within a roster entry to the rank field (i32).
|
||||
pub const ROSTER_ENTRY_RANK: usize = 0x0C;
|
||||
|
||||
/// Party member GUIDs — 4 slots, 8 bytes each (lo/hi u32 pairs).
|
||||
/// Contains other party members (not local player). Stride = 8.
|
||||
pub const PARTY_MEMBER_GUIDS: usize = 0x00bc6f48;
|
||||
|
||||
/// GetPlayerGUID — __fastcall(), no params, returns EAX(low):EDX(high).
|
||||
pub const FN_GET_PLAYER_GUID: usize = 0x00468550;
|
||||
|
||||
/// RetrieveNPCDataFromCache — __thiscall(ECX=cache_obj), 6 stack params, RET 0x18.
|
||||
/// (guid_low, guid_high, name_buf_ptr, 0, 0, 0) → char* name or NULL.
|
||||
/// Used by GetRaidRosterInfo to resolve player names from GUIDs.
|
||||
pub const FN_NAME_CACHE_LOOKUP: usize = 0x0055f080;
|
||||
|
||||
/// Name cache object — static instance at this address. Passed as ECX (this)
|
||||
/// to RetrieveNPCDataFromCache.
|
||||
pub const NAME_CACHE_OBJ: usize = 0x00c0e228;
|
||||
|
||||
Reference in New Issue
Block a user