mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 00:03:50 +00:00
Load WoW.exe PE image for Warden MEM_CHECK responses
Parse PE sections from WoW.exe into a flat virtual memory image so MEM_CHECK returns real binary contents instead of zeros. Also mocks KUSER_SHARED_DATA (0x7FFE026C) with Windows 7 version info.
This commit is contained in:
parent
cb79e43a29
commit
5e8384f34e
5 changed files with 280 additions and 3 deletions
|
|
@ -105,6 +105,7 @@ set(WOWEE_SOURCES
|
||||||
src/game/warden_crypto.cpp
|
src/game/warden_crypto.cpp
|
||||||
src/game/warden_module.cpp
|
src/game/warden_module.cpp
|
||||||
src/game/warden_emulator.cpp
|
src/game/warden_emulator.cpp
|
||||||
|
src/game/warden_memory.cpp
|
||||||
src/game/transport_manager.cpp
|
src/game/transport_manager.cpp
|
||||||
src/game/world.cpp
|
src/game/world.cpp
|
||||||
src/game/player.cpp
|
src/game/player.cpp
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
namespace wowee::game {
|
namespace wowee::game {
|
||||||
class TransportManager;
|
class TransportManager;
|
||||||
class WardenCrypto;
|
class WardenCrypto;
|
||||||
|
class WardenMemory;
|
||||||
class WardenModuleManager;
|
class WardenModuleManager;
|
||||||
class PacketParsers;
|
class PacketParsers;
|
||||||
}
|
}
|
||||||
|
|
@ -1299,6 +1300,7 @@ private:
|
||||||
uint32_t wardenPacketsAfterGate_ = 0;
|
uint32_t wardenPacketsAfterGate_ = 0;
|
||||||
bool wardenCharEnumBlockedLogged_ = false;
|
bool wardenCharEnumBlockedLogged_ = false;
|
||||||
std::unique_ptr<WardenCrypto> wardenCrypto_;
|
std::unique_ptr<WardenCrypto> wardenCrypto_;
|
||||||
|
std::unique_ptr<WardenMemory> wardenMemory_;
|
||||||
std::unique_ptr<WardenModuleManager> wardenModuleManager_;
|
std::unique_ptr<WardenModuleManager> wardenModuleManager_;
|
||||||
|
|
||||||
// Warden module download state
|
// Warden module download state
|
||||||
|
|
|
||||||
51
include/game/warden_memory.hpp
Normal file
51
include/game/warden_memory.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides WoW.exe PE memory image for Warden MEM_CHECK responses.
|
||||||
|
* Parses PE headers to build a flat virtual memory image, then serves
|
||||||
|
* readMemory() calls with real bytes. Also mocks KUSER_SHARED_DATA.
|
||||||
|
*/
|
||||||
|
class WardenMemory {
|
||||||
|
public:
|
||||||
|
WardenMemory();
|
||||||
|
~WardenMemory();
|
||||||
|
|
||||||
|
/** Search standard candidate dirs for WoW.exe and load it. */
|
||||||
|
bool load();
|
||||||
|
|
||||||
|
/** Load PE image from a specific file path. */
|
||||||
|
bool loadFromFile(const std::string& exePath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read bytes from virtual address space.
|
||||||
|
* Handles PE sections + KUSER_SHARED_DATA mock.
|
||||||
|
*/
|
||||||
|
bool readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) const;
|
||||||
|
|
||||||
|
bool isLoaded() const { return loaded_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loaded_ = false;
|
||||||
|
uint32_t imageBase_ = 0;
|
||||||
|
uint32_t imageSize_ = 0;
|
||||||
|
std::vector<uint8_t> image_;
|
||||||
|
|
||||||
|
// KUSER_SHARED_DATA mock (0x7FFE0000 - 0x7FFE0FFF)
|
||||||
|
static constexpr uint32_t KUSER_BASE = 0x7FFE0000;
|
||||||
|
static constexpr uint32_t KUSER_SIZE = 0x1000;
|
||||||
|
uint8_t kuserData_[KUSER_SIZE] = {};
|
||||||
|
|
||||||
|
bool parsePE(const std::vector<uint8_t>& fileData);
|
||||||
|
void initKuserSharedData();
|
||||||
|
std::string findWowExe() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
|
} // namespace wowee
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "game/packet_parsers.hpp"
|
#include "game/packet_parsers.hpp"
|
||||||
#include "game/transport_manager.hpp"
|
#include "game/transport_manager.hpp"
|
||||||
#include "game/warden_crypto.hpp"
|
#include "game/warden_crypto.hpp"
|
||||||
|
#include "game/warden_memory.hpp"
|
||||||
#include "game/warden_module.hpp"
|
#include "game/warden_module.hpp"
|
||||||
#include "game/opcodes.hpp"
|
#include "game/opcodes.hpp"
|
||||||
#include "game/update_field_table.hpp"
|
#include "game/update_field_table.hpp"
|
||||||
|
|
@ -2355,10 +2356,25 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
uint8_t readLen = decrypted[pos++];
|
uint8_t readLen = decrypted[pos++];
|
||||||
LOG_INFO("Warden: MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(),
|
LOG_INFO("Warden: MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(),
|
||||||
" len=", (int)readLen);
|
" len=", (int)readLen);
|
||||||
// Response: [uint8 result=0][data zeros]
|
|
||||||
// We don't have real memory, send zeros
|
// Lazy-load WoW.exe PE image on first MEM_CHECK
|
||||||
|
if (!wardenMemory_) {
|
||||||
|
wardenMemory_ = std::make_unique<WardenMemory>();
|
||||||
|
if (!wardenMemory_->load()) {
|
||||||
|
LOG_WARNING("Warden: Could not load WoW.exe for MEM_CHECK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read real bytes from PE image (falls back to zeros if unavailable)
|
||||||
|
std::vector<uint8_t> memBuf(readLen, 0);
|
||||||
|
if (wardenMemory_->isLoaded() && wardenMemory_->readMemory(offset, readLen, memBuf.data())) {
|
||||||
|
LOG_INFO("Warden: MEM_CHECK served from PE image");
|
||||||
|
} else {
|
||||||
|
LOG_WARNING("Warden: MEM_CHECK fallback to zeros for 0x",
|
||||||
|
[&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}());
|
||||||
|
}
|
||||||
resultData.push_back(0x00);
|
resultData.push_back(0x00);
|
||||||
for (int i = 0; i < readLen; i++) resultData.push_back(0x00);
|
resultData.insert(resultData.end(), memBuf.begin(), memBuf.end());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CT_PAGE_A: {
|
case CT_PAGE_A: {
|
||||||
|
|
|
||||||
207
src/game/warden_memory.cpp
Normal file
207
src/game/warden_memory.cpp
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
#include "game/warden_memory.hpp"
|
||||||
|
#include "core/logger.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
namespace wowee {
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
static inline uint32_t readLE32(const std::vector<uint8_t>& data, size_t offset) {
|
||||||
|
return data[offset] | (uint32_t(data[offset+1]) << 8)
|
||||||
|
| (uint32_t(data[offset+2]) << 16) | (uint32_t(data[offset+3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t readLE16(const std::vector<uint8_t>& data, size_t offset) {
|
||||||
|
return data[offset] | (uint16_t(data[offset+1]) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
WardenMemory::WardenMemory() = default;
|
||||||
|
WardenMemory::~WardenMemory() = default;
|
||||||
|
|
||||||
|
bool WardenMemory::parsePE(const std::vector<uint8_t>& fileData) {
|
||||||
|
// DOS header: MZ magic
|
||||||
|
if (fileData.size() < 64) return false;
|
||||||
|
if (fileData[0] != 'M' || fileData[1] != 'Z') {
|
||||||
|
LOG_ERROR("WardenMemory: Not a valid PE file (no MZ header)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// e_lfanew at offset 0x3C -> PE signature offset
|
||||||
|
uint32_t peOffset = readLE32(fileData, 0x3C);
|
||||||
|
if (peOffset + 4 > fileData.size()) return false;
|
||||||
|
|
||||||
|
// PE signature "PE\0\0"
|
||||||
|
if (fileData[peOffset] != 'P' || fileData[peOffset+1] != 'E'
|
||||||
|
|| fileData[peOffset+2] != 0 || fileData[peOffset+3] != 0) {
|
||||||
|
LOG_ERROR("WardenMemory: Invalid PE signature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// COFF header at peOffset + 4
|
||||||
|
size_t coffOfs = peOffset + 4;
|
||||||
|
if (coffOfs + 20 > fileData.size()) return false;
|
||||||
|
|
||||||
|
uint16_t numSections = readLE16(fileData, coffOfs + 2);
|
||||||
|
uint16_t optHeaderSize = readLE16(fileData, coffOfs + 16);
|
||||||
|
|
||||||
|
// Optional header
|
||||||
|
size_t optOfs = coffOfs + 20;
|
||||||
|
if (optOfs + optHeaderSize > fileData.size()) return false;
|
||||||
|
|
||||||
|
uint16_t magic = readLE16(fileData, optOfs);
|
||||||
|
if (magic != 0x10B) {
|
||||||
|
LOG_ERROR("WardenMemory: Not PE32 (magic=0x", std::hex, magic, std::dec, ")");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PE32 fields
|
||||||
|
imageBase_ = readLE32(fileData, optOfs + 28);
|
||||||
|
imageSize_ = readLE32(fileData, optOfs + 56);
|
||||||
|
uint32_t sizeOfHeaders = readLE32(fileData, optOfs + 60);
|
||||||
|
|
||||||
|
LOG_INFO("WardenMemory: PE ImageBase=0x", std::hex, imageBase_,
|
||||||
|
" ImageSize=0x", imageSize_,
|
||||||
|
" Sections=", std::dec, numSections);
|
||||||
|
|
||||||
|
// Allocate flat image (zero-filled)
|
||||||
|
image_.resize(imageSize_, 0);
|
||||||
|
|
||||||
|
// Copy headers
|
||||||
|
uint32_t headerCopy = std::min({sizeOfHeaders, imageSize_, static_cast<uint32_t>(fileData.size())});
|
||||||
|
std::memcpy(image_.data(), fileData.data(), headerCopy);
|
||||||
|
|
||||||
|
// Section table follows optional header
|
||||||
|
size_t secTableOfs = optOfs + optHeaderSize;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < numSections; i++) {
|
||||||
|
size_t secOfs = secTableOfs + i * 40;
|
||||||
|
if (secOfs + 40 > fileData.size()) break;
|
||||||
|
|
||||||
|
char secName[9] = {};
|
||||||
|
std::memcpy(secName, fileData.data() + secOfs, 8);
|
||||||
|
|
||||||
|
uint32_t virtualSize = readLE32(fileData, secOfs + 8);
|
||||||
|
uint32_t virtualAddr = readLE32(fileData, secOfs + 12);
|
||||||
|
uint32_t rawDataSize = readLE32(fileData, secOfs + 16);
|
||||||
|
uint32_t rawDataOffset = readLE32(fileData, secOfs + 20);
|
||||||
|
|
||||||
|
if (rawDataSize == 0 || rawDataOffset == 0) continue;
|
||||||
|
|
||||||
|
// Clamp copy size to file and image bounds
|
||||||
|
uint32_t copySize = std::min(rawDataSize, virtualSize);
|
||||||
|
if (rawDataOffset + copySize > fileData.size())
|
||||||
|
copySize = static_cast<uint32_t>(fileData.size()) - rawDataOffset;
|
||||||
|
if (virtualAddr + copySize > imageSize_)
|
||||||
|
copySize = imageSize_ - virtualAddr;
|
||||||
|
|
||||||
|
std::memcpy(image_.data() + virtualAddr, fileData.data() + rawDataOffset, copySize);
|
||||||
|
|
||||||
|
LOG_INFO("WardenMemory: Section '", secName,
|
||||||
|
"' VA=0x", std::hex, imageBase_ + virtualAddr,
|
||||||
|
" size=0x", copySize, std::dec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WardenMemory::initKuserSharedData() {
|
||||||
|
std::memset(kuserData_, 0, KUSER_SIZE);
|
||||||
|
|
||||||
|
// NtMajorVersion at offset 0x026C = 6 (Vista/7/8/10)
|
||||||
|
uint32_t ntMajor = 6;
|
||||||
|
std::memcpy(kuserData_ + 0x026C, &ntMajor, 4);
|
||||||
|
|
||||||
|
// NtMinorVersion at offset 0x0270 = 1 (Windows 7)
|
||||||
|
uint32_t ntMinor = 1;
|
||||||
|
std::memcpy(kuserData_ + 0x0270, &ntMinor, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) const {
|
||||||
|
if (length == 0) return true;
|
||||||
|
|
||||||
|
// KUSER_SHARED_DATA range
|
||||||
|
if (va >= KUSER_BASE && static_cast<uint64_t>(va) + length <= KUSER_BASE + KUSER_SIZE) {
|
||||||
|
std::memcpy(outBuf, kuserData_ + (va - KUSER_BASE), length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PE image range
|
||||||
|
if (!loaded_ || va < imageBase_) return false;
|
||||||
|
uint32_t offset = va - imageBase_;
|
||||||
|
if (static_cast<uint64_t>(offset) + length > imageSize_) return false;
|
||||||
|
|
||||||
|
std::memcpy(outBuf, image_.data() + offset, length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WardenMemory::findWowExe() const {
|
||||||
|
std::vector<std::string> candidateDirs;
|
||||||
|
if (const char* env = std::getenv("WOWEE_INTEGRITY_DIR")) {
|
||||||
|
if (env && *env) candidateDirs.push_back(env);
|
||||||
|
}
|
||||||
|
candidateDirs.push_back("Data/misc");
|
||||||
|
if (const char* home = std::getenv("HOME")) {
|
||||||
|
if (home && *home) {
|
||||||
|
candidateDirs.push_back(std::string(home) + "/Downloads/twmoa_1180");
|
||||||
|
candidateDirs.push_back(std::string(home) + "/twmoa_1180");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* candidateExes[] = { "WoW.exe", "TurtleWoW.exe", "Wow.exe" };
|
||||||
|
|
||||||
|
for (const auto& dir : candidateDirs) {
|
||||||
|
for (const char* exe : candidateExes) {
|
||||||
|
std::string path = dir;
|
||||||
|
if (!path.empty() && path.back() != '/') path += '/';
|
||||||
|
path += exe;
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WardenMemory::load() {
|
||||||
|
std::string path = findWowExe();
|
||||||
|
if (path.empty()) {
|
||||||
|
LOG_WARNING("WardenMemory: WoW.exe not found in any candidate directory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG_INFO("WardenMemory: Found ", path);
|
||||||
|
return loadFromFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WardenMemory::loadFromFile(const std::string& exePath) {
|
||||||
|
std::ifstream f(exePath, std::ios::binary);
|
||||||
|
if (!f.is_open()) {
|
||||||
|
LOG_ERROR("WardenMemory: Cannot open ", exePath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.seekg(0, std::ios::end);
|
||||||
|
auto fileSize = f.tellg();
|
||||||
|
f.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<uint8_t> fileData(static_cast<size_t>(fileSize));
|
||||||
|
f.read(reinterpret_cast<char*>(fileData.data()), fileSize);
|
||||||
|
|
||||||
|
if (!parsePE(fileData)) {
|
||||||
|
LOG_ERROR("WardenMemory: Failed to parse PE from ", exePath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initKuserSharedData();
|
||||||
|
loaded_ = true;
|
||||||
|
LOG_INFO("WardenMemory: Loaded PE image (", fileData.size(), " bytes on disk, ",
|
||||||
|
imageSize_, " bytes virtual)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
|
} // namespace wowee
|
||||||
Loading…
Add table
Add a link
Reference in a new issue