Unbacked pages of a PRT range are anonymous zero pages, so a failed allocation puts those back with _zeroPrt instead of leaving the range reserved and unreadable.
_ammAllocatePrt took pool pages one at a time and mapped and recorded them run by run, so a failure on run k lost the pool pages of runs k to n. It now maps the unbacked part through AmmPool::MapScattered and returns the range to the reservation if that throws, and the unused _takePoolPages is gone.
Move the pool bookkeeping into AmmPool.hpp. A map from a fragmented pool now plans its pages without changing the pool, stages the page and reference records in their own maps, maps the runs, and only then splits the pool and moves the records in with no allocation, so a failed map leaves the pool and the records as they were. Adding a page inserts its reference record first and undoes it if the page record throws.
_ammMap took one contiguous run of the direct memory given to the mapper and
threw (an uncaught runtime_error -> abort) when the pool was fragmented:
PPSA03671 aborted ~4 min into a slot-4 load on an 11 MB map ("AMM: no free run
of 11010048 bytes"). The PS5 maps the mapper's pages through page tables, so a
fragmented pool still serves the map: when no run fits, the map now takes
pages from several runs (first fit) and maps each contiguous group. Run hww8:
the 11 MB map and several 10-33 MB maps went through, no abort.
AmmSubmit_nid_no_patch stored the id _submit returned into lastSubmit
after _submit had released the queue's submit lock. Two submitters could
store out of order: the later id was stored first, then the earlier one
overwrote it, and a wait for the later, already submitted id failed with
ESRCH until the next submission.
The wait now checks the id against the AMM queue's own submitted count,
which _submit raises under the queue mutex before it returns, and
lastSubmit goes.
The test has four threads submit empty AMM buffers while another waits
on the newest id any of them has published, through
sceKernelWaitCommandBufferCompletion and
sceAmprAmmWaitCommandBufferCompletion, and requires every wait to
succeed.
The range, direct offset and protection checks of the AMM map, unmap,
remap, multimap and protection commands, and the records they build,
move from libSceAmpr's Export.cpp into the APR command buffer header
that libSceAmpr and libkernel already share. libSceAmpr records the
same bytes and returns the same errors in the same order; the kernel
writers can now encode the same records without a second copy.
Windows APR reads are staged through a 64 KiB std::array on the stack of
the thread that submits them (f7953ecac). Titles submit reads from fibers
whose stacks they allocate themselves, which can be smaller than that and
have no guard page, so the array runs past the bottom of the fiber stack
and the file data lands on whatever lies below it. Demon's Souls
(PPSA01341) then crashes in scePthreadMutexLock about half a second after
start, on a mutex that holds bytes of playgo-chunkdefs.xml.
The staging buffer is now allocated for each read, at most 64 KiB. The
new guest_apr_read_stack test reads on a thread with a 64 KiB stack; with
the stack array it ends in a stack overflow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Copy bounded host reads into the destination in user mode so demand-committed pages can be handled by the process. Preserve short-read validation, zero-fill at EOF and HostWrite protection checks. Add synthetic read and Windows page-fault regressions. AI-assisted: yes.
_wstat64, the FindFirstFileExW listing and std::filesystem::file_size
report the size of a file symbolic link itself (0). stat therefore gave
0 for linked files, and APR resolved them as empty, so Unity treated a
linked globalgamemanagers as empty and never read it.
stat takes the size from the handle it already opens, which follows the
link. The APR listing skips reparse points, and names missing from it
are sized through stat.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
One queue for APR and AMM buffers kept an AMM buffer behind an APR buffer
waiting on an address that the AMM buffer writes, so neither ran.
Marvel's Wolverine does this: an APR buffer waits on a counter that the
WriteAddress commands of later AMM buffers advance, and its loading
stopped. APR and AMM buffers now keep their order within their own queue.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The AMM ranges were reserved at the last 64 GiB of the guest arena and
the reservation threw when that window was not free. On Windows the
stacks of the threads that exist before the arena is reserved (the main
thread and the loader's workers) are placed at random addresses and are
left in the arena as host regions; when one fell in the last 64 GiB, the
first AMM use threw "fixed guest mapping ... overlaps a host region in
the guest arena". Reserve the highest free 64 GiB window instead: the
last 64 GiB when it is free, and the window just below an occupied part
of it otherwise.
sceKernelAprSubmitCommandBuffer ran the whole buffer on the submitting
thread, so a WaitOnAddress or WaitOnCounter that the submitter itself
satisfies after the submit returned never returned. A buffer now runs on
the submitting thread until such a wait; the rest of it, and every APR or
AMM buffer submitted after it, runs in order on one APR thread.
sceKernelAprWaitCommandBuffer and sceAmprAmmWaitCommandBufferCompletion
wait for that completion.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wu3nuU87RJwjkkmxqft3sD
The AMM ranges were reserved address-less on first use, so they took the
lowest free address of the arena, directly after the title's last mapping,
where the title's next hinted maps go. They are now reserved at a fixed
place, the last 64 GiB of the guest arena, which maps without MAP_FIXED and
the hints derived from them do not reach; a reservation that cannot be made
there throws.
Fixes#3146
AJM's instance and batch tables and APR's file, directory and AMM
state were namespace-scope statics, destroyed by std::exit while the
title's threads keep calling the libraries. A decoder thread then read
a freed instance (an unknown codec), sceAjmBatchWait no longer found
its batch, and APR resolved file ids through a freed vector. The state
is now allocated once and never destroyed.
sceKernelAprSubmitCommandBuffer rejected a buffer object with EINVAL
unless sceAmprAprCommandBufferConstructor had tagged it. The tag is
AnyPS5's own: in the retail layout the type word only carries state
bits and the constructors zero the object, so a zero-filled object
bound with SetBuffer is a valid APR buffer.
CRI's file system in Sonic Origins (PPSA05324) lays out its
AprCommandBuffer objects in its own zeroed work memory and never calls
the constructors. Its first read submit failed with 0x80020016, and the
CRI file system stopped there.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AMPR events were registered and raised as EVFILT_USER. Titles that
dispatch their AMPR queue by filter ignore those events, so reads that
complete through WriteKernelEventQueue never finish. Add, delete and
trigger them with EVFILT_AMPR (-25).
sceAmprAmmCommandBufferMapAsPrt, AllocatePaForPrt, RemapIntoPrt and
UnmapToPrt, with the MapAsPrt and AllocatePaForPrt measures. Signatures,
checks and their order follow ampr_emu.
The executor keeps the PRT pages of the AMM ranges. Unbacked PRT pages
are zero-filled with GPU read access; AllocatePaForPrt backs each
unbacked page with a pool page and changes the type and protection of
backed ones; RemapIntoPrt moves a mapped range into a PRT range; and
UnmapToPrt returns the pages to the pool and zero-fills them again. A
plain map, unmap or remap over a PRT range ends it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
libSceAmpr gains sceAmprAmmCommandBufferRemap, MultiMap, ModifyProtect
and ModifyMtypeProtect with their GPU mask forms and the eight measure
functions. Signatures and the protection and mask checks follow ampr_emu.
The executor keeps a page table of the AMM ranges: each page's physical
offset, protection, memory type and whether it came from the pool. A
remap moves the pages of a fully mapped range and leaves the old range
reserved; a multimap maps the same pages again; a pool page returns to
the pool only when its last mapping goes. A protection change applies
the masked bits page by page.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
libSceAmpr gains the AMM command buffer (constructor, destructor, map,
map direct and unmap, with their GPU mask forms and measures),
sceAmprAmmGiveDirectMemory, sceAmprAmmGetVirtualAddressRanges, the three
submit forms and sceAmprAmmWaitCommandBufferCompletion. The APR
MapBegin, MapDirectBegin and MapEnd commands and their measures record
the same map commands.
The libkernel executor now maps: a plain map takes pages from the direct
memory given to the mapper with usage 1, a direct map uses the given
offset, and an unmap returns the pages to that pool and leaves the range
reserved. The AMM ranges are reserved in the guest arena on first use.
Commands are walked by the buffer's byte offset so an AMM submission can
run raw command memory.
Signatures, the protection mask and the APR map rules follow ampr_emu;
the mapper model follows PS5PCEM. While an APR map region is open, a
second begin and every completion write return EPERM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceAmprAprCommandBufferReadFileGather, ReadFileScatter,
ReadFileGatherScatter and ResetGatherScatterState, with their measure
functions. Signatures, argument checks and the read cursor follow
ampr_emu: each read leaves the file, the next offset and the next
destination; a gather reads at a new offset into the next destination, a
scatter continues in the file into a new destination, and a
gather-scatter takes both. Recording one needs a file read since the last
reset, tracked in the command buffer object next to its type.
sceAmprAprCommandBufferReadFile now applies the same length, offset and
destination checks and rejects a null command buffer instead of
dereferencing it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceAmprCommandBufferWaitOnCounter_04_00 and WriteCounter_04_00 take the
counter access size and offset, the wait mask and the write operation,
with their measure functions; ConstructNop records a nop with an optional
leading word and a payload of up to 60 bytes, and ConstructMarker records
the set, push and pop markers by type. Signatures and checks follow
ampr_emu.
The executor now reads and writes counters by access: the 8-byte access
of counter n spans n and n + 1, the 2- and 1-byte accesses a half or a
byte of the counter. Waits compare at the access width, so the signed and
wrapped compares see the field's own sign bit, and the atomic writes wrap
within the field. The legacy counter commands record a 4-byte access.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceAmprCommandBufferWaitOnAddress took (address, reference, mask,
compare) and WaitOnCounter (counter, reference, compare), and both read
the compare value as WAIT_REG_MEM (0 always, 1 <, 2 <=, 3 ==, ...).
ampr_emu, which replaces libSceAmpr for retail titles on the console,
has (address, reference, compare, flush) and (counter, reference,
compare, flush) with the SCE compare values (0 ==, 1 > unsigned,
2 < unsigned, 3 !=), so waits were decoded as other comparisons and
could block forever or pass early.
WriteAddressFromCounterPairOnCompletion takes one counter index, not
two, and WriteKernelEventQueueOnCompletion an int id and no user data.
Counter indices above 127, compare values above 3, flush values above 1
and misaligned wait addresses return EINVAL, as in ampr_emu.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceAmprCommandBufferPushMarker, PushMarkerWithColor, PopMarker, SetMarker
and SetMarkerWithColor record a marker command, and the matching
sceAmprMeasureCommandSize functions return its size. The APR executor
skips markers, as it does a nop.
Signatures follow the ampr_emu reimplementation of libSceAmpr.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign-in, web browser and player dialogs finish cancelled; the message dialog finishes at once;
NpAuth, entitlement consumption, reachability, account age and the NpUtility bandwidth test
report signed out; sceKernelMtypeprotect changes the protection (the memory type has no host
equivalent); the APR ForEach resolvers resolve every path and report each failure in its own
result entry. vswprintf's stub is dropped in favor of the existing wide-formatter
implementation, which already covers it on every platform.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Claude Sonnet 5 <[email protected]>
A file read into guest memory is a host write: before the I/O it pins the shared pages it
covers, which reopens any page armed for write tracking and keeps write tracking from arming
it again until the read ends, when the pages count as written. A destination the guest cannot
write fails with EFAULT before any I/O; read and pread throw as for any failed read, AIO
reports SCE_KERNEL_ERROR_EFAULT, and APR throws. APR also throws when a read ends before the
requested size.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>