Add KEY_UP/DOWN events, implement GetGUIDFromName support for owner/target/pet/mark, bug fixes

This commit is contained in:
avitasia
2026-02-22 13:44:55 -08:00
parent a54af0b960
commit 86d0c47778
14 changed files with 13769 additions and 12956 deletions
+123
View File
@@ -44,6 +44,7 @@ For custom Lua functions, see [SCRIPTS.md](SCRIPTS.md). For general usage inform
- [SPELL_MISS_SELF and SPELL_MISS_OTHER](#spell_miss_self-and-spell_miss_other)
- [UNIT_DIED](#unit_died)
- [Unit GUID Events](#unit-guid-events)
- [KEY_DOWN and KEY_UP](#key_down-and-key_up)
---
@@ -575,6 +576,128 @@ frame:RegisterEvent("UNIT_DIED", function(guid)
end)
```
### KEY_DOWN and KEY_UP
Fire when keyboard input is processed by `CSimpleTop::OnKeyDown` / `CSimpleTop::OnKeyUp`. You could accomplish similar already
by calling EnableKeyBoard and using 'OnKeyDown' / 'OnKeyUp' scripts but that blocks all keyboard input to the game. You could also poll for IsShiftKeyDown, IsAltKeyDown, in OnUpdate but that was also limiting.
Parameters:
1. int key
2. int metaKeyState
3. int repeat
4. int time
Note: `repeat` currently appears to stay constant in testing and may not reflect real key repeat count/state.
`metaKeyState` bitmask:
- Shift = `1`
- Ctrl = `2`
- Alt = `4`
```c
typedef enum KEY {
KEY_NONE=-1,
KEY_SHIFT=0,
KEY_CONTROL=1,
KEY_ALT=2,
KEY_LASTMETAKEY=2,
KEY_SPACE=32,
KEY_0=48,
KEY_1=49,
KEY_2=50,
KEY_3=51,
KEY_4=52,
KEY_5=53,
KEY_6=54,
KEY_7=55,
KEY_8=56,
KEY_9=57,
KEY_A=65,
KEY_B=66,
KEY_C=67,
KEY_D=68,
KEY_E=69,
KEY_F=70,
KEY_G=71,
KEY_H=72,
KEY_I=73,
KEY_J=74,
KEY_K=75,
KEY_L=76,
KEY_M=77,
KEY_N=78,
KEY_O=79,
KEY_P=80,
KEY_Q=81,
KEY_R=82,
KEY_S=83,
KEY_T=84,
KEY_U=85,
KEY_V=86,
KEY_W=87,
KEY_X=88,
KEY_Y=89,
KEY_Z=90,
KEY_TILDE=256,
KEY_NUMPAD0=257,
KEY_NUMPAD1=258,
KEY_NUMPAD2=259,
KEY_NUMPAD3=260,
KEY_NUMPAD4=261,
KEY_NUMPAD5=262,
KEY_NUMPAD6=263,
KEY_NUMPAD7=264,
KEY_NUMPAD8=265,
KEY_NUMPAD9=266,
KEY_NUMPAD_PLUS=267,
KEY_NUMPAD_MINUS=268,
KEY_NUMPAD_MULTIPLY=269,
KEY_NUMPAD_DIVIDE=270,
KEY_NUMPAD_DECIMAL=271,
KEY_PLUS=272,
KEY_MINUS=273,
KEY_BRACKET_OPEN=274,
KEY_BRACKET_CLOSE=275,
KEY_SLASH=276,
KEY_BACKSLASH=277,
KEY_SEMICOLON=278,
KEY_APOSTROPHE=279,
KEY_COMMA=280,
KEY_PERIOD=281,
KEY_ESCAPE=512,
KEY_ENTER=513,
KEY_BACKSPACE=514,
KEY_TAB=515,
KEY_LEFT=516,
KEY_UP=517,
KEY_RIGHT=518,
KEY_DOWN=519,
KEY_INSERT=520,
KEY_DELETE=521,
KEY_HOME=522,
KEY_END=523,
KEY_PAGEUP=524,
KEY_PAGEDOWN=525,
KEY_CAPSLOCK=526,
KEY_NUMLOCK=527,
KEY_SCROLLLOCK=528,
KEY_PAUSE=529,
KEY_PRINTSCREEN=530,
KEY_F1=768,
KEY_F2=769,
KEY_F3=770,
KEY_F4=771,
KEY_F5=772,
KEY_F6=773,
KEY_F7=774,
KEY_F8=775,
KEY_F9=776,
KEY_F10=777,
KEY_F11=778,
KEY_F12=779,
KEY_LAST=780
} KEY;
```
---
## Unit GUID Events
+27 -1
View File
@@ -142,6 +142,31 @@ Examples:
/run print(GetSpellTexture("Fireball")) -- name search
```
### Unit Token Extensions (`UnitGUID` + all `unitToken`/`target` string params)
`UnitGUID(unitToken)` now uses the same extended unit-token parser used by Nampower's unit-string APIs.
In practice, any Lua function documented as taking a unit string (`unitToken`, `target`, etc.) can use the formats below.
Supported formats:
- Standard unit tokens supported by the client parser (`player`, `target`, `pet`, `mouseover`, `party1`, `raid1`, etc.)
- Raw GUID strings: `0x` + 16 hex digits (example: `0xF5300000000000A5`)
- Raid target markers: `mark1` through `mark8`
- Derived-unit suffixes appended directly to a base unit string (no separator): `<base>owner`, `<base>target`, `<base>pet`
How base resolution works:
- `<base>` can be a normal unit token (`targetowner`, `party1pet`, etc.)
- `<base>` can be a GUID string (`0xF5300000000000A5owner`, `0xF5300000000000A5target`, `0xF5300000000000A5pet`)
- `<base>` can be a raid marker (`mark1target`, `mark8owner`, etc.)
- Suffix matching is case-insensitive (`targetOwner`, `mark1PET`, etc.)
Examples:
```
/run print(UnitGUID("mark1"))
/run print(UnitGUID("mark1target"))
/run print(UnitGUID("targetowner"))
/run print(UnitGUID("party1pet"))
/run print(UnitGUID("0xF5300000000000A5target"))
```
### CSimpleFrame:GetName() Enhancement
The built-in `CSimpleFrame:GetName()` method is hooked to support an optional argument for retrieving the GUID of the unit associated with a nameplate frame.
@@ -176,7 +201,7 @@ Valid ranges are: `talentPage` = `1-3`, `talentIndex` = `1-32`, `rank` = `1-5`.
For unit data APIs, object-reference GUID fields are returned as strings (hex format) rather than numbers to avoid Lua 64-bit precision issues.
This applies to both `GetUnitData` and `GetUnitField` for fields such as: `charm`, `summon`, `charmedBy`, `summonedBy`, `createdBy`, `target`, `persuaded`, and `channelObject`.
`CancelPlayerAuraSpellId(spellId, [ignoreMissing])` supports an optional second parameter where `1` skips the aura-slot presence check (useful for buff-capped cases) and `0`/omitted keeps the default check.
`CancelPlayerAuraSpellId(spellId, [ignoreMissing])` supports an optional second parameter where `1` skips the aura-slot presence check (thought this might work when buff-capped but doesn't seem to work) and `0`/omitted keeps the default check.
Cooldown detail tables now also expose `itemId`, `itemHasActiveSpell`, and `itemActiveSpellId` alongside the existing per-category timing data.
@@ -198,6 +223,7 @@ Available events:
- SPELL_HEAL_BY_SELF, SPELL_HEAL_BY_OTHER, and SPELL_HEAL_ON_SELF - Spell healing events (fires when spell heals are processed; includes target, caster, spell ID, amount, critical flag, and periodic flag). Set `NP_EnableSpellHealEvents=1` to enable. See EVENTS.md for details.
- SPELL_ENERGIZE_BY_SELF, SPELL_ENERGIZE_BY_OTHER, and SPELL_ENERGIZE_ON_SELF - Spell energize events (fires when power is restored via spells; includes target, caster, spell ID, power type, amount, and periodic flag). Set `NP_EnableSpellEnergizeEvents=1` to enable. See EVENTS.md for details.
- UNIT_DIED - Fires when a unit dies
- KEY_DOWN and KEY_UP - Keyboard key events from `CSimpleTop::OnKeyDown` / `CSimpleTop::OnKeyUp` (see [EVENTS.md](EVENTS.md#key_down-and-key_up) for full argument and key-code details)
## Bug Reporting
If you encounter any bugs please report them in the issues tab. Please include the nampower_debug.txt file in the same directory as your WoW.exe to help me diagnose the issue. If you are able to reproduce the bug please include the steps to reproduce it. In a future version once bugs are ironed out I'll make logging optional.
+39
View File
@@ -32,6 +32,9 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
- [GetItemIconTexture](#getitemicontexturedisplayinfoid)
- [GetSpellIconTexture](#getspellicontexturespelliconid)
- [GetPlayerAuraDuration](#getplayerauradurationauraslot)
- [CancelPlayerAuraSlot](#cancelplayerauraslotauraslot)
- [CancelPlayerAuraSpellId](#cancelplayerauraspellidspellid-ignoremissing)
- [LearnTalentRank](#learntalentranktalentpage-talentindex-rank)
- [Spell Casting and Queuing](#spell-casting-and-queuing)
- [QueueSpellByName](#queuespellbynamespellname)
- [CastSpellByNameNoQueue](#castspellbynamenoqueuespellname)
@@ -500,6 +503,39 @@ for slot = 32, 47 do
end
```
#### CancelPlayerAuraSpellId(spellId, [ignoreMissing])
Cancels a player aura by spell ID.
**Parameters:**
- `spellId` (number): Aura spell ID to cancel
- `ignoreMissing` (number, optional): Pass `1` to skip the aura-slot presence check (useful for buff-capped cases). Pass `0` or omit to keep the default check.
**Returns:**
- `1` if cancel was attempted
- `0` if `spellId` is invalid or (when `ignoreMissing` is `0`/omitted) no matching aura is present
#### CancelPlayerAuraSlot(auraSlot)
Cancels a player aura by raw aura slot index.
**Parameters:**
- `auraSlot` (number): Aura slot index to cancel
**Returns:**
- `1` if the slot contains an aura and cancel was attempted
- `0` if the slot is invalid/out of range or no aura is present in that slot
#### LearnTalentRank(talentPage, talentIndex, rank)
Learns a specific talent rank directly by tab/index.
**Parameters:**
- `talentPage` (number): Valid range `1-3`
- `talentIndex` (number): Valid range `1-32`
- `rank` (number): Valid range `1-5`
**Returns:**
- `1` on success
- Raises a Lua error on invalid parameters or if the talent entry cannot be resolved
#### GetSpellRec(spellId, [copy])
Returns a Lua table reference containing all fields for the spell's `SpellRec` record (including localized `name` and `rank`). Returns nil if the spell cannot be found.
@@ -650,6 +686,7 @@ Returns a Lua table reference containing all unit fields for the specified unit.
**Returns:**
- A Lua table reference containing all unit fields, or nil if the unit cannot be found
- Object-reference GUID fields are returned as hex GUID strings (not numbers) to avoid Lua 64-bit precision issues. This includes fields such as `charm`, `summon`, `charmedBy`, `summonedBy`, `createdBy`, `target`, `persuaded`, and `channelObject`.
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
@@ -678,6 +715,7 @@ Fast lookup for a single field on a unit. More efficient than GetUnitData when y
**Returns:**
- The requested field value; returns nil if the unit is not found; raises a Lua error if the field name is invalid
- For array fields (like "aura", "resistances"), returns a Lua table with numeric indices
- Object-reference GUID fields are returned as hex GUID strings (not numbers) to avoid Lua 64-bit precision issues. This includes fields such as `charm`, `summon`, `charmedBy`, `summonedBy`, `createdBy`, `target`, `persuaded`, and `channelObject`.
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
@@ -929,6 +967,7 @@ A Lua table where each entry contains:
- `itemId` (number)
- `trinketName` (string, `"Unknown"` if no name available)
- `texture` (string): Texture name for the item icon
- `itemLevel` (number): Item level
- `bagIndex` (number|nil): `nil` when equipped; `0` for backpack; `1-4` for equipped bags
- `slotIndex` (number): Lua 1-based slot within the container (or 1/2 for equipped trinket slots)
+13227 -12937
View File
File diff suppressed because it is too large Load Diff
+82
View File
@@ -35,6 +35,29 @@
#include <cstdint>
namespace game {
namespace {
// CGUnit_C descriptor pointer is stored at object + 0x110 in this client build.
constexpr uintptr_t kUnitDescriptorPtrOffset = 0x110;
constexpr uintptr_t kOwnerGuidOffset = 0x10;
constexpr uintptr_t kOwnerGuidFallbackOffset = 0x20;
constexpr uintptr_t kPetGuidOffset = 0x08;
constexpr uintptr_t kTargetGuidOffset = 0x28;
uint8_t *GetUnitDescriptor(uintptr_t *unit) {
if (!unit) {
return nullptr;
}
auto descriptorPtr = *reinterpret_cast<uintptr_t *>(reinterpret_cast<uint8_t *>(unit) +
kUnitDescriptorPtrOffset);
if (descriptorPtr == 0) {
return nullptr;
}
return reinterpret_cast<uint8_t *>(descriptorPtr);
}
}
uintptr_t *GetObjectPtr(std::uint64_t guid) {
uintptr_t *(__stdcall *getObjectPtr)(std::uint64_t) = hadesmem::detail::AliasCast<decltype(getObjectPtr)>(
Offsets::GetObjectPtr);
@@ -171,6 +194,65 @@ namespace game {
return unitFields->target;
}
uint64_t UnitGetOwnerGuid(uintptr_t *unit) {
auto *desc = GetUnitDescriptor(unit);
if (!desc) {
return 0;
}
uint32_t low = *reinterpret_cast<uint32_t *>(desc + kOwnerGuidOffset);
uint32_t high = *reinterpret_cast<uint32_t *>(desc + kOwnerGuidOffset + sizeof(uint32_t));
if (low == 0 && high == 0) {
low = *reinterpret_cast<uint32_t *>(desc + kOwnerGuidFallbackOffset);
high = *reinterpret_cast<uint32_t *>(desc + kOwnerGuidFallbackOffset + sizeof(uint32_t));
}
return (static_cast<uint64_t>(high) << 32) | low;
}
uint64_t UnitGetPetGuid(uintptr_t *unit) {
auto *desc = GetUnitDescriptor(unit);
if (!desc) {
return 0;
}
uint32_t low = *reinterpret_cast<uint32_t *>(desc + kPetGuidOffset);
uint32_t high = *reinterpret_cast<uint32_t *>(desc + kPetGuidOffset + sizeof(uint32_t));
return (static_cast<uint64_t>(high) << 32) | low;
}
uint64_t UnitGetOwnerGuidForGuid(uint64_t guid) {
auto *unit = ClntObjMgrObjectPtr(static_cast<TypeMask>(TYPEMASK_UNIT | TYPEMASK_PLAYER), guid);
return UnitGetOwnerGuid(unit);
}
uint64_t UnitGetTargetGuidForGuid(uint64_t guid) {
auto *unit = ClntObjMgrObjectPtr(static_cast<TypeMask>(TYPEMASK_UNIT | TYPEMASK_PLAYER), guid);
if (!unit) {
return 0;
}
uint64_t targetGuid = UnitGetTargetGuid(unit);
if (targetGuid != 0) {
return targetGuid;
}
// Fallback to descriptor offsets used by legacy code paths.
auto *desc = GetUnitDescriptor(unit);
if (!desc) {
return 0;
}
uint32_t low = *reinterpret_cast<uint32_t *>(desc + kTargetGuidOffset);
uint32_t high = *reinterpret_cast<uint32_t *>(desc + kTargetGuidOffset + sizeof(uint32_t));
return (static_cast<uint64_t>(high) << 32) | low;
}
uint64_t UnitGetPetGuidForGuid(uint64_t guid) {
auto *unit = ClntObjMgrObjectPtr(static_cast<TypeMask>(TYPEMASK_UNIT | TYPEMASK_PLAYER), guid);
return UnitGetPetGuid(unit);
}
bool UnitIsPvpFlagged(uintptr_t *unit) {
if (!unit) {
return false;
+10
View File
@@ -1635,6 +1635,10 @@ namespace game {
UNIT_AURA_GUID = 595,
UNIT_DYNAMIC_FLAGS_GUID = 596,
PLAYER_GUILD_UPDATE_GUID = 597,
KEY_DOWN = 598,
KEY_UP = 599,
UNIT_CASTEVENT=600 // superwow
};
enum TypeMask {
@@ -1935,6 +1939,12 @@ namespace game {
bool UnitCanAttackUnit(uintptr_t *unit1, uintptr_t *unit2);
uint64_t UnitGetTargetGuid(uintptr_t *unit);
uint64_t UnitGetOwnerGuid(uintptr_t *unit);
uint64_t UnitGetPetGuid(uintptr_t *unit);
uint64_t UnitGetOwnerGuidForGuid(uint64_t guid);
uint64_t UnitGetTargetGuidForGuid(uint64_t guid);
uint64_t UnitGetPetGuidForGuid(uint64_t guid);
bool UnitIsPvpFlagged(uintptr_t *unit);
+23 -9
View File
@@ -6,8 +6,7 @@
#include "offsets.hpp"
#include "main.hpp"
#include "dbc_fields.hpp"
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <unordered_map>
#include <string>
@@ -20,6 +19,7 @@ namespace Nampower {
lua_typeT lua_type = reinterpret_cast<lua_typeT>(Offsets::lua_type);
lua_isstringT lua_isstring = reinterpret_cast<lua_isstringT>(Offsets::lua_isstring);
lua_isnumberT lua_isnumber = reinterpret_cast<lua_isnumberT>(Offsets::lua_isnumber);
lua_toflagT lua_toflag = reinterpret_cast<lua_toflagT>(Offsets::lua_toflag);
lua_tostringT lua_tostring = reinterpret_cast<lua_tostringT>(Offsets::lua_tostring);
lua_tonumberT lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber);
lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber);
@@ -369,14 +369,28 @@ namespace Nampower {
return 0;
}
// Check if it's a GUID string (starts with "0x" or "0X")
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
return std::stoull(unitToken, nullptr, 16);
} else {
// Get GUID from unit token
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
return getGUIDFromName(unitToken);
if (unitToken[0] == '\0') {
return 0;
}
// Resolve through the shared unit-token parser first so suffix forms
// (owner/target/pet, mark1-mark8, nested forms) work consistently.
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
const uint64_t resolvedGuid = getGUIDFromName(unitToken);
if (resolvedGuid != 0) {
return resolvedGuid;
}
// Fallback: strict raw-hex parse when resolver returns 0.
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
char *endPtr = nullptr;
const auto parsed = std::strtoull(unitToken, &endPtr, 16);
if (endPtr && *endPtr == '\0') {
return static_cast<uint64_t>(parsed);
}
}
return 0;
}
uint64_t GetUnitGuidFromLuaParam(uintptr_t *luaState, int paramIndex) {
+1
View File
@@ -15,6 +15,7 @@ namespace Nampower {
extern lua_typeT lua_type;
extern lua_isstringT lua_isstring;
extern lua_isnumberT lua_isnumber;
extern lua_toflagT lua_toflag;
extern lua_tostringT lua_tostring;
extern lua_tonumberT lua_tonumber;
extern lua_pushnumberT lua_pushnumber;
+18
View File
@@ -157,6 +157,9 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<GetSpellSlotFromLuaT> > gGetSpellSlotFromLuaDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gCSimpleFrame_GetNameDetour;
std::unique_ptr<hadesmem::PatchDetour<CSimpleTop_OnKeyDownT> > gCSimpleTop_OnKeyDownDetour;
std::unique_ptr<hadesmem::PatchDetour<CSimpleTop_OnKeyUpT> > gCSimpleTop_OnKeyUpDetour;
std::unique_ptr<hadesmem::PatchDetour<GetGUIDFromNameT> > gGetGUIDFromNameDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gCastSpellByNameDetour;
std::unique_ptr<hadesmem::PatchDetour<SendUnitSignalT> > gSendUnitSignalDetour;
std::unique_ptr<hadesmem::PatchDetour<CGGameUI_ShowCombatFeedbackT> > gCGGameUI_ShowCombatFeedbackDetour;
@@ -1422,6 +1425,8 @@ namespace Nampower {
&Spell_C_SpellFailedHook);
gSpellGoDetour = createHook<SpellGoT>(process, Offsets::SpellGo, &SpellGoHook);
gSpellDelayedDetour = createHook<PacketHandlerT>(process, Offsets::SpellDelayed, &SpellDelayedHook);
gGetGUIDFromNameDetour = createHook<GetGUIDFromNameT>(process, Offsets::GetGUIDFromName,
&GetGUIDFromNameHook);
gCastSpellByNameDetour = createHook<LuaScriptT>(process, Offsets::Script_CastSpellByName,
&Script_CastSpellByNameHook);
gSpellTargetUnitDetour = createHook<LuaScriptT>(process, Offsets::Script_SpellTargetUnit,
@@ -1458,6 +1463,10 @@ namespace Nampower {
&AttackRoundInfo_ReadPacketHook);
gCSimpleFrame_GetNameDetour = createHook<LuaScriptT>(process, Offsets::CSimpleFrame_GetName,
&CSimpleFrame_GetNameHook);
gCSimpleTop_OnKeyDownDetour = createHook<CSimpleTop_OnKeyDownT>(
process, Offsets::CSimpleTop_OnKeyDown, &CSimpleTop_OnKeyDownHook);
gCSimpleTop_OnKeyUpDetour = createHook<CSimpleTop_OnKeyUpT>(
process, Offsets::CSimpleTop_OnKeyUp, &CSimpleTop_OnKeyUpHook);
gSendUnitSignalDetour = createHook<SendUnitSignalT>(process, Offsets::SendUnitSignal,
&SendUnitSignalHook);
gCGGameUI_ShowCombatFeedbackDetour = createHook<CGGameUI_ShowCombatFeedbackT>(
@@ -1644,6 +1653,12 @@ namespace Nampower {
char PLAYER_GUILD_UPDATE_GUID[] = "PLAYER_GUILD_UPDATE_GUID";
addCustomEvent(game::PLAYER_GUILD_UPDATE_GUID, PLAYER_GUILD_UPDATE_GUID);
char KEY_DOWN[] = "KEY_DOWN";
addCustomEvent(game::KEY_DOWN, KEY_DOWN);
char KEY_UP[] = "KEY_UP";
addCustomEvent(game::KEY_UP, KEY_UP);
}
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId) {
@@ -1819,6 +1834,9 @@ namespace Nampower {
char setMouseoverUnit[] = "SetMouseoverUnit";
RegisterLuaFunction(setMouseoverUnit, reinterpret_cast<uintptr_t *>(Script_SetMouseoverUnit));
char unitGUID[] = "UnitGUID";
RegisterLuaFunction(unitGUID, reinterpret_cast<uintptr_t *>(Script_UnitGUID));
}
void load() {
+10 -1
View File
@@ -25,7 +25,7 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 40;
constexpr uint32_t MINOR_VERSION = 41;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
@@ -89,6 +89,11 @@ namespace Nampower {
using Spell_C_HandleSpriteClickT = bool (__fastcall *)(game::CSpriteClickEvent *event);
using Spell_C_HandleTerrainClickT = uint32_t (__fastcall *)(game::CTerrainClickEvent *event);
using CGWorldFrame_OnLayerTrackTerrainT = void (__fastcall *)(void *thisptr, int dummy_edx, int param_1);
using CSimpleTop_OnKeyDownT = bool (__fastcall *)(EVENT_DATA_KEY *param_1, int param_2);
using CSimpleTop_OnKeyUpT = bool (__fastcall *)(EVENT_DATA_KEY *param_1, int param_2);
using IsModifierKeyDownT = uint32_t (__fastcall *)(int param_1);
using Script_IsAltKeyDownT = uint32_t (__fastcall *)(void *param_1);
using UpdateSyncKeyStateT = void (__fastcall *)(void *evtContext, uint32_t key, int *syncType);
using Spell_C_TargetSpellT = bool (__fastcall *)(
uint32_t *player,
uint32_t *spellId,
@@ -123,6 +128,7 @@ namespace Nampower {
using LuaGetTableT = void (__fastcall *)(uintptr_t *luaState, int globalsIndex);
using LuaCallT = void (__fastcall *)(const char *code, const char *unused);
using LuaScriptT = uint32_t (__fastcall *)(uintptr_t *luaState);
using FrameScript_ExecuteT = void (__fastcall *)(int param_1);
using GetGUIDFromNameT = std::uint64_t (__fastcall *)(const char *);
using GetUnitFromNameT = uintptr_t *(__fastcall *)(const char *);
using GetNamesFromGUIDT = char **(__fastcall *)(uint64_t *guid, int *numNamesReturn);
@@ -133,6 +139,7 @@ namespace Nampower {
using lua_typeT = int (__fastcall *)(uintptr_t *, int);
using lua_isstringT = bool (__fastcall *)(uintptr_t *, int);
using lua_isnumberT = bool (__fastcall *)(uintptr_t *, int);
using lua_toflagT = uint32_t (__fastcall *)(uintptr_t *, int, uint32_t);
using lua_tostringT = char *(__fastcall *)(uintptr_t *, int);
using lua_tonumberT = double (__fastcall *)(uintptr_t *, int);
using lua_pushnumberT = void (__fastcall *)(uintptr_t *, double);
@@ -181,6 +188,8 @@ namespace Nampower {
using CGGameUI_DisplayErrorT = void (__cdecl *)(uint32_t errorCode);
using XMLNode_GetAttributeByNameT = uint32_t (__thiscall *)(void *thisptr, const char *name);
void RegisterLuaFunction(char *, uintptr_t *func);
void LuaCall(const char *code);
+186 -8
View File
@@ -3,6 +3,7 @@
//
#include "misc_scripts.hpp"
#include "logging.hpp"
#include "offsets.hpp"
#include "items.hpp"
#include "dbc_fields.hpp"
@@ -10,6 +11,9 @@
#include "helper.hpp"
#include "lua_refs.hpp"
#include "auras.hpp"
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cstring>
namespace Nampower {
@@ -916,6 +920,129 @@ namespace Nampower {
return 1;
}
uint32_t Script_UnitGUID(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: UnitGUID(unitToken)");
return 0;
}
const char *unitToken = lua_tostring(luaState, 1);
if (!unitToken || unitToken[0] == '\0') {
lua_pushnil(luaState);
return 1;
}
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
const uint64_t guid = getGUIDFromName(unitToken);
if (guid == 0) {
lua_pushnil(luaState);
} else {
PushGuidString(luaState, guid);
}
return 1;
}
uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr) {
auto const original = detour->GetTrampolineT<GetGUIDFromNameT>();
if (!nameStr || nameStr[0] == '\0') {
return 0;
}
// Hex GUID with optional suffix: "0x[16 hex digits][owner|target|pet]"
const size_t tokenLen = std::strlen(nameStr);
const bool isHexToken = (nameStr[0] == '0' && (nameStr[1] == 'x' || nameStr[1] == 'X'));
const bool hasHexAndUnitSuffix = isHexToken && tokenLen > 18 &&
(_stricmp(nameStr + 18, "owner") == 0 ||
_stricmp(nameStr + 18, "target") == 0 ||
_stricmp(nameStr + 18, "pet") == 0);
// Handle mark1-mark8 before calling original, so original parser can't error on custom tokens.
if (_strnicmp(nameStr, "mark", 4) == 0) {
const char digit = nameStr[4];
if (digit >= '1' && digit <= '8' && nameStr[5] == '\0') {
const uint32_t markIndex = static_cast<uint32_t>(digit - '1');
auto const markGuids = reinterpret_cast<uint64_t *>(static_cast<uintptr_t>(Offsets::RaidTargetMarkGuidBase));
return markGuids[markIndex];
}
}
if (!hasHexAndUnitSuffix) {
uint64_t result = original(nameStr);
if (result != 0) {
return result;
}
}
if (isHexToken) {
if (tokenLen < 18) {
return 0;
}
char guidHex[17] = {};
std::memcpy(guidHex, nameStr + 2, 16);
if (!std::all_of(guidHex, guidHex + 16,
[](unsigned char c) { return std::isxdigit(c) != 0; })) {
return 0;
}
const uint64_t parsedGuid = std::strtoull(guidHex, nullptr, 16);
// Suffix must begin exactly after 16 hex digits (no substring matching).
const char *const hexSuffix = nameStr + 18;
// "owner" suffix
if (_stricmp(hexSuffix, "owner") == 0) {
return game::UnitGetOwnerGuidForGuid(parsedGuid);
}
// "target" suffix
if (_stricmp(hexSuffix, "target") == 0) {
return game::UnitGetTargetGuidForGuid(parsedGuid);
}
// "pet" suffix
if (_stricmp(hexSuffix, "pet") == 0) {
return game::UnitGetPetGuidForGuid(parsedGuid);
}
return parsedGuid;
}
// Non-hex token ending in owner/target/pet suffix (up to 20 chars total)
if (tokenLen <= 0x14) {
auto resolveBaseGuid = [&](size_t suffixLen) -> uint64_t {
// Copy prefix (everything before suffix) and resolve it.
// Call via the hooked address so nested custom tokens also resolve.
char buf[0x15] = {};
std::memcpy(buf, nameStr, tokenLen - suffixLen);
auto const hooked = reinterpret_cast<GetGUIDFromNameT>(
static_cast<uint32_t>(Offsets::GetGUIDFromName));
return hooked(buf);
};
if (tokenLen > 5 && _stricmp(nameStr + tokenLen - 5, "owner") == 0) {
const uint64_t baseGuid = resolveBaseGuid(5);
return game::UnitGetOwnerGuidForGuid(baseGuid);
}
if (tokenLen > 6 && _stricmp(nameStr + tokenLen - 6, "target") == 0) {
const uint64_t baseGuid = resolveBaseGuid(6);
return game::UnitGetTargetGuidForGuid(baseGuid);
}
if (tokenLen > 3 && _stricmp(nameStr + tokenLen - 3, "pet") == 0) {
const uint64_t baseGuid = resolveBaseGuid(3);
return game::UnitGetPetGuidForGuid(baseGuid);
}
}
return 0;
}
uint32_t CSimpleFrame_GetNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
luaState = GetLuaStatePtr();
@@ -930,6 +1057,7 @@ namespace Nampower {
uint32_t typeId = *nameplateTypeId;
uintptr_t *frameObj = nullptr;
uintptr_t *vtable = nullptr;
int luaArgType = lua_type(luaState, 1);
if (luaArgType == LUA_TTABLE) {
@@ -940,10 +1068,19 @@ namespace Nampower {
if (frameObj == nullptr) {
lua_error(luaState, "Attempt to find 'this' in non-frame table");
} else if (!IsReadableMemory(frameObj, sizeof(uintptr_t))) {
// Stale frame pointer (object was destroyed) - return nil silently
lua_pushnil(luaState);
return 1;
} else {
vtable = reinterpret_cast<uintptr_t *>(*frameObj);
// Validate vtable covers entries [0..4] before touching isType at [4]
if (!IsReadableMemory(vtable, 5 * sizeof(uintptr_t))) {
lua_pushnil(luaState);
return 1;
}
// Type check via vtable[4] (offset 0x10)
using IsTypeT = char (__thiscall *)(uintptr_t *thisPtr, uint32_t typeId);
auto vtable = reinterpret_cast<uintptr_t *>(*frameObj);
auto isType = reinterpret_cast<IsTypeT>(vtable[4]);
if (isType(frameObj, typeId) == 0) {
lua_error(luaState, "Wrong object type for member function");
@@ -953,18 +1090,22 @@ namespace Nampower {
lua_error(luaState, "Attempt to find 'this' in non-table value");
}
// frameObj and vtable are guaranteed valid here (lua_error longjmps otherwise)
// Check if second arg is passed (e.g. nameplate:GetName(1) to get GUID)
if (lua_isnumber(luaState, 2) && static_cast<int>(lua_tonumber(luaState, 2)) != 0) {
// Return GUID string from frame object at offset 0x4E8/0x4EC
uint32_t guidLow = frameObj[0x13a];
uint32_t guidHigh = frameObj[0x13b];
uint64_t guid = (static_cast<uint64_t>(guidHigh) << 32) | guidLow;
PushGuidString(luaState, guid);
if (!IsReadableMemory(frameObj + 0x13a, 2 * sizeof(uint32_t))) {
lua_pushnil(luaState);
} else {
uint32_t guidLow = frameObj[0x13a];
uint32_t guidHigh = frameObj[0x13b];
uint64_t guid = (static_cast<uint64_t>(guidHigh) << 32) | guidLow;
PushGuidString(luaState, guid);
}
} else {
// GetName via vtable[1] (offset 0x4)
// GetName via vtable[1] (offset 0x4) - vtable already validated above
using GetNameT = char *(__thiscall *)(uintptr_t *thisPtr);
auto vtable = reinterpret_cast<uintptr_t *>(*frameObj);
auto getName = reinterpret_cast<GetNameT>(vtable[1]);
char *name = getName(frameObj);
@@ -978,4 +1119,41 @@ namespace Nampower {
return 1;
}
void EmitKeyEvent(uint32_t eventCode, EVENT_DATA_KEY *keyData) {
// don't trigger events until player exists as event won't be registered
if (keyData == nullptr || game::ClntObjMgrGetActivePlayerGuid() == 0) {
return;
}
static char format[] = "%d%d%d%d";
reinterpret_cast<int (__cdecl *)(int, char *, uint32_t, uint32_t, uint32_t, uint32_t)>(Offsets::SignalEventParam)(
eventCode,
format,
keyData->key,
keyData->metaKeyState,
keyData->repeat,
keyData->time);
}
bool CSimpleTop_OnKeyDownHook(hadesmem::PatchDetourBase *detour, EVENT_DATA_KEY *keyData, int param_2) {
auto const onKeyDown = detour->GetTrampolineT<CSimpleTop_OnKeyDownT>();
auto result = onKeyDown(keyData, param_2);
auto const isModifierKeyDown = reinterpret_cast<IsModifierKeyDownT>(Offsets::Script_IsAltKeyDown);
auto altDown = isModifierKeyDown(2);
EmitKeyEvent(game::KEY_DOWN, keyData);
return result;
}
bool CSimpleTop_OnKeyUpHook(hadesmem::PatchDetourBase *detour, EVENT_DATA_KEY *keyData, int param_2) {
auto const onKeyUp = detour->GetTrampolineT<CSimpleTop_OnKeyUpT>();
auto result = onKeyUp(keyData, param_2);
EmitKeyEvent(game::KEY_UP, keyData);
return result;
}
}
+4
View File
@@ -38,6 +38,10 @@ namespace Nampower {
uint32_t Script_CancelPlayerAuraSpellId(uintptr_t *luaState);
uint32_t Script_SetMouseoverUnit(uintptr_t *luaState);
uint32_t Script_UnitGUID(uintptr_t *luaState);
uint32_t CSimpleFrame_GetNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr);
bool CSimpleTop_OnKeyDownHook(hadesmem::PatchDetourBase *detour, EVENT_DATA_KEY *keyData, int param_2);
bool CSimpleTop_OnKeyUpHook(hadesmem::PatchDetourBase *detour, EVENT_DATA_KEY *keyData, int param_2);
}
+13
View File
@@ -154,6 +154,8 @@ enum class Offsets : std::uint32_t {
Framescript_SetEventCount = 0X007053B0,
Framescript_EventObject_Data = 0x00CEEF68,
FrameScript_Execute = 0x00704CF0,
// Existing script functions
GetGUIDFromName = 0X00515970,
Script_CastSpellByName = 0x004B4AB0,
@@ -161,6 +163,7 @@ enum class Offsets : std::uint32_t {
Script_SetCVar = 0x00488C10,
Script_RunScript = 0x0048B980,
Script_SpellStopCasting = 0x006E6E80,
Script_IsAltKeyDown = 0x0041F8F0,
Script_UseInventoryItem = 0x004c8de0,
Script_UseContainerItem = 0x004fa0e0,
@@ -171,6 +174,7 @@ enum class Offsets : std::uint32_t {
lua_type = 0X006F3400,
lua_isstring = 0x006F3510,
lua_isnumber = 0X006F34D0,
lua_toflag = 0X006F1C10,
lua_tostring = 0x006F3690,
lua_tonumber = 0X006F3620,
lua_pushnumber = 0X006F3810,
@@ -192,6 +196,8 @@ enum class Offsets : std::uint32_t {
luaL_unref = 0x006F5400,
CGInputControlGetActive = 0XBE1148,
UpdateSyncKeyState = 0x00424810,
ClientEventContext = 0x00882670,
LastHardwareAction = 0X00CF0BC8,
CGInputControlSetReleaseAction = 0x514810,
CGInputControlSetControlBit = 0x515090,
@@ -248,5 +254,12 @@ enum class Offsets : std::uint32_t {
CursorModelSetSequence = 0x00523c20,
CGGameUI_DisplayError = 0x00496720,
CSimpleTop_OnKeyDown = 0x00765f10,
CSimpleTop_OnKeyUp = 0x00765fd0,
CSimpleFrame_GetName = 0x007a1390,
XMLNode_GetAttributeByName = 0x006f2cf0,
RaidTargetMarkGuidBase = 0xB71368,
};
+6
View File
@@ -158,3 +158,9 @@ struct CastData {
uint32_t channelDuration;
};
struct EVENT_DATA_KEY {
uint32_t key;
uint32_t metaKeyState;
uint32_t repeat;
uint32_t time;
};