Files
2026-10-11 06:51:57 +00:00

172 lines
5.8 KiB
C++

// Adapted from Unleashed Recompiled (https://github.com/hedge-dev/UnleashedRecomp),
// GPL-3.0-or-later. Modified for SpeedBreaker.
#include "../stdafx.h"
#include "memory.h"
#ifndef _WIN32
#include <cerrno>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#ifdef __APPLE__
#include <mach/mach.h>
#endif
bool Memory::MapPhysicalMirrors()
{
constexpr size_t PHYSICAL_SIZE = 0x20000000; // 512 MB
#ifdef __APPLE__
// A named memory entry, mapped three times with vm_map. The iOS sandbox
// refuses shm_open (EPERM); this works there and on macOS alike.
memory_object_size_t entrySize = PHYSICAL_SIZE;
mach_port_t entry = MACH_PORT_NULL;
kern_return_t kr = mach_make_memory_entry_64(mach_task_self(), &entrySize, 0,
MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE, &entry, MACH_PORT_NULL);
if (kr != KERN_SUCCESS)
{
fprintf(stderr, "[memory] mach_make_memory_entry_64: %s\n", mach_error_string(kr));
failedError = kr;
return false;
}
bool ok = true;
#else
char name[64];
snprintf(name, sizeof(name), "/speedbreaker-%d", int(getpid()));
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd < 0)
{
failedError = errno;
perror("[memory] shm_open");
return false;
}
shm_unlink(name); // the mappings keep it alive
bool ok = ftruncate(fd, PHYSICAL_SIZE) == 0;
if (!ok)
failedError = errno;
#endif
struct View { uint32_t guest; size_t offset; size_t length; };
const View views[] = {
{ 0xA0000000, 0, PHYSICAL_SIZE },
{ 0xC0000000, 0, PHYSICAL_SIZE },
{ 0xE0000000, 0x1000, PHYSICAL_SIZE - 0x1000 },
};
for (const View& v : views)
{
if (!ok)
break;
void* at = base + v.guest;
// mmap offsets must be host-page aligned. On 16 KB-page hosts (Apple
// Silicon) the 0xE0000000 window's 4 KB shift can't be expressed, so
// it is mapped unshifted there; MmGetPhysicalAddress uses
// eWindowShift so addresses stay consistent. 4 KB-page hosts (x86
// Linux, the Steam Deck) get the exact hardware layout.
size_t offset = v.offset;
if (offset % size_t(sysconf(_SC_PAGESIZE)) != 0)
offset = 0;
if (v.guest == 0xE0000000)
eWindowShift = uint32_t(offset);
#ifdef __APPLE__
vm_address_t address = vm_address_t(at);
kr = vm_map(mach_task_self(), &address, v.length, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, entry,
vm_offset_t(offset), FALSE, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr, "[memory] vm_map physical view: %s\n", mach_error_string(kr));
failedError = kr;
ok = false;
}
#else
if (mmap(at, v.length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, off_t(offset)) == MAP_FAILED)
{
failedError = errno;
perror("[memory] mmap physical view");
ok = false;
}
#endif
}
#ifdef __APPLE__
mach_port_deallocate(mach_task_self(), entry); // the mappings keep it alive
#else
close(fd);
#endif
return ok;
}
#endif
Memory::Memory()
{
#ifdef _WIN32
base = (uint8_t*)VirtualAlloc((void*)0x100000000ull, PPC_MEMORY_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (base == nullptr)
base = (uint8_t*)VirtualAlloc(nullptr, PPC_MEMORY_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (base == nullptr)
return;
DWORD oldProtect;
VirtualProtect(base, 4096, PAGE_NOACCESS, &oldProtect);
#else
// NFSMW_TEST_RESERVE_FAIL=1|2: fail the reservation or the mirrors as an
// iOS without room would (memory_status.h), to see the startup message
// anywhere.
const char* testFail = std::getenv("NFSMW_TEST_RESERVE_FAIL");
int failStep = testFail ? std::atoi(testFail) : 0;
// SpeedBreaker: one contiguous 4 GB, since guest addresses are offsets
// from base. iOS makes room for it with Extended Virtual Addressing or
// Increased Memory Limit (scripts/ios/SpeedBreaker/SpeedBreaker.entitlements,
// kernel/memory_status.h); without them only about 5 GB is free in one
// piece, so this runs in static initialization, before anything else
// takes address space. If it fails (ENOMEM), main.cpp says so on screen.
base = failStep == 1 ? (uint8_t*)MAP_FAILED
: (uint8_t*)mmap((void*)0x100000000ull, PPC_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (base == (uint8_t*)MAP_FAILED && failStep != 1)
base = (uint8_t*)mmap(NULL, PPC_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (base == (uint8_t*)MAP_FAILED)
{
failedStep = 1;
failedError = failStep == 1 ? ENOMEM : errno;
base = nullptr;
return;
}
mprotect(base, 4096, PROT_NONE);
// SpeedBreaker: the 360 exposes its 512 MB of physical memory through three
// windows that alias each other: 0xA0000000 and 0xC0000000 map physical 0,
// 0xE0000000 maps physical +0x1000 (Xenia's map_info). D3D writes records
// through one window and reads them through another, so all three must be
// the same host memory: one shared object, mapped three times.
if (failStep == 2 || !MapPhysicalMirrors())
{
failedStep = 2;
if (failStep == 2)
{
#ifdef __APPLE__
failedError = KERN_NO_SPACE;
#else
failedError = ENOMEM;
#endif
}
munmap(base, PPC_MEMORY_SIZE);
base = nullptr;
return;
}
#endif
for (size_t i = 0; PPCFuncMappings[i].guest != 0; i++)
{
if (PPCFuncMappings[i].host != nullptr)
InsertFunction(PPCFuncMappings[i].guest, PPCFuncMappings[i].host);
}
}
void* MmGetHostAddress(uint32_t ptr)
{
return g_memory.Translate(ptr);
}