add new file commands, other small fixes

This commit is contained in:
avitasia
2026-03-03 14:23:12 -08:00
parent 97ba0ff2f4
commit f279bb19f8
14 changed files with 14706 additions and 13632 deletions
+1 -1
View File
@@ -103,4 +103,4 @@ add_subdirectory(nampower)
install(FILES install(FILES
"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt" "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt"
"${CMAKE_CURRENT_SOURCE_DIR}/README.md" "${CMAKE_CURRENT_SOURCE_DIR}/README.md"
DESTINATION "${CMAKE_INSTALL_PREFIX}") DESTINATION "${CMAKE_INSTALL_PREFIX}")
+3 -1
View File
@@ -195,8 +195,10 @@ This includes functions for:
- Aura duration tracking, cancel helpers, and aura visibility checks (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId, IsAuraHidden) - Aura duration tracking, cancel helpers, and aura visibility checks (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId, IsAuraHidden)
- Spell duration lookup (GetSpellDuration) - returns channel duration for channeling spells and the first aura effect duration for non-channeling spells - Spell duration lookup (GetSpellDuration) - returns channel duration for channeling spells and the first aura effect duration for non-channeling spells
- Player movement state queries (PlayerIsMoving, PlayerIsRooted, PlayerIsSwimming) - Player movement state queries (PlayerIsMoving, PlayerIsRooted, PlayerIsSwimming)
- File and script helpers (WriteCustomFile, ReadCustomFile, CustomFileExists, ImportFile, ExportFile, ExecuteCustomLuaFile)
- Encrypted login helpers (EncryptPassword, EncryptedServerLogin)
- Talent helpers (LearnTalentRank) - Talent helpers (LearnTalentRank)
- Spell lookups and utilities - Spell lookups and utilities (GetUnitGUID, CombatLogFlush, etc.)
`LearnTalentRank(talentPage, talentIndex, rank)` learns a specific talent rank directly by tab/index. `LearnTalentRank(talentPage, talentIndex, rank)` learns a specific talent rank directly by tab/index.
Valid ranges are: `talentPage` = `1-3`, `talentIndex` = `1-32`, `rank` = `1-5`. Valid ranges are: `talentPage` = `1-3`, `talentIndex` = `1-32`, `rank` = `1-5`.
+268 -26
View File
@@ -24,6 +24,7 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
- [GetSpellDuration](#getspelldurationspellid-ignoremodifiers) - [GetSpellDuration](#getspelldurationspellid-ignoremodifiers)
- [GetUnitData](#getunitdataunittoken-copy) - [GetUnitData](#getunitdataunittoken-copy)
- [GetUnitField](#getunitfieldunittoken-fieldname-copy) - [GetUnitField](#getunitfieldunittoken-fieldname-copy)
- [GetUnitGUID](#getunitguidunittoken)
- [GetSpellIdForName](#getspellidfornamenspellname) - [GetSpellIdForName](#getspellidfornamenspellname)
- [GetSpellNameAndRankForId](#getspellnameandrankforidid) - [GetSpellNameAndRankForId](#getspellnameandrankforidid)
- [GetSpellSlotTypeIdForName](#getspellslottypeidfornamenspellname) - [GetSpellSlotTypeIdForName](#getspellslottypeidfornamenspellname)
@@ -58,7 +59,15 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
- [PlayerIsRooted](#playerisrooted) - [PlayerIsRooted](#playerisrooted)
- [PlayerIsSwimming](#playerisswimming) - [PlayerIsSwimming](#playerisswimming)
- [Utility Functions](#utility-functions) - [Utility Functions](#utility-functions)
- [GetUnitGUID](#getunitguidunittoken) - [WriteCustomFile(filename, content, [mode])](#writecustomfilefilename-content-mode)
- [ReadCustomFile(filename)](#readcustomfilefilename)
- [CustomFileExists(filename)](#customfileexistsfilename)
- [ImportFile(filename)](#importfilefilename)
- [ExportFile(filename, text)](#exportfilefilename-text)
- [ExecuteCustomLuaFile(filename)](#executecustomluafilefilename)
- [EncryptPassword(password)](#encryptpasswordpassword)
- [EncryptedServerLogin(username, encryptedPasswordBase64String)](#encryptedserverloginusername-encryptedpasswordbase64string)
- [CombatLogFlush()](#combatlogflush)
- [DisenchantAll](#disenchantallitemidorname-includesoulbound-or-disenchantallquality-includesoulbound) - [DisenchantAll](#disenchantallitemidorname-includesoulbound-or-disenchantallquality-includesoulbound)
--- ---
@@ -762,6 +771,42 @@ local resistances = GetUnitField("player", "resistances")
-- resistances[1] = armor, [2] = holy, [3] = fire, [4] = nature, [5] = frost, [6] = shadow, [7] = arcane -- resistances[1] = armor, [2] = holy, [3] = fire, [4] = nature, [5] = frost, [6] = shadow, [7] = arcane
``` ```
#### GetUnitGUID(unitToken)
Returns the GUID of the unit identified by the given unit token.
Supports Nampower's extended unit-token formats (see [Unit Token Extensions](README.md#unit-token-extensions-getunitguid--all-unittokentarget-string-params)).
**Parameters:**
- `unitToken` (string): A unit token or extended unit token string.
**Returns:**
- `guid` (string): The unit's GUID as a hex string (e.g. `"0xF5300000000000A5"`), or `nil` if the unit cannot be resolved.
**Supported token formats:**
- Standard tokens: `"player"`, `"target"`, `"pet"`, `"mouseover"`, `"party1"``"party4"`, `"partypet1"``"partypet4"`, `"raid1"``"raid40"`, `"raidpet1"``"raidpet40"`
- Raid target marks: `"mark1"``"mark8"`
- Suffix forms: any token with `"owner"`, `"target"`, or `"pet"` appended (e.g. `"targetowner"`, `"mark1target"`, `"party1pet"`)
- Raw hex GUIDs with optional suffix: `"0x[16 hex digits]"`, `"0x[16 hex digits]target"`, etc.
**Examples:**
```lua
-- Standard tokens
print(GetUnitGUID("player"))
print(GetUnitGUID("target"))
print(GetUnitGUID("party1"))
-- Raid target marks
print(GetUnitGUID("mark1"))
-- Suffix forms
print(GetUnitGUID("mark1target")) -- target of the unit marked with mark 1
print(GetUnitGUID("targetowner")) -- owner of the current target
print(GetUnitGUID("party1pet")) -- pet of party member 1
-- Raw hex GUID with suffix
print(GetUnitGUID("0xF5300000000000A5target"))
```
#### QueueSpellByName(spellName) #### QueueSpellByName(spellName)
Will force queue a spell regardless of the appropriate queue window. If no spell is currently being cast it will be cast immediately. Will force queue a spell regardless of the appropriate queue window. If no spell is currently being cast it will be cast immediately.
For example can make a macro with For example can make a macro with
@@ -1223,44 +1268,241 @@ end
### Utility Functions ### Utility Functions
#### GetUnitGUID(unitToken) #### WriteCustomFile(filename, content, [mode])
Returns the GUID of the unit identified by the given unit token. Writes text to a file in the `CustomData` directory.
Supports Nampower's extended unit-token formats (see [Unit Token Extensions](README.md#unit-token-extensions-getunitguid--all-unittokentarget-string-params)). **Availability:**
- In-game Lua and GlueXML.
**Parameters:** **Parameters:**
- `unitToken` (string): A unit token or extended unit token string. - `filename` (string): File name only (must not contain path separators or invalid filename characters).
- `content` (string): Content to write.
- `mode` (string, optional): One-character mode:
- `"w"` = truncate/overwrite (default)
- `"b"` = write in binary mode
- `"a"` = append
**Returns:** **Returns:**
- `guid` (string): The unit's GUID as a hex string (e.g. `"0xF5300000000000A5"`), or `nil` if the unit cannot be resolved. - No return value.
- Raises a Lua error on invalid filename, invalid mode, or write failure.
**Supported token formats:** **Behavior:**
- Standard tokens: `"player"`, `"target"`, `"pet"`, `"mouseover"`, `"party1"``"party4"`, `"partypet1"``"partypet4"`, `"raid1"``"raid40"`, `"raidpet1"``"raidpet40"` - Paths are constrained to the `CustomData` directory.
- Raid target marks: `"mark1"``"mark8"` - If `mode` is omitted, `"w"` is used.
- Suffix forms: any token with `"owner"`, `"target"`, or `"pet"` appended (e.g. `"targetowner"`, `"mark1target"`, `"party1pet"`)
- Raw hex GUIDs with optional suffix: `"0x[16 hex digits]"`, `"0x[16 hex digits]target"`, etc.
**Examples:** **Examples:**
```lua ```lua
-- Standard tokens WriteCustomFile("notes.txt", "hello")
print(GetUnitGUID("player")) WriteCustomFile("notes.txt", "more text\n", "a")
print(GetUnitGUID("target")) WriteCustomFile("blob.bin", "raw-bytes", "b")
print(GetUnitGUID("party1"))
-- Raid target marks
print(GetUnitGUID("mark1"))
-- Suffix forms
print(GetUnitGUID("mark1target")) -- target of the unit marked with mark 1
print(GetUnitGUID("targetowner")) -- owner of the current target
print(GetUnitGUID("party1pet")) -- pet of party member 1
-- Raw hex GUID with suffix
print(GetUnitGUID("0xF5300000000000A5target"))
``` ```
--- ---
#### ReadCustomFile(filename)
Reads text from a file in the `CustomData` directory.
**Availability:**
- In-game Lua and GlueXML.
**Parameters:**
- `filename` (string): File name only (must not contain path separators or invalid filename characters).
**Returns:**
- File contents as a string.
- `nil` if the file does not exist.
- Raises a Lua error on invalid filename/path or other read failures.
**Behavior:**
- Paths are constrained to the `CustomData` directory.
**Examples:**
```lua
local text = ReadCustomFile("notes.txt")
```
---
#### CustomFileExists(filename)
Returns whether a file exists in the `CustomData` directory.
**Availability:**
- In-game Lua and GlueXML.
**Parameters:**
- `filename` (string): File name only (must not contain path separators or invalid filename characters).
**Returns:**
- `true` if the file exists and is a regular file.
- `false` if it does not exist.
- Raises a Lua error on invalid filename/path or stat failures.
**Behavior:**
- Paths are constrained to the `CustomData` directory.
**Examples:**
```lua
if CustomFileExists("notes.txt") then
print("notes.txt exists")
end
```
---
#### ImportFile(filename)
Reads a `.txt` file from the `Imports` directory. This is provided for backwards compatibility with SuperWoW and to patch some security issues with the original implementation.
**Availability:**
- In-game Lua and GlueXML.
**Parameters:**
- `filename` (string): Base filename; `.txt` is always appended automatically.
**Returns:**
- File contents as a string.
- `nil` if the file does not exist.
- Raises a Lua error if the filename/path is invalid or other read failures occur.
**Behavior:**
- Path is constrained to `Imports`.
- `.txt` is always appended to the provided filename.
**Examples:**
```lua
local data = ImportFile("data") -- will read from Imports/data.txt
```
---
#### ExportFile(filename, text)
Writes text to a `.txt` file in the `Imports` directory. This is provided for backwards compatibility with SuperWoW and to patch some security issues with the original implementation.
**Availability:**
- In-game Lua and GlueXML.
**Parameters:**
- `filename` (string): Base filename; `.txt` is always appended automatically.
- `text` (string): Content to write.
**Returns:**
- No return value.
- Raises a Lua error on invalid parameters, invalid filename/path, or write failure.
**Behavior:**
- Path is constrained to `Imports`.
- `.txt` is always appended to the provided filename.
- Exactly 2 parameters are accepted (no mode argument).
- Writes in overwrite mode.
**Examples:**
```lua
ExportFile("profile", "value=1")
```
---
#### ExecuteCustomLuaFile(filename)
Executes a `.lua` file from the `CustomData` directory using the same function used to load WTF files.
**Availability:**
- In-game Lua and GlueXML.
**Parameters:**
- `filename` (string): Must end with `.lua`.
**Returns:**
- No return value.
- Raises a Lua error on invalid filename/path or execution setup failure.
**Behavior:**
- Path is constrained to `CustomData`.
- Only `.lua` files are accepted.
**Examples:**
```lua
ExecuteCustomLuaFile("bootstrap.lua")
```
---
#### EncryptPassword(password)
Encrypts a plaintext password using Windows DPAPI and returns a tagged ciphertext string.
Requires the user to have set an environment variable WOW_ENCRYPTION_KEY.
This exists to support autologin workflows without storing passwords in plain text.
**Availability:**
- GlueXML only (not available to in-game Lua).
**Parameters:**
- `password` (string): Plaintext password, or an already encrypted tagged value.
**Returns:**
- Encrypted value as `":encrypted:" .. base64Ciphertext`.
- If input already starts with `:encrypted:`, it is returned unchanged to avoid double encrypting.
- Raises a Lua error if `WOW_ENCRYPTION_KEY` is not set or encryption fails.
**Behavior:**
- Uses `WOW_ENCRYPTION_KEY` as DPAPI entropy.
- Output is idempotently tagged with `:encrypted:`.
**Examples:**
```lua
local p = EncryptPassword("hunter2")
-- p starts with ":encrypted:"
```
---
#### EncryptedServerLogin(username, encryptedPasswordBase64String)
Logs in using an encrypted password generated by `EncryptPassword`.
Requires the user to have set an environment variable WOW_ENCRYPTION_KEY.
This exists to support autologin workflows without storing passwords in plain text.
**Availability:**
- GlueXML only (not available to in-game Lua).
**Parameters:**
- `username` (string): Account username.
- `encryptedPasswordBase64String` (string): Must start with `:encrypted:`.
**Returns:**
- No return value.
- Raises a Lua error if parameters are invalid, the prefix is missing, `WOW_ENCRYPTION_KEY` is not set, or decryption fails.
**Behavior:**
- Looks for the `:encrypted:` prefix added by EncryptPassword.
- Removes the prefix before base64+DPAPI decryption.
- Uses `WOW_ENCRYPTION_KEY` as DPAPI entropy before calling the game login function.
**Examples:**
```lua
local enc = EncryptPassword("hunter2")
EncryptedServerLogin("my_user", enc)
```
---
#### CombatLogFlush()
Forces the current combat log file buffer to flush to disk immediately.
**Availability:**
- In-game Lua and GlueXML.
**Parameters:**
- None.
**Returns:**
- No return value.
**Behavior:**
- Calls the client `SLogFlush` routine with the current combat-log file handle.
- Useful if you need log lines persisted to disk right away.
**Examples:**
```lua
CombatLogFlush()
```
#### DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound]) #### DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound])
Automatically disenchants items in your inventory. Can disenchant a specific item by ID/name, or all weapons and armor of a specified quality. Automatically disenchants items in your inventory. Can disenchant a specific item by ID/name, or all weapons and armor of a specified quality.
+13799 -13596
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -25,6 +25,8 @@ set(SOURCE_FILES
spellevents.cpp spellevents.cpp
misc_scripts.hpp misc_scripts.hpp
misc_scripts.cpp misc_scripts.cpp
file_scripts.hpp
file_scripts.cpp
spell_scripts.hpp spell_scripts.hpp
spell_scripts.cpp spell_scripts.cpp
cooldown_scripts.hpp cooldown_scripts.hpp
+525
View File
@@ -0,0 +1,525 @@
#include "file_scripts.hpp"
#include "helper.hpp"
#include "offsets.hpp"
#include <cstring>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <wincrypt.h>
#include <windows.h>
#pragma comment(lib, "Crypt32.lib")
namespace Nampower {
static constexpr const char *kEncryptedPasswordPrefix = ":encrypted:";
static bool IsValidFilename(const char *name) {
return name && name[0] != '\0' && std::strpbrk(name, "<>:\"/\\|?*") == nullptr;
}
static bool ValidateLuaFilename(uintptr_t *luaState, const char *filename) {
if (!IsValidFilename(filename)) {
lua_error(luaState, "Invalid or empty filename (must not contain: < > : \" / \\ | ?*)");
return false;
}
return true;
}
static bool GetEnvVar(const char *name, std::string &outValue) {
const DWORD valueLen = GetEnvironmentVariableA(name, nullptr, 0);
if (valueLen == 0) {
return false;
}
std::vector<char> buffer(valueLen);
const DWORD copied = GetEnvironmentVariableA(name, buffer.data(), valueLen);
if (copied == 0 || copied >= valueLen) {
return false;
}
outValue.assign(buffer.data(), copied);
return true;
}
static bool Base64Decode(const std::string &input, std::vector<BYTE> &outBytes) {
DWORD decodedLen = 0;
if (!CryptStringToBinaryA(input.c_str(), static_cast<DWORD>(input.size()), CRYPT_STRING_BASE64, nullptr,
&decodedLen, nullptr, nullptr)) {
return false;
}
outBytes.resize(decodedLen);
if (!CryptStringToBinaryA(input.c_str(), static_cast<DWORD>(input.size()), CRYPT_STRING_BASE64, outBytes.data(),
&decodedLen, nullptr, nullptr)) {
return false;
}
outBytes.resize(decodedLen);
return true;
}
static bool Base64Encode(const BYTE *data, DWORD dataLen, std::string &outBase64) {
DWORD encodedLen = 0;
if (!CryptBinaryToStringA(data, dataLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &encodedLen)) {
return false;
}
std::vector<char> encoded(encodedLen);
if (!CryptBinaryToStringA(data, dataLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, encoded.data(),
&encodedLen)) {
return false;
}
if (encodedLen > 0 && encoded[encodedLen - 1] == '\0') {
--encodedLen;
}
outBase64.assign(encoded.data(), encodedLen);
return true;
}
static bool EncryptDpapiBase64(const std::string &plaintext, const std::string &entropy,
std::string &outCiphertextBase64) {
DATA_BLOB plainBlob = {};
plainBlob.pbData = reinterpret_cast<BYTE *>(const_cast<char *>(plaintext.data()));
plainBlob.cbData = static_cast<DWORD>(plaintext.size());
DATA_BLOB entropyBlob = {};
if (!entropy.empty()) {
entropyBlob.pbData = reinterpret_cast<BYTE *>(const_cast<char *>(entropy.data()));
entropyBlob.cbData = static_cast<DWORD>(entropy.size());
}
DATA_BLOB encryptedBlob = {};
if (!CryptProtectData(&plainBlob, nullptr, entropy.empty() ? nullptr : &entropyBlob, nullptr, nullptr, 0,
&encryptedBlob)) {
return false;
}
const bool encoded = Base64Encode(encryptedBlob.pbData, encryptedBlob.cbData, outCiphertextBase64);
SecureZeroMemory(encryptedBlob.pbData, encryptedBlob.cbData);
LocalFree(encryptedBlob.pbData);
return encoded;
}
static bool DecryptDpapiBase64(const std::string &ciphertextBase64, const std::string &entropy,
std::string &outPlaintext) {
std::vector<BYTE> encryptedBytes;
if (!Base64Decode(ciphertextBase64, encryptedBytes)) {
return false;
}
DATA_BLOB encryptedBlob = {};
encryptedBlob.pbData = encryptedBytes.data();
encryptedBlob.cbData = static_cast<DWORD>(encryptedBytes.size());
DATA_BLOB entropyBlob = {};
if (!entropy.empty()) {
entropyBlob.pbData = reinterpret_cast<BYTE *>(const_cast<char *>(entropy.data()));
entropyBlob.cbData = static_cast<DWORD>(entropy.size());
}
DATA_BLOB decryptedBlob = {};
if (!CryptUnprotectData(&encryptedBlob, nullptr, entropy.empty() ? nullptr : &entropyBlob, nullptr, nullptr, 0,
&decryptedBlob)) {
return false;
}
outPlaintext.assign(reinterpret_cast<char *>(decryptedBlob.pbData), decryptedBlob.cbData);
SecureZeroMemory(decryptedBlob.pbData, decryptedBlob.cbData);
LocalFree(decryptedBlob.pbData);
return true;
}
static bool EnsureDirectoryExists(const char *baseDir) {
if (CreateDirectoryA(baseDir, nullptr) != 0) {
return true;
}
const DWORD errorCode = GetLastError();
return errorCode == ERROR_ALREADY_EXISTS;
}
static std::string BuildPath(const char *baseDir, const char *filename, bool forceTxtExtension) {
std::string fileNameStr(filename);
if (forceTxtExtension) {
fileNameStr += ".txt";
}
std::string fullPath(baseDir);
fullPath += "\\";
fullPath += fileNameStr;
return fullPath;
}
static bool TryGetFullPath(const std::string &inputPath, std::string &outFullPath) {
char buffer[MAX_PATH] = {};
const DWORD result = GetFullPathNameA(inputPath.c_str(), MAX_PATH, buffer, nullptr);
if (result == 0 || result >= MAX_PATH) {
return false;
}
outFullPath.assign(buffer, result);
return true;
}
static bool ResolveValidatedPath(const char *baseDir, const char *filename, bool forceTxtExtension,
std::string &outPath) {
if (!filename || filename[0] == '\0') {
return false;
}
std::string baseFullPath;
if (!TryGetFullPath(baseDir, baseFullPath)) {
return false;
}
std::string candidatePath = BuildPath(baseDir, filename, forceTxtExtension);
std::string candidateFullPath;
if (!TryGetFullPath(candidatePath, candidateFullPath)) {
return false;
}
std::string basePrefix = baseFullPath;
if (!basePrefix.empty() && basePrefix.back() != '\\' && basePrefix.back() != '/') {
basePrefix += "\\";
}
if (candidateFullPath.size() <= basePrefix.size()) {
return false;
}
if (_strnicmp(candidateFullPath.c_str(), basePrefix.c_str(), basePrefix.size()) != 0) {
return false;
}
outPath = candidateFullPath;
return true;
}
static uint32_t ReadFileFromDirectory(uintptr_t *luaState, const char *baseDir, bool forceTxtExtension,
bool returnNilIfMissing) {
const auto *filename = lua_tostring(luaState, 1);
std::string fullPath;
if (!ResolveValidatedPath(baseDir, filename, forceTxtExtension, fullPath)) {
std::string err = "Invalid filename/path (must remain inside ";
err += baseDir;
err += ").";
lua_error(luaState, err.c_str());
return 0;
}
DEBUG_LOG("Attempting to read file from " << baseDir << ": " << fullPath);
const DWORD attributes = GetFileAttributesA(fullPath.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES) {
const DWORD errorCode = GetLastError();
if (returnNilIfMissing && (errorCode == ERROR_FILE_NOT_FOUND || errorCode == ERROR_PATH_NOT_FOUND)) {
lua_pushnil(luaState);
return 1;
}
lua_error(luaState, "Failed to open file for reading.");
return 0;
}
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
lua_error(luaState, "Failed to open file for reading.");
return 0;
}
std::ifstream in(fullPath);
if (!in) {
lua_error(luaState, "Failed to open file for reading.");
return 0;
}
std::ostringstream buf;
buf << in.rdbuf();
std::string data = buf.str();
lua_pushstring(luaState, const_cast<char *>(data.c_str()));
return 1;
}
static uint32_t WriteFileToDirectory(uintptr_t *luaState, const char *baseDir, bool forceTxtExtension,
const char *filename, char mode, const char *content) {
if (!EnsureDirectoryExists(baseDir)) {
lua_error(luaState, "Failed to create output directory.");
return 0;
}
std::string fullPath;
if (!ResolveValidatedPath(baseDir, filename, forceTxtExtension, fullPath)) {
std::string err = "Invalid filename/path (must remain inside ";
err += baseDir;
err += ").";
lua_error(luaState, err.c_str());
return 0;
}
DEBUG_LOG("Writing file to " << baseDir << ": " << fullPath << " (mode=" << mode << ")");
std::ios::openmode openMode = std::ios::out;
if (mode == 'w') {
openMode |= std::ios::trunc;
} else if (mode == 'a') {
openMode |= std::ios::app;
} else if (mode == 'b') {
openMode |= std::ios::binary;
} else {
lua_error(luaState, "WriteCustomFile: mode must be 'w', 'b', or 'a'");
return 0;
}
std::ofstream out(fullPath, openMode);
if (!out) {
lua_error(luaState, "Failed to open file for writing.");
return 0;
}
out << content;
if (!out) {
lua_error(luaState, "Failed to write file.");
return 0;
}
return 0;
}
uint32_t Script_WriteCustomFile(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
const int argCount = lua_gettop(luaState);
if (argCount < 2 || argCount > 3) {
lua_error(luaState, "Usage: WriteCustomFile(filename, content, [mode])");
return 0;
}
if (!lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: WriteCustomFile(filename, content, [mode])");
return 0;
}
const auto *filename = lua_tostring(luaState, 1);
if (!ValidateLuaFilename(luaState, filename)) {
return 0;
}
const auto *content = lua_tostring(luaState, 2);
char mode = 'w';
if (argCount == 3) {
if (!lua_isstring(luaState, 3)) {
lua_error(luaState, "Usage: WriteCustomFile(filename, content, [mode])");
return 0;
}
const auto *modeStr = lua_tostring(luaState, 3);
if (!modeStr || modeStr[0] == '\0' || modeStr[1] != '\0') {
lua_error(luaState, "WriteCustomFile: mode must be 'w', 'b', or 'a'");
return 0;
}
mode = modeStr[0];
}
if (!content) {
lua_error(luaState, "WriteCustomFile: invalid content");
return 0;
}
return WriteFileToDirectory(luaState, "CustomData", false, filename, mode, content);
}
uint32_t Script_ReadCustomFile(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: ReadCustomFile(filename)");
return 0;
}
const auto *filename = lua_tostring(luaState, 1);
if (!ValidateLuaFilename(luaState, filename)) {
return 0;
}
return ReadFileFromDirectory(luaState, "CustomData", false, true);
}
uint32_t Script_CustomFileExists(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: CustomFileExists(filename)");
return 0;
}
const auto *filename = lua_tostring(luaState, 1);
if (!ValidateLuaFilename(luaState, filename)) {
return 0;
}
std::string fullPath;
if (!ResolveValidatedPath("CustomData", filename, false, fullPath)) {
lua_error(luaState, "Invalid filename/path (must remain inside CustomData).");
return 0;
}
const DWORD attributes = GetFileAttributesA(fullPath.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES) {
const DWORD errorCode = GetLastError();
if (errorCode == ERROR_FILE_NOT_FOUND || errorCode == ERROR_PATH_NOT_FOUND) {
lua_pushboolean(luaState, 0);
return 1;
}
lua_error(luaState, "CustomFileExists: failed to check file");
return 0;
}
lua_pushboolean(luaState, (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
return 1;
}
uint32_t Script_ImportFile(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: ImportFile(filename)");
return 0;
}
const auto *filename = lua_tostring(luaState, 1);
if (!ValidateLuaFilename(luaState, filename)) {
return 0;
}
return ReadFileFromDirectory(luaState, "Imports", true, true);
}
uint32_t Script_ExportFile(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (lua_gettop(luaState) != 2 || !lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: ExportFile(filename, text)");
return 0;
}
const auto *filename = lua_tostring(luaState, 1);
if (!ValidateLuaFilename(luaState, filename)) {
return 0;
}
const auto *content = lua_tostring(luaState, 2);
lua_settop(luaState, 2);
return WriteFileToDirectory(luaState, "Imports", true, filename, 'w', content);
}
uint32_t Script_ExecuteCustomLuaFile(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: ExecuteCustomLuaFile(filename)");
return 0;
}
const auto *filename = lua_tostring(luaState, 1);
if (!ValidateLuaFilename(luaState, filename)) {
return 0;
}
const char *dot = std::strrchr(filename, '.');
if (!dot || _stricmp(dot, ".lua") != 0) {
lua_error(luaState, "ExecuteCustomLuaFile: filename must end with .lua");
return 0;
}
std::string validatedFullPath;
if (!ResolveValidatedPath("CustomData", filename, false, validatedFullPath)) {
lua_error(luaState, "Invalid filename/path (must remain inside CustomData).");
return 0;
}
std::string relativePath = BuildPath("CustomData", filename, false);
DEBUG_LOG("Executing Lua file: " << validatedFullPath);
auto const executeFile = reinterpret_cast<uint32_t(__fastcall *)(char *filePath, int *updateMD5, int *cStatus)>(
Offsets::FrameScript_ExecuteFile);
executeFile(const_cast<char *>(relativePath.c_str()), nullptr, nullptr);
return 0;
}
uint32_t Script_EncryptPassword(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: EncryptPassword(password)");
return 0;
}
auto *password = lua_tostring(luaState, 1);
if (!password) {
lua_error(luaState, "EncryptPassword: invalid password");
return 0;
}
const std::string passwordString(password);
const std::string prefix(kEncryptedPasswordPrefix);
if (passwordString.compare(0, prefix.size(), prefix) == 0) {
lua_pushstring(luaState, const_cast<char *>(passwordString.c_str()));
return 1;
}
std::string entropy;
if (!GetEnvVar("WOW_ENCRYPTION_KEY", entropy)) {
lua_error(luaState, "EncryptPassword: WOW_ENCRYPTION_KEY is not set");
return 0;
}
std::string encryptedPasswordBase64;
if (!EncryptDpapiBase64(passwordString, entropy, encryptedPasswordBase64)) {
lua_error(luaState, "EncryptPassword: failed to encrypt password");
return 0;
}
encryptedPasswordBase64.insert(0, prefix);
lua_pushstring(luaState, const_cast<char *>(encryptedPasswordBase64.c_str()));
return 1;
}
uint32_t Script_EncryptedServerLogin(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) {
lua_error(luaState, "Usage: EncryptedServerLogin(username, encryptedPasswordBase64String)");
return 0;
}
auto *username = lua_tostring(luaState, 1);
auto *encryptedPasswordBase64 = lua_tostring(luaState, 2);
if (!username || !encryptedPasswordBase64) {
lua_error(luaState, "EncryptedServerLogin: invalid username or encrypted password");
return 0;
}
std::string encryptedPasswordValue(encryptedPasswordBase64);
const std::string prefix(kEncryptedPasswordPrefix);
if (encryptedPasswordValue.compare(0, prefix.size(), prefix) != 0) {
lua_error(luaState, "EncryptedServerLogin: encrypted password must start with :encrypted:");
return 0;
}
encryptedPasswordValue.erase(0, prefix.size());
std::string entropy;
if (!GetEnvVar("WOW_ENCRYPTION_KEY", entropy)) {
lua_error(luaState, "EncryptedServerLogin: WOW_ENCRYPTION_KEY is not set");
return 0;
}
std::string decryptedPassword;
if (!DecryptDpapiBase64(encryptedPasswordValue, entropy, decryptedPassword)) {
lua_error(luaState, "EncryptedServerLogin: failed to decrypt password");
return 0;
}
auto const defaultServerLogin = reinterpret_cast<void(__fastcall *)(char *username, char *password)>(
Offsets::CGlueMgr_DefaultServerLogin);
defaultServerLogin(username, const_cast<char *>(decryptedPassword.c_str()));
return 0;
}
}
+18
View File
@@ -0,0 +1,18 @@
//
// File read/write Lua script bindings
//
#pragma once
#include "main.hpp"
namespace Nampower {
uint32_t Script_WriteCustomFile(uintptr_t *luaState);
uint32_t Script_ReadCustomFile(uintptr_t *luaState);
uint32_t Script_CustomFileExists(uintptr_t *luaState);
uint32_t Script_ImportFile(uintptr_t *luaState);
uint32_t Script_ExportFile(uintptr_t *luaState);
uint32_t Script_ExecuteCustomLuaFile(uintptr_t *luaState);
uint32_t Script_EncryptPassword(uintptr_t *luaState);
uint32_t Script_EncryptedServerLogin(uintptr_t *luaState);
}
+1 -1
View File
@@ -24,7 +24,7 @@ namespace Nampower {
lua_tonumberT lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber); lua_tonumberT lua_tonumber = reinterpret_cast<lua_tonumberT>(Offsets::lua_tonumber);
lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber); lua_pushnumberT lua_pushnumber = reinterpret_cast<lua_pushnumberT>(Offsets::lua_pushnumber);
lua_pushstringT lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring); lua_pushstringT lua_pushstring = reinterpret_cast<lua_pushstringT>(Offsets::lua_pushstring);
// lua_pushbooleanT lua_pushboolean = reinterpret_cast<lua_pushbooleanT>(Offsets::lua_pushboolean); doesn't seem to work properly lua_pushbooleanT lua_pushboolean = reinterpret_cast<lua_pushbooleanT>(Offsets::lua_pushboolean);
lua_pushnilT lua_pushnil = reinterpret_cast<lua_pushnilT>(Offsets::lua_pushnil); lua_pushnilT lua_pushnil = reinterpret_cast<lua_pushnilT>(Offsets::lua_pushnil);
lua_newtableT lua_newtable = reinterpret_cast<lua_newtableT>(Offsets::lua_newtable); lua_newtableT lua_newtable = reinterpret_cast<lua_newtableT>(Offsets::lua_newtable);
lua_settableT lua_settable = reinterpret_cast<lua_settableT>(Offsets::lua_settable); lua_settableT lua_settable = reinterpret_cast<lua_settableT>(Offsets::lua_settable);
+60 -4
View File
@@ -34,6 +34,7 @@
#include "spellevents.hpp" #include "spellevents.hpp"
#include "spellcast.hpp" #include "spellcast.hpp"
#include "misc_scripts.hpp" #include "misc_scripts.hpp"
#include "file_scripts.hpp"
#include "spell_scripts.hpp" #include "spell_scripts.hpp"
#include "player_scripts.hpp" #include "player_scripts.hpp"
#include "spellchannel.hpp" #include "spellchannel.hpp"
@@ -167,13 +168,15 @@ namespace Nampower {
std::unique_ptr<hadesmem::PatchDetour<TogglePetSlotAutocastT> > gTogglePetSlotAutocastDetour; std::unique_ptr<hadesmem::PatchDetour<TogglePetSlotAutocastT> > gTogglePetSlotAutocastDetour;
std::unique_ptr<hadesmem::PatchDetour<CGPetInfo_GetPetSpellActionT> > gCGPetInfo_GetPetSpellActionDetour; std::unique_ptr<hadesmem::PatchDetour<CGPetInfo_GetPetSpellActionT> > gCGPetInfo_GetPetSpellActionDetour;
std::unique_ptr<hadesmem::PatchDetour<CGGameUI_ShowCombatFeedbackT> > gCGGameUI_ShowCombatFeedbackDetour; std::unique_ptr<hadesmem::PatchDetour<CGGameUI_ShowCombatFeedbackT> > gCGGameUI_ShowCombatFeedbackDetour;
std::unique_ptr<hadesmem::PatchDetour<LoadScriptFunctionsT> > gGlueLoadScriptFunctionsDetour;
// Flags for one-time initialization // Flags for one-time initialization
std::once_flag loadFlag; std::once_flag loadFlag;
std::once_flag initHooksFlag; std::once_flag initHooksFlag;
// Forward declarations for hook functions // Forward declarations for hook functions
void LoadScriptFunctionsHook(hadesmem::PatchDetourBase *detour); void Player_LoadScriptFunctionsHook(hadesmem::PatchDetourBase *detour);
void Glue_LoadScriptFunctionsHook(hadesmem::PatchDetourBase *detour);
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId); void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId);
@@ -1524,8 +1527,10 @@ namespace Nampower {
process, Offsets::CGPetInfo_GetPetSpellAction, &CGPetInfo_GetPetSpellActionHook); process, Offsets::CGPetInfo_GetPetSpellAction, &CGPetInfo_GetPetSpellActionHook);
gCGGameUI_ShowCombatFeedbackDetour = createHook<CGGameUI_ShowCombatFeedbackT>( gCGGameUI_ShowCombatFeedbackDetour = createHook<CGGameUI_ShowCombatFeedbackT>(
process, Offsets::CGGameUI_ShowCombatFeedback, &CGGameUI_ShowCombatFeedbackHook); process, Offsets::CGGameUI_ShowCombatFeedback, &CGGameUI_ShowCombatFeedbackHook);
gLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::LoadScriptFunctions, gLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::Player_LoadScriptFunctions,
&LoadScriptFunctionsHook); &Player_LoadScriptFunctionsHook);
gGlueLoadScriptFunctionsDetour = createHook<LoadScriptFunctionsT>(process, Offsets::Glue_LoadScriptFunctions,
&Glue_LoadScriptFunctionsHook);
gCreateEventsDetour = createHook<FrameScript_CreateEventsT>(process, Offsets::FrameScript_CreateEvents, gCreateEventsDetour = createHook<FrameScript_CreateEventsT>(process, Offsets::FrameScript_CreateEvents,
&FrameScript_CreateEventsHook); &FrameScript_CreateEventsHook);
gSetEventCountDetour = createHook<FramescriptSetEventCountT>(process, Offsets::Framescript_SetEventCount, gSetEventCountDetour = createHook<FramescriptSetEventCountT>(process, Offsets::Framescript_SetEventCount,
@@ -1735,7 +1740,7 @@ namespace Nampower {
setEventCount(thisPtr, dummy_edx, count); setEventCount(thisPtr, dummy_edx, count);
} }
void LoadScriptFunctionsHook(hadesmem::PatchDetourBase *detour) { void Player_LoadScriptFunctionsHook(hadesmem::PatchDetourBase *detour) {
auto const loadScriptFunctions = detour->GetTrampolineT<LoadScriptFunctionsT>(); auto const loadScriptFunctions = detour->GetTrampolineT<LoadScriptFunctionsT>();
loadScriptFunctions(); loadScriptFunctions();
@@ -1896,6 +1901,57 @@ namespace Nampower {
char isAuraHidden[] = "IsAuraHidden"; char isAuraHidden[] = "IsAuraHidden";
RegisterLuaFunction(isAuraHidden, reinterpret_cast<uintptr_t *>(Script_IsAuraHidden)); RegisterLuaFunction(isAuraHidden, reinterpret_cast<uintptr_t *>(Script_IsAuraHidden));
char combatLogFlush[] = "CombatLogFlush";
RegisterLuaFunction(combatLogFlush, reinterpret_cast<uintptr_t *>(Script_CombatLogFlush));
char writeCustomFile[] = "WriteCustomFile";
RegisterLuaFunction(writeCustomFile, reinterpret_cast<uintptr_t *>(Script_WriteCustomFile));
char readCustomFile[] = "ReadCustomFile";
RegisterLuaFunction(readCustomFile, reinterpret_cast<uintptr_t *>(Script_ReadCustomFile));
char customFileExists[] = "CustomFileExists";
RegisterLuaFunction(customFileExists, reinterpret_cast<uintptr_t *>(Script_CustomFileExists));
char importFile[] = "ImportFile";
RegisterLuaFunction(importFile, reinterpret_cast<uintptr_t *>(Script_ImportFile));
char exportFile[] = "ExportFile";
RegisterLuaFunction(exportFile, reinterpret_cast<uintptr_t *>(Script_ExportFile));
char executeCustomLuaFile[] = "ExecuteCustomLuaFile";
RegisterLuaFunction(executeCustomLuaFile, reinterpret_cast<uintptr_t *>(Script_ExecuteCustomLuaFile));
}
void Glue_LoadScriptFunctionsHook(hadesmem::PatchDetourBase *detour) {
auto const loadScriptFunctions = detour->GetTrampolineT<LoadScriptFunctionsT>();
loadScriptFunctions();
DEBUG_LOG("Registering Glue Lua functions");
char writeCustomFile[] = "WriteCustomFile";
RegisterLuaFunction(writeCustomFile, reinterpret_cast<uintptr_t *>(Script_WriteCustomFile));
char readCustomFile[] = "ReadCustomFile";
RegisterLuaFunction(readCustomFile, reinterpret_cast<uintptr_t *>(Script_ReadCustomFile));
char customFileExists[] = "CustomFileExists";
RegisterLuaFunction(customFileExists, reinterpret_cast<uintptr_t *>(Script_CustomFileExists));
char importFile[] = "ImportFile";
RegisterLuaFunction(importFile, reinterpret_cast<uintptr_t *>(Script_ImportFile));
char exportFile[] = "ExportFile";
RegisterLuaFunction(exportFile, reinterpret_cast<uintptr_t *>(Script_ExportFile));
char executeCustomLuaFile[] = "ExecuteCustomLuaFile";
RegisterLuaFunction(executeCustomLuaFile, reinterpret_cast<uintptr_t *>(Script_ExecuteCustomLuaFile));
char encryptPassword[] = "EncryptPassword";
RegisterLuaFunction(encryptPassword, reinterpret_cast<uintptr_t *>(Script_EncryptPassword));
char encryptedServerLogin[] = "EncryptedServerLogin";
RegisterLuaFunction(encryptedServerLogin, reinterpret_cast<uintptr_t *>(Script_EncryptedServerLogin));
} }
void load() { void load() {
+1 -1
View File
@@ -26,7 +26,7 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 3; constexpr uint32_t MAJOR_VERSION = 3;
constexpr uint32_t MINOR_VERSION = 1; constexpr uint32_t MINOR_VERSION = 2;
constexpr uint32_t PATCH_VERSION = 0; constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_REGISTRYINDEX = -10000;
+10
View File
@@ -962,6 +962,16 @@ namespace Nampower {
return 1; return 1;
} }
uint32_t Script_CombatLogFlush(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
auto const slogFlush = reinterpret_cast<void(__stdcall *)(uint32_t)>(Offsets::SLogFlush);
auto const combatLogFileHandle = *reinterpret_cast<uint32_t *>(Offsets::CombatLogFileHandle);
slogFlush(combatLogFileHandle);
return 0;
}
uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr) { uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr) {
auto const original = detour->GetTrampolineT<GetGUIDFromNameT>(); auto const original = detour->GetTrampolineT<GetGUIDFromNameT>();
+1
View File
@@ -40,6 +40,7 @@ namespace Nampower {
uint32_t Script_SetMouseoverUnit(uintptr_t *luaState); uint32_t Script_SetMouseoverUnit(uintptr_t *luaState);
uint32_t Script_GetUnitGUID(uintptr_t *luaState); uint32_t Script_GetUnitGUID(uintptr_t *luaState);
uint32_t Script_IsAuraHidden(uintptr_t *luaState); uint32_t Script_IsAuraHidden(uintptr_t *luaState);
uint32_t Script_CombatLogFlush(uintptr_t *luaState);
uint32_t CSimpleFrame_GetNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); uint32_t CSimpleFrame_GetNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr); uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr);
+6 -1
View File
@@ -37,6 +37,7 @@ enum class Offsets : std::uint32_t {
ClntObjMgrObjectPtr = 0x00468460, ClntObjMgrObjectPtr = 0x00468460,
GetObjectPtr = 0x464870, GetObjectPtr = 0x464870,
GetActivePlayer = 0x468550, GetActivePlayer = 0x468550,
CGlueMgr_DefaultServerLogin = 0x0046AFB0,
GetUnitFromName = 0x00515940, GetUnitFromName = 0x00515940,
GetNamesFromGUID = 0x00000000, // TODO: set correct offset GetNamesFromGUID = 0x00000000, // TODO: set correct offset
SendUnitSignal = 0x00515e50, SendUnitSignal = 0x00515e50,
@@ -143,18 +144,22 @@ enum class Offsets : std::uint32_t {
CVarLookup = 0x0063DEC0, CVarLookup = 0x0063DEC0,
RegisterCVar = 0X0063DB90, RegisterCVar = 0X0063DB90,
SLogFlush = 0x0065A970,
CombatLogFileHandle = 0x00B50544,
GetClientConnection = 0X005AB490, GetClientConnection = 0X005AB490,
GetNetStats = 0X00537F20, GetNetStats = 0X00537F20,
ClientServices_Send = 0X005AB630, ClientServices_Send = 0X005AB630,
LoadScriptFunctions = 0x00490250, Player_LoadScriptFunctions = 0x00490250,
Glue_LoadScriptFunctions = 0x0046ABB0,
FrameScript_RegisterFunction = 0x00704120, FrameScript_RegisterFunction = 0x00704120,
FrameScript_CreateEvents = 0X00703D90, FrameScript_CreateEvents = 0X00703D90,
Framescript_SetEventCount = 0X007053B0, Framescript_SetEventCount = 0X007053B0,
Framescript_EventObject_Data = 0x00CEEF68, Framescript_EventObject_Data = 0x00CEEF68,
FrameScript_Execute = 0x00704CF0, FrameScript_Execute = 0x00704CF0,
FrameScript_ExecuteFile = 0x00704BC0,
// Existing script functions // Existing script functions
GetGUIDFromName = 0X00515970, GetGUIDFromName = 0X00515970,
+11 -1
View File
@@ -168,7 +168,17 @@ namespace Nampower {
uint64_t targetGUID = GetUnitGuidFromString(target); uint64_t targetGUID = GetUnitGuidFromString(target);
auto playerUnit = game::GetObjectPtr(game::ClntObjMgrGetActivePlayerGuid()); const auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
if (playerGuid == 0) {
lua_error(luaState, "IsSpellInRange: active player not available");
return 0;
}
auto playerUnit = game::GetObjectPtr(playerGuid);
if (!playerUnit) {
lua_error(luaState, "IsSpellInRange: active player unit not available");
return 0;
}
auto const RangeCheckSelected = reinterpret_cast<RangeCheckSelectedT>(Offsets::RangeCheckSelected); auto const RangeCheckSelected = reinterpret_cast<RangeCheckSelectedT>(Offsets::RangeCheckSelected);
auto const result = RangeCheckSelected(playerUnit, spell, targetGUID, '\0'); auto const result = RangeCheckSelected(playerUnit, spell, targetGUID, '\0');