Repository navigation
Description
Onimusha WotS (PPSA27836) hangs on black screen waiting on SphiaSema
Summary
The entry-point crash is resolved on current main with #4237. However, Onimusha: Way of the Sword now gets stuck on a black screen during startup.
The game window remains open, but there is no visible gameplay, audio, or NVIDIA overlay. CPU usage spikes to around 60–70% during the first second, then drops to approximately 5–9% and stays there.
I captured a full synchronization trace using APS5_TRACE_SYNC=1. The trace suggests that a guest thread is waiting indefinitely on a SphiaSema semaphore after the initial startup and NP callback stages have completed.
Environment
- Game: Onimusha: Way of the Sword
- Title ID: PPSA27836
- Base version: v01.000.001
- Executable: Unwrapped ELF
- Emulator: AnyPS5, current
main - OS: Windows 11, Build 28000.3086
- GPU: NVIDIA GeForce RTX 5070 Ti
- Additional patch: The local
mprotectmask patch is still applied while waiting for fix(libkernel): drop undefined protection bits in sceKernelMprotect #5770. Everything else is stockmain.
What works
The game successfully gets through the following stages:
- ELF entry point
- PRX library loading
sceHttp2Init- Vulkan device and swapchain initialization (1484 × 835, two images)
- Shader registration and compilation
- Controller initialization (
PadInput, Xbox Series X controller) - NP initialization and callbacks, including:
sceNpSetNpTitleIdsceNpRegisterStateCallbackAsceNpGetOnlineId
- Rebe/Sphia initialization, with
SPHIA-BYPASS-SUCCESSreported
The shader disk cache reports 897 hits, 0 misses, and 0 writes.
Current behavior
After initialization, the game remains on a black screen indefinitely. There is no audio, and the NVIDIA overlay does not appear. The window remains responsive enough to stay open, and the emulator does not crash.
The last normal log activity includes opening the game's PAK files:
[src\Open.cpp:214 sceKernelOpen] path=/app0/re_chunk_000.pak ...
[src\Open.cpp:214 sceKernelOpen] path=/app0/re_chunk_000.pak.patch_001.pak ...
[src\Open.cpp:214 sceKernelOpen] path=/app0/re_chunk_000.pak.sub_000.pak.patch_001.pak ...
[shader-disk-cache] (10 s): 897 hits, 0 misses, 0 writes
Synchronization trace
I ran app_v19.exe with APS5_TRACE_SYNC=1. The resulting trace is approximately 413 KB and contains 4,798 lines.
After the successful initialization stages, the trace repeatedly reports an infinite wait on SphiaSema:
[sema] SPHIA-BYPASS-SUCCESS ... 'SphiaSema' caller=0000000408778687
[sema] wait ... 'SphiaSema' need=1 have=0 timeout=-1 caller=0000000408778687
The trace contains three distinct SphiaSema objects:
- One with
max=1, which is signaled once at line 232; Rebe initialization subsequently passes. - One with
max=2147483647, which is also signaled once at line 232. - Another with
max=2147483647, which is waited on indefinitely by guest caller0000000408778687.
Other observations from the trace:
- No
sceHttp2CreateRequestorsceHttp2SendRequestcalls appear. - Only three event-flag set operations appear in the entire trace, at lines 219, 229, and 477.
- The guest thread name
RebeWebApiThdoes not appear in the trace. - CPU usage drops from approximately 60–70% to 5–9% after the first second and stays low.
Questions
- How can I map the guest caller address
0x408778687to its module, function, or PRX so I can identify which code is waiting on this semaphore? - Would additional tracing, such as guest thread creation and naming,
sceKernelWaitSemareturn addresses, or semaphore wait results, help identify the thread responsible for the hang? - Is there a known pattern in AnyPS5 for games getting stuck in an indefinite semaphore wait after NP callbacks and Sphia/Rebe initialization have completed?
I can provide the full APS5_TRACE_SYNC=1 trace and app_diag_log.txt if needed.

Activity
MarcZX10 commentedon Oct 14, 2026
The repository implements
sceKernelGetModuleInfoFromAddr(address, 2, &info)on both Windows and Linux. If you can add a temporary guest-side diagnostic for0x408778687, it should identify the loaded module containing that address and return its segment ranges. Could you share the returned module name and executable segment range (or the error if it returnsESRCH)? That will tell us whether to inspect the unwrapped eboot or a PRX before adding more trace instrumentation.If the unwrapped module retains symbols,
llvm-addr2line/llvm-symbolizercan then resolve the PC using that exact module. If it is stripped, the module-relative offset and segment range still let us inspect the matching disassembly without assuming this is eboot code.amiraction0938 commentedon Oct 14, 2026
@MarcZX100 — thanks for the pointer! We added the diagnostic you suggested and tracked
0x408778687down to the unwrapped eboot. Here's what we found.Diagnostic output
We added a temporary diagnostic to
sceKernelWaitSemainlibkernel.prx, callingsceKernelGetModuleInfoFromAddr(0x408778687, 2, &info)whenRebeWebApiThfirst attempted to wait onSphiaSema.Findings
sceKernelGetModuleInfoFromAddrreturnedSUCCESS, so the address belongs to a registered module.app_v25.exe), loaded at0x400000000using--windows-fixed-base.R+X), at module-relative offset0x8778687.nmandreadelf -sWfound no symbols, sollvm-addr2linedoesn't seem useful here.Disassembly around the address
We mapped guest address
0x408778687back to the raw eboot file and disassembled the surrounding code:This looks like a worker loop: it waits on a semaphore, calls two functions, checks a flag at
[rbx+0x40], and loops while the flag is not set.One important detail:
0x408778687is the return address immediately after thesceKernelWaitSemacall, rather than the address where the thread is blocked. The loop itself is in the eboot, not a PRX.Current understanding
We haven't identified what
[rbx+0x40]represents yet. Also, based on the current trace and our understanding of the Sphia implementation, we haven't found an emulator-side path that signals this particular semaphore. That may explain why the worker never progresses, but we'd like to verify the intended behavior before patching anything.What's the best next step?
Would you recommend one of these approaches?
SphiaSemais created and whether the game ever callssceKernelSignalSemaon it.0x8ecbe60,0x877c770, and0x8ecb460to better understand what happens during each iteration.[rbx+0x40]and determine what needs to happen for the worker to exit.I'd prefer to understand the expected synchronization behavior before forcing the flag or faking a semaphore signal, since either could just move the hang somewhere else.
Which direction would you recommend?
Thanks!
amiraction0938 commentedon Oct 14, 2026
Following up on my last question: I did not wait for an answer and instead used a temporary workaround for the Sphia wait so I could see what comes after it. Details below.
Update: Onimusha now gets past Rebe/Sphia and the PAK loading, but aborts in the shader recompiler (tessellation + bindless image descriptors)
Thanks again to @MarcZX100 for the pointer. I have more findings, and the blocker has moved from CPU-side sync to the shader recompiler. I'm sharing the exact values in case they help.
Important: these results are on tag
v0.1.2(detached HEAD) with local, uncommitted patches, not on currentmain. I have not re-tested on currentmain.1. A mistake in my original report: the PAKs were not under
app0The game opens
/app0/re_chunk_000.paketc., which resolves to<run dir>/app0/re_chunk_000.pak. My PAK files were only in the run directory root, so those opens would presumably fail.This was easy to miss:
sceKernelOpenlogspath=... flags=...before resolving the path, and I saw no failure message in the trace, so the log looked fine. It might be useful to also log the resolved native path and the errno/result on failure.I fixed it with NTFS hard links (
mklink /H) of the 4 PAK files intoapp0\. After this, the same build ran much further (the trace grew from ~330 lines to 2700+).2. Local workarounds I applied (these are hacks, not proposed fixes)
sceKernelWaitSemafor the semaphore namedSphiaSema(it returns OK immediately). The runs in this report used an unmodified eboot: an earlier experiment that NOP'ed thejneat file offset0x877C69Bwas not in the binary used for these runs (correction to my earlier wording). This only avoids the infinite wait; I don't know the intended behavior of Sphia.3. Hull shader / tessellation lowering failures (4 shaders, 3 different checks)
All thrown from
core/shader/recompiler/Optimization/src/TessellationLowering.cpp. For each I added a temporaryfprintfto dump the values at the failing check. Every failing hull shader hadinputs=1, outputs=1(a single input and single output control point).(a)
the hull shader reads no LDS per control point, so the local stride is unknown(shaders0x2106876600,0x211243e600, stage 2, pc0x58):localStrideis only derived from aLocalOutputaccess, aControlInputwithvertex != 0, or a dynamicControlInput. Here the access is non-dynamic withvertex=0, solocalStridestays 0 althoughrelative / inputs = 24is clearly the stride.(b)
the hull shader writes off-chip memory outside its control points and patch constants(shaders0x2106870700,0x2106879900, stage 2, pc0x8c):With one output control point,
vertexstays 0, so the write is classified as a patch-constant record andcontrolStrideis never established (it is only set fromvertex != 0writes or from the domain shader reads).(c)
per-patch tessellation output is not written by control point 0 alone(shader0x2112038600, stage 2, pc0x9c):What I changed locally (guesses, unverified):
localStride == 0, derive it from a non-dynamicControlInputwithvertex == 0asrelative / inputs.outputs == 1andcontrolStride == 0, derive it from aControlOutputwithvertex == 0andrelative(when the write fits inrelative), and treat that write as a control-point write.FirstInvocationOnlyrequirement wheninputs == 1 && outputs == 1, since there is only one invocation per patch.With these three relaxations the recompiler gets past all four hull shaders. I have not been able to check whether the resulting rendering is correct, because of the next failure. I would really appreciate a review from someone who knows this lowering: is "single control point, so vertex index is folded away" the right interpretation, or is there a better fix?
Attached: tessellation_lowering_local_changes.txt (my local diff for (a)-(c), including the temporary fprintf diagnostics). It is a workaround to get past the checks, not a proposed fix.
4. Current blocker: bindless-style image descriptor
If I read the chain correctly, the image descriptor is loaded from a buffer at an offset computed from a runtime value read from the SRT (index * stride), i.e. a bindless-like table of descriptors. This is a missing recompiler feature rather than a small bug, so I stopped patching here. The process aborts with an unhandled
std::runtime_error(SIGABRT). The window stays black, with no frames presented.Questions
I can attach the full trace, the
TessellationLowering.cppdiff, and the diagnostic output if useful.Environment: Windows 11 (Build 28000.3086), RTX 5070 Ti, i5-14600K, 32 GB RAM, tag
v0.1.2with local patches.tessellation_lowering_local_changes.txt
MarcZX10 commentedon Oct 14, 2026
I opened #6123 to extend
APS5_TRACE_SYNCwith opt-in semaphore create, signal, and wait begin/end records keyed by semaphore address, name, and guest caller. It should let a run distinguish same-named semaphore instances and show whether a signal reaches the particular waiter. The focused Linux trace regression passes locally; the emulator-linked test is blocked here by the host CPU feature gate. No need to apply a local diagnostic patch if the PR is useful after review.MarcZX10 commentedon Oct 14, 2026
I opened #6132 to add failure diagnostics to
sceKernelOpen: a failed native open now logs the guest path, resolved path, and returned guest error; path-resolution failures log their error and result. The regression check covers a missing.pakpath. The Linux target builds, though the emulator-linked test cannot reach its body on this host because it lacks the required CPU extensions. This improves visibility into path failures; it does not establish that the Onimusha wait is caused by missing PAK files.MarcZX10 commentedon Oct 15, 2026
Thanks, this moves the blocker from the semaphore to the recompiler. There is related work in #6191 and open PR #6232, but #6232 specifically follows pointer hops from an image-table record to a T#. Its description explicitly says the Ratchet chain with a second
readfirstlaneindex is not covered. Your reported chain (GetBufferResourcefrom SRT plus anIMul32-computedReadConstBufferoffset) is not demonstrated to fit that record model, so I would treat it as potentially distinct rather than assume #6232 fixes it. Since these results are from v0.1.2 with local workarounds, the next useful check is whether the same planner chain still occurs on currentmain; if so, comparing that exact shader against #6232 would show whether a separate issue is needed. I would avoid the semaphore/tessellation workarounds as evidence for this descriptor path.MarcZX10 commentedon Oct 17, 2026
For the next step, please first reproduce the descriptor failure on current
mainwithout the local semaphore/tessellation workarounds, then capture the planner chain and shader PC again. The report explicitly pins those results to v0.1.2, so we cannot yet tell whether currentmainhas changed the chain. If it still fails, compare that exact chain against #6232 and #6191 (including the dynamic SRT-derived buffer offset); if neither models it, a focused shader fixture or issue for runtime-indexed descriptor loading would give us a testable scope. The semaphore path is separate and already has tracing work in #6123. I would not force a signal or relax the hull checks without evidence of the intended synchronization/lowering semantics.amiraction0938 commentedon Oct 17, 2026
Following up on the request to retest on current
mainwithout the local semaphore/tessellation workarounds.Setup
mainat51acb79ac(6 commits behindorigin/main3f171d03e; none of those touchGraphics/src/State.cpp)--windows --windows-fixed-base 0x400000000)onimusha_main_local_patches.diff):sceKernelMprotect:LinuxProtFromScemasks undefined protection bits (prot &= 0x3F7) instead of throwing, as in the pending fix(libkernel): drop undefined protection bits in sceKernelMprotect #5770libSceAgcDriver/Graphics/src/State.cpp(described below)What happened on
mainI did not reach the descriptor failure from the first report. I can't say whether it is fixed on
mainor just not reached, because each run stopped earlier. The blockers, in order:a color target's sample count differs from the rasterizer's(State.cpp, color-target loop)colorSamples=1 rasterSamples=2 colorsSoFar=0 hasDepth=0 a2c=0, register0x2f8=0x108001,CB_COLOR_ATTRIB=0, format 122, extent 1456x1640.the depth target's sample count differs from the rasterizer'sdepthSamples=1 rasterSamples=2, format 130, same extent 1456x1640, same0x2f8=0x108001.MSAA_ENABLE on a single-sample target whose sample is off the pixel center0x2fe = 0x40cin one draw and0xc04in another.VGT_SHADER_STAGES_EN=0x2030: unsupported geometry input or output assemblyprimitive=1,0x29b=0x1,0x2ce=5,0x1ff=255,0x25b=0x6633. The check requires0x29b == 2.Experimental workarounds (not proposed fixes)
To see what comes after (1)-(3), I made these local edits, all in
State.cpp:samples = 1for that draw and reset the sample-location fieldsI don't know if this models the hardware correctly. It only moves the failure forward.
Result with those edits
Questions
samples=2and0x2f8=0x108001onto single-sample color and depth targets, with off-center sample locations: is that a pattern the project models somewhere (for example sample locations used without real MSAA)? What are the intended semantics?0x29b=0x1looks like line-strip output to me, but I have not confirmed that against the register spec. Is line-strip GS output planned or already tracked? Theamplification = verticesPerPrimitive - 2path suggests only triangle strips are handled today.Attachments:
onimusha_main_local_patches.diff,report_main_findings.txt.