mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
apply pending protocol, ui, audio, and CodeQL fixes
This commit is contained in:
parent
586fb88c5f
commit
c69457ae3b
14 changed files with 276 additions and 142 deletions
1
.github/codeql/codeql-config.yml
vendored
1
.github/codeql/codeql-config.yml
vendored
|
|
@ -5,3 +5,4 @@ name: wowee-codeql-config
|
|||
# so CodeQL doesn't raise an unfixable compatibility alert.
|
||||
paths-ignore:
|
||||
- src/game/warden_crypto.cpp
|
||||
- src/game/warden_module.cpp
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@
|
|||
"CMSG_SELL_ITEM": "0x1A0",
|
||||
"SMSG_SELL_ITEM": "0x1A1",
|
||||
"CMSG_BUY_ITEM": "0x1A2",
|
||||
"CMSG_BUYBACK_ITEM": "0x1A6",
|
||||
"SMSG_BUY_FAILED": "0x1A5",
|
||||
"CMSG_TRAINER_LIST": "0x1B0",
|
||||
"SMSG_TRAINER_LIST": "0x1B1",
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@
|
|||
"CMSG_SELL_ITEM": "0x1A0",
|
||||
"SMSG_SELL_ITEM": "0x1A1",
|
||||
"CMSG_BUY_ITEM": "0x1A2",
|
||||
"CMSG_BUYBACK_ITEM": "0x1A6",
|
||||
"SMSG_BUY_FAILED": "0x1A5",
|
||||
"CMSG_TRAINER_LIST": "0x1B0",
|
||||
"SMSG_TRAINER_LIST": "0x1B1",
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@
|
|||
"CMSG_SELL_ITEM": "0x1A0",
|
||||
"SMSG_SELL_ITEM": "0x1A1",
|
||||
"CMSG_BUY_ITEM": "0x1A2",
|
||||
"CMSG_BUYBACK_ITEM": "0x1A6",
|
||||
"SMSG_BUY_FAILED": "0x1A5",
|
||||
"CMSG_TRAINER_LIST": "0x1B0",
|
||||
"SMSG_TRAINER_LIST": "0x1B1",
|
||||
|
|
|
|||
|
|
@ -186,8 +186,9 @@
|
|||
"CMSG_LIST_INVENTORY": "0x19E",
|
||||
"SMSG_LIST_INVENTORY": "0x19F",
|
||||
"CMSG_SELL_ITEM": "0x1A0",
|
||||
"SMSG_SELL_ITEM": "0x1A1",
|
||||
"SMSG_SELL_ITEM": "0x1A4",
|
||||
"CMSG_BUY_ITEM": "0x1A2",
|
||||
"CMSG_BUYBACK_ITEM": "0x290",
|
||||
"SMSG_BUY_FAILED": "0x1A5",
|
||||
"CMSG_TRAINER_LIST": "0x01B0",
|
||||
"SMSG_TRAINER_LIST": "0x01B1",
|
||||
|
|
|
|||
|
|
@ -282,6 +282,7 @@ enum class LogicalOpcode : uint16_t {
|
|||
CMSG_SELL_ITEM,
|
||||
SMSG_SELL_ITEM,
|
||||
CMSG_BUY_ITEM,
|
||||
CMSG_BUYBACK_ITEM,
|
||||
SMSG_BUY_FAILED,
|
||||
|
||||
// ---- Trainer ----
|
||||
|
|
|
|||
|
|
@ -2052,7 +2052,7 @@ public:
|
|||
/** CMSG_BUY_ITEM packet builder */
|
||||
class BuyItemPacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t vendorGuid, uint32_t itemId, uint32_t count);
|
||||
static network::Packet build(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count);
|
||||
};
|
||||
|
||||
/** CMSG_SELL_ITEM packet builder */
|
||||
|
|
@ -2061,6 +2061,12 @@ public:
|
|||
static network::Packet build(uint64_t vendorGuid, uint64_t itemGuid, uint32_t count);
|
||||
};
|
||||
|
||||
/** CMSG_BUYBACK_ITEM packet builder */
|
||||
class BuybackItemPacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t vendorGuid, uint32_t slot);
|
||||
};
|
||||
|
||||
/** SMSG_LIST_INVENTORY parser */
|
||||
class ListInventoryParser {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "../../extern/miniaudio.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
|
|
@ -180,10 +181,10 @@ void AudioEngine::shutdown() {
|
|||
// Clean up all active sounds
|
||||
for (auto& activeSound : activeSounds_) {
|
||||
ma_sound_uninit(activeSound.sound);
|
||||
delete activeSound.sound;
|
||||
std::free(activeSound.sound);
|
||||
ma_audio_buffer* buffer = static_cast<ma_audio_buffer*>(activeSound.buffer);
|
||||
ma_audio_buffer_uninit(buffer);
|
||||
delete buffer;
|
||||
std::free(buffer);
|
||||
}
|
||||
activeSounds_.clear();
|
||||
|
||||
|
|
@ -239,16 +240,22 @@ bool AudioEngine::playSound2D(const std::vector<uint8_t>& wavData, float volume,
|
|||
);
|
||||
bufferConfig.sampleRate = decoded.sampleRate; // Critical: preserve original sample rate!
|
||||
|
||||
ma_audio_buffer* audioBuffer = new ma_audio_buffer();
|
||||
ma_audio_buffer* audioBuffer = static_cast<ma_audio_buffer*>(std::malloc(sizeof(ma_audio_buffer)));
|
||||
if (!audioBuffer) return false;
|
||||
ma_result result = ma_audio_buffer_init(&bufferConfig, audioBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
LOG_WARNING("Failed to create audio buffer: ", result);
|
||||
delete audioBuffer;
|
||||
std::free(audioBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create sound from audio buffer
|
||||
ma_sound* sound = new ma_sound();
|
||||
ma_sound* sound = static_cast<ma_sound*>(std::malloc(sizeof(ma_sound)));
|
||||
if (!sound) {
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
std::free(audioBuffer);
|
||||
return false;
|
||||
}
|
||||
result = ma_sound_init_from_data_source(
|
||||
engine_,
|
||||
audioBuffer,
|
||||
|
|
@ -260,8 +267,8 @@ bool AudioEngine::playSound2D(const std::vector<uint8_t>& wavData, float volume,
|
|||
if (result != MA_SUCCESS) {
|
||||
LOG_WARNING("Failed to create sound: ", result);
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
delete audioBuffer;
|
||||
delete sound;
|
||||
std::free(audioBuffer);
|
||||
std::free(sound);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -274,8 +281,8 @@ bool AudioEngine::playSound2D(const std::vector<uint8_t>& wavData, float volume,
|
|||
LOG_WARNING("Failed to start sound: ", result);
|
||||
ma_sound_uninit(sound);
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
delete audioBuffer;
|
||||
delete sound;
|
||||
std::free(audioBuffer);
|
||||
std::free(sound);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -321,15 +328,21 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
|||
);
|
||||
bufferConfig.sampleRate = decoded.sampleRate; // Critical: preserve original sample rate!
|
||||
|
||||
ma_audio_buffer* audioBuffer = new ma_audio_buffer();
|
||||
ma_audio_buffer* audioBuffer = static_cast<ma_audio_buffer*>(std::malloc(sizeof(ma_audio_buffer)));
|
||||
if (!audioBuffer) return false;
|
||||
ma_result result = ma_audio_buffer_init(&bufferConfig, audioBuffer);
|
||||
if (result != MA_SUCCESS) {
|
||||
delete audioBuffer;
|
||||
std::free(audioBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create 3D sound (spatialization enabled, pitch enabled)
|
||||
ma_sound* sound = new ma_sound();
|
||||
ma_sound* sound = static_cast<ma_sound*>(std::malloc(sizeof(ma_sound)));
|
||||
if (!sound) {
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
std::free(audioBuffer);
|
||||
return false;
|
||||
}
|
||||
result = ma_sound_init_from_data_source(
|
||||
engine_,
|
||||
audioBuffer,
|
||||
|
|
@ -341,8 +354,8 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
|||
if (result != MA_SUCCESS) {
|
||||
LOG_WARNING("playSound3D: Failed to create sound, error: ", result);
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
delete audioBuffer;
|
||||
delete sound;
|
||||
std::free(audioBuffer);
|
||||
std::free(sound);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -361,8 +374,8 @@ bool AudioEngine::playSound3D(const std::vector<uint8_t>& wavData, const glm::ve
|
|||
if (result != MA_SUCCESS) {
|
||||
ma_sound_uninit(sound);
|
||||
ma_audio_buffer_uninit(audioBuffer);
|
||||
delete audioBuffer;
|
||||
delete sound;
|
||||
std::free(audioBuffer);
|
||||
std::free(sound);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -423,7 +436,13 @@ bool AudioEngine::playMusic(const std::vector<uint8_t>& musicData, float volume,
|
|||
musicDecoder_ = decoder;
|
||||
|
||||
// Create streaming sound from decoder
|
||||
musicSound_ = new ma_sound();
|
||||
musicSound_ = static_cast<ma_sound*>(std::malloc(sizeof(ma_sound)));
|
||||
if (!musicSound_) {
|
||||
ma_decoder_uninit(decoder);
|
||||
delete decoder;
|
||||
musicDecoder_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
result = ma_sound_init_from_data_source(
|
||||
engine_,
|
||||
decoder,
|
||||
|
|
@ -437,7 +456,7 @@ bool AudioEngine::playMusic(const std::vector<uint8_t>& musicData, float volume,
|
|||
ma_decoder_uninit(decoder);
|
||||
delete decoder;
|
||||
musicDecoder_ = nullptr;
|
||||
delete musicSound_;
|
||||
std::free(musicSound_);
|
||||
musicSound_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -451,7 +470,7 @@ bool AudioEngine::playMusic(const std::vector<uint8_t>& musicData, float volume,
|
|||
if (result != MA_SUCCESS) {
|
||||
LOG_ERROR("Failed to start music playback: ", result);
|
||||
ma_sound_uninit(musicSound_);
|
||||
delete musicSound_;
|
||||
std::free(musicSound_);
|
||||
musicSound_ = nullptr;
|
||||
ma_decoder_uninit(decoder);
|
||||
delete decoder;
|
||||
|
|
@ -469,7 +488,7 @@ bool AudioEngine::playMusic(const std::vector<uint8_t>& musicData, float volume,
|
|||
void AudioEngine::stopMusic() {
|
||||
if (musicSound_) {
|
||||
ma_sound_uninit(musicSound_);
|
||||
delete musicSound_;
|
||||
std::free(musicSound_);
|
||||
musicSound_ = nullptr;
|
||||
}
|
||||
if (musicDecoder_) {
|
||||
|
|
@ -507,10 +526,10 @@ void AudioEngine::update(float deltaTime) {
|
|||
if (!ma_sound_is_playing(it->sound)) {
|
||||
// Sound finished, clean up
|
||||
ma_sound_uninit(it->sound);
|
||||
delete it->sound;
|
||||
std::free(it->sound);
|
||||
ma_audio_buffer* buffer = static_cast<ma_audio_buffer*>(it->buffer);
|
||||
ma_audio_buffer_uninit(buffer);
|
||||
delete buffer;
|
||||
std::free(buffer);
|
||||
it = activeSounds_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
|
|
|
|||
|
|
@ -3094,11 +3094,9 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
wardenModuleData_.clear();
|
||||
|
||||
{
|
||||
std::string hashHex, keyHex;
|
||||
std::string hashHex;
|
||||
for (auto b : wardenModuleHash_) { char s[4]; snprintf(s, 4, "%02x", b); hashHex += s; }
|
||||
for (auto b : wardenModuleKey_) { char s[4]; snprintf(s, 4, "%02x", b); keyHex += s; }
|
||||
LOG_INFO("Warden: MODULE_USE hash=", hashHex,
|
||||
" key=", keyHex, " size=", wardenModuleSize_);
|
||||
LOG_INFO("Warden: MODULE_USE hash=", hashHex, " size=", wardenModuleSize_);
|
||||
|
||||
// Try to load pre-computed challenge/response entries
|
||||
loadWardenCRFile(hashHex);
|
||||
|
|
@ -3192,11 +3190,6 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
}
|
||||
|
||||
std::vector<uint8_t> seed(decrypted.begin() + 1, decrypted.begin() + 17);
|
||||
{
|
||||
std::string seedHex;
|
||||
for (auto b : seed) { char s[4]; snprintf(s, 4, "%02x", b); seedHex += s; }
|
||||
LOG_INFO("Warden: HASH_REQUEST seed=", seedHex);
|
||||
}
|
||||
|
||||
// --- Try CR lookup (pre-computed challenge/response entries) ---
|
||||
if (!wardenCREntries_.empty()) {
|
||||
|
|
@ -3232,12 +3225,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
std::vector<uint8_t> newDecryptKey(match->serverKey, match->serverKey + 16);
|
||||
wardenCrypto_->replaceKeys(newEncryptKey, newDecryptKey);
|
||||
|
||||
{
|
||||
std::string ekHex, dkHex;
|
||||
for (int i = 0; i < 16; i++) { char s[4]; snprintf(s, 4, "%02x", newEncryptKey[i]); ekHex += s; }
|
||||
for (int i = 0; i < 16; i++) { char s[4]; snprintf(s, 4, "%02x", newDecryptKey[i]); dkHex += s; }
|
||||
LOG_INFO("Warden: Switched to CR keys encrypt=", ekHex, " decrypt=", dkHex);
|
||||
}
|
||||
LOG_INFO("Warden: Switched to CR key set");
|
||||
|
||||
wardenState_ = WardenState::WAIT_CHECKS;
|
||||
break;
|
||||
|
|
@ -3349,13 +3337,9 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
std::vector<uint8_t> ek(newEncryptKey, newEncryptKey + 16);
|
||||
std::vector<uint8_t> dk(newDecryptKey, newDecryptKey + 16);
|
||||
wardenCrypto_->replaceKeys(ek, dk);
|
||||
|
||||
{
|
||||
std::string ekHex, dkHex;
|
||||
for (int i = 0; i < 16; i++) { char s[4]; snprintf(s, 4, "%02x", newEncryptKey[i]); ekHex += s; }
|
||||
for (int i = 0; i < 16; i++) { char s[4]; snprintf(s, 4, "%02x", newDecryptKey[i]); dkHex += s; }
|
||||
LOG_INFO("Warden: Derived keys from seed: encrypt=", ekHex, " decrypt=", dkHex);
|
||||
}
|
||||
for (auto& b : newEncryptKey) b = 0;
|
||||
for (auto& b : newDecryptKey) b = 0;
|
||||
LOG_INFO("Warden: Derived and applied key update from seed");
|
||||
}
|
||||
|
||||
wardenState_ = WardenState::WAIT_CHECKS;
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
|
|||
{"CMSG_SELL_ITEM", LogicalOpcode::CMSG_SELL_ITEM},
|
||||
{"SMSG_SELL_ITEM", LogicalOpcode::SMSG_SELL_ITEM},
|
||||
{"CMSG_BUY_ITEM", LogicalOpcode::CMSG_BUY_ITEM},
|
||||
{"CMSG_BUYBACK_ITEM", LogicalOpcode::CMSG_BUYBACK_ITEM},
|
||||
{"SMSG_BUY_FAILED", LogicalOpcode::SMSG_BUY_FAILED},
|
||||
{"CMSG_TRAINER_LIST", LogicalOpcode::CMSG_TRAINER_LIST},
|
||||
{"SMSG_TRAINER_LIST", LogicalOpcode::SMSG_TRAINER_LIST},
|
||||
|
|
@ -591,8 +592,9 @@ void OpcodeTable::loadWotlkDefaults() {
|
|||
{LogicalOpcode::CMSG_LIST_INVENTORY, 0x19E},
|
||||
{LogicalOpcode::SMSG_LIST_INVENTORY, 0x19F},
|
||||
{LogicalOpcode::CMSG_SELL_ITEM, 0x1A0},
|
||||
{LogicalOpcode::SMSG_SELL_ITEM, 0x1A1},
|
||||
{LogicalOpcode::SMSG_SELL_ITEM, 0x1A4},
|
||||
{LogicalOpcode::CMSG_BUY_ITEM, 0x1A2},
|
||||
{LogicalOpcode::CMSG_BUYBACK_ITEM, 0x290},
|
||||
{LogicalOpcode::SMSG_BUY_FAILED, 0x1A5},
|
||||
{LogicalOpcode::CMSG_TRAINER_LIST, 0x01B0},
|
||||
{LogicalOpcode::SMSG_TRAINER_LIST, 0x01B1},
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ bool WardenModule::load(const std::vector<uint8_t>& moduleData,
|
|||
std::cout << "[WardenModule] ✓ MD5 verified" << '\n';
|
||||
|
||||
// Step 2: RC4 decrypt
|
||||
// lgtm [cpp/weak-cryptographic-algorithm]
|
||||
// Warden module payload encryption is legacy RC4 by protocol design.
|
||||
// Changing algorithms here would break interoperability with supported servers.
|
||||
if (!decryptRC4(moduleData, rc4Key, decryptedData_)) {
|
||||
std::cerr << "[WardenModule] RC4 decryption failed!" << '\n';
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -2126,36 +2126,76 @@ bool ItemQueryResponseParser::parse(network::Packet& packet, ItemQueryResponseDa
|
|||
packet.readUInt32(); // ScalingStatDistribution
|
||||
packet.readUInt32(); // ScalingStatValue
|
||||
|
||||
// 5 damage types
|
||||
bool haveWeaponDamage = false;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
float dmgMin = packet.readFloat();
|
||||
float dmgMax = packet.readFloat();
|
||||
uint32_t damageType = packet.readUInt32();
|
||||
if (!haveWeaponDamage && dmgMax > 0.0f) {
|
||||
// Prefer physical damage when available, otherwise first non-zero entry.
|
||||
if (damageType == 0 || data.damageMax <= 0.0f) {
|
||||
data.damageMin = dmgMin;
|
||||
data.damageMax = dmgMax;
|
||||
haveWeaponDamage = (damageType == 0);
|
||||
const size_t preDamagePos = packet.getReadPos();
|
||||
struct DamageParseResult {
|
||||
float damageMin = 0.0f;
|
||||
float damageMax = 0.0f;
|
||||
int32_t armor = 0;
|
||||
uint32_t delayMs = 0;
|
||||
bool ok = false;
|
||||
};
|
||||
auto parseDamageBlock = [&](int damageEntries) -> DamageParseResult {
|
||||
DamageParseResult r;
|
||||
packet.setReadPos(preDamagePos);
|
||||
bool haveWeaponDamage = false;
|
||||
for (int i = 0; i < damageEntries; i++) {
|
||||
float dmgMin = packet.readFloat();
|
||||
float dmgMax = packet.readFloat();
|
||||
uint32_t damageType = packet.readUInt32();
|
||||
if (!haveWeaponDamage && dmgMax > 0.0f) {
|
||||
if (damageType == 0 || r.damageMax <= 0.0f) {
|
||||
r.damageMin = dmgMin;
|
||||
r.damageMax = dmgMax;
|
||||
haveWeaponDamage = (damageType == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.armor = static_cast<int32_t>(packet.readUInt32());
|
||||
if (packet.getSize() - packet.getReadPos() >= 28) {
|
||||
packet.readUInt32(); // HolyRes
|
||||
packet.readUInt32(); // FireRes
|
||||
packet.readUInt32(); // NatureRes
|
||||
packet.readUInt32(); // FrostRes
|
||||
packet.readUInt32(); // ShadowRes
|
||||
packet.readUInt32(); // ArcaneRes
|
||||
data.delayMs = packet.readUInt32();
|
||||
r.armor = static_cast<int32_t>(packet.readUInt32());
|
||||
if (packet.getSize() - packet.getReadPos() >= 28) {
|
||||
packet.readUInt32(); // HolyRes
|
||||
packet.readUInt32(); // FireRes
|
||||
packet.readUInt32(); // NatureRes
|
||||
packet.readUInt32(); // FrostRes
|
||||
packet.readUInt32(); // ShadowRes
|
||||
packet.readUInt32(); // ArcaneRes
|
||||
r.delayMs = packet.readUInt32();
|
||||
r.ok = true;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
|
||||
// Most WotLK/TBC cores use 2 damage entries, but some custom cores still
|
||||
// serialize a 5-entry damage block. Try both and select the plausible one.
|
||||
DamageParseResult parsed2 = parseDamageBlock(2);
|
||||
DamageParseResult parsed5 = parseDamageBlock(5);
|
||||
|
||||
auto looksArmorItem = [&](const DamageParseResult& r) {
|
||||
return (data.itemClass == 4) && (data.inventoryType != 0) && (r.armor > 0);
|
||||
};
|
||||
auto looksWeaponItem = [&](const DamageParseResult& r) {
|
||||
return (data.itemClass == 2) && (r.damageMax > 0.0f) && (r.delayMs > 0);
|
||||
};
|
||||
|
||||
const DamageParseResult* chosen = &parsed2;
|
||||
if (parsed5.ok && !parsed2.ok) {
|
||||
chosen = &parsed5;
|
||||
} else if (parsed2.ok && parsed5.ok) {
|
||||
if (looksArmorItem(parsed5) && !looksArmorItem(parsed2)) chosen = &parsed5;
|
||||
else if (looksWeaponItem(parsed5) && !looksWeaponItem(parsed2)) chosen = &parsed5;
|
||||
}
|
||||
int chosenDamageEntries = (chosen == &parsed5) ? 5 : 2;
|
||||
|
||||
data.damageMin = chosen->damageMin;
|
||||
data.damageMax = chosen->damageMax;
|
||||
data.armor = chosen->armor;
|
||||
data.delayMs = chosen->delayMs;
|
||||
|
||||
data.valid = !data.name.empty();
|
||||
LOG_DEBUG("Item query response: ", data.name, " (quality=", data.quality,
|
||||
" invType=", data.inventoryType, " stack=", data.maxStack, ")");
|
||||
" invType=", data.inventoryType, " stack=", data.maxStack,
|
||||
" class=", data.itemClass, " armor=", data.armor,
|
||||
" dmgEntries=", chosenDamageEntries, ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -3118,21 +3158,12 @@ bool QuestRequestItemsParser::parse(network::Packet& packet, QuestRequestItemsDa
|
|||
int score = -1;
|
||||
};
|
||||
|
||||
auto parseTail = [&](size_t startPos, bool closeFlagIsU32) -> ParsedTail {
|
||||
auto parseTail = [&](size_t startPos, size_t prefixSkip) -> ParsedTail {
|
||||
ParsedTail out;
|
||||
packet.setReadPos(startPos);
|
||||
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) return out;
|
||||
/*uint32_t emoteDelay =*/ packet.readUInt32();
|
||||
/*uint32_t emoteId =*/ packet.readUInt32();
|
||||
|
||||
if (closeFlagIsU32) {
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
/*uint32_t closeOnCancel =*/ packet.readUInt32();
|
||||
} else {
|
||||
if (packet.getReadPos() + 1 > packet.getSize()) return out;
|
||||
/*uint8_t autoFinish =*/ packet.readUInt8();
|
||||
}
|
||||
if (packet.getReadPos() + prefixSkip > packet.getSize()) return out;
|
||||
packet.setReadPos(packet.getReadPos() + prefixSkip);
|
||||
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) return out;
|
||||
out.requiredMoney = packet.readUInt32();
|
||||
|
|
@ -3157,22 +3188,33 @@ bool QuestRequestItemsParser::parse(network::Packet& packet, QuestRequestItemsDa
|
|||
out.score = 0;
|
||||
if (requiredItemCount <= 6) out.score += 4;
|
||||
if (out.requiredItems.size() == requiredItemCount) out.score += 3;
|
||||
if ((out.completableFlags & ~0x3u) == 0) out.score += 2;
|
||||
if (closeFlagIsU32) out.score += 1; // classic cores often use 32-bit here
|
||||
if ((out.completableFlags & ~0x3u) == 0) out.score += 5;
|
||||
if (out.requiredMoney == 0) out.score += 4;
|
||||
else if (out.requiredMoney <= 100000) out.score += 2; // <=10g is common
|
||||
else if (out.requiredMoney >= 1000000) out.score -= 3; // implausible for most quests
|
||||
if (!out.requiredItems.empty()) out.score += 1;
|
||||
size_t remaining = packet.getSize() - packet.getReadPos();
|
||||
if (remaining <= 16) out.score += 3;
|
||||
else if (remaining <= 32) out.score += 2;
|
||||
else if (remaining <= 64) out.score += 1;
|
||||
if (prefixSkip == 0) out.score += 1;
|
||||
else if (prefixSkip <= 12) out.score += 1;
|
||||
return out;
|
||||
};
|
||||
|
||||
size_t tailStart = packet.getReadPos();
|
||||
ParsedTail parseU8 = parseTail(tailStart, false);
|
||||
ParsedTail parseU32 = parseTail(tailStart, true);
|
||||
std::vector<ParsedTail> candidates;
|
||||
candidates.reserve(25);
|
||||
for (size_t skip = 0; skip <= 24; ++skip) {
|
||||
candidates.push_back(parseTail(tailStart, skip));
|
||||
}
|
||||
|
||||
const ParsedTail* chosen = nullptr;
|
||||
if (parseU8.ok && parseU32.ok) {
|
||||
chosen = (parseU32.score >= parseU8.score) ? &parseU32 : &parseU8;
|
||||
} else if (parseU32.ok) {
|
||||
chosen = &parseU32;
|
||||
} else if (parseU8.ok) {
|
||||
chosen = &parseU8;
|
||||
} else {
|
||||
for (const auto& cand : candidates) {
|
||||
if (!cand.ok) continue;
|
||||
if (!chosen || cand.score > chosen->score) chosen = &cand;
|
||||
}
|
||||
if (!chosen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -3197,52 +3239,116 @@ bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData
|
|||
return true;
|
||||
}
|
||||
|
||||
/*autoFinish*/ packet.readUInt8();
|
||||
/*flags*/ packet.readUInt32();
|
||||
/*suggestedPlayers*/ packet.readUInt32();
|
||||
struct ParsedTail {
|
||||
uint32_t rewardMoney = 0;
|
||||
uint32_t rewardXp = 0;
|
||||
std::vector<QuestRewardItem> choiceRewards;
|
||||
std::vector<QuestRewardItem> fixedRewards;
|
||||
bool ok = false;
|
||||
int score = -1000;
|
||||
};
|
||||
|
||||
// Emotes
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
uint32_t emoteCount = packet.readUInt32();
|
||||
for (uint32_t i = 0; i < emoteCount; ++i) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) break;
|
||||
packet.readUInt32(); // delay
|
||||
packet.readUInt32(); // emote
|
||||
auto parseTail = [&](size_t startPos, bool hasFlags, bool fixedArrays) -> ParsedTail {
|
||||
ParsedTail out;
|
||||
packet.setReadPos(startPos);
|
||||
|
||||
if (packet.getReadPos() + 1 > packet.getSize()) return out;
|
||||
/*autoFinish*/ packet.readUInt8();
|
||||
if (hasFlags) {
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
/*flags*/ packet.readUInt32();
|
||||
}
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
/*suggestedPlayers*/ packet.readUInt32();
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
uint32_t emoteCount = packet.readUInt32();
|
||||
if (emoteCount > 64) return out; // guard against misalignment
|
||||
for (uint32_t i = 0; i < emoteCount; ++i) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) return out;
|
||||
packet.readUInt32(); // delay
|
||||
packet.readUInt32(); // emote
|
||||
}
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
uint32_t choiceCount = packet.readUInt32();
|
||||
if (choiceCount > 6) return out;
|
||||
uint32_t choiceSlots = fixedArrays ? 6u : choiceCount;
|
||||
out.choiceRewards.reserve(choiceCount);
|
||||
uint32_t nonZeroChoice = 0;
|
||||
for (uint32_t i = 0; i < choiceSlots; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return out;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
item.choiceSlot = i;
|
||||
if (item.itemId > 0) {
|
||||
out.choiceRewards.push_back(item);
|
||||
nonZeroChoice++;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return out;
|
||||
uint32_t rewardCount = packet.readUInt32();
|
||||
if (rewardCount > 4) return out;
|
||||
uint32_t rewardSlots = fixedArrays ? 4u : rewardCount;
|
||||
out.fixedRewards.reserve(rewardCount);
|
||||
uint32_t nonZeroFixed = 0;
|
||||
for (uint32_t i = 0; i < rewardSlots; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return out;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
if (item.itemId > 0) {
|
||||
out.fixedRewards.push_back(item);
|
||||
nonZeroFixed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||
out.rewardMoney = packet.readUInt32();
|
||||
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||
out.rewardXp = packet.readUInt32();
|
||||
|
||||
out.ok = true;
|
||||
out.score = 0;
|
||||
if (hasFlags) out.score += 1;
|
||||
if (fixedArrays) out.score += 1;
|
||||
if (choiceCount <= 6) out.score += 3;
|
||||
if (rewardCount <= 4) out.score += 3;
|
||||
if (fixedArrays) {
|
||||
if (nonZeroChoice <= choiceCount) out.score += 3;
|
||||
if (nonZeroFixed <= rewardCount) out.score += 3;
|
||||
} else {
|
||||
out.score += 3; // variable arrays align naturally with count
|
||||
}
|
||||
if (packet.getReadPos() <= packet.getSize()) out.score += 2;
|
||||
size_t remaining = packet.getSize() - packet.getReadPos();
|
||||
if (remaining <= 32) out.score += 2;
|
||||
return out;
|
||||
};
|
||||
|
||||
size_t tailStart = packet.getReadPos();
|
||||
ParsedTail a = parseTail(tailStart, true, true); // WotLK-like (flags + fixed 6/4 arrays)
|
||||
ParsedTail b = parseTail(tailStart, false, true); // no flags + fixed 6/4 arrays
|
||||
ParsedTail c = parseTail(tailStart, true, false); // flags + variable arrays
|
||||
ParsedTail d = parseTail(tailStart, false, false); // classic-like variable arrays
|
||||
|
||||
const ParsedTail* best = nullptr;
|
||||
for (const ParsedTail* cand : {&a, &b, &c, &d}) {
|
||||
if (!cand->ok) continue;
|
||||
if (!best || cand->score > best->score) best = cand;
|
||||
}
|
||||
|
||||
// Choice reward items (pick one): count + 6 * (id, count, displayInfo)
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
/*choiceCount*/ packet.readUInt32();
|
||||
for (uint32_t i = 0; i < 6; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
item.choiceSlot = i;
|
||||
if (item.itemId > 0)
|
||||
data.choiceRewards.push_back(item);
|
||||
if (best) {
|
||||
data.choiceRewards = best->choiceRewards;
|
||||
data.fixedRewards = best->fixedRewards;
|
||||
data.rewardMoney = best->rewardMoney;
|
||||
data.rewardXp = best->rewardXp;
|
||||
}
|
||||
|
||||
// Fixed reward items: count + 4 * (id, count, displayInfo)
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
/*rewardCount*/ packet.readUInt32();
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
if (item.itemId > 0)
|
||||
data.fixedRewards.push_back(item);
|
||||
}
|
||||
|
||||
// Money and XP
|
||||
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||
data.rewardMoney = packet.readUInt32();
|
||||
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||
data.rewardXp = packet.readUInt32();
|
||||
|
||||
LOG_INFO("Quest offer reward: id=", data.questId, " title='", data.title,
|
||||
"' choices=", data.choiceRewards.size(), " fixed=", data.fixedRewards.size());
|
||||
return true;
|
||||
|
|
@ -3280,11 +3386,14 @@ network::Packet ListInventoryPacket::build(uint64_t npcGuid) {
|
|||
return packet;
|
||||
}
|
||||
|
||||
network::Packet BuyItemPacket::build(uint64_t vendorGuid, uint32_t itemId, uint32_t count) {
|
||||
network::Packet BuyItemPacket::build(uint64_t vendorGuid, uint32_t itemId, uint32_t slot, uint32_t count) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_BUY_ITEM));
|
||||
packet.writeUInt64(vendorGuid);
|
||||
packet.writeUInt32(itemId); // item entry
|
||||
packet.writeUInt32(slot); // vendor slot index from SMSG_LIST_INVENTORY
|
||||
packet.writeUInt32(count);
|
||||
// WotLK/AzerothCore expects a trailing byte on CMSG_BUY_ITEM.
|
||||
packet.writeUInt8(0);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
|
@ -3296,6 +3405,13 @@ network::Packet SellItemPacket::build(uint64_t vendorGuid, uint64_t itemGuid, ui
|
|||
return packet;
|
||||
}
|
||||
|
||||
network::Packet BuybackItemPacket::build(uint64_t vendorGuid, uint32_t slot) {
|
||||
network::Packet packet(wireOpcode(Opcode::CMSG_BUYBACK_ITEM));
|
||||
packet.writeUInt64(vendorGuid);
|
||||
packet.writeUInt32(slot);
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool ListInventoryParser::parse(network::Packet& packet, ListInventoryData& data) {
|
||||
data = ListInventoryData{};
|
||||
if (packet.getSize() - packet.getReadPos() < 9) {
|
||||
|
|
|
|||
|
|
@ -443,7 +443,11 @@ void WorldSocket::initEncryption(const std::vector<uint8_t>& sessionKey, uint32_
|
|||
std::vector<uint8_t> encryptHash = auth::Crypto::hmacSHA1(encryptKey, sessionKey);
|
||||
std::vector<uint8_t> decryptHash = auth::Crypto::hmacSHA1(decryptKey, sessionKey);
|
||||
|
||||
// lgtm [cpp/weak-cryptographic-algorithm]
|
||||
// WoW WotLK world-header stream cipher is protocol-defined RC4.
|
||||
// Replacing it would break interoperability with target servers.
|
||||
encryptCipher.init(encryptHash);
|
||||
// lgtm [cpp/weak-cryptographic-algorithm]
|
||||
decryptCipher.init(decryptHash);
|
||||
|
||||
// Drop first 1024 bytes of keystream (WoW WotLK protocol requirement)
|
||||
|
|
|
|||
|
|
@ -858,13 +858,7 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
|||
size_t pos = 0;
|
||||
while (pos < text.size()) {
|
||||
// Find next special element: URL or WoW link
|
||||
size_t urlStart = std::string::npos;
|
||||
size_t httpPos = text.find("http://", pos);
|
||||
size_t httpsPos = text.find("https://", pos);
|
||||
if (httpPos != std::string::npos && (httpsPos == std::string::npos || httpPos < httpsPos))
|
||||
urlStart = httpPos;
|
||||
else if (httpsPos != std::string::npos)
|
||||
urlStart = httpsPos;
|
||||
size_t urlStart = text.find("https://", pos);
|
||||
|
||||
// Find next WoW item link: |cXXXXXXXX|Hitem:ENTRY:...|h[Name]|h|r
|
||||
size_t linkStart = text.find("|c", pos);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue