mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-21 23:26:54 +00:00
add new file commands, other small fixes
This commit is contained in:
+1
-1
@@ -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}")
|
||||||
|
|||||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user