diff --git a/include/game/warden_emulator.hpp b/include/game/warden_emulator.hpp index 320afd0d..c3dbc37c 100644 --- a/include/game/warden_emulator.hpp +++ b/include/game/warden_emulator.hpp @@ -152,6 +152,7 @@ private: // Memory allocation tracking std::map allocations_; + std::map freeBlocks_; // free-list keyed by base address uint32_t nextHeapAddr_; // Hook handles for cleanup diff --git a/src/game/warden_emulator.cpp b/src/game/warden_emulator.cpp index 9338cc00..b1a99f7e 100644 --- a/src/game/warden_emulator.cpp +++ b/src/game/warden_emulator.cpp @@ -2,6 +2,7 @@ #include "core/logger.hpp" #include #include +#include #ifdef HAVE_UNICORN // Unicorn Engine headers @@ -46,6 +47,12 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3 LOG_ERROR("WardenEmulator: Already initialized"); return false; } + // Reset allocator state so re-initialization starts with a clean heap. + allocations_.clear(); + freeBlocks_.clear(); + apiAddresses_.clear(); + hooks_.clear(); + nextHeapAddr_ = heapBase_; { char addrBuf[32]; @@ -282,17 +289,37 @@ std::string WardenEmulator::readString(uint32_t address, size_t maxLen) { } uint32_t WardenEmulator::allocateMemory(size_t size, [[maybe_unused]] uint32_t protection) { + if (size == 0) return 0; + // Align to 4KB size = (size + 0xFFF) & ~0xFFF; + const uint32_t allocSize = static_cast(size); - if (nextHeapAddr_ + size > heapBase_ + heapSize_) { + // First-fit from free list so released blocks can be reused. + for (auto it = freeBlocks_.begin(); it != freeBlocks_.end(); ++it) { + if (it->second < size) continue; + const uint32_t addr = it->first; + const size_t blockSz = it->second; + freeBlocks_.erase(it); + if (blockSz > size) + freeBlocks_[addr + allocSize] = blockSz - size; + allocations_[addr] = size; + { + char mBuf[32]; + std::snprintf(mBuf, sizeof(mBuf), "0x%X", addr); + LOG_DEBUG("WardenEmulator: Reused ", size, " bytes at ", mBuf); + } + return addr; + } + + const uint64_t heapEnd = static_cast(heapBase_) + heapSize_; + if (static_cast(nextHeapAddr_) + size > heapEnd) { LOG_ERROR("WardenEmulator: Heap exhausted"); return 0; } uint32_t addr = nextHeapAddr_; - nextHeapAddr_ += size; - + nextHeapAddr_ += allocSize; allocations_[addr] = size; { @@ -320,7 +347,41 @@ bool WardenEmulator::freeMemory(uint32_t address) { std::snprintf(fBuf, sizeof(fBuf), "0x%X", address); LOG_DEBUG("WardenEmulator: Freed ", it->second, " bytes at ", fBuf); } + + const size_t freedSize = it->second; allocations_.erase(it); + + // Insert in free list and coalesce adjacent blocks to limit fragmentation. + auto [curr, inserted] = freeBlocks_.emplace(address, freedSize); + if (!inserted) curr->second += freedSize; + + if (curr != freeBlocks_.begin()) { + auto prev = std::prev(curr); + if (static_cast(prev->first) + prev->second == curr->first) { + prev->second += curr->second; + freeBlocks_.erase(curr); + curr = prev; + } + } + + auto next = std::next(curr); + if (next != freeBlocks_.end() && + static_cast(curr->first) + curr->second == next->first) { + curr->second += next->second; + freeBlocks_.erase(next); + } + + // Roll back the bump pointer if the highest free block reaches it. + while (!freeBlocks_.empty()) { + auto last = std::prev(freeBlocks_.end()); + if (static_cast(last->first) + last->second == nextHeapAddr_) { + nextHeapAddr_ = last->first; + freeBlocks_.erase(last); + } else { + break; + } + } + return true; }