A bundled libc.prx can export the ten allocator NIDs while forwarding
them to HLE, so adopting its exports for a title whose replacement
table is empty sends HLE allocations back into themselves. Keep the
default heap in that case, as before the guest SDK adoption.
scriptingGetMem and scriptingFreeMem went through the title's registered
allocator. A title that ships its own libc.prx has already initialized that
allocator, and the application heap's lazy initialization then ran its
initializer a second time: Metal Gear Solid Master Collection (PPSA18194)
started a second Unity heap and aborted when it freed a block of the first.
The hooks now allocate and free through libc's guest heap, so they never
initialize the registered allocator; sizes that overflow return null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ship of Harkinian (PPSA99320) logs its heap use with malloc_stats_fast
every 10 s and stopped on the first call, since only an application
allocator could answer it. The default heap now keeps its system and
in-use byte counts with their peaks and fills the same managed-size
block as sceLibcMspaceMallocStatsFast, whose layout moves to a shared
header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the registered application allocator and the source-backed alignment/size signature. Add callback ownership and failure checks; record unmeasured console behavior.
realloc(NULL, 0) returned NULL without allocating anything, while malloc(0) returns a unique pointer. C11 7.22.3.5p3 says realloc with a null pointer behaves like malloc for the specified size, so code that grows an empty buffer from zero elements (p = realloc(p, n * size) with n == 0) and treats NULL as out of memory failed on its first call.
FreeBSD 11.4 never returns NULL from realloc for a zero size; je_realloc rounds the size up before choosing between resizing and allocating (contrib/jemalloc/src/jemalloc.c, je_realloc).
A null pointer with size 0 now reaches the allocator's realloc, which allocates as malloc(0) does. A non-null pointer with size 0 is still freed and NULL returned, as before.
Co-authored-by: milescoviello <kitslayer29@gmail.com>
A title that replaces the allocator through its libc parameters also
supplies malloc_stats_fast in that table, and PPSA28997 calls it. It is
now forwarded to the title's function. Without a replacement allocator
that provides it, the call throws.
The finalizer was registered with std::atexit from the heap handshake, which
runs after libc's own __cxa_atexit runner and the title's exit callbacks
register theirs. std::atexit runs handlers in reverse registration order, so
the heap was torn down first: an exit user that allocated then threw
"application heap: allocator has been finalized" out of a destructor path,
which terminates the process.
Register the finalizer from a namespace-scope object instead, so it is
registered when libc is loaded and therefore runs last, and make finalize()
idempotent rather than throwing on a second pass.
application_heap_exit_order registers a guest exit callback that allocates
through the heap and aborts with "application heap: allocator has been
finalized" on the previous code.
SceLibcMallocReplace lists calloc before realloc (malloc, free, calloc,
realloc, memalign, reallocalign, posix_memalign, ...), as the table in
retail titles shows. realloc therefore called the application's calloc
with the old pointer as the element count, and calloc called realloc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>