romfs: zelda is a blight upon this earth

This commit is contained in:
Michael Scire 2023-05-11 23:39:02 -07:00
parent a9fc5fdab0
commit 576f1c43a4
4 changed files with 147 additions and 67 deletions

View file

@ -24,7 +24,7 @@ namespace ams {
namespace { namespace {
/* TODO: we really shouldn't be using malloc just to avoid dealing with real allocator separation. */ /* TODO: we really shouldn't be using malloc just to avoid dealing with real allocator separation. */
constexpr size_t MallocBufferSize = 16_MB; constexpr size_t MallocBufferSize = 32_MB;
alignas(os::MemoryPageSize) constinit u8 g_malloc_buffer[MallocBufferSize]; alignas(os::MemoryPageSize) constinit u8 g_malloc_buffer[MallocBufferSize];
} }

View file

@ -28,80 +28,161 @@ namespace ams::mitm::fs {
struct ApplicationWithDynamicHeapInfo { struct ApplicationWithDynamicHeapInfo {
ncm::ProgramId program_id; ncm::ProgramId program_id;
size_t dynamic_heap_size; size_t dynamic_app_heap_size;
size_t dynamic_system_heap_size;
}; };
constexpr const ApplicationWithDynamicHeapInfo ApplicationsWithDynamicHeap[] = { constexpr const ApplicationWithDynamicHeapInfo ApplicationsWithDynamicHeap[] = {
{ 0x01006F8002326000, 32_MB }, /* Animal Crossing: New Horizons. */ /* Animal Crossing: New Horizons. */
{ 0x0100A6301214E000, 40_MB }, /* Fire Emblem: Engage. */ /* Requirement ~24 MB. */
/* No particular heap sensitivity. */
{ 0x01006F8002326000, 16_MB, 0_MB },
/* Fire Emblem: Engage. */
/* Requirement ~32+ MB. */
/* No particular heap sensitivity. */
{ 0x0100A6301214E000, 16_MB, 0_MB },
/* The Legend of Zelda: Tears of the Kingdom. */
/* Requirement ~48 MB. */
/* Game is highly sensitive to memory stolen from application heap. */
/* 1.0.0 tolerates no more than 16 MB stolen. 1.1.0 no more than 12 MB. */
{ 0x0100F2C0115B6000, 10_MB, 8_MB },
}; };
constexpr size_t GetDynamicHeapSize(ncm::ProgramId program_id) { constexpr size_t GetDynamicAppHeapSize(ncm::ProgramId program_id) {
for (const auto &info : ApplicationsWithDynamicHeap) { for (const auto &info : ApplicationsWithDynamicHeap) {
if (info.program_id == program_id) { if (info.program_id == program_id) {
return info.dynamic_heap_size; return info.dynamic_app_heap_size;
} }
} }
return 0; return 0;
} }
constexpr size_t GetDynamicSysHeapSize(ncm::ProgramId program_id) {
for (const auto &info : ApplicationsWithDynamicHeap) {
if (info.program_id == program_id) {
return info.dynamic_system_heap_size;
}
}
return 0;
}
template<auto MapImpl, auto UnmapImpl>
struct DynamicHeap {
uintptr_t heap_address{};
size_t heap_size{};
size_t outstanding_allocations{};
util::TypedStorage<mem::StandardAllocator> heap{};
os::SdkMutex release_heap_lock{};
constexpr DynamicHeap() = default;
void Map() {
if (this->heap_address == 0) {
/* NOTE: Lock not necessary, because this is the only location which do 0 -> non-zero. */
R_ABORT_UNLESS(MapImpl(std::addressof(this->heap_address), this->heap_size));
AMS_ABORT_UNLESS(this->heap_address != 0);
/* Create heap. */
util::ConstructAt(this->heap, reinterpret_cast<void *>(this->heap_address), this->heap_size);
}
}
void TryRelease() {
if (this->outstanding_allocations == 0) {
std::scoped_lock lk(this->release_heap_lock);
if (this->heap_address != 0) {
util::DestroyAt(this->heap);
this->heap = {};
R_ABORT_UNLESS(UnmapImpl(this->heap_address, this->heap_size));
this->heap_address = 0;
}
}
}
void *Allocate(size_t size) {
void * const ret = util::GetReference(this->heap).Allocate(size);
if (AMS_LIKELY(ret != nullptr)) {
++this->outstanding_allocations;
}
return ret;
}
bool TryFree(void *p) {
if (this->IsAllocated(p)) {
--this->outstanding_allocations;
util::GetReference(this->heap).Free(p);
return true;
} else {
return false;
}
}
bool IsAllocated(void *p) const {
const uintptr_t address = reinterpret_cast<uintptr_t>(p);
return this->heap_address != 0 && (this->heap_address <= address && address < this->heap_address + this->heap_size);
}
void Reset() {
/* This should require no remaining allocations. */
AMS_ABORT_UNLESS(this->outstanding_allocations == 0);
/* Free the heap. */
this->TryRelease();
AMS_ABORT_UNLESS(this->heap_address == 0);
/* Clear the heap size. */
this->heap_size = 0;
}
};
Result MapByHeap(uintptr_t *out, size_t size) {
R_TRY(os::SetMemoryHeapSize(size));
R_RETURN(os::AllocateMemoryBlock(out, size));
}
Result UnmapByHeap(uintptr_t address, size_t size) {
os::FreeMemoryBlock(address, size);
R_RETURN(os::SetMemoryHeapSize(0));
}
/* Dynamic allocation globals. */ /* Dynamic allocation globals. */
constinit os::SdkMutex g_romfs_build_lock; constinit os::SdkMutex g_romfs_build_lock;
constinit ncm::ProgramId g_dynamic_heap_program_id{}; constinit ncm::ProgramId g_dynamic_heap_program_id{};
constinit size_t g_dynamic_heap_size = 0;
constinit os::SdkMutex g_release_dynamic_heap_lock;
constinit bool g_building_from_dynamic_heap = false; constinit bool g_building_from_dynamic_heap = false;
constinit uintptr_t g_dynamic_heap_address = 0;
constinit size_t g_dynamic_heap_outstanding_allocations = 0;
constinit util::TypedStorage<mem::StandardAllocator> g_dynamic_heap{}; constinit DynamicHeap<os::AllocateUnsafeMemory, os::FreeUnsafeMemory> g_dynamic_app_heap;
constinit DynamicHeap<MapByHeap, UnmapByHeap> g_dynamic_sys_heap;
bool IsAllocatedFromDynamicHeap(void *p) {
const uintptr_t address = reinterpret_cast<uintptr_t>(p);
return g_dynamic_heap_address != 0 && (g_dynamic_heap_address <= address && address < g_dynamic_heap_address + g_dynamic_heap_size);
}
void InitializeDynamicHeapForBuildRomfs(ncm::ProgramId program_id) { void InitializeDynamicHeapForBuildRomfs(ncm::ProgramId program_id) {
if (program_id == g_dynamic_heap_program_id && g_dynamic_heap_size > 0) { if (program_id == g_dynamic_heap_program_id && g_dynamic_app_heap.heap_size > 0) {
/* This romfs will build out of dynamic heap. */ /* This romfs will build out of dynamic heap. */
g_building_from_dynamic_heap = true; g_building_from_dynamic_heap = true;
if (g_dynamic_heap_address == 0) { g_dynamic_app_heap.Map();
/* NOTE: Lock not necessary, because this is the only location which do 0 -> non-zero. */
/* Map application memory as heap. */ if (g_dynamic_sys_heap.heap_size > 0) {
R_ABORT_UNLESS(os::AllocateUnsafeMemory(std::addressof(g_dynamic_heap_address), g_dynamic_heap_size)); g_dynamic_sys_heap.Map();
AMS_ABORT_UNLESS(g_dynamic_heap_address != 0);
/* Create exp heap. */
util::ConstructAt(g_dynamic_heap, reinterpret_cast<void *>(g_dynamic_heap_address), g_dynamic_heap_size);
} }
} }
} }
void ReleaseDynamicHeap() {
std::scoped_lock lk(g_release_dynamic_heap_lock);
if (g_dynamic_heap_address != 0) {
util::DestroyAt(g_dynamic_heap);
g_dynamic_heap = {};
R_ABORT_UNLESS(os::FreeUnsafeMemory(g_dynamic_heap_address, g_dynamic_heap_size));
g_dynamic_heap_address = 0;
}
}
void FinalizeDynamicHeapForBuildRomfs() { void FinalizeDynamicHeapForBuildRomfs() {
/* We are definitely no longer building out of dynamic heap. */ /* We are definitely no longer building out of dynamic heap. */
g_building_from_dynamic_heap = false; g_building_from_dynamic_heap = false;
if (g_dynamic_heap_outstanding_allocations == 0) { g_dynamic_app_heap.TryRelease();
ReleaseDynamicHeap();
}
} }
} }
@ -110,10 +191,15 @@ namespace ams::mitm::fs {
AMS_UNUSED(type); AMS_UNUSED(type);
if (g_building_from_dynamic_heap) { if (g_building_from_dynamic_heap) {
void * const ret = util::GetReference(g_dynamic_heap).Allocate(size); void *ret = g_dynamic_app_heap.Allocate(size);
AMS_ABORT_UNLESS(ret != nullptr);
++g_dynamic_heap_outstanding_allocations; if (ret == nullptr && g_dynamic_sys_heap.heap_address != 0) {
ret = g_dynamic_sys_heap.Allocate(size);
}
if (ret == nullptr) {
ret = std::malloc(size);
}
return ret; return ret;
} else { } else {
@ -125,12 +211,13 @@ namespace ams::mitm::fs {
AMS_UNUSED(type); AMS_UNUSED(type);
AMS_UNUSED(size); AMS_UNUSED(size);
if (IsAllocatedFromDynamicHeap(p)) { if (g_dynamic_app_heap.TryFree(p)) {
--g_dynamic_heap_outstanding_allocations; if (!g_building_from_dynamic_heap) {
util::GetReference(g_dynamic_heap).Free(p); g_dynamic_app_heap.TryRelease();
}
if (!g_building_from_dynamic_heap && g_dynamic_heap_outstanding_allocations == 0) { } else if (g_dynamic_sys_heap.TryFree(p)) {
ReleaseDynamicHeap(); if (!g_building_from_dynamic_heap) {
g_dynamic_sys_heap.TryRelease();
} }
} else { } else {
std::free(p); std::free(p);
@ -916,20 +1003,12 @@ namespace ams::mitm::fs {
R_SUCCEED_IF(!is_application); R_SUCCEED_IF(!is_application);
/* First, we need to ensure that, if the game used dynamic heap, we clear it. */ /* First, we need to ensure that, if the game used dynamic heap, we clear it. */
if (g_dynamic_heap_size > 0) { if (g_dynamic_app_heap.heap_size > 0) {
mitm::fs::FinalizeLayeredRomfsStorage(g_dynamic_heap_program_id); mitm::fs::FinalizeLayeredRomfsStorage(g_dynamic_heap_program_id);
/* This should have freed any remaining allocations. */
AMS_ABORT_UNLESS(g_dynamic_heap_outstanding_allocations == 0);
/* Free the heap. */ /* Free the heap. */
if (g_dynamic_heap_address != 0) { g_dynamic_app_heap.Reset();
ReleaseDynamicHeap(); g_dynamic_sys_heap.Reset();
}
/* Clear the heap globals. */
g_dynamic_heap_address = 0;
g_dynamic_heap_size = 0;
} }
/* Next, if we aren't going to end up building a romfs, we can ignore dynamic heap. */ /* Next, if we aren't going to end up building a romfs, we can ignore dynamic heap. */
@ -940,10 +1019,11 @@ namespace ams::mitm::fs {
/* Next, set the new program id for dynamic heap. */ /* Next, set the new program id for dynamic heap. */
g_dynamic_heap_program_id = program_id; g_dynamic_heap_program_id = program_id;
g_dynamic_heap_size = GetDynamicHeapSize(g_dynamic_heap_program_id); g_dynamic_app_heap.heap_size = GetDynamicAppHeapSize(g_dynamic_heap_program_id);
g_dynamic_sys_heap.heap_size = GetDynamicSysHeapSize(g_dynamic_heap_program_id);
/* Set output. */ /* Set output. */
*out_size = g_dynamic_heap_size; *out_size = g_dynamic_app_heap.heap_size;
R_SUCCEED(); R_SUCCEED();
} }

View file

@ -259,8 +259,8 @@ namespace ams::pm::impl {
if (mitm_boost_size > 0 || is_application) { if (mitm_boost_size > 0 || is_application) {
R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(mitm_boost_size)); R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(mitm_boost_size));
ON_RESULT_FAILURE_2 { R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(0)); };
} }
ON_RESULT_FAILURE_2 { if (mitm_boost_size > 0 || is_application) { R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(0)); } };
/* Ensure resources are available. */ /* Ensure resources are available. */
resource::WaitResourceAvailable(std::addressof(program_info)); resource::WaitResourceAvailable(std::addressof(program_info));

View file

@ -33,7 +33,7 @@ namespace ams::pm::resource {
constexpr size_t ReservedMemorySize600 = 5_MB; constexpr size_t ReservedMemorySize600 = 5_MB;
/* Atmosphere always allocates extra memory for system usage. */ /* Atmosphere always allocates extra memory for system usage. */
constexpr size_t ExtraSystemMemorySizeAtmosphere = 24_MB; constexpr size_t ExtraSystemMemorySizeAtmosphere = 32_MB;
/* Desired extra threads. */ /* Desired extra threads. */
constexpr u64 BaseApplicationThreads = 96; constexpr u64 BaseApplicationThreads = 96;