Compare commits

..

60 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
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
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
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
KimLS 75ddf8dfc3 Merge branch 'master' into obr_login 2026-04-04 14:14:47 -07:00
KimLS a3802ff257 opcode fixes 2026-04-02 11:50:08 -07:00
KimLS ca23b8612e WIP world select packets 2026-03-30 21:54:15 -07:00
KimLS 485ae4809d some changes, working on login 2026-03-30 18:36:13 -07:00
KimLS 452407ed67 Fix seq in on login handshake 2026-03-26 21:59:53 -07:00
KimLS aa1c481f65 update opcodes 2026-03-26 13:01:24 -07:00
KimLS 780dcdab5a Renamed "larion" to "steam latest" 2026-03-26 12:50:46 -07:00
KimLS 16ec08e71c Some investigation on packets, mostly the same as rof2 / laurion but not entirely. 2026-03-25 18:08:46 -07:00
KimLS 0024073cee Login exploration 2026-03-24 23:59:42 -07:00
KimLS 37b8428c48 Starting research 2026-03-24 23:22:08 -07:00
110 changed files with 13090 additions and 866 deletions
+4 -27
View File
@@ -3,8 +3,9 @@ on:
push:
branches:
- master
- develop
- tob_patch
pull_request:
jobs:
linux:
name: Linux
@@ -25,17 +26,6 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential ninja-build ccache uuid-dev
- name: Restore vcpkg Cache
uses: actions/cache@v5
with:
path: |
build/vcpkg_installed
submodules/vcpkg/downloads
key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }}
restore-keys: |
${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }}-
${{ runner.os }}-vcpkg-
- name: Configure
run: |
cmake -S . -B build -G Ninja \
@@ -58,9 +48,6 @@ jobs:
windows:
name: Windows
runs-on: windows-latest
env:
VCPKG_DOWNLOADS: ${{ github.workspace }}\submodules\vcpkg\downloads
VCPKG_BINARY_SOURCES: 'clear;files,${{ github.workspace }}\vcpkg_archives,readwrite'
steps:
- name: Checkout source
uses: actions/checkout@v5
@@ -69,22 +56,12 @@ jobs:
- name: Enable long paths
run: git config --global core.longpaths true
- name: Setup MSVC environment
uses: TheMrMilchmann/setup-msvc-dev@v4
uses: ilammy/msvc-dev-cmd@v1
with:
arch: x64
- name: Restore vcpkg Cache
uses: actions/cache@v5
with:
path: |
${{ env.VCPKG_DOWNLOADS }}
${{ github.workspace }}/vcpkg_archives
key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json') }}
restore-keys: |
${{ runner.os }}-vcpkg-
- name: Configure
shell: pwsh
run: |
+2 -2
View File
@@ -42,7 +42,7 @@ option(EQEMU_BUILD_PCH "Build with precompiled headers (Windows)" ON)
if(MSVC)
add_compile_options(/bigobj)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS NOMINMAX WIN32_LEAN_AND_MEAN CRASH_LOGGING)
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." OFF)
@@ -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)
+13 -3
View File
@@ -6,7 +6,6 @@ set(common_sources
bodytypes.cpp
classes.cpp
cli/eqemu_command_handler.cpp
compiler_macros.h
compression.cpp
content/world_content_service.cpp
crash.cpp
@@ -87,6 +86,7 @@ set(common_sources
packet_dump_file.cpp
packet_dump.cpp
packet_functions.cpp
patches/client_version.cpp
patches/patches.cpp
patches/rof_limits.cpp
patches/rof.cpp
@@ -98,6 +98,8 @@ set(common_sources
patches/sof.cpp
patches/titanium_limits.cpp
patches/titanium.cpp
patches/tob.cpp
patches/tob_limits.cpp
patches/uf_limits.cpp
patches/uf.cpp
path_manager.cpp
@@ -134,6 +136,7 @@ set(common_sources
util/directory.cpp
util/uuid.cpp
zone_store.cpp
links.cpp
)
set(repositories
@@ -653,6 +656,8 @@ set(common_headers
packet_dump_file.h
packet_dump.h
packet_functions.h
patches/IMessage.h
patches/client_version.h
patches/patches.h
patches/rof_limits.h
patches/rof_ops.h
@@ -669,7 +674,7 @@ set(common_headers
patches/sof_limits.h
patches/sof_ops.h
patches/sof_structs.h
patches/sof.h
patches/sof.h
patches/ss_declare.h
patches/ss_define.h
patches/ss_register.h
@@ -677,6 +682,10 @@ set(common_headers
patches/titanium_ops.h
patches/titanium_structs.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
@@ -728,7 +737,8 @@ set(common_headers
util/memory_stream.h
util/uuid.h
version.h
zone_store.h
zone_store.h
links.h
)
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources})
+1
View File
@@ -62,6 +62,7 @@ public:
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); }
+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;
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#if defined(_MSC_VER)
#define PUSH_DISABLE_DEPRECATED_WARNINGS() __pragma(warning(push)) \
__pragma(warning(disable:4996))
#define POP_DISABLE_DEPRECATED_WARNINGS() __pragma(warning(pop))
#elif defined(__GNUC__) || defined(__clang__)
#define PUSH_DISABLE_DEPRECATED_WARNINGS() _Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
#define POP_DISABLE_DEPRECATED_WARNINGS() _Pragma("GCC diagnostic pop")
#else
#define PUSH_DISABLE_DEPRECATED_WARNINGS()
#define POP_DISABLE_DEPRECATED_WARNINGS()
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define UNREACHABLE() __assume(0)
#else
#define UNREACHABLE() __builtin_unreachable()
#endif
+18
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;
+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
@@ -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),
@@ -381,6 +379,7 @@ N(OP_MercenaryTimer),
N(OP_MercenaryTimerRequest),
N(OP_MercenaryUnknown1),
N(OP_MercenaryUnsuspendResponse),
N(OP_MerchantBulkItems),
N(OP_MobEnduranceUpdate),
N(OP_MobHealth),
N(OP_MobManaUpdate),
@@ -399,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"
+8
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 {
+8
View File
@@ -271,7 +271,11 @@ static size_t const stackLimit_g = JSONCPP_DEPRECATED_STACK_LIMIT; // see readVa
namespace Json {
#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
typedef std::unique_ptr<CharReader> CharReaderPtr;
#else
typedef std::auto_ptr<CharReader> CharReaderPtr;
#endif
// Implementation of class Features
// ////////////////////////////////
@@ -4149,7 +4153,11 @@ Value& Path::make(Value& root) const {
namespace Json {
#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
typedef std::unique_ptr<StreamWriter> StreamWriterPtr;
#else
typedef std::auto_ptr<StreamWriter> StreamWriterPtr;
#endif
static bool containsControlCharacter(const char* str) {
while (*str) {
+31
View File
@@ -0,0 +1,31 @@
//
// Created by dannu on 4/18/2026.
//
#include "links.h"
#include "spdat.h"
void Links::FormatItemLink(char* Buffer, size_t BufferSize, const EQ::ItemInstance* item)
{
// TODO: Reverse 0x14064B220 to get definition of this function
}
void Links::FormatSpellLink(char* Buffer, size_t BufferSize, 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 = {});
}
+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();
}
+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
+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
+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
+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
+96
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>
@@ -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
+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
+2 -1
View File
@@ -350,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)
+109 -50
View File
@@ -380,59 +380,118 @@ std::string Strings::NumberToWords(unsigned long long int n)
return res;
}
std::string Strings::Money(uint64 platinum, uint64 gold, uint64 silver, uint64 copper, bool commify) {
uint64 values[] = { platinum, gold, silver, copper };
const char* names[] = { " platinum", " gold", " silver", " copper" };
std::vector<std::string> parts;
for (int i = 0; i < 4; ++i) {
if (values[i] > 0) {
std::string s = std::to_string(values[i]);
parts.push_back((commify ? Strings::Commify(s) : s) + names[i]);
}
std::string Strings::Money(uint64 platinum, uint64 gold, uint64 silver, uint64 copper)
{
std::string money_string = "Unknown";
if (copper && silver && gold && platinum) { // CSGP
money_string = fmt::format(
"{} platinum, {} gold, {} silver, and {} copper",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(gold)),
Strings::Commify(std::to_string(silver)),
Strings::Commify(std::to_string(copper))
);
}
if (parts.empty()) return "0 copper";
if (parts.size() == 1) return parts[0];
std::string result;
for (size_t i = 0; i < parts.size(); ++i) {
result += parts[i];
if (i < parts.size() - 2) {
result += ", ";
}
else if (i == parts.size() - 2) {
// Oxford comma logic: ", and " for 3+ items, " and " for 2
result += (parts.size() > 2) ? ", and " : " and ";
}
else if (copper && silver && !gold && platinum) { // CSP
money_string = fmt::format(
"{} platinum, {} silver, and {} copper",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(silver)),
Strings::Commify(std::to_string(copper))
);
}
return result;
else if (copper && silver && gold && !platinum) { // CSG
money_string = fmt::format(
"{} gold, {} silver, and {} copper",
Strings::Commify(std::to_string(gold)),
Strings::Commify(std::to_string(silver)),
Strings::Commify(std::to_string(copper))
);
}
else if (copper && !silver && !gold && platinum) { // CP
money_string = fmt::format(
"{} platinum and {} copper",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(copper))
);
}
else if (copper && silver && !gold && !platinum) { // CS
money_string = fmt::format(
"{} silver and {} copper",
Strings::Commify(std::to_string(silver)),
Strings::Commify(std::to_string(copper))
);
}
else if (!copper && silver && gold && platinum) { // SGP
money_string = fmt::format(
"{} platinum, {} gold, and {} silver",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(gold)),
Strings::Commify(std::to_string(silver))
);
}
else if (!copper && silver && !gold && platinum) { // SP
money_string = fmt::format(
"{} platinum and {} silver",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(silver))
);
}
else if (!copper && silver && gold && !platinum) { // SG
money_string = fmt::format(
"{} gold and {} silver",
Strings::Commify(std::to_string(gold)),
Strings::Commify(std::to_string(silver))
);
}
else if (copper && !silver && gold && platinum) { // CGP
money_string = fmt::format(
"{} platinum, {} gold, and {} copper",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(gold)),
Strings::Commify(std::to_string(copper))
);
}
else if (copper && !silver && gold && !platinum) { // CG
money_string = fmt::format(
"{} gold and {} copper",
Strings::Commify(std::to_string(gold)),
Strings::Commify(std::to_string(copper))
);
}
else if (!copper && !silver && gold && platinum) { // GP
money_string = fmt::format(
"{} platinum and {} gold",
Strings::Commify(std::to_string(platinum)),
Strings::Commify(std::to_string(gold))
);
}
else if (!copper && !silver && !gold && platinum) { // P
money_string = fmt::format(
"{} platinum",
Strings::Commify(std::to_string(platinum))
);
}
else if (!copper && !silver && gold && !platinum) { // G
money_string = fmt::format(
"{} gold",
Strings::Commify(std::to_string(gold))
);
}
else if (!copper && silver && !gold && !platinum) { // S
money_string = fmt::format(
"{} silver",
Strings::Commify(std::to_string(silver))
);
}
else if (copper && !silver && !gold && !platinum) { // C
money_string = fmt::format(
"{} copper",
Strings::Commify(std::to_string(copper))
);
}
return money_string;
}
std::string Strings::MoneyShort(uint64 copper, bool commify) {
// Matches merchant format
uint64 values[] = {
copper / 1000,
(copper / 100) % 10,
(copper / 10) % 10,
copper % 10
};
const char* names[] = { " platinum", " gold", " silver", " copper" };
std::string result;
for (int i = 0; i < 4; ++i) {
if (values[i] > 0) {
if (!result.empty()) result += " ";
std::string s = std::to_string(values[i]);
result += (commify ? Strings::Commify(s) : s) + names[i];
}
}
return result.empty() ? "0 copper" : result;
}
std::string Strings::SecondsToTime(int duration, bool is_milliseconds)
{
if (duration <= 0) {
+1 -2
View File
@@ -62,8 +62,7 @@ public:
static std::string Join(const std::vector<std::string> &ar, const std::string &delim);
static std::string Join(const std::vector<uint32_t> &ar, const std::string &delim);
static std::string MillisecondsToTime(int duration);
static std::string Money(uint64 platinum, uint64 gold = 0, uint64 silver = 0, uint64 copper = 0, bool commify = true);
static std::string MoneyShort(uint64 copper = 0, bool commify = true); // Matches merchant format when commify is false
static std::string Money(uint64 platinum, uint64 gold = 0, uint64 silver = 0, uint64 copper = 0);
static std::string NumberToWords(unsigned long long int n);
static std::string Repeat(std::string s, int n);
static std::string Replace(std::string subject, const std::string &search, const std::string &replace);
+1 -1
View File
@@ -103,7 +103,7 @@ namespace luabind { namespace detail
if (luabind::move_back_reference(L, ptr))
return;
make_instance(L, std::unique_ptr<T>(ptr));
make_instance(L, std::auto_ptr<T>(ptr));
}
};
+1 -1
View File
@@ -335,7 +335,7 @@ namespace luabind
template <class T>
struct default_pointer<null_type, T>
{
typedef std::unique_ptr<T> type;
typedef std::auto_ptr<T> type;
};
template <class Class, class Pointer, class Signature, class Policies>
+4 -4
View File
@@ -46,7 +46,7 @@ struct construct_aux<0, T, Pointer, Signature>
object_rep* self = touserdata<object_rep>(self_);
class_rep* cls = self->crep();
std::unique_ptr<T> instance(new T);
std::auto_ptr<T> instance(new T);
inject_backref(self_.interpreter(), instance.get(), instance.get());
void* naked_ptr = instance.get();
@@ -55,7 +55,7 @@ struct construct_aux<0, T, Pointer, Signature>
void* storage = self->allocate(sizeof(holder_type));
self->set_instance(new (storage) holder_type(
std::move(ptr), registered_class<T>::id, naked_ptr, cls));
ptr, registered_class<T>::id, naked_ptr, cls));
}
};
@@ -92,7 +92,7 @@ struct construct_aux<N, T, Pointer, Signature>
object_rep* self = touserdata<object_rep>(self_);
class_rep* cls = self->crep();
std::unique_ptr<T> instance(new T(BOOST_PP_ENUM_PARAMS(N,_)));
std::auto_ptr<T> instance(new T(BOOST_PP_ENUM_PARAMS(N,_)));
inject_backref(self_.interpreter(), instance.get(), instance.get());
void* naked_ptr = instance.get();
@@ -101,7 +101,7 @@ struct construct_aux<N, T, Pointer, Signature>
void* storage = self->allocate(sizeof(holder_type));
self->set_instance(new (storage) holder_type(
std::move(ptr), registered_class<T>::id, naked_ptr, cls));
ptr, registered_class<T>::id, naked_ptr, cls));
}
};
@@ -58,7 +58,7 @@ namespace has_get_pointer_
T* get_pointer(T const volatile*);
template<class T>
T* get_pointer(std::unique_ptr<T> const&);
T* get_pointer(std::auto_ptr<T> const&);
# endif
@@ -57,7 +57,7 @@ inline mpl::true_ check_const_pointer(void const*)
}
template <class T>
void release_ownership(std::unique_ptr<T>& p)
void release_ownership(std::auto_ptr<T>& p)
{
p.release();
}
@@ -83,7 +83,7 @@ public:
P p, class_id dynamic_id, void* dynamic_ptr, class_rep* cls
)
: instance_holder(cls, check_const_pointer(false ? get_pointer(p) : 0))
, p(std::move(p))
, p(p)
, weak(0)
, dynamic_id(dynamic_id)
, dynamic_ptr(dynamic_ptr)
@@ -88,7 +88,7 @@ void make_instance(lua_State* L, P p)
try
{
new (storage) holder_type(std::move(p), dynamic.first, dynamic.second, cls);
new (storage) holder_type(p, dynamic.first, dynamic.second, cls);
}
catch (...)
{
+3 -3
View File
@@ -169,7 +169,7 @@ namespace luabind { namespace detail
{
if (get_pointer(x))
{
make_instance(L, std::move(x));
make_instance(L, x);
}
else
{
@@ -180,8 +180,8 @@ namespace luabind { namespace detail
template <class T>
void make_pointee_instance(lua_State* L, T& x, mpl::false_, mpl::true_)
{
std::unique_ptr<T> ptr(new T(x));
make_instance(L, std::move(ptr));
std::auto_ptr<T> ptr(new T(x));
make_instance(L, ptr);
}
template <class T>
+1 -1
View File
@@ -46,7 +46,7 @@ namespace detail
template <class F, class Policies>
scope def(char const* name, F f, Policies const& policies)
{
return scope(std::unique_ptr<detail::registration>(
return scope(std::auto_ptr<detail::registration>(
new detail::function_registration<F, Policies>(name, f, policies)));
}
+1 -1
View File
@@ -56,7 +56,7 @@ namespace luabind {
struct LUABIND_API scope
{
scope();
explicit scope(std::unique_ptr<detail::registration> reg);
explicit scope(std::auto_ptr<detail::registration> reg);
scope(scope const& other_);
~scope();
+5 -5
View File
@@ -235,7 +235,7 @@ namespace luabind { namespace detail {
// -- interface ---------------------------------------------------------
class_base::class_base(char const* name)
: scope(std::unique_ptr<registration>(
: scope(std::auto_ptr<registration>(
m_registration = new class_registration(name))
)
{
@@ -258,14 +258,14 @@ namespace luabind { namespace detail {
void class_base::add_member(registration* member)
{
std::unique_ptr<registration> ptr(member);
m_registration->m_members.operator,(scope(std::move(ptr)));
std::auto_ptr<registration> ptr(member);
m_registration->m_members.operator,(scope(ptr));
}
void class_base::add_default_member(registration* member)
{
std::unique_ptr<registration> ptr(member);
m_registration->m_default_members.operator,(scope(std::move(ptr)));
std::auto_ptr<registration> ptr(member);
m_registration->m_default_members.operator,(scope(ptr));
}
const char* class_base::name() const
+2 -3
View File
@@ -49,8 +49,7 @@ namespace luabind { namespace detail {
{
}
scope::scope(std::unique_ptr<detail::registration> reg)
scope::scope(std::auto_ptr<detail::registration> reg)
: m_chain(reg.release())
{
}
@@ -194,7 +193,7 @@ namespace luabind {
};
namespace_::namespace_(char const* name)
: scope(std::unique_ptr<detail::registration>(
: scope(std::auto_ptr<detail::registration>(
m_registration = new registration_(name)))
{
}
-10
View File
@@ -37,13 +37,3 @@ target_include_directories(loginserver PRIVATE ..)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set_property(TARGET loginserver PROPERTY FOLDER executables/servers)
# vcpkg doesn't copy legacy.dll automatically because it is loaded at runtime, not via the import table.
if(WIN32 AND DEFINED VCPKG_INSTALLED_DIR AND DEFINED VCPKG_TARGET_TRIPLET)
add_custom_command(TARGET loginserver POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<IF:$<CONFIG:Debug>,${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug/bin/legacy.dll,${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/legacy.dll>"
"$<TARGET_FILE_DIR:loginserver>/legacy.dll"
VERBATIM
)
endif()
+41 -47
View File
@@ -88,6 +88,9 @@ bool Client::Process()
SendPlayToWorld((const char *) app->pBuffer);
break;
}
case OP_SystemFingerprint: {
break;
}
}
delete app;
@@ -104,16 +107,19 @@ void Client::HandleSessionReady(const char *data, unsigned int size)
return;
}
if (size < sizeof(unsigned int)) {
if (size < sizeof(int32)) {
LogError("Session ready was too small");
return;
}
//existing sequence id
int32 sequence_in = *(int32*)data;
m_client_status = cs_waiting_for_login;
auto *outapp = new EQApplicationPacket(OP_ChatMessage, sizeof(LoginHandShakeReply));
auto buf = reinterpret_cast<LoginHandShakeReply *>(outapp->pBuffer);
buf->base_header.sequence = 0x02;
buf->base_header.sequence = sequence_in;
buf->base_reply.success = true;
buf->base_reply.error_str_id = 0x65; // 101 "No Error"
@@ -296,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)];
}
}
@@ -358,16 +357,17 @@ void Client::SendFailedLogin()
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence; // login (3)
h.sequence = m_login_base_message.sequence;
h.encrypt_type = m_login_base_message.encrypt_type;
h.unk3 = m_login_base_message.unk3;
// encrypted
PlayerLoginReply 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);
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");
}
@@ -378,6 +378,7 @@ void Client::SendFailedLogin()
outapp.WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
m_connection->QueuePacket(&outapp);
m_client_status = cs_failed_to_login;
}
@@ -465,40 +466,33 @@ void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a)
// unencrypted
LoginBaseMessage h{};
h.sequence = m_login_base_message.sequence;
h.compressed = false;
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;
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());
PlayerLoginReply r = m_client_version == cv_tob ? PlayerLoginReply(PlayerLoginReplyTOB{}) : PlayerLoginReply(PlayerLoginReplyOld{});
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};
if (m_client_version != cv_tob) {
SendExpansionPacketData(r.old());
}
auto rc = eqcrypt_block((const char *) &r, sizeof(r), encrypted_buffer, 1);
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");
}
constexpr int outsize = sizeof(LoginBaseMessage) + sizeof(encrypted_buffer);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
auto outapp = std::make_unique<EQApplicationPacket>(OP_LoginAccepted, outsize);
outapp->WriteData(&h, sizeof(h));
outapp->WriteData(&encrypted_buffer, sizeof(encrypted_buffer));
@@ -507,7 +501,7 @@ void Client::DoSuccessfulLogin(LoginAccountsRepository::LoginAccounts &a)
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'
@@ -534,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
+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);
+20 -20
View File
@@ -74,7 +74,7 @@ void CheckSoDOpcodeFile(const std::string &path)
}
}
void CheckLarionOpcodeFile(const std::string &path)
void CheckTOBOpcodeFile(const std::string &path)
{
if (File::Exists(path)) {
return;
@@ -87,15 +87,15 @@ void CheckLarionOpcodeFile(const std::string &path)
fprintf(f, "OP_Login=0x0002\n");
fprintf(f, "OP_ServerListRequest=0x0004\n");
fprintf(f, "OP_PlayEverquestRequest=0x000d\n");
fprintf(f, "OP_PlayEverquestResponse=0x0022\n");
fprintf(f, "OP_ChatMessage=0x0017\n");
fprintf(f, "OP_LoginAccepted=0x0018\n");
fprintf(f, "OP_ServerListResponse=0x0019\n");
fprintf(f, "OP_Poll=0x0029\n");
fprintf(f, "OP_PlayEverquestResponse=0x0023\n");
fprintf(f, "OP_ChatMessage=0x0018\n");
fprintf(f, "OP_LoginAccepted=0x0019\n");
fprintf(f, "OP_ServerListResponse=0x001a\n");
fprintf(f, "OP_Poll=0x002a\n");
fprintf(f, "OP_EnterChat=0x000f\n");
fprintf(f, "OP_PollResponse=0x0011\n");
fprintf(f, "OP_SystemFingerprint=0x0016\n");
fprintf(f, "OP_ExpansionList=0x0030\n");
fprintf(f, "OP_ExpansionList=0x0031\n");
fclose(f);
}
}
@@ -177,40 +177,40 @@ ClientManager::ClientManager()
}
);
int larion_port = server.config.GetVariableInt("client_configuration", "larion_port", 15900);
int tob_port = server.config.GetVariableInt("client_configuration", "tob_port", 15900);
EQStreamManagerInterfaceOptions larion_opts(larion_port, false, false);
EQStreamManagerInterfaceOptions tob_opts(tob_port, false, false);
m_larion_stream = new EQ::Net::EQStreamManager(larion_opts);
m_larion_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_larion.conf"
"login_opcodes_tob.conf"
);
CheckLarionOpcodeFile(opcodes_path);
CheckTOBOpcodeFile(opcodes_path);
if (!m_larion_ops->LoadOpcodes(opcodes_path.c_str())) {
if (!m_tob_ops->LoadOpcodes(opcodes_path.c_str())) {
LogError(
"ClientManager fatal error: couldn't load opcodes for Larion file [{}]",
server.config.GetVariableString("client_configuration", "larion_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_larion_stream->OnNewConnection(
m_tob_stream->OnNewConnection(
[this](std::shared_ptr<EQ::Net::EQStream> stream) {
LogInfo(
"New Larion client connection from [{}:{}]",
"New TOB client connection from [{}:{}]",
long2ip(stream->GetRemoteIP()),
stream->GetRemotePort()
);
stream->SetOpcodeManager(&m_larion_ops);
Client *c = new Client(stream, cv_larion);
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_larion_ops;
EQ::Net::EQStreamManager *m_larion_stream;
OpcodeManager *m_tob_ops;
EQ::Net::EQStreamManager *m_tob_stream;
};
+28 -114
View File
@@ -16,12 +16,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "encryption.h"
#include "common/compiler_macros.h"
#ifdef EQEMU_USE_OPENSSL
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/provider.h>
#include <openssl/des.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
#endif
#ifdef EQEMU_USE_MBEDTLS
#include <mbedtls/des.h>
@@ -33,8 +32,6 @@
#include <cstring>
#include <string>
#include <memory>
#ifdef ENABLE_SECURITY
#include <sodium.h>
@@ -130,104 +127,21 @@ const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buff
#endif
#ifdef EQEMU_USE_OPENSSL
// Decrypt requires block-aligned input; encrypt zero-pads a trailing
// partial block to match the legacy DES_ncbc_encrypt semantics the
// game protocol expects.
DES_key_schedule k;
DES_cblock v;
memset(&k, 0, sizeof(DES_key_schedule));
memset(&v, 0, sizeof(DES_cblock));
if (!enc && buffer_in_sz && buffer_in_sz % 8 != 0) {
return nullptr;
}
unsigned char key[8] = {0};
unsigned char iv[8] = {0};
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
return nullptr;
}
bool result = EVP_CipherInit_ex2(ctx, EVP_des_cbc(), key, iv, enc, nullptr) == 1;
if (result) {
EVP_CIPHER_CTX_set_padding(ctx, 0);
const unsigned char* src = reinterpret_cast<const unsigned char*>(buffer_in);
size_t src_len = buffer_in_sz;
std::unique_ptr<unsigned char[]> padded;
if (enc && buffer_in_sz % 8 != 0) {
src_len = ((buffer_in_sz / 8) + 1) * 8;
padded.reset(new unsigned char[src_len]());
memcpy(padded.get(), buffer_in, buffer_in_sz);
src = padded.get();
}
int outl = 0;
int final_len = 0;
result = EVP_CipherUpdate(ctx, reinterpret_cast<unsigned char*>(buffer_out), &outl, src, static_cast<int>(src_len)) == 1
&& EVP_CipherFinal_ex(ctx, reinterpret_cast<unsigned char*>(buffer_out) + outl, &final_len) == 1;
}
EVP_CIPHER_CTX_free(ctx);
if (!result) {
return nullptr;
}
DES_ncbc_encrypt((const unsigned char*)buffer_in, (unsigned char*)buffer_out, (long)buffer_in_sz, &k, &v, enc);
#endif
return buffer_out;
}
#ifdef EQEMU_USE_OPENSSL
static OSSL_PROVIDER *s_legacy_provider = nullptr;
static OSSL_PROVIDER *s_default_provider = nullptr;
#endif
bool eqcrypt_init()
{
#ifdef EQEMU_USE_OPENSSL
#ifdef _WIN32
// Set OpenSSL default provider search path to the executable directory.
char* exe_path = nullptr;
if (_get_pgmptr(&exe_path) == 0 && exe_path != nullptr && *exe_path != '\0') {
std::string exe_dir{exe_path};
if (auto sep = exe_dir.find_last_of("\\/"); sep != std::string::npos) {
exe_dir.resize(sep);
OSSL_PROVIDER_set_default_search_path(nullptr, exe_dir.c_str());
}
}
#endif
if (!s_default_provider) {
s_default_provider = OSSL_PROVIDER_load(nullptr, "default");
}
if (!s_legacy_provider) {
s_legacy_provider = OSSL_PROVIDER_load(nullptr, "legacy");
}
if (!s_default_provider || !s_legacy_provider) {
char buf[256];
while (auto err = ERR_get_error()) {
ERR_error_string_n(err, buf, sizeof(buf));
LogError("OpenSSL provider load failure: {}", buf);
}
return false;
}
#endif
return true;
}
void eqcrypt_shutdown()
{
#ifdef EQEMU_USE_OPENSSL
if (s_legacy_provider) {
OSSL_PROVIDER_unload(s_legacy_provider);
s_legacy_provider = nullptr;
}
if (s_default_provider) {
OSSL_PROVIDER_unload(s_default_provider);
s_default_provider = nullptr;
}
#endif
}
std::string eqcrypt_md5(const std::string &msg)
{
std::string ret;
@@ -250,12 +164,12 @@ std::string eqcrypt_md5(const std::string &msg)
unsigned char md5_digest[16];
char tmp[4];
if (EVP_Digest(msg.data(), msg.length(), md5_digest, nullptr, EVP_md5(), nullptr) == 1) {
for (int i = 0; i < 16; ++i) {
sprintf(&tmp[0], "%02x", md5_digest[i]);
ret.push_back(tmp[0]);
ret.push_back(tmp[1]);
}
MD5((const unsigned char*)msg.c_str(), msg.length(), md5_digest);
for (int i = 0; i < 16; ++i) {
sprintf(&tmp[0], "%02x", md5_digest[i]);
ret.push_back(tmp[0]);
ret.push_back(tmp[1]);
}
#endif
@@ -284,12 +198,12 @@ std::string eqcrypt_sha1(const std::string &msg)
unsigned char sha_digest[20];
char tmp[4];
if (EVP_Digest(msg.data(), msg.length(), sha_digest, nullptr, EVP_sha1(), nullptr) == 1) {
for (int i = 0; i < 20; ++i) {
sprintf(&tmp[0], "%02x", sha_digest[i]);
ret.push_back(tmp[0]);
ret.push_back(tmp[1]);
}
SHA1((const unsigned char*)msg.c_str(), msg.length(), sha_digest);
for (int i = 0; i < 20; ++i) {
sprintf(&tmp[0], "%02x", sha_digest[i]);
ret.push_back(tmp[0]);
ret.push_back(tmp[1]);
}
#endif
@@ -318,12 +232,12 @@ std::string eqcrypt_sha512(const std::string &msg)
unsigned char sha_digest[64];
char tmp[4];
if (EVP_Digest(msg.data(), msg.length(), sha_digest, nullptr, EVP_sha512(), nullptr) == 1) {
for (int i = 0; i < 64; ++i) {
sprintf(&tmp[0], "%02x", sha_digest[i]);
ret.push_back(tmp[0]);
ret.push_back(tmp[1]);
}
SHA512((const unsigned char*)msg.c_str(), msg.length(), sha_digest);
for (int i = 0; i < 64; ++i) {
sprintf(&tmp[0], "%02x", sha_digest[i]);
ret.push_back(tmp[0]);
ret.push_back(tmp[1]);
}
#endif
-10
View File
@@ -48,20 +48,10 @@ namespace CryptoHash {
}
std::string GetEncryptionByModeId(uint32 mode);
// DES-CBC with an all-zero key and IV (EQ login protocol obfuscation, not security).
// On encrypt, a trailing partial block is zero-padded to the next 8-byte boundary, so
// buffer_out must be at least ((buffer_in_sz + 7) / 8) * 8 bytes. On decrypt, buffer_in_sz
// must already be a multiple of 8 or the call returns nullptr.
const char *eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char *buffer_out, bool enc);
std::string eqcrypt_hash(const std::string &username, const std::string &password, int mode);
bool eqcrypt_verify_hash(const std::string &username, const std::string &password, const std::string &pwhash, int mode);
// OpenSSL 3.0 moved DES behind the "legacy" provider; these load/unload it
// for the lifetime of the process. No-op when built against mbedtls.
bool eqcrypt_init();
void eqcrypt_shutdown();
struct EncryptionResult {
std::string password;
int mode = 0;
+93 -3
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,10 +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
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 = 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
@@ -100,12 +181,18 @@ struct PlayEverquestResponse {
uint32 server_number;
};
//for reference
struct SystemFingerprint {
LoginBaseMessage base_header;
char fingerprint[1];
};
#pragma pack()
enum LSClientVersion {
cv_titanium,
cv_sod,
cv_larion
cv_tob
};
enum LSClientStatus {
@@ -174,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.
};
}
@@ -3,12 +3,12 @@ OP_SessionReady=0x0001
OP_Login=0x0002
OP_ServerListRequest=0x0004
OP_PlayEverquestRequest=0x000d
OP_PlayEverquestResponse=0x0022
OP_ChatMessage=0x0017
OP_LoginAccepted=0x0018
OP_ServerListResponse=0x0019
OP_Poll=0x0029
OP_PlayEverquestResponse=0x0023
OP_ChatMessage=0x0018
OP_LoginAccepted=0x0019
OP_ServerListResponse=0x001a
OP_Poll=0x002a
OP_EnterChat=0x000f
OP_PollResponse=0x0011
OP_SystemFingerprint=0x0016
OP_ExpansionList=0x0030
OP_ExpansionList=0x0031
+4 -7
View File
@@ -26,7 +26,6 @@
#include "common/platform.h"
#include "common/timer.h"
#include "common/types.h"
#include "loginserver/encryption.h"
#include "loginserver/login_server.h"
#include "loginserver/loginserver_command_handler.h"
#include "loginserver/loginserver_webserver.h"
@@ -159,12 +158,12 @@ void start_web_server()
int main(int argc, char **argv)
{
RegisterExecutablePlatform(ExePlatformLogin);
EQEmuLogSys::Instance()->LoadLogSettingsDefaults();
set_exception_handler();
if (!eqcrypt_init()) {
LogError("Failed to initialize crypto providers");
return 1;
LogInfo("Logging System Init");
if (argc == 1) {
EQEmuLogSys::Instance()->LoadLogSettingsDefaults();
}
PathManager::Instance()->Init();
@@ -281,7 +280,5 @@ int main(int argc, char **argv)
LogInfo("Server Manager Shutdown");
delete server.server_manager;
eqcrypt_shutdown();
return 0;
}
+68 -31
View File
@@ -706,19 +706,17 @@ bool WorldServer::ValidateWorldServerAdminLogin(
void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_local_ip, LSClientVersion version) const
{
// see LoginClientServerData_Struct
if (use_local_ip) {
out.WriteString(GetLocalIP());
}
else {
out.WriteString(m_remote_ip_address);
}
if (version == cv_tob) {
if (use_local_ip) {
out.WriteString(GetLocalIP());
}
else {
out.WriteString(m_remote_ip_address);
}
if (version == cv_larion) {
out.WriteUInt32(9000);
}
out.WriteInt32(9000); // port, not currently settable in eqemu but needed for compat
switch (GetServerListID()) {
switch (GetServerListID()) {
case LS::ServerType::Legends:
out.WriteInt32(LS::ServerTypeFlags::Legends);
break;
@@ -728,35 +726,74 @@ void WorldServer::SerializeForClientServerList(SerializeBuffer &out, bool use_lo
default:
out.WriteInt32(LS::ServerTypeFlags::Standard);
break;
}
if (version == cv_larion) {
auto server_id = m_server_id;
//if this is 0, the client will not show the server in the list
out.WriteUInt32(1);
out.WriteUInt32(server_id);
}
else {
}
out.WriteInt32(289); //unsure what this is yet
out.WriteUInt32(m_server_id);
}
out.WriteString(m_server_long_name);
out.WriteString("us"); // country code
out.WriteString("en"); // language code
out.WriteString(m_server_long_name);
out.WriteString("US"); // country code
out.WriteString("EN"); // language code
out.WriteString("Standard");
out.WriteString("This server has no description set currently.");
// 0 = Up, 1 = Down, 2 = Up, 3 = down, 4 = locked, 5 = locked(down)
if (GetStatus() < 0) {
if (GetZonesBooted() == 0) {
out.WriteInt32(LS::ServerStatusFlags::Down);
if (GetStatus() < 0) {
if (GetZonesBooted() == 0) {
out.WriteInt32(LS::ServerStatusFlags::Down);
}
else {
out.WriteInt32(LS::ServerStatusFlags::Locked);
}
}
else {
out.WriteInt32(LS::ServerStatusFlags::Locked);
out.WriteInt32(LS::ServerStatusFlags::Up);
}
out.WriteUInt32(GetPlayersOnline());
out.WriteInt32(31); //expansions
out.WriteInt32(0); //truebox
}
else {
out.WriteInt32(LS::ServerStatusFlags::Up);
}
// see LoginClientServerData_Struct
if (use_local_ip) {
out.WriteString(GetLocalIP());
}
else {
out.WriteString(m_remote_ip_address);
}
switch (GetServerListID()) {
case LS::ServerType::Legends:
out.WriteInt32(LS::ServerTypeFlags::Legends);
break;
case LS::ServerType::Preferred:
out.WriteInt32(LS::ServerTypeFlags::Preferred);
break;
default:
out.WriteInt32(LS::ServerTypeFlags::Standard);
break;
}
out.WriteUInt32(GetPlayersOnline());
out.WriteUInt32(m_server_id);
out.WriteString(m_server_long_name);
out.WriteString("us"); // country code
out.WriteString("en"); // language code
// 0 = Up, 1 = Down, 2 = Up, 3 = down, 4 = locked, 5 = locked(down)
if (GetStatus() < 0) {
if (GetZonesBooted() == 0) {
out.WriteInt32(LS::ServerStatusFlags::Down);
}
else {
out.WriteInt32(LS::ServerStatusFlags::Locked);
}
}
else {
out.WriteInt32(LS::ServerStatusFlags::Up);
}
out.WriteUInt32(GetPlayersOnline());
}
}
void WorldServer::FormatWorldServerName(char *name, int8 server_list_type)
+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 | | |
+1
View File
@@ -0,0 +1 @@
This is a bunch of ImHex patterns for viewing various Outer Brood packets
@@ -0,0 +1,23 @@
struct BaseResponse
{
u8 success;
u32 error_str_id;
char error_str[];
};
struct Packet {
BaseResponse base;
s8 unk1; //I think this is just padding
s8 unk2; //I think this is just padding
u32 lsid;
char key[];
s32 failed_attempts;
u8 show_player_count;
s32 unk3; // 0
s32 unk4; // 0
char username[];
char password[]; //I'm not sure this is correct, it feels like this might be some internal refresh token
char paddingEnd[2];
};
Packet p @ 0x00;
+19
View File
@@ -0,0 +1,19 @@
// 0x01
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
u8 success;
s32 error_str_id;
char error_msg[];
char other_msg[];
};
Packet packet @0x00;
+22
View File
@@ -0,0 +1,22 @@
// 0x31
struct Expansion
{
u32 index;
u8 owned;
s32 expansion_name_string_id;
s32 order_string_id;
s32 unknown_string_id;
u32 unknown17;
};
struct Packet {
u32 unknown00;
u32 unknown04;
u16 unknown08;
u32 expansion_count;
Expansion expansions[expansion_count];
};
Packet packet @0x00;
+18
View File
@@ -0,0 +1,18 @@
// 0x02
#include <std/mem.pat>
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
u8 payload[std::mem::size() - $];
};
Packet packet @0x00;
+18
View File
@@ -0,0 +1,18 @@
// 0x19
#include <std/mem.pat>
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
u8 payload[std::mem::size() - $];
};
Packet packet @0x00;
@@ -0,0 +1,20 @@
// 0xd
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
u32 server_id;
char fingerprint[];
u32 unknown1;
u8 unknown2;
u32 unknown3;
};
Packet packet @0x00;
@@ -0,0 +1,19 @@
// 0x23
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
u8 success;
u32 login_server_string_id;
char login_server_string;
};
Packet packet @0x00;
@@ -0,0 +1,15 @@
// 0x04
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
};
Packet packet @0x00;
@@ -0,0 +1,44 @@
// 0x1a
// work in progress
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Realm
{
char address[];
u32 port;
u32 server_category;
//not sure yet, seen 289 on a lot of classic servers
//41 fangbreaker, teek, oakwynd, tormax
//31 yelniak
//33 vaniki, mischief
u32 status_code;
u32 server_id;
char name[];
char language[];
char region[];
char server_type_desc[];
char server_desc[];
u32 server_flags;
u32 players_online;
u32 expansion; //I think
u32 truebox_max_clients;
};
struct Packet {
LoginBase base;
u8 success;
u32 login_server_string_id;
char login_server_string[];
u32 realm_count;
Realm realms[realm_count];
};
Packet packet @0x00;
+16
View File
@@ -0,0 +1,16 @@
// 0x01
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
u16 unknown0a;
};
Packet packet @0x00;
@@ -0,0 +1,10 @@
// 0x16
struct Packet {
u32 sequence_id;
u32 unknown04;
u16 unknown08;
char fingerprint_data[];
};
Packet packet @0x00;
+17
View File
@@ -0,0 +1,17 @@
// 0x03
// I'm not sure what this packet is, it sends right after play everquest response it sent client->server
struct LoginBase
{
u32 sequence_id;
u8 compressed;
u8 encrypt_type;
u32 unknown08;
};
struct Packet {
LoginBase base;
};
Packet packet @0x00;
+27
View File
@@ -0,0 +1,27 @@
import argparse
from Crypto.Cipher import DES
def decrypt_hex_string(hex_data):
raw_hex = "".join(hex_data).replace(" ", "")
try:
encrypted_bytes = bytes.fromhex(raw_hex)
except ValueError:
return "Error: Input is not valid hexadecimal."
key = b'\x00' * 8
iv = b'\x00' * 8
cipher = DES.new(key, DES.MODE_CBC, iv)
decrypted_bytes = cipher.decrypt(encrypted_bytes)
return decrypted_bytes
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Decrypt EQ Default Encryption.")
parser.add_argument("data", nargs="+", help="The data hex string to decrypt")
args = parser.parse_args()
result = decrypt_hex_string(args.data)
print("--- Decrypted Data ---")
print(f"Data: {result.hex(' ').upper()}")
+26
View File
@@ -0,0 +1,26 @@
struct CharacterCreateAllocation
{
u32 index;
u32 base_stats[7];
u32 default_allocations[7];
};
struct RaceClassCombo
{
u64 expansion_req;
u32 race;
u32 class;
u32 deity;
u32 allocation_index;
u32 zone;
};
struct Packet {
u8 padding1;
u32 allocation_count;
CharacterCreateAllocation allocations[allocation_count];
u32 race_class_combo_count;
RaceClassCombo race_class_combos[race_class_combo_count];
};
Packet p @ 0x00;
+26
View File
@@ -0,0 +1,26 @@
struct MembershipEntry
{
u32 purchase_id;
u32 bitwise_entry;
};
struct MembershipSetting
{
s8 setting_index;
s32 setting_id; // 0 to 23 actually seen but the OP_Membership packet has up to 32
s32 setting_value;
};
struct Membership
{
u32 membership_setting_count;
MembershipSetting membership_settings[membership_setting_count];
u32 race_entry_count;
MembershipEntry membership_races[race_entry_count];
u32 class_entry_count;
MembershipEntry membership_classes[class_entry_count];
u32 exit_url_length;
char exit_url[exit_url_length];
};
Membership m @ 0x00;
+434
View File
@@ -0,0 +1,434 @@
struct Bind {
u32 zoneid;
float x;
float y;
float z;
float heading;
};
struct ArmorProperty
{
s32 type;
s32 variation;
s32 material;
s32 newArmorId;
s32 newArmorType;
};
struct AA
{
s32 index;
s32 points_spent;
s32 charges_spent;
};
struct EQGuid
{
u32 entity_id;
u32 realm_id;
};
struct SlotData
{
s32 slot_id;
s64 value;
};
struct EQAffect
{
float modifier;
EQGuid caster_id;
u32 duration;
u32 max_duration;
u8 level;
s32 spell_id;
s32 hitcount;
u32 flags;
u32 viral_timer;
u8 type;
SlotData slots[6];
};
struct Coin
{
u32 platinum;
u32 gold;
u32 silver;
u32 copper;
};
struct BandolierItemInfo {
char name[];
s32 item_id;
s32 icon;
};
struct BandolierSet
{
char name[];
BandolierItemInfo items[4];
};
struct ItemIndex
{
s16 slot1;
s16 slot2;
s16 slot3;
};
struct Claim
{
s32 feature_id;
s32 count;
};
struct Tribute {
u32 BenefitTimer;
s32 unknown1;
s32 current_favor;
s32 unknown2;
s32 all_time_favor;
s32 unknown3; //some of these are probably the bools on the pcclient;
u16 unknown4;
};
struct TributeBenefit
{
s32 benefit_id;
s32 benefit_tier;
};
struct RaidData
{
u32 main_assist1;
u32 main_assist2;
u32 main_assist3;
char main_assist_name1[];
char main_assist_name2[];
char main_assist_name3[];
u32 main_marker1;
u32 main_marker2;
u32 main_marker3;
u32 master_looter;
};
struct LdonData
{
u32 count;
u32 ldon_categories[count];
u32 ldon_points_available;
};
struct PvPData
{
u32 kills;
u32 deaths;
u32 current_points;
u32 career_points;
u32 best_kill_streak;
u32 worst_death_streak;
u32 current_kill_streak;
};
struct PvPKill
{
char name[];
u32 level;
u32 unknown1; //not sure
u32 unknown2; //not sure
u32 race;
u32 class;
u32 zone;
u32 time;
u32 points;
};
struct PvPDeath
{
char name[];
u32 level;
u32 race;
u32 class;
u32 zone;
u32 time;
u32 points;
};
struct AltCurrency
{
u32 alt_currency_str_length;
u32 unknown1;
char alt_currency_string[alt_currency_str_length];
};
struct AchivementSubComponentData
{
s32 achievement_id;
s32 component_id;
s32 requirement_id;
s32 requirement_type;
s32 count;
};
struct AlchemyBonusSkillData
{
s32 skill_id;
s32 bonus;
};
struct PersonaItemSlot
{
u32 item_id;
u32 slot_id;
};
struct PersonaEquipment
{
PersonaItemSlot item;
u32 augment_count;
PersonaItemSlot augments[augment_count];
};
struct PersonaEquipmentSet
{
u32 class_id;
u32 equipment_count;
PersonaEquipment equipment[equipment_count];
};
struct PcProfile
{
u32 profile_type;
u32 profile_id;
u32 shroud_template_id;
u8 gender;
u32 race;
u32 class;
u8 level;
u8 level1;
u32 bind_count;
Bind binds[bind_count];
u32 deity;
u32 intoxication;
u32 property_count;
u32 properties[property_count];
u32 armor_prop_count;
ArmorProperty armor_props[armor_prop_count];
u32 base_armor_prop_count;
ArmorProperty base_armor_props[base_armor_prop_count];
u32 body_tint_count;
u32 body_tints[body_tint_count];
u32 equip_tint_count;
u32 equip_tints[equip_tint_count];
u8 hair_color;
u8 facial_hair_color;
u32 npc_tint_index;
u8 eye_color1;
u8 eye_color2;
u8 hair_style;
u8 facial_hair;
u8 face;
u8 old_face;
u32 heritage;
u32 tattoo;
u32 details;
u8 texture_type;
u8 material;
u8 variation;
float height;
float width;
float length;
float view_height;
u32 primary;
u32 secondary;
u32 practices;
u32 base_mana;
u32 base_hp;
u32 base_str;
u32 base_sta;
u32 base_cha;
u32 base_dex;
u32 base_int;
u32 base_agi;
u32 base_wis;
u32 base_heroic_str;
u32 base_heroic_sta;
u32 base_heroic_cha;
u32 base_heroic_dex;
u32 base_heroic_int;
u32 base_heroic_agi;
u32 base_heroic_wis;
u32 aa_count;
AA aas[aa_count];
u32 skill_count;
s32 skills[skill_count];
u32 innate_skill_count;
s32 innate_skills[innate_skill_count];
u32 combat_ability_count;
s32 combat_abilities[combat_ability_count];
u32 combat_ability_timer_count;
s32 combat_ability_timers[combat_ability_timer_count];
u32 unk_ability_count;
u32 linked_spell_timer_count;
s32 linked_spell_timers[linked_spell_timer_count];
u32 item_recast_timer_count;
s32 item_recast_timers[item_recast_timer_count];
u32 spell_book_slot_count;
s32 spell_book_slots[spell_book_slot_count];
u32 spell_gem_count;
s32 spell_gems[spell_gem_count];
u32 spell_recast_timer_count;
s32 spell_recast_timers[spell_recast_timer_count];
u8 max_allowed_spell_slots;
u32 buff_count;
EQAffect buffs[buff_count];
Coin coin;
Coin cursor_coin;
u32 disc_timer;
u32 mend_timer;
u32 forage_timer;
u32 thirst;
u32 hunger;
u32 aa_spent;
u32 aa_window_count;
u32 aa_window_stats[aa_window_count];
u32 aa_points_unspent;
u8 sneak;
u8 hide;
u32 bandolier_count;
BandolierSet bandolier_sets[bandolier_count];
u32 invslot_bitmask;
u32 basedata_hp;
u32 basedata_mana;
u32 basedata_endur;
u32 basedata_mr;
u32 basedata_fr;
u32 basedata_cr;
u32 basedata_pr;
u32 basedata_dr;
u32 basedata_corrupt;
u32 basedata_phr;
float basedata_walkspeed;
float basedata_runspeed;
u32 basedata_hpregen;
u32 basedata_manaregen;
u32 basedata_mountmanaregen;
u32 basedata_endurregen;
u32 basedata_ac;
u32 basedata_atk;
u32 basedata_dmg;
u32 basedata_delay;
u32 endurance;
u32 heroic_type;
ItemIndex keyring_item_index[5];
u64 exp;
u64 aa_exp; //this is a guess, used to be 32 upped to 64
u16 unknown1;
EQGuid character_id;
u32 name_length;
char name[name_length];
u32 last_name_length;
char last_name[last_name_length];
u32 creation_time;
u32 account_creation_time;
u32 last_played_time;
u32 played_minutes;
u32 entitled_days;
u32 expansion_flags;
u32 unknown2; //new field from laurion to obrood
u32 language_count;
u8 languages[language_count];
u32 current_zone;
float current_x;
float current_y;
float current_z;
float current_heading;
u8 animation;
u8 pvp;
u8 anon;
u8 gm;
u64 guild_id;
u8 guild_show_sprite;
u8 status;
Coin coin2;
Coin bank2;
u32 bank_shared_plat;
u32 claim_count;
Claim claims[claim_count];
Tribute tribute;
u32 tribute_benefit_count;
TributeBenefit tribute_benefits[tribute_benefit_count];
u32 trophy_tribute_benefit_count;
TributeBenefit trophy_tribute_benefit[trophy_tribute_benefit_count];
u8 tasks[137]; //honestly not sure what this is, was just a guess
u32 good_points_available;
u32 good_points_earned;
u32 bad_points_available;
u32 bad_points_earned;
u32 momentum_balance;
u32 loyalty_reward_balance;
u32 parcel_status;
u32 vehicle_name_length;
char vehicle_name[vehicle_name_length];
u8 super_pkill;
u8 unclone;
u8 dead;
u32 ld_timer;
u32 spell_interrupt_count;
u8 autosplit;
u8 tells_off;
u8 gm_invis;
u32 kill_me;
u8 cheater_ld_flag;
u8 norent;
u8 corpse;
u8 client_gm_flag_set;
u32 mentor_pct;
RaidData raid;
u32 unique_player_id;
LdonData ldon_data;
u32 air_supply;
PvPData pvp_data;
PvPKill last_kill;
PvPDeath last_death;
u32 kills_in_past_24_hours;
u32 kill_list_count;
PvPKill kill_list[kill_list_count];
u32 pvp_infamy_level;
u32 pvp_vitality;
u32 cursor_krono;
u32 krono;
u8 autoconsent_group;
u8 autoconsent_raid;
u8 autoconsent_guild;
u8 autoconsent_fellowship;
u8 private_for_eq_players;
u32 main_level;
u8 show_helm;
u32 downtime;
AltCurrency alt_currency;
u32 completed_event_subcomponent_count;
AchivementSubComponentData completed_event_subcomponents[completed_event_subcomponent_count];
u32 inprogress_event_subcomponent_count;
AchivementSubComponentData inprogress_event_subcomponents[inprogress_event_subcomponent_count];
u64 merc_aa_exp;
u32 merc_aa_points;
u32 merc_aa_spent;
u32 starting_city_zone_id;
u8 use_advanced_looting;
u8 is_master_loot_candidate;
u32 alchemy_bonus_list_count;
AlchemyBonusSkillData alchemy_bonus_list[alchemy_bonus_list_count];
u32 persona_count;
PersonaEquipmentSet persona_equipment_set[persona_count];
u8 term;
};
struct Packet
{
u32 crc;
u32 length;
PcProfile profile;
};
Packet p @ 0x00;
+28
View File
@@ -0,0 +1,28 @@
def patch_template(template_path, opcodes_path, output_path):
try:
with open(opcodes_path, 'r') as f:
opcodes = [line.strip() for line in f if line.strip()]
with open(template_path, 'r') as f:
content = f.read()
for index, value in enumerate(opcodes):
placeholder = f"{{{{{index}}}}}"
content = content.replace(placeholder, value)
with open(output_path, 'w') as f:
f.write(content)
print(f"Successfully transformed: {output_path}")
except FileNotFoundError as e:
print(f"Error: File Not Found - {e}")
except Exception as e:
print(f"Error: Exception - {e}")
if __name__ == "__main__":
patch_template(
template_path='opcode.template',
opcodes_path='opcodes.csv',
output_path='patch_TOB.conf'
)
View File
File diff suppressed because it is too large Load Diff
+699
View File
@@ -0,0 +1,699 @@
# ShowEQ Import Notes:
# ZERO THE FILE first
# perl -pi -e 's/0x[0-9a-fA-F]{4}/0x0000/g' opcodes.conf
# Unknown Mapping:
# OP_Action2 -> OP_Damage
# OP_EnvDamage -> OP_Damage ---> might have been a one time mistake
# Name Differences:
# OP_CancelInvite -> OP_GroupCancelInvite
# OP_GMFind -> OP_FindPersonRequest
# OP_CommonMessage -> OP_ChannelMessage
OP_Unknown=0x0000
OP_ExploreUnknown=0x0000 # used for unknown explorer
# world packets
# Required to reach Char Select:
OP_SendLoginInfo=0x722a
OP_ApproveWorld=0x0000
OP_LogServer=0x2cae
OP_SendCharInfo=0x5d1a
OP_ExpansionInfo=0x393a
OP_EnterWorld=0x7fb8
OP_PostEnterWorld=0x1945 # unused
OP_World_Client_CRC1=0x777f # This is OP_SendExeChecksum
OP_World_Client_CRC2=0x0492 # This is OP_SendBaseDataChecksum
OP_World_Client_CRC3=0x0690 # This is OP_SendSkillCapsChecksum
OP_SendSpellChecksum=0x0000 # There is no spell checksum in TOB
OP_SendSkillCapsChecksum=0x0690
# Character Select Related:
OP_SendMaxCharacters=0x25eb
OP_SendMembership=0x5789
OP_SendMembershipDetails=0x2373
OP_CharacterCreateRequest=0x6b67
OP_CharacterCreate=0x1859
OP_DeleteCharacter=0x71ca
OP_RandomNameGenerator=0x7f4a # TOB client no longer sends this, and the S->C packet isn't used by emu
OP_ApproveName=0x5306
OP_MOTD=0x1eef
OP_SetChatServer=0x0000
OP_SetChatServer2=0x34c1
OP_ZoneServerInfo=0x2323
OP_WorldComplete=0x1223
OP_WorldUnknown001=0x7723 # SetServerTime, S->C
OP_FloatListThing=0x504f # Movement History, C->S
# Reasons for Disconnect:
OP_ZoneUnavail=0x29a6
OP_WorldClientReady=0x538f
OP_CharacterStillInZone=0x0000
OP_WorldChecksumFailure=0x0000
OP_WorldLoginFailed=0x0000
OP_WorldLogout=0x0000
OP_WorldLevelTooHigh=0x0000
OP_CharInacessable=0x0000
OP_UserCompInfo=0x0000
OP_SendExeChecksum=0x777f
OP_SendBaseDataChecksum=0x0492
# Zone in opcodes
OP_AckPacket=0x776d
OP_ZoneEntry=0x713d
OP_ReqNewZone=0x1ccc
OP_NewZone=0x5ec0
OP_ZoneSpawns=0x6cd9
OP_PlayerProfile=0x08bf
OP_TimeOfDay=0x5503
OP_LevelUpdate=0x54bf
OP_Stamina=0x3b01
OP_RequestClientZoneChange=0x7a59
OP_ZoneChange=0x4816
OP_LockoutTimerInfo=0x0000
OP_ZoneServerReady=0x0000
OP_ZoneInUnknown=0x0000
OP_LogoutReply=0x0000
OP_PreLogoutReply=0x0000
# Required to fully log in
OP_SpawnAppearance=0x513a
OP_ChangeSize=0x1ed0
OP_Weather=0x1e6d
OP_ReqClientSpawn=0x6a27
OP_SpawnDoor=0x532b
OP_GroundSpawn=0x14c7
OP_SendZonepoints=0x21a2
OP_BlockedBuffs=0x0f6f
OP_RemoveBlockedBuffs=0x4471
OP_ClearBlockedBuffs=0x27ce
OP_WorldObjectsSent=0x4f32
OP_SendExpZonein=0x7267
OP_SendAATable=0x22bd
OP_ClearAA=0x6093
OP_ClearLeadershipAbilities=0x0000 #removed; leadership abilities are baked in and always on
OP_RespondAA=0x4449
OP_UpdateAA=0x1655
OP_SendAAStats=0x7416 # Removed in TOB
OP_AAExpUpdate=0x04c3 #need to look into whether this has changed; exp did
OP_ExpUpdate=0x0e55
OP_HPUpdate=0x2723
OP_ManaChange=0x08f6
OP_TGB=0x0000 #removed; tgb is baked in and always on
OP_SpecialMesg=0x08bd
OP_CharInventory=0x15b4
OP_WearChange=0x5897
OP_ClientUpdate=0x1615
OP_ClientReady=0x666f
OP_SetServerFilter=0x5a35
# Guild Opcodes
OP_GuildsList=0x0000
OP_GuildMemberList=0x0000
OP_GuildMOTD=0x0000
OP_GetGuildMOTD=0x0000
OP_GetGuildMOTDReply=0x0000
OP_GuildMemberUpdate=0x0000
OP_GuildInvite=0x0000
OP_GuildRemove=0x0000
OP_GuildPeace=0x0000
OP_SetGuildMOTD=0x0000
OP_GuildWar=0x0000
OP_GuildLeader=0x0000
OP_GuildDelete=0x0000
OP_GuildInviteAccept=0x0000
OP_GuildDemote=0x0000
OP_GuildPromote=0x0000
OP_GuildPublicNote=0x0000
OP_GuildManageBanker=0x0000
OP_GuildBank=0x0000
OP_GuildBankItemList=0x0000
OP_SetGuildRank=0x0000
OP_GuildUpdate=0x0000
OP_GuildStatus=0x0000
OP_GuildCreate=0x0000
OP_GuildOpenGuildWindow=0x0000
OP_GuildMemberLevel=0x0000
OP_GuildMemberRankAltBanker=0x0000
OP_GuildMemberPublicNote=0x0000
OP_GuildMemberAdd=0x0000
OP_GuildMemberRename=0x0000
OP_GuildMemberDelete=0x0000
OP_GuildMemberDetails=0x0000
OP_GuildRenameGuild=0x0000
OP_LFGuild=0x0000
OP_GuildDeleteGuild=0x0000
# GM/Guide Opcodes
OP_GMServers=0x0000
OP_GMBecomeNPC=0x0000
OP_GMZoneRequest=0x0000
OP_GMZoneRequest2=0x0000
OP_GMGoto=0x0000
OP_GMSearchCorpse=0x0000
OP_GMHideMe=0x0000
OP_GMDelCorpse=0x0000
OP_GMApproval=0x0000
OP_GMToggle=0x0000
OP_GMSummon=0x0000
OP_GMEmoteZone=0x0000
OP_GMEmoteWorld=0x0000
OP_GMFind=0x0000
OP_GMKick=0x0000
OP_GMKill=0x0000
OP_GMNameChange=0x0000
OP_GMLastName=0x0000
# Misc Opcodes
OP_QueryUCSServerStatus=0x7093
OP_InspectRequest=0x0000
OP_InspectAnswer=0x0000
OP_InspectMessageUpdate=0x0000
OP_BeginCast=0x34b1
OP_ColoredText=0x1743
OP_ConsentResponse=0x74b3
OP_MemorizeSpell=0x6af0
OP_LinkedReuse=0x5683
OP_SwapSpell=0x32cc
OP_CastSpell=0x1d63
OP_Consider=0x4568
OP_FormattedMessage=0x29b4
OP_SimpleMessage=0x5b2d
OP_Buff=0x2427
OP_Illusion=0x7fb0
OP_MoneyOnCorpse=0x6f63
OP_RandomReply=0x1234
OP_DenyResponse=0x339b
OP_SkillUpdate=0x0149
OP_GMTrainSkillConfirm=0x3365
OP_RandomReq=0x0313
OP_Death=0x1e90
OP_GMTraining=0x3d75
OP_GMEndTraining=0x1bf2
OP_GMTrainSkill=0x1525
OP_Animation=0x3807
OP_Begging=0x217b
OP_Consent=0x6c66
OP_ConsentDeny=0x5343
OP_AutoFire=0x2583
OP_PetCommands=0x0000
OP_PetCommandState=0x0000
OP_PetHoTT=0x0000
OP_DeleteSpell=0x5d53
OP_Surname=0x0000
OP_ClearSurname=0x0000
OP_FaceChange=0x0000
OP_SetFace=0x0000
OP_SenseHeading=0x2ff2
OP_Action=0x7d28
OP_ConsiderCorpse=0x2f98
OP_HideCorpse=0x2623
OP_CorpseDrag=0x7200
OP_CorpseDrop=0x2a53
OP_Bug=0x2846
OP_Feedback=0x0000
OP_Report=0x6b73
OP_Damage=0x5b42
OP_ChannelMessage=0x7622
OP_Assist=0x51a0
OP_AssistGroup=0x4879
OP_MoveCoin=0x1987
OP_ZonePlayerToBind=0x1860
OP_KeyRing=0x0000
OP_WhoAllRequest=0x3328
OP_WhoAllResponse=0x4dfd
OP_FriendsWho=0x3547
OP_ConfirmDelete=0x14a8 # This is sent fromt the client after a movement update (with just spawn ID as the content)
OP_Logout=0x46f8
OP_Rewind=0x898a
OP_TargetCommand=0x46bf
OP_Hide=0x4f10
OP_Jump=0x2b69
OP_Camp=0x4fe0
OP_Emote=0x0000
OP_SetRunMode=0x3b78
OP_BankerChange=0x0fa6
OP_TargetMouse=0x7f48
OP_MobHealth=0x445e
OP_InitialMobHealth=0x0000 # Unused?
OP_TargetHoTT=0x0000
OP_TargetBuffs=0x0000
OP_XTargetResponse=0x0000
OP_XTargetRequest=0x0000
OP_XTargetAutoAddHaters=0x0000
OP_XTargetOpen=0x0000
OP_XTargetOpenResponse=0x0000
OP_BuffCreate=0x754c
OP_BuffRemoveRequest=0x0c06
OP_DeleteSpawn=0x33fa
OP_AutoAttack=0x3ced
OP_AutoAttack2=0x1824
OP_Consume=0x2d2d
OP_MoveItem=0x121c
OP_MoveMultipleItems=0x6bf7
OP_DeleteItem=0x29e6
OP_DeleteCharge=0x4bef
OP_ItemPacket=0x0fb6
OP_ItemLinkResponse=0x0000
OP_ItemLinkClick=0x0000
OP_ItemPreview=0x0000
OP_NewSpawn=0x053d
OP_Track=0x24c2
OP_TrackTarget=0x0941
OP_TrackUnknown=0x0f55
OP_ClickDoor=0x1678
OP_MoveDoor=0x6e1d
OP_RemoveAllDoors=0x01df
OP_EnvDamage=0x2dbe
OP_BoardBoat=0x0c52
OP_LeaveBoat=0x6097
OP_ControlBoat=0x79ad
OP_Forage=0x5b12
OP_SafeFallSuccess=0x6341
OP_RezzComplete=0x0000
OP_RezzRequest=0x0000
OP_RezzAnswer=0x0000
OP_Shielding=0x0000
OP_RequestDuel=0x0000
OP_MobRename=0x0000
OP_AugmentItem=0x322a
OP_WeaponEquip1=0x0000
OP_PlayerStateAdd=0x0e05
OP_PlayerStateRemove=0x0ec0
OP_ApplyPoison=0x7216
OP_Save=0x667f
OP_TestBuff=0x0000
OP_CustomTitles=0x0000
OP_Split=0x3ea6
OP_YellForHelp=0x1330
OP_LoadSpellSet=0x0000
OP_Bandolier=0x0000
OP_PotionBelt=0x0000
OP_DuelDecline=0x0000
OP_DuelAccept=0x0000
OP_SaveOnZoneReq=0x16e3
OP_ReadBook=0x165a
OP_Dye=0x0000
OP_InterruptCast=0x5313
OP_AAAction=0x48fb
OP_LeadershipExpToggle=0x0000 #removed, these act as if all purchased now
OP_LeadershipExpUpdate=0x0000 #removed, these act as if all purchased now
OP_PurchaseLeadershipAA=0x0000 #removed, these act as if all purchased now
OP_UpdateLeadershipAA=0x0000 #removed, these act as if all purchased now
OP_MarkNPC=0x0000
OP_ClearNPCMarks=0x0000
OP_DelegateAbility=0x0000
OP_SetGroupTarget=0x0000
OP_Charm=0x2509
OP_Stun=0x7f1d
OP_SendFindableNPCs=0x0000
OP_FindPersonRequest=0x0000
OP_FindPersonReply=0x0000
OP_Sound=0x5949
OP_CashReward=0x3237
OP_PetBuffWindow=0x0000
OP_LevelAppearance=0x5eb5
OP_Translocate=0x0611
OP_Sacrifice=0x4b76
OP_PopupResponse=0x4032
OP_OnLevelMessage=0x552e
OP_AugmentInfo=0x19eb
OP_Petition=0x0000
OP_SomeItemPacketMaybe=0x0000
OP_PVPStats=0x0000
OP_PVPLeaderBoardRequest=0x0000
OP_PVPLeaderBoardReply=0x0000
OP_PVPLeaderBoardDetailsRequest=0x0000
OP_PVPLeaderBoardDetailsReply=0x0000
OP_RestState=0x1930
OP_RespawnWindow=0x5c
OP_LDoNButton=0x0000
OP_SetStartCity=0x0000
OP_VoiceMacroIn=0x2963
OP_VoiceMacroOut=0x028d
OP_ItemViewUnknown=0x0000
OP_VetRewardsAvaliable=0x0000
OP_VetClaimRequest=0x0000
OP_VetClaimReply=0x0000
OP_DisciplineUpdate=0x0a2f
OP_DisciplineTimer=0x2782
OP_BecomeCorpse=0x0000 # Unused?
OP_Action2=0x0000 # Unused?
OP_MobUpdate=0x0000
OP_NPCMoveUpdate=0x0000
OP_CameraEffect=0x3352
OP_SpellEffect=0x7ea2
OP_AddNimbusEffect=0x54b1
OP_RemoveNimbusEffect=0x4e88
OP_AltCurrency=0x0000
OP_AltCurrencyMerchantRequest=0x0000
OP_AltCurrencyMerchantReply=0x0000
OP_AltCurrencyPurchase=0x0000
OP_AltCurrencySell=0x0000
OP_AltCurrencySellSelection=0x0000
OP_AltCurrencyReclaim=0x0000
OP_CrystalCountUpdate=0x0000
OP_CrystalCreate=0x0000
OP_CrystalReclaim=0x0000
OP_Untargetable=0x6205
OP_IncreaseStats=0x1312
OP_Weblink=0x41f7
OP_OpenContainer=0x1e8a
OP_Marquee=0x257c
OP_ItemRecastDelay=0x0fe4
#OP_OpenInventory=0x0000 # Likely does not exist in RoF -U
OP_ResetAA=0x3126
OP_Fling=0x1101
OP_CancelSneakHide=0x14b0
OP_AggroMeterLockTarget=0x0000
OP_AggroMeterTargetInfo=0x0000
OP_AggroMeterUpdate=0x0000
OP_UnderWorld=0x156c # clients sends up when they detect an underworld issue, might be useful for cheat detection
OP_KickPlayers=0x1257
OP_BookButton=0x4e78
# Expeditions
OP_DzQuit=0x0000
OP_DzListTimers=0x0000
OP_DzAddPlayer=0x0000
OP_DzRemovePlayer=0x0000
OP_DzSwapPlayer=0x0000
OP_DzMakeLeader=0x0000
OP_DzPlayerList=0x0000
OP_DzExpeditionInvite=0x0000
OP_DzExpeditionInviteResponse=0x0000
OP_DzExpeditionInfo=0x0000
OP_DzExpeditionLockoutTimers=0x0000
OP_DzMemberList=0x0000
OP_DzMemberListName=0x0000
OP_DzMemberListStatus=0x0000
OP_DzSetLeaderName=0x0000
OP_DzExpeditionEndsWarning=0x0000
OP_DzCompass=0x0000
OP_DzChooseZone=0x0000
OP_DzChooseZoneReply=0x0000
# New Opcodes
OP_SpawnPositionUpdate=0x0000 # Actually OP_MobUpdate ?
OP_ManaUpdate=0x0000
OP_EnduranceUpdate=0x0000
OP_MobManaUpdate=0x0000
OP_MobEnduranceUpdate=0x0000
# Mercenary Opcodes
OP_MercenaryDataUpdateRequest=0x0000
OP_MercenaryDataUpdate=0x0000
OP_MercenaryDataRequest=0x0000
OP_MercenaryDataResponse=0x0000
OP_MercenaryHire=0x0000
OP_MercenaryDismiss=0x0000
OP_MercenaryTimerRequest=0x0000
OP_MercenaryTimer=0x0000
OP_MercenaryUnknown1=0x0000
OP_MercenaryCommand=0x0000
OP_MercenarySuspendRequest=0x0000
OP_MercenarySuspendResponse=0x0000
OP_MercenaryUnsuspendResponse=0x0000
# Looting
OP_LootRequest=0x2239
OP_EndLootRequest=0x173c
OP_LootItem=0x5241
OP_LootComplete=0x5470
# bazaar trader stuff:
OP_BazaarSearch=0x0000
OP_TraderDelItem=0x0000
OP_BecomeTrader=0x0000
OP_TraderShop=0x0000
OP_TraderBulkSend=0x0000
OP_Trader=0x0000
OP_Barter=0x0000
OP_BuyerItems=0x0000
OP_TraderBuy=0x0000
OP_ShopItem=0x0000
OP_BazaarInspect=0x0000
OP_Bazaar=0x0000
OP_TraderItemUpdate=0x0000
# pc/npc trading
OP_TradeRequest=0x1f5d
OP_TradeAcceptClick=0x1d90
OP_TradeRequestAck=0x2737
OP_TradeCoins=0x63d8
OP_FinishTrade=0x0875
OP_CancelTrade=0x5fb5
OP_TradeMoneyUpdate=0x544d
OP_MoneyUpdate=0x578a
OP_TradeBusy=0x43b8
# Sent after canceling trade or after closing tradeskill object
OP_FinishWindow=0x1935
OP_FinishWindow2=0x5458
# Sent on Live for what seems to be item existance verification
# Ex. Before Right Click Effect happens from items
OP_ItemVerifyRequest=0x4d8e
OP_ItemVerifyReply=0x0bce
OP_ItemAdvancedLoreText=0x0000
# merchant stuff
OP_ShopPlayerSell=0x5d8e
OP_ShopRequest=0x25d0
OP_ShopEnd=0x3e98
OP_ShopEndConfirm=0x493d
OP_ShopPlayerBuy=0x0696
OP_ShopDelItem=0x0672
OP_ShopSendParcel=0x3d05
OP_ShopDeleteParcel=0x109b
OP_ShopRetrieveParcel=0x5d5b
OP_ShopParcelIcon=0x1936
# tradeskill stuff:
OP_ClickObject=0x6693
OP_ClickObjectAction=0x3dbe
OP_ClearObject=0x3df2
OP_RecipeDetails=0x400f
OP_RecipesFavorite=0x2d6b
OP_RecipesSearch=0x1a3a
OP_RecipeReply=0x3e33
OP_RecipeAutoCombine=0x5257
OP_TradeSkillCombine=0x40af
# Tribute Packets:
OP_TributeUpdate=0x0000
OP_TributeTimer=0x0000
OP_SendTributes=0x0000
OP_RequestGuildTributes=0x0000
OP_TributeInfo=0x0000
OP_OpenTributeMaster=0x0000
OP_SelectTribute=0x0000
OP_TributeItem=0x0000
OP_TributeMoney=0x0000
OP_TributeToggle=0x0000
OP_TributePointUpdate=0x0000
OP_TributeNPC=0x0000
OP_GuildTributeInfo=0x0000
OP_OpenTributeReply=0x0000
OP_GuildTributeStatus=0x0000
OP_GuildSaveActiveTributes=0x0000
OP_GuildSendActiveTributes=0x0000
OP_GuildTributeToggleReq=0x0000
OP_GuildTributeToggleReply=0x0000
OP_GuildTributeFavorAndTimer=0x0000
OP_GuildTributeDonateItem=0x0000
OP_GuildTributeDonatePlat=0x0000
OP_GuildSelectTribute=0x0000
OP_GuildModifyBenefits=0x0000
OP_GuildOptInOut=0x0000
OP_SendGuildTributes=0x0000
OP_OpenGuildTributeMaster=0x0000
# Adventure packets:
OP_LeaveAdventure=0x0000
OP_AdventureFinish=0x0000
OP_AdventureInfoRequest=0x0000
OP_AdventureInfo=0x0000
OP_AdventureRequest=0x0000
OP_AdventureDetails=0x0000
OP_AdventureData=0x0000
OP_AdventureUpdate=0x0000
OP_AdventureMerchantRequest=0x0000
OP_AdventureMerchantResponse=0x0000
OP_AdventureMerchantPurchase=0x0000
OP_AdventureMerchantSell=0x0000
OP_AdventurePointsUpdate=0x0000
OP_AdventureStatsRequest=0x0000
OP_AdventureStatsReply=0x0000
OP_AdventureLeaderboardRequest=0x0000
OP_AdventureLeaderboardReply=0x0000
# Group Opcodes
OP_GroupDisband=0x0573
OP_GroupInvite=0x7e79
OP_GroupFollow=0x0000
OP_GroupUpdate=0x0000
OP_GroupUpdateB=0x0000
OP_GroupCancelInvite=0x0000
OP_GroupAcknowledge=0x0000
OP_GroupDelete=0x0000
OP_CancelInvite=0x0000
OP_GroupFollow2=0x0000
OP_GroupInvite2=0x43b1
OP_GroupDisbandYou=0x0000
OP_GroupDisbandOther=0x0000
OP_GroupLeaderChange=0x0000
OP_GroupRoles=0x0000
OP_GroupMakeLeader=0x0000
OP_DoGroupLeadershipAbility=0x0000
OP_GroupLeadershipAAUpdate=0x0000 # removed these act as if you have always purchased them
OP_GroupMentor=0x0000
OP_InspectBuffs=0x0000
# LFG/LFP Opcodes
OP_LFGCommand=0x0000
OP_LFGGetMatchesRequest=0x0000
OP_LFGGetMatchesResponse=0x0000
OP_LFPGetMatchesRequest=0x0000
OP_LFPGetMatchesResponse=0x0000
OP_LFPCommand=0x0000
OP_LFGAppearance=0x0000
OP_LFGResponse=0x0000
# Raid Opcodes
OP_RaidInvite=0x0000
OP_RaidUpdate=0x0000
OP_RaidJoin=0x0000
OP_RaidDelegateAbility=0x0000
OP_MarkRaidNPC=0x0000
OP_RaidClearNPCMarks=0x0000
# Button-push commands
OP_Taunt=0x6ad9
OP_CombatAbility=0x50e2
OP_SenseTraps=0x235e
OP_PickPocket=0x2c63
OP_DisarmTraps=0x7362
OP_Disarm=0x5a91
OP_Sneak=0x7f05
OP_Fishing=0x3cdb
OP_InstillDoubt=0x3cdb
OP_FeignDeath=0x3d9f
OP_Mend=0x3bac
OP_Bind_Wound=0x580f
OP_LDoNOpen=0x7a62
OP_LDoNPickLock=0x36ea
OP_LDoNInspect=0x256a
# Task packets
OP_TaskDescription=0x0000
OP_TaskActivity=0x0000
OP_CompletedTasks=0x0000
OP_TaskActivityComplete=0x0000
OP_AcceptNewTask=0x0000
OP_CancelTask=0x0000
OP_AvaliableTask=0x0000
OP_TaskHistoryRequest=0x0000
OP_TaskHistoryReply=0x0000
OP_DeclineAllTasks=0x0000
OP_TaskRequestTimer=0x0000
OP_TaskSelectWindow=0x0000
# Shared Tasks
OP_SharedTaskMemberList=0x0000 #
OP_SharedTaskRemovePlayer=0x0000 # /taskremoveplayer
OP_SharedTaskAddPlayer=0x0000 # /taskaddplayer
OP_SharedTaskMakeLeader=0x0000 # /taskmakeleader
OP_SharedTaskInvite=0x0000 # Dialog window
OP_SharedTaskInviteResponse=0x0000 # Dialog window response
OP_SharedTaskAcceptNew=0x0000 #
OP_SharedTaskMemberChange=0x0000 #
OP_TaskTimers=0x0000 # /tasktimers
OP_SharedTaskQuit=0x0000 # /taskquit
OP_SharedTaskSelectWindow=0x0000
OP_SharedTaskPlayerList=0x0000 # /taskplayerlist
# Title opcodes
OP_NewTitlesAvailable=0x0000
OP_RequestTitles=0x0000
OP_SendTitleList=0x0000
OP_SetTitle=0x0000
OP_SetTitleReply=0x0000
# mail opcodes
OP_Command=0x0000
OP_MailboxHeader=0x0000
OP_MailHeader=0x0000
OP_MailBody=0x0000
OP_NewMail=0x0000
OP_SentConfirm=0x0000
########### Below this point should not be needed ###########
# This section are all unknown in Titanium
OP_ForceFindPerson=0x0000
OP_LocInfo=0x0000
OP_ReloadUI=0x0000
OP_ItemName=0x0000
OP_ItemLinkText=0x0000
OP_MultiLineMsg=0x0000
OP_MendHPUpdate=0x0000
OP_TargetReject=0x0000
OP_SafePoint=0x0000
OP_ApproveZone=0x0000
OP_ZoneComplete=0x0000
OP_ClientError=0x0000
OP_DumpName=0x0000
OP_Heartbeat=0x0000
OP_CrashDump=0x0000
OP_LoginComplete=0x0000
# discovered opcodes not yet used:
OP_PickLockSuccess=0x0000
OP_PlayMP3=0x2e09
OP_ReclaimCrystals=0x0000
OP_DynamicWall=0x0000
OP_OpenDiscordMerchant=0x0000
OP_DiscordMerchantInventory=0x0000
OP_GiveMoney=0x0000
OP_RequestKnowledgeBase=0x0000
OP_KnowledgeBase=0x0000
OP_SlashAdventure=0x0000 # /adventure
OP_BecomePVPPrompt=0x0000
OP_MoveLogRequest=0x0000 # gone I think
OP_MoveLogDisregard=0x0000 # gone I think
# named unknowns, to make looking for real unknown easier
OP_AnnoyingZoneUnknown=0x0000
OP_Some6ByteHPUpdate=0x0000 #seems to happen when you target group members
OP_QueryResponseThing=0x0000
# realityincarnate: these are just here to stop annoying several thousand byte packet dumps
#OP_LoginUnknown1=0x0000 # OP_SendSpellChecksum
#OP_LoginUnknown2=0x0000 # OP_SendSkillCapsChecksum
# Petition Opcodes
OP_PetitionSearch=0x0000 #search term for petition
OP_PetitionSearchResults=0x0000 #(list of?) matches from search
OP_PetitionSearchText=0x0000 #text results of search
OP_PetitionUpdate=0x0000
OP_PetitionCheckout=0x0000
OP_PetitionCheckIn=0x0000
OP_PetitionQue=0x0000
OP_PetitionUnCheckout=0x0000
OP_PetitionDelete=0x0000
OP_DeletePetition=0x0000
OP_PetitionResolve=0x0000
OP_PDeletePetition=0x0000
OP_PetitionBug=0x0000
OP_PetitionRefresh=0x0000
OP_PetitionCheckout2=0x0000
OP_PetitionViewPetition=0x0000
#aura related
OP_UpdateAura=0x0000
OP_RemoveTrap=0x0000
OP_SystemFingerprint=0x52ef
+4 -2
View File
@@ -1223,8 +1223,10 @@ bool Client::Process() {
}
if(connect.Check()){
SendGuildList();// Send OPCode: OP_GuildsList
SendApproveWorld();
if (!(m_ClientVersionBit & EQ::versions::maskTOBAndLater)) {
SendGuildList();// Send OPCode: OP_GuildsList
SendApproveWorld();
}
connect.Disable();
}
+4
View File
@@ -28,6 +28,7 @@ set(zone_sources
client_mods.cpp
client_packet.cpp
client_process.cpp
client_version.cpp
combat_record.cpp
corpse.cpp
dialogue_window.cpp
@@ -130,6 +131,7 @@ set(zone_headers
cheat_manager.h
client.h
client_packet.h
client_version.h
combat_record.h
command.h
common.h
@@ -673,6 +675,8 @@ set_property(TARGET gm_commands_zone PROPERTY FOLDER libraries)
add_executable(zone ${zone_sources} ${zone_headers})
target_include_directories(zone PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
install(TARGETS zone RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
if(EQEMU_BUILD_PCH)
+2 -2
View File
@@ -5922,13 +5922,13 @@ bool Bot::CastSpell(
if (DivineAura()) {
LogSpellsDetail("Spell casting canceled: cannot cast while Divine Aura is in effect");
InterruptSpell(SPELL_FIZZLE, 0x121, false);
InterruptSpell(SPELL_FIZZLE, Chat::SpellFailure, false);
return false;
}
if (slot < EQ::spells::CastingSlot::MaxGems && !CheckFizzle(spell_id)) {
int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE;
InterruptSpell(fizzle_msg, 0x121, spell_id);
InterruptSpell(fizzle_msg, Chat::SpellFailure, spell_id);
uint32 use_mana = ((spells[spell_id].mana) / 4);
LogSpellsDetail("Spell casting canceled: fizzled. [{}] mana has been consumed", use_mana);
+3 -3
View File
@@ -775,7 +775,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
bot_owner->Message(Chat::Green, "%s", description.c_str());
}
void SendSpellTypeWindow(Client* c, const Seperator* sep, bool short_names) {
void SendSpellTypeWindow(Client* c, const Seperator* sep) {
std::string arg0 = sep->arg[0];
std::string arg1 = sep->arg[1];
@@ -828,7 +828,7 @@ void SendSpellTypeWindow(Client* c, const Seperator* sep, bool short_names) {
std::string popup_text = DialogueWindow::TableRow(
DialogueWindow::TableCell(DialogueWindow::ColorMessage(goldenrod, spell_type_field))
+
DialogueWindow::TableCell((!short_names ? DialogueWindow::ColorMessage(goldenrod, id_field) : DialogueWindow::ColorMessage(goldenrod, shortname_field)))
DialogueWindow::TableCell((!arg0.compare("^spelltypeids") ? DialogueWindow::ColorMessage(goldenrod, id_field) : DialogueWindow::ColorMessage(goldenrod, shortname_field)))
);
popup_text += DialogueWindow::TableRow(
@@ -845,7 +845,7 @@ void SendSpellTypeWindow(Client* c, const Seperator* sep, bool short_names) {
popup_text += DialogueWindow::TableRow(
DialogueWindow::TableCell(DialogueWindow::ColorMessage(forest_green, Bot::GetSpellTypeNameByID(i)))
+
DialogueWindow::TableCell((!short_names ? DialogueWindow::ColorMessage(slate_blue, std::to_string(i)) : DialogueWindow::ColorMessage(slate_blue, Bot::GetSpellTypeShortNameByID(i))))
DialogueWindow::TableCell((!arg0.compare("^spelltypeids") ? DialogueWindow::ColorMessage(slate_blue, std::to_string(i)) : DialogueWindow::ColorMessage(slate_blue, Bot::GetSpellTypeShortNameByID(i))))
);
}
+1 -1
View File
@@ -1182,4 +1182,4 @@ bool helper_is_help_or_usage(const char* arg);
bool helper_no_available_bots(Client *bot_owner, Bot *my_bot = nullptr);
void helper_send_available_subcommands(Client *bot_owner, const char* command_simile, std::vector<const char*> subcommand_list);
void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type);
void SendSpellTypeWindow(Client* c, const Seperator* sep, bool short_names = false);
void SendSpellTypeWindow(Client* c, const Seperator* sep);
+1 -1
View File
@@ -24,5 +24,5 @@ void bot_command_spelltype_ids(Client* c, const Seperator* sep)
void bot_command_spelltype_names(Client* c, const Seperator* sep)
{
SendSpellTypeWindow(c, sep, true);
SendSpellTypeWindow(c, sep);
}
+91 -112
View File
@@ -46,6 +46,7 @@
#include "common/zone_store.h"
#include "zone/bot_command.h"
#include "zone/cheat_manager.h"
#include "zone/client_version.h"
#include "zone/command.h"
#include "zone/dialogue_window.h"
#include "zone/dynamic_zone.h"
@@ -1205,7 +1206,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message);
if (RuleB(Chat, AlwaysCaptureCommandText)) {
if (message[0] == COMMAND_CHAR) {
if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
@@ -1538,7 +1539,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
}
}
if (message[0] == COMMAND_CHAR) {
if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
@@ -1792,7 +1793,7 @@ void Client::Message(uint32 type, const char* message, ...) {
}
void Client::FilteredMessage(Mob *sender, uint32 type, eqFilterType filter, const char* message, ...) {
if (!FilteredMessageCheck(sender, filter))
if (!ShouldGetPacket(sender, filter))
return;
va_list argptr;
@@ -3808,18 +3809,12 @@ void Client::MessageString(uint32 type, uint32 string_id, uint32 distance)
return;
if (GetFilter(FilterSpellCrits) == FilterHide && type == Chat::SpellCrit)
return;
auto outapp = new EQApplicationPacket(OP_SimpleMessage, 12);
SimpleMessage_Struct* sms = (SimpleMessage_Struct*)outapp->pBuffer;
sms->color=type;
sms->string_id=string_id;
sms->unknown8=0;
if(distance>0)
entity_list.QueueCloseClients(this,outapp,false,distance);
if (distance > 0)
Message::CloseMessageString(this, false, static_cast<float>(distance))(
type, string_id);
else
QueuePacket(outapp);
safe_delete(outapp);
Message::MessageString(this, type, string_id);
}
//
@@ -3829,9 +3824,9 @@ void Client::MessageString(uint32 type, uint32 string_id, uint32 distance)
// This hack sucks but it's gonna work for now.
//
void Client::MessageString(uint32 type, uint32 string_id, const char* message1,
const char* message2,const char* message3,const char* message4,
const char* message5,const char* message6,const char* message7,
const char* message8,const char* message9, uint32 distance)
const char* message2, const char* message3, const char* message4,
const char* message5, const char* message6, const char* message7,
const char* message8, const char* message9, uint32 distance)
{
if (GetFilter(FilterSpellDamage) == FilterHide && type == Chat::NonMelee)
return;
@@ -3847,34 +3842,12 @@ void Client::MessageString(uint32 type, uint32 string_id, const char* message1,
if (type == Chat::Emote)
type = 4;
if (!message1) {
MessageString(type, string_id); // use the simple message instead
return;
}
const char *message_arg[] = {
message1, message2, message3, message4, message5,
message6, message7, message8, message9
};
SerializeBuffer buf(20);
buf.WriteInt32(0); // unknown
buf.WriteInt32(string_id);
buf.WriteInt32(type);
for (auto &m : message_arg) {
if (m == nullptr)
break;
buf.WriteString(m);
}
buf.WriteInt8(0); // prevent oob in packet translation, maybe clean that up sometime
auto outapp = std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buf));
if (distance > 0)
entity_list.QueueCloseClients(this, outapp.get(), false, distance);
Message::CloseMessageString(this, false, static_cast<float>(distance))(type, string_id, message1,
message2, message3, message4, message5, message6, message7, message8, message9);
else
QueuePacket(outapp.get());
Message::MessageString(this, type, string_id, message1, message2, message3, message4, message5,
message6, message7, message8, message9);
}
void Client::MessageString(const CZClientMessageString_Struct* msg)
@@ -3898,60 +3871,49 @@ void Client::MessageString(const CZClientMessageString_Struct* msg)
}
}
// helper function, returns true if we should see the message
bool Client::FilteredMessageCheck(Mob *sender, eqFilterType filter)
// helper function, returns true if the client should get the packet based on the filter and sender
bool Client::ShouldGetPacket(Mob *sender, eqFilterType filter)
{
eqFilterMode mode = GetFilter(filter);
// easy ones first
if (mode == FilterShow) {
return true;
} else if (mode == FilterHide) {
return false;
}
if (sender != this && mode == FilterShowSelfOnly) {
// easy ones first
if (mode == FilterShow)
return true;
if (mode == FilterHide)
return false;
} else if (sender) {
if (mode == FilterShowGroupOnly) {
auto g = GetGroup();
auto r = GetRaid();
if (g) {
if (g->IsGroupMember(sender)) {
return true;
}
} else if (r && sender->IsClient()) {
auto rgid1 = r->GetGroup(this);
auto rgid2 = r->GetGroup(sender->CastToClient());
if (rgid1 != RAID_GROUPLESS && rgid1 == rgid2) {
return true;
}
} else {
return false;
}
if (sender != this && mode == FilterShowSelfOnly)
return false;
if (sender != nullptr && mode == FilterShowGroupOnly) {
if (sender == this)
return true;
Group* g = GetGroup();
if (g && g->IsGroupMember(sender))
return true;
Raid* r = GetRaid();
if (r && sender->IsClient()) {
uint32 rgid1 = r->GetGroup(this);
uint32 rgid2 = r->GetGroup(sender->CastToClient());
if (rgid1 != RAID_GROUPLESS && rgid1 == rgid2)
return true;
} else {
return false;
}
}
// we passed our checks
// fallback case (send by default)
return true;
}
void Client::FilteredMessageString(Mob *sender, uint32 type,
eqFilterType filter, uint32 string_id)
{
if (!FilteredMessageCheck(sender, filter))
return;
auto outapp = new EQApplicationPacket(OP_SimpleMessage, 12);
SimpleMessage_Struct *sms = (SimpleMessage_Struct *)outapp->pBuffer;
sms->color = type;
sms->string_id = string_id;
sms->unknown8 = 0;
QueuePacket(outapp);
safe_delete(outapp);
return;
if (ShouldGetPacket(sender, filter))
MessageString(type, string_id);
}
void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id,
@@ -3959,37 +3921,16 @@ void Client::FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter
const char *message4, const char *message5, const char *message6,
const char *message7, const char *message8, const char *message9)
{
if (!FilteredMessageCheck(sender, filter))
return;
if (type == Chat::Emote)
type = 4;
if (!message1) {
FilteredMessageString(sender, type, filter, string_id); // use the simple message instead
return;
} else if (ShouldGetPacket(sender, filter)) {
if (type == Chat::Emote)
type = 4;
MessageString(
type, string_id, message1, message2, message3, message4,
message5, message6, message7, message8, message9);
}
const char *message_arg[] = {
message1, message2, message3, message4, message5,
message6, message7, message8, message9
};
SerializeBuffer buf(20);
buf.WriteInt32(0); // unknown
buf.WriteInt32(string_id);
buf.WriteInt32(type);
for (auto &m : message_arg) {
if (m == nullptr)
break;
buf.WriteString(m);
}
buf.WriteInt8(0); // prevent oob in packet translation, maybe clean that up sometime
auto outapp = std::make_unique<EQApplicationPacket>(OP_FormattedMessage, std::move(buf));
QueuePacket(outapp.get());
}
void Client::Tell_StringID(uint32 string_id, const char *who, const char *message)
@@ -8133,7 +8074,7 @@ void Client::GarbleMessage(char *message, uint8 variance)
int delimiter_count = 0;
// Don't garble # commands
if (message[0] == COMMAND_CHAR || message[0] == BOT_COMMAND_CHAR) {
if (message[0] == COMMAND_CHAR || message[0] == COMMAND_CHAR_NON_HASH || message[0] == BOT_COMMAND_CHAR) {
return;
}
@@ -9208,6 +9149,44 @@ void Client::SendHPUpdateMarquee(){
SendMarqueeMessage(Chat::Yellow, 510, 0, 3000, 3000, health_update_notification);
}
void Client::SendMembership() {
if (m_ClientVersion >= EQ::versions::ClientVersion::TOB) {
auto outapp = new EQApplicationPacket(OP_SendMembership, sizeof(Membership_Struct));
Membership_Struct* mc = (Membership_Struct*)outapp->pBuffer;
mc->membership = 2; //Hardcode to gold for now. We don't use anything else.
mc->races = 0x1ffff; // Available Races (4110 for silver)
mc->classes = 0x1ffff; // Available Classes (4614 for silver) - Was 0x101ffff
mc->entrysize = 21; // Number of membership setting entries below
mc->entries[0] = 0xffffffff; // Max AA Restriction
mc->entries[1] = 0xffffffff; // Max Level Restriction
mc->entries[2] = 0xffffffff; // Max Char Slots per Account (not used by client?)
mc->entries[3] = 0xffffffff; // 1 for Silver
mc->entries[4] = 0xffffffff; // Main Inventory Size (0xffffffff on Live for Gold, but limiting to 8 until 10 is supported)
mc->entries[5] = 0xffffffff; // Max Platinum per level
mc->entries[6] = 1; // 0 for Silver
mc->entries[7] = 1; // 0 for Silver
mc->entries[8] = 1; // 1 for Silver
mc->entries[9] = 0xffffffff; // Unknown - Maybe Loyalty Points every 12 hours? 60 per week for Silver
mc->entries[10] = 1; // 1 for Silver
mc->entries[11] = 0xffffffff; // Shared Bank Slots
mc->entries[12] = 0xffffffff; // Unknown - Maybe Max Active Tasks?
mc->entries[13] = 1; // 1 for Silver
mc->entries[14] = 1; // 0 for Silver
mc->entries[15] = 1; // 0 for Silver
mc->entries[16] = 1; // 1 for Silver
mc->entries[17] = 1; // 0 for Silver
mc->entries[18] = 1; // 0 for Silver
mc->entries[19] = 0xffffffff; // 0 for Silver
mc->entries[20] = 0xffffffff; // 0 for Silver
mc->exit_url_length = 0;
//mc->exit_url = 0; // Used on Live: "http://www.everquest.com/free-to-play/exit-silver"
QueuePacket(outapp);
safe_delete(outapp);
}
}
uint32 Client::GetMoney(uint8 type, uint8 subtype) {
uint32 value = 0;
+6 -4
View File
@@ -343,10 +343,10 @@ public:
void DyeArmor(EQ::TintProfile* dye);
void DyeArmorBySlot(uint8 slot, uint8 red, uint8 green, uint8 blue, uint8 use_tint = 0x00);
uint8 SlotConvert(uint8 slot,bool bracer=false);
void MessageString(uint32 type, uint32 string_id, uint32 distance = 0);
void MessageString(uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0);
void MessageString(uint32 type, uint32 string_id, uint32 distance = 0) override;
void MessageString(uint32 type, uint32 string_id, const char* message,const char* message2=0,const char* message3=0,const char* message4=0,const char* message5=0,const char* message6=0,const char* message7=0,const char* message8=0,const char* message9=0, uint32 distance = 0) override;
void MessageString(const CZClientMessageString_Struct* msg);
bool FilteredMessageCheck(Mob *sender, eqFilterType filter);
bool ShouldGetPacket(Mob *sender, eqFilterType filter);
void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id);
void FilteredMessageString(Mob *sender, uint32 type, eqFilterType filter,
uint32 string_id, const char *message1, const char *message2 = nullptr,
@@ -1550,7 +1550,8 @@ public:
inline const EQ::versions::ClientVersion ClientVersion() const { return m_ClientVersion; }
inline const uint32 ClientVersionBit() const { return m_ClientVersionBit; }
inline void SetClientVersion(EQ::versions::ClientVersion client_version) { m_ClientVersion = client_version; }
void SetClientVersion(EQ::versions::ClientVersion client_version);
EQ::versions::ClientVersion GetClientVersion() const;
/** Adventure Stuff **/
void SendAdventureError(const char *error);
@@ -1856,6 +1857,7 @@ public:
void ResetHPUpdateTimer() { hpupdate_timer.Start(); }
void SendHPUpdateMarquee();
void SendMembership();
void CheckRegionTypeChanges();
+58 -93
View File
@@ -105,7 +105,6 @@ void MapOpcodes()
ConnectingOpcodes[OP_ZoneEntry] = &Client::Handle_Connect_OP_ZoneEntry;
// connected opcode handler assignments:
ConnectedOpcodes[OP_0x0193] = &Client::Handle_0x0193;
ConnectedOpcodes[OP_AAAction] = &Client::Handle_OP_AAAction;
ConnectedOpcodes[OP_AcceptNewTask] = &Client::Handle_OP_AcceptNewTask;
ConnectedOpcodes[OP_AdventureInfoRequest] = &Client::Handle_OP_AdventureInfoRequest;
@@ -1240,9 +1239,6 @@ void Client::Handle_Connect_OP_WorldObjectsSent(const EQApplicationPacket *app)
void Client::Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app)
{
auto outapp = new EQApplicationPacket(OP_0x0347, 0);
QueuePacket(outapp);
safe_delete(outapp);
return;
}
@@ -1705,10 +1701,8 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
if ((m_pp.RestTimer > RuleI(Character, RestRegenTimeToActivate)) && (m_pp.RestTimer > RuleI(Character, RestRegenRaidTimeToActivate)))
m_pp.RestTimer = 0;
/* This checksum should disappear once dynamic structs are in... each struct strategy will do it */ // looks to be in place now
//CRC32::SetEQChecksum((unsigned char*)&m_pp, sizeof(PlayerProfile_Struct) - sizeof(m_pp.m_player_profile_version) - 4);
// m_pp.checksum = 0; // All server out-bound player profile packets are now translated - no need to waste cycles calculating this...
SendMembership();
outapp = new EQApplicationPacket(OP_PlayerProfile, sizeof(PlayerProfile_Struct));
/* The entityid field in the Player Profile is used by the Client in relation to Group Leadership AA */
@@ -1882,16 +1876,6 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
return;
}
// connected opcode handlers
void Client::Handle_0x0193(const EQApplicationPacket *app)
{
// Not sure what this opcode does. It started being sent when OP_ClientUpdate was
// changed to pump OP_ClientUpdate back out instead of OP_MobUpdate
// 2 bytes: 00 00
return;
}
void Client::Handle_OP_AAAction(const EQApplicationPacket *app)
{
LogAA("Received OP_AAAction");
@@ -4277,21 +4261,30 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
if (IsLFP())
worldserver.StopLFP(CharacterID());
if (GetGM())
{
if (RuleB(Character, EnableHackedFastCampForGM))
{
camp_timer.Start(100, true);
}
else {
OnDisconnect(true);
if (ClientVersion() >= EQ::versions::ClientVersion::TOB) {
if (!GetGM()) {
camp_timer.Start(29000, true);
}
return;
auto outapp = new EQApplicationPacket(OP_Camp, 1);
FastQueuePacket(&outapp);
}
else {
if (GetGM())
{
if (RuleB(Character, EnableHackedFastCampForGM))
{
camp_timer.Start(100, true);
}
else {
OnDisconnect(true);
}
return;
}
camp_timer.Start(29000, true);
camp_timer.Start(29000, true);
}
if (RuleB(Bots, Enabled)) {
bot_camp_timer.Start((RuleI(Bots, CampTimer) * 1000), true);
@@ -8919,31 +8912,20 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app)
tmHidden = Timer::GetCurrentTime();
}
if (GetClass() == Class::Rogue) {
auto outapp = new EQApplicationPacket(OP_SimpleMessage, sizeof(SimpleMessage_Struct));
SimpleMessage_Struct *msg = (SimpleMessage_Struct *)outapp->pBuffer;
msg->color = 0x010E;
Mob *evadetar = GetTarget();
if (!auto_attack && (evadetar && evadetar->CheckAggro(this)
&& evadetar->IsNPC())) {
uint32 string_id = HIDE_FAIL;
Mob* evadetar = GetTarget();
if (!auto_attack && evadetar && evadetar->CheckAggro(this) && evadetar->IsNPC()) {
if (zone->random.Int(0, 260) < (int)GetSkill(EQ::skills::SkillHide)) {
msg->string_id = EVADE_SUCCESS;
string_id = EVADE_SUCCESS;
RogueEvade(evadetar);
}
else {
msg->string_id = EVADE_FAIL;
}
}
else {
if (hidden) {
msg->string_id = HIDE_SUCCESS;
}
else {
msg->string_id = HIDE_FAIL;
}
}
FastQueuePacket(&outapp);
} else
string_id = EVADE_FAIL;
} else if (hidden)
string_id = HIDE_SUCCESS;
MessageString(Chat::Skills, string_id);
}
return;
}
void Client::Handle_OP_HideCorpse(const EQApplicationPacket *app)
@@ -10332,10 +10314,10 @@ void Client::Handle_OP_ManaChange(const EQApplicationPacket *app)
if (app->size == 0) {
// i think thats the sign to stop the songs
if (IsBardSong(casting_spell_id) || HasActiveSong()) {
InterruptSpell(SONG_ENDS, 0x121); //Live doesn't send song end message anymore (~Kayen 1/26/22)
InterruptSpell(SONG_ENDS, Chat::SpellFailure); //Live doesn't send song end message anymore (~Kayen 1/26/22)
}
else {
InterruptSpell(INTERRUPT_SPELL, 0x121);
InterruptSpell(INTERRUPT_SPELL, Chat::SpellFailure);
}
return;
}
@@ -12346,6 +12328,9 @@ void Client::Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app)
case EQ::versions::ClientVersion::RoF2:
ConnectionType = EQ::versions::ucsRoF2Combined;
break;
case EQ::versions::ClientVersion::TOB:
ConnectionType = EQ::versions::ucsTOBCombined;
break;
default:
ConnectionType = EQ::versions::ucsUnknown;
break;
@@ -14378,8 +14363,9 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
sizeof(Merchant_Purchase_Struct), app->size);
return;
}
RDTSC_Timer t1(true);
Merchant_Purchase_Struct* mp = (Merchant_Purchase_Struct*)app->pBuffer;
Mob* vendor = entity_list.GetMob(mp->npcid);
if (vendor == 0 || !vendor->IsNPC() || vendor->GetClass() != Class::Merchant)
@@ -14389,51 +14375,35 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
if (DistanceSquared(m_Position, vendor->GetPosition()) > USE_NPC_RANGE2)
return;
uint32 price = 0;
uint32 itemid = GetItemIDAt(mp->itemslot);
if (itemid == 0)
return;
const EQ::ItemData* item = database.GetItem(itemid);
EQ::ItemInstance* inst = GetInv().GetItem(mp->itemslot);
if (!item || !inst) {
Message(Chat::Red, "You seem to have misplaced that item..");
Message(Chat::Red, "You seemed to have misplaced that item..");
return;
}
if (!item->NoDrop) {
return;
}
if (mp->quantity > 1) {
if (mp->quantity > 1)
{
if ((inst->GetCharges() < 0) || (mp->quantity > (uint32)inst->GetCharges()))
return;
}
// Check for veto from script
if (parse->PlayerHasQuestSub(EVENT_MERCHANT_PRESELL)) {
std::string export_string = fmt::format("{} {} {}", mp->itemslot, itemid, inst->GetItemType());
std::vector<std::any> extra_pointers = { vendor, inst };
int result = parse->EventPlayer(EVENT_MERCHANT_PRESELL, this, export_string, 0, &extra_pointers);
// CANCEL: If a script returns -1 for this event, the sale wil be cancelled. Sends a dummy packet sent to satisfy the client
if (result == -1) {
auto outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct));
Merchant_Purchase_Struct* mco = (Merchant_Purchase_Struct*)outapp->pBuffer;
mco->npcid = vendor->GetID();
mco->itemslot = -1; // Critical or the client will remove the item visually
mco->quantity = 0;
mco->price = 0;
QueuePacket(outapp);
safe_delete(outapp);
return;
}
if (!item->NoDrop) {
//Message(Chat::Red,"%s tells you, 'LOL NOPE'", vendor->GetName());
return;
}
uint32 cost_quantity = inst->IsCharged() ? 1 : mp->quantity;
uint32 price = 0;
uint32 cost_quantity = mp->quantity;
if (inst->IsCharged())
uint32 cost_quantity = 1;
uint32 i;
if (RuleB(Merchant, UsePriceMod)) {
for (uint32 i = 1; i <= cost_quantity; i++) {
for (i = 1; i <= cost_quantity; i++) {
price = (uint32)(item->Price * i) * Client::CalcPriceMod(vendor, true);
// Don't use SellCostMod if using UseClassicPriceMod
@@ -14451,7 +14421,7 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
}
}
else {
for (uint32 i = 1; i <= cost_quantity; i++) {
for (i = 1; i <= cost_quantity; i++) {
price = (uint32)((item->Price * i)*(RuleR(Merchant, BuyCostMod)) + 0.5); // need to round up, because client does it automatically when displaying price
if (price > 4000000000) {
cost_quantity = i;
@@ -14463,7 +14433,6 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
AddMoneyToPP(price);
// Update merchant stock and refresh client
if (inst->IsStackable() || inst->IsCharged())
{
unsigned int i_quan = inst->GetCharges();
@@ -14577,8 +14546,6 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
QueuePacket(outapp);
safe_delete(outapp);
SendMoneyUpdate();
RDTSC_Timer t1(true);
t1.start();
Save(1);
t1.stop();
@@ -14686,7 +14653,10 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
mco->rate = 1 / buy_cost_mod;
}
outapp->priority = 6;
if (m_ClientVersion >= EQ::versions::ClientVersion::TOB) {
mco->player_id = GetID();
}
QueuePacket(outapp);
safe_delete(outapp);
@@ -14697,11 +14667,6 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
if ((tabs_to_display & Parcel) == Parcel) {
SendBulkParcels();
}
if (parse->PlayerHasQuestSub(EVENT_MERCHANT_OPEN)) {
std::vector<std::any> extra_pointers = { tmp };
parse->EventPlayer(EVENT_MERCHANT_OPEN, this, "", 0, &extra_pointers);
}
}
return;
-2
View File
@@ -38,8 +38,6 @@
void Handle_Connect_OP_ZoneComplete(const EQApplicationPacket *app);
void Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app);
/* Connected opcode handlers*/
void Handle_0x0193(const EQApplicationPacket *app);
void Handle_0x01e7(const EQApplicationPacket *app);
void Handle_OP_AAAction(const EQApplicationPacket *app);
void Handle_OP_AcceptNewTask(const EQApplicationPacket *app);
void Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app);
+2 -2
View File
@@ -229,11 +229,11 @@ bool Client::Process() {
}
if (song_target == nullptr) {
InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong);
InterruptSpell(SONG_ENDS_ABRUPTLY, Chat::SpellFailure, bardsong);
}
else {
if (!ApplyBardPulse(bardsong, song_target, bardsong_slot)) {
InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong);
InterruptSpell(SONG_ENDS_ABRUPTLY, Chat::SpellFailure, bardsong);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
/* 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"
using Version = EQ::versions::ClientVersion;
void Client::SetClientVersion(Version client_version)
{
m_ClientVersion = client_version;
m_ClientVersionBit = EQ::versions::ConvertClientVersionToClientVersionBit(client_version);
}
Version Client::GetClientVersion() const { return m_ClientVersion; }
+150
View File
@@ -0,0 +1,150 @@
//
// Created by dannu on 4/21/2026.
//
#pragma once
#include "common/emu_versions.h"
#include "common/patches/client_version.h"
#include "common/patches/IMessage.h"
#include "zone/client.h"
#include "zone/mob.h"
// store all _generic_ static functions for the different patches here
namespace ClientPatch {
using ClientList = std::unordered_map<uint16, Client*>;
template<typename Obj> using ComponentGetter = std::function<Obj*(const Client*)>;
template <typename Fun, typename Obj, typename... Args>
requires std::is_member_function_pointer_v<Fun>
static void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args)
{
if (obj != nullptr) {
std::unique_ptr<EQApplicationPacket> app = std::invoke(fun, obj, std::forward<Args>(args)...);
if (app)
c->QueuePacket(app.get());
}
}
// packet generator queue functions
static auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true)
{
return [=]<typename Fun, typename Obj, typename... Args>(Fun fun, const ComponentGetter<Obj>& component, Args&&... args)
requires std::is_member_function_pointer_v<Fun>
{
std::array<std::unique_ptr<EQApplicationPacket>, EQ::versions::ClientVersionCount> build_packets;
std::unordered_map<uint16, Client*> client_list = entity_list.GetClientList();
for (auto [_, ent] : client_list) {
if (!ignore_sender || ent != sender) {
auto& packet = build_packets.at(static_cast<uint32_t>(ent->ClientVersion()));
if (!packet)
if (auto comp = component(ent); comp != nullptr)
packet = std::invoke(fun, comp, std::forward<Args>(args)...);
if (packet)
ent->QueuePacket(packet.get(), ackreq, Client::CLIENT_CONNECTED);
}
}
};
}
static auto QueueCloseClients(
Mob* sender, bool ignore_sender = false, float distance = 200,
Mob* skipped_mob = nullptr, bool is_ack_required = true,
eqFilterType filter = FilterNone)
{
if (distance <= 0) distance = static_cast<float>(zone->GetClientUpdateRange());
return [=]<typename Fun, typename Obj, typename... Args>(Fun fun, const ComponentGetter<Obj>& component, Args&&... args)
requires std::is_member_function_pointer_v<Fun>
{
if (sender == nullptr) {
QueueClients(sender, ignore_sender, is_ack_required)(fun, component, std::forward<Args>(args)...);
} else {
float distance_squared = distance * distance;
std::array<std::unique_ptr<EQApplicationPacket>, EQ::versions::ClientVersionCount> build_packets;
for (auto& [_, mob] : sender->GetCloseMobList(distance)) {
if (mob && mob->IsClient()) {
Client* client = mob->CastToClient();
if ((!ignore_sender || client != sender)
&& client != skipped_mob
&& DistanceSquared(client->GetPosition(), sender->GetPosition()) < distance_squared
&& client->Connected()
&& client->ShouldGetPacket(sender, filter))
{
auto& packet = build_packets.at(static_cast<uint32_t>(client->ClientVersion()));
if (!packet)
if (auto comp = component(client); comp != nullptr)
packet = std::invoke(fun, comp, std::forward<Args>(args)...);
if (packet)
client->QueuePacket(packet.get(), is_ack_required, Client::CLIENT_CONNECTED);
}
}
}
}
};
}
} // namespace ClientPatch
// Helpers for the Message interface to send message packets
namespace Message {
// this can return nullptr when the component doesn't exist for the version
static std::function GetComponent = [](const Client* c) -> IMessage* {
return GetMessageComponent(c->GetClientVersion()).get();
};
// Helper functions to wrap the packet construction in sends
template <AllConstChar... Args>
requires (sizeof...(Args) <= 9)
void MessageString(Client* c, uint32_t type, uint32_t id, Args&&... args)
{
if constexpr (sizeof...(Args) == 0) {
ClientPatch::QueuePacket(c, &IMessage::Simple, GetComponent(c), type, id);
} else {
std::array<const char*, 9> a = {args...};
ClientPatch::QueuePacket(c, &IMessage::Formatted, GetComponent(c), type, id, a);
}
}
static auto CloseMessageString(
Mob* sender, bool ignore_sender = false, float distance = 200.f,
Mob* skipped_mob = nullptr, bool is_ack_required = true,
eqFilterType filter = FilterNone)
{
return [=]<AllConstChar... Args>(uint32_t type, uint32_t id, Args&&... args)
requires (sizeof...(Args) <= 9)
{
auto queue_close_clients = ClientPatch::QueueCloseClients(sender, ignore_sender, distance, skipped_mob,
is_ack_required, filter);
if constexpr (sizeof...(Args) == 0) {
return queue_close_clients(&IMessage::Simple, GetComponent, type, id);
} else {
std::array<const char*, 9> a = {args...};
return queue_close_clients(&IMessage::Formatted, GetComponent, type, id, a);
}
};
}
inline void InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link)
{
ClientPatch::QueuePacket(c, &IMessage::InterruptSpell, GetComponent(c), message, spawn_id, spell_link);
}
inline void InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name,
const char* spell_link)
{
ClientPatch::QueueCloseClients(sender, true, RuleI(Range, SongMessages), nullptr, true,
sender->IsClient() ? FilterPCSpells : FilterNPCSpells)(
&IMessage::InterruptSpellOther, GetComponent, sender, message, spawn_id, name, spell_link);
}
} // namespace Message
+1
View File
@@ -25,6 +25,7 @@ class Client;
class Seperator;
#define COMMAND_CHAR '#'
#define COMMAND_CHAR_NON_HASH '$'
typedef void (*CmdFuncPtr)(Client *, const Seperator *);
+22 -57
View File
@@ -17,13 +17,11 @@
*/
#ifdef EMBPERL
#include "zone/embparser.h"
#include "common/compiler_macros.h"
#include "common/features.h"
#include "common/misc_functions.h"
#include "common/seperator.h"
#include "common/strings.h"
#include "zone/embparser.h"
#include "zone/masterentity.h"
#include "zone/qglobals.h"
#include "zone/questmgr.h"
@@ -163,10 +161,8 @@ const char* QuestEventSubroutines[_LargestEventID] = {
"EVENT_LANGUAGE_SKILL_UP",
"EVENT_ALT_CURRENCY_MERCHANT_BUY",
"EVENT_ALT_CURRENCY_MERCHANT_SELL",
"EVENT_MERCHANT_OPEN",
"EVENT_MERCHANT_BUY",
"EVENT_MERCHANT_SELL",
"EVENT_MERCHANT_PRESELL",
"EVENT_INSPECT",
"EVENT_TASK_BEFORE_UPDATE",
"EVENT_AA_BUY",
@@ -402,8 +398,6 @@ int PerlembParser::EventCommon(
zone
);
}
return 0;
}
int PerlembParser::EventNPC(
@@ -1217,33 +1211,31 @@ QuestType PerlembParser::GetQuestTypes(
event_id == EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE
) {
return is_global ? QuestType::SpellGlobal : QuestType::Spell;
}
if (npc_mob) {
if (!inst) {
if (npc_mob->IsBot()) {
return is_global ? QuestType::BotGlobal : QuestType::Bot;
} else if (npc_mob->IsMerc()) {
return is_global ? QuestType::MercGlobal : QuestType::Merc;
} else if (npc_mob->IsNPC()) {
return is_global ? QuestType::NPCGlobal : QuestType::NPC;
} else {
if (npc_mob) {
if (!inst) {
if (npc_mob->IsBot()) {
return is_global ? QuestType::BotGlobal : QuestType::Bot;
} else if (npc_mob->IsMerc()) {
return is_global ? QuestType::MercGlobal : QuestType::Merc;
} else if (npc_mob->IsNPC()) {
return is_global ? QuestType::NPCGlobal : QuestType::NPC;
}
} else {
return is_global ? QuestType::ItemGlobal : QuestType::Item;
}
} else {
return is_global ? QuestType::ItemGlobal : QuestType::Item;
}
} else if (mob) {
if (!inst) {
if (mob->IsClient()) {
return is_global ? QuestType::PlayerGlobal : QuestType::Player;
} else if (!npc_mob && mob) {
if (!inst) {
if (mob->IsClient()) {
return is_global ? QuestType::PlayerGlobal : QuestType::Player;
}
} else {
return is_global ? QuestType::ItemGlobal : QuestType::Item;
}
} else {
return is_global ? QuestType::ItemGlobal : QuestType::Item;
} else if (zone) {
return is_global ? QuestType::ZoneGlobal : QuestType::Zone;
}
} else if (zone) {
return is_global ? QuestType::ZoneGlobal : QuestType::Zone;
}
UNREACHABLE();
}
std::string PerlembParser::GetQuestPackageName(
@@ -2291,33 +2283,6 @@ void PerlembParser::ExportEventVariables(
break;
}
case EVENT_MERCHANT_OPEN: {
if (!extra_pointers || extra_pointers->size() < 1) break;
auto mob_ptr = std::any_cast<Mob*>(extra_pointers->at(0));
if (!mob_ptr) break;
ExportVar(package_name.c_str(), "other", "Mob", mob_ptr);
break;
}
case EVENT_MERCHANT_PRESELL: {
Seperator sep(data);
ExportVar(package_name.c_str(), "slot_id", sep.arg[0]);
ExportVar(package_name.c_str(), "item_id", sep.arg[1]);
ExportVar(package_name.c_str(), "item_type", sep.arg[2]);
if (!extra_pointers || extra_pointers->size() < 2) break;
auto mob_ptr = std::any_cast<Mob*>(extra_pointers->at(0));
auto inst_ptr = std::any_cast<EQ::ItemInstance*>(extra_pointers->at(1));
if (!mob_ptr || !inst_ptr) break;
ExportVar(package_name.c_str(), "other", "Mob", mob_ptr);
ExportVar(package_name.c_str(), "item", "ItemInstance", inst_ptr);
break;
}
case EVENT_AA_BUY: {
Seperator sep(data);
ExportVar(package_name.c_str(), "aa_cost", sep.arg[0]);
-2
View File
@@ -116,10 +116,8 @@ enum QuestEventID {
EVENT_LANGUAGE_SKILL_UP,
EVENT_ALT_CURRENCY_MERCHANT_BUY,
EVENT_ALT_CURRENCY_MERCHANT_SELL,
EVENT_MERCHANT_OPEN,
EVENT_MERCHANT_BUY,
EVENT_MERCHANT_SELL,
EVENT_MERCHANT_PRESELL,
EVENT_INSPECT,
EVENT_TASK_BEFORE_UPDATE,
EVENT_AA_BUY,
-2
View File
@@ -6968,10 +6968,8 @@ luabind::scope lua_register_events() {
luabind::value("language_skill_up", static_cast<int>(EVENT_LANGUAGE_SKILL_UP)),
luabind::value("alt_currency_merchant_buy", static_cast<int>(EVENT_ALT_CURRENCY_MERCHANT_BUY)),
luabind::value("alt_currency_merchant_sell", static_cast<int>(EVENT_ALT_CURRENCY_MERCHANT_SELL)),
luabind::value("merchant_open", static_cast<int>(EVENT_MERCHANT_OPEN)),
luabind::value("merchant_buy", static_cast<int>(EVENT_MERCHANT_BUY)),
luabind::value("merchant_sell", static_cast<int>(EVENT_MERCHANT_SELL)),
luabind::value("merchant_presell", static_cast<int>(EVENT_MERCHANT_PRESELL)),
luabind::value("inspect", static_cast<int>(EVENT_INSPECT)),
luabind::value("task_before_update", static_cast<int>(EVENT_TASK_BEFORE_UPDATE)),
luabind::value("aa_buy", static_cast<int>(EVENT_AA_BUY)),
-6
View File
@@ -158,11 +158,6 @@ uint32 Lua_ItemInst::GetItemScriptID() {
return self->GetItemScriptID();
}
uint8 Lua_ItemInst::GetItemType() {
Lua_Safe_Call_Int();
return self->GetItemType();
}
int Lua_ItemInst::GetCharges() {
Lua_Safe_Call_Int();
return self->GetCharges();
@@ -502,7 +497,6 @@ luabind::scope lua_register_iteminst() {
.def("GetItemID", (uint32(Lua_ItemInst::*)(int))&Lua_ItemInst::GetItemID)
.def("GetItemLink", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemLink)
.def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID)
.def("GetItemType", (uint8(Lua_ItemInst::*)(void)) & Lua_ItemInst::GetItemType)
.def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl)
.def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName)
.def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber)

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