Handle remaining Turtle world opcodes with safe minimal parsers

This commit is contained in:
Kelsi 2026-02-18 23:26:58 -08:00
parent e2b3c3c265
commit a1c16762af
4 changed files with 109 additions and 0 deletions

View file

@ -28,8 +28,10 @@
"SMSG_CHARACTER_LOGIN_FAILED": "0x041",
"SMSG_PONG": "0x1DD",
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
"SMSG_INIT_WORLD_STATES": "0x2C2",
"SMSG_LOGIN_SETTIMESPEED": "0x042",
"SMSG_TUTORIAL_FLAGS": "0x0FD",
"SMSG_INITIALIZE_FACTIONS": "0x122",
"SMSG_WARDEN_DATA": "0x2E6",
"CMSG_WARDEN_DATA": "0x2E7",
"SMSG_ACCOUNT_DATA_TIMES": "0x209",
@ -46,6 +48,7 @@
"CMSG_QUERY_TIME": "0x1CE",
"SMSG_QUERY_TIME_RESPONSE": "0x1CF",
"SMSG_FRIEND_STATUS": "0x068",
"SMSG_CONTACT_LIST": "0x067",
"CMSG_ADD_FRIEND": "0x069",
"CMSG_DEL_FRIEND": "0x06A",
"CMSG_ADD_IGNORE": "0x06C",
@ -171,6 +174,7 @@
"CMSG_QUESTLOG_REMOVE_QUEST": "0x194",
"SMSG_QUESTUPDATE_ADD_KILL": "0x199",
"SMSG_QUESTUPDATE_COMPLETE": "0x198",
"SMSG_QUEST_FORCE_REMOVE": "0x21E",
"CMSG_QUEST_QUERY": "0x05C",
"SMSG_QUEST_QUERY_RESPONSE": "0x05D",
"SMSG_QUESTLOG_FULL": "0x195",
@ -204,6 +208,7 @@
"MSG_MOVE_WORLDPORT_ACK": "0x0DC",
"SMSG_TRANSFER_ABORTED": "0x040",
"SMSG_FORCE_RUN_SPEED_CHANGE": "0x0E2",
"SMSG_CLIENT_CONTROL_UPDATE": "0x159",
"CMSG_FORCE_RUN_SPEED_CHANGE_ACK": "0x0E3",
"SMSG_SHOWTAXINODES": "0x1A9",
"SMSG_ACTIVATETAXIREPLY": "0x1AE",

View file

@ -50,8 +50,10 @@ enum class LogicalOpcode : uint16_t {
SMSG_CHARACTER_LOGIN_FAILED,
SMSG_PONG,
SMSG_LOGIN_VERIFY_WORLD,
SMSG_INIT_WORLD_STATES,
SMSG_LOGIN_SETTIMESPEED,
SMSG_TUTORIAL_FLAGS,
SMSG_INITIALIZE_FACTIONS,
SMSG_WARDEN_DATA,
CMSG_WARDEN_DATA,
SMSG_ACCOUNT_DATA_TIMES,
@ -79,6 +81,7 @@ enum class LogicalOpcode : uint16_t {
// ---- Social Commands ----
SMSG_FRIEND_STATUS,
SMSG_CONTACT_LIST,
CMSG_ADD_FRIEND,
CMSG_DEL_FRIEND,
CMSG_SET_CONTACT_NOTES,
@ -259,6 +262,7 @@ enum class LogicalOpcode : uint16_t {
SMSG_QUESTUPDATE_ADD_KILL,
SMSG_QUESTUPDATE_ADD_ITEM,
SMSG_QUESTUPDATE_COMPLETE,
SMSG_QUEST_FORCE_REMOVE,
CMSG_QUEST_QUERY,
SMSG_QUEST_QUERY_RESPONSE,
SMSG_QUESTLOG_FULL,
@ -306,6 +310,7 @@ enum class LogicalOpcode : uint16_t {
// ---- Speed Changes ----
SMSG_FORCE_RUN_SPEED_CHANGE,
SMSG_CLIENT_CONTROL_UPDATE,
CMSG_FORCE_RUN_SPEED_CHANGE_ACK,
// ---- Mount ----

View file

@ -965,6 +965,22 @@ void GameHandler::handlePacket(network::Packet& packet) {
handleFriendStatus(packet);
}
break;
case Opcode::SMSG_CONTACT_LIST: {
// Known variants:
// - Full form: uint32 listMask, uint32 count, then variable-size entries.
// - Minimal/legacy keepalive-ish form observed on some servers: 1 byte.
size_t remaining = packet.getSize() - packet.getReadPos();
if (remaining >= 8) {
/*uint32_t listMask =*/ packet.readUInt32();
/*uint32_t count =*/ packet.readUInt32();
} else if (remaining == 1) {
/*uint8_t marker =*/ packet.readUInt8();
} else if (remaining > 0) {
// Unknown short variant: consume to keep stream aligned, no warning spam.
packet.setReadPos(packet.getSize());
}
break;
}
case Opcode::MSG_RANDOM_ROLL:
if (state == WorldState::IN_WORLD) {
@ -1019,6 +1035,28 @@ void GameHandler::handlePacket(network::Packet& packet) {
case Opcode::SMSG_FORCE_RUN_SPEED_CHANGE:
handleForceRunSpeedChange(packet);
break;
case Opcode::SMSG_CLIENT_CONTROL_UPDATE: {
// Minimal parse: PackedGuid + uint8 allowMovement.
if (packet.getSize() - packet.getReadPos() < 2) {
LOG_WARNING("SMSG_CLIENT_CONTROL_UPDATE too short: ", packet.getSize(), " bytes");
break;
}
uint8_t guidMask = packet.readUInt8();
size_t guidBytes = 0;
for (int i = 0; i < 8; ++i) {
if (guidMask & (1u << i)) ++guidBytes;
}
if (packet.getSize() - packet.getReadPos() < guidBytes + 1) {
LOG_WARNING("SMSG_CLIENT_CONTROL_UPDATE malformed (truncated packed guid)");
packet.setReadPos(packet.getSize());
break;
}
for (size_t i = 0; i < guidBytes; ++i) {
packet.readUInt8();
}
/*uint8_t allowMovement =*/ packet.readUInt8();
break;
}
// ---- Phase 2: Combat ----
case Opcode::SMSG_ATTACKSTART:
@ -1253,6 +1291,48 @@ void GameHandler::handlePacket(network::Packet& packet) {
}
// Silently ignore common packets we don't handle yet
case Opcode::SMSG_INIT_WORLD_STATES: {
// Minimal parse: uint32 mapId, uint32 zoneId, uint16 count, repeated (uint32 key, uint32 val)
if (packet.getSize() - packet.getReadPos() < 10) {
LOG_WARNING("SMSG_INIT_WORLD_STATES too short: ", packet.getSize(), " bytes");
break;
}
/*uint32_t mapId =*/ packet.readUInt32();
/*uint32_t zoneId =*/ packet.readUInt32();
uint16_t count = packet.readUInt16();
size_t needed = static_cast<size_t>(count) * 8;
if (packet.getSize() - packet.getReadPos() < needed) {
LOG_WARNING("SMSG_INIT_WORLD_STATES truncated: expected ", needed,
" bytes of state pairs, got ", packet.getSize() - packet.getReadPos());
packet.setReadPos(packet.getSize());
break;
}
for (uint16_t i = 0; i < count; ++i) {
packet.readUInt32();
packet.readUInt32();
}
break;
}
case Opcode::SMSG_INITIALIZE_FACTIONS: {
// Minimal parse: uint32 count, repeated (uint8 flags, int32 standing)
if (packet.getSize() - packet.getReadPos() < 4) {
LOG_WARNING("SMSG_INITIALIZE_FACTIONS too short: ", packet.getSize(), " bytes");
break;
}
uint32_t count = packet.readUInt32();
size_t needed = static_cast<size_t>(count) * 5;
if (packet.getSize() - packet.getReadPos() < needed) {
LOG_WARNING("SMSG_INITIALIZE_FACTIONS truncated: expected ", needed,
" bytes of faction data, got ", packet.getSize() - packet.getReadPos());
packet.setReadPos(packet.getSize());
break;
}
for (uint32_t i = 0; i < count; ++i) {
packet.readUInt8();
packet.readUInt32();
}
break;
}
case Opcode::SMSG_FEATURE_SYSTEM_STATUS:
case Opcode::SMSG_SET_FLAT_SPELL_MODIFIER:
case Opcode::SMSG_SET_PCT_SPELL_MODIFIER:
@ -1581,6 +1661,15 @@ void GameHandler::handlePacket(network::Packet& packet) {
}
break;
}
case Opcode::SMSG_QUEST_FORCE_REMOVE: {
// Minimal parse: uint32 questId
if (packet.getSize() - packet.getReadPos() < 4) {
LOG_WARNING("SMSG_QUEST_FORCE_REMOVE too short");
break;
}
/*uint32_t questId =*/ packet.readUInt32();
break;
}
case Opcode::SMSG_QUEST_QUERY_RESPONSE: {
// Quest data from server (big packet with title, objectives, rewards, etc.)
LOG_INFO("SMSG_QUEST_QUERY_RESPONSE: packet size=", packet.getSize());

View file

@ -51,8 +51,10 @@ static const OpcodeNameEntry kOpcodeNames[] = {
{"SMSG_CHARACTER_LOGIN_FAILED", LogicalOpcode::SMSG_CHARACTER_LOGIN_FAILED},
{"SMSG_PONG", LogicalOpcode::SMSG_PONG},
{"SMSG_LOGIN_VERIFY_WORLD", LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD},
{"SMSG_INIT_WORLD_STATES", LogicalOpcode::SMSG_INIT_WORLD_STATES},
{"SMSG_LOGIN_SETTIMESPEED", LogicalOpcode::SMSG_LOGIN_SETTIMESPEED},
{"SMSG_TUTORIAL_FLAGS", LogicalOpcode::SMSG_TUTORIAL_FLAGS},
{"SMSG_INITIALIZE_FACTIONS", LogicalOpcode::SMSG_INITIALIZE_FACTIONS},
{"SMSG_WARDEN_DATA", LogicalOpcode::SMSG_WARDEN_DATA},
{"CMSG_WARDEN_DATA", LogicalOpcode::CMSG_WARDEN_DATA},
{"SMSG_ACCOUNT_DATA_TIMES", LogicalOpcode::SMSG_ACCOUNT_DATA_TIMES},
@ -72,6 +74,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
{"CMSG_QUERY_TIME", LogicalOpcode::CMSG_QUERY_TIME},
{"SMSG_QUERY_TIME_RESPONSE", LogicalOpcode::SMSG_QUERY_TIME_RESPONSE},
{"SMSG_FRIEND_STATUS", LogicalOpcode::SMSG_FRIEND_STATUS},
{"SMSG_CONTACT_LIST", LogicalOpcode::SMSG_CONTACT_LIST},
{"CMSG_ADD_FRIEND", LogicalOpcode::CMSG_ADD_FRIEND},
{"CMSG_DEL_FRIEND", LogicalOpcode::CMSG_DEL_FRIEND},
{"CMSG_SET_CONTACT_NOTES", LogicalOpcode::CMSG_SET_CONTACT_NOTES},
@ -210,6 +213,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
{"SMSG_QUESTUPDATE_ADD_KILL", LogicalOpcode::SMSG_QUESTUPDATE_ADD_KILL},
{"SMSG_QUESTUPDATE_ADD_ITEM", LogicalOpcode::SMSG_QUESTUPDATE_ADD_ITEM},
{"SMSG_QUESTUPDATE_COMPLETE", LogicalOpcode::SMSG_QUESTUPDATE_COMPLETE},
{"SMSG_QUEST_FORCE_REMOVE", LogicalOpcode::SMSG_QUEST_FORCE_REMOVE},
{"CMSG_QUEST_QUERY", LogicalOpcode::CMSG_QUEST_QUERY},
{"SMSG_QUEST_QUERY_RESPONSE", LogicalOpcode::SMSG_QUEST_QUERY_RESPONSE},
{"SMSG_QUESTLOG_FULL", LogicalOpcode::SMSG_QUESTLOG_FULL},
@ -244,6 +248,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
{"MSG_MOVE_WORLDPORT_ACK", LogicalOpcode::MSG_MOVE_WORLDPORT_ACK},
{"SMSG_TRANSFER_ABORTED", LogicalOpcode::SMSG_TRANSFER_ABORTED},
{"SMSG_FORCE_RUN_SPEED_CHANGE", LogicalOpcode::SMSG_FORCE_RUN_SPEED_CHANGE},
{"SMSG_CLIENT_CONTROL_UPDATE", LogicalOpcode::SMSG_CLIENT_CONTROL_UPDATE},
{"CMSG_FORCE_RUN_SPEED_CHANGE_ACK", LogicalOpcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK},
{"CMSG_CANCEL_MOUNT_AURA", LogicalOpcode::CMSG_CANCEL_MOUNT_AURA},
{"SMSG_SHOWTAXINODES", LogicalOpcode::SMSG_SHOWTAXINODES},
@ -400,8 +405,10 @@ void OpcodeTable::loadWotlkDefaults() {
{LogicalOpcode::SMSG_CHARACTER_LOGIN_FAILED, 0x041},
{LogicalOpcode::SMSG_PONG, 0x1DD},
{LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD, 0x236},
{LogicalOpcode::SMSG_INIT_WORLD_STATES, 0x2C2},
{LogicalOpcode::SMSG_LOGIN_SETTIMESPEED, 0x042},
{LogicalOpcode::SMSG_TUTORIAL_FLAGS, 0x0FD},
{LogicalOpcode::SMSG_INITIALIZE_FACTIONS, 0x122},
{LogicalOpcode::SMSG_WARDEN_DATA, 0x2E6},
{LogicalOpcode::CMSG_WARDEN_DATA, 0x2E7},
{LogicalOpcode::SMSG_ACCOUNT_DATA_TIMES, 0x209},
@ -421,6 +428,7 @@ void OpcodeTable::loadWotlkDefaults() {
{LogicalOpcode::CMSG_QUERY_TIME, 0x1CE},
{LogicalOpcode::SMSG_QUERY_TIME_RESPONSE, 0x1CF},
{LogicalOpcode::SMSG_FRIEND_STATUS, 0x068},
{LogicalOpcode::SMSG_CONTACT_LIST, 0x067},
{LogicalOpcode::CMSG_ADD_FRIEND, 0x069},
{LogicalOpcode::CMSG_DEL_FRIEND, 0x06A},
{LogicalOpcode::CMSG_SET_CONTACT_NOTES, 0x06B},
@ -558,6 +566,7 @@ void OpcodeTable::loadWotlkDefaults() {
{LogicalOpcode::SMSG_QUESTUPDATE_ADD_KILL, 0x196},
{LogicalOpcode::SMSG_QUESTUPDATE_ADD_ITEM, 0x197},
{LogicalOpcode::SMSG_QUESTUPDATE_COMPLETE, 0x195},
{LogicalOpcode::SMSG_QUEST_FORCE_REMOVE, 0x21E},
{LogicalOpcode::CMSG_QUEST_QUERY, 0x05C},
{LogicalOpcode::SMSG_QUEST_QUERY_RESPONSE, 0x05D},
{LogicalOpcode::SMSG_QUESTLOG_FULL, 0x1A3},
@ -592,6 +601,7 @@ void OpcodeTable::loadWotlkDefaults() {
{LogicalOpcode::MSG_MOVE_WORLDPORT_ACK, 0x00DC},
{LogicalOpcode::SMSG_TRANSFER_ABORTED, 0x0040},
{LogicalOpcode::SMSG_FORCE_RUN_SPEED_CHANGE, 0x00E2},
{LogicalOpcode::SMSG_CLIENT_CONTROL_UPDATE, 0x0159},
{LogicalOpcode::CMSG_FORCE_RUN_SPEED_CHANGE_ACK, 0x00E3},
{LogicalOpcode::CMSG_CANCEL_MOUNT_AURA, 0x0375},
{LogicalOpcode::SMSG_SHOWTAXINODES, 0x01A9},