// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). // // xboxkrnl memory exports. Semantics follow Xenia's xboxkrnl_memory.cc. #include "../stdafx.h" #include "function.h" #include "status.h" #include "vmem.h" using namespace vmem; namespace { constexpr uint32_t PAGE_EXECUTE_ANY = 0x10 | 0x20 | 0x40 | 0x80; PageHeap& VirtualHeapFor(uint32_t type) { return (type & X_MEM_LARGE_PAGES) ? Virtual64K() : Virtual4K(); } } uint32_t NtAllocateVirtualMemory(be* baseAddress, be* regionSize, uint32_t type, uint32_t protect, uint32_t debugMemory) { if (baseAddress == nullptr || regionSize == nullptr || *regionSize == 0) return STATUS_INVALID_PARAMETER; if (!(type & (X_MEM_COMMIT | X_MEM_RESET | X_MEM_RESERVE))) return STATUS_INVALID_PARAMETER; if ((type & X_MEM_RESET) && (type & ~X_MEM_RESET)) return STATUS_INVALID_PARAMETER; if (type & X_MEM_RESET) return STATUS_SUCCESS; // contents may be discarded; keeping them is valid protect &= ~PAGE_EXECUTE_ANY; uint32_t requested = *baseAddress; // Some games pass negative sizes (Xenia). uint32_t size = int32_t(uint32_t(*regionSize)) < 0 ? uint32_t(-int32_t(uint32_t(*regionSize))) : uint32_t(*regionSize); PageHeap* heap; uint32_t addr = 0; if (requested != 0) { heap = HeapFor(requested); if (heap == nullptr || heap == &Physical()) return STATUS_INVALID_PARAMETER; uint32_t page = heap->PageSize(); uint32_t base = requested - requested % page; size = (requested + size - base + page - 1) / page * page; if (heap->AllocFixed(base, size, type, protect, nullptr)) addr = base; } else { heap = &VirtualHeapFor(type); uint32_t page = heap->PageSize(); size = (size + page - 1) / page * page; addr = heap->Alloc(size, page, type, protect, (type & X_MEM_TOP_DOWN) != 0); } if (addr == 0) { fprintf(stderr, "[vmem] NtAllocateVirtualMemory failed: base %08X size %08X type %08X\n", requested, size, type); return STATUS_NO_MEMORY; } *baseAddress = addr; *regionSize = size; return STATUS_SUCCESS; } uint32_t NtFreeVirtualMemory(be* baseAddress, be* regionSize, uint32_t type, uint32_t debugMemory) { uint32_t addr = baseAddress ? uint32_t(*baseAddress) : 0; if (addr == 0) return STATUS_MEMORY_NOT_ALLOCATED; PageHeap* heap = HeapFor(addr); if (heap == nullptr || heap == &Physical()) return STATUS_INVALID_PARAMETER; uint32_t size = regionSize ? uint32_t(*regionSize) : 0; bool ok; if (type == X_MEM_DECOMMIT) { uint32_t page = heap->PageSize(); size = (size + page - 1) / page * page; ok = size != 0 && heap->Decommit(addr, size); } else { ok = heap->Release(addr, &size); } if (!ok) return STATUS_UNSUCCESSFUL; *baseAddress = addr; if (regionSize) *regionSize = size; return STATUS_SUCCESS; } struct X_MEMORY_BASIC_INFORMATION { be baseAddress; be allocationBase; be allocationProtect; be regionSize; be state; be protect; be type; }; uint32_t NtQueryVirtualMemory(uint32_t address, X_MEMORY_BASIC_INFORMATION* info) { PageHeap* heap = HeapFor(address); RegionInfo r; if (heap == nullptr || info == nullptr || !heap->Query(address, &r)) return STATUS_INVALID_PARAMETER; info->baseAddress = r.baseAddress; info->allocationBase = r.allocationBase; info->allocationProtect = r.allocationProtect; info->regionSize = r.regionSize; info->state = r.state; info->protect = r.protect; info->type = X_MEM_PRIVATE; return STATUS_SUCCESS; } uint32_t NtProtectVirtualMemory(be* baseAddress, be* regionSize, uint32_t protect, be* oldProtect, uint32_t debugMemory) { uint32_t addr = baseAddress ? uint32_t(*baseAddress) : 0; PageHeap* heap = HeapFor(addr); if (heap == nullptr || regionSize == nullptr) return STATUS_INVALID_PARAMETER; uint32_t page = heap->PageSize(); uint32_t base = addr - addr % page; uint32_t size = (addr + uint32_t(*regionSize) - base + page - 1) / page * page; uint32_t old = 0; if (!heap->SetProtect(base, size, protect & ~PAGE_EXECUTE_ANY, &old)) return STATUS_INVALID_PARAMETER; *baseAddress = base; *regionSize = size; if (oldProtect) *oldProtect = old; return STATUS_SUCCESS; } // Physical memory: flags carry the page size (X_MEM_LARGE_PAGES / X_MEM_16MB_PAGES) // in `protect`, as Xenia observed. min/max are physical addresses. uint32_t MmAllocatePhysicalMemoryEx(uint32_t flags, uint32_t size, uint32_t protect, uint32_t minAddress, uint32_t maxAddress, uint32_t alignment) { if (!(protect & (0x02 | 0x04))) // PAGE_READONLY | PAGE_READWRITE return 0; uint32_t page = (protect & X_MEM_LARGE_PAGES) ? 0x10000 : (protect & X_MEM_16MB_PAGES) ? 0x1000000 : 0x1000; size = (size + page - 1) / page * page; alignment = std::max(page, (alignment + page - 1) / page * page); PageHeap& heap = Physical(); uint32_t lo = heap.Base() + std::min(minAddress, 0x1FFFFFFFu); uint32_t hi = heap.Base() + std::min(maxAddress, 0x1FFFFFFFu); uint32_t addr = heap.Alloc(size, alignment, X_MEM_RESERVE | X_MEM_COMMIT, protect & 0xFFFF, true, lo, hi); if (addr == 0) fprintf(stderr, "[vmem] MmAllocatePhysicalMemoryEx failed: size %08X align %08X range %08X-%08X\n", size, alignment, minAddress, maxAddress); return addr; } uint32_t MmAllocatePhysicalMemory(uint32_t flags, uint32_t size, uint32_t protect) { return MmAllocatePhysicalMemoryEx(flags, size, protect, 0, 0xFFFFFFFF, 0); } void MmFreePhysicalMemory(uint32_t type, uint32_t baseAddress) { if (baseAddress != 0) Physical().Release(baseAddress, nullptr); } uint32_t MmGetPhysicalAddress(uint32_t address) { uint32_t physical = address & 0x1FFFFFFF; if (address >= 0xE0000000) physical += g_memory.eWindowShift; return physical; } uint32_t MmQueryAddressProtect(uint32_t address) { PageHeap* heap = HeapFor(address); if (heap != nullptr) return heap->Protect(address); // The XEX image and kernel objects are ordinary read/write memory. return (address >= 0x7F000000 && address < 0xA0000000) ? 0x04 : 0; } uint32_t MmQueryAllocationSize(uint32_t address) { PageHeap* heap = HeapFor(address); return heap ? heap->AllocationSize(address) : 0; } void MmSetAddressProtect(uint32_t address, uint32_t size, uint32_t protect) { if (PageHeap* heap = HeapFor(address)) heap->SetProtect(address, size, protect & ~PAGE_EXECUTE_ANY, nullptr); } // Kernel pool: ordinary committed virtual memory. uint32_t ExAllocatePoolWithTag(uint32_t size, uint32_t tag) { return Virtual4K().Alloc(size, 0x1000, X_MEM_RESERVE | X_MEM_COMMIT, 0x04, false); } uint32_t ExAllocatePool(uint32_t size) { return ExAllocatePoolWithTag(size, 0); } void ExFreePool(uint32_t address) { if (address != 0) Virtual4K().Release(address, nullptr); } struct X_MM_QUERY_STATISTICS_SECTION { be availablePages; be totalVirtualMemoryBytes; be reservedVirtualMemoryBytes; be physicalPages; be poolPages; be stackPages; be imagePages; be heapPages; be virtualPages; be pageTablePages; be cachePages; }; struct X_MM_QUERY_STATISTICS_RESULT { be size; be totalPhysicalPages; be kernelPages; X_MM_QUERY_STATISTICS_SECTION title; X_MM_QUERY_STATISTICS_SECTION system; be highestPhysicalPage; }; static_assert(sizeof(X_MM_QUERY_STATISTICS_RESULT) == 104); // Constants follow Xenia (mostly guesses there too); available pages are real. uint32_t MmQueryStatistics(X_MM_QUERY_STATISTICS_RESULT* stats) { if (stats == nullptr) return STATUS_INVALID_PARAMETER; if (stats->size != sizeof(X_MM_QUERY_STATISTICS_RESULT)) return 0xC0000023; // STATUS_BUFFER_TOO_SMALL memset(stats, 0, sizeof(*stats)); stats->size = sizeof(X_MM_QUERY_STATISTICS_RESULT); stats->totalPhysicalPages = 0x00020000; // 512 MB / 4 KB stats->kernelPages = 0x00000300; uint32_t used = Physical().UsedPages(); // 4 KB pages stats->title.availablePages = 0x00020000 - std::min(used, 0x0001FFFF); stats->title.totalVirtualMemoryBytes = 0x2FFF0000; stats->title.reservedVirtualMemoryBytes = 0x00160000; stats->title.physicalPages = 0x00001000; stats->title.poolPages = 0x00000010; stats->title.stackPages = 0x00000100; stats->title.imagePages = 0x00000100; stats->title.heapPages = 0x00000100; stats->title.virtualPages = 0x00000100; stats->title.pageTablePages = 0x00000100; stats->title.cachePages = 0x00000100; stats->highestPhysicalPage = 0x0001FFFF; return STATUS_SUCCESS; } GUEST_FUNCTION_HOOK(__imp__MmQueryStatistics, MmQueryStatistics); GUEST_FUNCTION_HOOK(__imp__NtAllocateVirtualMemory, NtAllocateVirtualMemory); GUEST_FUNCTION_HOOK(__imp__NtFreeVirtualMemory, NtFreeVirtualMemory); GUEST_FUNCTION_HOOK(__imp__NtQueryVirtualMemory, NtQueryVirtualMemory); GUEST_FUNCTION_HOOK(__imp__NtProtectVirtualMemory, NtProtectVirtualMemory); GUEST_FUNCTION_HOOK(__imp__MmAllocatePhysicalMemoryEx, MmAllocatePhysicalMemoryEx); GUEST_FUNCTION_HOOK(__imp__MmAllocatePhysicalMemory, MmAllocatePhysicalMemory); GUEST_FUNCTION_HOOK(__imp__MmFreePhysicalMemory, MmFreePhysicalMemory); GUEST_FUNCTION_HOOK(__imp__MmGetPhysicalAddress, MmGetPhysicalAddress); GUEST_FUNCTION_HOOK(__imp__MmQueryAddressProtect, MmQueryAddressProtect); GUEST_FUNCTION_HOOK(__imp__MmQueryAllocationSize, MmQueryAllocationSize); GUEST_FUNCTION_HOOK(__imp__MmSetAddressProtect, MmSetAddressProtect); GUEST_FUNCTION_HOOK(__imp__ExAllocatePoolWithTag, ExAllocatePoolWithTag); GUEST_FUNCTION_HOOK(__imp__ExAllocatePool, ExAllocatePool); GUEST_FUNCTION_HOOK(__imp__ExFreePool, ExFreePool);