mirror of
https://github.com/SpeedBreakerProject/speedbreaker.git
synced 2026-10-17 22:01:34 +02:00
Co-authored-by: Lydacious <81742991+lydacious@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
172 lines
5.8 KiB
C++
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);
|
|
}
|