Compare commits

..

56 Commits

Author SHA1 Message Date
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 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
183 changed files with 11864 additions and 2098 deletions
+1
View File
@@ -20,3 +20,4 @@
*.css text
*.js text
*.types text
*.pdf binary
+1
View File
@@ -3,6 +3,7 @@ on:
push:
branches:
- master
- tob_patch
pull_request:
jobs:
+4 -4
View File
@@ -20,7 +20,7 @@ endif()
project(EQEmu
VERSION 24.10.3
LANGUAGES CXX
LANGUAGES CXX
)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
@@ -42,10 +42,10 @@ option(EQEMU_BUILD_PCH "Build with precompiled headers (Windows)" ON)
if(MSVC)
add_compile_options(/bigobj)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS NOMINMAX CRASH_LOGGING _HAS_AUTO_PTR_ETC)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS NOMINMAX WIN32_LEAN_AND_MEAN CRASH_LOGGING _HAS_AUTO_PTR_ETC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
option(EQEMU_DISABLE_MSVC_WARNINGS "Disable MSVC compile warnings." ON)
option(EQEMU_DISABLE_MSVC_WARNINGS "Disable MSVC compile warnings." OFF)
if(EQEMU_DISABLE_MSVC_WARNINGS)
add_compile_options(/W0 /wd4005 /wd4996 /nologo /Os)
endif()
@@ -71,7 +71,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)
+77 -81
View File
@@ -7,20 +7,20 @@ set(common_sources
classes.cpp
cli/eqemu_command_handler.cpp
compression.cpp
condition.cpp
content/world_content_service.cpp
crash.cpp
crc16.cpp
crc32.cpp
data_bucket.cpp
data_bucket.cpp
database_instances.cpp
database.cpp
database/database_dump_service.cpp
database/database_update.cpp
database_instances.cpp
dbcore.cpp
deity.cpp
discord/discord.cpp
discord/discord_manager.cpp
discord/discord.cpp
dynamic_zone_base.cpp
dynamic_zone_lockout.cpp
emu_constants.cpp
@@ -31,13 +31,16 @@ set(common_sources
eq_packet.cpp
eq_stream_ident.cpp
eq_stream_proxy.cpp
eqdb.cpp
eqdb_res.cpp
eqdb.cpp
eqemu_config.cpp
eqemu_exception.cpp
eqemu_logsys.cpp
eqtime.cpp
event_sub.cpp
event/event_loop.cpp
event/task_scheduler.cpp
event/timer.cpp
events/player_event_discord_formatter.cpp
events/player_event_logs.cpp
evolving_items.cpp
@@ -51,62 +54,59 @@ set(common_sources
ipc_mutex.cpp
item_data.cpp
item_instance.cpp
json/json.hpp
json/jsoncpp.cpp
json_config.cpp
json/jsoncpp.cpp
light_source.cpp
md5.cpp
memory/ksm.hpp
memory_buffer.cpp
memory_mapped_file.cpp
misc.cpp
memory/ksm.cpp
misc_functions.cpp
mutex.cpp
misc.cpp
mysql_request_result.cpp
mysql_request_row.cpp
mysql_stmt.cpp
net/console_server.cpp
net/console_server_connection.cpp
net/console_server.cpp
net/crc32.cpp
net/dns.cpp
net/eqstream.cpp
net/packet.cpp
net/reliable_stream_connection.cpp
net/servertalk_client_connection.cpp
net/servertalk_legacy_client_connection.cpp
net/servertalk_server.cpp
net/servertalk_server_connection.cpp
net/servertalk_server.cpp
net/tcp_connection.cpp
net/tcp_server.cpp
net/websocket_server.cpp
net/websocket_server_connection.cpp
net/websocket_server.cpp
opcode_map.cpp
opcodemgr.cpp
packet_dump.cpp
packet_dump_file.cpp
packet_dump.cpp
packet_functions.cpp
patches/client_version.cpp
patches/patches.cpp
patches/rof.cpp
patches/rof2.cpp
patches/rof2_limits.cpp
patches/rof_limits.cpp
patches/sod.cpp
patches/rof.cpp
patches/rof2_limits.cpp
patches/rof2.cpp
patches/sod_limits.cpp
patches/sof.cpp
patches/sod.cpp
patches/sof_limits.cpp
patches/titanium.cpp
patches/sof.cpp
patches/titanium_limits.cpp
patches/uf.cpp
patches/titanium.cpp
patches/tob.cpp
patches/tob_limits.cpp
patches/uf_limits.cpp
patches/uf.cpp
path_manager.cpp
path_manager.cpp
perl_eqdb.cpp
perl_eqdb_res.cpp
perl_eqdb.cpp
platform.cpp
platform/inet.h
platform/platform.h
platform/posix/include_inet.h
platform/posix/include_pthreads.h
platform/win/include_windows.h
platform/win/include_winsock2.h
proc_launcher.cpp
process.cpp
process/process.cpp
@@ -123,12 +123,12 @@ set(common_sources
shareddb.cpp
skill_caps.cpp
skills.cpp
spdat.cpp
spdat_bot.cpp
spdat.cpp
StackWalker/StackWalker.cpp
strings.cpp
strings_legacy.cpp
strings_misc.cpp
strings.cpp
struct_strategy.cpp
textures.cpp
timer.cpp
@@ -136,6 +136,7 @@ set(common_sources
util/directory.cpp
util/uuid.cpp
zone_store.cpp
links.cpp
)
set(repositories
@@ -538,35 +539,32 @@ set(repositories
)
set(common_headers
StackWalker/StackWalker.h
additive_lagged_fibonacci_engine.h
base_packet.h
bazaar.h
bodytypes.h
classes.h
cli/argh.h
cli/eqemu_command_handler.h
cli/terminal_color.hpp
classes.h
compression.h
condition.h
content/world_content_service.h
crash.h
crc16.h
crc32.h
cron/croncpp.h
data_bucket.cpp
data_verification.h
database_schema.h
database.h
database/database_dump_service.h
database/database_update.h
database/database_update_manifest.h
database/database_update_manifest_bots.h
database/database_update_manifest_custom.h
database_schema.h
database/database_update_manifest.h
database/database_update.h
dbcore.h
deity.h
discord/discord.h
discord/discord_manager.h
discord/discord.h
dynamic_zone_base.h
dynamic_zone_lockout.h
emu_constants.h
@@ -576,24 +574,24 @@ set(common_headers
emu_versions.h
eq_constants.h
eq_limits.h
eq_packet.h
eq_packet_structs.h
eq_packet.h
eq_stream_ident.h
eq_stream_intf.h
eq_stream_locator.h
eq_stream_proxy.h
eqdb.h
eqdb_res.h
eqemu_config.h
eqdb.h
eqemu_config_elements.h
eqemu_config.h
eqemu_exception.h
eqemu_logsys.h
eqemu_logsys_log_aliases.h
eqemu_logsys.h
eqtime.h
event_sub.h
event/event_loop.h
event/task.h
event/timer.h
event_sub.h
events/player_event_discord_formatter.h
events/player_event_logs.h
events/player_events.h
@@ -614,10 +612,11 @@ set(common_headers
ipc_mutex.h
item_data.h
item_instance.h
json_config.h
json/json_archive_single_line.h
json/json-forwards.h
json/json.h
json/json_archive_single_line.h
json_config.h
json/json.hpp
light_source.h
linked_list.h
loot.h
@@ -625,14 +624,14 @@ set(common_headers
md5.h
memory_buffer.h
memory_mapped_file.h
misc.h
memory/ksm.h
misc_functions.h
mutex.h
misc.h
mysql_request_result.h
mysql_request_row.h
mysql_stmt.h
net/console_server.h
net/console_server_connection.h
net/console_server.h
net/crc32.h
net/dns.h
net/endian.h
@@ -644,49 +643,60 @@ set(common_headers
net/servertalk_client_connection.h
net/servertalk_common.h
net/servertalk_legacy_client_connection.h
net/servertalk_server.h
net/servertalk_server_connection.h
net/tcp_connection.h
net/servertalk_server.h
net/tcp_connection_pooling.h
net/tcp_connection.h
net/tcp_server.h
net/websocket_server.h
net/websocket_server_connection.h
net/websocket_server.h
op_codes.h
opcode_dispatch.h
opcodemgr.h
packet_dump.h
packet_dump_file.h
packet_dump.h
packet_functions.h
patches/IMessage.h
patches/client_version.h
patches/patches.h
patches/rof.h
patches/rof2.h
patches/rof2_limits.h
patches/rof2_ops.h
patches/rof2_structs.h
patches/rof_limits.h
patches/rof_ops.h
patches/rof_structs.h
patches/sod.h
patches/rof.h
patches/rof2_limits.h
patches/rof2_ops.h
patches/rof2_structs.h
patches/rof2.h
patches/sod_limits.h
patches/sod_ops.h
patches/sod_structs.h
patches/sof.h
patches/sod.h
patches/sof_limits.h
patches/sof_ops.h
patches/sof_structs.h
patches/sof.h
patches/ss_declare.h
patches/ss_define.h
patches/ss_register.h
patches/titanium.h
patches/titanium_limits.h
patches/titanium_ops.h
patches/titanium_structs.h
patches/uf.h
patches/titanium.h
patches/tob.h
patches/tob_limits.h
patches/tob_ops.h
patches/tob_structs.h
patches/uf_limits.h
patches/uf_ops.h
patches/uf_structs.h
path_manager.cpp
patches/uf.h
platform.h
platform/inet.h
platform/platform.h
platform/posix/include_inet.h
platform/posix/include_pthreads.h
platform/win/include_windows.h
platform/win/include_winsock2.h
proc_launcher.h
process.h
process/process.h
@@ -713,6 +723,7 @@ set(common_headers
skills.h
spdat.h
stacktrace/backward.hpp
StackWalker/StackWalker.h
strings.h
struct_strategy.h
tasks.h
@@ -726,28 +737,13 @@ set(common_headers
util/memory_stream.h
util/uuid.h
version.h
zone_store.h
zone_store.h
links.h
)
# Source Groups (Regex based for automatic subdirectory handling)
source_group("CLI" REGULAR_EXPRESSION "^cli/")
source_group("Content" REGULAR_EXPRESSION "^content/")
source_group("Cron" REGULAR_EXPRESSION "^cron/")
source_group("Database" REGULAR_EXPRESSION "^database/")
source_group("Discord" REGULAR_EXPRESSION "^discord/")
source_group("Event" REGULAR_EXPRESSION "^event/")
source_group("Events" REGULAR_EXPRESSION "^events/")
source_group("Http" REGULAR_EXPRESSION "^http/")
source_group("Json" REGULAR_EXPRESSION "^json/")
source_group("Memory" REGULAR_EXPRESSION "^memory/")
source_group("Net" REGULAR_EXPRESSION "^net/")
source_group("Patches" REGULAR_EXPRESSION "^patches/")
source_group("Process" REGULAR_EXPRESSION "^process/")
source_group("Repositories" REGULAR_EXPRESSION "^repositories/")
source_group("StackWalker" REGULAR_EXPRESSION "^StackWalker/")
source_group("Stacktrace" REGULAR_EXPRESSION "^stacktrace/")
source_group("Termcolor" REGULAR_EXPRESSION "^termcolor/")
source_group("Util" REGULAR_EXPRESSION "^util/")
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources})
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Header Files" FILES ${common_headers})
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/repositories" PREFIX "Repositories" FILES ${repositories})
option(EQEMU_ADD_PROFILER "Link with Google perftools profiler" OFF)
#PRNG options
+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) {
+22 -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); }
@@ -73,12 +86,6 @@ public:
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
-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. */
+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
+3 -2
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),
@@ -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),
+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 {
+14
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];
@@ -326,6 +333,7 @@ union
bool buyer;
bool untargetable;
uint32 npc_tint_id;
EqGuid CharacterGuid;
};
struct PlayerState_Struct {
@@ -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>
@@ -15,37 +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/mutex.h"
#include "common/platform/posix/include_pthreads.h"
#include "common/platform/win/include_windows.h"
#include "common/event/event_loop.h"
#include "uv.h"
//Sombody, someday needs to figure out how to implement a condition
//system on windows...
namespace EQ {
EventLoop& EventLoop::Get()
{
thread_local EventLoop inst;
return inst;
}
class Condition {
private:
#ifdef WIN32
enum {
SignalEvent = 0,
BroadcastEvent,
_eventCount
};
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());
}
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();
};
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
@@ -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 ||
(
+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, uint32_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, uint32_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;
};
+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
#include "client_version.h"
// Migration path: replace string_ids.h usage with ID enum values one call site at a time.
class Client;
class Mob;
class EQApplicationPacket;
namespace Message {
template<typename... Args>
concept AllConstChar = (std::is_convertible_v<Args, const char*> && ...);
class IMessage
{
public:
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 std::array<const char*, 9>& 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
+86
View File
@@ -0,0 +1,86 @@
/* 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/patches/titanium.h"
#include "common/patches/sof.h"
#include "common/patches/sod.h"
#include "common/patches/uf.h"
#include "common/patches/rof.h"
#include "common/patches/rof2.h"
#include "common/patches/tob.h"
#include <array>
using Version = EQ::versions::ClientVersion;
struct ClientComponents
{
explicit ClientComponents(Version version) : version(version)
{
switch (version) {
case Version::TOB:
messageComponent = std::make_unique<Message::TOB>();
break;
case Version::RoF2:
messageComponent = std::make_unique<Message::RoF2>();
break;
case Version::RoF:
messageComponent = std::make_unique<Message::RoF>();
break;
case Version::UF:
messageComponent = std::make_unique<Message::UF>();
break;
case Version::SoD:
messageComponent = std::make_unique<Message::SoD>();
break;
case Version::SoF:
messageComponent = std::make_unique<Message::SoF>();
break;
case Version::Titanium:
messageComponent = std::make_unique<Message::Titanium>();
break;
default:
break;
}
}
const Version version;
std::unique_ptr<Message::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),
}
};
const std::unique_ptr<Message::IMessage>& GetMessageComponent(Version version)
{
return s_patches.at(static_cast<uint32_t>(version)).messageComponent;
}
+13
View File
@@ -0,0 +1,13 @@
//
// Created by dannu on 4/21/2026.
//
#pragma once
#include "common/emu_versions.h"
#include <memory>
namespace Message { class IMessage; }
// store all static functions for the different patches here, this can return nullptr for unsupported patches
const std::unique_ptr<Message::IMessage>& GetMessageComponent(EQ::versions::ClientVersion version);
+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();
}
+1 -1
View File
@@ -3221,7 +3221,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;
+12
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "uf.h"
#include "common/struct_strategy.h"
class EQStreamIdentifier;
@@ -48,3 +49,14 @@ namespace RoF
};
} /*RoF*/
namespace Message {
class RoF : public UF
{
public:
RoF() = default;
~RoF() override = default;
};
} // namespace Message
+15 -12
View File
@@ -689,7 +689,7 @@ namespace RoF2
EQApplicationPacket *outapp = nullptr;
if (eq->bufffade == 1)
{
outapp = new EQApplicationPacket(OP_BuffCreate, 29);
outapp = new EQApplicationPacket(OP_BuffCreate, 29u);
outapp->WriteUInt32(emu->entityid);
outapp->WriteUInt32(0); // tic timer
outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ?
@@ -753,7 +753,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 +1622,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 +2207,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 +2278,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 +2316,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
{
@@ -3820,7 +3823,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;
@@ -4120,8 +4123,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 +4602,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);
+12
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "rof.h"
#include "common/struct_strategy.h"
class EQStreamIdentifier;
@@ -48,3 +49,14 @@ namespace RoF2
};
}; /*RoF2*/
namespace Message {
class RoF2 : public RoF
{
public:
RoF2() = default;
~RoF2() override = default;
};
} // namespace Message
+1 -1
View File
@@ -2128,7 +2128,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;
+12
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "sof.h"
#include "common/struct_strategy.h"
class EQStreamIdentifier;
@@ -48,3 +49,14 @@ namespace SoD
};
} /*SoD*/
namespace Message {
class SoD : public SoF
{
public:
SoD() = default;
~SoD() override = default;
};
} // namespace Message
+1 -1
View File
@@ -1785,7 +1785,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;
+12
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "titanium.h"
#include "common/struct_strategy.h"
class EQStreamIdentifier;
@@ -48,3 +49,14 @@ namespace SoF
};
} /*SoF*/
namespace Message {
class SoF : public Titanium
{
public:
SoF() = default;
~SoF() override = default;
};
} // namespace Message
+97 -1
View File
@@ -32,6 +32,7 @@
#include "common/raid.h"
#include "common/rulesys.h"
#include "common/strings.h"
#include "zone/string_ids.h"
#include <sstream>
@@ -1991,7 +1992,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;
@@ -3919,3 +3920,98 @@ namespace Titanium
return index; // as long as we guard against bad slots server side, we should be fine
}
} /*Titanium*/
namespace Message {
std::unique_ptr<EQApplicationPacket> Titanium::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> Titanium::Formatted(
uint32_t color, uint32_t id, const std::array<const char*, 9>& 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> Titanium::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> Titanium::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;
fmt::format_to_n(ic->message, strlen(name) + 1, "{}\0", name);
return outapp;
}
// A value of 0 means that the string isn't mapped in this client, valid string ids start at 1
uint32_t Titanium::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 Titanium::ResolveArguments(uint32_t id, std::array<const char*, 9>& args) const
{
switch (id) {
case SPELL_FIZZLE:
case MISS_NOTE:
args[0] = nullptr; // drop spell link
break;
case SPELL_FIZZLE_OTHER:
case MISSED_NOTE_OTHER:
args[1] = nullptr; // drop spell link
break;
default:
break;
}
}
} // namespace Message
+28
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "IMessage.h"
#include "common/struct_strategy.h"
class EQStreamIdentifier;
@@ -48,3 +49,30 @@ namespace Titanium
};
} /*Titanium*/
// out-going message packets
namespace Message {
class Titanium : public IMessage
{
public:
Titanium() = default;
~Titanium() override = default;
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 std::array<const char*, 9>& 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]] virtual uint32_t ResolveID(uint32_t id) const;
virtual void ResolveArguments(uint32_t id, std::array<const char*, 9>& args) const;
};
} // namespace Message
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include "rof2.h"
#include "../struct_strategy.h"
class EQStreamIdentifier;
namespace TOB
{
//these are the only public member of this namespace.
extern void Register(EQStreamIdentifier& into);
extern void Reload();
//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 "tob_ops.h"
};
}; /*TOB*/
namespace Message {
class TOB : public RoF2
{
public:
TOB() {}
~TOB() override {}
std::unique_ptr<EQApplicationPacket> Formatted(uint32_t color, uint32_t id,
const std::array<const char*, 9>& 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;
};
} // namespace Message
+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 "../types.h"
#include "../emu_versions.h"
#include "../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*/
+100
View File
@@ -0,0 +1,100 @@
//list of packets we need to encode on the way out:
E(OP_AAExpUpdate)
E(OP_Action)
E(OP_Animation)
E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_CancelTrade)
E(OP_CastSpell)
E(OP_ChannelMessage)
E(OP_CharacterCreateRequest)
E(OP_CharInventory)
E(OP_ClickObjectAction)
E(OP_ClientUpdate)
E(OP_Consider)
E(OP_Damage)
E(OP_Death)
E(OP_DeleteCharge)
E(OP_DeleteItem)
E(OP_DeleteSpawn)
E(OP_DisciplineUpdate)
E(OP_ExpansionInfo)
E(OP_ExpUpdate)
E(OP_GMTraining)
E(OP_GMTrainSkillConfirm)
E(OP_GroundSpawn)
E(OP_HPUpdate)
E(OP_Illusion)
E(OP_ItemPacket)
E(OP_LogServer)
E(OP_ManaChange)
E(OP_MemorizeSpell)
E(OP_MobHealth)
E(OP_MoneyOnCorpse)
E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PlayerProfile)
E(OP_RemoveBlockedBuffs)
E(OP_RespondAA)
E(OP_RequestClientZoneChange)
E(OP_RecipeAutoCombine)
E(OP_SendAATable)
E(OP_SendCharInfo)
E(OP_SendMaxCharacters)
E(OP_SendMembership)
E(OP_SendMembershipDetails)
E(OP_SendZonepoints)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SkillUpdate)
E(OP_SpecialMesg)
E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_Stun)
E(OP_WearChange)
E(OP_ZoneChange)
E(OP_ZoneEntry)
E(OP_ZonePlayerToBind)
E(OP_ZoneSpawns)
//list of packets we need to decode on the way in:
D(OP_Animation)
D(OP_ApplyPoison)
D(OP_ApproveName)
D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BlockedBuffs)
D(OP_CastSpell)
D(OP_ChannelMessage)
D(OP_CharacterCreate)
D(OP_ClientUpdate)
D(OP_ClickDoor)
D(OP_Consider)
D(OP_ConsiderCorpse)
D(OP_DeleteItem)
D(OP_EnterWorld)
D(OP_GMTraining)
D(OP_GroupDisband)
D(OP_GroupInvite)
D(OP_GroupInvite2)
D(OP_MemorizeSpell)
D(OP_MoveItem)
D(OP_RemoveBlockedBuffs)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_SpawnAppearance)
D(OP_TradeSkillCombine)
D(OP_WearChange)
D(OP_ZoneEntry)
D(OP_ZoneChange)
#undef E
#undef D
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -2711,7 +2711,7 @@ namespace UF
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;
+12
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "sod.h"
#include "common/struct_strategy.h"
class EQStreamIdentifier;
@@ -48,3 +49,14 @@ namespace UF
};
}; /*UF*/
namespace Message {
class UF : public SoD
{
public:
UF() = default;
~UF() override = default;
};
} // namespace Message
+1
View File
@@ -19,6 +19,7 @@
#ifdef _WINDOWS
#include "common/platform/win/include_windows.h"
#include <WinSock2.h>
#endif // _WINDOWS
+3 -1
View File
@@ -23,11 +23,13 @@
inline std::string random_string(size_t length)
{
auto randchar = []() -> char {
auto randchar = []() -> char
{
const char charset[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
return charset[static_cast<size_t>(std::rand()) % max_index];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
+3 -1
View File
@@ -255,6 +255,7 @@ RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat")
RULE_BOOL(Mercs, MercsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.")
RULE_INT(Mercs, MercsHasteCap, 100, "Haste cap for non-v3(over haste) haste")
RULE_INT(Mercs, MercsHastev3Cap, 25, "Haste cap for v3(over haste) haste")
RULE_INT(Mercs, MaxMercSlots, 6, "Maximum number of mercenary slots per character (max = MAXMERCS)")
RULE_CATEGORY_END()
RULE_CATEGORY(Guild)
@@ -349,11 +350,12 @@ RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to ha
RULE_STRING(World, Rules, "", "Server Rules, change from empty to have this be used instead of variables table 'rules' value, lines are pipe (|) separated, example: A|B|C")
RULE_BOOL(World, EnableAutoLogin, false, "Enables or disables auto login of characters, allowing people to log characters in directly from loginserver to ingame")
RULE_BOOL(World, EnablePVPRegions, true, "Enables or disables PVP Regions automatically setting your PVP flag")
RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2. Example: Titanium,RoF2")
RULE_STRING(World, SupportedClients, "RoF2,TOB", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2 | TOB. Example: Titanium,RoF2,TOB")
RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1")
RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files")
RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified")
RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.")
RULE_INT(World, Id, 100, "Used by later clients to create GUIDs, expected to be Unique to the world but ultimately not that important")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
+24 -20
View File
@@ -27,7 +27,7 @@
class SerializeBuffer
{
public:
SerializeBuffer() : m_buffer(nullptr), m_capacity(0), m_pos(0) {}
SerializeBuffer() = default;
explicit SerializeBuffer(size_t size) : m_capacity(size), m_pos(0)
{
@@ -35,8 +35,10 @@ public:
memset(m_buffer, 0, size);
}
SerializeBuffer(const SerializeBuffer &rhs)
: m_buffer(new unsigned char[rhs.m_capacity]), m_capacity(rhs.m_capacity), m_pos(rhs.m_pos)
SerializeBuffer(const SerializeBuffer& rhs)
: m_buffer(new unsigned char[rhs.m_capacity])
, m_capacity(rhs.m_capacity)
, m_pos(rhs.m_pos)
{
memcpy(m_buffer, rhs.m_buffer, rhs.m_capacity);
}
@@ -53,30 +55,31 @@ public:
return *this;
}
SerializeBuffer(SerializeBuffer &&rhs) : m_buffer(rhs.m_buffer), m_capacity(rhs.m_capacity), m_pos(rhs.m_pos)
SerializeBuffer(SerializeBuffer&& rhs)
: m_buffer(std::exchange(rhs.m_buffer, nullptr))
, m_capacity(std::exchange(rhs.m_capacity, 0))
, m_pos(std::exchange(rhs.m_pos, 0))
{
rhs.m_buffer = nullptr;
rhs.m_capacity = 0;
rhs.m_pos = 0;
}
SerializeBuffer &operator=(SerializeBuffer &&rhs)
SerializeBuffer& operator=(SerializeBuffer&& rhs)
{
if (this != &rhs) {
if (this != &rhs)
{
delete[] m_buffer;
m_buffer = rhs.m_buffer;
m_capacity = rhs.m_capacity;
m_pos = rhs.m_pos;
rhs.m_buffer = nullptr;
rhs.m_capacity = 0;
rhs.m_pos = 0;
m_buffer = std::exchange(rhs.m_buffer, nullptr);
m_capacity = std::exchange(rhs.m_capacity, 0);
m_pos = std::exchange(rhs.m_pos, 0);
}
return *this;
}
~SerializeBuffer() { delete[] m_buffer; }
~SerializeBuffer()
{
delete[] m_buffer;
}
void WriteUInt8(uint8_t v)
{
@@ -209,7 +212,8 @@ public:
private:
void Grow(size_t new_size);
void Reset();
unsigned char *m_buffer;
size_t m_capacity;
size_t m_pos;
unsigned char* m_buffer = nullptr;
size_t m_capacity = 0;
size_t m_pos = 0;
};
+28 -31
View File
@@ -389,35 +389,31 @@ enum {
class ServerPacket
{
public:
~ServerPacket() { safe_delete_array(pBuffer); }
ServerPacket(uint16 in_opcode = 0, uint32 in_size = 0) {
this->compressed = false;
size = in_size;
opcode = in_opcode;
if (size == 0) {
pBuffer = 0;
}
else {
~ServerPacket()
{
safe_delete_array(pBuffer);
}
ServerPacket(uint16 in_opcode = 0, size_t in_size = 0)
: size(static_cast<uint32>(in_size))
, opcode(in_opcode)
{
if (size != 0)
{
pBuffer = new uchar[size];
memset(pBuffer, 0, size);
}
_wpos = 0;
_rpos = 0;
}
ServerPacket(uint16 in_opcode, const EQ::Net::Packet &p) {
this->compressed = false;
size = (uint32)p.Length();
opcode = in_opcode;
if (size == 0) {
pBuffer = 0;
}
else {
ServerPacket(uint16 in_opcode, const EQ::Net::Packet& p)
: size(static_cast<uint32>(p.Length()))
, opcode(in_opcode)
{
if (size != 0)
{
pBuffer = new uchar[size];
memcpy(pBuffer, p.Data(), size);
}
_wpos = 0;
_rpos = 0;
}
ServerPacket* Copy() {
@@ -447,14 +443,14 @@ public:
void ReadSkipBytes(uint32 count) { _rpos += count; }
void SetReadPosition(uint32 Newrpos) { _rpos = Newrpos; }
uint32 size;
uint16 opcode;
uchar* pBuffer;
uint32 _wpos;
uint32 _rpos;
bool compressed;
uint32 InflatedSize;
uint32 destination;
uint32 size = 0;
uint16 opcode = 0;
uchar* pBuffer = nullptr;
uint32 _wpos = 0;
uint32 _rpos = 0;
bool compressed = false;
uint32 InflatedSize = 0;
uint32 destination = 0;
};
#pragma pack(push)
@@ -989,11 +985,12 @@ struct LauncherConnectInfo {
char name[64];
};
typedef enum {
enum ZoneRequestCommands {
ZR_Start,
ZR_Restart,
ZR_Stop
} ZoneRequestCommands;
};
struct LauncherZoneRequest {
uint8 command;
char short_name[33];
+1 -1
View File
@@ -999,7 +999,7 @@ uint8 GetSpellLevel(uint16 spell_id, uint8 class_id)
return UINT8_MAX;
}
if (class_id >= Class::PLAYER_CLASS_COUNT) {
if (class_id < Class::Warrior || class_id > Class::PLAYER_CLASS_COUNT) {
return UINT8_MAX;
}
+2 -2
View File
@@ -38,10 +38,10 @@
#define RELOADTASKS 0
#define RELOADTASKSETS 2
typedef enum {
enum TaskMethodType {
METHODSINGLEID = 0,
METHODQUEST = 2
} TaskMethodType;
};
enum class TaskActivityType : int32_t // task element/objective
{
+1 -1
View File
@@ -94,7 +94,7 @@ namespace
{
m_cache.insert(std::make_pair(
key_type(src, target, dynamic_id, object_offset)
, cache_entry(offset, distance)
, cache_entry(offset, static_cast<int>(distance))
));
}
+53 -132
View File
@@ -302,20 +302,13 @@ void Client::SendPlayResponse(EQApplicationPacket *outapp)
void Client::GenerateRandomLoginKey()
{
m_key.clear();
int count = 0;
while (count < 10) {
static const char key_selection[] =
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
static constexpr std::string_view key_selection = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
constexpr size_t key_length = 10;
m_key.append((const char *) &key_selection[m_random.Int(0, 35)], 1);
count++;
m_key.clear();
m_key.reserve(key_length);
for (size_t i = 0; i < key_length; ++i) {
m_key += key_selection[m_random.Int(0, key_selection.size() - 1)];
}
}
@@ -362,55 +355,30 @@ void Client::SendFailedLogin()
m_stored_username.clear();
m_stored_password.clear();
if (m_client_version == cv_steam_latest) {
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence; // login (3)
h.encrypt_type = m_login_base_message.encrypt_type;
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence;
h.encrypt_type = m_login_base_message.encrypt_type;
h.unk3 = m_login_base_message.unk3;
// encrypted
PlayerLoginReplySteamLatest r{};
r.base_reply.success = false;
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
PlayerLoginReply r = m_client_version == cv_tob ? PlayerLoginReply(PlayerLoginReplyTOB{}) : PlayerLoginReply(PlayerLoginReplyOld{});
r.set_error_code(LS::ErrStr::ERROR_INVALID_CREDS);
// We don't care what key we send, just that it exists and is 10 characters so that we do not shift
r.set_key("InvalidKey");
char encrypted_buffer[80] = { 0 };
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block for failed login");
}
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
outapp.WriteData(&h, sizeof(h));
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(&outapp);
char encrypted_buffer[80] = { 0 };
auto rc = eqcrypt_block(r.data(), r.size(), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block for failed login");
}
else {
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence; // login (3)
h.encrypt_type = m_login_base_message.encrypt_type;
// encrypted
PlayerLoginReply r{};
r.base_reply.success = false;
r.base_reply.error_str_id = 105; // Error - The username and/or password were not valid
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
outapp.WriteData(&h, sizeof(h));
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
char encrypted_buffer[80] = { 0 };
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block for failed login");
}
m_connection->QueuePacket(&outapp);
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
EQApplicationPacket outapp(OP_LoginAccepted, outsize);
outapp.WriteData(&h, sizeof(h));
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(&outapp);
}
m_client_status = cs_failed_to_login;
}
@@ -496,91 +464,44 @@ void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a)
m_account_name = a.account_name;
m_loginserver_name = a.source_loginserver;
if (m_client_version == cv_steam_latest) {
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence;
h.compressed = false;
h.encrypt_type = m_login_base_message.encrypt_type;
h.unk3 = m_login_base_message.unk3;
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence;
h.compressed = false;
h.encrypt_type = m_login_base_message.encrypt_type;
h.unk3 = m_login_base_message.unk3;
// not serializing any of the variable length strings so just use struct directly
PlayerLoginReplySteamLatest r{};
r.base_reply.success = true;
r.base_reply.error_str_id = 101; // No Error
r.unk1 = 0;
r.unk2 = 0;
r.lsid = a.id;
r.failed_attempts = 0;
r.show_player_count = server.options.IsShowPlayerCountEnabled();
r.unk3 = 0;
r.unk4 = 0;
memcpy(r.key, m_key.c_str(), m_key.size());
// not serializing any of the variable length strings so just use struct directly
PlayerLoginReply r = m_client_version == cv_tob ? PlayerLoginReply(PlayerLoginReplyTOB{}) : PlayerLoginReply(PlayerLoginReplyOld{});
//todo: needs to be fixed
//SendExpansionPacketData(r);
r.set_success(true);
r.set_error_code(LS::ErrStr::ERROR_NONE);
r.set_lsid(a.id);
r.set_show_player_count(server.options.IsShowPlayerCountEnabled());
r.set_key(m_key);
char encrypted_buffer[80] = { 0 };
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block");
}
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
outapp->WriteData(&h, sizeof(h));
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(outapp.get());
if (m_client_version != cv_tob) {
SendExpansionPacketData(r.old());
}
else {
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence;
h.compressed = false;
h.encrypt_type = m_login_base_message.encrypt_type;
h.unk3 = m_login_base_message.unk3;
// not serializing any of the variable length strings so just use struct directly
PlayerLoginReply r{};
r.base_reply.success = true;
r.base_reply.error_str_id = 101; // No Error
r.unk1 = 0;
r.unk2 = 0;
r.lsid = a.id;
r.failed_attempts = 0;
r.show_player_count = server.options.IsShowPlayerCountEnabled();
r.offer_min_days = 99;
r.offer_min_views = -1;
r.offer_cooldown_minutes = 0;
r.web_offer_number = 0;
r.web_offer_min_days = 99;
r.web_offer_min_views = -1;
r.web_offer_cooldown_minutes = 0;
memcpy(r.key, m_key.c_str(), m_key.size());
char encrypted_buffer[80] = { 0 };
SendExpansionPacketData(r);
char encrypted_buffer[80] = { 0 };
auto rc = eqcrypt_block((const char*)&r, sizeof(r), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block");
}
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
outapp->WriteData(&h, sizeof(h));
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(outapp.get());
auto rc = eqcrypt_block(r.data(), r.size(), encrypted_buffer, 1);
if (rc == nullptr) {
LogDebug("Failed to encrypt eqcrypt block");
}
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
outapp->WriteData(&h, sizeof(h));
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(outapp.get());
m_client_status = cs_logged_in;
}
void Client::SendExpansionPacketData(PlayerLoginReply &plrs)
void Client::SendExpansionPacketData(PlayerLoginReplyOld &plrs)
{
SerializeBuffer buf;
//from eqlsstr_us.txt id of each expansion, excluding 'Everquest'
@@ -607,7 +528,7 @@ void Client::SendExpansionPacketData(PlayerLoginReply &plrs)
//generate expansion data
for (int i = 0; i < 19; i++) {
buf.WriteInt32(i); //sequenctial number
buf.WriteInt32(i); //sequential number
buf.WriteInt32((expansion & (1 << i)) == (1 << i) ? 0x01 : 0x00); //1 own 0 not own
buf.WriteInt8(0x00);
buf.WriteInt32(ExpansionLookup[i]); //from eqlsstr_us.txt
@@ -620,7 +541,7 @@ void Client::SendExpansionPacketData(PlayerLoginReply &plrs)
buf.WriteInt32(0xFFFFFFFF);
}
auto out = std::make_unique<EQApplicationPacket>(OP_LoginExpansionPacketData, buf);
auto out = std::make_unique<EQApplicationPacket>(OP_LoginExpansionPacketData, std::move(buf));
m_connection->QueuePacket(out.get());
}
+1 -1
View File
@@ -38,7 +38,7 @@ public:
// Titanium uses the encrypted data block to contact the expansion (You own xxx:) and the max expansions (of yyy)
// Rof uses a separate data packet specifically for the expansion data
// Live, as of July 2021 uses a similar but slightly different seperate data packet
void SendExpansionPacketData(PlayerLoginReply &plrs);
void SendExpansionPacketData(PlayerLoginReplyOld &plrs);
void SendPlayToWorld(const char *data);
void SendServerListPacket(uint32 seq);
void SendPlayResponse(EQApplicationPacket *outapp);
+14 -14
View File
@@ -74,7 +74,7 @@ void CheckSoDOpcodeFile(const std::string &path)
}
}
void CheckSteamLatestOpcodeFile(const std::string &path)
void CheckTOBOpcodeFile(const std::string &path)
{
if (File::Exists(path)) {
return;
@@ -177,40 +177,40 @@ ClientManager::ClientManager()
}
);
int steam_latest_port = server.config.GetVariableInt("client_configuration", "steam_latest_port", 15900);
int tob_port = server.config.GetVariableInt("client_configuration", "tob_port", 15900);
EQStreamManagerInterfaceOptions steam_latest_opts(steam_latest_port, false, false);
EQStreamManagerInterfaceOptions tob_opts(tob_port, false, false);
m_steam_latest_stream = new EQ::Net::EQStreamManager(steam_latest_opts);
m_steam_latest_ops = new RegularOpcodeManager;
m_tob_stream = new EQ::Net::EQStreamManager(tob_opts);
m_tob_ops = new RegularOpcodeManager;
opcodes_path = fmt::format(
"{}/{}",
PathManager::Instance()->GetOpcodePath(),
"login_opcodes_steam_latest.conf"
"login_opcodes_tob.conf"
);
CheckSteamLatestOpcodeFile(opcodes_path);
CheckTOBOpcodeFile(opcodes_path);
if (!m_steam_latest_ops->LoadOpcodes(opcodes_path.c_str())) {
if (!m_tob_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for Steam Latest file [{}]",
server.config.GetVariableString("client_configuration", "steam_latest_opcodes", "login_opcodes.conf")
"ClientManager fatal error: couldn't load opcodes for TOB file [{}]",
server.config.GetVariableString("client_configuration", "tob_opcodes", "login_opcodes.conf")
);
run_server = false;
}
m_steam_latest_stream->OnNewConnection(
m_tob_stream->OnNewConnection(
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
LogInfo(
"New Steam Latest client connection from [{}:{}]",
"New TOB client connection from [{}:{}]",
long2ip(stream->GetRemoteIP()),
stream->GetRemotePort()
);
stream->SetOpcodeManager(&m_steam_latest_ops);
Client *c = new Client(stream, cv_steam_latest);
stream->SetOpcodeManager(&m_tob_ops);
Client *c = new Client(stream, cv_tob);
m_clients.push_back(c);
}
);
+2 -2
View File
@@ -37,6 +37,6 @@ private:
EQ::Net::EQStreamManager *m_titanium_stream;
OpcodeManager *m_sod_ops;
EQ::Net::EQStreamManager *m_sod_stream;
OpcodeManager *m_steam_latest_ops;
EQ::Net::EQStreamManager *m_steam_latest_stream;
OpcodeManager *m_tob_ops;
EQ::Net::EQStreamManager *m_tob_stream;
};
+85 -16
View File
@@ -20,6 +20,7 @@
#include "common/types.h"
#include <string>
#include <variant>
#pragma pack(push)
#pragma pack(1)
@@ -45,7 +46,7 @@ struct LoginHandShakeReply {
};
// variable length, can use directly if not serializing strings
struct PlayerLoginReply {
struct PlayerLoginReplyOld {
// base header excluded to make struct data easier to encrypt
//LoginBaseMessage base_header;
LoginBaseReplyMessage base_reply;
@@ -61,25 +62,90 @@ struct PlayerLoginReply {
int32_t offer_cooldown_minutes; // guess (default: 0)
int32_t web_offer_number; // web order view number, 0 nothing (default: 0)
int32_t web_offer_min_days; // number of days to show offer (based on first offer time in client eqls ini) (default: 99)
int32_t web_offer_min_views; // mininum views, -1 for no minimum, 0 for never shows (based on client eqls ini) (default: -1)
int32_t web_offer_min_views; // minimum views, -1 for no minimum, 0 for never shows (based on client eqls ini) (default: -1)
int32_t web_offer_cooldown_minutes; // minimum minutes between offers (based on last offer time in client eqls ini) (default: 0)
char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct
char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select
};
struct PlayerLoginReplySteamLatest
{
void set_success(bool v) { base_reply.success = v; }
void set_error_code(int32_t v) { base_reply.error_str_id = v; }
void set_lsid(int32_t v) { lsid = v; }
void set_show_player_count(bool v) { show_player_count = v; }
void set_hardcoded_success_values() {
offer_min_days = 99;
offer_min_views = -1;
web_offer_min_days = 99;
web_offer_min_views = -1;
}
};
static_assert(sizeof(PlayerLoginReplyOld) == 58, "PlayerLoginReplyOld struct size does not match expected size");
static_assert(std::is_trivially_copyable_v<PlayerLoginReplyOld>);
static_assert(std::is_standard_layout_v<PlayerLoginReplyOld>);
struct PlayerLoginReplyTOB {
LoginBaseReplyMessage base_reply;
int8_t unk1; // (default: 0)
int8_t unk2; // (default: 0)
int32_t lsid; // (default: -1)
char key[11]; // client reads until null (variable length)
int32_t failed_attempts;
bool show_player_count; // admin flag, enables admin button and shows server player counts (default: false)
int32_t unk3; // guess, needs more investigation (default: 0)
int32_t unk4; // guess, needs more investigation (default: 0)
char username[1]; // variable length, if not empty client attempts to re-login to server select when quitting from char select and sends this in a struct
char unknown[1]; // variable length, password unlikely? client doesn't send this on re-login from char select
int8_t unk1 = 0;
int8_t unk2 = 0;
int32_t lsid = -1;
char key[11] = {};
int32_t failed_attempts = 0;
int32_t display_error_str_id = 0;
int32_t unk3 = 0;
bool show_player_count = false;
char username[1] = {};
char unk4[1] = {};
void set_success(bool v) { base_reply.success = v; }
void set_error_code(int32_t v) { display_error_str_id = v; base_reply.error_str_id = v; }
void set_lsid(int32_t v) { lsid = v; }
void set_show_player_count(bool v) { show_player_count = v; }
void set_hardcoded_success_values() {}
};
static_assert(sizeof(PlayerLoginReplyTOB) == 38, "PlayerLoginReplyTOB struct size does not match expected size");
static_assert(std::is_trivially_copyable_v<PlayerLoginReplyTOB>);
static_assert(std::is_standard_layout_v<PlayerLoginReplyTOB>);
class PlayerLoginReply {
std::variant<PlayerLoginReplyOld, PlayerLoginReplyTOB> v_;
static_assert(sizeof(PlayerLoginReplyOld::key) == sizeof(PlayerLoginReplyTOB::key), "Old and TOB key buffers must match in size due to code assumptions");
public:
PlayerLoginReply(PlayerLoginReplyOld s) : v_(s) {}
PlayerLoginReply(PlayerLoginReplyTOB s) : v_(s) {}
void set_success(bool val) {
std::visit([val](auto& s) { s.set_success(val); }, v_);
}
void set_error_code(int32_t val) {
std::visit([val](auto& s) { s.set_error_code(val); }, v_);
}
void set_lsid(int32_t val) {
std::visit([val](auto& s) { s.set_lsid(val); }, v_);
}
void set_show_player_count(bool val) {
std::visit([val](auto& s) { s.set_show_player_count(val); }, v_);
}
void set_key(std::string_view s) {
std::visit([&](auto& st) {
const size_t n = s.copy(st.key, sizeof(st.key) - 1);
st.key[n] = '\0';
}, v_);
}
template<size_t N>
void set_key(const char (&s)[N]) {
static_assert(N != (sizeof(PlayerLoginReplyTOB::key) - 1), "Key literal does not match reply struct's key buffer (without null terminator)");
set_key(std::string_view{s, N - 1});
}
PlayerLoginReplyOld& old() { return std::get<PlayerLoginReplyOld>(v_); }
const PlayerLoginReplyOld& old() const { return std::get<PlayerLoginReplyOld>(v_); }
char* data() noexcept {
return std::visit([](auto& s) { return reinterpret_cast<char*>(&s); }, v_);
}
size_t size() const noexcept {
return std::visit([](auto const& s) { return sizeof(s); }, v_);
}
};
// variable length, for reference
@@ -126,7 +192,7 @@ struct SystemFingerprint {
enum LSClientVersion {
cv_titanium,
cv_sod,
cv_steam_latest
cv_tob
};
enum LSClientStatus {
@@ -195,11 +261,14 @@ namespace LS {
namespace ErrStr {
constexpr static int ERROR_NONE = 101; // No Error
constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred
constexpr static int ERROR_INVALID_CREDS = 105; // Error - Invalid Account Name or Password
constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again.
constexpr static int ERROR_PASSWORD_RESET = 112; // Require password reset
constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later.
constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information.
constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information.
constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later.
constexpr static int ERROR_REQUIRE_2FA = 342; // This account requires two-factor authentication.
};
}
+1 -1
View File
@@ -706,7 +706,7 @@ bool WorldServer::ValidateWorldServerAdminLogin(
void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip, LSClientVersion version) const
{
if (version == cv_steam_latest) {
if (version == cv_tob) {
if (use_local_ip) {
out.WriteString(GetLocalIP());
}
+1 -1
View File
@@ -153,7 +153,7 @@ std::unique_ptr<EQApplicationPacket> WorldServerManager::CreateServerListPacket(
s->SerializeForClientServerList(buf, use_local_ip, client->GetClientVersion());
}
return std::make_unique<EQApplicationPacket>(OP_ServerListResponse, buf);
return std::make_unique<EQApplicationPacket>(OP_ServerListResponse, std::move(buf));
}
void WorldServerManager::SendUserLoginToWorldRequest(
+636
View File
@@ -0,0 +1,636 @@
### Status
Below is a status list for the 450 opcodes we currently use on the server for the TOB client. Currently uses 3 status levels (let me know if we should do more):
- 🔴 Not-Set (Opcode not set in the patch file)
- 🟡 Unverified (Opcode set but structure hasn't been verified as completely working)
- 🟢 Verified (Opcode set and structure is working)
### World/Zone Opcode Implementation Status
| Opcode | Status | Notes | Working On |
|:----------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------|
| `OP_AAAction` | 🟡 Unverified | | |
| `OP_AAExpUpdate` | 🟢 Verified | | |
| `OP_AcceptNewTask` | 🔴 Not-Set | | |
| `OP_AckPacket` | 🟢 Verified | | |
| `OP_Action` | 🟡 Unverified | | |
| `OP_Action2` | 🔴 Not-Set | | |
| `OP_AddNimbusEffect` | 🟡 Unverified | | |
| `OP_AdventureData` | 🔴 Not-Set | | |
| `OP_AdventureDetails` | 🔴 Not-Set | | |
| `OP_AdventureFinish` | 🔴 Not-Set | | |
| `OP_AdventureInfo` | 🔴 Not-Set | | |
| `OP_AdventureInfoRequest` | 🔴 Not-Set | | |
| `OP_AdventureLeaderboardReply` | 🔴 Not-Set | | |
| `OP_AdventureLeaderboardRequest` | 🔴 Not-Set | | |
| `OP_AdventureMerchantPurchase` | 🔴 Not-Set | | |
| `OP_AdventureMerchantRequest` | 🔴 Not-Set | | |
| `OP_AdventureMerchantResponse` | 🔴 Not-Set | | |
| `OP_AdventureMerchantSell` | 🔴 Not-Set | | |
| `OP_AdventurePointsUpdate` | 🔴 Not-Set | | |
| `OP_AdventureRequest` | 🔴 Not-Set | | |
| `OP_AdventureStatsReply` | 🔴 Not-Set | | |
| `OP_AdventureStatsRequest` | 🔴 Not-Set | | |
| `OP_AdventureUpdate` | 🔴 Not-Set | | |
| `OP_AggroMeterLockTarget` | 🔴 Not-Set | | |
| `OP_AggroMeterTargetInfo` | 🔴 Not-Set | | |
| `OP_AggroMeterUpdate` | 🔴 Not-Set | | |
| `OP_AltCurrency` | 🔴 Not-Set | | |
| `OP_AltCurrencyMerchantReply` | 🔴 Not-Set | | |
| `OP_AltCurrencyMerchantRequest` | 🔴 Not-Set | | |
| `OP_AltCurrencyPurchase` | 🔴 Not-Set | | |
| `OP_AltCurrencyReclaim` | 🔴 Not-Set | | |
| `OP_AltCurrencySell` | 🔴 Not-Set | | |
| `OP_AltCurrencySellSelection` | 🔴 Not-Set | | |
| `OP_Animation` | 🟡 Unverified | | |
| `OP_AnnoyingZoneUnknown` | 🔴 Not-Set | | |
| `OP_ApplyPoison` | 🟡 Unverified | | |
| `OP_ApproveName` | 🟡 Unverified | This takes multiple parameters from the client, and it can take multiple integer values from the server | |
| `OP_ApproveWorld` | 🔴 Not-Set | | |
| `OP_ApproveZone` | 🔴 Not-Set | | |
| `OP_Assist` | 🟡 Unverified | | |
| `OP_AssistGroup` | 🟡 Unverified | | |
| `OP_AugmentInfo` | 🟡 Unverified | | |
| `OP_AugmentItem` | 🟡 Unverified | | |
| `OP_AutoAttack` | 🟡 Unverified | | |
| `OP_AutoAttack2` | 🟡 Unverified | | |
| `OP_AutoFire` | 🟡 Unverified | | |
| `OP_Bandolier` | 🔴 Not-Set | | |
| `OP_BankerChange` | 🟡 Unverified | | |
| `OP_Barter` | 🔴 Not-Set | | |
| `OP_Bazaar` | 🔴 Not-Set | | |
| `OP_BazaarInspect` | 🔴 Not-Set | | |
| `OP_BazaarSearch` | 🔴 Not-Set | | |
| `OP_BecomeCorpse` | 🔴 Not-Set | | |
| `OP_BecomeTrader` | 🔴 Not-Set | | |
| `OP_Begging` | 🟡 Unverified | | |
| `OP_BeginCast` | 🟢 Verified | | |
| `OP_Bind_Wound` | 🟡 Unverified | | |
| `OP_BlockedBuffs` | 🟢 Verified | | |
| `OP_BoardBoat` | 🟡 Unverified | | |
| `OP_BookButton` | 🟡 Unverified | | |
| `OP_Buff` | 🟡 Unverified | | |
| `OP_BuffCreate` | 🟡 Unverified | | |
| `OP_BuffRemoveRequest` | 🟡 Unverified | | |
| `OP_Bug` | 🟡 Unverified | | |
| `OP_BuyerItems` | 🔴 Not-Set | | |
| `OP_CameraEffect` | 🟡 Unverified | | |
| `OP_Camp` | 🟡 Unverified | | |
| `OP_CancelSneakHide` | 🟡 Unverified | | |
| `OP_CancelTask` | 🔴 Not-Set | | |
| `OP_CancelTrade` | 🟡 Unverified | | |
| `OP_CashReward` | 🟡 Unverified | | |
| `OP_CastSpell` | 🟢 Verified | | |
| `OP_ChangeSize` | 🟢 Verified | | |
| `OP_ChannelMessage` | 🟢 Verified | | |
| `OP_ChangePetName` | 🔴 Not-Set | | |
| `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | |
| `OP_CharacterCreateRequest` | 🟢 Verified | | |
| `OP_CharInventory` | 🟢 Verified | | |
| `OP_Charm` | 🟡 Unverified | | |
| `OP_ChatMessage` | 🔴 Not-Set | | |
| `OP_ClearAA` | 🟢 Verified | | |
| `OP_ClearBlockedBuffs` | 🟢 Verified | | |
| `OP_ClearLeadershipAbilities` | 🔴 Not-Set | | |
| `OP_ClearNPCMarks` | 🔴 Not-Set | | |
| `OP_ClearObject` | 🟡 Unverified | | |
| `OP_ClearSurname` | 🔴 Not-Set | | |
| `OP_ClickDoor` | 🟡 Unverified | | |
| `OP_ClickObject` | 🟡 Unverified | | |
| `OP_ClickObjectAction` | 🟡 Unverified | | |
| `OP_ClientError` | 🔴 Not-Set | | |
| `OP_ClientReady` | 🟢 Verified | | |
| `OP_ClientTimeStamp` | 🔴 Not-Set | | |
| `OP_ClientUpdate` | 🟢 Verified | | |
| `OP_CloseContainer` | 🔴 Not-Set | | |
| `OP_CloseTributeMaster` | 🔴 Not-Set | | |
| `OP_ColoredText` | 🟢 Verified | | |
| `OP_CombatAbility` | 🟡 Unverified | | |
| `OP_Command` | 🔴 Not-Set | | |
| `OP_CompletedTasks` | 🔴 Not-Set | | |
| `OP_ConfirmDelete` | 🟡 Unverified | | |
| `OP_Consent` | 🟡 Unverified | | |
| `OP_ConsentDeny` | 🟡 Unverified | | |
| `OP_ConsentResponse` | 🟢 Verified | | |
| `OP_Consider` | 🟢 Verified | | |
| `OP_ConsiderCorpse` | 🟡 Unverified | | |
| `OP_Consume` | 🟡 Unverified | | |
| `OP_ControlBoat` | 🟡 Unverified | | |
| `OP_CorpseDrag` | 🟡 Unverified | | |
| `OP_CorpseDrop` | 🟡 Unverified | | |
| `OP_CrashDump` | 🔴 Not-Set | | |
| `OP_CrystalCountUpdate` | 🔴 Not-Set | | |
| `OP_CrystalCreate` | 🔴 Not-Set | | |
| `OP_CrystalReclaim` | 🔴 Not-Set | | |
| `OP_CustomTitles` | 🔴 Not-Set | | |
| `OP_Damage` | 🟡 Unverified | | |
| `OP_Death` | 🟡 Unverified | | |
| `OP_DelegateAbility` | 🔴 Not-Set | | |
| `OP_DeleteCharacter` | 🟢 Verified | | |
| `OP_DeleteCharge` | 🟡 Unverified | | |
| `OP_DeleteItem` | 🟡 Unverified | | |
| `OP_DeletePetition` | 🔴 Not-Set | | |
| `OP_DeleteSpawn` | 🟡 Unverified | | |
| `OP_DeleteSpell` | 🟡 Unverified | | |
| `OP_DenyResponse` | 🟡 Unverified | | |
| `OP_Disarm` | 🟡 Unverified | | |
| `OP_DisarmTraps` | 🟡 Unverified | | |
| `OP_DisciplineTimer` | 🟡 Unverified | | |
| `OP_DisciplineUpdate` | 🟡 Unverified | | |
| `OP_DiscordMerchantInventory` | 🔴 Not-Set | | |
| `OP_DoGroupLeadershipAbility` | 🔴 Not-Set | | |
| `OP_DuelDecline` | 🔴 Not-Set | | |
| `OP_DuelAccept` | 🔴 Not-Set | | |
| `OP_DumpName` | 🔴 Not-Set | | |
| `OP_Dye` | 🔴 Not-Set | | |
| `OP_DynamicWall` | 🔴 Not-Set | | |
| `OP_DzAddPlayer` | 🔴 Not-Set | | |
| `OP_DzChooseZone` | 🔴 Not-Set | | |
| `OP_DzChooseZoneReply` | 🔴 Not-Set | | |
| `OP_DzCompass` | 🔴 Not-Set | | |
| `OP_DzExpeditionEndsWarning` | 🔴 Not-Set | | |
| `OP_DzExpeditionInfo` | 🔴 Not-Set | | |
| `OP_DzExpeditionInvite` | 🔴 Not-Set | | |
| `OP_DzExpeditionInviteResponse` | 🔴 Not-Set | | |
| `OP_DzExpeditionLockoutTimers` | 🔴 Not-Set | | |
| `OP_DzListTimers` | 🔴 Not-Set | | |
| `OP_DzMakeLeader` | 🔴 Not-Set | | |
| `OP_DzMemberList` | 🔴 Not-Set | | |
| `OP_DzMemberListName` | 🔴 Not-Set | | |
| `OP_DzMemberListStatus` | 🔴 Not-Set | | |
| `OP_DzPlayerList` | 🔴 Not-Set | | |
| `OP_DzQuit` | 🔴 Not-Set | | |
| `OP_DzRemovePlayer` | 🔴 Not-Set | | |
| `OP_DzSetLeaderName` | 🔴 Not-Set | | |
| `OP_DzSwapPlayer` | 🔴 Not-Set | | |
| `OP_Emote` | 🔴 Not-Set | | |
| `OP_EndLootRequest` | 🟡 Unverified | | |
| `OP_EnduranceUpdate` | 🔴 Not-Set | | |
| `OP_EnterChat` | 🔴 Not-Set | | |
| `OP_EnterWorld` | 🟢 Verified | | |
| `OP_EnvDamage` | 🟡 Unverified | | |
| `OP_EvolveItem` | 🔴 Not-Set | | |
| `OP_ExpansionInfo` | 🟢 Verified | Updated from u32 to u64 and works now | |
| `OP_ExpUpdate` | 🟢 Verified | | |
| `OP_FaceChange` | 🔴 Not-Set | | |
| `OP_Feedback` | 🔴 Not-Set | | |
| `OP_FeignDeath` | 🟡 Unverified | | |
| `OP_FellowshipUpdate` | 🔴 Not-Set | | |
| `OP_FindPersonReply` | 🔴 Not-Set | | |
| `OP_FindPersonRequest` | 🔴 Not-Set | | |
| `OP_FinishTrade` | 🟡 Unverified | | |
| `OP_FinishWindow` | 🟡 Unverified | | |
| `OP_FinishWindow2` | 🟡 Unverified | | |
| `OP_Fishing` | 🟡 Unverified | | |
| `OP_Fling` | 🟡 Unverified | | |
| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as verified. Reference is 0x1402FFAD0 | |
| `OP_Forage` | 🟡 Unverified | | |
| `OP_ForceFindPerson` | 🔴 Not-Set | | |
| `OP_FormattedMessage` | 🟢 Verified | Some major work to do here -- the client now expects a spell link in the packet, will need to refactor the client work to decouple the stream from the internal representation | |
| `OP_FriendsWho` | 🟡 Unverified | | |
| `OP_GetGuildMOTD` | 🔴 Not-Set | | |
| `OP_GetGuildMOTDReply` | 🔴 Not-Set | | |
| `OP_GetGuildsList` | 🔴 Not-Set | | |
| `OP_GiveMoney` | 🔴 Not-Set | | |
| `OP_GMApproval` | 🔴 Not-Set | | |
| `OP_GMBecomeNPC` | 🔴 Not-Set | | |
| `OP_GMDelCorpse` | 🔴 Not-Set | | |
| `OP_GMEmoteZone` | 🔴 Not-Set | | |
| `OP_GMEndTraining` | 🟡 Unverified | | |
| `OP_GMEndTrainingResponse` | 🔴 Not-Set | | |
| `OP_GMFind` | 🔴 Not-Set | | |
| `OP_GMGoto` | 🔴 Not-Set | | |
| `OP_GMHideMe` | 🔴 Not-Set | | |
| `OP_GMKick` | 🔴 Not-Set | | |
| `OP_GMKill` | 🔴 Not-Set | | |
| `OP_GMLastName` | 🔴 Not-Set | | |
| `OP_GMNameChange` | 🔴 Not-Set | | |
| `OP_GMSearchCorpse` | 🔴 Not-Set | | |
| `OP_GMServers` | 🔴 Not-Set | | |
| `OP_GMSummon` | 🔴 Not-Set | | |
| `OP_GMToggle` | 🔴 Not-Set | | |
| `OP_GMTraining` | 🟡 Unverified | | |
| `OP_GMTrainSkill` | 🟡 Unverified | | |
| `OP_GMTrainSkillConfirm` | 🟡 Unverified | | |
| `OP_GMZoneRequest` | 🔴 Not-Set | | |
| `OP_GMZoneRequest2` | 🔴 Not-Set | | |
| `OP_GroundSpawn` | 🟢 Verified | | |
| `OP_GroupAcknowledge` | 🔴 Not-Set | | |
| `OP_GroupCancelInvite` | 🔴 Not-Set | | |
| `OP_GroupDelete` | 🔴 Not-Set | | |
| `OP_GroupDisband` | 🟡 Unverified | | |
| `OP_GroupDisbandOther` | 🔴 Not-Set | | |
| `OP_GroupDisbandYou` | 🔴 Not-Set | | |
| `OP_GroupFollow` | 🔴 Not-Set | | |
| `OP_GroupFollow2` | 🔴 Not-Set | | |
| `OP_GroupInvite` | 🟡 Unverified | | |
| `OP_GroupInvite2` | 🔴 Not-Set | | |
| `OP_GroupLeaderChange` | 🔴 Not-Set | | |
| `OP_GroupLeadershipAAUpdate` | 🔴 Not-Set | | |
| `OP_GroupMakeLeader` | 🔴 Not-Set | | |
| `OP_GroupMentor` | 🔴 Not-Set | | |
| `OP_GroupRoles` | 🔴 Not-Set | | |
| `OP_GroupUpdate` | 🔴 Not-Set | | |
| `OP_GroupUpdateB` | 🔴 Not-Set | | |
| `OP_GroupUpdateLeaderAA` | 🔴 Not-Set | | |
| `OP_GuildBank` | 🔴 Not-Set | | |
| `OP_GuildBankItemList` | 🔴 Not-Set | | |
| `OP_GuildCreate` | 🔴 Not-Set | | |
| `OP_GuildDelete` | 🔴 Not-Set | | |
| `OP_GuildDeleteGuild` | 🔴 Not-Set | | |
| `OP_GuildDemote` | 🔴 Not-Set | | |
| `OP_GuildInvite` | 🔴 Not-Set | | |
| `OP_GuildInviteAccept` | 🔴 Not-Set | | |
| `OP_GuildLeader` | 🔴 Not-Set | | |
| `OP_GuildManageAdd` | 🔴 Not-Set | | |
| `OP_GuildManageBanker` | 🔴 Not-Set | | |
| `OP_GuildManageRemove` | 🔴 Not-Set | | |
| `OP_GuildManageStatus` | 🔴 Not-Set | | |
| `OP_GuildMemberLevelUpdate` | 🔴 Not-Set | | |
| `OP_GuildMemberList` | 🔴 Not-Set | | |
| `OP_GuildMemberUpdate` | 🔴 Not-Set | | |
| `OP_GuildMemberLevel` | 🔴 Not-Set | | |
| `OP_GuildMemberRankAltBanker` | 🔴 Not-Set | | |
| `OP_GuildMemberPublicNote` | 🔴 Not-Set | | |
| `OP_GuildMemberAdd` | 🔴 Not-Set | | |
| `OP_GuildMemberRename` | 🔴 Not-Set | | |
| `OP_GuildMemberDelete` | 🔴 Not-Set | | |
| `OP_GuildMemberDetails` | 🔴 Not-Set | | |
| `OP_GuildRenameGuild` | 🔴 Not-Set | | |
| `OP_GuildMOTD` | 🔴 Not-Set | | |
| `OP_GuildPeace` | 🔴 Not-Set | | |
| `OP_GuildPromote` | 🔴 Not-Set | | |
| `OP_GuildPublicNote` | 🔴 Not-Set | | |
| `OP_GuildRemove` | 🔴 Not-Set | | |
| `OP_GuildSelectTribute` | 🔴 Not-Set | | |
| `OP_GuildModifyBenefits` | 🔴 Not-Set | | |
| `OP_GuildTributeToggleReq` | 🔴 Not-Set | | |
| `OP_GuildTributeToggleReply` | 🔴 Not-Set | | |
| `OP_GuildOptInOut` | 🔴 Not-Set | | |
| `OP_GuildSaveActiveTributes` | 🔴 Not-Set | | |
| `OP_GuildSendActiveTributes` | 🔴 Not-Set | | |
| `OP_GuildTributeFavorAndTimer` | 🔴 Not-Set | | |
| `OP_GuildsList` | 🔴 Not-Set | | |
| `OP_GuildStatus` | 🔴 Not-Set | | |
| `OP_GuildTributeInfo` | 🔴 Not-Set | | |
| `OP_GuildUpdate` | 🔴 Not-Set | | |
| `OP_GuildTributeDonateItem` | 🔴 Not-Set | | |
| `OP_GuildTributeDonatePlat` | 🔴 Not-Set | | |
| `OP_GuildWar` | 🔴 Not-Set | | |
| `OP_Heartbeat` | 🔴 Not-Set | | |
| `OP_Hide` | 🟡 Unverified | | |
| `OP_HideCorpse` | 🟡 Unverified | | |
| `OP_HPUpdate` | 🟢 Verified | | |
| `OP_Illusion` | 🟡 Unverified | | |
| `OP_IncreaseStats` | 🟡 Unverified | | |
| `OP_InitialHPUpdate` | 🔴 Not-Set | | |
| `OP_InitialMobHealth` | 🔴 Not-Set | | |
| `OP_InspectAnswer` | 🔴 Not-Set | | |
| `OP_InspectBuffs` | 🔴 Not-Set | | |
| `OP_InspectMessageUpdate` | 🔴 Not-Set | | |
| `OP_InspectRequest` | 🔴 Not-Set | | |
| `OP_InstillDoubt` | 🟡 Unverified | | |
| `OP_InterruptCast` | 🟢 Verified | Some major work to do here -- the client now expects a spell link in the packet, will need to refactor the client work to decouple the stream from the internal representation | |
| `OP_InvokeChangePetName` | 🔴 Not-Set | | |
| `OP_InvokeChangePetNameImmediate` | 🔴 Not-Set | | |
| `OP_InvokeNameChangeImmediate` | 🔴 Not-Set | | |
| `OP_InvokeNameChangeLazy` | 🔴 Not-Set | | |
| `OP_ItemLinkClick` | 🔴 Not-Set | | |
| `OP_ItemLinkResponse` | 🔴 Not-Set | | |
| `OP_ItemLinkText` | 🔴 Not-Set | | |
| `OP_ItemName` | 🔴 Not-Set | | |
| `OP_ItemPacket` | 🟡 Unverified | | |
| `OP_ItemPreview` | 🔴 Not-Set | | |
| `OP_ItemPreviewRequest` | 🔴 Not-Set | | |
| `OP_ItemRecastDelay` | 🟡 Unverified | | |
| `OP_ItemVerifyReply` | 🟡 Unverified | | |
| `OP_ItemVerifyRequest` | 🟡 Unverified | | |
| `OP_ItemViewUnknown` | 🔴 Not-Set | | |
| `OP_Jump` | 🟡 Unverified | | |
| `OP_KeyRing` | 🔴 Not-Set | | |
| `OP_KickPlayers` | 🟡 Unverified | | |
| `OP_KnowledgeBase` | 🔴 Not-Set | | |
| `OP_LDoNButton` | 🔴 Not-Set | | |
| `OP_LDoNDisarmTraps` | 🔴 Not-Set | | |
| `OP_LDoNInspect` | 🔴 Not-Set | | |
| `OP_LDoNOpen` | 🟡 Unverified | | |
| `OP_LDoNPickLock` | 🟡 Unverified | | |
| `OP_LDoNSenseTraps` | 🟡 Unverified | | |
| `OP_LeadershipExpToggle` | 🔴 Not-Set | | |
| `OP_LeadershipExpUpdate` | 🔴 Not-Set | | |
| `OP_LeaveAdventure` | 🔴 Not-Set | | |
| `OP_LeaveBoat` | 🟡 Unverified | | |
| `OP_LevelAppearance` | 🟡 Unverified | | |
| `OP_LevelUpdate` | 🟢 Verified | | |
| `OP_LFGAppearance` | 🔴 Not-Set | | |
| `OP_LFGCommand` | 🔴 Not-Set | | |
| `OP_LFGGetMatchesRequest` | 🔴 Not-Set | | |
| `OP_LFGGetMatchesResponse` | 🔴 Not-Set | | |
| `OP_LFGResponse` | 🔴 Not-Set | | |
| `OP_LFGuild` | 🔴 Not-Set | | |
| `OP_LFPCommand` | 🔴 Not-Set | | |
| `OP_LFPGetMatchesRequest` | 🔴 Not-Set | | |
| `OP_LFPGetMatchesResponse` | 🔴 Not-Set | | |
| `OP_LinkedReuse` | 🟢 Verified | | |
| `OP_LoadSpellSet` | 🔴 Not-Set | | |
| `OP_LocInfo` | 🔴 Not-Set | | |
| `OP_LockoutTimerInfo` | 🔴 Not-Set | | |
| `OP_Login` | 🔴 Not-Set | | |
| `OP_LoginAccepted` | 🔴 Not-Set | | |
| `OP_LoginComplete` | 🔴 Not-Set | | |
| `OP_LoginExpansionPacketData` | 🔴 Not-Set | | |
| `OP_LoginUnknown1` | 🔴 Not-Set | | |
| `OP_LoginUnknown2` | 🔴 Not-Set | | |
| `OP_Logout` | 🟡 Unverified | | |
| `OP_LogoutReply` | 🔴 Not-Set | | |
| `OP_LogServer` | 🟢 Verified | Mostly unused values | |
| `OP_LootComplete` | 🟡 Unverified | | |
| `OP_LootItem` | 🟡 Unverified | | |
| `OP_LootRequest` | 🟡 Unverified | | |
| `OP_ManaChange` | 🟢 Verified | | |
| `OP_ManaUpdate` | 🔴 Not-Set | | |
| `OP_MarkNPC` | 🔴 Not-Set | | |
| `OP_MarkRaidNPC` | 🔴 Not-Set | | |
| `OP_Marquee` | 🟡 Unverified | | |
| `OP_MemorizeSpell` | 🟢 Verified | | |
| `OP_Mend` | 🟡 Unverified | | |
| `OP_MendHPUpdate` | 🔴 Not-Set | | |
| `OP_MercenaryAssign` | 🔴 Not-Set | | |
| `OP_MercenaryCommand` | 🔴 Not-Set | | |
| `OP_MercenaryDataRequest` | 🔴 Not-Set | | |
| `OP_MercenaryDataResponse` | 🔴 Not-Set | | |
| `OP_MercenaryDataUpdate` | 🔴 Not-Set | | |
| `OP_MercenaryDataUpdateRequest` | 🔴 Not-Set | | |
| `OP_MercenaryDismiss` | 🔴 Not-Set | | |
| `OP_MercenaryHire` | 🔴 Not-Set | | |
| `OP_MercenarySuspendRequest` | 🔴 Not-Set | | |
| `OP_MercenarySuspendResponse` | 🔴 Not-Set | | |
| `OP_MercenaryTimer` | 🔴 Not-Set | | |
| `OP_MercenaryTimerRequest` | 🔴 Not-Set | | |
| `OP_MercenaryUnknown1` | 🔴 Not-Set | | |
| `OP_MercenaryUnsuspendResponse` | 🔴 Not-Set | | |
| `OP_MerchantBulkItems` | 🔴 Not-Set | | |
| `OP_MobEnduranceUpdate` | 🔴 Not-Set | | |
| `OP_MobHealth` | 🟡 Unverified | | |
| `OP_MobManaUpdate` | 🔴 Not-Set | | |
| `OP_MobRename` | 🔴 Not-Set | | |
| `OP_MobUpdate` | 🔴 Not-Set | | |
| `OP_MoneyOnCorpse` | 🟡 Unverified | | |
| `OP_MoneyUpdate` | 🟡 Unverified | | |
| `OP_MOTD` | 🟢 Verified | | |
| `OP_MoveCoin` | 🟡 Unverified | | |
| `OP_MoveDoor` | 🟡 Unverified | | |
| `OP_MoveItem` | 🟢 Verified | | |
| `OP_MoveMultipleItems` | 🟡 Unverified | | |
| `OP_MoveLogDisregard` | 🔴 Not-Set | | |
| `OP_MoveLogRequest` | 🔴 Not-Set | | |
| `OP_MultiLineMsg` | 🔴 Not-Set | | |
| `OP_NewSpawn` | 🟢 Verified | Deprecated in the client, already handled in emu | |
| `OP_NewTitlesAvailable` | 🔴 Not-Set | | |
| `OP_NewZone` | 🟢 Verified | | |
| `OP_NPCMoveUpdate` | 🔴 Not-Set | | |
| `OP_OnLevelMessage` | 🟡 Unverified | | |
| `OP_OpenContainer` | 🟡 Unverified | | |
| `OP_OpenDiscordMerchant` | 🔴 Not-Set | | |
| `OP_OpenGuildTributeMaster` | 🔴 Not-Set | | |
| `OP_OpenInventory` | 🔴 Not-Set | | |
| `OP_OpenTributeMaster` | 🔴 Not-Set | | |
| `OP_PDeletePetition` | 🔴 Not-Set | | |
| `OP_PetBuffWindow` | 🔴 Not-Set | | |
| `OP_PetCommands` | 🔴 Not-Set | | |
| `OP_PetCommandState` | 🔴 Not-Set | | |
| `OP_PetHoTT` | 🔴 Not-Set | | |
| `OP_Petition` | 🔴 Not-Set | | |
| `OP_PetitionBug` | 🔴 Not-Set | | |
| `OP_PetitionCheckIn` | 🔴 Not-Set | | |
| `OP_PetitionCheckout` | 🔴 Not-Set | | |
| `OP_PetitionCheckout2` | 🔴 Not-Set | | |
| `OP_PetitionDelete` | 🔴 Not-Set | | |
| `OP_PetitionQue` | 🔴 Not-Set | | |
| `OP_PetitionRefresh` | 🔴 Not-Set | | |
| `OP_PetitionResolve` | 🔴 Not-Set | | |
| `OP_PetitionSearch` | 🔴 Not-Set | | |
| `OP_PetitionSearchResults` | 🔴 Not-Set | | |
| `OP_PetitionSearchText` | 🔴 Not-Set | | |
| `OP_PetitionUnCheckout` | 🔴 Not-Set | | |
| `OP_PetitionUpdate` | 🔴 Not-Set | | |
| `OP_PickPocket` | 🟡 Unverified | | |
| `OP_PickZone` | 🔴 Not-Set | | |
| `OP_PickZoneWindow` | 🔴 Not-Set | | |
| `OP_PlayerProfile` | 🟢 Verified | | |
| `OP_PlayerStateAdd` | 🟡 Unverified | | |
| `OP_PlayerStateRemove` | 🟡 Unverified | | |
| `OP_PlayEverquestRequest` | 🔴 Not-Set | | |
| `OP_PlayEverquestResponse` | 🔴 Not-Set | | |
| `OP_PlayMP3` | 🟡 Unverified | | |
| `OP_Poll` | 🔴 Not-Set | | |
| `OP_PollResponse` | 🔴 Not-Set | | |
| `OP_PopupResponse` | 🟡 Unverified | | |
| `OP_PostEnterWorld` | 🟢 Verified | | |
| `OP_PotionBelt` | 🔴 Not-Set | | |
| `OP_PreLogoutReply` | 🔴 Not-Set | | |
| `OP_PurchaseLeadershipAA` | 🔴 Not-Set | | |
| `OP_PVPLeaderBoardDetailsReply` | 🔴 Not-Set | | |
| `OP_PVPLeaderBoardDetailsRequest` | 🔴 Not-Set | | |
| `OP_PVPLeaderBoardReply` | 🔴 Not-Set | | |
| `OP_PVPLeaderBoardRequest` | 🔴 Not-Set | | |
| `OP_PVPStats` | 🔴 Not-Set | | |
| `OP_QueryResponseThing` | 🔴 Not-Set | | |
| `OP_QueryUCSServerStatus` | 🟢 Verified | | |
| `OP_RaidDelegateAbility` | 🔴 Not-Set | | |
| `OP_RaidClearNPCMarks` | 🔴 Not-Set | | |
| `OP_RaidInvite` | 🔴 Not-Set | | |
| `OP_RaidJoin` | 🔴 Not-Set | | |
| `OP_RaidUpdate` | 🔴 Not-Set | | |
| `OP_RandomNameGenerator` | 🟢 Verified | The client no longer sends this packet (random name generation is done entirely in the client). The client will still accept this packet to set name (emu doesn't do this, but it's always been supported) | |
| `OP_RandomReply` | 🟡 Unverified | | |
| `OP_RandomReq` | 🟡 Unverified | | |
| `OP_ReadBook` | 🟡 Unverified | | |
| `OP_RecipeAutoCombine` | 🟡 Unverified | | |
| `OP_RecipeDetails` | 🟡 Unverified | | |
| `OP_RecipeReply` | 🟡 Unverified | | |
| `OP_RecipesFavorite` | 🟡 Unverified | | |
| `OP_RecipesSearch` | 🟡 Unverified | | |
| `OP_ReclaimCrystals` | 🔴 Not-Set | | |
| `OP_ReloadUI` | 🔴 Not-Set | | |
| `OP_RemoveAllDoors` | 🟡 Unverified | | |
| `OP_RemoveBlockedBuffs` | 🟢 Verified | | |
| `OP_RemoveNimbusEffect` | 🟡 Unverified | | |
| `OP_RemoveTrap` | 🔴 Not-Set | | |
| `OP_Report` | 🟡 Unverified | | |
| `OP_ReqClientSpawn` | 🟢 Verified | | |
| `OP_ReqNewZone` | 🟢 Verified | Client does not send this (in LS or TOB), but it does receive it. emu does not send it | |
| `OP_RequestClientZoneChange` | 🟢 Verified | parity with RoF2, there's a string that gets passed to teleport at the end that's not known | |
| `OP_RequestDuel` | 🔴 Not-Set | | |
| `OP_RequestGuildTributes` | 🔴 Not-Set | | |
| `OP_RequestKnowledgeBase` | 🔴 Not-Set | | |
| `OP_RequestTitles` | 🔴 Not-Set | | |
| `OP_RespawnWindow` | 🟡 Unverified | | |
| `OP_RespondAA` | 🟢 Verified | | |
| `OP_RestState` | 🟡 Unverified | | |
| `OP_Rewind` | 🟡 Unverified | | |
| `OP_RezzAnswer` | 🔴 Not-Set | | |
| `OP_RezzComplete` | 🔴 Not-Set | | |
| `OP_RezzRequest` | 🔴 Not-Set | | |
| `OP_Sacrifice` | 🟡 Unverified | | |
| `OP_SafeFallSuccess` | 🟡 Unverified | | |
| `OP_SafePoint` | 🔴 Not-Set | | |
| `OP_Save` | 🟡 Unverified | | |
| `OP_SaveOnZoneReq` | 🟡 Unverified | | |
| `OP_SelectTribute` | 🔴 Not-Set | | |
| `OP_SendAAStats` | 🟡 Unverified | | |
| `OP_SendAATable` | 🟢 Verified | | |
| `OP_SendCharInfo` | 🟢 Verified | | |
| `OP_SendExpZonein` | 🟢 Verified | | |
| `OP_SendFindableNPCs` | 🔴 Not-Set | | |
| `OP_SendGuildTributes` | 🔴 Not-Set | | |
| `OP_SendLoginInfo` | 🟢 Verified | | |
| `OP_SendMaxCharacters` | 🟢 Verified | | |
| `OP_SendMembership` | 🟢 Verified | | |
| `OP_SendMembershipDetails` | 🟢 Verified | The struct is correct, will need reversing for actual option keys/values | |
| `OP_SendSystemStats` | 🔴 Not-Set | | |
| `OP_SendTitleList` | 🔴 Not-Set | | |
| `OP_SendTributes` | 🔴 Not-Set | | |
| `OP_SendZonepoints` | 🟢 Verified | | |
| `OP_SenseHeading` | 🟡 Unverified | | |
| `OP_SenseTraps` | 🟡 Unverified | | |
| `OP_ServerListRequest` | 🔴 Not-Set | | |
| `OP_ServerListResponse` | 🔴 Not-Set | | |
| `OP_SessionReady` | 🔴 Not-Set | | |
| `OP_SetChatServer` | 🔴 Not-Set | | |
| `OP_SetChatServer2` | 🟢 Verified | | |
| `OP_SetFace` | 🔴 Not-Set | | |
| `OP_SetGroupTarget` | 🔴 Not-Set | | |
| `OP_SetGuildMOTD` | 🔴 Not-Set | | |
| `OP_SetGuildRank` | 🔴 Not-Set | | |
| `OP_SetRunMode` | 🟡 Unverified | | |
| `OP_SetServerFilter` | 🟢 Verified | | |
| `OP_SetStartCity` | 🔴 Not-Set | | |
| `OP_SetTitle` | 🔴 Not-Set | | |
| `OP_SetTitleReply` | 🔴 Not-Set | | |
| `OP_SharedTaskMemberList` | 🔴 Not-Set | | |
| `OP_SharedTaskAddPlayer` | 🔴 Not-Set | | |
| `OP_SharedTaskRemovePlayer` | 🔴 Not-Set | | |
| `OP_SharedTaskMakeLeader` | 🔴 Not-Set | | |
| `OP_SharedTaskMemberInvite` | 🔴 Not-Set | | |
| `OP_SharedTaskInvite` | 🔴 Not-Set | | |
| `OP_SharedTaskInviteResponse` | 🔴 Not-Set | | |
| `OP_SharedTaskAcceptNew` | 🔴 Not-Set | | |
| `OP_SharedTaskMemberChange` | 🔴 Not-Set | | |
| `OP_SharedTaskPlayerList` | 🔴 Not-Set | | |
| `OP_SharedTaskSelectWindow` | 🔴 Not-Set | | |
| `OP_SharedTaskQuit` | 🔴 Not-Set | | |
| `OP_TaskTimers` | 🔴 Not-Set | | |
| `OP_Shielding` | 🔴 Not-Set | | |
| `OP_ShopDelItem` | 🟡 Unverified | | |
| `OP_ShopEnd` | 🟡 Unverified | | |
| `OP_ShopEndConfirm` | 🟡 Unverified | | |
| `OP_ShopItem` | 🔴 Not-Set | | |
| `OP_ShopPlayerBuy` | 🟡 Unverified | | |
| `OP_ShopPlayerSell` | 🟡 Unverified | | |
| `OP_ShopSendParcel` | 🟡 Unverified | | |
| `OP_ShopDeleteParcel` | 🟡 Unverified | | |
| `OP_ShopRespondParcel` | 🔴 Not-Set | | |
| `OP_ShopRetrieveParcel` | 🟡 Unverified | | |
| `OP_ShopParcelIcon` | 🟡 Unverified | | |
| `OP_ShopRequest` | 🟡 Unverified | | |
| `OP_SimpleMessage` | 🟢 Verified | | |
| `OP_SkillUpdate` | 🟡 Unverified | | |
| `OP_Sneak` | 🟡 Unverified | | |
| `OP_Some3ByteHPUpdate` | 🔴 Not-Set | | |
| `OP_Some6ByteHPUpdate` | 🔴 Not-Set | | |
| `OP_SomeItemPacketMaybe` | 🔴 Not-Set | | |
| `OP_Sound` | 🟡 Unverified | | |
| `OP_SpawnAppearance` | 🟢 Verified | | |
| `OP_SpawnDoor` | 🟢 Verified | | |
| `OP_SpawnPositionUpdate` | 🔴 Not-Set | | |
| `OP_SpecialMesg` | 🟢 Verified | | |
| `OP_SpellEffect` | 🟡 Unverified | | |
| `OP_Split` | 🟡 Unverified | | |
| `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | |
| `OP_Stun` | 🟡 Unverified | | |
| `OP_Surname` | 🔴 Not-Set | | |
| `OP_SwapSpell` | 🟢 Verified | | |
| `OP_SystemFingerprint` | 🔴 Not-Set | | |
| `OP_TargetBuffs` | 🔴 Not-Set | | |
| `OP_TargetCommand` | 🟡 Unverified | | |
| `OP_TargetHoTT` | 🔴 Not-Set | | |
| `OP_TargetMouse` | 🟡 Unverified | | |
| `OP_TargetReject` | 🔴 Not-Set | | |
| `OP_TaskActivity` | 🔴 Not-Set | | |
| `OP_TaskActivityComplete` | 🔴 Not-Set | | |
| `OP_TaskDescription` | 🔴 Not-Set | | |
| `OP_TaskHistoryReply` | 🔴 Not-Set | | |
| `OP_TaskHistoryRequest` | 🔴 Not-Set | | |
| `OP_TaskRequestTimer` | 🔴 Not-Set | | |
| `OP_TaskSelectWindow` | 🔴 Not-Set | | |
| `OP_Taunt` | 🟡 Unverified | | |
| `OP_TestBuff` | 🔴 Not-Set | | |
| `OP_TGB` | 🔴 Not-Set | | |
| `OP_TimeOfDay` | 🟢 Verified | | |
| `OP_Track` | 🟡 Unverified | | |
| `OP_TrackTarget` | 🟡 Unverified | | |
| `OP_TrackUnknown` | 🟡 Unverified | | |
| `OP_TradeAcceptClick` | 🟡 Unverified | | |
| `OP_TradeBusy` | 🟡 Unverified | | |
| `OP_TradeCoins` | 🟡 Unverified | | |
| `OP_TradeMoneyUpdate` | 🟡 Unverified | | |
| `OP_Trader` | 🔴 Not-Set | | |
| `OP_TraderBulkSend` | 🔴 Not-Set | | |
| `OP_TraderBuy` | 🔴 Not-Set | | |
| `OP_TraderDelItem` | 🔴 Not-Set | | |
| `OP_TradeRequest` | 🟡 Unverified | | |
| `OP_TradeRequestAck` | 🟡 Unverified | | |
| `OP_TraderItemUpdate` | 🔴 Not-Set | | |
| `OP_TraderShop` | 🔴 Not-Set | | |
| `OP_TradeSkillCombine` | 🟡 Unverified | | |
| `OP_TradeSkillRecipeInspect` | 🔴 Not-Set | | |
| `OP_Translocate` | 🟡 Unverified | | |
| `OP_TributeInfo` | 🔴 Not-Set | | |
| `OP_TributeItem` | 🔴 Not-Set | | |
| `OP_TributeMoney` | 🔴 Not-Set | | |
| `OP_TributeNPC` | 🔴 Not-Set | | |
| `OP_TributePointUpdate` | 🔴 Not-Set | | |
| `OP_TributeTimer` | 🔴 Not-Set | | |
| `OP_TributeToggle` | 🔴 Not-Set | | |
| `OP_TributeUpdate` | 🔴 Not-Set | | |
| `OP_Untargetable` | 🟡 Unverified | | |
| `OP_UpdateAA` | 🟢 Verified | | |
| `OP_UpdateAura` | 🔴 Not-Set | | |
| `OP_UpdateLeadershipAA` | 🔴 Not-Set | | |
| `OP_VetClaimReply` | 🔴 Not-Set | | |
| `OP_VetClaimRequest` | 🔴 Not-Set | | |
| `OP_VetRewardsAvaliable` | 🔴 Not-Set | | |
| `OP_VoiceMacroIn` | 🟡 Unverified | | |
| `OP_VoiceMacroOut` | 🟡 Unverified | | |
| `OP_WeaponEquip1` | 🔴 Not-Set | | |
| `OP_WearChange` | 🟢 Verified | | |
| `OP_Weather` | 🟢 Verified | | |
| `OP_Weblink` | 🟡 Unverified | | |
| `OP_WhoAllRequest` | 🟡 Unverified | | |
| `OP_WhoAllResponse` | 🟡 Unverified | | |
| `OP_World_Client_CRC1` | 🟢 Verified | | |
| `OP_World_Client_CRC2` | 🟢 Verified | | |
| `OP_World_Client_CRC3` | 🟢 Verified | | |
| `OP_WorldClientReady` | 🟢 Verified | | |
| `OP_WorldComplete` | 🟢 Verified | | |
| `OP_WorldLogout` | 🔴 Not-Set | | |
| `OP_WorldObjectsSent` | 🟢 Verified | | |
| `OP_WorldUnknown001` | 🟢 Verified | SetServerTime. emu doesn't currently send it so setting it to verified, but the reference is 0x140292550 | |
| `OP_XTargetAutoAddHaters` | 🔴 Not-Set | | |
| `OP_XTargetOpen` | 🔴 Not-Set | | |
| `OP_XTargetOpenResponse` | 🔴 Not-Set | | |
| `OP_XTargetRequest` | 🔴 Not-Set | | |
| `OP_XTargetResponse` | 🔴 Not-Set | | |
| `OP_YellForHelp` | 🟡 Unverified | | |
| `OP_ZoneChange` | 🟢 Verified | | |
| `OP_ZoneComplete` | 🔴 Not-Set | | |
| `OP_ZoneEntry` | 🟢 Verified | unknown fields in C->S struct are various CRCs, emu doesn't use them | |
| `OP_ZoneGuildList` | 🔴 Not-Set | | |
| `OP_ZoneInUnknown` | 🔴 Not-Set | | |
| `OP_ZonePlayerToBind` | 🟡 Unverified | | |
| `OP_ZoneServerInfo` | 🟢 Verified | | |
| `OP_ZoneServerReady` | 🔴 Not-Set | | |
| `OP_ZoneSpawns` | 🟢 Verified | This is deprecated in the client (and emu never sends it directly) | |
| `OP_ZoneUnavail` | 🟢 Verified | The client discards all content of this packet | |
| `OP_ResetAA` | 🟡 Unverified | | |
| `OP_UnderWorld` | 🟡 Unverified | | |

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