/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/base/memory.h" #include "xenia/base/platform_win.h" #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | \ WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) #define XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS #endif namespace xe { namespace memory { size_t page_size() { static size_t value = 0; if (!value) { SYSTEM_INFO si; GetSystemInfo(&si); value = si.dwPageSize; } return value; } size_t allocation_granularity() { static size_t value = 0; if (!value) { SYSTEM_INFO si; GetSystemInfo(&si); value = si.dwAllocationGranularity; } return value; } DWORD ToWin32ProtectFlags(PageAccess access) { switch (access) { case PageAccess::kNoAccess: return PAGE_NOACCESS; case PageAccess::kReadOnly: return PAGE_READONLY; case PageAccess::kReadWrite: return PAGE_READWRITE; case PageAccess::kExecuteReadOnly: return PAGE_EXECUTE_READ; case PageAccess::kExecuteReadWrite: return PAGE_EXECUTE_READWRITE; default: assert_unhandled_case(access); return PAGE_NOACCESS; } } PageAccess ToXeniaProtectFlags(DWORD access) { if (access & PAGE_GUARD) { // Strip the page guard flag for now... access &= ~PAGE_GUARD; } switch (access) { case PAGE_NOACCESS: return PageAccess::kNoAccess; case PAGE_READONLY: return PageAccess::kReadOnly; case PAGE_READWRITE: return PageAccess::kReadWrite; case PAGE_EXECUTE_READ: return PageAccess::kExecuteReadOnly; case PAGE_EXECUTE_READWRITE: return PageAccess::kExecuteReadWrite; default: return PageAccess::kNoAccess; } } bool IsWritableExecutableMemorySupported() { #ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS return true; #else // To test FromApp functions on desktop, undefine // XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS and link to WindowsApp.lib. return false; #endif } void* AllocFixed(void* base_address, size_t length, AllocationType allocation_type, PageAccess access) { DWORD alloc_type = 0; switch (allocation_type) { case AllocationType::kReserve: alloc_type = MEM_RESERVE; break; case AllocationType::kCommit: alloc_type = MEM_COMMIT; break; case AllocationType::kReserveCommit: alloc_type = MEM_RESERVE | MEM_COMMIT; break; default: assert_unhandled_case(allocation_type); break; } DWORD protect = ToWin32ProtectFlags(access); #ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS return VirtualAlloc(base_address, length, alloc_type, protect); #else return VirtualAllocFromApp(base_address, length, ULONG(alloc_type), ULONG(protect)); #endif } bool DeallocFixed(void* base_address, size_t length, DeallocationType deallocation_type) { DWORD free_type = 0; switch (deallocation_type) { case DeallocationType::kRelease: free_type = MEM_RELEASE; length = 0; break; case DeallocationType::kDecommit: free_type = MEM_DECOMMIT; break; default: assert_unhandled_case(deallocation_type); break; } return VirtualFree(base_address, length, free_type) ? true : false; } bool Protect(void* base_address, size_t length, PageAccess access, PageAccess* out_old_access) { if (out_old_access) { *out_old_access = PageAccess::kNoAccess; } DWORD new_protect = ToWin32ProtectFlags(access); #ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS DWORD old_protect = 0; BOOL result = VirtualProtect(base_address, length, new_protect, &old_protect); #else ULONG old_protect = 0; BOOL result = VirtualProtectFromApp(base_address, length, ULONG(new_protect), &old_protect); #endif if (!result) { return false; } if (out_old_access) { *out_old_access = ToXeniaProtectFlags(DWORD(old_protect)); } return true; } bool QueryProtect(void* base_address, size_t& length, PageAccess& access_out) { access_out = PageAccess::kNoAccess; MEMORY_BASIC_INFORMATION info; ZeroMemory(&info, sizeof(info)); SIZE_T result = VirtualQuery(base_address, &info, length); if (!result) { return false; } length = info.RegionSize; access_out = ToXeniaProtectFlags(info.Protect); return true; } FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, size_t length, PageAccess access, bool commit) { DWORD protect = ToWin32ProtectFlags(access) | (commit ? SEC_COMMIT : SEC_RESERVE); auto full_path = "Local" / path; #ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS return CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, protect, static_cast(length >> 32), static_cast(length), full_path.c_str()); #else return CreateFileMappingFromApp(INVALID_HANDLE_VALUE, nullptr, ULONG(protect), ULONG64(length), full_path.c_str()); #endif } void CloseFileMappingHandle(FileMappingHandle handle, const std::filesystem::path& path) { CloseHandle(handle); } void* MapFileView(FileMappingHandle handle, void* base_address, size_t length, PageAccess access, size_t file_offset) { #ifdef XE_BASE_MEMORY_WIN_USE_DESKTOP_FUNCTIONS DWORD target_address_low = static_cast(file_offset); DWORD target_address_high = static_cast(file_offset >> 32); DWORD file_access = 0; switch (access) { case PageAccess::kReadOnly: file_access = FILE_MAP_READ; break; case PageAccess::kReadWrite: file_access = FILE_MAP_ALL_ACCESS; break; case PageAccess::kExecuteReadOnly: file_access = FILE_MAP_READ | FILE_MAP_EXECUTE; break; case PageAccess::kExecuteReadWrite: file_access = FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE; break; case PageAccess::kNoAccess: default: assert_unhandled_case(access); return nullptr; } return MapViewOfFileEx(handle, file_access, target_address_high, target_address_low, length, base_address); #else // VirtualAlloc2FromApp and MapViewOfFile3FromApp were added in 10.0.17134.0. // https://docs.microsoft.com/en-us/uwp/win32-and-com/win32-apis HANDLE process = GetCurrentProcess(); void* placeholder = VirtualAlloc2FromApp( process, base_address, length, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0); if (!placeholder) { return nullptr; } void* mapping = MapViewOfFile3FromApp( handle, process, placeholder, ULONG64(file_offset), length, MEM_REPLACE_PLACEHOLDER, ULONG(ToWin32ProtectFlags(access)), nullptr, 0); if (!mapping) { VirtualFree(placeholder, length, MEM_RELEASE); return nullptr; } return mapping; #endif } bool UnmapFileView(FileMappingHandle handle, void* base_address, size_t length) { return UnmapViewOfFile(base_address) ? true : false; } } // namespace memory } // namespace xe