Compare commits

..

78 Commits

Author SHA1 Message Date
brainiac 11a8e46ee8 CMake cleanup (#5082)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-05-17 15:05:46 -07:00
KimLS 7d98b10c18 Merge branch 'master' into develop
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-05-07 20:56:48 -07:00
dannuic 65330af50e Validating packets (starting with loot) (#5079) 2026-05-07 20:56:10 -07:00
Knightly ef6dfe0469 Load openssl libs from executable directory and update logging (#5078)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-05-07 20:25:01 -07:00
Dan d7e010a3ec strings: refactor Money and add MoneyShort (#5075) 2026-05-07 20:24:15 -07:00
dannuic 99f99c8b8e Fixed the buff database entries and updated database migrations (#5076)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-05-05 21:23:43 -07:00
dannuic e5ce882b9d Updated the database scripts and re-synced the database bindings (#5077)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-05-03 21:37:24 -07:00
dannuic 18df055f16 Add Buff infrastructure to go through different logic paths from different patches (#5073)
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2026-05-02 21:53:26 -07:00
KimLS c253734c57 Merge branch 'tob_patch' into develop
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-30 00:06:37 -07:00
nytmyr 85c3255568 [Bots] Command add fix for spelltypeids/spelltypenames (#5074)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-28 23:59:19 -07:00
KimLS 9cc3d492ce Merge branch 'master' into develop
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-28 20:39:33 -07:00
KimLS c6ddf300a4 Add back msvc step with a maintained fork
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2026-04-28 20:15:06 -07:00
KimLS 42d6004467 Remove step i think isn't required for windows, change linux path 2026-04-28 20:00:01 -07:00
KimLS cabe06ea92 Revert linux part for now 2026-04-28 19:42:26 -07:00
KimLS 4abd9c1b40 Some build changes, may or may not fix cache
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2026-04-28 18:01:20 -07:00
KimLS accda849ac Merge fix
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2026-04-28 00:15:35 -07:00
KimLS f72bbab0e9 Change paths to try to get this working
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2026-04-28 00:04:40 -07:00
KimLS 9647a5b82c Add develop branch 2026-04-27 23:20:29 -07:00
KimLS 84a90cef23 Linux build doesn't like the way we hash this 2026-04-27 22:22:54 -07:00
KimLS a8db057440 Add vcpkg cache, hopefully works. 2026-04-27 22:19:15 -07:00
dannuic 743fd45b17 Added component-based patch system (#5070)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-25 23:29:12 -07:00
Knightly 6d6cc8ca95 Update openssl calls to use EVP interface (#5072)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-25 16:49:03 -07:00
brainiac 6694281f22 Fix gcc warnings (#5071)
Build / Linux (push) Waiting to run
Build / Windows (push) Waiting to run
2026-04-24 23:05:32 -07:00
Knightly 0ada77f340 Login Updates for TOB (#5068)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-20 22:45:58 -07:00
Alex 9c8107ce96 Merge pull request #5069 from Knightly1/update_ci
CI tob_patch branch
2026-04-20 21:13:28 -07:00
Knightly 4581059e78 CI tob_patch branch 2026-04-20 16:29:00 -10:00
KimLS dceec36fad Merge branch 'master' into tob_patch 2026-04-20 18:41:45 -07:00
Alex 3035e906fe Merge pull request #5067 from dannuic/tob_fullylogin
Validated up to OP_SimpleMessage
2026-04-20 18:40:08 -07:00
xJeris 5dc093fe5e Add Multiple Mercenary Hire Functionality (#5059)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-20 17:52:23 -07:00
ltroylove 758774b0bf fix: only dismiss pet summoned by the fading familiar buff (#5063) 2026-04-20 17:51:47 -07:00
ltroylove 1958a12bc7 fix: include base skill damage in FlyingKick, Kick, RoundKick, and Bash (#5061) 2026-04-20 17:51:17 -07:00
ltroylove 348094b881 fix: correct off-by-one in GetSpellLevel for Berserker class (#5060)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 17:50:36 -07:00
dannuic 0e0162edc0 Added spell links to interrupt and fizzle messages 2026-04-19 23:07:02 -06:00
dannuic af06fb703c confirmed consider 2026-04-18 12:37:56 -06:00
dannuic c5d089de68 Validated message and interrupt packets, needs refactor for full functionality 2026-04-18 12:08:54 -06:00
dannuic b8ee811ac6 Fixed unmemming spells on cast 2026-04-18 06:53:37 -06:00
dannuic 08cdd8234d Validated to OP_CastSpell -- still causes a spell to unmem after cast 2026-04-18 00:08:18 -06:00
dannuic 3bb7f94713 Fixed memorization for parity with RoF2 2026-04-17 23:35:34 -06:00
dannuic ec5a9d0bd4 Validated up to OP_MemorizeSpell (still needs testing in client) 2026-04-17 18:07:39 -06:00
dannuic 2da6d3f37c Fixed item index mapping 2026-04-17 14:57:35 -06:00
dannuic 6a7baf8f1c Validated through OP_SetServerFilter 2026-04-17 11:56:46 -06:00
dannuic a8e3ab41e1 Validated up to OP_ExpUpdate 2026-04-16 17:13:17 -06:00
dannuic fe4146050f Validated up to OP_UpdateAA 2026-04-16 16:51:41 -06:00
dannuic 36ea946255 Attempt to fix opcodes formatting 2026-04-16 16:28:08 -06:00
dannuic f29d87aced Validated up to OP_WorldObjectsSent 2026-04-16 15:59:54 -06:00
KimLS 767f04731b Merge fix 2026-04-15 22:06:39 -07:00
Alex 8e7964b835 Merge pull request #5066 from dannuic/tob_loginpatches
TOB packet work through zone in
2026-04-15 22:03:39 -07:00
dannuic a9333fb51b Added padding into client position struct 2026-04-15 22:52:21 -06:00
dannuic 27ad857ee5 Fixed spawn position struct 2026-04-15 22:51:23 -06:00
dannuic 5549daedb1 Some ZoneChange work 2026-04-15 21:10:41 -05:00
dannuic b5cc8dfab1 Verified and corrected packets through character select 2026-04-14 13:39:28 -05:00
dannuic 865f619e21 Validated and filled out SendMaxCharacters 2026-04-13 22:56:49 -06:00
dannuic a4785d30e0 Added some comments for trivial opcodes 2026-04-13 17:48:11 -05:00
dannuic a54711817d Validated EnterWorld (in & out) 2026-04-13 17:34:21 -05:00
dannuic 139575661d Validated SendCharInfo 2026-04-13 17:10:38 -05:00
dannuic 8dd24f4a70 Updated comments for LogServer 2026-04-13 15:47:13 -05:00
KimLS 517d9419a7 Update opcode tracker 2026-04-12 17:03:29 -07:00
KimLS a789b22fc7 Change from obr to tob dir, added a status doc 2026-04-12 13:16:23 -07:00
KimLS 6e1fe45090 Merge branch 'tob_patch' of https://github.com/EQEmu/EQEmu into tob_patch 2026-04-09 20:41:53 -07:00
KimLS 492d848f6a adjust offsets by 2 in OP_LogServer on tob, I had done it simply from assembly and misread the register and it's off by 2 for each one. 2026-04-09 20:41:40 -07:00
Alex ce5e216be9 Merge pull request #5058 from dannuic/tob_zonein
TOB zonein + all opcodes for TOB
2026-04-09 00:01:53 -07:00
dannuic 48e0847f21 renamed straggler file for tob 2026-04-08 23:06:09 -05:00
dannuic 7f42add39b Finished up player profile 2026-04-08 22:13:37 -05:00
dannuic 49161a618f Added up to DoN currency (not inclusive) 2026-04-08 17:57:08 -05:00
dannuic aac7bbf48a Fixed enough to allow zoning 2026-04-08 15:57:37 -05:00
dannuic 2c4d82f1b9 Fixed packed structs 2026-04-08 10:26:36 -05:00
dannuic dea5031d83 WIP attempts at zoning in 2026-04-07 23:35:16 -05:00
KimLS 30c9c6317f Update OP_LogServer
Fix the source group matching in common lib
Added a couple packet structure documentations I had looked at.
2026-04-06 20:51:52 -07:00
Alex c7eea72997 Merge pull request #5057 from dannuic/tob_charactercreate
Implemented through character creation packets
2026-04-06 14:51:18 -07:00
Knightly ba2ca5eada Lua: Header Matching and Cleanup (#5055)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-06 14:48:46 -07:00
dannuic 28e6ef29d4 Corrected missing find/replace for steam_latest 2026-04-06 16:23:06 -05:00
dannuic a6f4438c0d Removed the _t for consistency 2026-04-06 16:15:20 -05:00
dannuic e5a111d8d8 Implemented through character creation packets 2026-04-06 16:09:59 -05:00
brainiac 491b1edd12 Warning fixes, general cleanup (#5053)
Build / Linux (push) Has been cancelled
Build / Windows (push) Has been cancelled
2026-04-04 23:27:21 -07:00
KimLS abbaf6f9a1 update opcodes 2026-04-04 16:47:41 -07:00
KimLS ccdc9f2e43 fix dumb compile errors from my hand merge 2026-04-04 15:48:43 -07:00
KimLS a9effc7bac A couple laurion renames i missed 2026-04-04 15:28:59 -07:00
KimLS a2b3b36cf1 WIP, porting old laurion changes to tob 2026-04-04 15:11:21 -07:00
389 changed files with 17596 additions and 7448 deletions
+1
View File
@@ -20,3 +20,4 @@
*.css text
*.js text
*.types text
*.pdf binary
+30 -6
View File
@@ -3,8 +3,8 @@ on:
push:
branches:
- master
- develop
pull_request:
jobs:
linux:
name: Linux
@@ -25,10 +25,21 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential ninja-build ccache uuid-dev
- name: Restore vcpkg Cache
uses: actions/cache@v5
with:
path: |
build/vcpkg_installed
submodules/vcpkg/downloads
key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }}
restore-keys: |
${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }}-
${{ runner.os }}-vcpkg-
- name: Configure
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DEQEMU_BUILD_TESTS=ON \
@@ -47,6 +58,9 @@ jobs:
windows:
name: Windows
runs-on: windows-latest
env:
VCPKG_DOWNLOADS: ${{ github.workspace }}\submodules\vcpkg\downloads
VCPKG_BINARY_SOURCES: 'clear;files,${{ github.workspace }}\vcpkg_archives,readwrite'
steps:
- name: Checkout source
uses: actions/checkout@v5
@@ -57,15 +71,25 @@ jobs:
run: git config --global core.longpaths true
- name: Setup MSVC environment
uses: ilammy/msvc-dev-cmd@v1
uses: TheMrMilchmann/setup-msvc-dev@v4
with:
arch: x64
- name: Restore vcpkg Cache
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_DOWNLOADS }}
${{ github.workspace }}/vcpkg_archives
key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }}
restore-keys: |
${{ runner.os }}-vcpkg-
- name: Configure
shell: pwsh
run: |
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_BUILD_TYPE=Release `
-DEQEMU_BUILD_TESTS=ON `
-DEQEMU_BUILD_LOGIN=ON `
-DEQEMU_BUILD_LUA=ON `
@@ -74,8 +98,8 @@ jobs:
- name: Build
shell: pwsh
run: cmake --build build --config RelWithDebInfo --target ALL_BUILD -- /m
run: cmake --build build --config Release --target ALL_BUILD -- /m
- name: Test
working-directory: build
run: ./bin/RelWithDebInfo/tests.exe
run: ./bin/Release/tests.exe
+23 -18
View File
@@ -18,43 +18,48 @@ if(NOT CMAKE_TOOLCHAIN_FILE)
endif()
endif()
# Uncomment to generate just the standard Debug and Release configurations
#set(CMAKE_BUILD_TYPE "Debug")
#set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(dummy "${CMAKE_C_COMPILER}")
project(EQEmu
VERSION 24.10.3
LANGUAGES CXX
LANGUAGES CXX
)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
#explicitly set CMP0167 for Find Boost
if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
cmake_policy(SET CMP0167 NEW)
endif()
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type")
endif()
option(EQEMU_BUILD_PCH "Build with precompiled headers" ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
option(EQEMU_BUILD_PCH "Build with precompiled headers (Windows)" ON)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
if(MSVC)
add_compile_options(/bigobj)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS NOMINMAX CRASH_LOGGING _HAS_AUTO_PTR_ETC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
# Always build with debug symbols
add_compile_options("$<$<CONFIG:RELEASE>:/Zi>")
add_link_options("$<$<CONFIG:RELEASE>:/DEBUG /OPT:REF /OPT:ICF>")
option(EQEMU_DISABLE_MSVC_WARNINGS "Disable MSVC compile warnings." ON)
if(EQEMU_DISABLE_MSVC_WARNINGS)
add_compile_options(/W0 /wd4005 /wd4996 /nologo /Os)
endif()
add_compile_options(/bigobj /MP)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS NOMINMAX WIN32_LEAN_AND_MEAN CRASH_LOGGING)
option(EQEMU_MSVC_DEBUG_CRT "Use the debug CRT on MSVC debug builds" OFF)
if(EQEMU_MSVC_DEBUG_CRT)
add_compile_definitions(_CRT_DBG_MAP_ALLOC)
endif()
else()
add_compile_options("$<$<CONFIG:RELEASE>:-g>")
add_compile_definitions(HAS_UNION_SEMUN)
endif()
@@ -71,7 +76,7 @@ if(UNIX)
endif()
endif()
find_package(Boost REQUIRED COMPONENTS dynamic_bitset foreach tuple)
find_package(Boost REQUIRED COMPONENTS dynamic_bitset foreach tuple CONFIG REQUIRED)
find_package(cereal CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
+73 -48
View File
@@ -1,49 +1,74 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 19,
"patch": 0
},
"configurePresets": [
{
"name": "linux-debug",
"displayName": "Linux Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
"EQEMU_BUILD_LOGIN": "ON",
"EQEMU_BUILD_TESTS": "ON",
"EQEMU_ADD_PROFILER": "ON"
}
},
{
"name": "linux-release",
"displayName": "Linux Release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
"EQEMU_BUILD_LOGIN": "ON"
}
},
{
"name": "win-msvc",
"displayName": "Windows MSVC (VS 2022)",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build/${presetName}",
"architecture": { "value": "x64" },
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Debug;Release",
"EQEMU_BUILD_LOGIN": "ON",
"EQEMU_BUILD_TESTS": "ON"
}
}
]
}
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "linux",
"displayName": "Linux-Ninja",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"EQEMU_BUILD_TESTS": "ON"
}
},
{
"name": "linux-clang",
"displayName": "Linux-Ninja (Clang)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"EQEMU_BUILD_TESTS": "ON",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
},
{
"name": "linux-ccache",
"displayName": "Linux-Ninja (Ccache)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
"EQEMU_BUILD_TESTS": "ON"
}
},
{
"name": "linux-clang-ccache",
"displayName": "Linux-Ninja (Clang, Ccache)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
"EQEMU_BUILD_TESTS": "ON",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
},
{
"name": "win-msvc-2022",
"displayName": "Windows MSVC (VS 2022)",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build",
"architecture": { "value": "x64" },
"cacheVariables": {
"EQEMU_BUILD_TESTS": "ON"
}
},
{
"name": "win-msvc-2026",
"displayName": "Windows MSVC (VS 2026)",
"generator": "Visual Studio 18 2026",
"binaryDir": "${sourceDir}/build",
"architecture": { "value": "x64" },
"cacheVariables": {
"EQEMU_BUILD_TESTS": "ON"
}
}
]
}
-2
View File
@@ -1,4 +1,2 @@
cmake_minimum_required(VERSION 3.20)
add_subdirectory(import)
add_subdirectory(export)
-3
View File
@@ -1,5 +1,3 @@
cmake_minimum_required(VERSION 3.20.0)
set(export_sources
main.cpp
)
@@ -12,7 +10,6 @@ add_executable(export_client_files ${export_sources} ${export_headers})
install(TARGETS export_client_files RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
target_link_libraries(export_client_files common)
target_include_directories(export_client_files PRIVATE ../..)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set_property(TARGET export_client_files PROPERTY FOLDER executables/tools)
-3
View File
@@ -1,5 +1,3 @@
cmake_minimum_required(VERSION 3.20.0)
set(import_sources
main.cpp
)
@@ -12,7 +10,6 @@ add_executable(import_client_files ${import_sources} ${import_headers})
install(TARGETS import_client_files RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
target_link_libraries(import_client_files common)
target_include_directories(import_client_files PRIVATE ../..)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set_property(TARGET import_client_files PROPERTY FOLDER executables/tools)
+757 -768
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -359,7 +359,7 @@ public:
BOOL Publics; // contains public symbols
};
*/
typedef struct IMAGEHLP_MODULE64_V2 {
struct IMAGEHLP_MODULE64_V2 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
+1 -1
View File
@@ -134,7 +134,7 @@ protected:
CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
} CallstackEntry;
typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
+14 -24
View File
@@ -19,44 +19,34 @@
#include "common/misc.h"
#include "common/packet_dump.h"
BasePacket::BasePacket(const unsigned char *buf, uint32 len)
BasePacket::BasePacket(const unsigned char* buf, size_t len)
{
pBuffer=nullptr;
size=0;
_wpos = 0;
_rpos = 0;
timestamp.tv_sec = 0;
if (len>0) {
size=len;
pBuffer= new unsigned char[len];
if (len > 0) {
size = static_cast<uint32>(len);
pBuffer = new unsigned char[len];
if (buf) {
memcpy(pBuffer,buf,len);
} else {
memset(pBuffer,0,len);
memcpy(pBuffer, buf, len);
}
else {
memset(pBuffer, 0, len);
}
}
}
BasePacket::BasePacket(SerializeBuffer &buf)
BasePacket::BasePacket(SerializeBuffer&& buf)
: pBuffer(std::exchange(buf.m_buffer, nullptr))
{
pBuffer = buf.m_buffer;
buf.m_buffer = nullptr;
size = buf.m_pos;
buf.m_pos = 0;
// We are essentially taking ownership of this serialize buffer.
size = static_cast<uint32>(std::exchange(buf.m_pos, 0));
buf.m_capacity = 0;
_wpos = 0;
_rpos = 0;
timestamp.tv_sec = 0;
}
BasePacket::~BasePacket()
{
if (pBuffer)
delete[] pBuffer;
pBuffer=nullptr;
delete[] pBuffer;
pBuffer = nullptr;
}
void BasePacket::build_raw_header_dump(char *buffer, uint16 seq) const
{
if (timestamp.tv_sec) {
+23 -15
View File
@@ -23,14 +23,26 @@
#include <cstdio>
class BasePacket {
class BasePacket
{
protected:
BasePacket() = default;
BasePacket(const unsigned char* buf, size_t len);
BasePacket(SerializeBuffer&& buf);
virtual ~BasePacket();
public:
unsigned char *pBuffer;
uint32 size, _wpos, _rpos;
uint32 src_ip,dst_ip;
uint16 src_port,dst_port;
uint32 priority;
timeval timestamp;
unsigned char* pBuffer = nullptr;
uint32 size = 0;
uint32 _wpos = 0;
uint32 _rpos = 0;
uint32 src_ip = 0;
uint32 dst_ip = 0;
uint16 src_port = 0;
uint16 dst_port = 0;
uint32 priority = 0;
timeval timestamp{};
virtual void build_raw_header_dump(char *buffer, uint16 seq=0xffff) const;
virtual void build_header_dump(char *buffer) const;
@@ -40,16 +52,17 @@ public:
void setSrcInfo(uint32 sip, uint16 sport) { src_ip=sip; src_port=sport; }
void setDstInfo(uint32 dip, uint16 dport) { dst_ip=dip; dst_port=dport; }
void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { timestamp.tv_sec=ts_sec; timestamp.tv_usec=ts_usec; }
void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { timestamp.tv_sec = ts_sec; timestamp.tv_usec = ts_usec; }
void copyInfo(const BasePacket *p) { src_ip=p->src_ip; src_port=p->src_port; dst_ip=p->dst_ip; dst_port=p->dst_port; timestamp.tv_sec=p->timestamp.tv_sec; timestamp.tv_usec=p->timestamp.tv_usec; }
inline bool operator<(const BasePacket &rhs) {
return (timestamp.tv_sec < rhs.timestamp.tv_sec || (timestamp.tv_sec==rhs.timestamp.tv_sec && timestamp.tv_usec < rhs.timestamp.tv_usec));
return (timestamp.tv_sec < rhs.timestamp.tv_sec || (timestamp.tv_sec == rhs.timestamp.tv_sec && timestamp.tv_usec < rhs.timestamp.tv_usec));
}
void WriteUInt8(uint8 value) { *(uint8 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint8); }
void WriteUInt32(uint32 value) { *(uint32 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint32); }
void WriteUInt64(uint64 value) { *(uint64 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint64); }
void WriteSInt16(int32 value) { *(int16*)(pBuffer + _wpos) = value; _wpos += sizeof(int16); }
void WriteUInt16(uint32 value) { *(uint16 *)(pBuffer + _wpos) = value; _wpos += sizeof(uint16); }
void WriteSInt32(int32 value) { *(int32 *)(pBuffer + _wpos) = value; _wpos += sizeof(int32); }
void WriteFloat(float value) { *(float *)(pBuffer + _wpos) = value; _wpos += sizeof(float); }
@@ -68,17 +81,12 @@ public:
void ReadString(char *str) { uint32 len = static_cast<uint32>(strlen((char *)(pBuffer + _rpos))) + 1; memcpy(str, pBuffer + _rpos, len); _rpos += len; }
void ReadString(std::string &str) { str = reinterpret_cast<char *>(pBuffer + _rpos); _rpos += str.length() + 1; }
void ReadString(char *str, uint32 Offset, uint32 MaxLength) const;
void ReadLengthString(std::string& str) { uint32 len = *(uint32*)(pBuffer + _rpos); _rpos += sizeof(uint32); str.resize(len); memcpy(&str[0], pBuffer + _rpos, len); _rpos += len; }
uint32 GetWritePosition() { return _wpos; }
uint32 GetReadPosition() { return _rpos; }
void SetWritePosition(uint32 Newwpos) { _wpos = Newwpos; }
void SetReadPosition(uint32 Newrpos) { _rpos = Newrpos; }
protected:
virtual ~BasePacket();
BasePacket() { pBuffer=nullptr; size=0; _wpos = 0; _rpos = 0; }
BasePacket(const unsigned char *buf, const uint32 len);
BasePacket(SerializeBuffer &buf);
};
extern void DumpPacketHex(const BasePacket* app);
+3
View File
@@ -71,6 +71,9 @@ namespace Class {
constexpr uint8 FellowshipMaster = 69;
constexpr uint8 AlternateCurrencyMerchant = 70;
constexpr uint8 MercenaryLiaison = 71;
constexpr uint8 RealEstateMerchant = 72;
constexpr uint8 LoyaltyMerchant = 73;
constexpr uint8 TributeMaster2 = 74;
constexpr uint8 PLAYER_CLASS_COUNT = 16;
constexpr uint16 ALL_CLASSES_BITMASK = 65535;
+3 -6
View File
@@ -23,12 +23,9 @@
namespace EQEmuCommand {
std::map<std::string, void (*)(
int argc,
char **argv,
argh::parser &cmd,
std::string &description
)> function_map;
using CommandFunction = void(*)(int argc, char** argv, argh::parser& cmd, std::string& description);
std::map<std::string, CommandFunction> function_map;
/**
* @param cmd
+1 -1
View File
@@ -51,7 +51,7 @@
# include <unistd.h>
#elif defined(TERMCOLOR_OS_WINDOWS)
# include <io.h>
# include <windows.h>
# include "common/platform/win/include_windows.h"
#endif
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#if defined(_MSC_VER)
#define PUSH_DISABLE_DEPRECATED_WARNINGS() __pragma(warning(push)) \
__pragma(warning(disable:4996))
#define POP_DISABLE_DEPRECATED_WARNINGS() __pragma(warning(pop))
#elif defined(__GNUC__) || defined(__clang__)
#define PUSH_DISABLE_DEPRECATED_WARNINGS() _Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#define POP_DISABLE_DEPRECATED_WARNINGS() _Pragma("GCC diagnostic pop")
#else
#define PUSH_DISABLE_DEPRECATED_WARNINGS()
#define POP_DISABLE_DEPRECATED_WARNINGS()
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define UNREACHABLE() __assume(0)
#else
#define UNREACHABLE() __builtin_unreachable()
#endif
-146
View File
@@ -1,146 +0,0 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "condition.h"
#ifdef _WINDOWS
Condition::Condition()
{
m_events[SignalEvent] = CreateEvent (nullptr, // security
FALSE, // is auto-reset event?
FALSE, // is signaled initially?
nullptr); // name
m_events[BroadcastEvent] = CreateEvent (nullptr, // security
TRUE, // is auto-reset event?
FALSE, // is signaled initially?
nullptr); // name
m_waiters = 0;
InitializeCriticalSection(&CSMutex);
}
Condition::~Condition()
{
DeleteCriticalSection(&CSMutex);
CloseHandle(m_events[SignalEvent]);
CloseHandle(m_events[BroadcastEvent]);
}
void Condition::Signal()
{
EnterCriticalSection(&CSMutex);
if(m_waiters > 0)
SetEvent(m_events[SignalEvent]);
LeaveCriticalSection(&CSMutex);
}
void Condition::SignalAll()
{
EnterCriticalSection(&CSMutex);
if(m_waiters > 0)
SetEvent(m_events[BroadcastEvent]);
LeaveCriticalSection(&CSMutex);
}
void Condition::Wait()
{
EnterCriticalSection(&CSMutex);
m_waiters++;
LeaveCriticalSection(&CSMutex);
int result = WaitForMultipleObjects (_eventCount, m_events, FALSE, INFINITE);
EnterCriticalSection(&CSMutex);
m_waiters--;
//see if we are the last person waiting on the condition, and there was a broadcast
//if so, we need to reset the broadcast event.
if(m_waiters == 0 && result == (WAIT_OBJECT_0+BroadcastEvent))
ResetEvent(m_events[BroadcastEvent]);
LeaveCriticalSection(&CSMutex);
}
#else
#include <pthread.h>
#include <sys/time.h>
#include <errno.h>
Condition::Condition()
{
pthread_cond_init(&cond,nullptr);
pthread_mutex_init(&mutex,nullptr);
}
void Condition::Signal()
{
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
void Condition::SignalAll()
{
pthread_mutex_lock(&mutex);
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
void Condition::Wait()
{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
}
/*
I commented this specifically because I think it might be very
difficult to write a windows counterpart to it, so I would like
to discourage its use until we can confirm that it can be reasonably
implemented on windows.
bool Condition::TimedWait(unsigned long usec)
{
struct timeval now;
struct timespec timeout;
int retcode=0;
pthread_mutex_lock(&mutex);
gettimeofday(&now,nullptr);
now.tv_usec+=usec;
timeout.tv_sec = now.tv_sec + (now.tv_usec/1000000);
timeout.tv_nsec = (now.tv_usec%1000000) *1000;
//cout << "now=" << now.tv_sec << "."<<now.tv_usec << endl;
//cout << "timeout=" << timeout.tv_sec << "."<<timeout.tv_nsec << endl;
retcode=pthread_cond_timedwait(&cond,&mutex,&timeout);
pthread_mutex_unlock(&mutex);
return retcode!=ETIMEDOUT;
}
*/
Condition::~Condition()
{
pthread_mutex_lock(&mutex);
pthread_cond_destroy(&cond);
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
}
#endif
+2 -2
View File
@@ -132,7 +132,7 @@ void WorldContentService::SetContentFlags(const std::vector<ContentFlagsReposito
bool WorldContentService::IsContentFlagEnabled(const std::string &content_flag)
{
for (auto &f: GetContentFlags()) {
if (f.flag_name == content_flag && f.enabled == true) {
if (f.flag_name == content_flag && f.enabled == 1) {
return true;
}
}
@@ -147,7 +147,7 @@ bool WorldContentService::IsContentFlagEnabled(const std::string &content_flag)
bool WorldContentService::IsContentFlagDisabled(const std::string &content_flag)
{
for (auto &f: GetContentFlags()) {
if (f.flag_name == content_flag && f.enabled == false) {
if (f.flag_name == content_flag && f.enabled == 0) {
return true;
}
}
+19 -1
View File
@@ -99,6 +99,24 @@ uint32 CRC32::GenerateNoFlip(const uint8* buf, uint32 bufsize) {
return Update(buf, bufsize);
}
unsigned long CRC32::GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at)
{
unsigned long data;
unsigned long check = 0xffffffff;
for (uint32 i = start_at; i < in_length; i++)
{
data = in_data[i];
data = data ^ (check);
data = data & 0x000000ff;
check = check >> 8;
data = CRC32Table[data];
check = check ^ data;
}
return check;
}
void CRC32::SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at)
{
unsigned long data;
@@ -125,6 +143,6 @@ uint32 CRC32::Update(const uint8* buf, uint32 bufsize, uint32 crc32var) {
return crc32var;
}
inline void CRC32::Calc(const uint8 byte, uint32& crc32var) {
void CRC32::Calc(const uint8 byte, uint32& crc32var) {
crc32var = ((crc32var) >> 8) ^ CRC32Table[(byte) ^ ((crc32var) & 0x000000FF)];
}
+1
View File
@@ -25,6 +25,7 @@ public:
static uint32 Generate(const uint8* buf, uint32 bufsize);
static uint32 GenerateNoFlip(const uint8* buf, uint32 bufsize); // Same as Generate(), but without the ~
static void SetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at=4);
static unsigned long GetEQChecksum(uchar* in_data, uint32 in_length, uint32 start_at = 4);
// Multiple buffer CRC32
static uint32 Update(const uint8* buf, uint32 bufsize, uint32 crc32 = 0xFFFFFFFF);
+2 -2
View File
@@ -728,7 +728,7 @@ bool Database::LoadVariables()
return true;
}
LockMutex lock(&Mvarcache);
std::scoped_lock lock(Mvarcache);
for (const auto& e : l) {
varcache.last_update = std::time(nullptr);
@@ -747,7 +747,7 @@ bool Database::LoadVariables()
bool Database::GetVariable(const std::string& name, std::string& value)
{
LockMutex lock(&Mvarcache);
std::scoped_lock lock(Mvarcache);
if (name.empty()) {
return false;
+3 -4
View File
@@ -20,13 +20,12 @@
#include "common/dbcore.h"
#include "common/eq_packet_structs.h"
#include "common/eqemu_logsys.h"
#include "common/linked_list.h"
#include "common/types.h"
#include <cmath>
#include <map>
#include <mutex>
#include <string>
#include <vector>
#include <map>
#define AUTHENTICATION_TIMEOUT 60
#define INVALID_ID 0xFFFFFFFF
@@ -265,7 +264,7 @@ public:
uint64_t GetNextTableId(const std::string& table_name);
private:
Mutex Mvarcache;
std::mutex Mvarcache;
VarCache_Struct varcache;
/* Groups, utility methods. */
+102 -146
View File
@@ -28,6 +28,7 @@
#include "common/strings.h"
#include <filesystem>
#include <ranges>
constexpr int BREAK_LENGTH = 70;
@@ -73,35 +74,38 @@ void DatabaseUpdate::CheckDbUpdates()
return;
}
if (UpdateManifest(manifest_entries, v.server_database_version, b.server_database_version)) {
int server_database_version = UpdateManifest(manifest_entries, v.server_database_version, b.server_database_version);
if (server_database_version > v.server_database_version) {
LogInfo(
"Updates ran successfully, setting database version to [{}] from [{}]",
b.server_database_version,
server_database_version,
v.server_database_version
);
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", b.server_database_version));
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", server_database_version));
}
if (UpdateManifest(manifest_entries_custom, v.custom_database_version, b.custom_database_version)) {
int custom_database_version = UpdateManifest(manifest_entries_custom, v.custom_database_version, b.custom_database_version);
if (custom_database_version > v.server_database_version) {
LogInfo(
"Updates ran successfully, setting database version to [{}] from [{}]",
b.custom_database_version,
custom_database_version,
v.custom_database_version
);
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `custom_version` = {}", b.custom_database_version));
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `custom_version` = {}", custom_database_version));
}
if (b.bots_database_version > 0) {
if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) {
int bots_database_version = UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version);
if (bots_database_version > v.bots_database_version) {
LogInfo(
"Updates ran successfully, setting database version to [{}] from [{}]",
b.bots_database_version,
bots_database_version,
v.bots_database_version
);
m_database->QueryDatabase(
fmt::format(
"UPDATE `db_version` SET `bots_version` = {}",
b.bots_database_version
bots_database_version
)
);
}
@@ -131,7 +135,7 @@ std::string DatabaseUpdate::GetQueryResult(const ManifestEntry& e)
return Strings::Join(result_lines, "\n");
}
bool DatabaseUpdate::ShouldRunMigration(ManifestEntry &e, std::string query_result)
bool DatabaseUpdate::ShouldRunMigration(const ManifestEntry& e, std::string& query_result)
{
std::string r = Strings::Trim(query_result);
if (e.condition == "contains") {
@@ -163,53 +167,52 @@ bool is_atty()
#endif
}
// return true if we ran updates
bool DatabaseUpdate::UpdateManifest(
std::vector<ManifestEntry> entries,
std::string DisplayPrompt(const std::string& prompt)
{
std::string input;
if (is_atty()) {
LogInfo("{} (Timeout 60s)", prompt);
// user input
bool gave_input = false;
time_t start_time = time(nullptr);
time_t wait_time_seconds = 60;
// spawn a concurrent thread that waits for input from std::cin
std::thread t1(
[&]() {
std::cin >> input;
gave_input = true;
}
);
t1.detach();
// check the inputReceived flag once every 50ms for 10 seconds
while (time(nullptr) < start_time + wait_time_seconds && !gave_input) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
return Strings::Trim(input);
}
// return last successful version updated
int DatabaseUpdate::UpdateManifest(
std::vector<ManifestEntry>& entries,
int version_low,
int version_high
)
{
std::vector<int> missing_migrations = {};
int latest_version = version_low;
if (version_low != version_high) {
// assume at this point that we have a migration to do because there is a version number difference. If a
// migration for a specific manifest entry does not happen because of a missing test, then log it and
// continue (the assumption here is that the user has manually fixed the database at this point). If a force
// interactive flag is set, then stop for each query. Fail the migration if the user says no or it times out
// because it means the database isn't going to have a correct state to continue. Start with backing up the
// database as per user options.
EQEmuLogSys::Instance()->DisableMySQLErrorLogs();
bool force_interactive = false;
for (int version = version_low + 1; version <= version_high; ++version) {
for (auto &e: entries) {
if (e.version == version) {
bool has_migration = true;
std::string r = GetQueryResult(e);
if (ShouldRunMigration(e, r)) {
has_migration = false;
missing_migrations.emplace_back(e.version);
}
std::string prefix = fmt::format(
"[{}]",
has_migration ? "ok" : "missing"
);
LogInfo(
"[{}] {:>10} | [{}]",
e.version,
prefix,
e.description
);
if (!has_migration && e.force_interactive) {
force_interactive = true;
}
}
}
}
EQEmuLogSys::Instance()->EnableMySQLErrorLogs();
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
if (!missing_migrations.empty() && m_skip_backup) {
LogInfo("Skipping database backup");
}
else if (!missing_migrations.empty()) {
if (!m_skip_backup) {
LogInfo("Automatically backing up database before applying updates");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
auto s = DatabaseDumpService();
@@ -217,120 +220,73 @@ bool DatabaseUpdate::UpdateManifest(
s.SetDumpWithCompression(true);
s.DatabaseDump();
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
} else {
LogInfo("Skipping database backup");
}
if (!missing_migrations.empty()) {
LogInfo("Running database migrations. Please wait...");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
}
LogInfo("Running database migrations. Please wait...");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
if (force_interactive && !std::getenv("FORCE_INTERACTIVE")) {
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
LogInfo("Some migrations require user input. Running interactively");
LogInfo("This is usually due to a major change that could cause data loss");
LogInfo("Your server is automatically backed up before these updates are applied");
LogInfo("but you should also make sure you take a backup prior to running this update");
LogInfo("Would you like to run this update? [y/n] (Timeout 60s)");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
auto filtered_entries = entries | std::views::filter(
[version_low, version_high](const ManifestEntry& entry)
{ return entry.version > version_low && entry.version <= version_high; });
// user input
std::string input;
bool gave_input = false;
time_t start_time = time(nullptr);
time_t wait_time_seconds = 60;
std::vector<ManifestEntry> sorted_entries{filtered_entries.begin(), filtered_entries.end()};
std::ranges::sort(sorted_entries, {}, &ManifestEntry::version);
// spawn a concurrent thread that waits for input from std::cin
std::thread t1(
[&]() {
std::cin >> input;
gave_input = true;
}
);
t1.detach();
for (const auto& entry : sorted_entries) {
// this is the test to run this individual migration. If the test fails, then it is safe to assume
// that this migration has already happened manually or otherwise and it's safe to skip
// suppress error messages here, it's all tested in the following function
EQEmuLogSys::Instance()->DisableMySQLErrorLogs();
std::string result = GetQueryResult(entry);
EQEmuLogSys::Instance()->EnableMySQLErrorLogs();
// check the inputReceived flag once every 50ms for 10 seconds
while (time(nullptr) < start_time + wait_time_seconds && !gave_input) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
if (ShouldRunMigration(entry, result)) {
if (entry.force_interactive && !std::getenv("FORCE_INTERACTIVE")) {
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
LogInfo("This migration requires user input. Running interactively");
LogInfo("This is usually due to a major change that could cause data loss");
LogInfo("Your server is automatically backed up before these updates are applied");
LogInfo("but you should also make sure you take a backup prior to running this update");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
// prompt for user skip
if (Strings::Trim(input) != "y") {
LogInfo("Exiting due to user input");
std::exit(1);
}
}
for (auto &m: missing_migrations) {
for (auto &e: entries) {
if (e.version == m) {
bool errored_migration = false;
auto r = (e.content_schema_update ? m_content_database : m_database)->QueryDatabaseMulti(e.sql);
// ignore empty query result "errors"
if (r.ErrorNumber() != 1065 && !r.ErrorMessage().empty()) {
LogError("(#{}) [{}]", r.ErrorNumber(), r.ErrorMessage());
errored_migration = true;
LogInfo("Required database update failed. This could be a problem");
// if terminal attached then prompt for skip
if (is_atty()) {
LogInfo("Would you like to skip this update? [y/n] (Timeout 60s)");
// user input
std::string input;
bool gave_input = false;
time_t start_time = time(nullptr);
time_t wait_time_seconds = 60;
// spawn a concurrent thread that waits for input from std::cin
std::thread t1(
[&]() {
std::cin >> input;
gave_input = true;
}
);
t1.detach();
// check the inputReceived flag once every 50ms for 10 seconds
while (time(nullptr) < start_time + wait_time_seconds && !gave_input) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
// prompt for user skip
if (Strings::Trim(input) == "y") {
errored_migration = false;
LogInfo("Skipping update [{}] [{}]", e.version, e.description);
}
} else {
errored_migration = true;
LogInfo("Skipping update [{}] [{}]", e.version, e.description);
}
// prompt for user skip
if (DisplayPrompt("Would you like to run this update? [y/n]") != "y") {
LogInfo("Exiting due to user input");
return latest_version;
}
}
LogInfo(
"[{}] [{}] [{}]",
e.version,
e.description,
(errored_migration ? "error" : "ok")
);
auto r = (entry.content_schema_update ? m_content_database : m_database)->QueryDatabaseMulti(entry.sql);
if (errored_migration) {
LogError("Fatal | Database migration [{}] failed to run", e.description);
// ignore empty query result "errors"
if (r.ErrorNumber() != 1065 && !r.ErrorMessage().empty()) {
LogError("(#{}) [{}]", r.ErrorNumber(), r.ErrorMessage());
LogInfo("Required database update failed.");
// if terminal attached then prompt for skip
if (DisplayPrompt("Would you like to skip this update? [y/n]") == "y") {
LogInfo("Skipping update [{}] [{}]", entry.version, entry.description);
} else {
LogError("Fatal | Database migration [{}] failed to run", entry.description);
LogError("Fatal | Shutting down");
std::exit(1);
return latest_version;
}
LogInfo("[{}] [{}] [error]", entry.version, entry.description);
} else {
LogInfo("[{}] [{}] [ok]", entry.version, entry.description);
}
latest_version = entry.version;
}
}
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
return true;
}
return false;
return latest_version;
}
DatabaseUpdate *DatabaseUpdate::SetDatabase(Database *db)
+2 -2
View File
@@ -42,8 +42,8 @@ public:
DatabaseVersion GetBinaryDatabaseVersions();
void CheckDbUpdates();
std::string GetQueryResult(const ManifestEntry& e);
static bool ShouldRunMigration(ManifestEntry &e, std::string query_result);
bool UpdateManifest(std::vector<ManifestEntry> entries, int version_low, int version_high);
static bool ShouldRunMigration(const ManifestEntry& e, std::string& query_result);
int UpdateManifest(std::vector<ManifestEntry>& entries, int version_low, int version_high);
DatabaseUpdate *SetDatabase(Database *db);
DatabaseUpdate *SetContentDatabase(Database *db);
@@ -7207,6 +7207,64 @@ ALTER TABLE `character_parcels_containers`
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9329,
.description = "2026_04_27_spell_id_type_correction.sql",
.check = "SHOW COLUMNS FROM `character_spells` WHERE `Field` LIKE 'spell_id' AND `Type` LIKE 'int(%)'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `blocked_spells` MODIFY COLUMN `spellid` INTEGER NULL;
ALTER TABLE `character_buffs` MODIFY COLUMN `spell_id` INTEGER NOT NULL;
ALTER TABLE `character_memmed_spells` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `character_spells` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `damageshieldtypes` MODIFY COLUMN `spellid` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `ldon_trap_templates` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE IF EXISTS `merc_buffs` MODIFY COLUMN `SpellId` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE IF EXISTS `merc_spell_list_entries` MODIFY COLUMN `spell_id` INTEGER NOT NULL;
ALTER TABLE `npc_spells_entries` MODIFY COLUMN `spellid` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `spell_buckets` MODIFY COLUMN `spell_id` INTEGER NOT NULL;
)"
},
ManifestEntry{
.version = 9330,
.description = "2026_04_30_buffdurations.sql",
.check = "SHOW COLUMNS FROM `character_buffs` LIKE 'initialduration'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `character_buffs`
ADD COLUMN `initialduration` INT(11) SIGNED NOT NULL DEFAULT 0 AFTER `ticsremaining`,
CHANGE COLUMN `dot_rune` `dot_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0;
UPDATE `character_buffs` SET `initialduration` = `ticsremaining` WHERE TRUE;
ALTER TABLE IF EXISTS `merc_buffs`
CHANGE COLUMN `dot_rune` `dot_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0,
ADD COLUMN `InitialDuration` INT(11) SIGNED NOT NULL DEFAULT 0 AFTER `TicsRemaining`,
ADD COLUMN `instrument_mod` INT(10) UNSIGNED NOT NULL DEFAULT 10 AFTER `ExtraDIChance`;
IF EXISTS(
SELECT 1 FROM `information_schema`.`TABLES` WHERE `table_schema` = DATABASE() AND `table_name` = 'merc_buffs'
) THEN UPDATE `merc_buffs` SET `InitialDuration` = `TicsRemaining` WHERE TRUE; END IF;
ALTER TABLE `character_pet_buffs`
CHANGE COLUMN `char_id` `character_id` INT(11) UNSIGNED NOT NULL,
CHANGE COLUMN `slot` `slot_id` TINYINT(3) UNSIGNED NOT NULL,
CHANGE COLUMN `caster_level` `caster_level` TINYINT(3) UNSIGNED NOT NULL,
RENAME COLUMN `castername` TO `caster_name`,
ADD COLUMN `initialduration` INT(11) SIGNED NOT NULL DEFAULT 0 AFTER `ticsremaining`,
CHANGE COLUMN `counters` `counters` INT(10) UNSIGNED NOT NULL DEFAULT 0,
CHANGE COLUMN `numhits` `numhits` INT(10) UNSIGNED NOT NULL DEFAULT 0,
CHANGE COLUMN `rune` `melee_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0,
ADD COLUMN `magic_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `melee_rune`,
ADD COLUMN `persistent` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `magic_rune`,
ADD COLUMN `dot_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `persistent`,
ADD COLUMN `caston_x` INT(10) NOT NULL DEFAULT 0 AFTER `dot_rune`,
ADD COLUMN `caston_y` INT(10) NOT NULL DEFAULT 0 AFTER `caston_x`,
ADD COLUMN `caston_z` INT(10) NOT NULL DEFAULT 0 AFTER `caston_y`,
ADD COLUMN `ExtraDIChance` INT(10) NOT NULL DEFAULT 0 AFTER `caston_z`,
CHANGE COLUMN `instrument_mod` `instrument_mod` INT(10) UNSIGNED NOT NULL DEFAULT 10 AFTER `ExtraDIChance`;
)",
},
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
// .version = 9228,
@@ -2174,7 +2174,53 @@ FROM bot_settings
WHERE `setting_id` = 0
AND `setting_type` = 0;
)"
}
},
ManifestEntry{
.version = 9056,
.description = "some_new_migration.sql",
.check = "SHOW COLUMNS FROM `table_name` LIKE 'column_name'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `bot_blocked_buffs` MODIFY COLUMN `spell_id` INTEGER NOT NULL;
ALTER TABLE `bot_buffs` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `bot_pet_buffs` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `bot_pets` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `bot_spell_settings` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `bot_spells_entries` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
ALTER TABLE `bot_timers` MODIFY COLUMN `spell_id` INTEGER NOT NULL DEFAULT 0;
)"
},
ManifestEntry{
.version = 9057,
.description = "2026_04_30_buffdurations.sql",
.check = "SHOW COLUMNS FROM `bot_buffs` LIKE 'initial_duration'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `bot_buffs`
ADD COLUMN `caster_name` VARCHAR(64) NOT NULL DEFAULT '' AFTER `caster_level`,
ADD COLUMN `initial_duration` INT(11) SIGNED NOT NULL DEFAULT 0 AFTER `tics_remaining`;
UPDATE `bot_buffs` SET `initial_duration` = `tics_remaining` WHERE TRUE;
ALTER TABLE `bot_pet_buffs`
ADD COLUMN `caster_name` VARCHAR(64) NOT NULL DEFAULT '' AFTER `caster_level`,
CHANGE COLUMN `duration` `tics_remaining` INT(11) SIGNED NOT NULL DEFAULT 0 AFTER `caster_name`,
ADD COLUMN `initial_duration` INT(11) SIGNED NOT NULL DEFAULT 0 AFTER `tics_remaining`,
ADD COLUMN `counters` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `initial_duration`,
ADD COLUMN `numhits` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `counters`,
ADD COLUMN `melee_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `numhits`,
ADD COLUMN `magic_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `melee_rune`,
ADD COLUMN `dot_rune` INT(10) UNSIGNED NOT NULL DEFAULT 0 AFTER `magic_rune`,
ADD COLUMN `persistent` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `dot_rune`,
ADD COLUMN `caston_x` INT(10) NOT NULL DEFAULT 0 AFTER `persistent`,
ADD COLUMN `caston_y` INT(10) NOT NULL DEFAULT 0 AFTER `caston_x`,
ADD COLUMN `caston_z` INT(10) NOT NULL DEFAULT 0 AFTER `caston_y`,
ADD COLUMN `extra_di_chance` INT(10) NOT NULL DEFAULT 0 AFTER `caston_z`,
ADD COLUMN `instrument_mod` INT(10) UNSIGNED NOT NULL DEFAULT 10 AFTER `extra_di_chance`;
UPDATE `bot_pet_buffs` SET `initial_duration` = `tics_remaining` WHERE TRUE;
)",
},
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
// .version = 9228,
+23 -40
View File
@@ -33,17 +33,9 @@
#endif
DBcore::DBcore()
: mysql(mysql_init(nullptr))
, m_mutex(std::make_shared<Mutex>())
{
mysql = mysql_init(nullptr);
mysqlOwner = true;
pHost = nullptr;
pUser = nullptr;
pPassword = nullptr;
pDatabase = nullptr;
pCompress = false;
pSSL = false;
pStatus = Closed;
m_mutex = new Mutex;
}
DBcore::~DBcore()
@@ -56,20 +48,17 @@ DBcore::~DBcore()
if (mysqlOwner) {
mysql_close(mysql);
}
safe_delete_array(pHost);
safe_delete_array(pUser);
safe_delete_array(pPassword);
safe_delete_array(pDatabase);
}
// Sends the MySQL server a keepalive
void DBcore::ping()
{
if (!m_mutex->trylock()) {
if (!m_mutex->try_lock())
{
// well, if's it's locked, someone's using it. If someone's using it, it doesnt need a keepalive
return;
}
mysql_ping(mysql);
m_mutex->unlock();
}
@@ -92,7 +81,7 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
BenchTimer timer;
timer.reset();
LockMutex lock(m_mutex);
std::scoped_lock lock(*m_mutex);
// Reconnect if we are not connected before hand.
if (pStatus != Connected) {
@@ -217,15 +206,12 @@ bool DBcore::Open(
bool iSSL
)
{
LockMutex lock(m_mutex);
safe_delete_array(pHost);
safe_delete_array(pUser);
safe_delete_array(pPassword);
safe_delete_array(pDatabase);
pHost = strcpy(new char[strlen(iHost) + 1], iHost);
pUser = strcpy(new char[strlen(iUser) + 1], iUser);
pPassword = strcpy(new char[strlen(iPassword) + 1], iPassword);
pDatabase = strcpy(new char[strlen(iDatabase) + 1], iDatabase);
std::scoped_lock lock(*m_mutex);
m_host = iHost;
m_user = iUser;
m_password = iPassword;
m_database = iDatabase;
pCompress = iCompress;
pPort = iPort;
pSSL = iSSL;
@@ -234,10 +220,12 @@ bool DBcore::Open(
bool DBcore::Open(uint32 *errnum, char *errbuf)
{
// Expects m_mutex to already be locked.
if (errbuf) {
errbuf[0] = 0;
}
LockMutex lock(m_mutex);
if (GetStatus() == Connected) {
return true;
}
@@ -245,7 +233,7 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
mysql_close(mysql);
mysql_init(mysql); // Initialize structure again
}
if (!pHost) {
if (m_host.empty()) {
return false;
}
/*
@@ -268,11 +256,10 @@ bool DBcore::Open(uint32 *errnum, char *errbuf)
mysql_options(mysql, MYSQL_OPT_SSL_ENFORCE, &off);
mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &off);
}
if (mysql_real_connect(mysql, pHost, pUser, pPassword, pDatabase, pPort, 0, flags)) {
if (mysql_real_connect(mysql, m_host.c_str(), m_user.c_str(), m_password.c_str(), m_database.c_str(), pPort, nullptr, flags)) {
pStatus = Connected;
std::string connected_origin_host = pHost;
SetOriginHost(connected_origin_host);
SetOriginHost(m_host);
return true;
}
@@ -293,9 +280,9 @@ const std::string &DBcore::GetOriginHost() const
return origin_host;
}
void DBcore::SetOriginHost(const std::string &origin_host)
void DBcore::SetOriginHost(const std::string& originHost)
{
DBcore::origin_host = origin_host;
DBcore::origin_host = originHost;
}
std::string DBcore::Escape(const std::string& s)
@@ -307,12 +294,8 @@ std::string DBcore::Escape(const std::string& s)
return temp.data();
}
void DBcore::SetMutex(Mutex *mutex)
void DBcore::SetMutex(const std::shared_ptr<Mutex>& mutex)
{
if (m_mutex && m_mutex != mutex) {
safe_delete(m_mutex);
}
DBcore::m_mutex = mutex;
}
@@ -326,7 +309,7 @@ MySQLRequestResult DBcore::QueryDatabaseMulti(const std::string &query)
BenchTimer timer;
timer.reset();
LockMutex lock(m_mutex);
std::scoped_lock lock(*m_mutex);
// Reconnect if we are not connected before hand.
if (pStatus != Connected) {
@@ -449,5 +432,5 @@ MySQLRequestResult DBcore::QueryDatabaseMulti(const std::string &query)
mysql::PreparedStmt DBcore::Prepare(std::string query)
{
return mysql::PreparedStmt(*mysql, std::move(query), m_mutex);
return mysql::PreparedStmt(*mysql, std::move(query), *m_mutex);
}
+20 -18
View File
@@ -17,11 +17,11 @@
*/
#pragma once
#include "common/mutex.h"
#include "common/mysql_request_result.h"
#include "common/types.h"
#include "mysql.h"
#include <memory>
#include <mutex>
#define CR_SERVER_GONE_ERROR 2006
@@ -29,12 +29,15 @@
namespace mysql { class PreparedStmt; }
class DBcore {
class DBcore
{
public:
enum eStatus {
Closed, Connected, Error
};
using Mutex = std::recursive_mutex;
DBcore();
~DBcore();
eStatus GetStatus() { return pStatus; }
@@ -48,17 +51,17 @@ public:
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
void ping();
const std::string &GetOriginHost() const;
void SetOriginHost(const std::string &origin_host);
const std::string& GetOriginHost() const;
void SetOriginHost(const std::string& origin_host);
bool DoesTableExist(const std::string& table_name);
void SetMySQL(const DBcore &o)
void SetMySQL(const DBcore& o)
{
mysql = o.mysql;
mysqlOwner = false;
}
void SetMutex(Mutex *mutex);
void SetMutex(const std::shared_ptr<Mutex>& mutex);
// only safe on connections shared with other threads if results buffered
// unsafe to use off main thread due to internal server logging
@@ -81,22 +84,21 @@ protected:
private:
bool Open(uint32 *errnum = nullptr, char *errbuf = nullptr);
MYSQL* mysql;
bool mysqlOwner;
Mutex *m_mutex;
eStatus pStatus;
MYSQL* mysql = nullptr;
bool mysqlOwner = true;
eStatus pStatus = Closed;
std::mutex m_query_lock{};
std::shared_ptr<Mutex> m_mutex;
std::string origin_host;
char *pHost;
char *pUser;
char *pPassword;
char *pDatabase;
bool pCompress;
uint32 pPort;
bool pSSL;
std::string m_host;
std::string m_user;
std::string m_password;
std::string m_database;
bool pCompress = false;
uint32 pPort = 0;
bool pSSL = false;
// allows multiple queries to be executed within the same query
// do not use this under normal operation
+7 -6
View File
@@ -18,8 +18,6 @@
// system use
N(OP_ExploreUnknown),
// start (please add new opcodes in descending order and re-order any name changes where applicable)
N(OP_0x0193),
N(OP_0x0347),
N(OP_AAAction),
N(OP_AAExpUpdate),
N(OP_AcceptNewTask),
@@ -80,8 +78,8 @@ N(OP_Bind_Wound),
N(OP_BlockedBuffs),
N(OP_BoardBoat),
N(OP_BookButton),
N(OP_Buff),
N(OP_BuffCreate),
N(OP_BuffDefinition),
N(OP_RefreshBuffs),
N(OP_BuffRemoveRequest),
N(OP_Bug),
N(OP_BuyerItems),
@@ -376,10 +374,12 @@ N(OP_MercenaryDismiss),
N(OP_MercenaryHire),
N(OP_MercenarySuspendRequest),
N(OP_MercenarySuspendResponse),
N(OP_MercenarySwitch),
N(OP_MercenaryTimer),
N(OP_MercenaryTimerRequest),
N(OP_MercenaryUnknown1),
N(OP_MercenaryUnsuspendResponse),
N(OP_MerchantBulkItems),
N(OP_MobEnduranceUpdate),
N(OP_MobHealth),
N(OP_MobManaUpdate),
@@ -398,6 +398,7 @@ N(OP_MultiLineMsg),
N(OP_NewSpawn),
N(OP_NewTitlesAvailable),
N(OP_NewZone),
N(OP_NPCMoveUpdate),
N(OP_OnLevelMessage),
N(OP_OpenContainer),
N(OP_OpenDiscordMerchant),
@@ -405,7 +406,7 @@ N(OP_OpenGuildTributeMaster),
N(OP_OpenInventory),
N(OP_OpenTributeMaster),
N(OP_PDeletePetition),
N(OP_PetBuffWindow),
N(OP_RefreshPetBuffs),
N(OP_PetCommands),
N(OP_PetCommandState),
N(OP_PetHoTT),
@@ -561,7 +562,7 @@ N(OP_Stun),
N(OP_Surname),
N(OP_SwapSpell),
N(OP_SystemFingerprint),
N(OP_TargetBuffs),
N(OP_RefreshTargetBuffs),
N(OP_TargetCommand),
N(OP_TargetHoTT),
N(OP_TargetMouse),
+111
View File
@@ -54,6 +54,8 @@ const char* EQ::versions::ClientVersionName(ClientVersion client_version)
return "RoF";
case ClientVersion::RoF2:
return "RoF2";
case ClientVersion::TOB:
return "TOB";
default:
return "Invalid Version";
};
@@ -74,6 +76,8 @@ uint32 EQ::versions::ConvertClientVersionToClientVersionBit(ClientVersion client
return bitRoF;
case ClientVersion::RoF2:
return bitRoF2;
case ClientVersion::TOB:
return bitTOB;
default:
return bitUnknown;
}
@@ -94,6 +98,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertClientVersionBitToClientVersion
return ClientVersion::RoF;
case ((uint32)1 << (static_cast<unsigned int>(ClientVersion::RoF2) - 1)) :
return ClientVersion::RoF2;
case ((uint32)1 << (static_cast<unsigned int>(ClientVersion::TOB) - 1)) :
return ClientVersion::TOB;
default:
return ClientVersion::Unknown;
}
@@ -182,6 +188,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
return "RoF";
case MobVersion::RoF2:
return "RoF2";
case MobVersion::TOB:
return "TOB";
case MobVersion::NPC:
return "NPC";
case MobVersion::NPCMerchant:
@@ -210,6 +218,8 @@ const char* EQ::versions::MobVersionName(MobVersion mob_version)
return "Offline RoF";
case MobVersion::OfflineRoF2:
return "Offline RoF2";
case MobVersion::OfflineTOB:
return "Offline TOB";
default:
return "Invalid Version";
};
@@ -233,6 +243,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertMobVersionToClientVersion(MobVe
return ClientVersion::RoF;
case MobVersion::RoF2:
return ClientVersion::RoF2;
case MobVersion::TOB:
return ClientVersion::TOB;
default:
return ClientVersion::Unknown;
}
@@ -256,6 +268,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToMobVersion(ClientVe
return MobVersion::RoF;
case ClientVersion::RoF2:
return MobVersion::RoF2;
case ClientVersion::TOB:
return MobVersion::TOB;
default:
return MobVersion::Unknown;
}
@@ -276,6 +290,8 @@ EQ::versions::MobVersion EQ::versions::ConvertPCMobVersionToOfflinePCMobVersion(
return MobVersion::OfflineRoF;
case MobVersion::RoF2:
return MobVersion::OfflineRoF2;
case MobVersion::TOB:
return MobVersion::OfflineTOB;
default:
return MobVersion::Unknown;
}
@@ -296,6 +312,8 @@ EQ::versions::MobVersion EQ::versions::ConvertOfflinePCMobVersionToPCMobVersion(
return MobVersion::RoF;
case MobVersion::OfflineRoF2:
return MobVersion::RoF2;
case MobVersion::OfflineTOB:
return MobVersion::TOB;
default:
return MobVersion::Unknown;
}
@@ -316,6 +334,8 @@ EQ::versions::ClientVersion EQ::versions::ConvertOfflinePCMobVersionToClientVers
return ClientVersion::RoF;
case MobVersion::OfflineRoF2:
return ClientVersion::RoF2;
case MobVersion::OfflineTOB:
return ClientVersion::TOB;
default:
return ClientVersion::Unknown;
}
@@ -336,6 +356,8 @@ EQ::versions::MobVersion EQ::versions::ConvertClientVersionToOfflinePCMobVersion
return MobVersion::OfflineRoF;
case ClientVersion::RoF2:
return MobVersion::OfflineRoF2;
case ClientVersion::TOB:
return MobVersion::OfflineTOB;
default:
return MobVersion::Unknown;
}
@@ -386,6 +408,28 @@ const char* EQ::expansions::ExpansionName(Expansion expansion)
return "Rain of Fear";
case Expansion::CotF:
return "Call of the Forsaken";
case Expansion::TDS:
return "The Darkened Sea";
case Expansion::TBM:
return "The Broken Mirror";
case Expansion::EoK:
return "Empires of Kunark";
case Expansion::RoS:
return "Ring of Scale";
case Expansion::TBL:
return "The Burning Lands";
case Expansion::ToV:
return "Torment of Velious";
case Expansion::CoV:
return "Claws of Veeshan";
case Expansion::ToL:
return "Terror of Luclin";
case Expansion::NoS:
return "Night of Shadows";
case Expansion::LS:
return "Laurion's Song";
case Expansion::TOB:
return "The Outer Brood";
default:
return "Invalid Expansion";
}
@@ -439,6 +483,29 @@ uint32 EQ::expansions::ConvertExpansionToExpansionBit(Expansion expansion)
return bitRoF;
case Expansion::CotF:
return bitCotF;
case Expansion::TDS:
return bitTDS;
case Expansion::TBM:
return bitTBM;
case Expansion::EoK:
return bitEoK;
case Expansion::RoS:
return bitRoS;
case Expansion::TBL:
return bitTBL;
case Expansion::ToV:
return bitToV;
case Expansion::CoV:
return bitCoV;
case Expansion::ToL:
return bitToL;
case Expansion::NoS:
return bitNoS;
case Expansion::LS:
return bitLS;
case Expansion::TOB:
return bitTOB;
default:
return bitEverQuest;
}
@@ -487,6 +554,28 @@ EQ::expansions::Expansion EQ::expansions::ConvertExpansionBitToExpansion(uint32
return Expansion::RoF;
case bitCotF:
return Expansion::CotF;
case bitTDS:
return Expansion::TDS;
case bitTBM:
return Expansion::TBM;
case bitEoK:
return Expansion::EoK;
case bitRoS:
return Expansion::RoS;
case bitTBL:
return Expansion::TBL;
case bitToV:
return Expansion::ToV;
case bitCoV:
return Expansion::CoV;
case bitToL:
return Expansion::ToL;
case bitNoS:
return Expansion::NoS;
case bitLS:
return Expansion::LS;
case bitTOB:
return Expansion::TOB;
default:
return Expansion::EverQuest;
}
@@ -535,6 +624,28 @@ uint32 EQ::expansions::ConvertExpansionToExpansionsMask(Expansion expansion)
return maskRoF;
case Expansion::CotF:
return maskCotF;
case Expansion::TDS:
return maskTDS;
case Expansion::TBM:
return maskTBM;
case Expansion::EoK:
return maskEoK;
case Expansion::RoS:
return maskRoS;
case Expansion::TBL:
return maskTBL;
case Expansion::ToV:
return maskToV;
case Expansion::CoV:
return maskCoV;
case Expansion::ToL:
return maskToL;
case Expansion::NoS:
return maskNoS;
case Expansion::LS:
return maskLS;
case Expansion::TOB:
return maskTOB;
default:
return maskEverQuest;
}
+49 -10
View File
@@ -32,7 +32,8 @@ namespace EQ
SoD, // Build: 'Dec 19 2008 15:22:49'
UF, // Build: 'Jun 8 2010 16:44:32'
RoF, // Build: 'Dec 10 2012 17:35:44'
RoF2 // Build: 'May 10 2013 23:30:08'
RoF2, // Build: 'May 10 2013 23:30:08'
TOB // Build: 'Sep 11 2025 11:54:10'
};
enum ClientVersionBitmask : uint32 {
@@ -44,6 +45,7 @@ namespace EQ
bitUF = 0x00000010,
bitRoF = 0x00000020,
bitRoF2 = 0x00000040,
bitTOB = 0x00000080,
maskUnknown = 0x00000000,
maskTitaniumAndEarlier = 0x00000003,
maskSoFAndEarlier = 0x00000007,
@@ -55,11 +57,12 @@ namespace EQ
maskUFAndLater = 0xFFFFFFF0,
maskRoFAndLater = 0xFFFFFFE0,
maskRoF2AndLater = 0xFFFFFFC0,
maskTOBAndLater = 0xFFFFFF80,
maskAllClients = 0xFFFFFFFF
};
const ClientVersion LastClientVersion = ClientVersion::RoF2;
const size_t ClientVersionCount = (static_cast<size_t>(LastClientVersion) + 1);
inline constexpr ClientVersion LastClientVersion = ClientVersion::TOB;
inline constexpr size_t ClientVersionCount = (static_cast<size_t>(LastClientVersion) + 1);
bool IsValidClientVersion(ClientVersion client_version);
ClientVersion ValidateClientVersion(ClientVersion client_version);
@@ -76,6 +79,7 @@ namespace EQ
UF,
RoF,
RoF2,
TOB,
NPC,
NPCMerchant,
Merc,
@@ -89,13 +93,14 @@ namespace EQ
OfflineSoD,
OfflineUF,
OfflineRoF,
OfflineRoF2
OfflineRoF2,
OfflineTOB
};
const MobVersion LastMobVersion = MobVersion::OfflineRoF2;
const MobVersion LastPCMobVersion = MobVersion::RoF2;
const MobVersion LastMobVersion = MobVersion::OfflineTOB;
const MobVersion LastPCMobVersion = MobVersion::TOB;
const MobVersion LastNonPCMobVersion = MobVersion::BotPet;
const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineRoF2;
const MobVersion LastOfflinePCMobVersion = MobVersion::OfflineTOB;
const size_t MobVersionCount = (static_cast<size_t>(LastMobVersion) + 1);
bool IsValidMobVersion(MobVersion mob_version);
@@ -127,7 +132,8 @@ namespace EQ
ucsSoDCombined = 'D',
ucsUFCombined = 'E',
ucsRoFCombined = 'F',
ucsRoF2Combined = 'G'
ucsRoF2Combined = 'G',
ucsTOBCombined = 'H'
};
} /*versions*/
@@ -154,7 +160,18 @@ namespace EQ
HoT,
VoA,
RoF,
CotF
CotF,
TDS,
TBM,
EoK,
RoS,
TBL,
ToV,
CoV,
ToL,
NoS,
LS,
TOB
};
enum ExpansionBitmask : uint32 {
@@ -179,6 +196,17 @@ namespace EQ
bitVoA = 0x00020000,
bitRoF = 0x00040000,
bitCotF = 0x00080000,
bitTDS = 0x00100000,
bitTBM = 0x00200000,
bitEoK = 0x00400000,
bitRoS = 0x00800000,
bitTBL = 0x01000000,
bitToV = 0x02000000,
bitCoV = 0x04000000,
bitToL = 0x08000000,
bitNoS = 0x10000000,
bitLS = 0x20000000,
bitTOB = 0x40000000,
maskEverQuest = 0x00000000,
maskRoK = 0x00000001,
maskSoV = 0x00000003,
@@ -199,7 +227,18 @@ namespace EQ
maskHoT = 0x0001FFFF,
maskVoA = 0x0003FFFF,
maskRoF = 0x0007FFFF,
maskCotF = 0x000FFFFF
maskCotF = 0x000FFFFF,
maskTDS = 0x001FFFFF,
maskTBM = 0x003FFFFF,
maskEoK = 0x007FFFFF,
maskRoS = 0x00FFFFFF,
maskTBL = 0x01FFFFFF,
maskToV = 0x03FFFFFF,
maskCoV = 0x07FFFFFF,
maskToL = 0x0FFFFFFF,
maskNoS = 0x1FFFFFFF,
maskLS = 0x3FFFFFFF,
maskTOB = 0x7FFFFFFF,
};
const char* ExpansionName(Expansion expansion);
+40
View File
@@ -759,6 +759,46 @@ typedef enum {
FilterStrikethrough = 26, //0=show, 1=hide // RoF2 Confirmed
FilterStuns = 27, //0=show, 1=hide // RoF2 Confirmed
FilterBardSongsOnPets = 28, //0=show, 1=hide // RoF2 Confirmed
FilterSwarmPetDeath = 29,
FilterFellowshipChat = 30,
FilterMercenaryMessages = 31,
FilterSpam = 32,
FilterAchievements = 33,
FilterPvPMessages = 34,
FilterSpellNameInCast = 35,
FilterRandomMine = 36,
FilterRandomGroupRaid = 37,
FilterRandomOthers = 38,
FilterEnvironmentalDamage = 39,
FilterMessages = 40,
FilterOverwriteDetrimental = 41,
FilterOverwriteBeneficial = 42,
FilterCantUseCommand = 43,
FilterCombatAbilityReuse = 44,
FilterAAAbilityReuse = 45,
FilterProcBeginCasting = 46,
FilterDestroyedItems = 47,
FilterYourAuras = 48,
FilterOtherAuras = 49,
FilterYourHeals = 50,
FilterOtherHeals = 51,
FilterYourDoTs = 52,
FilterOtherDoTs = 53,
FilterOtherDirectDamage = 54,
FilterSpellEmotes = 55,
FilterFactionMessages = 56,
FilterTauntMessages = 57,
FilterYourDisciplines = 58,
FilterOtherDisplines = 59,
FilterAchievementsOthers = 60,
FilterRaidVictory = 61,
FilterOtherDirectDamageCrits = 62,
FilterDoTYoursCritical = 63,
FilterDoTOthersCritical = 64,
FilterDoTDamageTaken = 65,
FilterHealsReceived = 66,
FilterHealsYoursCritical = 67,
FilterHealsOthersCritical = 68,
_FilterCount
} eqFilterType;
+89
View File
@@ -110,6 +110,15 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
RoF2::constants::CHARACTER_CREATION_LIMIT,
RoF2::constants::SAY_LINK_BODY_SIZE,
RoF2::constants::MAX_BAZAAR_TRADERS
),
/*[ClientVersion::TOB] =*/
EQ::constants::LookupEntry(
TOB::constants::EXPANSION,
TOB::constants::EXPANSION_BIT,
TOB::constants::EXPANSIONS_MASK,
TOB::constants::CHARACTER_CREATION_LIMIT,
TOB::constants::SAY_LINK_BODY_SIZE,
TOB::constants::MAX_BAZAAR_TRADERS
)
};
@@ -376,6 +385,34 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers
RoF2::inventory::ConcatenateInvTypeLimbo,
RoF2::inventory::AllowOverLevelEquipment
),
/*[MobVersion::TOB] =*/
//TOBTodo: These need to be set to the latest values not just use RoF2
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
EQ::invtype::POSSESSIONS_SIZE, RoF2::invtype::BANK_SIZE, RoF2::invtype::SHARED_BANK_SIZE,
RoF2::invtype::TRADE_SIZE, RoF2::invtype::WORLD_SIZE, RoF2::invtype::LIMBO_SIZE,
RoF2::invtype::TRIBUTE_SIZE, RoF2::invtype::TROPHY_TRIBUTE_SIZE, RoF2::invtype::GUILD_TRIBUTE_SIZE,
RoF2::invtype::MERCHANT_SIZE, RoF2::invtype::DELETED_SIZE, RoF2::invtype::CORPSE_SIZE,
RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::invtype::REAL_ESTATE_SIZE,
RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE,
RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::invtype::ALT_STORAGE_SIZE, RoF2::invtype::ARCHIVED_SIZE,
RoF2::invtype::MAIL_SIZE, RoF2::invtype::GUILD_TROPHY_TRIBUTE_SIZE, RoF2::invtype::KRONO_SIZE,
RoF2::invtype::GUILD_BANK_MAIN_SIZE, RoF2::invtype::GUILD_BANK_DEPOSIT_SIZE, RoF2::invtype::OTHER_SIZE
),
RoF2::invslot::EQUIPMENT_BITMASK,
RoF2::invslot::GENERAL_BITMASK,
RoF2::invslot::CURSOR_BITMASK,
RoF2::invslot::POSSESSIONS_BITMASK,
RoF2::invslot::CORPSE_BITMASK,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
RoF2::inventory::AllowEmptyBagInBag,
RoF2::inventory::AllowClickCastFromBag,
RoF2::inventory::ConcatenateInvTypeLimbo,
RoF2::inventory::AllowOverLevelEquipment
),
/*[MobVersion::NPC] =*/
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
@@ -748,6 +785,35 @@ static const EQ::inventory::LookupEntry inventory_static_lookup_entries[EQ::vers
RoF2::INULL,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
false,
false,
false,
false
),
/*[MobVersion::OfflineTOB] =*/
//TOBTodo: Need to use their own values instead of RoF2
EQ::inventory::LookupEntry(
EQ::inventory::LookupEntry::InventoryTypeSize_Struct(
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::invtype::TRADE_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::invtype::MERCHANT_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::invtype::BAZAAR_SIZE, RoF2::invtype::INSPECT_SIZE, RoF2::INULL,
RoF2::invtype::VIEW_MOD_PC_SIZE, RoF2::invtype::VIEW_MOD_BANK_SIZE, RoF2::invtype::VIEW_MOD_SHARED_BANK_SIZE,
RoF2::invtype::VIEW_MOD_LIMBO_SIZE, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL,
RoF2::INULL, RoF2::INULL, RoF2::INULL
),
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::INULL,
RoF2::invbag::SLOT_COUNT,
RoF2::invaug::SOCKET_COUNT,
false,
false,
@@ -1000,6 +1066,11 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::TOB] =*/
//TOBTodo: We need this value set properly
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::NPC] =*/
EQ::behavior::LookupEntry(
EQ::behavior::CoinHasWeight
@@ -1053,6 +1124,11 @@ static const EQ::behavior::LookupEntry behavior_static_lookup_entries[EQ::versio
RoF::behavior::CoinHasWeight
),
/*[MobVersion::OfflineRoF2] =*/
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
),
/*[MobVersion::OfflineTOB] =*/
//TOBTodo: We need this value set properly
EQ::behavior::LookupEntry(
RoF2::behavior::CoinHasWeight
)
@@ -1208,6 +1284,19 @@ static const EQ::spells::LookupEntry spells_static_lookup_entries[EQ::versions::
RoF2::spells::NPC_BUFFS,
RoF2::spells::PET_BUFFS,
RoF2::spells::MERC_BUFFS
),
/*[ClientVersion::TOB] =*/
EQ::spells::LookupEntry(
TOB::spells::SPELL_ID_MAX,
TOB::spells::SPELLBOOK_SIZE,
UF::spells::SPELL_GEM_COUNT, // client translators are setup to allow the max value a client supports..however, the top 4 indices are not valid in this case
TOB::spells::LONG_BUFFS,
TOB::spells::SHORT_BUFFS,
TOB::spells::DISC_BUFFS,
TOB::spells::TOTAL_BUFFS,
TOB::spells::NPC_BUFFS,
TOB::spells::PET_BUFFS,
TOB::spells::MERC_BUFFS
)
};
+1
View File
@@ -23,6 +23,7 @@
#include "common/patches/rof2_limits.h"
#include "common/patches/sod_limits.h"
#include "common/patches/sof_limits.h"
#include "common/patches/tob_limits.h"
#include "common/patches/titanium_limits.h"
#include "common/patches/uf_limits.h"
#include "common/types.h"
+20 -11
View File
@@ -31,9 +31,19 @@
#include <iomanip>
#include <iostream>
EQPacket::EQPacket(EmuOpcode op, const unsigned char *buf, uint32 len)
: BasePacket(buf, len),
emu_opcode(op)
EQPacket::EQPacket()
{
}
EQPacket::EQPacket(EmuOpcode op, const unsigned char *buf, size_t len)
: BasePacket(buf, len)
, emu_opcode(op)
{
}
EQPacket::EQPacket(EmuOpcode opcode, SerializeBuffer&& buf)
: BasePacket(std::move(buf))
, emu_opcode(opcode)
{
}
@@ -360,17 +370,16 @@ EQRawApplicationPacket::EQRawApplicationPacket(const unsigned char *buf, const u
}
}
void DumpPacket(const EQApplicationPacket* app, bool iShowInfo) {
void DumpPacket(const EQApplicationPacket* app, bool iShowInfo)
{
if (iShowInfo) {
std::cout << "Dumping Applayer: 0x" << std::hex << std::setfill('0') << std::setw(4) << app->GetOpcode() << std::dec;
std::cout << " size:" << app->size << std::endl;
printf("Dumping Applayer: 0x%04x size: %u", app->GetOpcode(), app->size);
}
DumpPacketHex(app->pBuffer, app->size);
// DumpPacketAscii(app->pBuffer, app->size);
}
std::string DumpPacketToString(const EQApplicationPacket* app){
std::ostringstream out;
out << DumpPacketHexToString(app->pBuffer, app->size);
return out.str();
std::string DumpPacketToString(const EQApplicationPacket* app)
{
return DumpPacketHexToString(app->pBuffer, app->size);
}
+49 -29
View File
@@ -28,8 +28,15 @@
#include "common/emu_opcodes.h"
#endif
class EQPacket : public BasePacket {
class EQPacket : public BasePacket
{
friend class EQStream;
protected:
EQPacket();
EQPacket(EmuOpcode opcode, const unsigned char* buf, size_t len);
EQPacket(EmuOpcode opcode, SerializeBuffer&& buf);
public:
virtual ~EQPacket() {}
@@ -41,19 +48,12 @@ public:
virtual void DumpRawHeaderNoTime(uint16 seq=0xffff, FILE *to = stdout) const;
void SetOpcode(EmuOpcode op) { emu_opcode = op; }
const EmuOpcode GetOpcode() const { return(emu_opcode); }
// const char *GetOpcodeName() const;
EmuOpcode GetOpcode() const { return(emu_opcode); }
protected:
//this is just a cache so we dont look it up several times on Get()
//and it is mutable so we can store the cached copy even on a const object
EmuOpcode emu_opcode;
EQPacket(EmuOpcode opcode, const unsigned char *buf, const uint32 len);
EQPacket(EmuOpcode opcode, SerializeBuffer &buf) : BasePacket(buf), emu_opcode(opcode) { };
// EQPacket(const EQPacket &p) { }
EQPacket() { emu_opcode=OP_Unknown; pBuffer=nullptr; size=0; }
EmuOpcode emu_opcode = OP_Unknown;
};
class EQRawApplicationPacket;
@@ -90,19 +90,43 @@ protected:
uint16 opcode;
};
class EQApplicationPacket : public EQPacket {
class EQApplicationPacket : public EQPacket
{
friend class EQStream;
public:
EQApplicationPacket()
{
}
EQApplicationPacket(EmuOpcode op)
: EQPacket(op, nullptr, 0)
{
}
EQApplicationPacket(EmuOpcode op, size_t len)
: EQPacket(op, nullptr, len)
{
}
EQApplicationPacket(EmuOpcode op, const unsigned char* buf, size_t len)
: EQPacket(op, buf, len)
{
}
EQApplicationPacket(EmuOpcode op, SerializeBuffer&& buf)
: EQPacket(op, std::move(buf))
{
}
private:
EQApplicationPacket(const EQApplicationPacket& p)
: EQPacket(p.emu_opcode, p.pBuffer, p.size)
, app_opcode_size(p.app_opcode_size)
, opcode_bypass(p.opcode_bypass)
{
}
public:
EQApplicationPacket() : EQPacket(OP_Unknown, nullptr, 0), opcode_bypass(0)
{ app_opcode_size = GetExecutablePlatform() == ExePlatformUCS ? 1 : 2; }
EQApplicationPacket(const EmuOpcode op) : EQPacket(op, nullptr, 0), opcode_bypass(0)
{ app_opcode_size = GetExecutablePlatform() == ExePlatformUCS ? 1 : 2; }
EQApplicationPacket(const EmuOpcode op, const uint32 len) : EQPacket(op, nullptr, len), opcode_bypass(0)
{ app_opcode_size = GetExecutablePlatform() == ExePlatformUCS ? 1 : 2; }
EQApplicationPacket(const EmuOpcode op, const unsigned char *buf, const uint32 len) : EQPacket(op, buf, len), opcode_bypass(0)
{ app_opcode_size = GetExecutablePlatform() == ExePlatformUCS ? 1 : 2; }
EQApplicationPacket(const EmuOpcode op, SerializeBuffer &buf) : EQPacket(op, buf), opcode_bypass(0)
{ app_opcode_size = GetExecutablePlatform() == ExePlatformUCS ? 1 : 2; }
bool combine(const EQApplicationPacket *rhs);
uint32 serialize (uint16 opcode, unsigned char *dest) const;
uint32 Size() const { return size+app_opcode_size; }
@@ -119,15 +143,11 @@ public:
uint16 GetProtocolOpcode() const { return protocol_opcode; }
void SetProtocolOpcode(uint16 v) { protocol_opcode = v; }
protected:
uint16 protocol_opcode;
uint8 app_opcode_size;
uint16 opcode_bypass;
private:
EQApplicationPacket(const EQApplicationPacket &p) : EQPacket(p.emu_opcode, p.pBuffer, p.size), opcode_bypass(p.opcode_bypass) { app_opcode_size = p.app_opcode_size; }
uint16 protocol_opcode = 0;
uint8 app_opcode_size = GetExecutablePlatform() == ExePlatformUCS ? 1 : 2;
uint16 opcode_bypass = 0;
};
class EQRawApplicationPacket : public EQApplicationPacket {
+32 -18
View File
@@ -47,6 +47,13 @@ static const uint32 ADVANCED_LORE_LENGTH = 8192;
#pragma pack(push)
#pragma pack(1)
struct EqGuid
{
uint32_t Id;
uint16_t WorldId;
uint16_t Reserved;
};
struct LoginInfo {
/*000*/ char login_info[64];
/*064*/ uint8 unknown064[124];
@@ -121,7 +128,7 @@ struct LDoNTrapTemplate
{
uint32 id;
LDoNChestTypes type;
uint32 spell_id;
int32 spell_id;
uint16 skill;
uint8 locked;
};
@@ -326,6 +333,7 @@ union
bool buyer;
bool untargetable;
uint32 npc_tint_id;
EqGuid CharacterGuid;
};
struct PlayerState_Struct {
@@ -414,7 +422,7 @@ struct NewZone_Struct {
*/
struct MemorizeSpell_Struct {
uint32 slot; // Spot in the spell book/memorized slot
uint32 spell_id; // Spell id (200 or c8 is minor healing, etc)
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming
uint32 reduction; // lower reuse
};
@@ -463,7 +471,7 @@ struct ManaChange_Struct
{
/*00*/ uint32 new_mana; // New Mana AMount
/*04*/ uint32 stamina;
/*08*/ uint32 spell_id;
/*08*/ int32 spell_id;
/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting?
/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like
/*16*/ int32 slot; // -1 normal, otherwise clear ETA and GCD
@@ -481,14 +489,14 @@ struct BeginCast_Struct
{
// len = 8
/*000*/ uint16 caster_id;
/*002*/ uint16 spell_id;
/*002*/ int16 spell_id;
/*004*/ uint32 cast_time; // in miliseconds
};
struct CastSpell_Struct
{
uint32 slot;
uint32 spell_id;
int32 spell_id;
uint32 inventoryslot; // slot for clicky item, 0xFFFF = normal cast
uint32 target_id;
uint32 cs_unknown1;
@@ -536,7 +544,7 @@ struct SpellBuff_Struct
/*001*/ uint8 level;
/*002*/ uint8 bard_modifier;
/*003*/ uint8 unknown003; // MQ2 used to call this "damage shield" -- don't see client referencing it, so maybe server side DS type tracking? -- OSX client calls this "activated"
/*004*/ uint32 spellid;
/*004*/ int32 spellid;
/*008*/ int32 duration;
/*012*/ uint32 counters; // single book keeping value (counters, rune/vie)
/*016*/ uint32 player_id; // caster ID, pretty sure just zone ID
@@ -566,7 +574,7 @@ struct BuffRemoveRequest_Struct
struct PetBuff_Struct {
/*000*/ uint32 petid;
/*004*/ uint32 spellid[PET_BUFF_COUNT];
/*004*/ int32 spellid[PET_BUFF_COUNT];
/*124*/ int32 ticsremaining[PET_BUFF_COUNT];
/*244*/ uint32 buffcount;
};
@@ -867,7 +875,7 @@ struct BindStruct {
struct SuspendedMinion_Struct
{
/*000*/ uint16 SpellID;
/*000*/ int16 SpellID;
/*002*/ uint32 HP;
/*006*/ uint32 Mana;
/*010*/ SpellBuff_Struct Buffs[BUFF_COUNT];
@@ -1002,7 +1010,7 @@ struct PlayerProfile_Struct
/*2505*/ uint8 unknown2541[47]; // ?
/*2552*/ uint8 languages[MAX_PP_LANGUAGE];
/*2580*/ uint8 unknown2616[4];
/*2584*/ uint32 spell_book[EQ::spells::SPELLBOOK_SIZE];
/*2584*/ int32 spell_book[EQ::spells::SPELLBOOK_SIZE];
/*4504*/ uint8 unknown4540[128]; // Was [428] all 0xff
/*4632*/ uint32 mem_spells[EQ::spells::SPELL_GEM_COUNT];
/*4668*/ uint8 unknown4704[32]; //
@@ -1336,7 +1344,7 @@ struct CombatDamage_Struct
/* 00 */ uint16 target;
/* 02 */ uint16 source;
/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells, skill
/* 05 */ uint16 spellid;
/* 05 */ int16 spellid;
/* 07 */ uint32 damage;
/* 11 */ float force;
/* 15 */ float hit_heading; // see above notes in Action_Struct
@@ -1369,7 +1377,7 @@ struct Death_Struct
/*004*/ uint32 killer_id;
/*008*/ uint32 corpseid; // was corpseid
/*012*/ uint32 bindzoneid;
/*016*/ uint32 spell_id;
/*016*/ int32 spell_id;
/*020*/ uint32 attack_skill;
/*024*/ uint32 damage;
/*028*/ uint32 unknown028;
@@ -2975,7 +2983,7 @@ struct Resurrect_Struct {
/*024*/ char your_name[64];
/*088*/ uint32 unknown088;
/*092*/ char rezzer_name[64];
/*156*/ uint32 spellid;
/*156*/ int32 spellid;
/*160*/ char corpse_name[64];
/*224*/ uint32 action;
/* 228 */
@@ -2983,7 +2991,7 @@ struct Resurrect_Struct {
struct Translocate_Struct {
/*000*/ uint32 ZoneID;
/*004*/ uint32 SpellID;
/*004*/ int32 SpellID;
/*008*/ uint32 unknown008; //Heading ?
/*012*/ char Caster[64];
/*076*/ float y;
@@ -3000,7 +3008,7 @@ struct PendingTranslocate_Struct
float x;
float y;
float z;
uint32 spell_id;
int32 spell_id;
};
struct Sacrifice_Struct {
@@ -4867,7 +4875,7 @@ struct MarkNPC_Struct
};
struct InspectBuffs_Struct {
/*000*/ uint32 spell_id[BUFF_COUNT];
/*000*/ int32 spell_id[BUFF_COUNT];
/*100*/ int32 tics_remaining[BUFF_COUNT];
};
@@ -5136,7 +5144,7 @@ struct SendAA_Struct {
/*0040*/ uint32 prereq_skill; //is < 0, abs() is category #
/*0044*/ uint32 prereq_minpoints; //min points in the prereq
/*0048*/ uint32 type;
/*0052*/ uint32 spellid;
/*0052*/ int32 spellid;
/*0056*/ uint32 spell_type;
/*0060*/ uint32 spell_refresh;
/*0064*/ uint16 classes;
@@ -5637,7 +5645,7 @@ struct HideCorpse_Struct
struct BuffIconEntry_Struct
{
uint32 buff_slot;
uint32 spell_id;
int32 spell_id;
int32 tics_remaining;
uint32 num_hits;
char caster[64];
@@ -5648,7 +5656,7 @@ struct BuffIcon_Struct
uint32 entity_id;
uint8 all_buffs;
uint16 count;
uint8 type; // 0 = self buff window, 1 = self target window, 4 = group, 5 = PC, 7 = NPC
uint8 type; // 0 = self buff window, 1 = self target window, 2 = pet buff or target window, 4 = group, 5 = PC, 7 = NPC
int32 tic_timer;
int32 name_lengths; // so ahh we kind of do these packets hacky, this is the total length of all the names to make creating the real packets in the translators easier
BuffIconEntry_Struct entries[0];
@@ -6236,6 +6244,12 @@ struct SuspendMercenary_Struct {
/*0001*/
};
// [OPCode: 0x1b37 (RoF2)] [Client->Server] [Size: 4]
struct SwitchMercenary_Struct {
/*0000*/ uint32 MercIndex; // 0-based UI index into owned merc list
/*0004*/
};
// [OPCode: 0x2528] On Live as of April 2 2012 [Server->Client] [Size: 4]
// Response to suspend merc with timestamp
struct SuspendMercenaryResponse_Struct {
+2 -2
View File
@@ -26,13 +26,13 @@
//this is the only part of an EQStream that is seen by the application.
typedef enum {
enum EQStreamState {
ESTABLISHED,
CLOSING, //waiting for pending data to flush.
DISCONNECTING, //have sent disconnect, waiting for their disconnect reply.
CLOSED, //received a disconnect from remote side.
UNESTABLISHED
} EQStreamState;
};
class EQApplicationPacket;
class OpcodeManager;
+1 -3
View File
@@ -33,10 +33,8 @@
#include <sys/stat.h>
#ifdef _WINDOWS
#include <conio.h>
#include "common/platform/platform.h"
#include <direct.h>
#include <process.h>
#include <windows.h>
#else
#include <sys/stat.h>
#include <thread>
+36 -29
View File
@@ -15,36 +15,43 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/types.h"
#include "common/platform/posix/include_pthreads.h"
#include "common/platform/win/include_windows.h"
#include "common/event/event_loop.h"
#include "uv.h"
class Mutex {
public:
Mutex();
~Mutex();
namespace EQ {
void lock();
void unlock();
bool trylock();
protected:
private:
#if defined _WINDOWS
CRITICAL_SECTION CSMutex;
#else
pthread_mutex_t CSMutex;
#endif
};
EventLoop& EventLoop::Get()
{
thread_local EventLoop inst;
return inst;
}
class LockMutex {
public:
LockMutex(Mutex* in_mut, bool iLock = true);
~LockMutex();
void unlock();
void lock();
private:
bool locked;
Mutex* mut;
};
EventLoop::EventLoop()
: m_loop(std::make_unique<uv_loop_t>())
{
memset(m_loop.get(), 0, sizeof(uv_loop_t));
uv_loop_init(m_loop.get());
}
EventLoop::~EventLoop()
{
uv_loop_close(m_loop.get());
}
void EventLoop::Process()
{
uv_run(m_loop.get(), UV_RUN_NOWAIT);
}
void EventLoop::Run()
{
uv_run(m_loop.get(), UV_RUN_DEFAULT);
}
void EventLoop::Shutdown()
{
uv_stop(m_loop.get());
}
} // namespace EQ
+19 -36
View File
@@ -17,48 +17,31 @@
*/
#pragma once
#include "common/platform/win/include_windows.h" // uv.h is going to include it so let's do it first.
#include "uv.h" // FIXME: hide this
#include <memory>
#include <cstring>
typedef struct uv_loop_s uv_loop_t;
namespace EQ
namespace EQ {
class EventLoop
{
class EventLoop
{
public:
static EventLoop &Get() {
static thread_local EventLoop inst;
return inst;
}
public:
static EventLoop& Get();
~EventLoop() {
uv_loop_close(&m_loop);
}
~EventLoop();
EventLoop(const EventLoop&) = delete;
EventLoop& operator=(const EventLoop&) = delete;
void Process() {
uv_run(&m_loop, UV_RUN_NOWAIT);
}
void Process();
void Run();
void Shutdown();
void Run() {
uv_run(&m_loop, UV_RUN_DEFAULT);
}
uv_loop_t* Handle() { return m_loop.get(); }
void Shutdown() {
uv_stop(&m_loop);
}
private:
EventLoop();
uv_loop_t* Handle() { return &m_loop; }
std::unique_ptr<uv_loop_t> m_loop;
};
private:
EventLoop() {
memset(&m_loop, 0, sizeof(uv_loop_t));
uv_loop_init(&m_loop);
}
EventLoop(const EventLoop&);
EventLoop& operator=(const EventLoop&);
uv_loop_t m_loop;
};
}
} // namespace EQ
+128
View File
@@ -0,0 +1,128 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "task_scheduler.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
namespace EQ::Event {
static constexpr int DefaultThreadCount = 4;
struct TaskScheduler::SchedulerData
{
std::atomic_bool running{ false };
std::vector<std::thread> threads;
std::mutex lock;
std::condition_variable cv;
std::queue<std::function<void()>> tasks;
};
TaskScheduler::TaskScheduler()
: m_data(std::make_unique<SchedulerData>())
{
Start(DefaultThreadCount);
}
TaskScheduler::TaskScheduler(size_t threads)
{
Start(threads);
}
TaskScheduler::~TaskScheduler()
{
Stop();
}
void TaskScheduler::Start(size_t threads)
{
if (m_data->running.exchange(true))
return;
m_data->threads.reserve(threads);
for (size_t i = 0; i < threads; ++i)
{
m_data->threads.emplace_back(
[this]{ ProcessWork(); });
}
}
void TaskScheduler::Stop()
{
if (!m_data->running.exchange(false))
return;
m_data->cv.notify_all();
for (auto& t : m_data->threads)
{
t.join();
}
m_data->threads.clear();
}
void TaskScheduler::ProcessWork()
{
for (;;)
{
std::function<void()> work;
{
std::unique_lock lock(m_data->lock);
m_data->cv.wait(lock,
[this]
{
return !m_data->running || !m_data->tasks.empty();
});
if (!m_data->running)
{
return;
}
work = std::move(m_data->tasks.front());
m_data->tasks.pop();
}
work();
}
}
void TaskScheduler::AddTask(std::function<void()>&& task)
{
if (!m_data->running)
{
throw std::runtime_error("Enqueue on stopped scheduler.");
}
{
std::scoped_lock lock(m_data->lock);
m_data->tasks.push(std::move(task));
}
m_data->cv.notify_one();
}
} // namespace EQ::Event
+33 -104
View File
@@ -17,116 +17,45 @@
*/
#pragma once
#include <condition_variable>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
#include <memory>
namespace EQ
namespace EQ::Event {
class TaskScheduler
{
namespace Event
public:
TaskScheduler();
TaskScheduler(size_t threads);
~TaskScheduler();
void Start(size_t threads);
void Stop();
template <typename Fn, typename... Args>
auto Enqueue(Fn&& fn, Args&&... args) -> std::future<typename std::invoke_result<Fn, Args...>::type>
{
class TaskScheduler
{
public:
static const int DefaultThreadCount = 4;
TaskScheduler() : _running(false)
using return_type = typename std::invoke_result<Fn, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
[fn = std::forward<Fn>(fn), ...args = std::forward<Args>(args)]() mutable
{
Start(DefaultThreadCount);
}
TaskScheduler(size_t threads) : _running(false)
{
Start(threads);
return fn(std::forward<Args>(args)...);
}
);
~TaskScheduler() {
Stop();
}
void Start(size_t threads) {
if (true == _running) {
return;
}
_running = true;
for (size_t i = 0; i < threads; ++i) {
_threads.emplace_back(std::thread(std::bind(&TaskScheduler::ProcessWork, this)));
}
}
void Stop() {
if (false == _running) {
return;
}
{
std::unique_lock<std::mutex> lock(_lock);
_running = false;
}
_cv.notify_all();
for (auto &t : _threads) {
t.join();
}
}
template<typename Fn, typename... Args>
auto Enqueue(Fn&& fn, Args&&... args) -> std::future<typename std::invoke_result<Fn, Args...>::type> {
using return_type = typename std::invoke_result<Fn, Args...>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(_lock);
if (false == _running) {
throw std::runtime_error("Enqueue on stopped scheduler.");
}
_tasks.emplace([task]() { (*task)(); });
}
_cv.notify_one();
return res;
}
private:
void ProcessWork() {
for (;;) {
std::function<void()> work;
{
std::unique_lock<std::mutex> lock(_lock);
_cv.wait(lock, [this] { return !_running || !_tasks.empty(); });
if (false == _running) {
return;
}
work = std::move(_tasks.front());
_tasks.pop();
}
work();
}
}
bool _running = true;
std::vector<std::thread> _threads;
std::mutex _lock;
std::condition_variable _cv;
std::queue<std::function<void()>> _tasks;
};
AddTask([task] { (*task)(); });
return task->get_future();
}
}
private:
void AddTask(std::function<void()>&& task);
void ProcessWork();
struct SchedulerData;
std::unique_ptr<SchedulerData> m_data;
};
} // namespace EQ::Event
+90
View File
@@ -0,0 +1,90 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common/event/timer.h"
#include "uv.h"
namespace EQ {
Timer::Timer(callback_t cb)
: m_cb(std::move(cb))
{
}
Timer::Timer(uint64_t duration_ms, bool repeats, callback_t cb)
: m_cb(std::move(cb))
{
Start(duration_ms, repeats);
}
Timer::~Timer()
{
Stop();
}
void Timer::Start(uint64_t duration_ms, bool repeats)
{
if (!m_timer)
{
uv_loop_t* loop = EventLoop::Get().Handle();
m_timer = std::make_unique<uv_timer_t>();
memset(m_timer.get(), 0, sizeof(uv_timer_t));
uv_timer_init(loop, m_timer.get());
m_timer->data = this;
if (repeats)
{
uv_timer_start(m_timer.get(), [](uv_timer_t* handle)
{
Timer* t = static_cast<Timer*>(handle->data);
t->Execute();
}, duration_ms, duration_ms);
}
else
{
uv_timer_start(m_timer.get(),
[](uv_timer_t* handle)
{
Timer* t = static_cast<Timer*>(handle->data);
t->Stop();
t->Execute();
}, duration_ms, 0);
}
}
}
void Timer::Stop()
{
if (m_timer)
{
uv_close(reinterpret_cast<uv_handle_t*>(m_timer.release()),
[](uv_handle_t* handle)
{
delete reinterpret_cast<uv_timer_t*>(handle);
});
}
}
void Timer::Execute()
{
m_cb(this);
}
} // namespace EQ
+20 -58
View File
@@ -19,69 +19,31 @@
#include "event_loop.h"
#include <cstring>
#include <functional>
#include <memory>
typedef struct uv_timer_s uv_timer_t;
namespace EQ {
class Timer
{
public:
Timer(std::function<void(Timer *)> cb)
{
m_timer = nullptr;
m_cb = cb;
}
Timer(uint64_t duration_ms, bool repeats, std::function<void(Timer *)> cb)
{
m_timer = nullptr;
m_cb = cb;
Start(duration_ms, repeats);
}
class Timer
{
public:
using callback_t = std::function<void(Timer*)>;
Timer(callback_t cb);
Timer(uint64_t duration_ms, bool repeats, callback_t cb);
~Timer()
{
Stop();
}
~Timer();
void Start(uint64_t duration_ms, bool repeats) {
auto loop = EventLoop::Get().Handle();
if (!m_timer) {
m_timer = new uv_timer_t;
memset(m_timer, 0, sizeof(uv_timer_t));
uv_timer_init(loop, m_timer);
m_timer->data = this;
void Start(uint64_t duration_ms, bool repeats);
void Stop();
if (repeats) {
uv_timer_start(m_timer, [](uv_timer_t *handle) {
Timer *t = (Timer*)handle->data;
t->Execute();
}, duration_ms, duration_ms);
}
else {
uv_timer_start(m_timer, [](uv_timer_t *handle) {
Timer *t = (Timer*)handle->data;
t->Stop();
t->Execute();
}, duration_ms, 0);
}
}
}
void Stop() {
if (m_timer) {
uv_close((uv_handle_t*)m_timer, [](uv_handle_t* handle) {
delete (uv_timer_t *)handle;
});
m_timer = nullptr;
}
}
private:
void Execute() {
m_cb(this);
}
private:
void Execute();
uv_timer_t *m_timer;
std::function<void(Timer*)> m_cb;
};
}
std::unique_ptr<uv_timer_t> m_timer;
callback_t m_cb;
};
} // namespace EQ
+2 -2
View File
@@ -56,12 +56,12 @@ void PlayerEventLogs::Init()
auto s = PlayerEventLogSettingsRepository::All(*m_database);
std::vector<int> db{};
db.reserve(s.size());
for (auto &e: s) {
for (auto& e: s) {
if (e.id >= PlayerEvent::MAX) {
continue;
}
m_settings[e.id] = e;
db.emplace_back(e.id);
db.emplace_back(static_cast<int>(e.id));
}
std::vector<PlayerEventLogSettingsRepository::PlayerEventLogSettings> settings_to_insert{};
+2 -2
View File
@@ -960,7 +960,7 @@ namespace PlayerEvent {
struct ResurrectAcceptEvent {
std::string resurrecter_name;
std::string spell_name;
uint32 spell_id;
int32 spell_id;
// cereal
template <class Archive>
@@ -1058,7 +1058,7 @@ namespace PlayerEvent {
uint32 killer_id;
std::string killer_name;
int64 damage;
uint32 spell_id;
int32 spell_id;
std::string spell_name;
int skill_id;
std::string skill_name;
+2 -2
View File
@@ -46,7 +46,7 @@
#define GUILD_INITIATE 7
#define GUILD_RECRUIT 8
typedef enum {
enum GuildAction {
GUILD_ACTION_BANNER_CHANGE = 1,
GUILD_ACTION_BANNER_PLANT = 2,
GUILD_ACTION_BANNER_REMOVE = 3,
@@ -77,6 +77,6 @@ typedef enum {
GUILD_ACTION_REAL_ESTATE_GUILD_PLOT_SELL = 28,
GUILD_ACTION_REAL_ESTATE_MODIFY_TROPHIES = 29,
GUILD_ACTION_MEMBERS_DEMOTE_SELF = 30,
} GuildAction;
};
constexpr int format_as(GuildAction action) { return static_cast<int>(action); }
+1 -1
View File
@@ -349,7 +349,7 @@ bool EQ::InventoryProfile::SwapItem(
fail_state = swapLevel;
return false;
}
if (source_item_instance->IsEvolving() > 0) {
if (source_item_instance->IsEvolving()) {
source_item_instance->SetEvolveEquipped(true);
}
}
+34 -125
View File
@@ -25,14 +25,24 @@
#include "common/net/dns.h"
#include "fmt/format.h"
#include <cstring>
#include <csignal>
#include <iostream>
#include <string>
#include <vector>
/**
* @param ip
* @return
*/
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
uint32_t IpUtil::IPToUInt(const std::string &ip)
{
int a, b, c, d;
@@ -49,12 +59,6 @@ uint32_t IpUtil::IPToUInt(const std::string &ip)
return addr;
}
/**
* @param ip
* @param network
* @param mask
* @return
*/
bool IpUtil::IsIpInRange(const std::string &ip, const std::string &network, const std::string &mask)
{
uint32_t ip_addr = IpUtil::IPToUInt(ip);
@@ -67,10 +71,6 @@ bool IpUtil::IsIpInRange(const std::string &ip, const std::string &network, cons
return ip_addr >= net_lower && ip_addr <= net_upper;
}
/**
* @param ip
* @return
*/
bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip)
{
return (
@@ -80,30 +80,13 @@ bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip)
);
}
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#include <iostream>
#include <string>
#include <cstring>
std::string IpUtil::GetLocalIPAddress()
{
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
return "";
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
return "";
}
#endif
char my_ip_address[INET_ADDRSTRLEN];
@@ -114,10 +97,10 @@ std::string IpUtil::GetLocalIPAddress()
// Create a UDP socket
#ifdef _WIN32
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == INVALID_SOCKET) {
WSACleanup();
return "";
}
if (sockfd == INVALID_SOCKET) {
WSACleanup();
return "";
}
#else
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
@@ -200,98 +183,24 @@ std::string IpUtil::GetPublicIPAddress()
return {};
}
std::string IpUtil::DNSLookupSync(const std::string &addr, int port)
{
auto task_runner = new EQ::Event::TaskScheduler();
auto res = task_runner->Enqueue(
[&]() -> std::string {
bool running = true;
std::string ret;
EQ::Net::DNSLookup(
addr, port, false, [&](const std::string &addr) {
ret = addr;
if (addr.empty()) {
ret = "";
running = false;
}
return ret;
}
);
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
auto &loop = EQ::EventLoop::Get();
while (running) {
if (!ret.empty()) {
running = false;
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() > 1500) {
LogInfo(
"Deadline exceeded [{}]",
1500
);
running = false;
}
loop.Process();
}
return ret;
}
);
std::string result = res.get();
safe_delete(task_runner);
return result;
}
bool IpUtil::IsIPAddress(const std::string &ip_address)
{
struct sockaddr_in sa{};
int result = inet_pton(AF_INET, ip_address.c_str(), &(sa.sin_addr));
sockaddr_in sa{};
int result = inet_pton(AF_INET, ip_address.c_str(), &(sa.sin_addr));
return result != 0;
}
#include <iostream>
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // Link against Winsock library
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
#include <iostream>
#include <string>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h> // For inet_pton
#pragma comment(lib, "ws2_32.lib") // Link against Winsock library
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // For inet_pton
#include <unistd.h>
#endif
bool IpUtil::IsPortInUse(const std::string& ip, int port) {
bool IpUtil::IsPortInUse(const std::string& ip, int port)
{
bool in_use = false;
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed\n";
return true; // Assume in use on failure
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed\n";
return true; // Assume in use on failure
}
#endif
int sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -319,20 +228,20 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) {
std::cerr << "Invalid IP address format: " << ip << std::endl;
#ifdef _WIN32
closesocket(sock);
WSACleanup();
WSACleanup();
#else
close(sock);
#endif
return true; // Assume in use on failure
}
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
if (bind(sock, (sockaddr*)&addr, sizeof(addr)) < 0) {
in_use = true; // Bind failed, port is in use
}
#ifdef _WIN32
closesocket(sock);
WSACleanup();
WSACleanup();
#else
close(sock);
#endif
+1 -4
View File
@@ -29,10 +29,7 @@ public:
static bool IsIpInPrivateRfc1918(const std::string &ip);
static std::string GetLocalIPAddress();
static std::string GetPublicIPAddress();
static std::string DNSLookupSync(
const std::string &addr,
int port
);
static bool IsIPAddress(const std::string &ip_address);
static bool IsPortInUse(const std::string& ip, int port);
+1
View File
@@ -320,6 +320,7 @@ bool EQ::ItemInstance::IsAugmentSlotAvailable(int32 augment_type, uint8 slot) co
}
return (
slot < invaug::SOCKET_COUNT &&
(
augment_type == -1 ||
(
-8
View File
@@ -271,11 +271,7 @@ static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readVa
namespace Json {
#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
typedef std::unique_ptr<CharReader> CharReaderPtr;
#else
typedef std::auto_ptr<CharReader> CharReaderPtr;
#endif
// Implementation of class Features
// ////////////////////////////////
@@ -4153,11 +4149,7 @@ Value& Path::make(Value& root) const {
namespace Json {
#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
typedef std::unique_ptr<StreamWriter> StreamWriterPtr;
#else
typedef std::auto_ptr<StreamWriter> StreamWriterPtr;
#endif
static bool containsControlCharacter(const char* str) {
while (*str) {
+31
View File
@@ -0,0 +1,31 @@
//
// Created by dannu on 4/18/2026.
//
#include "links.h"
#include "spdat.h"
void Links::FormatItemLink(char* Buffer, size_t BufferSize, const EQ::ItemInstance* item)
{
// TODO: Reverse 0x14064B220 to get definition of this function
}
void Links::FormatSpellLink(char* Buffer, size_t BufferSize, int32_t SpellID,
const char* spellNameOverride)
{
snprintf(Buffer, BufferSize, "%c%d3^%d^0^'%s%c", ITEM_TAG_CHAR, ETAG_SPELL, SpellID,
spellNameOverride && spellNameOverride[0] ? spellNameOverride : GetSpellName(SpellID), ITEM_TAG_CHAR);
}
void Links::FormatDialogLink(char* Buffer, size_t BufferSize, std::string_view keyword, std::string_view text)
{
if (text.empty()) {
snprintf(Buffer, BufferSize, "%c%d%.*s%c", ITEM_TAG_CHAR, ETAG_DIALOG_RESPONSE,
static_cast<int>(keyword.length()), keyword.data(), ITEM_TAG_CHAR);
} else {
snprintf(Buffer, BufferSize, "%c%d%.*s:%.*s%c", ITEM_TAG_CHAR, ETAG_DIALOG_RESPONSE,
static_cast<int>(keyword.length()), keyword.data(),
static_cast<int>(text.length()), text.data(), ITEM_TAG_CHAR);
}
}
+61
View File
@@ -0,0 +1,61 @@
//
// Created by dannu on 4/18/2026.
//
#pragma once
#include "item_instance.h"
namespace EQ { class ItemInstance; }
namespace Links
{
// Max Link Size in bytes
constexpr size_t MAX_LINK_SIZE = 512;
// Universal link tag character
constexpr char ITEM_TAG_CHAR = '\x12';
// Enumeration of different types of item tags
enum ETagCodes
{
ETAG_ITEM = 0,
ETAG_PLAYER,
ETAG_SPAM,
ETAG_ACHIEVEMENT,
ETAG_DIALOG_RESPONSE,
ETAG_COMMAND,
ETAG_SPELL,
ETAG_FACTION,
ETAG_COMMAND2,
ETAG_UNKNOWN9,
ETAG_COUNT,
ETAG_FIRST = ETAG_ITEM,
ETAG_LAST = ETAG_UNKNOWN9,
ETAG_INVALID = -1,
};
//----------------------------------------------------------------------------
// Link Formatting -- Pulled from MQ code
// Create an achievement link for the given achievement.
// TODO: implement this when achievements are added, leave the signature here for reference. Code in eqlib's ItemLinks.cpp
// void FormatAchievementLink(char* Buffer, size_t BufferSize, const Achievement* achievement,
// std::string_view playerName);
// Create an item link from the given item.
void FormatItemLink(char* Buffer, size_t BufferSize, const EQ::ItemInstance* item);
// Create a spell link for the given spell, with optional spell name override. Spells on items often have
// spell name overrides that changes the display name of the spell.
void FormatSpellLink(char* Buffer, size_t BufferSize, int32_t SpellID,
const char* spellNameOverride = nullptr);
// Format text into a clickable dialog link. The keyword is the text that will be displayed in the chat window,
// and the text is the text that will be sent to the server when the link is clicked. If no text is provided,
// then the keyword will be used as the text.
void FormatDialogLink(char* Buffer, size_t BufferSize, std::string_view keyword,
std::string_view text = {});
}
+1 -2
View File
@@ -45,5 +45,4 @@ struct LootItem {
uint32 lootdrop_id; // required for zone state referencing
};
typedef std::list<LootItem*> LootItems;
using LootItems = std::list<LootItem*>;
+192
View File
@@ -0,0 +1,192 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ksm.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h> // For madvise
#include <unistd.h> // For sysconf, sbrk
#endif
#ifdef _WIN32
// Windows-specific functionality
void* PageAlignedAllocatorBase::allocateInternal(size_t size, size_t alignment)
{
void* ptr = malloc(size);
if (!ptr)
{
throw std::bad_alloc();
}
return ptr;
}
size_t PageAlignedAllocatorBase::getPageSize() const
{
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize; // Page size in bytes
}
namespace KSM {
// Windows-specific placeholder functions (no-op)
void CheckPageAlignment(void* ptr)
{
}
void* AllocatePageAligned(size_t size)
{
return memset(malloc(size), 0, size);
}
void MarkMemoryForKSM(void* start, size_t size)
{
}
void AlignHeapToPageBoundary()
{
}
void* MarkHeapStart()
{
return nullptr;
}
size_t MeasureHeapUsage(void* start)
{
return 0;
}
} // namespace KSM
#else
// Linux-specific functionality
void* PageAlignedAllocatorBase::allocateInternal(size_t size, size_t alignment)
{
void* ptr = nullptr;
if (posix_memalign(&ptr, alignment, size) != 0)
{
throw std::bad_alloc();
}
return ptr;
}
size_t PageAlignedAllocatorBase::getPageSize() const
{
return static_cast<size_t>(sysconf(_SC_PAGESIZE));
}
namespace KSM {
void CheckPageAlignment(void* ptr)
{
size_t page_size = sysconf(_SC_PAGESIZE);
if (reinterpret_cast<uintptr_t>(ptr) % page_size == 0)
{
LogKSMDetail("Memory is page-aligned [{}]", ptr);
}
else
{
LogKSMDetail("Memory is NOT page-aligned [{}]", ptr);
}
}
void* AllocatePageAligned(size_t size)
{
size_t page_size = sysconf(_SC_PAGESIZE);
void* aligned_ptr = nullptr;
if (posix_memalign(&aligned_ptr, page_size, size) != 0)
{
LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size);
}
std::memset(aligned_ptr, 0, size);
return aligned_ptr;
}
void MarkMemoryForKSM(void* start, size_t size)
{
if (madvise(start, size, MADV_MERGEABLE) == 0)
{
LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size);
}
else
{
perror("madvise failed");
}
}
void AlignHeapToPageBoundary()
{
size_t page_size = sysconf(_SC_PAGESIZE);
if (page_size == 0)
{
LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size);
return;
}
void* current_break = sbrk(0);
if (current_break == (void*)-1)
{
LogKSM("Failed to retrieve the current program break");
return;
}
uintptr_t current_address = reinterpret_cast<uintptr_t>(current_break);
size_t misalignment = current_address % page_size;
if (misalignment != 0)
{
size_t adjustment = page_size - misalignment;
if (sbrk(adjustment) == (void*)-1)
{
LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment);
return;
}
}
LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0));
}
void* MarkHeapStart()
{
void* current_pos = sbrk(0);
AlignHeapToPageBoundary();
return current_pos;
}
size_t MeasureHeapUsage(void* start)
{
void* current_break = sbrk(0);
return static_cast<char*>(current_break) - static_cast<char*>(start);
}
} // namespace KSM
#endif
+74
View File
@@ -0,0 +1,74 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/eqemu_logsys.h"
class PageAlignedAllocatorBase
{
protected:
void* allocateInternal(size_t amount, size_t alignment);
size_t getPageSize() const;
};
// Page-aligned allocator for std::vector
template <typename T>
class PageAlignedAllocator : public PageAlignedAllocatorBase
{
public:
using value_type = T;
PageAlignedAllocator() noexcept = default;
template <typename U>
PageAlignedAllocator(const PageAlignedAllocator<U>&) noexcept {}
T* allocate(std::size_t n)
{
size_t size = n * sizeof(T);
return static_cast<T*>(allocateInternal(size, getPageSize()));
}
void deallocate(T* p, std::size_t) noexcept
{
free(p);
}
};
template <typename T, typename U>
bool operator==(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
return true;
}
template <typename T, typename U>
bool operator!=(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
return false;
}
// Kernel Samepage Merging (KSM) functionality
namespace KSM {
void CheckPageAlignment(void* ptr);
void* AllocatePageAligned(size_t size);
void MarkMemoryForKSM(void* start, size_t size);
void AlignHeapToPageBoundary();
void* MarkHeapStart();
size_t MeasureHeapUsage(void* start);
} // namespace KSM
-234
View File
@@ -1,234 +0,0 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/eqemu_logsys.h"
#include <iostream>
#include <vector>
#include <cstring>
#ifdef _WIN32
#include <malloc.h> // For _aligned_malloc, _aligned_free
#include <windows.h>
#else
#include <sys/mman.h> // For madvise
#include <unistd.h> // For sysconf, sbrk
#endif
// Page-aligned allocator for std::vector
template <typename T>
class PageAlignedAllocator {
public:
using value_type = T;
PageAlignedAllocator() noexcept = default;
template <typename U>
PageAlignedAllocator(const PageAlignedAllocator<U>&) noexcept {}
T* allocate(std::size_t n) {
void* ptr = nullptr;
size_t size = n * sizeof(T);
#ifdef _WIN32
// Simply allocate memory without alignment
ptr = malloc(size);
if (!ptr) throw std::bad_alloc();
#else
size_t alignment = getPageSize(); // Get the system's page size
if (posix_memalign(&ptr, alignment, size) != 0) {
throw std::bad_alloc();
}
#endif
return static_cast<T*>(ptr);
}
void deallocate(T* p, std::size_t) noexcept {
free(p);
}
private:
size_t getPageSize() const
{
#ifdef _WIN32
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize; // Page size in bytes
#else
return static_cast<size_t>(sysconf(_SC_PAGESIZE));
#endif
};
};
template <typename T, typename U>
bool operator==(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
return true;
}
template <typename T, typename U>
bool operator!=(const PageAlignedAllocator<T>&, const PageAlignedAllocator<U>&) noexcept {
return false;
}
// Kernel Samepage Merging (KSM) functionality
namespace KSM {
#ifdef _WIN32
// Windows-specific placeholder functions (no-op)
inline void CheckPageAlignment(void* ptr) {
}
inline void* AllocatePageAligned(size_t size) {
return memset(malloc(size), 0, size);
}
inline void MarkMemoryForKSM(void* start, size_t size) {
}
inline void AlignHeapToPageBoundary() {
}
inline void* MarkHeapStart() {
return nullptr;
}
inline size_t MeasureHeapUsage(void* start) {
return 0;
}
#else
// Linux-specific functionality
inline void CheckPageAlignment(void* ptr) {
size_t page_size = sysconf(_SC_PAGESIZE);
if (reinterpret_cast<uintptr_t>(ptr) % page_size == 0) {
LogKSMDetail("Memory is page-aligned [{}]", ptr);
} else {
LogKSMDetail("Memory is NOT page-aligned [{}]", ptr);
}
}
inline void* AllocatePageAligned(size_t size) {
size_t page_size = sysconf(_SC_PAGESIZE);
void* aligned_ptr = nullptr;
if (posix_memalign(&aligned_ptr, page_size, size) != 0) {
LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size);
}
std::memset(aligned_ptr, 0, size);
return aligned_ptr;
}
inline void MarkMemoryForKSM(void* start, size_t size) {
if (madvise(start, size, MADV_MERGEABLE) == 0) {
LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size);
} else {
perror("madvise failed");
}
}
inline void AlignHeapToPageBoundary() {
size_t page_size = sysconf(_SC_PAGESIZE);
if (page_size == 0) {
LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size);
return;
}
void* current_break = sbrk(0);
if (current_break == (void*)-1) {
LogKSM("Failed to retrieve the current program break");
return;
}
uintptr_t current_address = reinterpret_cast<uintptr_t>(current_break);
size_t misalignment = current_address % page_size;
if (misalignment != 0) {
size_t adjustment = page_size - misalignment;
if (sbrk(adjustment) == (void*)-1) {
LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment);
return;
}
}
LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0));
}
inline void* MarkHeapStart() {
void* current_pos = sbrk(0);
AlignHeapToPageBoundary();
return current_pos;
}
inline size_t MeasureHeapUsage(void* start) {
void* current_break = sbrk(0);
return static_cast<char*>(current_break) - static_cast<char*>(start);
}
#endif
inline size_t getPageSize()
{
#ifdef _WIN32
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize; // Page size in bytes
#else
return static_cast<size_t>(sysconf(_SC_PAGESIZE)); // POSIX page size
#endif
};
template <typename T>
inline void PageAlignVectorAligned(std::vector<T, PageAlignedAllocator<T>>& vec) {
if (vec.empty()) {
return;
}
size_t page_size = getPageSize();
void* start = vec.data();
size_t size = vec.size() * sizeof(T);
// Check if the memory is page-aligned
if (reinterpret_cast<std::uintptr_t>(start) % page_size != 0) {
// Allocate a new aligned vector
std::vector<T, PageAlignedAllocator<T>> aligned_vec(vec.get_allocator());
aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy
// Copy elements from the original vector
aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end());
// Swap the aligned vector with the original vector
vec.swap(aligned_vec);
// Clear the temporary aligned vector to free its memory
aligned_vec.clear();
// Verify the new alignment
start = vec.data();
if (reinterpret_cast<std::uintptr_t>(start) % page_size != 0) {
throw std::runtime_error("Failed to align vector memory to page boundaries.");
}
LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size);
} else {
LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size);
}
#ifndef _WIN32
// Mark memory for KSM (only on non-Windows systems)
MarkMemoryForKSM(start, size);
#endif
}
}
-162
View File
@@ -1,162 +0,0 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mutex.h"
#include <iostream>
#define DEBUG_MUTEX_CLASS 0
#if DEBUG_MUTEX_CLASS >= 1
#endif
#ifdef _WINDOWS
bool IsTryLockSupported();
bool TrylockSupported = IsTryLockSupported();
bool IsTryLockSupported() {
OSVERSIONINFOEX osvi;
BOOL bOsVersionInfoEx;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
{
// If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.
osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) {
#if DEBUG_MUTEX_CLASS >= 1
std::cout << "Mutex::trylock() NOT supported" << std::endl;
#endif
return false;
}
}
// Tests for Windows NT product family.
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion >= 4) {
#if DEBUG_MUTEX_CLASS >= 1
std::cout << "Mutex::trylock() SUPPORTED" << std::endl;
#endif
return true;
}
else {
#if DEBUG_MUTEX_CLASS >= 1
std::cout << "Mutex::trylock() NOT supported" << std::endl;
#endif
return false;
}
}
#endif
Mutex::Mutex() {
#if DEBUG_MUTEX_CLASS >= 7
std::cout << "Constructing Mutex" << std::endl;
#endif
#ifdef _WINDOWS
InitializeCriticalSection(&CSMutex);
#else
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
#if defined(__CYGWIN__) || defined(__APPLE__) || defined(FREEBSD)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
#else
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
#endif
pthread_mutex_init(&CSMutex, &attr);
pthread_mutexattr_destroy(&attr);
#endif
}
Mutex::~Mutex() {
#ifdef _WINDOWS
DeleteCriticalSection(&CSMutex);
#else
#endif
}
void Mutex::lock() {
#if DEBUG_MUTEX_CLASS >= 5
if (!trylock()) {
std::cout << "Locking Mutex: Having to wait" << std::endl;
#ifdef _WINDOWS
EnterCriticalSection(&CSMutex);
#else
pthread_mutex_lock(&CSMutex);
#endif
}
#else
#ifdef _WINDOWS
EnterCriticalSection(&CSMutex);
#else
pthread_mutex_lock(&CSMutex);
#endif
#endif
}
bool Mutex::trylock() {
#ifdef _WINDOWS
#if(_WIN32_WINNT >= 0x0400)
if (TrylockSupported)
return TryEnterCriticalSection(&CSMutex);
else {
EnterCriticalSection(&CSMutex);
return true;
}
#else
EnterCriticalSection(&CSMutex);
return true;
#endif
#else
return (pthread_mutex_trylock(&CSMutex) == 0);
#endif
}
void Mutex::unlock() {
#ifdef _WINDOWS
LeaveCriticalSection(&CSMutex);
#else
pthread_mutex_unlock(&CSMutex);
#endif
}
LockMutex::LockMutex(Mutex* in_mut, bool iLock) {
mut = in_mut;
locked = iLock;
if (locked) {
mut->lock();
}
}
LockMutex::~LockMutex() {
if (locked) {
mut->unlock();
}
}
void LockMutex::unlock() {
if (locked)
mut->unlock();
locked = false;
}
void LockMutex::lock() {
if (!locked)
mut->lock();
locked = true;
}
+10 -6
View File
@@ -18,7 +18,6 @@
#include "mysql_stmt.h"
#include "common/eqemu_logsys.h"
#include "common/mutex.h"
#include "common/timer.h"
#include <charconv>
@@ -31,14 +30,19 @@ void PreparedStmt::StmtDeleter::operator()(MYSQL_STMT* stmt) noexcept
// The connection must be locked when closing the stmt to avoid mysql errors
// in case another thread tries to use it during the close. If the mutex is
// changed to one that throws then exceptions need to be caught here.
LockMutex lock(mutex);
std::scoped_lock lock(mutex);
mysql_stmt_close(stmt);
}
PreparedStmt::PreparedStmt(MYSQL& mysql, std::string query, Mutex* mutex, StmtOptions opts)
: m_stmt(mysql_stmt_init(&mysql), { mutex }), m_query(std::move(query)), m_mutex(mutex), m_options(opts)
PreparedStmt::PreparedStmt(MYSQL& mysql, std::string query, DBcore::Mutex& mutex, StmtOptions opts)
: m_stmt(mysql_stmt_init(&mysql), { mutex })
, m_query(std::move(query))
, m_options(opts)
, m_mutex(mutex)
{
LockMutex lock(m_mutex);
std::scoped_lock lock(m_mutex);
if (mysql_stmt_prepare(m_stmt.get(), m_query.c_str(), static_cast<unsigned long>(m_query.size())) != 0)
{
ThrowError(fmt::format("Prepare error: {}", GetStmtError()));
@@ -186,7 +190,7 @@ void PreparedStmt::CheckArgs(size_t argc)
StmtResult PreparedStmt::DoExecute()
{
BenchTimer timer;
LockMutex lock(m_mutex);
std::scoped_lock lock(m_mutex);
if (m_need_bind && mysql_stmt_bind_param(m_stmt.get(), m_params.data()) != 0)
{
+7 -4
View File
@@ -17,10 +17,12 @@
*/
#pragma once
#include "mysql.h"
#include "common/dbcore.h"
#include <cassert>
#include <cstring>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <string_view>
@@ -183,7 +185,7 @@ public:
int64_t, uint64_t, float, double, bool, std::string_view, std::nullptr_t>;
PreparedStmt() = delete;
PreparedStmt(MYSQL& mysql, std::string query, Mutex* mutex, StmtOptions opts = {});
PreparedStmt(MYSQL& mysql, std::string query, DBcore::Mutex& mutex, StmtOptions opts = {});
const std::string& GetQuery() const { return m_query; }
StmtOptions GetOptions() const { return m_options; }
@@ -219,7 +221,8 @@ private:
struct StmtDeleter
{
Mutex* mutex = nullptr;
DBcore::Mutex& mutex;
void operator()(MYSQL_STMT* stmt) noexcept;
};
@@ -232,7 +235,7 @@ private:
std::string m_query;
StmtOptions m_options = {};
bool m_need_bind = true;
Mutex* m_mutex = nullptr; // connection mutex
DBcore::Mutex& m_mutex; // connection mutex
};
} // namespace mysql
+131
View File
@@ -0,0 +1,131 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dns.h"
#include "common/eqemu_logsys.h"
#include "common/event/event_loop.h"
#include "common/event/task_scheduler.h"
#include "uv.h"
namespace EQ::Net {
struct DNSBaton
{
dns_callback_t cb;
bool ipv6;
};
void DNSLookup(const std::string& addr, int port, bool ipv6, dns_callback_t cb)
{
addrinfo hints = {};
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
uv_loop_t* loop = EQ::EventLoop::Get().Handle();
uv_getaddrinfo_t* resolver = new uv_getaddrinfo_t();
memset(resolver, 0, sizeof(uv_getaddrinfo_t));
std::string port_str = std::to_string(port);
DNSBaton* baton = new DNSBaton();
baton->cb = std::move(cb);
baton->ipv6 = ipv6;
resolver->data = baton;
uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t* req, int status, addrinfo* result)
{
DNSBaton* baton = static_cast<DNSBaton*>(req->data);
dns_callback_t dns_callback = std::move(baton->cb);
bool ipv6 = baton->ipv6;
delete baton;
delete req;
if (status < 0)
{
dns_callback({});
return;
}
char addr[40] = {};
if (ipv6)
{
uv_ip6_name(reinterpret_cast<sockaddr_in6*>(result->ai_addr), addr, 40);
}
else
{
uv_ip4_name(reinterpret_cast<sockaddr_in*>(result->ai_addr), addr, 40);
}
uv_freeaddrinfo(result);
dns_callback(addr);
}, addr.c_str(), port_str.c_str(), &hints);
}
std::string DNSLookupSync(const std::string& addr, int port, bool ipv6 /* = false */)
{
EQ::Event::TaskScheduler task_runner;
auto res = task_runner.Enqueue(
[addr, port, ipv6]() -> std::string
{
bool running = true;
std::string ret;
EQ::Net::DNSLookup(
addr, port, ipv6, [&](const std::string& addr) {
ret = addr;
running = !addr.empty();
return ret;
}
);
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
auto& loop = EQ::EventLoop::Get();
while (running) {
if (!ret.empty()) {
running = false;
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() > 1500) {
LogInfo(
"Deadline exceeded [{}]",
1500
);
running = false;
}
loop.Process();
}
return ret;
});
return res.get();
}
} // namespace EQ::Net
+5 -53
View File
@@ -17,63 +17,15 @@
*/
#pragma once
#include "common/event/event_loop.h"
#include <functional>
#include <string>
namespace EQ
{
namespace Net
{
static void DNSLookup(const std::string &addr, int port, bool ipv6, std::function<void(const std::string&)> cb) {
struct DNSBaton
{
std::function<void(const std::string&)> cb;
bool ipv6;
};
namespace EQ::Net {
addrinfo hints;
memset(&hints, 0, sizeof(addrinfo));
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
using dns_callback_t = std::function<void(const std::string&)>;
auto loop = EQ::EventLoop::Get().Handle();
uv_getaddrinfo_t *resolver = new uv_getaddrinfo_t();
memset(resolver, 0, sizeof(uv_getaddrinfo_t));
auto port_str = std::to_string(port);
DNSBaton *baton = new DNSBaton();
baton->cb = cb;
baton->ipv6 = ipv6;
resolver->data = baton;
void DNSLookup(const std::string& addr, int port, bool ipv6, dns_callback_t cb);
uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t* req, int status, addrinfo* res) {
DNSBaton *baton = (DNSBaton*)req->data;
if (status < 0) {
auto cb = baton->cb;
delete baton;
delete req;
cb("");
return;
}
std::string DNSLookupSync(const std::string& addr, int port, bool ipv6 = false);
char addr[40] = { 0 };
if (baton->ipv6) {
uv_ip6_name((struct sockaddr_in6*)res->ai_addr, addr, 40);
}
else {
uv_ip4_name((struct sockaddr_in*)res->ai_addr, addr, 40);
}
auto cb = baton->cb;
delete baton;
delete req;
uv_freeaddrinfo(res);
cb(addr);
}, addr.c_str(), port_str.c_str(), &hints);
}
}
}
} // namespace EQ::Net
+2
View File
@@ -19,7 +19,9 @@
#include "common/net/tcp_connection_pooling.h"
#include "common/platform/platform.h"
#include "uv.h"
#include <functional>
#include <memory>
#include <string>
+2 -2
View File
@@ -49,9 +49,9 @@ namespace EQ
WebsocketException(const std::string &msg)
: _msg(msg.empty() ? "Unknown Error" : msg) { }
~WebsocketException() throw() {}
~WebsocketException() noexcept {}
virtual char const *what() const throw() {
virtual char const *what() const noexcept override {
return _msg.c_str();
}
private:
+13 -14
View File
@@ -17,17 +17,16 @@
*/
#pragma once
static const char OP_SessionRequest = 0x01;
static const char OP_SessionResponse = 0x02;
static const char OP_Combined = 0x03;
static const char OP_SessionDisconnect = 0x05;
static const char OP_KeepAlive = 0x06;
static const char OP_SessionStatRequest = 0x07;
static const char OP_SessionStatResponse= 0x08;
static const char OP_Packet = 0x09;
static const char OP_Fragment = 0x0d;
static const char OP_OutOfOrderAck = 0x11;
static const char OP_Ack = 0x15;
static const char OP_AppCombined = 0x19;
static const char OP_OutOfSession = 0x1d;
static constexpr char OP_SessionRequest = 0x01;
static constexpr char OP_SessionResponse = 0x02;
static constexpr char OP_Combined = 0x03;
static constexpr char OP_SessionDisconnect = 0x05;
static constexpr char OP_KeepAlive = 0x06;
static constexpr char OP_SessionStatRequest = 0x07;
static constexpr char OP_SessionStatResponse = 0x08;
static constexpr char OP_Packet = 0x09;
static constexpr char OP_Fragment = 0x0d;
static constexpr char OP_OutOfOrderAck = 0x11;
static constexpr char OP_Ack = 0x15;
static constexpr char OP_AppCombined = 0x19;
static constexpr char OP_OutOfSession = 0x1d;
+26 -23
View File
@@ -142,10 +142,12 @@ RegularOpcodeManager::~RegularOpcodeManager() {
safe_delete_array(eq_to_emu);
}
bool RegularOpcodeManager::LoadOpcodes(const char *filename, bool report_errors) {
bool RegularOpcodeManager::LoadOpcodes(const char *filename, bool report_errors)
{
std::scoped_lock lock(MOpcodes);
NormalMemStrategy s;
s.it = this;
MOpcodes.lock();
loaded = true;
eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE];
@@ -158,32 +160,30 @@ bool RegularOpcodeManager::LoadOpcodes(const char *filename, bool report_errors)
memset(emu_to_eq, 0, sizeof(uint16)*_maxEmuOpcode);
bool ret = LoadOpcodesFile(filename, &s, report_errors);
MOpcodes.unlock();
return ret;
}
bool RegularOpcodeManager::ReloadOpcodes(const char *filename, bool report_errors) {
if(!loaded)
return(LoadOpcodes(filename));
bool RegularOpcodeManager::ReloadOpcodes(const char* filename, bool report_errors)
{
if (!loaded)
return LoadOpcodes(filename);
std::scoped_lock lock(MOpcodes);
NormalMemStrategy s;
s.it = this;
MOpcodes.lock();
memset(eq_to_emu, 0, sizeof(uint16)*MAX_EQ_OPCODE);
bool ret = LoadOpcodesFile(filename, &s, report_errors);
MOpcodes.unlock();
return(ret);
memset(eq_to_emu, 0, sizeof(uint16) * MAX_EQ_OPCODE);
return LoadOpcodesFile(filename, &s, report_errors);
}
uint16 RegularOpcodeManager::EmuToEQ(const EmuOpcode emu_op) {
//opcode is checked for validity in GetEQOpcode
uint16 res;
MOpcodes.lock();
res = emu_to_eq[emu_op];
MOpcodes.unlock();
{
std::scoped_lock lock(MOpcodes);
res = emu_to_eq[emu_op];
}
LogNetcodeDetail("[Opcode Manager] Translate emu [{}] ({:#06x}) eq [{:#06x}]", OpcodeNames[emu_op], emu_op, res);
@@ -193,15 +193,18 @@ uint16 RegularOpcodeManager::EmuToEQ(const EmuOpcode emu_op) {
return(res);
}
EmuOpcode RegularOpcodeManager::EQToEmu(const uint16 eq_op) {
EmuOpcode RegularOpcodeManager::EQToEmu(const uint16 eq_op)
{
//opcode is checked for validity in GetEmuOpcode
//Disabled since current live EQ uses the entire uint16 bitspace for opcodes
// if(eq_op > MAX_EQ_OPCODE)
// return(OP_Unknown);
//Disabled since current live EQ uses the entire uint16 bitspace for opcodes
// if(eq_op > MAX_EQ_OPCODE)
// return(OP_Unknown);
EmuOpcode res;
MOpcodes.lock();
res = eq_to_emu[eq_op];
MOpcodes.unlock();
{
std::scoped_lock lock(MOpcodes);
res = eq_to_emu[eq_op];
}
#ifdef DEBUG_TRANSLATE
fprintf(stderr, "M Translate EQ 0x%.4x to Emu %s (%d)\n", eq_op, OpcodeNames[res], res);
#endif
+20 -16
View File
@@ -18,10 +18,10 @@
#pragma once
#include "common/emu_opcodes.h"
#include "common/mutex.h"
#include "common/types.h"
#include <map>
#include <mutex>
//enable the use of shared mem opcodes for world and zone only
#ifdef ZONE
@@ -31,7 +31,8 @@
#define SHARED_OPCODES
#endif
class OpcodeManager {
class OpcodeManager
{
public:
OpcodeManager();
virtual ~OpcodeManager() {}
@@ -48,24 +49,27 @@ public:
EmuOpcode NameSearch(const char *name);
//This has to be public for stupid visual studio
class OpcodeSetStrategy {
class OpcodeSetStrategy
{
public:
virtual ~OpcodeSetStrategy() {} //shut up compiler!
virtual ~OpcodeSetStrategy() = default;
virtual void Set(EmuOpcode emu_op, uint16 eq_op) = 0;
};
protected:
bool loaded; //true if all opcodes loaded
Mutex MOpcodes; //this only protects the local machine
std::mutex MOpcodes; //this only protects the local machine
//in a shared manager, this dosent protect others
static bool LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s, bool report_errors);
};
class MutableOpcodeManager : public OpcodeManager {
class MutableOpcodeManager : public OpcodeManager
{
public:
MutableOpcodeManager() : OpcodeManager() {}
virtual bool Mutable() { return(true); }
MutableOpcodeManager() = default;
virtual bool Mutable() override { return true; }
virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) = 0;
};
@@ -108,16 +112,16 @@ public:
virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op);
protected:
class NormalMemStrategy : public OpcodeManager::OpcodeSetStrategy {
class NormalMemStrategy : public OpcodeManager::OpcodeSetStrategy
{
public:
virtual ~NormalMemStrategy() {} //shut up compiler!
RegularOpcodeManager *it;
void Set(EmuOpcode emu_op, uint16 eq_op);
};
friend class NormalMemStrategy;
RegularOpcodeManager* it;
uint16 *emu_to_eq;
EmuOpcode *eq_to_emu;
virtual void Set(EmuOpcode emu_op, uint16 eq_op) override;
};
uint16* emu_to_eq;
EmuOpcode* eq_to_emu;
uint32 EQOpcodeCount;
uint32 EmuOpcodeCount;
};
+60
View File
@@ -0,0 +1,60 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/emu_opcodes.h"
#include <vector>
#include <functional>
class Client;
class Mob;
class EQApplicationPacket;
class Buffs_Struct;
namespace ClientPatch {
class IBuff
{
public:
using BuffSequenceFunc = std::function<std::unique_ptr<EQApplicationPacket>(const Client*)>;
IBuff(uint32_t maxLongBuffs, uint32_t maxShortBuffs)
: m_maxLongBuffs(maxLongBuffs)
, m_maxShortBuffs(maxShortBuffs)
{}
IBuff() = delete;
virtual ~IBuff() = default;
virtual std::unique_ptr<EQApplicationPacket> BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot,
bool fade) const = 0;
virtual std::unique_ptr<EQApplicationPacket> RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const = 0;
virtual void SetRefreshType(std::unique_ptr<EQApplicationPacket>& packet, uint8_t refresh_type) const = 0;
virtual bool NeedsWearMessage() const = 0;
uint32_t ServerToPatchBuffSlot(uint32_t slot) const;
protected:
uint32_t m_maxLongBuffs;
uint32_t m_maxShortBuffs;
};
} // namespace Buff
+51
View File
@@ -0,0 +1,51 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// Migration path: replace string_ids.h usage with ID enum values one call site at a time.
class Client;
class Mob;
class EQApplicationPacket;
namespace ClientPatch {
template<typename... Args>
concept AllConstChar = (std::is_convertible_v<Args, const char*> && ...);
class IMessage
{
public:
using FormattedArgs = std::array<const char*, 9>;
IMessage() = default;
virtual ~IMessage() = default;
// these two are the basic string message packets
virtual std::unique_ptr<EQApplicationPacket> Simple(uint32_t color, uint32_t id) const = 0;
virtual std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const FormattedArgs& args) const = 0;
// These aren't technically messages, but they use the same format and are similar enough to include here
virtual std::unique_ptr<EQApplicationPacket> InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const = 0;
virtual std::unique_ptr<EQApplicationPacket> InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name, const char* spell_link) const = 0;
};
} // namespace Message
+123
View File
@@ -0,0 +1,123 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "client_version.h"
#include "common/emu_constants.h"
#include "common/patches/titanium.h"
#include "common/patches/titanium_limits.h"
#include "common/patches/sof.h"
#include "common/patches/sof_limits.h"
#include "common/patches/sod.h"
#include "common/patches/sod_limits.h"
#include "common/patches/uf.h"
#include "common/patches/uf_limits.h"
#include "common/patches/rof.h"
#include "common/patches/rof_limits.h"
#include "common/patches/rof2.h"
#include "common/patches/rof2_limits.h"
#include "common/patches/tob.h"
#include "common/patches/tob_limits.h"
#include <array>
using Version = EQ::versions::ClientVersion;
struct ClientComponents
{
explicit ClientComponents(Version version) : version(version)
{
switch (version) {
case Version::TOB:
buffComponent = std::make_unique<TOB::BuffComponent>(TOB::spells::LONG_BUFFS, TOB::spells::SHORT_BUFFS);
messageComponent = std::make_unique<TOB::MessageComponent>();
break;
case Version::RoF2:
buffComponent = std::make_unique<UF::BuffComponent>(RoF2::spells::LONG_BUFFS, RoF2::spells::SHORT_BUFFS);
messageComponent = std::make_unique<Titanium::MessageComponent>();
break;
case Version::RoF:
buffComponent = std::make_unique<UF::BuffComponent>(RoF::spells::LONG_BUFFS, RoF::spells::SHORT_BUFFS);
messageComponent = std::make_unique<Titanium::MessageComponent>();
break;
case Version::UF:
buffComponent = std::make_unique<UF::BuffComponent>(UF::spells::LONG_BUFFS, UF::spells::SHORT_BUFFS);
messageComponent = std::make_unique<Titanium::MessageComponent>();
break;
case Version::SoD:
buffComponent = std::make_unique<SoD::BuffComponent>(SoD::spells::LONG_BUFFS, SoD::spells::SHORT_BUFFS);
messageComponent = std::make_unique<Titanium::MessageComponent>();
break;
case Version::SoF:
buffComponent = std::make_unique<Titanium::BuffComponent>(SoF::spells::LONG_BUFFS, SoF::spells::SHORT_BUFFS);
messageComponent = std::make_unique<Titanium::MessageComponent>();
break;
case Version::Titanium:
buffComponent = std::make_unique<Titanium::BuffComponent>(Titanium::spells::LONG_BUFFS, Titanium::spells::SHORT_BUFFS);
messageComponent = std::make_unique<Titanium::MessageComponent>();
break;
default:
break;
}
}
const Version version;
std::unique_ptr<ClientPatch::IBuff> buffComponent;
std::unique_ptr<ClientPatch::IMessage> messageComponent;
};
// this array must be in the same order as the Version enum because it converts Version to index directly
static const std::array<ClientComponents, EQ::versions::ClientVersionCount> s_patches = {
{
ClientComponents(Version::Unknown), // empty
ClientComponents(Version::Client62), // empty
ClientComponents(Version::Titanium),
ClientComponents(Version::SoF),
ClientComponents(Version::SoD),
ClientComponents(Version::UF),
ClientComponents(Version::RoF),
ClientComponents(Version::RoF2),
ClientComponents(Version::TOB),
}
};
template<>
const std::unique_ptr<ClientPatch::IBuff>& GetComponent(Version version)
{
return s_patches.at(static_cast<uint32_t>(version)).buffComponent;
}
template<>
const std::unique_ptr<ClientPatch::IMessage>& GetComponent(Version version)
{
return s_patches.at(static_cast<uint32_t>(version)).messageComponent;
}
uint32_t ClientPatch::IBuff::ServerToPatchBuffSlot(uint32_t slot) const
{
// we're a disc
if (slot >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS)
return slot - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS +
m_maxLongBuffs + m_maxShortBuffs;
// we're a song
if (slot >= EQ::spells::LONG_BUFFS)
return slot - EQ::spells::LONG_BUFFS + m_maxLongBuffs;
// we're a normal buff
return slot; // as long as we guard against bad slots server side, we should be fine
}
@@ -15,37 +15,24 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/mutex.h"
#include "common/platform/posix/include_pthreads.h"
#include "common/platform/win/include_windows.h"
#include "common/emu_versions.h"
#include <memory>
//Sombody, someday needs to figure out how to implement a condition
//system on windows...
namespace ClientPatch {
class IBuff;
class IMessage;
}
// store all static functions for the different patches here
// store all static functions for the different patches here, this can return nullptr for unsupported patches
template <typename Component>
const std::unique_ptr<Component>& GetComponent(EQ::versions::ClientVersion version);
class Condition {
private:
#ifdef WIN32
enum {
SignalEvent = 0,
BroadcastEvent,
_eventCount
};
template <>
const std::unique_ptr<ClientPatch::IBuff>& GetComponent(EQ::versions::ClientVersion version);
HANDLE m_events[_eventCount];
uint32 m_waiters;
CRITICAL_SECTION CSMutex;
#else
pthread_cond_t cond;
pthread_mutex_t mutex;
#endif
public:
Condition();
void Signal();
void SignalAll();
void Wait();
// bool TimedWait(unsigned long usec);
~Condition();
};
template <>
const std::unique_ptr<ClientPatch::IMessage>& GetComponent(EQ::versions::ClientVersion version);
+1 -1
View File
@@ -353,7 +353,7 @@
0x05ea,
0x1b6f,
0x198e,
0x7bd6, OP_Buff
0x7bd6, OP_BuffDefinition
0x3501,
0x47ab,
0x7a9e, OP_World_Client_CRC1
+3
View File
@@ -21,6 +21,7 @@
#include "common/patches/rof2.h"
#include "common/patches/sod.h"
#include "common/patches/sof.h"
#include "common/patches/tob.h"
#include "common/patches/titanium.h"
#include "common/patches/uf.h"
@@ -33,6 +34,7 @@ void RegisterAllPatches(EQStreamIdentifier &into)
UF::Register(into);
RoF::Register(into);
RoF2::Register(into);
TOB::Register(into);
}
void ReloadAllPatches()
@@ -43,4 +45,5 @@ void ReloadAllPatches()
UF::Reload();
RoF::Reload();
RoF2::Reload();
TOB::Reload();
}
+6 -103
View File
@@ -67,7 +67,6 @@ namespace RoF
static inline spells::CastingSlot ServerToRoFCastingSlot(EQ::spells::CastingSlot slot);
static inline EQ::spells::CastingSlot RoFToServerCastingSlot(spells::CastingSlot slot);
static inline int ServerToRoFBuffSlot(int index);
static inline int RoFToServerBuffSlot(int index);
void Register(EQStreamIdentifier &into)
@@ -402,7 +401,7 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -410,8 +409,7 @@ namespace RoF
OUT(entityid);
OUT(buff.effect_type);
OUT(buff.level);
// just so we're 100% sure we get a 1.0f ...
eq->buff.bard_modifier = emu->buff.bard_modifier == 10 ? 1.0f : emu->buff.bard_modifier / 10.0f;
OUT(buff.bard_modifier);
OUT(buff.spellid);
OUT(buff.duration);
OUT(buff.player_id);
@@ -420,59 +418,13 @@ namespace RoF
OUT(buff.x);
OUT(buff.z);
// TODO: implement slot_data stuff
eq->slotid = ServerToRoFBuffSlot(emu->slotid);
OUT(slotid);
if (emu->bufffade == 1)
eq->bufffade = 1;
else
eq->bufffade = 2;
// Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon
EQApplicationPacket *outapp = nullptr;
if (eq->bufffade == 1)
{
outapp = new EQApplicationPacket(OP_BuffCreate, 29);
outapp->WriteUInt32(emu->entityid);
outapp->WriteUInt32(0); // tic timer
outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ?
outapp->WriteUInt16(1); // 1 buff in this packet
outapp->WriteUInt32(eq->slotid);
outapp->WriteUInt32(0xffffffff); // SpellID (0xffff to remove)
outapp->WriteUInt32(0); // Duration
outapp->WriteUInt32(0); // numhits
outapp->WriteUInt8(0); // Caster name
outapp->WriteUInt8(0); // Type
}
FINISH_ENCODE();
if (outapp)
dest->FastQueuePacket(&outapp); // Send the OP_BuffCreate to remove the buff
}
ENCODE(OP_BuffCreate)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm
__packet->size = sz;
__packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz);
__packet->WriteUInt32(emu->entity_id);
__packet->WriteUInt32(emu->tic_timer);
__packet->WriteUInt8(emu->all_buffs); // 1 indicates all buffs on the player (0 to add or remove a single buff)
__packet->WriteUInt16(emu->count);
for (int i = 0; i < emu->count; ++i)
{
__packet->WriteUInt32(emu->type == 0 ? ServerToRoFBuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot);
__packet->WriteUInt32(emu->entries[i].spell_id);
__packet->WriteUInt32(emu->entries[i].tics_remaining);
__packet->WriteUInt32(emu->entries[i].num_hits); // Unknown
__packet->WriteString(emu->entries[i].caster);
}
__packet->WriteUInt8(emu->type); // Unknown
FINISH_ENCODE();
}
@@ -1858,38 +1810,6 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
{
// The format of the RoF packet is identical to the OP_BuffCreate packet.
SETUP_VAR_ENCODE(PetBuff_Struct);
uint32 sz = 12 + (17 * emu->buffcount);
__packet->size = sz;
__packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz);
__packet->WriteUInt32(emu->petid);
__packet->WriteUInt32(0); // PlayerID ?
__packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff)
__packet->WriteUInt16(emu->buffcount);
for (uint16 i = 0; i < PET_BUFF_COUNT; ++i)
{
if (emu->spellid[i])
{
__packet->WriteUInt32(i);
__packet->WriteUInt32(emu->spellid[i]);
__packet->WriteUInt32(emu->ticsremaining[i]);
__packet->WriteUInt32(0); // numhits
__packet->WriteString("");
}
}
__packet->WriteUInt8(0); // some sort of type
FINISH_ENCODE();
}
ENCODE(OP_PlayerProfile)
{
EQApplicationPacket *in = *p;
@@ -2179,7 +2099,7 @@ namespace RoF
outapp->WriteUInt32(emu->buffs[r].counters);
outapp->WriteUInt32(emu->buffs[r].duration);
outapp->WriteUInt8(emu->buffs[r].level);
outapp->WriteUInt32(emu->buffs[r].spellid);
outapp->WriteSInt32 (emu->buffs[r].spellid);
outapp->WriteUInt8(effect_type); // Only ever seen 2
outapp->WriteUInt32(emu->buffs[r].num_hits);
outapp->WriteUInt32(0);
@@ -3221,7 +3141,7 @@ namespace RoF
buf.WriteString(new_message);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, std::move(buf));
dest->FastQueuePacket(&outapp, ack_req);
delete in;
@@ -3239,8 +3159,6 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs) { ENCODE_FORWARD(OP_BuffCreate); }
ENCODE(OP_TaskDescription)
{
EQApplicationPacket *in = *p;
@@ -4196,7 +4114,7 @@ namespace RoF
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -6263,21 +6181,6 @@ namespace RoF
}
}
// these should be optimized out for RoF since they should all boil down to return index :P
// but lets leave it here for future proofing
static inline int ServerToRoFBuffSlot(int index)
{
// we're a disc
if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS)
return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS +
spells::LONG_BUFFS + spells::SHORT_BUFFS;
// we're a song
if (index >= EQ::spells::LONG_BUFFS)
return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS;
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
static inline int RoFToServerBuffSlot(int index)
{
// we're a disc
+16 -22
View File
@@ -18,33 +18,27 @@
#pragma once
#include "common/struct_strategy.h"
#include "common/patches/uf.h"
class EQStreamIdentifier;
namespace RoF
namespace RoF {
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier &into);
extern void Reload();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "rof_ops.h"
};
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "rof_ops.h"
};
} /*RoF*/
+18 -111
View File
@@ -69,7 +69,6 @@ namespace RoF2
static inline spells::CastingSlot ServerToRoF2CastingSlot(EQ::spells::CastingSlot slot);
static inline EQ::spells::CastingSlot RoF2ToServerCastingSlot(spells::CastingSlot slot);
static inline int ServerToRoF2BuffSlot(int index);
static inline int RoF2ToServerBuffSlot(int index);
void Register(EQStreamIdentifier &into)
@@ -661,7 +660,7 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -678,59 +677,13 @@ namespace RoF2
OUT(buff.y);
OUT(buff.x);
OUT(buff.z);
eq->slotid = ServerToRoF2BuffSlot(emu->slotid);
OUT(slotid);
// TODO: implement slot_data stuff
if (emu->bufffade == 1)
eq->bufffade = 1;
else
eq->bufffade = 2;
// Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon
EQApplicationPacket *outapp = nullptr;
if (eq->bufffade == 1)
{
outapp = new EQApplicationPacket(OP_BuffCreate, 29);
outapp->WriteUInt32(emu->entityid);
outapp->WriteUInt32(0); // tic timer
outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ?
outapp->WriteUInt16(1); // 1 buff in this packet
outapp->WriteUInt32(eq->slotid);
outapp->WriteUInt32(0xffffffff); // SpellID (0xffff to remove)
outapp->WriteUInt32(0); // Duration
outapp->WriteUInt32(0); // numhits
outapp->WriteUInt8(0); // Caster name
outapp->WriteUInt8(0); // Type
}
FINISH_ENCODE();
if (outapp)
dest->FastQueuePacket(&outapp); // Send the OP_BuffCreate to remove the buff
}
ENCODE(OP_BuffCreate)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm
__packet->size = sz;
__packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz);
__packet->WriteUInt32(emu->entity_id);
__packet->WriteUInt32(emu->tic_timer);
__packet->WriteUInt8(emu->all_buffs); // 1 indicates all buffs on the player (0 to add or remove a single buff)
__packet->WriteUInt16(emu->count);
for (int i = 0; i < emu->count; ++i)
{
__packet->WriteUInt32(emu->type == 0 ? ServerToRoF2BuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot);
__packet->WriteUInt32(emu->entries[i].spell_id);
__packet->WriteUInt32(emu->entries[i].tics_remaining);
__packet->WriteUInt32(emu->entries[i].num_hits); // Unknown
__packet->WriteString(emu->entries[i].caster);
}
__packet->WriteUInt8(emu->type); // Unknown
FINISH_ENCODE();
}
@@ -753,7 +706,7 @@ namespace RoF2
ar(bl);
//packet size
auto packet_size = bl.item_name.length() + 1 + 34;
size_t packet_size = bl.item_name.length() + 1 + 34;
for (auto const &b: bl.trade_items) {
packet_size += b.item_name.length() + 1;
packet_size += 12;
@@ -1622,7 +1575,7 @@ namespace RoF2
//Log.LogDebugType(Logs::General, Logs::Netcode, "[ERROR] Yourname is %s", gu2->yourname);
int MemberCount = 1;
int PacketLength = 8 + strlen(gu2->leadersname) + 1 + 22 + strlen(gu2->yourname) + 1;
uint32 PacketLength = 8 + strlen(gu2->leadersname) + 1 + 22 + strlen(gu2->yourname) + 1;
for (int i = 0; i < 5; ++i)
{
@@ -2207,7 +2160,7 @@ namespace RoF2
char *Buffer = (char *)in->pBuffer;
int PacketSize = sizeof(structs::MercenaryMerchantList_Struct) - 4 + emu->MercTypeCount * 4;
uint32 PacketSize = sizeof(structs::MercenaryMerchantList_Struct) - 4 + emu->MercTypeCount * 4;
PacketSize += (sizeof(structs::MercenaryListEntry_Struct) - sizeof(structs::MercenaryStance_Struct)) * emu->MercCount;
uint32 r;
@@ -2278,15 +2231,19 @@ namespace RoF2
// There are 2 different sized versions of this packet depending if a merc is hired or not
if (emu->MercStatus >= 0)
{
PacketSize += sizeof(structs::MercenaryDataUpdate_Struct) + (sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct)) * emu->MercCount;
// Per-merc size: base struct minus Stances[1] and MercUnk05,
// then add back actual stances and name length per merc.
// MercUnk05 is a single trailing field after all mercs.
PacketSize += sizeof(structs::MercenaryDataUpdate_Struct);
uint32 r;
uint32 k;
for (r = 0; r < emu->MercCount; r++)
{
PacketSize += sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct) - sizeof(uint32); // subtract Stances[1] and MercUnk05
PacketSize += sizeof(structs::MercenaryStance_Struct) * emu->MercData[r].StanceCount;
PacketSize += strlen(emu->MercData[r].MercName); // Null Terminator size already accounted for in the struct
}
PacketSize += sizeof(uint32); // MercUnk05 - trailing field after all mercs
outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, PacketSize);
Buffer = (char *)outapp->pBuffer;
@@ -2312,15 +2269,14 @@ namespace RoF2
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].StanceCount);
VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercData[r].MercUnk03);
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->MercData[r].MercUnk04);
//VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // MercName
VARSTRUCT_ENCODE_STRING(Buffer, emu->MercData[r].MercName);
for (k = 0; k < emu->MercData[r].StanceCount; k++)
{
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].StanceIndex);
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].Stance);
}
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); // MercUnk05
}
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[0].MercUnk05); // MercUnk05 - trailing field (unlocked slot count)
}
else
{
@@ -2461,38 +2417,6 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
{
// The format of the RoF2 packet is identical to the OP_BuffCreate packet.
SETUP_VAR_ENCODE(PetBuff_Struct);
uint32 sz = 12 + (17 * emu->buffcount);
__packet->size = sz;
__packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz);
__packet->WriteUInt32(emu->petid);
__packet->WriteUInt32(0); // PlayerID ?
__packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff)
__packet->WriteUInt16(emu->buffcount);
for (uint16 i = 0; i < PET_BUFF_COUNT; ++i)
{
if (emu->spellid[i])
{
__packet->WriteUInt32(i);
__packet->WriteUInt32(emu->spellid[i]);
__packet->WriteUInt32(emu->ticsremaining[i]);
__packet->WriteUInt32(0); // num hits
__packet->WriteString("");
}
}
__packet->WriteUInt8(0); // some sort of type
FINISH_ENCODE();
}
ENCODE(OP_PlayerProfile)
{
EQApplicationPacket *in = *p;
@@ -2783,7 +2707,7 @@ namespace RoF2
outapp->WriteUInt32(emu->buffs[r].counters);
outapp->WriteUInt32(emu->buffs[r].duration);
outapp->WriteUInt8(emu->buffs[r].level);
outapp->WriteUInt32(emu->buffs[r].spellid);
outapp->WriteSInt32 (emu->buffs[r].spellid);
outapp->WriteUInt8(effect_type); // Only ever seen 2
outapp->WriteUInt32(emu->buffs[r].num_hits);
outapp->WriteUInt32(0);
@@ -3820,7 +3744,7 @@ namespace RoF2
buf.WriteString(new_message);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, std::move(buf));
dest->FastQueuePacket(&outapp, ack_req);
delete in;
@@ -3838,8 +3762,6 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs) { ENCODE_FORWARD(OP_BuffCreate); }
ENCODE(OP_TaskDescription)
{
EQApplicationPacket *in = *p;
@@ -4120,8 +4042,8 @@ namespace RoF2
std::begin(emu->items),
std::end(emu->items),
std::begin(eq->items),
[&](const uint32 x) {
return x;
[&](uint64 x) {
return static_cast<uint32>(x);
}
);
std::copy_n(
@@ -4599,7 +4521,7 @@ namespace RoF2
int k;
for (r = 0; r < entrycount; r++, emu++) {
int PacketSize = 206;
uint32 PacketSize = 206;
PacketSize += strlen(emu->name);
PacketSize += strlen(emu->lastName);
@@ -5142,7 +5064,7 @@ namespace RoF2
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -7492,21 +7414,6 @@ namespace RoF2
}
}
// these should be optimized out for RoF2 since they should all boil down to return index :P
// but lets leave it here for future proofing
static inline int ServerToRoF2BuffSlot(int index)
{
// we're a disc
if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS)
return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS +
spells::LONG_BUFFS + spells::SHORT_BUFFS;
// we're a song
if (index >= EQ::spells::LONG_BUFFS)
return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS;
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
static inline int RoF2ToServerBuffSlot(int index)
{
// we're a disc
+16 -22
View File
@@ -18,33 +18,27 @@
#pragma once
#include "common/struct_strategy.h"
#include "common/patches/rof.h"
class EQStreamIdentifier;
namespace RoF2
namespace RoF2 {
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier &into);
extern void Reload();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "rof2_ops.h"
};
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "rof2_ops.h"
};
}; /*RoF2*/
+2 -5
View File
@@ -43,8 +43,7 @@ E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_BuffDefinition)
E(OP_BuyerItems)
E(OP_CancelTrade)
E(OP_CastSpell)
@@ -100,7 +99,6 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -123,7 +121,6 @@ E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_TaskDescription)
E(OP_TaskHistoryReply)
E(OP_Track)
@@ -153,7 +150,7 @@ D(OP_Barter)
D(OP_BazaarSearch)
D(OP_BlockedBuffs)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_BuffRemoveRequest)
D(OP_BuyerItems)
D(OP_CastSpell)
+11 -11
View File
@@ -668,7 +668,7 @@ struct NewZone_Struct {
*/
struct MemorizeSpell_Struct {
uint32 slot; // Spot in the spell book/memorized slot
uint32 spell_id; // Spell id (200 or c8 is minor healing, etc)
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming
uint32 reduction; // lowers reuse
};
@@ -705,7 +705,7 @@ struct ManaChange_Struct
{
/*00*/ uint32 new_mana; // New Mana AMount
/*04*/ uint32 stamina;
/*08*/ uint32 spell_id;
/*08*/ int32 spell_id;
/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting?
/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like
/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it
@@ -719,7 +719,7 @@ struct SwapSpell_Struct
struct BeginCast_Struct
{
/*000*/ uint32 spell_id;
/*000*/ int32 spell_id;
/*004*/ uint16 caster_id;
/*006*/ uint32 cast_time; // in miliseconds
/*010*/
@@ -728,7 +728,7 @@ struct BeginCast_Struct
struct CastSpell_Struct
{
/*00*/ uint32 slot;
/*04*/ uint32 spell_id;
/*04*/ int32 spell_id;
/*08*/ InventorySlot_Struct inventory_slot; // slot for clicky item, Seen unknown of 131 = normal cast
/*20*/ uint32 target_id;
/*24*/ uint32 cs_unknown[2];
@@ -760,7 +760,7 @@ struct SpellBuff_Struct
/*002*/ uint8 unknown002; //pretty sure padding now
/*003*/ uint8 unknown003; // MQ2 used to call this "damage shield" -- don't see client referencing it, so maybe server side DS type tracking?
/*004*/ float bard_modifier;
/*008*/ uint32 spellid;
/*008*/ int32 spellid;
/*012*/ uint32 duration;
/*016*/ uint32 player_id; // caster ID, pretty sure just zone ID
/*020*/ uint32 num_hits;
@@ -791,7 +791,7 @@ struct BuffRemoveRequest_Struct
// not in use
struct BuffIconEntry_Struct {
/*000*/ uint32 buff_slot;
/*004*/ uint32 spell_id;
/*004*/ int32 spell_id;
/*008*/ uint32 tics_remaining;
/*012*/ uint32 num_hits;
// char name[0]; caster name is also here sometimes
@@ -1513,7 +1513,7 @@ struct CombatDamage_Struct
/* 00 */ uint16 target;
/* 02 */ uint16 source;
/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells
/* 05 */ uint32 spellid;
/* 05 */ int32 spellid;
/* 09 */ int32 damage;
/* 13 */ float force; // cd cc cc 3d
/* 17 */ float hit_heading; // see above notes in Action_Struct
@@ -1549,7 +1549,7 @@ struct Death_Struct
/*004*/ uint32 killer_id;
/*008*/ uint32 corpseid; // was corpseid
/*012*/ uint32 attack_skill; // was type
/*016*/ uint32 spell_id;
/*016*/ int32 spell_id;
/*020*/ uint32 bindzoneid; //bindzoneid?
/*024*/ uint32 damage;
/*028*/ uint32 unknown028;
@@ -2658,7 +2658,7 @@ struct GroupFollow_Struct { // Live Follow Struct
};
struct InspectBuffs_Struct {
/*000*/ uint32 spell_id[BUFF_COUNT];
/*000*/ int32 spell_id[BUFF_COUNT];
/*168*/ int32 tics_remaining[BUFF_COUNT];
};
@@ -3077,7 +3077,7 @@ struct Resurrect_Struct
/*024*/ char your_name[64];
/*088*/ uint32 unknown088;
/*092*/ char rezzer_name[64];
/*156*/ uint32 spellid;
/*156*/ int32 spellid;
/*160*/ char corpse_name[64];
/*224*/ uint32 action;
/*228*/ uint32 unknown228;
@@ -4581,7 +4581,7 @@ struct SendAA_Struct {
/*0045*/ uint32 prereq_minpoints_count; // mutliple prereqs at least 1, even no prereqs
/*0049*/ uint32 prereq_minpoints; //min points in the prereq
/*0053*/ uint32 type;
/*0057*/ uint32 spellid;
/*0057*/ int32 spellid;
/*0061*/ uint32 unknown057; // Introduced during HoT - Seen 1 - Maybe account status or enable/disable AA?
/*0065*/ uint32 spell_type;
/*0069*/ uint32 spell_refresh;
+2 -5
View File
@@ -27,8 +27,7 @@ E(OP_Barter)
E(OP_BazaarSearch)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_BuffDefinition)
E(OP_CancelTrade)
E(OP_CastSpell)
E(OP_ChannelMessage)
@@ -81,7 +80,6 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -104,7 +102,6 @@ E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_TaskDescription)
E(OP_TaskHistoryReply)
E(OP_Track)
@@ -131,7 +128,7 @@ D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_BlockedBuffs)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_BuffRemoveRequest)
D(OP_CastSpell)
D(OP_ChannelMessage)
+11 -11
View File
@@ -614,7 +614,7 @@ struct NewZone_Struct {
*/
struct MemorizeSpell_Struct {
uint32 slot; // Spot in the spell book/memorized slot
uint32 spell_id; // Spell id (200 or c8 is minor healing, etc)
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming
uint32 reduction; // lowers reuse
};
@@ -651,7 +651,7 @@ struct ManaChange_Struct
{
/*00*/ uint32 new_mana; // New Mana AMount
/*04*/ uint32 stamina;
/*08*/ uint32 spell_id;
/*08*/ int32 spell_id;
/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting?
/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like
/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it
@@ -665,7 +665,7 @@ struct SwapSpell_Struct
struct BeginCast_Struct
{
/*000*/ uint32 spell_id;
/*000*/ int32 spell_id;
/*004*/ uint16 caster_id;
/*006*/ uint32 cast_time; // in miliseconds
/*010*/
@@ -674,7 +674,7 @@ struct BeginCast_Struct
struct CastSpell_Struct
{
/*00*/ uint32 slot;
/*04*/ uint32 spell_id;
/*04*/ int32 spell_id;
/*08*/ InventorySlot_Struct inventory_slot; // slot for clicky item, Seen unknown of 131 = normal cast
/*20*/ uint32 target_id;
/*24*/ uint32 cs_unknown[2];
@@ -706,7 +706,7 @@ struct SpellBuff_Struct
/*002*/ uint8 unknown002; //pretty sure padding now
/*003*/ uint8 unknown003; // MQ2 used to call this "damage shield" -- don't see client referencing it, so maybe server side DS type tracking?
/*004*/ float bard_modifier;
/*008*/ uint32 spellid;
/*008*/ int32 spellid;
/*012*/ uint32 duration;
/*016*/ uint32 player_id; // caster ID, pretty sure just zone ID
/*020*/ uint32 num_hits;
@@ -737,7 +737,7 @@ struct BuffRemoveRequest_Struct
// not in use
struct BuffIconEntry_Struct {
/*000*/ uint32 buff_slot;
/*004*/ uint32 spell_id;
/*004*/ int32 spell_id;
/*008*/ uint32 tics_remaining;
/*012*/ uint32 num_hits;
// char name[0]; caster name is also here sometimes
@@ -1500,7 +1500,7 @@ struct CombatDamage_Struct
/* 00 */ uint16 target;
/* 02 */ uint16 source;
/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells
/* 05 */ uint32 spellid;
/* 05 */ int32 spellid;
/* 09 */ int32 damage;
/* 13 */ float force; // cd cc cc 3d
/* 17 */ float hit_heading; // see above notes in Action_Struct
@@ -1536,7 +1536,7 @@ struct Death_Struct
/*004*/ uint32 killer_id;
/*008*/ uint32 corpseid; // was corpseid
/*012*/ uint32 attack_skill; // was type
/*016*/ uint32 spell_id;
/*016*/ int32 spell_id;
/*020*/ uint32 bindzoneid; //bindzoneid?
/*024*/ uint32 damage;
/*028*/ uint32 unknown028;
@@ -2603,7 +2603,7 @@ struct GroupFollow_Struct { // Live Follow Struct
};
struct InspectBuffs_Struct {
/*000*/ uint32 spell_id[BUFF_COUNT];
/*000*/ int32 spell_id[BUFF_COUNT];
/*168*/ int32 tics_remaining[BUFF_COUNT];
};
@@ -3013,7 +3013,7 @@ struct Resurrect_Struct
/*024*/ char your_name[64];
/*088*/ uint32 unknown088;
/*092*/ char rezzer_name[64];
/*156*/ uint32 spellid;
/*156*/ int32 spellid;
/*160*/ char corpse_name[64];
/*224*/ uint32 action;
/*228*/ uint32 unknown228;
@@ -4333,7 +4333,7 @@ struct SendAA_Struct {
/*0045*/ uint32 prereq_minpoints_count; // mutliple prereqs at least 1, even no prereqs
/*0049*/ uint32 prereq_minpoints; //min points in the prereq
/*0053*/ uint32 type;
/*0057*/ uint32 spellid;
/*0057*/ int32 spellid;
/*0061*/ uint32 unknown057; // Introduced during HoT - Seen 1 - Maybe account status or enable/disable AA?
/*0065*/ uint32 spell_type;
/*0069*/ uint32 spell_refresh;
+57 -79
View File
@@ -31,6 +31,8 @@
#include "common/raid.h"
#include "common/rulesys.h"
#include "common/strings.h"
#include "zone/client.h"
#include "zone/mob.h"
#include <iostream>
#include <sstream>
@@ -61,7 +63,6 @@ namespace SoD
static inline spells::CastingSlot ServerToSoDCastingSlot(EQ::spells::CastingSlot slot);
static inline EQ::spells::CastingSlot SoDToServerCastingSlot(spells::CastingSlot slot);
static inline int ServerToSoDBuffSlot(int index);
static inline int SoDToServerBuffSlot(int index);
void Register(EQStreamIdentifier &into)
@@ -293,7 +294,7 @@ namespace SoD
dest->FastQueuePacket(&in, ack_req);
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -306,7 +307,7 @@ namespace SoD
OUT(buff.duration);
OUT(buff.counters);
OUT(buff.player_id);
eq->slotid = ServerToSoDBuffSlot(emu->slotid);
OUT(slotid);
OUT(bufffade);
FINISH_ENCODE();
@@ -1375,38 +1376,6 @@ namespace SoD
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
{
EQApplicationPacket *in = *p;
*p = nullptr;
unsigned char *__emu_buffer = in->pBuffer;
PetBuff_Struct *emu = (PetBuff_Struct *)__emu_buffer;
int PacketSize = 7 + (emu->buffcount * 13);
in->size = PacketSize;
in->pBuffer = new unsigned char[in->size];
char *Buffer = (char *)in->pBuffer;
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petid);
VARSTRUCT_ENCODE_TYPE(uint16, Buffer, emu->buffcount);
for (unsigned int i = 0; i < PET_BUFF_COUNT; ++i)
{
if (emu->spellid[i])
{
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, i);
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spellid[i]);
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->ticsremaining[i]);
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string. Name of the caster of the buff.
}
}
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); // I think this is actually some sort of type
delete[] __emu_buffer;
dest->FastQueuePacket(&in, ack_req);
}
ENCODE(OP_PlayerProfile)
{
SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct);
@@ -2128,7 +2097,7 @@ namespace SoD
buf.WriteString(new_message);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, std::move(buf));
dest->FastQueuePacket(&outapp, ack_req);
delete in;
@@ -2145,35 +2114,6 @@ namespace SoD
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
uint32 sz = 7 + (13 * emu->count);
__packet->size = sz;
__packet->pBuffer = new unsigned char[sz];
memset(__packet->pBuffer, 0, sz);
uchar *ptr = __packet->pBuffer;
*((uint32*)ptr) = emu->entity_id;
ptr += sizeof(uint32);
*((uint16*)ptr) = emu->count;
ptr += sizeof(uint16);
for (uint16 i = 0; i < emu->count; ++i)
{
*((uint32*)ptr) = emu->entries[i].buff_slot;
ptr += sizeof(uint32);
*((uint32*)ptr) = emu->entries[i].spell_id;
ptr += sizeof(uint32);
*((uint32*)ptr) = emu->entries[i].tics_remaining;
ptr += sizeof(uint32);
ptr += 1;
}
FINISH_ENCODE();
}
ENCODE(OP_TaskDescription)
{
EQApplicationPacket *in = *p;
@@ -2877,7 +2817,7 @@ namespace SoD
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -4259,19 +4199,6 @@ namespace SoD
}
}
static inline int ServerToSoDBuffSlot(int index)
{
// we're a disc
if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS)
return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS +
spells::LONG_BUFFS + spells::SHORT_BUFFS;
// we're a song
if (index >= EQ::spells::LONG_BUFFS)
return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS;
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
static inline int SoDToServerBuffSlot(int index)
{
// we're a disc
@@ -4284,4 +4211,55 @@ namespace SoD
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
std::unique_ptr<EQApplicationPacket> BuffComponent::RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const
{
if (opcode == OP_RefreshPetBuffs || opcode == OP_RefreshTargetBuffs) {
Buffs_Struct* buffs = mob->GetBuffs();
size_t buffer_size = 7; // 7 bytes outside the list
std::vector<uint32_t> send_slots;
if (slots.empty()) {
for (uint32_t slot = 0; slot < mob->GetMaxTotalSlots(); ++slot)
if (buffs[slot].spellid > 1) {
buffer_size += 13 + strlen(buffs[slot].caster_name); // 13 includes the null terminator
send_slots.push_back(slot);
}
} else {
for (uint32_t slot : slots)
if (slot < mob->GetMaxTotalSlots() && buffs[slot].spellid > 1) {
buffer_size += 13 + strlen(buffs[slot].caster_name);
send_slots.push_back(slot);
}
}
// SoD only supports target and pet refresh, not self refresh packets
SerializeBuffer buffer(buffer_size);
buffer.WriteUInt32(mob->GetID());
buffer.WriteUInt16(send_slots.size());
for (uint32_t slot : send_slots) {
buffer.WriteUInt32(ServerToPatchBuffSlot(slot));
buffer.WriteInt32(remove ? -1 : buffs[slot].spellid);
buffer.WriteInt32(buffs[slot].ticsremaining);
buffer.WriteString(buffs[slot].caster_name);
}
buffer.WriteUInt8(opcode == OP_RefreshPetBuffs ? 2 : 0);
return std::make_unique<EQApplicationPacket>(opcode, std::move(buffer));
}
return nullptr;
}
// 0 = self buff window, 1 = self target window, 2 = pet buff or target window, 4 = group, 5 = PC, 7 = NPC
void BuffComponent::SetRefreshType(std::unique_ptr<EQApplicationPacket>& packet, uint8_t refresh_type) const
{
if (packet)
packet->pBuffer[packet->size - 1] = refresh_type;
}
} /*SoD*/
+26 -20
View File
@@ -18,33 +18,39 @@
#pragma once
#include "common/struct_strategy.h"
#include "common/patches/sof.h"
class EQStreamIdentifier;
namespace SoD
namespace SoD {
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier &into);
extern void Reload();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "sod_ops.h"
};
class BuffComponent : public Titanium::BuffComponent
{
public:
BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : Titanium::BuffComponent(maxLongBuffs, maxShortBuffs) {}
BuffComponent() = delete;
~BuffComponent() override = default;
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "sod_ops.h"
};
std::unique_ptr<EQApplicationPacket> RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const override;
void SetRefreshType(std::unique_ptr<EQApplicationPacket>& packet, uint8_t refresh_type) const override;
};
} /*SoD*/
+2 -4
View File
@@ -23,7 +23,7 @@ E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_Barter)
E(OP_BazaarSearch)
E(OP_Buff)
E(OP_BuffDefinition)
E(OP_CancelTrade)
E(OP_ChannelMessage)
E(OP_CharInventory)
@@ -65,7 +65,6 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -80,7 +79,6 @@ E(OP_SomeItemPacketMaybe)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_TaskDescription)
E(OP_Track)
E(OP_Trader)
@@ -102,7 +100,7 @@ D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_CastSpell)
D(OP_ChannelMessage)
D(OP_CharacterCreate)
+9 -9
View File
@@ -480,7 +480,7 @@ struct NewZone_Struct {
*/
struct MemorizeSpell_Struct {
uint32 slot; // Spot in the spell book/memorized slot
uint32 spell_id; // Spell id (200 or c8 is minor healing, etc)
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming
uint32 reduction; // lowers reuse
};
@@ -517,7 +517,7 @@ struct ManaChange_Struct
{
/*00*/ uint32 new_mana; // New Mana AMount
/*04*/ uint32 stamina;
/*08*/ uint32 spell_id;
/*08*/ int32 spell_id;
/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting?
/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like
/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it
@@ -535,14 +535,14 @@ struct BeginCast_Struct
{
// len = 8
/*004*/ uint16 caster_id;
/*006*/ uint16 spell_id;
/*006*/ int16 spell_id;
/*016*/ uint32 cast_time; // in miliseconds
};
struct CastSpell_Struct
{
uint32 slot;
uint32 spell_id;
int32 spell_id;
uint32 inventoryslot; // slot for clicky item, 0xFFFF = normal cast
uint32 target_id;
uint8 cs_unknown[4];
@@ -571,7 +571,7 @@ struct SpellBuff_Struct
/*001*/ uint8 level; // Seen 1 for no buff
/*002*/ uint8 bard_modifier;
/*003*/ uint8 unknown003; // MQ2 used to call this "damage shield" -- don't see client referencing it, so maybe server side DS type tracking?
/*004*/ uint32 spellid;
/*004*/ int32 spellid;
/*008*/ uint32 duration;
/*012*/ uint32 counters;
/*016*/ uint32 unknown016;
@@ -1266,7 +1266,7 @@ struct CombatDamage_Struct
/* 00 */ uint16 target;
/* 02 */ uint16 source;
/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells
/* 05 */ uint16 spellid;
/* 05 */ int16 spellid;
/* 07 */ int32 damage;
/* 11 */ float force; // cd cc cc 3d
/* 15 */ float hit_heading; // see above notes in Action_Struct
@@ -1301,7 +1301,7 @@ struct Death_Struct
/*004*/ uint32 killer_id;
/*008*/ uint32 corpseid; // was corpseid
/*012*/ uint32 attack_skill; // was type
/*016*/ uint32 spell_id;
/*016*/ int32 spell_id;
/*020*/ uint32 bindzoneid; //bindzoneid?
/*024*/ uint32 damage;
/*028*/ uint32 unknown028;
@@ -2534,7 +2534,7 @@ struct Resurrect_Struct {
char your_name[64];
uint32 unknown88;
char rezzer_name[64];
uint32 spellid;
int32 spellid;
char corpse_name[64];
uint32 action;
/* 228 */
@@ -3787,7 +3787,7 @@ struct SendAA_Struct {
/*0037*/ uint32 prereq_skill; //is < 0, abs() is category #
/*0041*/ uint32 prereq_minpoints; //min points in the prereq
/*0045*/ uint32 type;
/*0049*/ uint32 spellid;
/*0049*/ int32 spellid;
/*0053*/ uint32 spell_type;
/*0057*/ uint32 spell_refresh;
/*0061*/ uint32 classes;
+5 -40
View File
@@ -60,7 +60,6 @@ namespace SoF
static inline spells::CastingSlot ServerToSoFCastingSlot(EQ::spells::CastingSlot slot);
static inline EQ::spells::CastingSlot SoFToServerCastingSlot(spells::CastingSlot slot, uint32 item_location);
static inline int ServerToSoFBuffSlot(int index);
static inline int SoFToServerBuffSlot(int index);
void Register(EQStreamIdentifier &into)
@@ -272,7 +271,7 @@ namespace SoF
FINISH_ENCODE();
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -285,7 +284,7 @@ namespace SoF
OUT(buff.duration);
OUT(buff.counters);
OUT(buff.player_id);
eq->slotid = ServerToSoFBuffSlot(emu->slotid);
eq->slotid = SoFToServerBuffSlot(emu->slotid);
OUT(bufffade);
FINISH_ENCODE();
@@ -1049,28 +1048,6 @@ namespace SoF
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
{
ENCODE_LENGTH_EXACT(PetBuff_Struct);
SETUP_DIRECT_ENCODE(PetBuff_Struct, PetBuff_Struct);
OUT(petid);
OUT(buffcount);
int EQBuffSlot = 0; // do we really want to shuffle them around like this?
for (uint32 EmuBuffSlot = 0; EmuBuffSlot < PET_BUFF_COUNT; ++EmuBuffSlot)
{
if (emu->spellid[EmuBuffSlot])
{
eq->spellid[EQBuffSlot] = emu->spellid[EmuBuffSlot];
eq->ticsremaining[EQBuffSlot++] = emu->ticsremaining[EmuBuffSlot];
}
}
FINISH_ENCODE();
}
ENCODE(OP_PlayerProfile)
{
SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct);
@@ -1785,7 +1762,7 @@ namespace SoF
buf.WriteString(new_message);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, std::move(buf));
dest->FastQueuePacket(&outapp, ack_req);
delete in;
@@ -2321,7 +2298,7 @@ namespace SoF
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -2334,7 +2311,7 @@ namespace SoF
IN(buff.duration);
IN(buff.counters);
IN(buff.player_id);
emu->slotid = SoFToServerBuffSlot(eq->slotid);
IN(slotid);
IN(bufffade);
FINISH_DIRECT_DECODE();
@@ -3657,18 +3634,6 @@ namespace SoF
}
}
static inline int ServerToSoFBuffSlot(int index) {
// we're a disc
if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS)
return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS +
spells::LONG_BUFFS + spells::SHORT_BUFFS;
// we're a song
if (index >= EQ::spells::LONG_BUFFS)
return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS;
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
static inline int SoFToServerBuffSlot(int index)
{
// we're a disc
+16 -22
View File
@@ -18,33 +18,27 @@
#pragma once
#include "common/struct_strategy.h"
#include "common/patches/titanium.h"
class EQStreamIdentifier;
namespace SoF
namespace SoF {
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier &into);
extern void Reload();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "sof_ops.h"
};
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "sof_ops.h"
};
} /*SoF*/
+2 -3
View File
@@ -23,7 +23,7 @@ E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_Buff)
E(OP_BuffDefinition)
E(OP_CancelTrade)
E(OP_ChannelMessage)
E(OP_CharInventory)
@@ -60,7 +60,6 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -93,7 +92,7 @@ D(OP_ApplyPoison)
D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_Bug)
D(OP_CastSpell)
D(OP_ChannelMessage)
+9 -9
View File
@@ -480,7 +480,7 @@ struct NewZone_Struct {
*/
struct MemorizeSpell_Struct {
uint32 slot; // Spot in the spell book/memorized slot
uint32 spell_id; // Spell id (200 or c8 is minor healing, etc)
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming
uint32 reduction; // lowers reuse
};
@@ -517,7 +517,7 @@ struct ManaChange_Struct
{
/*00*/ uint32 new_mana; // New Mana AMount
/*04*/ uint32 stamina;
/*08*/ uint32 spell_id;
/*08*/ int32 spell_id;
/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting?
/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like
/*16*/ int32 slot; // -1 for normal usage slot for when we want silent interrupt? I think it does timer stuff or something. Linked Spell Reuse interrupt uses it
@@ -535,14 +535,14 @@ struct BeginCast_Struct
{
// len = 8
/*004*/ uint16 caster_id;
/*006*/ uint16 spell_id;
/*006*/ int16 spell_id;
/*016*/ uint32 cast_time; // in miliseconds
};
struct CastSpell_Struct
{
uint32 slot;
uint32 spell_id;
int32 spell_id;
uint32 inventoryslot; // slot for clicky item, 0xFFFF = normal cast
uint32 target_id;
uint8 cs_unknown[4];
@@ -571,7 +571,7 @@ struct SpellBuff_Struct
/*001*/ uint8 level; // Seen 1 for no buff
/*002*/ uint8 bard_modifier;
/*003*/ uint8 unknown003; // MQ2 used to call this "damage shield" -- don't see client referencing it, so maybe server side DS type tracking?
/*004*/ uint32 spellid;
/*004*/ int32 spellid;
/*008*/ uint32 duration;
/*012*/ uint32 counters;
/*016*/ uint32 unknown016;
@@ -1266,7 +1266,7 @@ struct CombatDamage_Struct
/* 00 */ uint16 target;
/* 02 */ uint16 source;
/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells
/* 05 */ uint16 spellid;
/* 05 */ int16 spellid;
/* 07 */ int32 damage;
/* 11 */ float force; // cd cc cc 3d
/* 15 */ float hit_heading; // see above notes in Action_Struct
@@ -1301,7 +1301,7 @@ struct Death_Struct
/*004*/ uint32 killer_id;
/*008*/ uint32 corpseid; // was corpseid
/*012*/ uint32 attack_skill; // was type
/*016*/ uint32 spell_id;
/*016*/ int32 spell_id;
/*020*/ uint32 bindzoneid; //bindzoneid?
/*024*/ uint32 damage;
/*028*/ uint32 unknown028;
@@ -2504,7 +2504,7 @@ struct Resurrect_Struct {
char your_name[64];
uint32 unknown88;
char rezzer_name[64];
uint32 spellid;
int32 spellid;
char corpse_name[64];
uint32 action;
/* 228 */
@@ -3711,7 +3711,7 @@ struct SendAA_Struct {
/*0037*/ uint32 prereq_skill; //is < 0, abs() is category #
/*0041*/ uint32 prereq_minpoints; //min points in the prereq
/*0045*/ uint32 type;
/*0049*/ uint32 spellid;
/*0049*/ int32 spellid;
/*0053*/ uint32 spell_type;
/*0057*/ uint32 spell_refresh;
/*0061*/ uint32 classes;
+13 -20
View File
@@ -23,27 +23,20 @@ class EQStreamIdentifier;
namespace TEMPLATE {
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier &into);
extern void Reload();
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
class Strategy : public StructStrategy {
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQClientVersion ClientVersion() const;
//magic macro to declare our opcodes
#include "ss_declare.h"
#include "TEMPLATE_ops.h"
};
protected:
virtual std::string Describe() const;
virtual const EQClientVersion ClientVersion() const;
//magic macro to declare our opcodes
#include "ss_declare.h"
#include "TEMPLATE_ops.h"
};
};
+180 -40
View File
@@ -32,6 +32,8 @@
#include "common/raid.h"
#include "common/rulesys.h"
#include "common/strings.h"
#include "zone/mob.h"
#include "zone/string_ids.h"
#include <sstream>
@@ -61,7 +63,6 @@ namespace Titanium
static inline spells::CastingSlot ServerToTitaniumCastingSlot(EQ::spells::CastingSlot slot);
static inline EQ::spells::CastingSlot TitaniumToServerCastingSlot(spells::CastingSlot slot, uint32 item_location);
static inline int ServerToTitaniumBuffSlot(int index);
static inline int TitaniumToServerBuffSlot(int index);
void Register(EQStreamIdentifier &into)
@@ -325,7 +326,7 @@ namespace Titanium
}
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -338,7 +339,7 @@ namespace Titanium
OUT(buff.duration);
OUT(buff.counters);
OUT(buff.player_id);
eq->slotid = ServerToTitaniumBuffSlot(emu->slotid);
OUT(slotid);
OUT(bufffade);
FINISH_ENCODE();
@@ -1306,28 +1307,6 @@ namespace Titanium
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
{
ENCODE_LENGTH_EXACT(PetBuff_Struct);
SETUP_DIRECT_ENCODE(PetBuff_Struct, PetBuff_Struct);
OUT(petid);
OUT(buffcount);
int EQBuffSlot = 0; // do we really want to shuffle them around like this?
for (uint32 EmuBuffSlot = 0; EmuBuffSlot < PET_BUFF_COUNT; ++EmuBuffSlot)
{
if (emu->spellid[EmuBuffSlot])
{
eq->spellid[EQBuffSlot] = emu->spellid[EmuBuffSlot];
eq->ticsremaining[EQBuffSlot++] = emu->ticsremaining[EmuBuffSlot];
}
}
FINISH_ENCODE();
}
ENCODE(OP_PlayerProfile)
{
SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct);
@@ -1991,7 +1970,7 @@ namespace Titanium
buf.WriteString(new_message);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf);
auto outapp = new EQApplicationPacket(OP_SpecialMesg, std::move(buf));
dest->FastQueuePacket(&outapp, ack_req);
delete in;
@@ -2539,7 +2518,7 @@ namespace Titanium
}
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -3893,19 +3872,6 @@ namespace Titanium
}
}
static inline int ServerToTitaniumBuffSlot(int index)
{
// we're a disc
if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS)
return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS +
spells::LONG_BUFFS + spells::SHORT_BUFFS;
// we're a song
if (index >= EQ::spells::LONG_BUFFS)
return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS;
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
static inline int TitaniumToServerBuffSlot(int index)
{
// we're a disc
@@ -3918,4 +3884,178 @@ namespace Titanium
// we're a normal buff
return index; // as long as we guard against bad slots server side, we should be fine
}
std::unique_ptr<EQApplicationPacket> MessageComponent::Simple(uint32_t color, uint32_t id) const
{
uint32_t string_id = ResolveID(id);
if (string_id > 0) {
auto outapp = std::make_unique<EQApplicationPacket>(OP_SimpleMessage, sizeof(SimpleMessage_Struct));
auto* sms = reinterpret_cast<SimpleMessage_Struct*>(outapp->pBuffer);
sms->string_id = string_id;
sms->color = color;
sms->unknown8 = 0;
return outapp;
}
return nullptr;
}
std::unique_ptr<EQApplicationPacket> MessageComponent::Formatted(uint32_t color, uint32_t id,
const FormattedArgs& args) const
{
uint32_t string_id = ResolveID(id);
if (string_id > 0) {
std::array<const char*, 9> resolved_args = args;
ResolveArguments(id, resolved_args);
if (!resolved_args[0])
return Simple(color, id);
SerializeBuffer buf(20);
buf.WriteUInt32(0);
buf.WriteUInt32(string_id);
buf.WriteUInt32(color);
for (const auto* a : resolved_args) {
if (a != nullptr)
buf.WriteString(a);
}
buf.WriteUInt8(0);
return std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buf));
}
return nullptr;
}
std::unique_ptr<EQApplicationPacket> MessageComponent::InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_InterruptCast, sizeof(InterruptCast_Struct));
auto ic = reinterpret_cast<InterruptCast_Struct*>(outapp->pBuffer);
ic->messageid = ResolveID(message);
ic->spawnid = spawn_id;
outapp->priority = 5;
return outapp;
}
std::unique_ptr<EQApplicationPacket> MessageComponent::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name, const char* spell_link) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(name) + 1);
auto ic = reinterpret_cast<InterruptCast_Struct*>(outapp->pBuffer);
ic->messageid = ResolveID(message);
ic->spawnid = spawn_id;
strcpy(ic->message, spell_link);
return outapp;
}
// A value of 0 means that the string isn't mapped in this client, valid string ids start at 1
uint32_t MessageComponent::ResolveID(uint32_t id) const
{
// passthrough — string IDs are defined at the base client level;
// override in patches where IDs need remapping
return id;
}
void MessageComponent::ResolveArguments(uint32_t id, std::array<const char*, 9>& args) const
{
switch (id) {
case SPELL_FIZZLE:
case MISS_NOTE:
args[0] = nullptr; // the 0th (and only) argument here is the spell link, not supported before TOB
break;
case SPELL_FIZZLE_OTHER:
case MISSED_NOTE_OTHER:
args[1] = nullptr; // the 1st argument here is the spell link, not supported before TOB
break;
default:
break;
}
}
std::unique_ptr<EQApplicationPacket> BuffComponent::BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot,
bool fade) const
{
auto outapp = std::make_unique<EQApplicationPacket>(OP_BuffDefinition, sizeof(SpellBuffPacket_Struct));
auto sbf = reinterpret_cast<SpellBuffPacket_Struct*>(outapp->pBuffer);
sbf->entityid = mob->GetID();
sbf->buff.effect_type = 2;
sbf->buff.level = buff.casterlevel > 0 ? buff.casterlevel : mob->GetLevel();
sbf->buff.bard_modifier = buff.instrument_mod;
sbf->buff.spellid = buff.spellid;
sbf->buff.duration = buff.ticsremaining;
if (buff.dot_rune)
sbf->buff.counters = buff.dot_rune;
else if (buff.magic_rune)
sbf->buff.counters = buff.magic_rune;
else if (buff.melee_rune)
sbf->buff.counters = buff.melee_rune;
else if (buff.counters)
sbf->buff.counters = buff.counters;
sbf->buff.player_id = buff.casterid;
sbf->buff.num_hits = buff.hit_number;
sbf->buff.y = buff.caston_y;
sbf->buff.x = buff.caston_x;
sbf->buff.z = buff.caston_z;
sbf->slotid = ServerToPatchBuffSlot(slot);
sbf->bufffade = fade;
return outapp;
}
std::unique_ptr<EQApplicationPacket> BuffComponent::RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const
{
// titanium only sends refresh for pet buffs
if (opcode == OP_RefreshPetBuffs) {
Buffs_Struct* buffs = mob->GetBuffs();
std::vector<uint32_t> send_slots;
if (slots.empty()) {
for (uint32_t slot = 0; slot < mob->GetMaxTotalSlots(); ++slot)
if (buffs[slot].spellid > 1)
send_slots.push_back(slot);
} else {
for (uint32_t slot : slots)
if (slot < mob->GetMaxTotalSlots() && buffs[slot].spellid > 1)
send_slots.push_back(slot);
}
auto outapp = std::make_unique<EQApplicationPacket>(OP_RefreshPetBuffs, sizeof(PetBuff_Struct));
auto pbs = reinterpret_cast<PetBuff_Struct*>(outapp->pBuffer);
memset(outapp->pBuffer, 0, outapp->size);
pbs->petid = mob->GetID();
int MaxSlots = mob->GetMaxTotalSlots();
if (MaxSlots > PET_BUFF_COUNT)
MaxSlots = PET_BUFF_COUNT;
int count = 0;
for (uint32_t slot : send_slots) {
if (slot < MaxSlots) {
pbs->spellid[slot] = buffs[slot].spellid;
pbs->ticsremaining[slot] = buffs[slot].ticsremaining;
++count;
}
}
pbs->buffcount = count;
return outapp;
}
return nullptr;
}
bool BuffComponent::NeedsWearMessage() const { return true; }
void BuffComponent::SetRefreshType(std::unique_ptr<EQApplicationPacket>& packet, uint8_t refresh_type) const {}
} /*Titanium*/
+48 -16
View File
@@ -18,33 +18,65 @@
#pragma once
#include "common/struct_strategy.h"
#include "common/patches/IBuff.h"
#include "common/patches/IMessage.h"
class EQStreamIdentifier;
namespace Titanium
namespace Titanium {
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier &into);
extern void Reload();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "titanium_ops.h"
};
class MessageComponent : public ClientPatch::IMessage
{
public:
MessageComponent() = default;
~MessageComponent() override = default;
//you should not directly access anything below..
//I just dont feel like making a seperate header for it.
std::unique_ptr<EQApplicationPacket> Simple(uint32_t color, uint32_t id) const override;
std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const FormattedArgs& args) const override;
class Strategy : public StructStrategy {
public:
Strategy();
std::unique_ptr<EQApplicationPacket> InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const override;
std::unique_ptr<EQApplicationPacket> InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name,
const char* spell_link) const override;
protected:
protected:
[[nodiscard]] virtual uint32_t ResolveID(uint32_t id) const;
virtual void ResolveArguments(uint32_t id, std::array<const char*, 9>& args) const;
};
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
class BuffComponent : public ClientPatch::IBuff
{
public:
BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : IBuff(maxLongBuffs, maxShortBuffs) {}
BuffComponent() = delete;
~BuffComponent() override = default;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "titanium_ops.h"
};
std::unique_ptr<EQApplicationPacket> BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot,
bool fade) const override;
std::unique_ptr<EQApplicationPacket> RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const override;
bool NeedsWearMessage() const override;
void SetRefreshType(std::unique_ptr<EQApplicationPacket>& packet, uint8_t refresh_type) const override;
};
} /*Titanium*/
+2 -3
View File
@@ -21,7 +21,7 @@ E(OP_AdventureMerchantSell)
E(OP_ApplyPoison)
E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_Buff)
E(OP_BuffDefinition)
E(OP_ChannelMessage)
E(OP_CharInventory)
E(OP_ClientUpdate)
@@ -61,7 +61,6 @@ E(OP_ManaChange)
E(OP_MemorizeSpell)
E(OP_MoveItem)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_PlayerProfile)
E(OP_NewSpawn)
E(OP_MarkRaidNPC)
@@ -91,7 +90,7 @@ D(OP_AdventureMerchantSell)
D(OP_ApplyPoison)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_Bug)
D(OP_CastSpell)
D(OP_ChannelMessage)
+9 -9
View File
@@ -406,7 +406,7 @@ struct NewZone_Struct {
*/
struct MemorizeSpell_Struct {
uint32 slot; // Spot in the spell book/memorized slot
uint32 spell_id; // Spell id (200 or c8 is minor healing, etc)
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if un-memming
uint32 reduction; // lowers reuse
};
@@ -442,7 +442,7 @@ struct ManaChange_Struct
{
/*00*/ uint32 new_mana; // New Mana AMount
/*04*/ uint32 stamina;
/*08*/ uint32 spell_id;
/*08*/ int32 spell_id;
/*12*/ uint8 keepcasting; // won't stop the cast. Change mana while casting?
/*13*/ uint8 padding[3]; // client doesn't read it, garbage data seems like
};
@@ -459,14 +459,14 @@ struct BeginCast_Struct
{
// len = 8
/*000*/ uint16 caster_id;
/*002*/ uint16 spell_id;
/*002*/ int16 spell_id;
/*004*/ uint32 cast_time; // in miliseconds
};
struct CastSpell_Struct
{
uint32 slot;
uint32 spell_id;
int32 spell_id;
uint32 inventoryslot; // slot for clicky item, 0xFFFF = normal cast
uint32 target_id;
uint8 cs_unknown[4];
@@ -495,7 +495,7 @@ struct SpellBuff_Struct
/*001*/ uint8 level;
/*002*/ uint8 bard_modifier;
/*003*/ uint8 unknown003; // MQ2 used to call this "damage shield" -- don't see client referencing it, so maybe server side DS type tracking?
/*004*/ uint32 spellid;
/*004*/ int32 spellid;
/*008*/ int32 duration;
/*012*/ uint32 counters; // single book keeping value (counters, rune/vie)
/*016*/ uint32 player_id; // caster ID, pretty sure just zone ID
@@ -1142,7 +1142,7 @@ struct CombatDamage_Struct
/* 00 */ uint16 target;
/* 02 */ uint16 source;
/* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells, skill
/* 05 */ uint16 spellid;
/* 05 */ int16 spellid;
/* 07 */ uint32 damage;
/* 11 */ float force;
/* 15 */ float hit_heading; // see above notes in Action_Struct
@@ -1177,7 +1177,7 @@ struct Death_Struct
/*004*/ uint32 killer_id;
/*008*/ uint32 corpseid; // was corpseid
/*012*/ uint32 attack_skill; // was type
/*016*/ uint32 spell_id;
/*016*/ int32 spell_id;
/*020*/ uint32 bindzoneid; //bindzoneid?
/*024*/ uint32 damage;
/*028*/ uint32 unknown028;
@@ -2233,7 +2233,7 @@ struct Resurrect_Struct {
char your_name[64];
uint32 unknown88;
char rezzer_name[64];
uint32 spellid;
int32 spellid;
char corpse_name[64];
uint32 action;
/* 228 */
@@ -3247,7 +3247,7 @@ struct SendAA_Struct {
/*0040*/ uint32 prereq_skill; //is < 0, abs() is category #
/*0044*/ uint32 prereq_minpoints; //min points in the prereq
/*0048*/ uint32 type;
/*0052*/ uint32 spellid;
/*0052*/ int32 spellid;
/*0056*/ uint32 spell_type;
/*0060*/ uint32 spell_refresh;
/*0064*/ uint32 classes;
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/struct_strategy.h"
#include "common/patches/rof2.h"
class EQStreamIdentifier;
namespace TOB {
extern void Register(EQStreamIdentifier& into);
extern void Reload();
class Strategy : public StructStrategy
{
public:
Strategy();
protected:
virtual std::string Describe() const;
virtual const EQ::versions::ClientVersion ClientVersion() const;
//magic macro to declare our opcode processors
#include "ss_declare.h"
#include "tob_ops.h"
};
class MessageComponent : public Titanium::MessageComponent
{
public:
MessageComponent() = default;
~MessageComponent() override = default;
std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const FormattedArgs& args) const override;
std::unique_ptr<EQApplicationPacket> InterruptSpell(uint32_t message, uint32_t spawn_id,
const char* spell_link) const override;
std::unique_ptr<EQApplicationPacket> InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id,
const char* name, const char* spell_link) const override;
protected:
[[nodiscard]] uint32_t ResolveID(uint32_t id) const override;
void ResolveArguments(uint32_t id, std::array<const char*, 9>& args) const override;
};
class BuffComponent : public UF::BuffComponent
{
public:
BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : UF::BuffComponent(maxLongBuffs, maxShortBuffs) {}
BuffComponent() = delete;
~BuffComponent() override = default;
std::unique_ptr<EQApplicationPacket>
BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot, bool fade) const override;
std::unique_ptr<EQApplicationPacket> RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const override;
bool NeedsWearMessage() const override;
void SetRefreshType(std::unique_ptr<EQApplicationPacket>& packet, uint8_t refresh_type) const override;
};
}; /*TOB*/
+265
View File
@@ -0,0 +1,265 @@
#include "tob_limits.h"
#include "../strings.h"
int16 TOB::invtype::GetInvTypeSize(int16 inv_type)
{
switch (inv_type) {
case invtype::typePossessions:
return invtype::POSSESSIONS_SIZE;
case invtype::typeBank:
return invtype::BANK_SIZE;
case invtype::typeSharedBank:
return invtype::SHARED_BANK_SIZE;
case invtype::typeTrade:
return invtype::TRADE_SIZE;
case invtype::typeWorld:
return invtype::WORLD_SIZE;
case invtype::typeLimbo:
return invtype::LIMBO_SIZE;
case invtype::typeTribute:
return invtype::TRIBUTE_SIZE;
case invtype::typeTrophyTribute:
return invtype::TROPHY_TRIBUTE_SIZE;
case invtype::typeGuildTribute:
return invtype::GUILD_TRIBUTE_SIZE;
case invtype::typeMerchant:
return invtype::MERCHANT_SIZE;
case invtype::typeDeleted:
return invtype::DELETED_SIZE;
case invtype::typeCorpse:
return invtype::CORPSE_SIZE;
case invtype::typeBazaar:
return invtype::BAZAAR_SIZE;
case invtype::typeInspect:
return invtype::INSPECT_SIZE;
case invtype::typeRealEstate:
return invtype::REAL_ESTATE_SIZE;
case invtype::typeViewMODPC:
return invtype::VIEW_MOD_PC_SIZE;
case invtype::typeViewMODBank:
return invtype::VIEW_MOD_BANK_SIZE;
case invtype::typeViewMODSharedBank:
return invtype::VIEW_MOD_SHARED_BANK_SIZE;
case invtype::typeViewMODLimbo:
return invtype::VIEW_MOD_LIMBO_SIZE;
case invtype::typeAltStorage:
return invtype::ALT_STORAGE_SIZE;
case invtype::typeArchived:
return invtype::ARCHIVED_SIZE;
case invtype::typeMail:
return invtype::MAIL_SIZE;
case invtype::typeGuildTrophyTribute:
return invtype::GUILD_TROPHY_TRIBUTE_SIZE;
case invtype::typeKrono:
return invtype::KRONO_SIZE;
case invtype::typeOther:
return invtype::OTHER_SIZE;
default:
return INULL;
}
}
const char* TOB::invtype::GetInvTypeName(int16 inv_type)
{
switch (inv_type) {
case invtype::TYPE_INVALID:
return "Invalid Type";
case invtype::typePossessions:
return "Possessions";
case invtype::typeBank:
return "Bank";
case invtype::typeSharedBank:
return "Shared Bank";
case invtype::typeTrade:
return "Trade";
case invtype::typeWorld:
return "World";
case invtype::typeLimbo:
return "Limbo";
case invtype::typeTribute:
return "Tribute";
case invtype::typeTrophyTribute:
return "Trophy Tribute";
case invtype::typeGuildTribute:
return "Guild Tribute";
case invtype::typeMerchant:
return "Merchant";
case invtype::typeDeleted:
return "Deleted";
case invtype::typeCorpse:
return "Corpse";
case invtype::typeBazaar:
return "Bazaar";
case invtype::typeInspect:
return "Inspect";
case invtype::typeRealEstate:
return "Real Estate";
case invtype::typeViewMODPC:
return "View MOD PC";
case invtype::typeViewMODBank:
return "View MOD Bank";
case invtype::typeViewMODSharedBank:
return "View MOD Shared Bank";
case invtype::typeViewMODLimbo:
return "View MOD Limbo";
case invtype::typeAltStorage:
return "Alt Storage";
case invtype::typeArchived:
return "Archived";
case invtype::typeMail:
return "Mail";
case invtype::typeGuildTrophyTribute:
return "Guild Trophy Tribute";
case invtype::typeKrono:
return "Krono";
case invtype::typeOther:
return "Other";
default:
return "Unknown Type";
}
}
bool TOB::invtype::IsInvTypePersistent(int16 inv_type)
{
switch (inv_type) {
case invtype::typePossessions:
case invtype::typeBank:
case invtype::typeSharedBank:
case invtype::typeTrade:
case invtype::typeWorld:
case invtype::typeLimbo:
case invtype::typeTribute:
case invtype::typeTrophyTribute:
case invtype::typeGuildTribute:
return true;
default:
return false;
}
}
const char* TOB::invslot::GetInvPossessionsSlotName(int16 inv_slot)
{
switch (inv_slot) {
case invslot::SLOT_INVALID:
return "Invalid Slot";
case invslot::slotCharm:
return "Charm";
case invslot::slotEar1:
return "Ear 1";
case invslot::slotHead:
return "Head";
case invslot::slotFace:
return "Face";
case invslot::slotEar2:
return "Ear 2";
case invslot::slotNeck:
return "Neck";
case invslot::slotShoulders:
return "Shoulders";
case invslot::slotArms:
return "Arms";
case invslot::slotBack:
return "Back";
case invslot::slotWrist1:
return "Wrist 1";
case invslot::slotWrist2:
return "Wrist 2";
case invslot::slotRange:
return "Range";
case invslot::slotHands:
return "Hands";
case invslot::slotPrimary:
return "Primary";
case invslot::slotSecondary:
return "Secondary";
case invslot::slotFinger1:
return "Finger 1";
case invslot::slotFinger2:
return "Finger 2";
case invslot::slotChest:
return "Chest";
case invslot::slotLegs:
return "Legs";
case invslot::slotFeet:
return "Feet";
case invslot::slotWaist:
return "Waist";
case invslot::slotPowerSource:
return "Power Source";
case invslot::slotAmmo:
return "Ammo";
case invslot::slotGeneral1:
return "General 1";
case invslot::slotGeneral2:
return "General 2";
case invslot::slotGeneral3:
return "General 3";
case invslot::slotGeneral4:
return "General 4";
case invslot::slotGeneral5:
return "General 5";
case invslot::slotGeneral6:
return "General 6";
case invslot::slotGeneral7:
return "General 7";
case invslot::slotGeneral8:
return "General 8";
case invslot::slotGeneral9:
return "General 9";
case invslot::slotGeneral10:
return "General 10";
case invslot::slotCursor:
return "Cursor";
default:
return "Unknown Slot";
}
}
const char* TOB::invslot::GetInvSlotName(int16 inv_type, int16 inv_slot)
{
if (inv_type == invtype::typePossessions)
return invslot::GetInvPossessionsSlotName(inv_slot);
int16 type_size = invtype::GetInvTypeSize(inv_type);
if (!type_size || inv_slot == invslot::SLOT_INVALID)
return "Invalid Slot";
if ((inv_slot + 1) >= type_size)
return "Unknown Slot";
static std::string ret_str;
ret_str = StringFormat("Slot %i", (inv_slot + 1));
return ret_str.c_str();
}
const char* TOB::invbag::GetInvBagIndexName(int16 bag_index)
{
if (bag_index == invbag::SLOT_INVALID)
return "Invalid Bag";
if (bag_index >= invbag::SLOT_COUNT)
return "Unknown Bag";
static std::string ret_str;
ret_str = StringFormat("Bag %i", (bag_index + 1));
return ret_str.c_str();
}
const char* TOB::invaug::GetInvAugIndexName(int16 aug_index)
{
if (aug_index == invaug::SOCKET_INVALID)
return "Invalid Augment";
if (aug_index >= invaug::SOCKET_COUNT)
return "Unknown Augment";
static std::string ret_str;
ret_str = StringFormat("Augment %i", (aug_index + 1));
return ret_str.c_str();
}
+337
View File
@@ -0,0 +1,337 @@
#ifndef COMMON_LAURION_LIMITS_H
#define COMMON_LAURION_LIMITS_H
#include "common/types.h"
#include "common/emu_versions.h"
#include "common/skills.h"
namespace TOB
{
const int16 IINVALID = -1;
const int16 INULL = 0;
namespace inventory {
inline EQ::versions::ClientVersion GetInventoryRef() { return EQ::versions::ClientVersion::TOB; }
const bool ConcatenateInvTypeLimbo = false;
const bool AllowOverLevelEquipment = true;
const bool AllowEmptyBagInBag = true;
const bool AllowClickCastFromBag = true;
} /*inventory*/
namespace invtype {
inline EQ::versions::ClientVersion GetInvTypeRef() { return EQ::versions::ClientVersion::TOB; }
namespace enum_ {
enum InventoryTypes : int16 {
typePossessions = INULL,
typeBank,
typeSharedBank,
typeTrade,
typeWorld,
typeLimbo,
typeTribute,
typeTrophyTribute,
typeGuildTribute,
typeMerchant,
typeDeleted,
typeCorpse,
typeBazaar,
typeInspect,
typeRealEstate,
typeViewMODPC,
typeViewMODBank,
typeViewMODSharedBank,
typeViewMODLimbo,
typeAltStorage,
typeArchived,
typeMail,
typeGuildTrophyTribute,
typeKrono,
typeOther,
typeMercenaryItems,
typeViewModMercenaryItems,
typeMountKeyRingItems,
typeViewModMountKeyRingItems,
typeIllusionKeyRingItems,
typeViewModIllusionKeyRingItems,
typeFamiliarKeyRingItems,
typeViewModFamiliarKeyRingItems,
typeHeroForgeKeyRingItems,
typeViewModHeroForgeKeyRingItems,
typeTeleportationKeyRingItems,
typeViewModTeleportationKeyRingItems,
typeOverflow,
typeDragonHoard,
typeTradeskillDepot,
typeGuildTradeskillDepot
};
} // namespace enum_
using namespace enum_;
const int16 POSSESSIONS_SIZE = 34;
const int16 BANK_SIZE = 24;
const int16 SHARED_BANK_SIZE = 2;
const int16 TRADE_SIZE = 8;
const int16 WORLD_SIZE = 10;
const int16 LIMBO_SIZE = 36;
const int16 TRIBUTE_SIZE = 5;
const int16 TROPHY_TRIBUTE_SIZE = 0;//unknown
const int16 GUILD_TRIBUTE_SIZE = 2;//unverified
const int16 MERCHANT_SIZE = 200;
const int16 DELETED_SIZE = 0;//unknown - "Recovery Tab"
const int16 CORPSE_SIZE = POSSESSIONS_SIZE;
const int16 BAZAAR_SIZE = 200;
const int16 INSPECT_SIZE = 23;
const int16 REAL_ESTATE_SIZE = 0;//unknown
const int16 VIEW_MOD_PC_SIZE = POSSESSIONS_SIZE;
const int16 VIEW_MOD_BANK_SIZE = BANK_SIZE;
const int16 VIEW_MOD_SHARED_BANK_SIZE = SHARED_BANK_SIZE;
const int16 VIEW_MOD_LIMBO_SIZE = LIMBO_SIZE;
const int16 ALT_STORAGE_SIZE = 0;//unknown - "Shroud Bank"
const int16 ARCHIVED_SIZE = 0;//unknown
const int16 MAIL_SIZE = 0;//unknown
const int16 GUILD_TROPHY_TRIBUTE_SIZE = 0;//unknown
const int16 KRONO_SIZE = 0;//unknown
const int16 OTHER_SIZE = 0;//unknown
const int16 TRADE_NPC_SIZE = 4; // defined by implication
const int16 TYPE_INVALID = IINVALID;
const int16 TYPE_BEGIN = typePossessions;
const int16 TYPE_END = typeOther;
const int16 TYPE_COUNT = (TYPE_END - TYPE_BEGIN) + 1;
int16 GetInvTypeSize(int16 inv_type);
const char* GetInvTypeName(int16 inv_type);
bool IsInvTypePersistent(int16 inv_type);
} /*invtype*/
namespace invslot {
inline EQ::versions::ClientVersion GetInvSlotRef() { return EQ::versions::ClientVersion::TOB; }
namespace enum_ {
enum InventorySlots : int16 {
slotCharm = INULL,
slotEar1,
slotHead,
slotFace,
slotEar2,
slotNeck,
slotShoulders,
slotArms,
slotBack,
slotWrist1,
slotWrist2,
slotRange,
slotHands,
slotPrimary,
slotSecondary,
slotFinger1,
slotFinger2,
slotChest,
slotLegs,
slotFeet,
slotWaist,
slotPowerSource,
slotAmmo,
slotGeneral1,
slotGeneral2,
slotGeneral3,
slotGeneral4,
slotGeneral5,
slotGeneral6,
slotGeneral7,
slotGeneral8,
slotGeneral9,
slotGeneral10,
slotGeneral11,
slotGeneral12,
slotCursor
};
constexpr int16 format_as(InventorySlots slot) { return static_cast<int16>(slot); }
} // namespace enum_
using namespace enum_;
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 POSSESSIONS_BEGIN = slotCharm;
const int16 POSSESSIONS_END = slotCursor;
const int16 POSSESSIONS_COUNT = (POSSESSIONS_END - POSSESSIONS_BEGIN) + 1;
const int16 EQUIPMENT_BEGIN = slotCharm;
const int16 EQUIPMENT_END = slotAmmo;
const int16 EQUIPMENT_COUNT = (EQUIPMENT_END - EQUIPMENT_BEGIN) + 1;
//We support more if enabled but for now lets leave it at the 10 slots
const int16 GENERAL_BEGIN = slotGeneral1;
const int16 GENERAL_END = slotGeneral10;
const int16 GENERAL_COUNT = (GENERAL_END - GENERAL_BEGIN) + 1;
const int16 BONUS_BEGIN = invslot::slotCharm;
const int16 BONUS_STAT_END = invslot::slotPowerSource;
const int16 BONUS_SKILL_END = invslot::slotAmmo;
const int16 CORPSE_BEGIN = invslot::slotGeneral1;
const int16 CORPSE_END = invslot::slotGeneral1 + invslot::slotCursor;
const uint64 EQUIPMENT_BITMASK = 0x00000000007FFFFF;
const uint64 GENERAL_BITMASK = 0x00000007FF800000;
const uint64 CURSOR_BITMASK = 0x0000000800000000;
const uint64 POSSESSIONS_BITMASK = (EQUIPMENT_BITMASK | GENERAL_BITMASK | CURSOR_BITMASK); // based on 36-slot count (TOB+)
const uint64 CORPSE_BITMASK = (GENERAL_BITMASK | CURSOR_BITMASK | (EQUIPMENT_BITMASK << 36)); // based on 36-slot count (TOB+)
const char* GetInvPossessionsSlotName(int16 inv_slot);
const char* GetInvSlotName(int16 inv_type, int16 inv_slot);
} /*invslot*/
namespace invbag {
inline EQ::versions::ClientVersion GetInvBagRef() { return EQ::versions::ClientVersion::TOB; }
const int16 SLOT_INVALID = IINVALID;
const int16 SLOT_BEGIN = INULL;
const int16 SLOT_END = 199;
const int16 SLOT_COUNT = 200; // server Size will be 200..unsure what actual client is (test)
const char* GetInvBagIndexName(int16 bag_index);
} /*invbag*/
namespace invaug {
inline EQ::versions::ClientVersion GetInvAugRef() { return EQ::versions::ClientVersion::TOB; }
const int16 SOCKET_INVALID = IINVALID;
const int16 SOCKET_BEGIN = INULL;
const int16 SOCKET_END = 5;
const int16 SOCKET_COUNT = 6;
const char* GetInvAugIndexName(int16 aug_index);
} /*invaug*/
namespace item {
inline EQ::versions::ClientVersion GetItemRef() { return EQ::versions::ClientVersion::TOB; }
//enum Unknown : int { // looks like item class..but, RoF has it too - nothing in UF-
// Unknown1 = 0,
// Unknown2 = 1,
// Unknown3 = 2,
// Unknown4 = 5 // krono?
//};
enum ItemPacketType : int {
ItemPacketMerchant = 0x64,
ItemPacketTradeView = 0x65,
ItemPacketLoot = 0x66,
ItemPacketTrade = 0x67,
//looks like they added something at 0x68 that didn't exist before and shifted everything after it up by 1
ItemPacketUnknown068 = 0x68, //Not sure but it seems to deal with the cursor somehow.
ItemPacketCharInventory = 0x6A, //Rof 0x69 -> Larion 0x6a (requires translation)
ItemPacketLimbo = 0x6B, //0x6A -> 0x6B
ItemPacketWorldContainer = 0x6C,
ItemPacketTributeItem = 0x6D,
ItemPacketGuildTribute = 0x6E,
ItemPacketCharmUpdate = 0x6f,
ItemPacketRecovery = 0x72,
ItemPacketParcel = 0x74,
ItemPacketUnknown075 = 0x75, //Not sure but uses a lot of the same logic as the trade and char inventory types
ItemPacketOverflow = 0x76,
ItemPacketDragonHoard = 0x77,
ItemPacketTradeskill = 0x78,
ItemPacketTradeskillDepot = 0x79,
ItemPacketInvalid = 0xFF
};
} /*item*/
namespace profile {
inline EQ::versions::ClientVersion GetProfileRef() { return EQ::versions::ClientVersion::TOB; }
const int16 BANDOLIERS_SIZE = 20; // number of bandolier instances
const int16 BANDOLIER_ITEM_COUNT = 4; // number of equipment slots in bandolier instance
const int16 POTION_BELT_SIZE = 5;
const int16 SKILL_ARRAY_SIZE = 100;
} /*profile*/
namespace constants {
inline EQ::versions::ClientVersion GetConstantsRef() { return EQ::versions::ClientVersion::TOB; }
const EQ::expansions::Expansion EXPANSION = EQ::expansions::Expansion::LS;
const uint32 EXPANSION_BIT = EQ::expansions::bitLS;
const uint32 EXPANSIONS_MASK = EQ::expansions::maskLS;
const size_t CHARACTER_CREATION_LIMIT = 12;
const size_t SAY_LINK_BODY_SIZE = 56;
const uint32 MAX_GUILD_ID = 50000;
const uint32 MAX_BAZAAR_TRADERS = 600;
} /*constants*/
namespace behavior {
inline EQ::versions::ClientVersion GetBehaviorRef() { return EQ::versions::ClientVersion::TOB; }
const bool CoinHasWeight = false;
} /*behavior*/
namespace skills {
inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::TOB; }
const size_t LastUsableSkill = EQ::skills::Skill2HPiercing;
} /*skills*/
namespace spells {
inline EQ::versions::ClientVersion GetSkillsRef() { return EQ::versions::ClientVersion::TOB; }
enum class CastingSlot : uint32 {
Gem1 = 0,
Gem2 = 1,
Gem3 = 2,
Gem4 = 3,
Gem5 = 4,
Gem6 = 5,
Gem7 = 6,
Gem8 = 7,
Gem9 = 8,
Gem10 = 9,
Gem11 = 10,
Gem12 = 11,
MaxGems = 18, // fallacy..only 12 slot are useable...
Item = 12,
Discipline = 13,
AltAbility = 0xFF
};
const int SPELL_ID_MAX = 71999;
const int SPELLBOOK_SIZE = 1120;
const int SPELL_GEM_COUNT = static_cast<uint32>(CastingSlot::MaxGems);
const int SPELL_GEM_RECAST_TIMER = 15;
const int LONG_BUFFS = 42;
const int SHORT_BUFFS = 30;
const int DISC_BUFFS = 1;
const int TOTAL_BUFFS = LONG_BUFFS + SHORT_BUFFS + DISC_BUFFS;
const int NPC_BUFFS = 400;
const int PET_BUFFS = NPC_BUFFS;
const int MERC_BUFFS = LONG_BUFFS;
} /*spells*/
}; /* TOB */
#endif /*COMMON_LAURION_LIMITS_H*/

Some files were not shown because too many files have changed in this diff Show More