mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-01 23:49:44 +08:00
5ee7cd1dfafdeb0ce0e458a365692df4b2e1c445
111 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea9be7484f |
AGC: correct GS program registers, stop dropping rect-list draws, add a missing size export (#734)
Three defects found while bringing up a PS5 title. None are title-specific. SPI_SHADER_PGM_LO_GS / HI_GS were 0x8A/0x8B, which are actually SPI_SHADER_PGM_RSRC1/RSRC2_GS. Reading them as an address produced a 58-bit value (observed live: 0x30004622C008300). The correct offsets are 0x88/0x89, consistent with SPI_SHADER_PGM_CHKSUM_GS = 0x80 and SPI_SHADER_PGM_LO_ES = 0xC8 already in the table. Draw translation dropped any RECT_LIST draw whose vertex program exported no parameters while the pixel shader had interpolated inputs. A disposition census over a real run measured this deleting ~620 of every 5000 draws (12%). Rect lists are what AMD drivers emit for clears, blits and resolves, so the guard was deleting clears and leaving previous frame contents on screen as trails. Rendering a draw whose interpolants are undefined is strictly better than deleting it. sceAgcDcbDrawIndexIndirectMultiGetSize was unimplemented while sceAgcDcbDrawIndexIndirectMulti emits an eight-dword packet. A title that sizes its command buffer from the missing export under-reserves by three dwords. NID derived with Ps5Nid.Compute, which reproduces the committed NIDs of the neighbouring exports. Tests: AgcContextRegisterTests and AgcShaderStageRegisterTests drive real PM4 packets through sceAgcDriverSubmitDcb and assert what the parser retained for the context and SH register dictionaries, via two new internal accessors; neither dictionary had any test surface, which is why register questions previously cost five-minute game runs. |
||
|
|
7c9740fee8 | [GUI] Add grid settings to GUI and implement grid snapping (#729) | ||
|
|
a7ec3d5a77 |
[GUI] Add inline per-game settings and unify options styling (#728)
* [GUI] Add inline per-game settings and unify options styling * [GUI] Update options styling & add scrollable area * [GUI] Remove focus and pointover styles for options |
||
|
|
97bd8c422e |
Combined: Pad, AGC, Metal, Vulkan, Ampr, overlay, and CPU hot-path fixes (#727)
* VideoOut: track guest-flip FPS and label load/stall gaps Headline FPS follows VideoOut submit cadence rather than host presents, and long gaps show LOAD/STALL instead of a stale multi-second MS average. * Metal: skip waitUntilCompleted when the command buffer is already Completed Tiny write-back batches often finish before the wait; checking status avoids redundant ordered-queue round-trips. * Metal: opt-in long-edge drawable cap via SHARPEMU_METAL_CAP_DRAWABLE Default presentation resolution is unchanged; set SHARPEMU_METAL_CAP_DRAWABLE=1 to cap the drawable long edge at 1920. * Pad: implement scePadGetTriggerEffectState under its own NID NID znaWI0gpuo8 was mapped to sceUserServiceGetUserName as a "title-captured alias". It is not that symbol. Recomputing the NID of every catalogued name (base64 of the reversed first eight sha1 bytes of name+salt) resolves znaWI0gpuo8 to scePadGetTriggerEffectState, and sceUserServiceGetUserName hashes to 1xxcMiGu2fo instead. Auditing all 1087 export declarations the same way found this to be the only NID whose declared name is wrong. The consequence was not a missing export but a wrong one: the user-service handler rejected the pad's arguments and returned SCE_USER_SERVICE_ERROR_INVALID_PARAMETER about eighteen thousand times per run in PPSA10112, so every poll fell back to a cached button bitmask. It also hid the calls from every search for pad activity, which is why this title was believed never to touch scePad at all. The state size is taken from the caller's own frame rather than assumed: the guest points the out-param at rbp-0x30 and stores its stack cookie at rbp-0x28, leaving eight bytes for the state. Writing the sixteen the frame superficially suggests would land on the cookie and fail the guest's stack check - the same failure this codebase has already hit three times from oversized HLE writes - so the test pins the size by asserting the cookie survives. No host pad exposes DualSense adaptive-trigger feedback, so the neutral all-zero state is reported as success, which lets the caller take its normal path instead of the fallback. * Apply SDWA ABS/NEG as float sign-bit, not integer, modifiers SDWA's ABS and NEG source modifiers are floating-point sign-bit operations on GCN: ABS clears the sign bit, NEG flips it. We applied them as integer operations instead - SAbs, and a two's-complement negate of the raw bit pattern. That turns 1.0 into -4.0 and -3.0 into 1.5. UE4 compiles the final line of DrawRectangle, OutPosition.xy *= float2(1,-1), into a single V_MOV_B32 with SDWA NEG, so every UE fullscreen pass had its clip-space Y silently skewed. The canonical fullscreen triangle (1,-1) (-3,-1) (1,3) became (1,-4) (-3,-4) (1,1.5), which covers 6/11 of the viewport instead of all of it. That reproduces the measured defect exactly, on four independent quantities: hypotenuse slope 8/11, crossings of y=+1 and y=-1 at x=+7/11 and x=-9/11, and covered area 6/11 = 54.55% of the 2304x1296 viewport. It also explains why the edge was resolution-independent and identical across six unrelated shaders - it is the same instruction in every one of them. The distinguishing evidence is the transform's fixed point. A wrong scale would hold NDC -1 in x and +1 in y; the observed transform holds the opposite corner in both. Independently, and using only the measured line rather than any assumed vertex position: a wrong multiplier alone leaves a residual of -18 whatever the multiplier, and a wrong addend alone forces slope 1, not 8/11. Both terms had to be wrong at once, which only a Y-only sign-bit corruption produces. Float instructions are unaffected: GetFloatSource passes applySdwaIntegerModifiers: false and applies its own modifiers, so this path only ever fed raw-source reads - where the hardware behaviour is the sign-bit one regardless of the opcode being a bit-move. The sign bit is selected by the SDWA source-select width so a 16-bit select flips bit 15 rather than bit 31. Verified: emitted SPIR-V for the same shader changes from OpISub %uint %uint_0 %2147 to OpBitwiseXor %uint %2147 %uint_2147483648; 25-program synthetic conformance gate passes; 805 tests green; three 100-110s live runs with no crashes and no new shader failures. The end-to-end pixel re-measurement is NOT yet closed - see task #26. * gpu: size single-channel 16-bit and two-channel 8-bit formats GetFormatCompatibilityClass listed only R16Sfloat in the 16-bit class, so GetVulkanImageByteCount computed zero bytes for R16Unorm, R16SNorm, R16Uint, R16Sint and the R8G8 family. UploadGuestImageInitialData treats a zero expected size as an incompatible upload and drops it, which leaves the texture blank for the rest of the run rather than failing loudly. Silent Hill uploads R16Unorm at 144x81, 240x135, 256x256, 512x512 and 1024x1024, and every one was rejected: the guest supplied exactly width*height*2 bytes each time (23328 for 144x81) against an expected zero. Also adds R8SNorm to the 8-bit class, which was missing for the same reason. * Vulkan: enable textureCompressionBC when the device supports it Guest BC1–BC7 textures can be sampled directly when the feature is available; warn when it is not. * Share one guest image across sRGB/UNORM aliases Ported from origin/fix/view-compatible-guest-images 7fb8fdf. Rendering as sRGB and ImageLoad/Store-ing as UNORM at the same guest address are the same surface accessed through different number formats. Recreating the guest image per number format ping-pongs content between two VkImages and loses the rendered pixels on every transition; the mutable-format image now accepts the counterpart identity and serves it through alias views. The commit names AvPlayer movie copies as the pattern that needs this. Adapted for this base: GetOrCreateGuestImage has since grown resolution scaling and 3D/array support, so the alias accept is folded into the current predicate (LogicalWidth/LogicalHeight/LogicalDepth/Type) rather than the old Width/Height pair, and the storage-counterpart widening is placed before the physical-dimension computation. The helper functions it relies on (GetStorageImageFormat, IsCompatibleViewFormat) already existed. * fix(gpu): one vertex attribute per guest stream view The scalar evaluator gave every buffer_load_format instruction its own attribute location. Two things multiply those: the CFG walk visits one instruction on several paths, and an uber vertex shader fetches the same stream from every material branch. UE's larger shaders reached 56 bindings from 8 distinct views, and one reached 701 from 5. Metal caps a vertex function at 31 attributes, so MoltenVK failed the MSL compile with "'attribute' attribute parameter is out of bounds" and the surrounding vkCreateGraphicsPipelines returned ErrorInitializationFailed. Every draw using those pipelines was dropped, which is why Silent Hill: The Short Message rendered a black scene. The vertex buffer count drove Metal's buffer indices out of range too, giving the companion "cannot reserve 'buffer' resource location at index 0" failures. Key attributes by the guest stream view they read - absolute element address, record stride and format - and alias every other fetch that resolves to the same view onto that binding, so both translators map those instruction PCs to one input variable. On PPSA10112 this takes the worst shader from 56 attributes to 8 and pipeline failures from 840 to 0. * AGC: deliver compute-queue completion events at the queue fence sceAgcDriverSubmitAcb submissions never produced a completion interrupt. NotifySubmittedDcbCompleted returned early for anything that was not the graphics queue, so an ACB reaching its ordered-queue fence published nothing. UE 4.27's dynamic-resolution GPU-timing heuristic parks the game thread on exactly that interrupt, so the render side never advanced and Silent Hill: The Short Message deadlocked after its first frame. Give every queue a CompletionEventId: 0 for graphics (what it already published) and the owner handle from sceAgcDriverSubmitAcb rdi for a compute queue, which is the same value the guest passes to sceAgcDriverAddEqEvent. Publish under that ident from the existing fence point, which both PumpSubmittedQueue and ResumeSuspendedDcb already reach only after the submission is fully parsed. Delivery is synchronous on the ordered guest-action queue rather than on a ThreadPool hop with a sleep. That action runs after the logical queue has flushed and waited for its latest fence, which is the moment hardware would raise end-of-pipe. Deferring past it can only make the interrupt late and reorder it against registration changes. Gating: the per-queue completion event is unconditional, because completion interrupts do fire on real hardware and because delivery is registration-gated -- TriggerRegisteredEvents only queues onto equeues that registered this exact (ident, graphics filter) pair, and sceAgcDriverAddEqEvent is the only producer of graphics registrations. A title that never registers its ACB owner handle observes no change. SHARPEMU_AGC_SUBMIT_COMPLETION_EVENT is left to gate only the broad ident-ignoring fan-out (TriggerRegisteredEventsDistinct), which is a compatibility guess rather than hardware behavior; it also stays scoped to the graphics queue where it was measured, so enabling the flag does not newly fan out across compute queues. * fix(gpu): reflect guest CPU writes into large and render-target-aliased images Ports the still-applicable half of the archived fork's stale-texture fix (a42ccae) onto current main. Silent Hill: The Short Message (PPSA10112) shows both faces: black title-screen UI (glyph atlas frozen at its first upload) and stale/garbage rows on the brightness screen (a 3840x2160 UI sheet whose backing bytes were never re-read). 1. Write tracking is armed under a byte budget, not a resolution cap. GetOrCreateGuestImage armed GuestImageWriteTracker only when target.Width <= 1920 && target.Height <= 1080. Silent Hill renders at 3840x2160, so every one of its render targets was excluded from CPU-write tracking and no guest rewrite of one could ever invalidate it: SyncCpuWrittenGuestImages (the flip / ACQUIRE_MEM re-upload path) only ever visits ranges the tracker armed. The cap presumably existed as a perf guard, but resolution is the wrong proxy for the cost. Arming is one mprotect over the range, and the fault handler unprotects the whole range on the first store, so a write burst costs one fault regardless of size. What actually scales with the surface is the dirty re-upload: one byte[byteCount] allocation plus a full guest-memory read per dirty flip. So the guard is now a byte budget, set equal to the 128 MiB limit that SyncCpuWrittenGuestImages itself enforces before re-uploading. Above that, arming can only cost faults; it can never produce a re-upload. That is generous for 4K (RGBA8 32 MiB, RGBA16F 63 MiB, RGBA32F 127 MiB all fit) while still excluding volume textures that a resolution cap could not see at all (512^3 RGBA8 is 512 MiB behind a "512x512" surface). Both sites now also arm the exact extent recorded in _guestImageExtents (GetTextureByteCount) instead of Width*Height*depth*GetTextureBytesPerPixel, so the armed range and the range the sync path reads back are the same bytes; the old expression over-counted block-compressed and unknown formats. 2. The CPU-texture refresh path no longer gates on IsCpuBacked alone. TryCreateCpuTextureRefreshResource bailed on !guestImage.IsCpuBacked. That flag is a latch: it flips false the first time an address is used as a render target and never flips back. A surface that was rendered into once and is afterwards rewritten by the guest CPU was therefore frozen at its last GPU content forever, even when the parse thread had already shipped fresh texels for it. The gate is replaced by ShouldRefreshGuestImageFromCpu: CPU-backed, or the parse-time write generation is above zero and differs from the generation recorded by the last upload. Keeping the positive- generation requirement preserves the pure GPU-feedback case (render into an image, then sample it) that IsCpuBacked used to protect: such a surface is now tracked (change 1) but never CPU-written, so its generation stays zero and its live image is left alone. This matters more than it did in the fork, precisely because change 1 arms tracking on far more render targets. Dropping the gate outright, as the fork did, would let a target sampled under a format tag that the availability map does not match be overwritten once with whatever sits in guest memory. The existing content fingerprint still suppresses redundant uploads, and MarkSampledImagesInitialized records the uploaded generation, so a rewritten surface re-uploads exactly once per guest write burst. Not ported: the fork's third part added a PeekDirty guard to the parse-time snapshot fast path in AgcExports. It is superseded. That path now calls IsGuestImageUploadKnown, which already compares _cpuBackedUploadGenerations against the tracker's write generation - a monotonic value that survives another owner consuming the dirty flag, unlike PeekDirty, which both EvictDirtyCachedTextures and SyncCpuWrittenGuestImages clear. Guest images with no generation entry are covered instead by SyncCpuWrittenGuestImages. Adding a non-consuming PeekDirty there would also make every draw between a CPU write and the next flip fall through to a full texel re-read of the surface (33 MiB for a 4K sheet), since Track re-arms without clearing the dirty flag. The fork's promotion-path Track ("vulkan.cpu-backed-image") is likewise superseded: AgcExports already arms every sampled texture's backing extent as "agc.decoded-texture" before reading its texels. Both decisions are extracted as pure internal predicates so they can be unit-tested; the Vulkan device code around them needs a real device. * Kernel: correct equeue event delivery and waiter lifetime Ports the event-queue rework from the archived silent-hill-minimal branch (968e9606, 9e3abb12, 2be9cfbf, f157a115) onto current upstream. The equeue implementation had not been touched upstream since that branch forked, so none of it had landed. Behaviour fixed: - Per-waiter event reservation. A blocked waiter used to wake on "the queue has any pending event" (TryWake => HasPendingEvents) and then re-read the queue on resume, so a woken waiter could find the event already drained by another waiter and park again with the wake consumed. Events are now reserved to the waiter they are delivered to. - Level-triggered events are preserved instead of being cleared by an unrelated read; only events that declare clear-on-read reset their trigger state. - Queued interrupts are bound to the registration generation that produced them, so an event registered after a queue was reused cannot consume an interrupt raised for the previous registration. - Deleting an equeue now terminates its waiters instead of leaving them blocked on a handle that no longer resolves. - sceKernelTriggerUserEvent stores its third argument in the event's udata (0x18) rather than its data word (0x10). The guest reads it back with sceKernelGetEventUserData, which loads 0x18, so every triggered user event previously read back as 0. This matches the reference behaviour in shadPS4, where TriggerEvent takes udata and sceKernelGetEventUserData returns ev->udata. The upstream test asserting the data word is updated, since it encoded the inconsistency rather than the ABI. KernelPthreadState gains TryGetCurrentThreadIdentity and a new KernelSyncTraceFormatter carries the shared, opt-in diagnostic formatting the ported code calls; both are gated behind the existing trace flag and do no work when it is off. Tests: 662 pass, 0 fail (SharpEmu.Libs.Tests 588 -> 598). * Implement VOP3 0x149 V_BFE_I32 and 0x36A V_CVT_PKRTZ_F16_F32 PPSA10112 dropped three shaders per run on two unimplemented VOP3 opcodes. The translator fails a whole shader on an opcode it does not know, so each one costs a dropped draw rather than wrong pixels. 0x149 sits between 0x148 V_BFE_U32 and 0x14A V_BFI_B32, and 0x36A between 0x369 V_CVT_PKNORM_U16_F32 and 0x36D V_ADD3_U32; the surrounding table entries already match the canonical map densely on both sides of each gap. V_CVT_PKRTZ_F16_F32 needed no emitter - it was already implemented for the VOP2 form at 0x2F and its VOP3 alias at 0x12F, and only the VOP3-only opcode number was missing. V_BFE_I32 mirrors its unsigned sibling, masking offset and width to 5 bits, and differs only in extracting through a signed type so the field sign-extends. GCN defines width 0 as returning 0 where SPIR-V leaves a zero Count unspecified; VBfeU32 has the same gap, so this matches it deliberately rather than diverging - fix both together if it matters. Also widen ReportGuestPointerSplit, which filtered to an incoming value of exactly 1. The crash being hunted leaves 0x0000007000000000, whose low dword is 0, so the one detector built to catch this bug class could never have reported it. Verified: unsupported-opcode errors 3-4 per run -> 0, over a 130s run that survives to the same stage. Metal translator still lacks VBfeI32; Vulkan/MoltenVK is the macOS path so it is a divergence, not a blocker. * AGC: retain active label producer records Ported from the archived silent-hill-minimal branch (274ccfdf). RegisterLabelProducer bounded its history with RemoveRange(0, 1024) once it reached 4096 entries, which evicts the oldest records regardless of whether their label write has completed. Those records are not a diagnostic cache: a suspended DCB resolves its wait_reg_mem by finding the producer that will write the watched label. Dropping an active record hides an earlier same-submission label write, so a legitimate in-stream fence suspends forever and the graphics queue stops. Compact only completed history, and let the list exceed the soft bound when every record is still active — correctness over a diagnostic limit. Two hardening changes over the ported version, because a suspended queue is exactly when every record is active: - compaction is a single order-preserving pass instead of repeated RemoveAt, which would shift the tail per eviction (quadratic) while the label gate is held; - when a pass frees nothing the bound is raised to twice the current count, so registration does not rescan the whole list on every subsequent add, and it is reset once compaction can make progress again. Tests: 664 pass, 0 fail (SharpEmu.Libs.Tests 598 -> 600). * AGC: keep produced label values a suspended wait still needs GpuWaitRegistry.RecordProduced dropped the entire produced-value table once it reached 8192 entries: if (_lastProduced.Count >= 8192) { _lastProduced.Clear(); } Those entries are release state, not a cache. CollectDeadlockBroken is the only way out for a DCB suspended on a WAIT_REG_MEM whose label the guest has since recycled for other data: it replays the value a real producer already wrote to that label. Clearing the table wholesale erases exactly the records live waiters depend on, so every such waiter is stranded permanently — the suspended graphics queue never resumes, and with it the render thread and the whole engine thread graph park. That is what Silent Hill (PPSA10112) hits. Its final unsatisfied waits watch labels holding guest allocator metadata (each reads a pointer into its own region rather than the expected 1), i.e. recycled memory, which is precisely the case the deadlock breaker exists to handle — yet agc.deadlock_break fires 0 times across a run with 490 wait suspensions and 1068 successful release_mem writes, because the producing values had already been cleared. Prune only values no registered waiter is watching, and let the table exceed the soft bound while they are all watched. Same reasoning as the label-producer history in the preceding commit: correctness of synchronization state takes precedence over a diagnostic bound. Tests: 666 pass, 0 fail. The regression test fails against the previous wholesale Clear() and passes with the prune, verified by reverting the fix. * Correct VOP3 0x36A: V_CVT_PK_U16_U32, not V_CVT_PKRTZ_F16_F32 a2d186e mapped 0x36A to V_CVT_PKRTZ_F16_F32 on the strength of a gap in our own opcode table. That was wrong, and wrong in the worst available way: an emitter for V_CVT_PKRTZ_F16_F32 already existed, so instead of failing loudly like an unknown opcode, the mapping would have emitted float-pack semantics for an integer-pack instruction and produced silently incorrect results. LLVM is unambiguous: defm V_CVT_PK_U16_U32 : VOP3Only_Real_gfx10<0x36a>; defm V_CVT_PKRTZ_F16_F32 : VOP2_Real_gfx6_gfx7_gfx10<0x02f>; so on Gen5 the float pack is VOP2 0x2F with VOP3 alias 0x12F - both of which our table already had - and there is no VOP3-only encoding of it to add. 0x36A sits with the other integer/normalised pack conversions at 0x368/0x369/0x36B. silent-hill-minimal-rebased had this right all along. The V_BFE_I32 half of a2d186e stands: LLVM confirms 0x149, and that branch maps it identically. Lesson, since I had just warned someone else about exactly this: a gap in a table is evidence about numbering, not about identity. Inference from neighbouring entries is fine for narrowing candidates and worth nothing as a conclusion - especially when a plausible emitter already exists to swallow the mistake quietly. * AGC: report release-label writes that never reach guest memory Both release_mem handlers recorded the produced label value only on success: if (wroteData && dataSelection is 1 or 2) GpuWaitRegistry.RecordProduced(...) with no else. A failed write is not benign here. That packet is the producer a suspended WAIT_REG_MEM is waiting for, so when it fails the label is neither written nor recorded, CollectDeadlockBroken has no value to replay, and the graphics queue stays suspended forever — the render thread and the engine thread graph park behind it with nothing in the log to say why. Measured on Silent Hill (PPSA10112): the fatal AccessViolation lands exactly here, on the presenter thread, in ApplySubmittedReleaseMem's label write into a protected guest page. The faulting variant kills the process; the returns-false variant wedges the GPU silently. Only the first was ever visible. Report the failure (rate-limited: first 16, then powers of two) instead of dropping it. This does not fix the underlying write failure — it makes a permanently suspended queue diagnosable rather than silent. Tests: 666 pass, 0 fail. * CPU: gate periodic import logs and fast-path memcpy/memmove leaves SHARPEMU_LOG_IMPORT_PERIODIC=1 re-enables periodic Import# tracing. memcpy/memmove use a thin TryCopy leaf that skips register marshalling. * Ampr: cooked-id index preload and host FD LRU cache Preload the app0 APR index during bind and bound the open host-file cache so streaming reads stay responsive under large title archives. * Vulkan: abandon timed-out guest fences without blocking later frames Move fence-timeout submissions out of the blocking queue, keep GPU objects alive until the fence signals, and retire them from an abandoned list so one hung dispatch cannot re-block capacity waits. * AGC: follow command-buffer branches across arena switches Implement sceAgcCbBranch and walk INDIRECT_BUFFER so submissions that continue in a linked buffer keep their flip and end-of-frame labels. |
||
|
|
93c9f14081 |
Ampr: cooked-id index preload and host FD LRU cache (#725)
Preload the app0 APR index during bind and bound the open host-file cache so streaming reads stay responsive under large title archives. |
||
|
|
816ec4ad27 |
Kernel: correct equeue event delivery and waiter lifetime (#723)
Ports the event-queue rework from the archived silent-hill-minimal branch (968e9606, 9e3abb12, 2be9cfbf, f157a115) onto current upstream. The equeue implementation had not been touched upstream since that branch forked, so none of it had landed. Behaviour fixed: - Per-waiter event reservation. A blocked waiter used to wake on "the queue has any pending event" (TryWake => HasPendingEvents) and then re-read the queue on resume, so a woken waiter could find the event already drained by another waiter and park again with the wake consumed. Events are now reserved to the waiter they are delivered to. - Level-triggered events are preserved instead of being cleared by an unrelated read; only events that declare clear-on-read reset their trigger state. - Queued interrupts are bound to the registration generation that produced them, so an event registered after a queue was reused cannot consume an interrupt raised for the previous registration. - Deleting an equeue now terminates its waiters instead of leaving them blocked on a handle that no longer resolves. - sceKernelTriggerUserEvent stores its third argument in the event's udata (0x18) rather than its data word (0x10). The guest reads it back with sceKernelGetEventUserData, which loads 0x18, so every triggered user event previously read back as 0. This matches the reference behaviour in shadPS4, where TriggerEvent takes udata and sceKernelGetEventUserData returns ev->udata. The upstream test asserting the data word is updated, since it encoded the inconsistency rather than the ABI. KernelPthreadState gains TryGetCurrentThreadIdentity and a new KernelSyncTraceFormatter carries the shared, opt-in diagnostic formatting the ported code calls; both are gated behind the existing trace flag and do no work when it is off. Tests: 662 pass, 0 fail (SharpEmu.Libs.Tests 588 -> 598). |
||
|
|
82c2c7f48c |
fix(gpu): reflect guest CPU writes into large and render-target-aliased images (#722)
Ports the still-applicable half of the archived fork's stale-texture fix
(a42ccae) onto current main. Silent Hill: The Short Message (PPSA10112)
shows both faces: black title-screen UI (glyph atlas frozen at its first
upload) and stale/garbage rows on the brightness screen (a 3840x2160 UI
sheet whose backing bytes were never re-read).
1. Write tracking is armed under a byte budget, not a resolution cap.
GetOrCreateGuestImage armed GuestImageWriteTracker only when
target.Width <= 1920 && target.Height <= 1080. Silent Hill renders at
3840x2160, so every one of its render targets was excluded from
CPU-write tracking and no guest rewrite of one could ever invalidate
it: SyncCpuWrittenGuestImages (the flip / ACQUIRE_MEM re-upload path)
only ever visits ranges the tracker armed.
The cap presumably existed as a perf guard, but resolution is the
wrong proxy for the cost. Arming is one mprotect over the range, and
the fault handler unprotects the whole range on the first store, so a
write burst costs one fault regardless of size. What actually scales
with the surface is the dirty re-upload: one byte[byteCount]
allocation plus a full guest-memory read per dirty flip. So the guard
is now a byte budget, set equal to the 128 MiB limit that
SyncCpuWrittenGuestImages itself enforces before re-uploading. Above
that, arming can only cost faults; it can never produce a re-upload.
That is generous for 4K (RGBA8 32 MiB, RGBA16F 63 MiB, RGBA32F
127 MiB all fit) while still excluding volume textures that a
resolution cap could not see at all (512^3 RGBA8 is 512 MiB behind a
"512x512" surface).
Both sites now also arm the exact extent recorded in
_guestImageExtents (GetTextureByteCount) instead of
Width*Height*depth*GetTextureBytesPerPixel, so the armed range and
the range the sync path reads back are the same bytes; the old
expression over-counted block-compressed and unknown formats.
2. The CPU-texture refresh path no longer gates on IsCpuBacked alone.
TryCreateCpuTextureRefreshResource bailed on !guestImage.IsCpuBacked.
That flag is a latch: it flips false the first time an address is
used as a render target and never flips back. A surface that was
rendered into once and is afterwards rewritten by the guest CPU was
therefore frozen at its last GPU content forever, even when the parse
thread had already shipped fresh texels for it.
The gate is replaced by ShouldRefreshGuestImageFromCpu: CPU-backed,
or the parse-time write generation is above zero and differs from the
generation recorded by the last upload. Keeping the positive-
generation requirement preserves the pure GPU-feedback case (render
into an image, then sample it) that IsCpuBacked used to protect: such
a surface is now tracked (change 1) but never CPU-written, so its
generation stays zero and its live image is left alone. This matters
more than it did in the fork, precisely because change 1 arms
tracking on far more render targets. Dropping the gate outright, as
the fork did, would let a target sampled under a format tag that the
availability map does not match be overwritten once with whatever
sits in guest memory.
The existing content fingerprint still suppresses redundant uploads,
and MarkSampledImagesInitialized records the uploaded generation, so
a rewritten surface re-uploads exactly once per guest write burst.
Not ported: the fork's third part added a PeekDirty guard to the
parse-time snapshot fast path in AgcExports. It is superseded. That path
now calls IsGuestImageUploadKnown, which already compares
_cpuBackedUploadGenerations against the tracker's write generation - a
monotonic value that survives another owner consuming the dirty flag,
unlike PeekDirty, which both EvictDirtyCachedTextures and
SyncCpuWrittenGuestImages clear. Guest images with no generation entry
are covered instead by SyncCpuWrittenGuestImages. Adding a non-consuming
PeekDirty there would also make every draw between a CPU write and the
next flip fall through to a full texel re-read of the surface (33 MiB
for a 4K sheet), since Track re-arms without clearing the dirty flag.
The fork's promotion-path Track ("vulkan.cpu-backed-image") is likewise
superseded: AgcExports already arms every sampled texture's backing
extent as "agc.decoded-texture" before reading its texels.
Both decisions are extracted as pure internal predicates so they can be
unit-tested; the Vulkan device code around them needs a real device.
|
||
|
|
531e35b6d5 |
AGC: deliver compute-queue completion events at the queue fence (#721)
sceAgcDriverSubmitAcb submissions never produced a completion interrupt. NotifySubmittedDcbCompleted returned early for anything that was not the graphics queue, so an ACB reaching its ordered-queue fence published nothing. UE 4.27's dynamic-resolution GPU-timing heuristic parks the game thread on exactly that interrupt, so the render side never advanced and Silent Hill: The Short Message deadlocked after its first frame. Give every queue a CompletionEventId: 0 for graphics (what it already published) and the owner handle from sceAgcDriverSubmitAcb rdi for a compute queue, which is the same value the guest passes to sceAgcDriverAddEqEvent. Publish under that ident from the existing fence point, which both PumpSubmittedQueue and ResumeSuspendedDcb already reach only after the submission is fully parsed. Delivery is synchronous on the ordered guest-action queue rather than on a ThreadPool hop with a sleep. That action runs after the logical queue has flushed and waited for its latest fence, which is the moment hardware would raise end-of-pipe. Deferring past it can only make the interrupt late and reorder it against registration changes. Gating: the per-queue completion event is unconditional, because completion interrupts do fire on real hardware and because delivery is registration-gated -- TriggerRegisteredEvents only queues onto equeues that registered this exact (ident, graphics filter) pair, and sceAgcDriverAddEqEvent is the only producer of graphics registrations. A title that never registers its ACB owner handle observes no change. SHARPEMU_AGC_SUBMIT_COMPLETION_EVENT is left to gate only the broad ident-ignoring fan-out (TriggerRegisteredEventsDistinct), which is a compatibility guess rather than hardware behavior; it also stays scoped to the graphics queue where it was measured, so enabling the flag does not newly fan out across compute queues. |
||
|
|
3f9bd2b92b |
AGC: follow command-buffer branches across arena switches (#720)
Implement sceAgcCbBranch and walk INDIRECT_BUFFER so submissions that continue in a linked buffer keep their flip and end-of-frame labels. |
||
|
|
eb0653eded |
fix(gpu): one vertex attribute per guest stream view (#718)
The scalar evaluator gave every buffer_load_format instruction its own attribute location. Two things multiply those: the CFG walk visits one instruction on several paths, and an uber vertex shader fetches the same stream from every material branch. UE's larger shaders reached 56 bindings from 8 distinct views, and one reached 701 from 5. Metal caps a vertex function at 31 attributes, so MoltenVK failed the MSL compile with "'attribute' attribute parameter is out of bounds" and the surrounding vkCreateGraphicsPipelines returned ErrorInitializationFailed. Every draw using those pipelines was dropped, which is why Silent Hill: The Short Message rendered a black scene. The vertex buffer count drove Metal's buffer indices out of range too, giving the companion "cannot reserve 'buffer' resource location at index 0" failures. Key attributes by the guest stream view they read - absolute element address, record stride and format - and alias every other fetch that resolves to the same view onto that binding, so both translators map those instruction PCs to one input variable. On PPSA10112 this takes the worst shader from 56 attributes to 8 and pipeline failures from 840 to 0. |
||
|
|
e1695cf87f |
Share one guest image across sRGB/UNORM aliases (#717)
Ported from origin/fix/view-compatible-guest-images 7fb8fdf. Rendering as sRGB and ImageLoad/Store-ing as UNORM at the same guest address are the same surface accessed through different number formats. Recreating the guest image per number format ping-pongs content between two VkImages and loses the rendered pixels on every transition; the mutable-format image now accepts the counterpart identity and serves it through alias views. The commit names AvPlayer movie copies as the pattern that needs this. Adapted for this base: GetOrCreateGuestImage has since grown resolution scaling and 3D/array support, so the alias accept is folded into the current predicate (LogicalWidth/LogicalHeight/LogicalDepth/Type) rather than the old Width/Height pair, and the storage-counterpart widening is placed before the physical-dimension computation. The helper functions it relies on (GetStorageImageFormat, IsCompatibleViewFormat) already existed. |
||
|
|
b572738547 |
Pad: implement scePadGetTriggerEffectState under its own NID (#712)
NID znaWI0gpuo8 was mapped to sceUserServiceGetUserName as a "title-captured alias". It is not that symbol. Recomputing the NID of every catalogued name (base64 of the reversed first eight sha1 bytes of name+salt) resolves znaWI0gpuo8 to scePadGetTriggerEffectState, and sceUserServiceGetUserName hashes to 1xxcMiGu2fo instead. Auditing all 1087 export declarations the same way found this to be the only NID whose declared name is wrong. The consequence was not a missing export but a wrong one: the user-service handler rejected the pad's arguments and returned SCE_USER_SERVICE_ERROR_INVALID_PARAMETER about eighteen thousand times per run in PPSA10112, so every poll fell back to a cached button bitmask. It also hid the calls from every search for pad activity, which is why this title was believed never to touch scePad at all. The state size is taken from the caller's own frame rather than assumed: the guest points the out-param at rbp-0x30 and stores its stack cookie at rbp-0x28, leaving eight bytes for the state. Writing the sixteen the frame superficially suggests would land on the cookie and fail the guest's stack check - the same failure this codebase has already hit three times from oversized HLE writes - so the test pins the size by asserting the cookie survives. No host pad exposes DualSense adaptive-trigger feedback, so the neutral all-zero state is reported as success, which lets the caller take its normal path instead of the fallback. |
||
|
|
5864328e35 |
Merge media decoding into one FFmpeg bridge (#706)
* [media] merge bink into shared ffmpeg bridge * [avplayer] decode in process and fix stream info size * [font] add glyph and teardown exports * [build] bump ffmpeg runtime to 3b502d4 * [font] add glyph and teardown exports * [build] bump ffmpeg runtime to 3b502d4 * [avplayer] decode in process * [font] add glyph and teardown exports * [build] bump ffmpeg runtime to 3b502d4 |
||
|
|
753ddf93be | [GUI] Update window chrome controls (#700) | ||
|
|
f08308daf2 | [GUI] Redesign options page (#699) | ||
|
|
77f22973cb |
[GUI] Update library page layout (#690)
* [GUI] Add horizontal game tiles * [GUI] Update launching elements layout * [GUI] Change cards format to squares |
||
|
|
faf49f689d | [GUI] Add live localization bindings and fill missing locales (#685) | ||
|
|
b07e4f2bc6 | [GUI] add game library watcher & remove rescan button (#678) | ||
|
|
2b6bd5a532 |
Sdl backend (#670)
* [audio] added sdl audio backend and in-tree atrac9 decoder * [input] replaced per-platform pad readers with sdl gamepad input * [video] added sdl window and host display plumbing * [gui] added host display options and per-game render settings * [bink] synced host movie playback to the guest audio clock * [cpu] hooked windows write faults into guest image tracking * [perf] added guest and render profiling, reserved host cpu lanes * [kernel] fixed stale pthread mutex handle alias * [host] wired the sdl session, save-data paths and project references * [audio] hoisted ajm trace stackalloc out of its loop * [video] Add guest image sync setting * [build] Strip native symbols * reuse |
||
|
|
db4339f698 |
fix(gta): restore wiped GTA foundation and gameplay path (PPSA04264) (#650)
* fix(kernel): implement APR ResolveFilepathsWithPrefixToIdsAndFileSizes Resource streamers resolve relative paths against a shared prefix; without this HLE every call returned NOT_FOUND and assets never got real ids/sizes. * fix(remoteplay): stub Initialize and GetConnectionStatus as disconnected Titles probe Remote Play during pad/network bring-up; unresolved imports returned NOT_FOUND. Report initialized + disconnected so callers take the normal offline path. * fix(agc): accept Gen5 hull shaders that omit PGM_LO/HI in CreateShader Type-5 headers can start with RSRC1/RSRC2; rejecting them left null handles and Main Thread AVs. Scan the SH table and skip PGM patch when absent. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(kernel): reject getdents on file fds and emit . / .. for empty dirs Returning rax=0 for non-directory or empty listings looked like EOF and let GTA treat the fd as a pointer (fiWriteAsyncDataWorker AV at 0xB1). * fix(hle): enable GuestImageWriteTracker CPU sync on Windows Windows previously hard-disabled the tracker, so CPU-written guest planes never marked dirty and host textures stayed empty. Arm pages with VirtualProtect, handle write AVs in VEH, and warm/test on VirtualAlloc memory so protect cannot poison the CRT heap. * fix(agc): skip CB metadata draws for EliminateFastClear/Fmask/DCC CB_COLOR_CONTROL modes 2/5/6 are colour-buffer metadata ops; applying the bound shader as a normal colour draw corrupts subsequent composites. Decode MODE from bits [6:4] and return before translate. * fix(agc): merge Prospero attrib-table formats onto IR vertex inputs IR-discovered BufferLoadFormat often keeps a stale float sharp format; patch DataFormat/offset from the AGC attrib table (semantic index), allow offen fetches, and map quirks 113/121 through NarrowVk for host vertex input. * fix(audio): harden AudioOut2 stack out-buffer writes against canary smash Titles that stack-allocate AudioOut2 outs next to the frame canary were corrupted by oversized or mistyped HLE writes; keep ContextPush pacing. * Revert "fix(memory): reserve only large regions (#608)" This reverts commit |
||
|
|
26c502914c |
feat(audio): implement sceAudioOutOutputs (#605)
* feat(audio): implement batched output submission * test(audio): cover batched output semantics * test(audio): cover multi-port output batches --------- Co-authored-by: diego <diego@DIGOTE-PC> |
||
|
|
a158960c20 |
feat(gpu): GPU compute detile for guest tiled textures (Vulkan + Metal) (#592)
* feat(gpu): GPU compute detile for guest tiled textures (Vulkan + Metal) Move RDNA2 exact-XOR deswizzle (swizzle modes 5/9/24/27, 4bpp) off the CPU onto a GPU compute pass. GnmTiling.GetDetileParams resolves the shared addressing into DetileParams; the CPU fallback and both GPU kernels consume the same params so they never disagree. Vulkan (verified bit-exact on NVIDIA): SpirvFixedShaders.CreateDetileCompute hand-emits the SPIR-V kernel; VulkanDetilePass.RecordDetile records the dispatch into the async batch command buffer (never a blocking submit on the render thread) with transients retired via fence; VulkanDetileSelfTest (SHARPEMU_DETILE_SELFTEST=1) checks both entry points against the CPU detile. Metal (Mac-untested): detile_compute.msl (detile_cs) + MetalDetilePass mirror the Vulkan pass. The active Metal path CPU-detiles via the new GnmTiling.DetileWithParams when a texture arrives packaged (empty RgbaPixels + TiledSource/Detile), keeping Metal correct under default-on with no regression; wiring MetalDetilePass live is the remaining on-device step. Flags: GPU detile is default-on (SHARPEMU_GPU_DETILE=0 disables); [GPU-DETILE] diagnostics gated behind SHARPEMU_LOG_GPU_DETILE=1. Tests: 17 detile unit tests pass, incl. DetileWithParams and GetDetileParams each matching TryDetile bit-for-bit across all supported modes/bpp, plus a SPIR-V structural-validity test. * feat(gpu): GPU compute detile for guest tiled textures (Vulkan + Metal) Move RDNA2 exact-XOR deswizzle (swizzle modes 5/9/24/27, 4bpp) off the CPU onto a GPU compute pass. GnmTiling.GetDetileParams resolves the shared addressing into DetileParams that the CPU fallback and both GPU kernels consume, so they never disagree; everything else keeps the CPU path. Vulkan (verified bit-exact on NVIDIA): SpirvFixedShaders.CreateDetileCompute hand-emits the kernel; VulkanDetilePass.RecordDetile records into the async batch command buffer (never a blocking submit on the render thread) with transients retired via fence, falling back to CPU detile on failure. VulkanDetileSelfTest (SHARPEMU_DETILE_SELFTEST=1) checks both entry points. Metal (Mac-untested): detile_compute.msl + MetalDetilePass mirror the Vulkan pass; the active Metal path CPU-detiles via GnmTiling.DetileWithParams so it stays correct under default-on. Wiring MetalDetilePass live is a follow-up. Flags: default-on (SHARPEMU_GPU_DETILE=0 disables); diagnostics behind SHARPEMU_LOG_GPU_DETILE=1. Adds 17 passing detile unit tests. * Fix: added support layered texture support for the GPU-Detiling. * Fix: Added support for BlockTable (1 / 4 / 8 (Morton/Z-order)) * feat: added support for 8 and 16 bpp (bytes per element) * Fixed a build failure specific to this branch --------- |
||
|
|
5228335f15 |
fix(gpu): support Gen5 flat memory and 3D images (#587)
Vector-mesh UI text samples type-10 volume LUTs; treat MIMG DIM=2 as Dim3D and transport depth through AGC and Vulkan so Z slices no longer collapse into a single 2D plane. |
||
|
|
6db095ec82 | revert: restore state before huge regression | ||
|
|
f9d92135a0 |
fix(agc): merge Prospero attrib-table formats onto IR vertex inputs (#556)
IR-discovered BufferLoadFormat often keeps a stale float sharp format; patch DataFormat/offset from the AGC attrib table (semantic index), allow offen fetches, and map quirks 113/121 through NarrowVk for host vertex input. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
82ab181861 |
fix(hle): enable GuestImageWriteTracker CPU sync on Windows (#550)
Windows previously hard-disabled the tracker, so CPU-written guest planes never marked dirty and host textures stayed empty. Arm pages with VirtualProtect, handle write AVs in VEH, and warm/test on VirtualAlloc memory so protect cannot poison the CRT heap. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2764aaab3f |
feat: implement cosf, time, ctype tables, tracked heap access, IL2CPP lookup ABI paths (#542)
* fix: add Messenger CRT and AGC compatibility shims * fix: keep Messenger IL2CPP bootstrap on HLE shims * Fix Messenger compatibility ABI handling * Make IL2CPP ABI regression tests portable |
||
|
|
7b950166d7 |
fix(remoteplay): stub Initialize and GetConnectionStatus as disconnected (#536)
* fix(remoteplay): stub Initialize and GetConnectionStatus as disconnected Titles probe Remote Play during pad/network bring-up; unresolved imports returned NOT_FOUND. Report initialized + disconnected so callers take the normal offline path. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retrigger gameplay CI for PR #536 Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
eb1195e59a |
fix(kernel): implement APR ResolveFilepathsWithPrefixToIdsAndFileSizes (#534)
* fix(kernel): implement APR ResolveFilepathsWithPrefixToIdsAndFileSizes Resource streamers resolve relative paths against a shared prefix; without this HLE every call returned NOT_FOUND and assets never got real ids/sizes. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retrigger gameplay CI for PR #534 Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
e13cb28267 |
fix(audio): harden AudioOut2 stack out-buffer writes against canary smash (#532)
Titles that stack-allocate AudioOut2 outs next to the frame canary were corrupted by oversized or mistyped HLE writes; keep ContextPush pacing. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
956da769a3 |
fix(kernel): finish Posix -1/errno for file ops and open EACCES (#567)
Map UnauthorizedAccess on open to PERMISSION_DENIED and route Posix lseek/pread/pwrite/rename/etc failures through PosixFailure so libc-style callers get RAX=-1 plus TLS errno, matching open/read/write. |
||
|
|
eb252af7b3 |
unstub: preserve notice screen skip flag (#559)
* fix(system-service): preserve notice screen skip flag * test(bink): avoid frame timing flake on CI |
||
|
|
f704586a8d |
[VideoOut] Add Bink2 support via FFMPEG bridge (#527)
* [VideoOut] Add Bink2 support via FFMPEG bridge * [CMake] update commit * [CMake] update commit |
||
|
|
d3600c9255 | fix(ajm): accept Gen5 codec types (#526) | ||
|
|
5f97031df5 | shader: allow larger bounded Gen5 programs (#514) | ||
|
|
fc9e3ff393 |
fix: roll back earlier host allocations on later gap failure in TryBackFixedRange (#472) (#474)
When a fixed mapping spans multiple free runs and a later gap cannot be backed, any earlier host allocations were leaked. Stage all allocations during the walk and insert MemoryRegions only after every gap has been backed successfully. On any failure, free all staged allocations. Fixes #472 🤖 Generated with Hermes Agent |
||
|
|
eb47d753f6 | [Ampr] Implement the FW 4.00 write-address command exports (#510) | ||
|
|
9be6f85ef0 |
[Font] Implement sceFontGetVerticalLayout (#492)
Add sceFontGetVerticalLayout (NID: 3BrWWFU+4ts) to the Font module, completing the vertical-text counterpart to the existing GetHorizontalLayout. The SceFontVerticalLayout structure is three floats (baseline, lineAdvance, decorationExtent) interpreted for vertical writing such as CJK text rendered top-to-bottom. - Write baseline=8.0f, lineAdvance=16.0f, decorationExtent=0.0f - Validate output pointer and return INVALID_ARGUMENT on null - Return MEMORY_FAULT when guest writes fail Tests: - GetVerticalLayout_WritesExactlyThreeFloats with sentinel guard - GetVerticalLayout_NullBuffer_ReturnsInvalidArgument NID sourced via: python scripts/aerolib_catalog.py lookup sceFontGetVerticalLayout Co-authored-by: tru3 <tru3@tru3.com> |
||
|
|
ada67a1924 |
cpu: recover SSE4a EXTRQ/INSERTQ faults on Linux (#482)
The fault-time SSE4a fallback was Windows-only because the POSIX signal bridge never carried XMM state: the CONTEXT scratch buffer only held the 17 general-purpose registers, so emulating EXTRQ/INSERTQ there would have computed results from zeroed bytes and discarded the write. Bridge the XMM registers on Linux by copying them between the mcontext's FXSAVE image (kernel sigcontext ABI, libc-independent) and the CONTEXT FltSave slots on capture and write-back, and gate the recovery on that bridge instead of on Windows. Darwin still declines: its XMM area remains unbridged. With this, guest EXTRQ/INSERTQ on Linux hosts without SSE4a (any Intel CPU) resumes with correct register state instead of dying on an unrecovered SIGILL (#328). |
||
|
|
105c58b380 |
[Tests] Isolate Gen5 scalar fallback test from parallel static mutation (#488)
ScalarLoadReadsTrackedFallbackMemory swaps the process-global static Gen5ShaderScalarEvaluator.FallbackMemoryReader under a lock private to the test class. The SharpEmu.Libs [ModuleInitializer] (AgcShaderCompilerHooks) assigns the same static to TryReadShaderGuestMemory the first time any Libs type is touched, and it does not take that lock. Under xUnit's default cross-class parallelism a concurrent Libs test could fire the initializer mid-test, clobbering the swapped-in reader — observed on CI (linux-x64) as the fallback returning all zeros: Expected [1181044592, 4, 1319632096, 4], Actual [0, 0, 0, 0]. Put the test in a DisableParallelization collection, matching the existing convention for shared-mutable-static tests (KernelMemoryCompatState, AjmState, AvPlayerPathState). The collection runs alone in the non-parallel phase, so no other test can mutate the static while this one holds it. Co-authored-by: slick-daddy <slick-daddy@users.noreply.github.com> |
||
|
|
1f3963c543 |
[Gpu] Factor the exact-XOR swizzle equation in the texture detiler (#483)
TryDetile's exact-XOR fast path (PS5 swizzle modes 5/9/24/27) ran the full AddrLib address equation per element: a 16-bit interleave with 32 PopCount calls for every pixel of textures that are millions of elements. Each output bit is parity(x & XMask) XOR parity(y & YMask), and parity distributes over XOR, so the offset factors into independent xTerm(x) ^ yTerm(y) fields. Precompute the per-column X term once and hoist the Y term per row, collapsing the inner loop to one array load and one XOR. Add GnmTilingDetileTests, which lays out a tiled buffer from an independent re-derivation of the mode-27 equation and asserts TryDetile reconstructs it byte-for-byte. Co-authored-by: slick-daddy <slick-daddy@users.noreply.github.com> |
||
|
|
e01092aa38 |
Kernel FS: close guest→host sandbox escapes in the path resolver (#478)
* Kernel FS: default-deny unmapped guest paths (fixes absolute-path host escape)
ResolveGuestPath returned any unrecognized guest path verbatim as the host
path. Because absolute paths ("/etc/passwd", "C:\Windows\...") are already
fully qualified, they skipped the relative-path app0 fallback and were handed
straight to FileStream/File.Delete/etc., giving a malicious game arbitrary
host-file read/write/delete outside the sandbox.
Return string.Empty (deny) on fallthrough instead. Most callers already treat
a nonexistent host path as NOT_FOUND; open/truncate/rename get an explicit
empty-path guard so a denied path can't reach FileStream and throw an
ArgumentException their catch blocks don't cover.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kernel FS: contain built-in mounts (fixes Windows drive-letter injection)
The built-in mount branches (app0/temp0/download0/hostapp/devlog) combined
the mount-relative guest path onto the host root without re-checking
containment. NormalizeMountRelativePath clamps ./.. but splits only on
separators, so a drive-qualified token like "C:" survives as a segment and
Path.Combine then discards the mount root, yielding a raw host path such as
"C:\Windows\..." (arbitrary host read/write).
Route every built-in branch through a new CombineWithinMount helper that
re-resolves with Path.GetFullPath and verifies the result stays under the
mount root -- the same guard TryResolveRegisteredGuestMount already applied.
Denied paths return string.Empty, which callers treat as unresolved.
AprStreamingContractTests passed a raw Path.GetTempFileName() as the guest
path, relying on the now-removed absolute-path passthrough; updated it to
address the file through a registered mount.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kernel FS: reject reparse points inside mounts (fixes symlink escape)
Lexical containment (Path.GetFullPath + StartsWith) proves the textual
path stays under the mount root but does not follow symlinks/junctions.
A malicious game dump could plant a reparse point inside app0/temp0/etc.
pointing outside it, so a contained-looking path resolved onto the host
filesystem. Walk each existing component from the mount root to the
candidate and refuse any reparse point, in both the built-in and
registered-mount resolution paths. Mirrors AvPlayer's existing defense.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kernel FS: fail closed when path containment cannot be verified
The reparse-point and drive-letter containment guards call Path.GetFullPath
and File.GetAttributes on untrusted guest paths. Both throw on crafted
over-long or invalid-char input, and ResolveGuestPath runs outside the file
syscalls' try blocks, so such a path was a guest-triggerable crash rather
than a denial.
Wrap the GetFullPath calls in CombineWithinMount and the registered-mount
path, and widen the GetAttributes catch, to treat any access/format failure
as an escape (deny) instead of propagating. Also tighten the ".." fallback
check so a legitimate file named "..foo" is not falsely rejected, and hoist
the repeated Path.GetFullPath(mountRoot) into a local.
Adds a regression test asserting the resolver returns without throwing for
an over-long and a NUL-embedded path under a mount.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kernel FS: assert malformed paths resolve to empty, not just no-throw
The fail-closed regression test asserted only Assert.NotNull, which a
non-nullable string return can never violate via its value (only a throw,
which aborts the test earlier anyway). Tighten to Assert.Equal(string.Empty)
so it also locks in fail-CLOSED: a regression where a malformed path resolved
to a non-empty host path would now be caught.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kernel FS: route new AMPR batch tests through a registered mount
Merging main brought in three AprStreamingContractTests that pass raw
Path.GetTempFileName()/temp host paths as guest paths. The default-deny
resolver from this branch rejects absolute host paths, so MissingMidBatch
failed at index 0 instead of the intended index 1. Address the present
file through a registered mount (as ResolveStatAndReadFile already does)
so entries 0 and 2 resolve and the batch fails at the genuinely-missing
entry. The two all-missing tests were unaffected but share the fix's intent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kernel FS: make a matched-mount denial terminal; fix Unix-only test asserts
A registered mount that claims a path by prefix but denies it (failed
containment or a reparse point inside the mount) now short-circuits in
ResolveGuestPath instead of falling through to the built-in mount branches.
The fall-through let an overlapping prefix (a registered "/app0" vs the
built-in SHARPEMU_APP0_DIR branch, which resolves against a cached root)
re-resolve a denied path and turn the denial back into a resolution -- the
reparse-point escape reappeared on Linux CI through exactly this path.
Also fix two tests that asserted Windows-specific behavior unconditionally:
a "C:\..." path is not absolute on Unix (it resolves contained under the
mount there), and that case is now pinned to Windows only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: slick-daddy <slick-daddy@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9d187dec55 |
Prevent AvPlayer movie startup failures across supported hosts (#456)
* Prevent AvPlayer movie startup failures across supported hosts * Prevent GR2 startup stalls during APR file checks and adaptive mutex self-locks. |
||
|
|
3574a3b145 |
Shader: lower VOP3P V_FMA_MIX_F32/LO/HI (was dropping Unity HDR shaders) (#466)
The decoder recognises the VOP3P mix ops (0x20 V_FMA_MIX_F32, 0x21 V_FMA_MIXLO_F16, 0x22 V_FMA_MIXHI_F16) but left them opaque (Vop3pRaw20/21/22), so at SPIR-V emission they fell through the vector-ALU switch to the default and failed with "unsupported vector opcode". A single unhandled instruction fails the whole compile, so any shader using fma_mix was dropped entirely. Unity's built-in-RP / PostProcessing v2 HDR, tone-mapping and auto-exposure shaders emit V_FMA_MIX_F32, so those passes never translated (this is what kept Superliminal's auto-exposure luminance chain from running). Name the three opcodes in DecodeVop3p (like the packed v_pk_* ops) and lower them in the SPIR-V translator. Each mix op computes a single f32 fma(a, b, c) where every source is read *independently* as either a full f32 register/constant or one f16 half widened to f32. Per operand, op_sel_hi selects f16-vs-f32 and op_sel picks which f16 half; the neg_hi field is repurposed as an absolute-value modifier and neg negates, applied abs-then-neg. This reuses the VOP3P op_sel/op_sel_hi/neg/neg_hi bit layout with the mix-specific meaning, not the packed-math meaning. The result is a scalar f32 for V_FMA_MIX_F32; _MIXLO/_MIXHI narrow it back to f16 (exact round-to-nearest-even, via the existing EmitFloatToHalf) and write it into the low/high 16 bits of vdst, preserving the other half. The clamp modifier saturates to [0, 1] consistently with the other VOP3P ops. Per-operand F16/F32 select and the abs/neg modifiers follow shadPS4's GetSrcMix, the authoritative reference for the mix semantics. Adds Gen5FmaMixSpirvTests: assembles V_FMA_MIX_F32 (with a representative op_sel/op_sel_hi/neg/abs) and V_FMA_MIXLO_F16 compute shaders and asserts they translate to GPU SPIR-V without hitting the drop path and emit a GLSL.std.450 Fma (and an FAbs for the neg_hi modifier). Both fail against the pre-fix tree with "unsupported vector opcode Vop3pRaw20/21". |
||
|
|
20eda4443c |
Shader: test a wave mask consumed as a per-lane predicate at the lane bit (#465)
* Shader: read a wave mask consumed as a per-lane predicate at the lane bit A VCC/EXEC wave mask consumed as a per-lane predicate (the VCndmask condition, a VCC/EXEC branch, or the derived _vcc/_exec bool) was tested in single-lane emulation with a whole-word non-zero test (IsNotZero64) instead of the current lane's bit. That is correct for comparison results (only the lane's own bit is ever set) but wrong for bitwise-complement wave-mask idioms (S_NOT / S_ORN2 / S_ANDN2 / S_NAND / S_NOR), which set the unused upper 63 bits: a whole-word test then reports the lane active even when its bit is clear. Unity's PostProcessing NaN killer does exactly this: per channel it computes isNaN = NLT AND NGT AND NEQ (against 0), then combines the channels as anyNaN OR NOT(v3-is-finite) via S_ORN2_B64. The complement set the upper mask bits, so every valid pixel read as NaN and was replaced with 0, zeroing the whole HDR scene before Bloom/Uber/tonemap. The 3D scene therefore rendered black behind the menu while the UI survived. Extract the current lane's bit in both single-lane and subgroup modes so IsWaveMaskActive matches the hardware. Fixes Superliminal (PPSA06084) black 3D scene: the storage room now renders behind the menu with natural exposure and no forced values. (cherry picked from commit 7af6f4b6f314fe302619c0d44f4db00971c5bf24) * test: wave-mask predicate is tested at the current lane bit Regression test for the wave-mask lane-bit fix. Compiles a shader that writes VCC at run time (V_CMP_EQ_F32) and asserts the emitted SPIR-V tests the wave mask at the current lane's bit (mask & lane_bit) rather than with a whole-word non-zero test. Fails against the previous IsNotZero64(mask) path, which zeroed complement wave-mask idioms (S_ORN2/S_NOT, e.g. Unity's NaN killer) across every lane. |
||
|
|
bb3318a503 |
kernel: return -1/errno from POSIX file syscalls on failure (#461)
* kernel: return -1/errno from POSIX open and fstat on failure The POSIX-named open (wuCroIGjt2g) and fstat (mqQMh1zPPT8) exports routed straight to the raw sceKernel* implementations, which report failure via the 0x8002xxxx OrbisGen2Result sentinel in the return value. libc callers follow the POSIX ABI and expect -1 with errno set, so they stored the sentinel as a valid fd. Unity's IL2CPP file layer did exactly this while probing the absent /app0/Media/il2cpp.usym: open returned NOT_FOUND (0x80020002), the guest kept the sentinel as an fd, passed it back into fstat, and eventually dereferenced a null pointer (vmovups xmm0,[rdi], rdi=0) deep in a native .prx, crashing with 0xC0000005. Wrap both entry points to translate a failed raw result into -1/errno, mirroring the existing PosixStat/PosixLseek convention. Add a shared PosixFailure helper (fstat maps a bad handle to EBADF; path calls default to ENOENT) and route it through PosixStat too. Covered by two regression tests reproducing the missing-file and misused-sentinel-fd cases. * kernel: return -1/errno from POSIX close, read and write on failure Same defect class as open/fstat: the POSIX-named close (bY-PO6JhzhQ), read (AqBioC2vF3I) and write (FN4gaPmuFV8) exports forwarded the raw sceKernel* core result, leaking the 0x8002xxxx sentinel to libc callers that expect -1/errno on a bad fd. close in particular is on the crashing Unity path, invoked on the sentinel the guest mistook for an fd. Wrap all three through PosixFailure with EBADF as the fd-not-found errno. Add regression tests for each, and correct the socket test that had locked in the old raw-sentinel contract for a double close. --------- Co-authored-by: slick-daddy <slick-daddy@users.noreply.github.com> |
||
|
|
33be88bdf9 |
memory: back the free pages of a partially-overlapping fixed mapping (#458)
A SCE_KERNEL_MAP_FIXED request whose window partially overlaps an existing allocation was failing outright: AllocateAt reserves the whole range in one all-or-nothing VirtualAlloc, which returns 0 on partial overlap. The mapping call then returned NOT_FOUND while leaving the free tail unmapped, so the guest faulted (0xC0000005) writing into it. Add IGuestAddressSpace.TryBackFixedRange, which walks the range via the host Query (VirtualQuery reports contiguous same-state runs) and fills only the free sub-ranges, leaving already-backed pages untouched. This matches the fixed-mapping contract on hardware. Route the fixed reservation path through it via a new backPartialOverlap flag. Co-authored-by: slick-daddy <slick-daddy@users.noreply.github.com> |
||
|
|
184e24fbb6 | PerGameSettings Null toggles (#453) | ||
|
|
327018e80a |
Encode linear-float flips to sRGB at present (#448)
PS5 float VideoOut buffers (A16B16G16R16F flips) hold linear scRGB light where 1.0 is SDR white; hardware scan-out applies the display transfer function. vkCmdBlitImage converts numerically only, so raw-blitting a linear-float guest frame into a UNORM swapchain crushes dim scenes to near-black. Blit float flip sources through a cached swapchain-sized sRGB intermediate (the sRGB store performs the linear->sRGB encode), then raw vkCmdCopyImage the encoded bytes into the same-compatibility-class UNORM swapchain image. Swapchains that are already sRGB keep the direct blit (their store encodes), and swapchain formats without an sRGB counterpart keep today's raw blit unchanged. |
||
|
|
04557fd250 |
Refresh CPU-rewritten guest textures by write generation (#447)
* Track guest CPU write generations * Refresh CPU-rewritten guest textures by write generation |