Initial commit: DayZ memory C++ port with DMA backend and overlay
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
#include "SigScanner/SigScanner.h"
|
||||
#include "RuntimeOffsets.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Constants
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
static constexpr size_t kChunkSize = 4 * 1024 * 1024; // 4 MB per DMA read
|
||||
static constexpr size_t kOverlap = 512; // cross-boundary safety
|
||||
static constexpr size_t kDefaultScanSize = 64 * 1024 * 1024; // fallback if PE read fails
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ParsePattern
|
||||
// -----------------------------------------------------------------------
|
||||
std::vector<SigScanner::PatternByte>
|
||||
SigScanner::ParsePattern(const std::string& hex)
|
||||
{
|
||||
std::vector<PatternByte> out;
|
||||
std::istringstream ss(hex);
|
||||
std::string token;
|
||||
while (ss >> token) {
|
||||
if (token == "?" || token == "??") {
|
||||
out.push_back({ 0x00, true });
|
||||
} else {
|
||||
uint8_t byte = static_cast<uint8_t>(std::stoul(token, nullptr, 16));
|
||||
out.push_back({ byte, false });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// FindFirst
|
||||
// -----------------------------------------------------------------------
|
||||
std::optional<size_t> SigScanner::FindFirst(
|
||||
const std::vector<uint8_t>& haystack,
|
||||
const std::vector<PatternByte>& pattern,
|
||||
size_t startPos)
|
||||
{
|
||||
if (pattern.empty()) return std::nullopt;
|
||||
const size_t patLen = pattern.size();
|
||||
if (haystack.size() < patLen) return std::nullopt;
|
||||
const size_t limit = haystack.size() - patLen;
|
||||
|
||||
for (size_t i = startPos; i <= limit; ++i) {
|
||||
bool ok = true;
|
||||
for (size_t j = 0; j < patLen; ++j) {
|
||||
if (!pattern[j].wildcard && haystack[i + j] != pattern[j].value) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) return i;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ReadU32
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t SigScanner::ReadU32(const std::vector<uint8_t>& bytes, size_t offset)
|
||||
{
|
||||
uint32_t v = 0;
|
||||
std::memcpy(&v, bytes.data() + offset, sizeof(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ResolveRip (MovCs)
|
||||
// target = matchRva + instrLen + sign_extend(disp32)
|
||||
// -----------------------------------------------------------------------
|
||||
uint64_t SigScanner::ResolveRip(uint64_t matchRva, int instrLen, uint32_t disp32Raw)
|
||||
{
|
||||
const int32_t disp = static_cast<int32_t>(disp32Raw);
|
||||
const int64_t target = static_cast<int64_t>(matchRva)
|
||||
+ static_cast<int64_t>(instrLen)
|
||||
+ static_cast<int64_t>(disp);
|
||||
return static_cast<uint64_t>(target);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ExtractMovReg
|
||||
// Find the first wildcard byte in the pattern; read uint32 from that
|
||||
// position in the original chunk. For DayZ, the 4 wildcard bytes that
|
||||
// immediately follow the opcode prefix are the struct member offset.
|
||||
// -----------------------------------------------------------------------
|
||||
uint32_t SigScanner::ExtractMovReg(const std::vector<uint8_t>& chunk,
|
||||
size_t matchPos,
|
||||
const std::vector<PatternByte>& pattern)
|
||||
{
|
||||
for (size_t i = 0; i < pattern.size(); ++i) {
|
||||
if (pattern[i].wildcard) {
|
||||
return ReadU32(chunk, matchPos + i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ReadModuleSize
|
||||
// -----------------------------------------------------------------------
|
||||
size_t SigScanner::ReadModuleSize(VmmAccessor& mem, uint32_t pid, uint64_t moduleBase)
|
||||
{
|
||||
uint16_t magic = 0;
|
||||
if (!mem.TryReadValue<uint16_t>(pid, moduleBase, magic) || magic != 0x5A4D) return 0;
|
||||
|
||||
uint32_t e_lfanew = 0;
|
||||
if (!mem.TryReadValue<uint32_t>(pid, moduleBase + 0x3C, e_lfanew)) return 0;
|
||||
|
||||
uint32_t peSig = 0;
|
||||
if (!mem.TryReadValue<uint32_t>(pid, moduleBase + e_lfanew, peSig) || peSig != 0x00004550) return 0;
|
||||
|
||||
// SizeOfImage: PE_header + 4 (PE sig) + 20 (COFF) + 0x38 (optional header offset)
|
||||
uint32_t sizeOfImage = 0;
|
||||
if (!mem.TryReadValue<uint32_t>(pid, moduleBase + e_lfanew + 24 + 0x38, sizeOfImage)) return 0;
|
||||
|
||||
return static_cast<size_t>(sizeOfImage);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Scan
|
||||
// -----------------------------------------------------------------------
|
||||
SigScanner::ScanResult
|
||||
SigScanner::Scan(VmmAccessor& mem, uint32_t pid, uint64_t moduleBase)
|
||||
{
|
||||
ScanResult result;
|
||||
|
||||
// ---- Determine scan range ----
|
||||
size_t scanSize = ReadModuleSize(mem, pid, moduleBase);
|
||||
if (scanSize == 0) {
|
||||
spdlog::warn("SigScanner: PE header unreadable — defaulting to {} MB",
|
||||
kDefaultScanSize / (1024 * 1024));
|
||||
scanSize = kDefaultScanSize;
|
||||
} else {
|
||||
spdlog::debug("SigScanner: SizeOfImage = {} MB", scanSize / (1024 * 1024));
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Pattern table — DayZ 1.29
|
||||
// ====================================================================
|
||||
|
||||
// ---- MovCs: RIP-relative globals ----
|
||||
// Modbase::World (48 8B 05 [disp32] 48 8D 54 24 ?? 48 8B 48 30)
|
||||
auto patWorld = ParsePattern(
|
||||
"48 8B 05 ? ? ? ? 48 8D 54 24 ? 48 8B 48 30");
|
||||
|
||||
// Modbase::Network (48 8D 0D [disp32] E8 ?? ?? ?? ?? 48 8B 1D ?? ?? ?? ?? 84 C0 74 20 41)
|
||||
auto patNetMgr = ParsePattern(
|
||||
"48 8D 0D ? ? ? ? E8 ? ? ? ? 48 8B 1D ? ? ? ? 84 C0 74 20 41");
|
||||
|
||||
// ---- MovReg: World struct member offsets ----
|
||||
// World::NearEntList (48 8B 83 [off32] 49 8B 14 06 48 3B D5)
|
||||
auto patNear = ParsePattern(
|
||||
"48 8B 83 ? ? ? ? 49 8B 14 06 48 3B D5");
|
||||
|
||||
// World::FarEntList (48 8B 83 [off32] 49 8B 0C 06 48 3B CD ...)
|
||||
auto patFar = ParsePattern(
|
||||
"48 8B 83 ? ? ? ? 49 8B 0C 06 48 3B CD 74 17 80 B9 ? ? ? ? ? 75 0E");
|
||||
|
||||
// World::BulletList (48 8B 83 [off32] 49 8B CF 48 03 0C F8)
|
||||
auto patBullet = ParsePattern(
|
||||
"48 8B 83 ? ? ? ? 49 8B CF 48 03 0C F8");
|
||||
|
||||
// World::Camera (4C 8B 83 [off32] 4C 8B 11 48 89 70 08)
|
||||
auto patCamera = ParsePattern(
|
||||
"4C 8B 83 ? ? ? ? 4C 8B 11 48 89 70 08");
|
||||
|
||||
// World::ItemList (48 89 85 [off32] 48 8D 15 ?? ?? ?? ?? 48 89 9D ?? ?? ?? ?? 48 8D 8D)
|
||||
auto patItem = ParsePattern(
|
||||
"48 89 85 ? ? ? ? 48 8D 15 ? ? ? ? 48 89 9D ? ? ? ? 48 8D 8D");
|
||||
|
||||
// ---- MovReg: entity struct offsets ----
|
||||
// Human::VisualState (48 8B 9F [off32] 49 8B CE FF 90 ?? ?? ?? ?? 8B 10)
|
||||
auto patVS = ParsePattern(
|
||||
"48 8B 9F ? ? ? ? 49 8B CE FF 90 ? ? ? ? 8B 10");
|
||||
|
||||
// Human::IsDead (0F B6 9A [off32] 48 89 7C 24 20 41 0F B6 B8 ?? ?? ?? ?? FF 90 B0 00)
|
||||
auto patDead = ParsePattern(
|
||||
"0F B6 9A ? ? ? ? 48 89 7C 24 20 41 0F B6 B8 ? ? ? ? FF 90 B0 00");
|
||||
|
||||
// DayZPlayer::Inventory (48 8B 8B [off32] 48 8B 01 FF 90 ?? ?? ?? ?? EB 02)
|
||||
auto patInv = ParsePattern(
|
||||
"48 8B 8B ? ? ? ? 48 8B 01 FF 90 ? ? ? ? EB 02");
|
||||
|
||||
// DayZPlayerInventory::Hands (48 8B 8B [off32] 48 8B F8 48 85 C9)
|
||||
auto patHands = ParsePattern(
|
||||
"48 8B 8B ? ? ? ? 48 8B F8 48 85 C9");
|
||||
|
||||
// ====================================================================
|
||||
// Chunk scan
|
||||
// ====================================================================
|
||||
std::vector<uint8_t> chunk;
|
||||
chunk.reserve(kChunkSize + kOverlap);
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset < scanSize) {
|
||||
// Early-out once everything is found
|
||||
if (result.baseWorld &&
|
||||
result.baseNetworkManager &&
|
||||
result.worldNearEntList &&
|
||||
result.worldFarEntList &&
|
||||
result.worldBulletList &&
|
||||
result.worldCamera &&
|
||||
result.worldItemList &&
|
||||
result.humanVisualState &&
|
||||
result.humanIsDead &&
|
||||
result.playerInventory &&
|
||||
result.inventoryHands)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t readSize = std::min(kChunkSize, scanSize - offset);
|
||||
if (!mem.ReadBytes(pid, moduleBase + offset, readSize, chunk)) {
|
||||
offset += readSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
// -- MovCs --
|
||||
if (!result.baseWorld) {
|
||||
if (auto pos = FindFirst(chunk, patWorld)) {
|
||||
result.baseWorld = ResolveRip(offset + *pos, 7, ReadU32(chunk, *pos + 3));
|
||||
}
|
||||
}
|
||||
if (!result.baseNetworkManager) {
|
||||
if (auto pos = FindFirst(chunk, patNetMgr)) {
|
||||
result.baseNetworkManager = ResolveRip(offset + *pos, 7, ReadU32(chunk, *pos + 3));
|
||||
}
|
||||
}
|
||||
|
||||
// -- MovReg (World struct) --
|
||||
if (!result.worldNearEntList) {
|
||||
if (auto pos = FindFirst(chunk, patNear)) {
|
||||
result.worldNearEntList = ExtractMovReg(chunk, *pos, patNear);
|
||||
}
|
||||
}
|
||||
if (!result.worldFarEntList) {
|
||||
if (auto pos = FindFirst(chunk, patFar)) {
|
||||
result.worldFarEntList = ExtractMovReg(chunk, *pos, patFar);
|
||||
}
|
||||
}
|
||||
if (!result.worldBulletList) {
|
||||
if (auto pos = FindFirst(chunk, patBullet)) {
|
||||
result.worldBulletList = ExtractMovReg(chunk, *pos, patBullet);
|
||||
}
|
||||
}
|
||||
if (!result.worldCamera) {
|
||||
if (auto pos = FindFirst(chunk, patCamera)) {
|
||||
result.worldCamera = ExtractMovReg(chunk, *pos, patCamera);
|
||||
}
|
||||
}
|
||||
if (!result.worldItemList) {
|
||||
if (auto pos = FindFirst(chunk, patItem)) {
|
||||
result.worldItemList = ExtractMovReg(chunk, *pos, patItem);
|
||||
}
|
||||
}
|
||||
|
||||
// -- MovReg (entity struct) --
|
||||
if (!result.humanVisualState) {
|
||||
if (auto pos = FindFirst(chunk, patVS)) {
|
||||
result.humanVisualState = ExtractMovReg(chunk, *pos, patVS);
|
||||
}
|
||||
}
|
||||
if (!result.humanIsDead) {
|
||||
if (auto pos = FindFirst(chunk, patDead)) {
|
||||
result.humanIsDead = ExtractMovReg(chunk, *pos, patDead);
|
||||
}
|
||||
}
|
||||
if (!result.playerInventory) {
|
||||
if (auto pos = FindFirst(chunk, patInv)) {
|
||||
result.playerInventory = ExtractMovReg(chunk, *pos, patInv);
|
||||
}
|
||||
}
|
||||
if (!result.inventoryHands) {
|
||||
if (auto pos = FindFirst(chunk, patHands)) {
|
||||
result.inventoryHands = ExtractMovReg(chunk, *pos, patHands);
|
||||
}
|
||||
}
|
||||
|
||||
if (readSize <= kOverlap) break;
|
||||
offset += readSize - kOverlap;
|
||||
}
|
||||
|
||||
// ---- Report ----
|
||||
auto log = [](const char* name, auto val) {
|
||||
if (val) spdlog::info("SigScanner: {:35s} = 0x{:X}", name, *val);
|
||||
else spdlog::warn("SigScanner: {:35s} NOT FOUND", name);
|
||||
};
|
||||
log("base::world", result.baseWorld);
|
||||
log("base::network_manager", result.baseNetworkManager);
|
||||
log("World::NearEntityList", result.worldNearEntList);
|
||||
log("World::FarEntityList", result.worldFarEntList);
|
||||
log("World::BulletTable", result.worldBulletList);
|
||||
log("World::Camera", result.worldCamera);
|
||||
log("World::ItemList", result.worldItemList);
|
||||
log("Common::VisualState", result.humanVisualState);
|
||||
log("Player::IsDead", result.humanIsDead);
|
||||
log("Inventory::Base", result.playerInventory);
|
||||
log("Inventory::Hands", result.inventoryHands);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Apply — write results into RuntimeOffsets, log each change
|
||||
// -----------------------------------------------------------------------
|
||||
void SigScanner::Apply(const ScanResult& result)
|
||||
{
|
||||
auto applyU64 = [](const char* name, uint64_t& dst, std::optional<uint64_t> src) {
|
||||
if (!src) return;
|
||||
if (*src != dst) {
|
||||
spdlog::info("SigScanner::Apply: {} 0x{:X} -> 0x{:X}", name, dst, *src);
|
||||
dst = *src;
|
||||
}
|
||||
};
|
||||
auto applyU32 = [](const char* name, uint64_t& dst, std::optional<uint32_t> src) {
|
||||
if (!src) return;
|
||||
if (static_cast<uint64_t>(*src) != dst) {
|
||||
spdlog::info("SigScanner::Apply: {} 0x{:X} -> 0x{:X}", name, dst, *src);
|
||||
dst = static_cast<uint64_t>(*src);
|
||||
}
|
||||
};
|
||||
|
||||
applyU64("Base::World", RuntimeOffsets::Base::World, result.baseWorld);
|
||||
applyU64("Base::NetworkManager", RuntimeOffsets::Base::NetworkManager, result.baseNetworkManager);
|
||||
|
||||
applyU32("World::NearEntityList", RuntimeOffsets::World::NearEntityList, result.worldNearEntList);
|
||||
applyU32("World::FarEntityList", RuntimeOffsets::World::FarEntityList, result.worldFarEntList);
|
||||
applyU32("World::BulletTable", RuntimeOffsets::World::BulletTable, result.worldBulletList);
|
||||
applyU32("World::Camera", RuntimeOffsets::World::Camera, result.worldCamera);
|
||||
applyU32("World::ItemList", RuntimeOffsets::World::ItemList, result.worldItemList);
|
||||
|
||||
// Derive companion offsets that always follow the primary by +8
|
||||
if (result.worldNearEntList) {
|
||||
RuntimeOffsets::World::NearTableSize = RuntimeOffsets::World::NearEntityList + 8;
|
||||
}
|
||||
if (result.worldFarEntList) {
|
||||
RuntimeOffsets::World::FarTableSize = RuntimeOffsets::World::FarEntityList + 8;
|
||||
}
|
||||
if (result.worldBulletList) {
|
||||
RuntimeOffsets::World::BulletCount = RuntimeOffsets::World::BulletTable + 8;
|
||||
}
|
||||
if (result.worldItemList) {
|
||||
RuntimeOffsets::World::ItemTableAllocCount = RuntimeOffsets::World::ItemList + 8;
|
||||
RuntimeOffsets::World::ItemTableValidCount = RuntimeOffsets::World::ItemList + 16;
|
||||
}
|
||||
|
||||
applyU32("Common::VisualState", RuntimeOffsets::Common::VisualState, result.humanVisualState);
|
||||
applyU32("Player::IsDead", RuntimeOffsets::Player::IsDead, result.humanIsDead);
|
||||
applyU32("Inventory::Base", RuntimeOffsets::Inventory::Base, result.playerInventory);
|
||||
applyU32("Inventory::Hands", RuntimeOffsets::Inventory::Hands, result.inventoryHands);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Memory/VmmAccessor.h"
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// SigScanner
|
||||
//
|
||||
// Reads the game module in chunks via DMA and searches for byte patterns
|
||||
// to extract offsets that change with each game update.
|
||||
//
|
||||
// Supported scan types:
|
||||
// MovCs — RIP-relative MOV/LEA: read int32 disp from the first wildcard
|
||||
// position, resolve to a module-relative RVA.
|
||||
// MovReg — Struct-member access: read uint32 from the first wildcard
|
||||
// position; the value IS the member offset within the struct.
|
||||
//
|
||||
// Usage:
|
||||
// auto result = SigScanner::Scan(mem, pid, moduleBase);
|
||||
// SigScanner::Apply(result); // writes into RuntimeOffsets
|
||||
// -----------------------------------------------------------------------
|
||||
class SigScanner {
|
||||
public:
|
||||
struct ScanResult {
|
||||
// ------------------------------------------------------------------
|
||||
// Base:: — module-relative RVAs of global-variable slots
|
||||
// (MovCs — resolved from RIP-relative displacement)
|
||||
// ------------------------------------------------------------------
|
||||
std::optional<uint64_t> baseWorld; // Base::World
|
||||
std::optional<uint64_t> baseNetworkManager; // Base::NetworkManager
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// World:: — struct member offsets within the World object
|
||||
// (MovReg — 4-byte little-endian value at the first wildcard pos)
|
||||
// ------------------------------------------------------------------
|
||||
std::optional<uint32_t> worldNearEntList; // World::NearEntityList
|
||||
std::optional<uint32_t> worldFarEntList; // World::FarEntityList
|
||||
std::optional<uint32_t> worldBulletList; // World::BulletTable
|
||||
std::optional<uint32_t> worldCamera; // World::Camera
|
||||
std::optional<uint32_t> worldItemList; // World::ItemList
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Entity struct offsets (MovReg)
|
||||
// ------------------------------------------------------------------
|
||||
std::optional<uint32_t> humanVisualState; // Common::VisualState
|
||||
std::optional<uint32_t> humanIsDead; // Player::IsDead
|
||||
std::optional<uint32_t> playerInventory; // Inventory::Base
|
||||
std::optional<uint32_t> inventoryHands; // Inventory::Hands
|
||||
};
|
||||
|
||||
// Scan the game module for all configured patterns.
|
||||
// moduleBase: runtime VA of the module's first mapped byte.
|
||||
static ScanResult Scan(VmmAccessor& mem, uint32_t pid, uint64_t moduleBase);
|
||||
|
||||
// Write scanned values into RuntimeOffsets, logging each change.
|
||||
static void Apply(const ScanResult& result);
|
||||
|
||||
private:
|
||||
struct PatternByte { uint8_t value; bool wildcard; };
|
||||
|
||||
// Parse a hex pattern string like "4C 8B 05 ? ? ? ? 48" into PatternByte vec.
|
||||
static std::vector<PatternByte> ParsePattern(const std::string& hex);
|
||||
|
||||
// Return the byte-offset of the first match in [haystack], or nullopt.
|
||||
static std::optional<size_t> FindFirst(const std::vector<uint8_t>& haystack,
|
||||
const std::vector<PatternByte>& pattern,
|
||||
size_t startPos = 0);
|
||||
|
||||
// Read a 4-byte little-endian uint32 from bytes[offset].
|
||||
static uint32_t ReadU32(const std::vector<uint8_t>& bytes, size_t offset);
|
||||
|
||||
// For MovCs: target = matchRva + instrLen + (int32_t)disp32
|
||||
static uint64_t ResolveRip(uint64_t matchRva, int instrLen, uint32_t disp32Raw);
|
||||
|
||||
// For MovReg: return the uint32 at the first wildcard position in the pattern.
|
||||
static uint32_t ExtractMovReg(const std::vector<uint8_t>& chunk,
|
||||
size_t matchPos,
|
||||
const std::vector<PatternByte>& pattern);
|
||||
|
||||
// Read SizeOfImage from the module's PE optional header.
|
||||
static size_t ReadModuleSize(VmmAccessor& mem, uint32_t pid, uint64_t moduleBase);
|
||||
};
|
||||
Reference in New Issue
Block a user