Skip to content

Cult of the Lamb (PPSA06465) on Windows: intermittent fault in Loading.PreloadManager during start-up (about half of 40 s runs) #5490

Open

Description

@p3n1cillin

Cult of the Lamb (PPSA06465, Unity IL2CPP, my own dump), Windows 11, MinGW-w64 GCC 15 (winlibs), Release. Relinked from a fresh work directory with --windows --module-dir Media/Modules --module-dir Media/Plugins. It intermittently faults on thread Loading.PreloadManager during start-up, 10 to 20 s in.

How often (40 s runs; the libraries changed between runs, the relinked app.exe and work directory didn't):

Libraries Faults
main a0f5916a2 4 of 8, alternated with the next row
main + #4973 5 of 8 (one of them FATAL: UCRT invalid parameter instead)
An older main 41b0ca597 with about 15 open PRs merged 4 of 4

Other short series on main gave 1 of 6 and 3 of 4. The runs that don't fault keep going: shaders compile, frames are presented, and the music bank opens. Runs with --trace-imports and APS5_TRACE_CALLS=all didn't fault in 2 runs, which fits a timing-dependent race.

The fault site changes from run to run, always on Loading.PreloadManager while asset files are being opened:

  1. rip libc.prx.guest.prx+0x20796, the title's own libc.prx at ELF 0x10796. This is the guest libc's memcpy-style bounds check: rdi + rdx lies above rbp and rdi lies below the frame, so it stores 0xa0020307 to fs:[0x28] and executes int 0x45. The arguments were rdi = 0 (destination) and rdx = 0xfffffffffdffb309 (a negative length). The caller is app.exe+0x96635f. The last file opened was /app0/Media/Resources/unity default resources, just after StreamingAssets/aa/PS5/buffer_scene_scenes_all_*.bundle.
  2. rip app.exe+0x95f5ed, a read of 0x00007ff99d83de58, after the same file opens.
  3. rip app.exe+0x453b2e, a write of 0x0. The frames run app.exe+0x78cfd4, +0xc23a9b, +0xc89f38, +0xc7b1c3, +0xc7bed2, the same chain as fault 1 below its top frame.

All three faults share the frames app.exe+0xc89f38 and +0xc7b1c3. At the time, many IL2CPP threads are parked in libkernel.prx+0x44995.

Interpretation, not established: a negative length with a NULL destination, and wild pointers in neighbouring code, look like data being read or freed while another thread still uses it. #4973's file-position lock doesn't change the rate, so this is not that race. Other candidates are IL2CPP's garbage collector stopping and resuming threads, or a synchronisation primitive returning early. I haven't narrowed it further. The same thread and symptom in other Unity titles were attributed to #3861/#4973 on #3805; for this title, that fix isn't enough.

AI-assisted (Claude Code).

Activity

  1. MarcZX10 commented on Oct 13, 2026

    @MarcZX10
    Contributor

    Thanks for the detailed reproduction. One check may separate malformed input from a synchronization race: does /app0/Media/Resources/unity default resources exist in this dump, and on a failing run does its sceKernelOpen succeed? If you can capture a lightweight trace for just that path (open result, each read offset/byte count/result, and the last successful read before the fault), it would show whether the deserializer receives a short/error read or complete data. A missing asset was one possible lead in the older Unity Loading.PreloadManager report (#3805), though it was never established there either. The 4/8 vs 5/8 rates don’t clearly show that #4973 changes this fault, so I wouldn’t infer either way from those samples.

  2. p3n1cillin commented on Oct 13, 2026

    @p3n1cillin
    Author

    Thanks. I checked both questions with a local-only logging build: main c7601b797, plus logging in sceKernelOpen/Close/Read/Lseek/Pread/Fstat for that one path's descriptor.

    • The file exists. Media/Resources/unity default resources is 3,740,432 bytes in the dump, and sceKernelOpen succeeds every time (fd 4 or 6).
    • The reads are complete and identical in faulting and non-faulting runs. Over 6 runs of 40 s (5 faulted), the sequence was always:
      open fd=4 flags=0x0                      (tid A)
      pread fd=4 at=0      n=65536 -> 65536    (tid A)
      pread fd=4 at=327680 n=65536 -> 65536    (tid A)
      pread fd=4 at=393216 n=65536 -> 65536    (tid A)
      pread fd=4 at=327680 n=65536 -> 65536    (tid A)
      close fd=4 -> 0                          (tid B = Loading.PreloadManager)
      
      There were no read, lseek or fstat calls on it. In faulting runs the fault follows that close on the same Loading.PreloadManager thread, for example app.exe+0x453b2e writing to 0x0.

    So the deserializer gets complete data for this file, and it isn't a short or error read here. The fault comes after the file has been consumed and closed.

    One run differed. The file was opened a second time later and got fd 0; then pread at=0 n=65536 and pread at=1075060 n=113428 both returned in full. That run ended with FATAL: UCRT invalid parameter on another thread, at libc.prx+0xb4b8 (host libc) called from libc.prx+0xaf5da. I'm not sure that is the same problem, but a file getting descriptor 0 may be worth a look on its own.

  3. MarcZX10 commented on Oct 13, 2026

    @MarcZX10
    Contributor

    Thanks, this rules out a missing or short unity default resources read on both faulting and successful runs. The close immediately before the null write is useful timing, but the complete, identical reads mean I don't see a file-I/O change justified by this trace alone.

    The fd-0 run may be a separate clue: the guest descriptor checks I found treat only negative values as invalid, so descriptor 0 is valid here. If that UCRT invalid-parameter report recurs, a stack or the invalid-parameter arguments would help identify which CRT call rejected them; I wouldn't tie it to the intermittent null write yet.

  4. EagleBearerYS commented on Oct 14, 2026

    @EagleBearerYS
    Contributor

    If you have time for another series: two open PRs change libkernel behaviour that could fit your "synchronisation primitive returning early" candidate. I haven't run Cult of the Lamb, so this is a guess.

    If the fault rate changes with either of them merged into main, that would narrow it down.

  5. p3n1cillin commented on Oct 14, 2026

    @p3n1cillin
    Author

    Thanks @EagleBearerYS, I ran that series. main a453108cf was compared with each PR merged onto it, in alternating 40 s runs (Windows 11, same relinked app.exe, only the libraries swapped):

    libraries first series (6) second series (10) total
    main 4 4 8/16
    + #5412 (semaphore hand-off) 2 — 2/6
    + #4899 (event flag hand-off) 1 5 6/16
    + #5312 (signal mask in handlers, setjmp) 3 — 3/6

    #4899's low first count didn't hold up over 10 more rounds, so none of the three clearly changes the rate. The faults keep the same mix of shapes with every set: 0xc0000005 on Loading.PreloadManager, abort(), and UCRT invalid parameter. #5412 and #5312 only had 6 runs each, so a smaller effect from them isn't ruled out.

    AI-assisted: yes (Claude Code)

  6. EagleBearerYS commented on Oct 14, 2026

    @EagleBearerYS
    Contributor

    Thanks for running the series. I read it the same way: none of the three clearly changes the fault rate, and #4899's early dip didn't hold over the longer series. With 6 runs each, a small effect from #5412 or #5312 isn't ruled out, but nothing here points to either of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions