Skip to content

Linux: a Unity title's il2cpp code starts PSNCore.prx with sceKernelLoadStartModule(path, 0, NULL), so its start fails with "PSNCore is an old version" and the title faults (PPSA13579) #4225

Closed

Description

@eliyarson

Symptom

Blasphemous 2 (PPSA13579) on Linux, main a11c8f7f7 + #3295, stops at start-up right after printing:

PSNCore is an old version that cannot be used by the current player runtime.
  Plugin args version, found 0000, expected 0200

then faults inside PSNCore.prx reading through a null argp (exit 139, 3 of 3 runs). It is the same validator as #918, but a different cause: here the module is started with no arguments at all.

What the title does

  • The engine (eboot) loads PS5Util with (0, NULL) and Il2CppUserAssemblies with a 52-byte block: sceKernelLoadStartModule(..., 0x34, argp, ...). The block is calloc(1, 0x34) in the eboot, kept in a global and never freed during the runs we observed.
  • At that load the block is still all zero. Later, a Unity platform-init function in the eboot fills it: {0x10, 0x200, <eboot interface getter>, ...}.
  • Il2CppUserAssemblies has no module start, so it ignores the block.
  • Managed code then loads /app0/Media/Plugins/PSNCore.prx through a generic loader shim in Il2CppUserAssemblies, which calls sceKernelLoadStartModule with args, argp, flags and opt hard-coded to zero.
  • PSNCore's module start requires args >= 16, argp[0] == 0x10 and argp[1] == 0x200, stores argp[2] (the engine's interface getter) and returns 0. With (0, NULL) it prints the message above and then reads through argp.
  • Later, PSNCore starts PSNCommon with a block on its own stack (args = 1). The title also loads libfmod and libfmodstudio with (0, NULL).

Evidence

  • Patching the PSNCore call in gdb to pass the engine's block (at that point already filled) lets PSNCore start without the message, and the title continues.
  • Passing a copy taken when Il2CppUserAssemblies loaded (all zero) avoids the fault, but PSNCore still prints "found 0000" and runs without the engine's interface.

Unknown

We have not established how the console's libkernel hands PSNCore the engine's block for this zero-argument load. The local change below reproduces what the title needs, but it is a heuristic, not a measured console behaviour.

Local change used to measure this

Branch on a11c8f7f7 (not submitted; PR intake is paused):

  • sceKernelLoadStartModule remembers the first non-empty (args, argp) pair from any thread and passes it, as the same pointer (not a copy), to later loads that pass (0, NULL).
  • A block on the calling thread's stack is passed to its own load but not remembered.
  • The guest_load_start_module_args test covers the replay, the first-not-last rule and the stack case. It is Linux-only: on Windows the test fixture would need to link libkernel and export the pending-arguments accessor, as guest_module_fixture does, which I could not build or verify.

Results, 3 runs × 240 s each (Linux, CachyOS, RTX 3080 Ti, NVIDIA 610.57.04):

Build Result
a11c8f7f7 + #3295 3/3 SIGSEGV at PSNCore start
+ replay of a copy 3/3 reach the title screen; "found 0000" in 3/3
+ replay of the live pointer 3/3 reach the title screen ("Press ✕ to continue", ~60 FPS); no message

Related

Activity

  1. eliyarson commented on Oct 12, 2026

    @eliyarson
    ContributorAuthor

    Follow-up, as promised on the draft #4296. I checked whether PPSA13579's engine skips a plugin preload because of something AnyPS5 answers differently from the console. It doesn't:

    • Unity preloads native plugins from the preloadedPlugins list in BuildSettings. In this title's Media/globalgamemanagers that list is empty (count 0), and PSNCore, lib_burst_generated and .prx appear nowhere in globalgamemanagers, level0 or resources.assets.
    • The engine's own plugin loader (the one that passes the 0x34 block, as seen in PPSA28997 in sceKernelLoadStartModule discards the module parameter block, so modules that validate it fault at start-up #918) is reached only through the BurstCompilerService::LoadBurstLibrary icall. Under gdb up to the title screen neither the loader nor that icall ever runs, and there is no directory listing of /app0/Media/Plugins or manifest read.
    • The only sceKernelLoadStartModule calls are PS5Util (0, NULL), Il2CppUserAssemblies (52, the engine block), PSNCore (0, NULL, from the il2cpp-side wrapper) and PSNCommon (1, a stack block).

    So the (0, NULL) load of PSNCore is also what the title does on a console, and something on the console must make PSNCore's validator (argp[0] == 0x10, argp[1] == 0x200) pass for it. We could not find what: the block may be substituted for a NULL argp by the kernel or libkernel, or supplied some other way. Without a console we cannot check.

    Question for the maintainers: is a fallback like #4296 (a zero-argument load gets the first non-empty block an earlier load passed; documented in TechnicalDebt as unverified) acceptable in that situation, or would you rather keep this issue open until someone can trace it on hardware? If anyone with a dev console can log module_start's args/argp for PSNCore.prx in PPSA13579, that would settle it.

  2. fma-rc commented on Oct 12, 2026

    @fma-rc
    Contributor

    Cult of the Lamb (PPSA06464, Unity) reaches the same validator one step earlier. On Linux with main 0ef9dc9 it faults at 1 s before any log line (exit 139, 2 of 2 runs).

    I relinked it with --module-dir Media/Modules --module-dir Media/Plugins. app.elf then lists UnifyPS5Plugin.prx as a needed library, along with Il2cppUserAssemblies, PS5Util, SavedGames and libfmod. Its start therefore runs from the dynamic loader at process start with (0, NULL), before the title has called sceKernelLoadStartModule at all. The backtrace is UnifyPS5Plugin.prx +0x19a, +0x500, +0x10eb5, then ld-linux.

    The start has the same shape as the one you describe for PSNCore: at +0x19a it reads argp[0] before comparing args with 16, then expects {0x10, 0x200, ptr} (or {0x58, 0x103}).

    So a plugin that another module needs gets the null block at startup, without any zero-argument load call.

  3. ducloc080202 commented on Oct 12, 2026

    @ducloc080202
    Contributor

    Cross-platform data point from Windows, same validator.

    Cat Quest III (PPSA15065, Unity IL2CPP, contentVersion 01.000.001) reaches it and faults.

    • Windows 11, RTX 3050 Laptop (driver 617.42), WinLibs GCC 15.2.0, Release; libraries and relinker from a local build of main.
    • The title's sce_module/libc.prx and its Media/Modules/*.prx and Media/Plugins/*.prx were unwrapped from their SELF/fSELF containers and relinked with --module-dir Media/Plugins --module-dir Media/Modules.
    • It prints:
       PSN is an old version that cannot be used by the current player runtime.
       Please update the PSN native module and any associated managed assemblies to the latest versions
      
      then dies with 0xC0000005 (read of 0x4) inside the title's own PSN.prx.guest.prx+0x13f37. Deterministic, 2 of 2 runs.

    Same shape as this issue: the fault is in the title's bundled PSN module, right after the version/argument validator rejects the block. Two details worth noting for whoever reconciles this:

    • This title's plugin is named PSN.prx, not PSNCore.prx (the message text differs slightly too), so the validator is shared across the family rather than specific to PSNCore.
    • Like PPSA13579, there is no directory listing of /app0/Media/Plugins in the sceKernelOpen trace. The engine opens boot.config, globalgamemanagers*, Resources/unity_builtin_extra, Metadata/global-metadata.dat, il2cpp.usym and UnitySubsystems, but never lists Plugins, so (matching your finding) only the il2cpp wrapper's zero-argument load is left.

    The new test in #4296 is registered only outside Windows (if(NOT WIN32)); the author notes the fixture would need to link libkernel and export the pending-arguments accessor. I'm on Windows and can try to build and run it there if that is useful.

  4. eliyarson commented on Oct 12, 2026

    @eliyarson
    ContributorAuthor

    Root cause found; it is not the module arguments. Details and a PR follow.

    PSNCore is loaded through the il2cpp find-plugin callback registered by the eboot (0xa46860). It looks the name up, builds /app0/Media/Plugins/<name>.prx, calls sceKernelStat on it and only when that succeeds takes the engine load path, which calls sceKernelLoadStartModule with rsi = 0x34 and the {0x10, 0x200, getInterface} block that PSNCore's module_start validates. If the stat fails the callback returns the bare name and il2cpp loads PSNCore itself with (0, NULL), which is the "PSNCore is an old version" message.

    In a relinked app0, Media/Plugins contains only PSNCore.prx.guest.prx. dlopen maps X to X.guest.prx (RelinkedModulePath), but sceKernelStat/sceKernelOpen/sceKernelCheckReachability have no such mapping, so the stat returns 0x80020002. gdb on an unmodified build:

    CB name=PSNCore
    STAT /app0/Media/Plugins/PSNCore.prx
    STAT-RET rax=80020002
    

    and with the original .prx files symlinked beside the guest images the same run shows STAT-RET rax=0, the engine load site is reached and the message disappears. The same happens with this PR and no symlinks.

    PR: #4688 (bring-up discussion: #4687). It shares the X -> X.guest.prx mapping between dlopen and the file queries instead of replaying arguments. It is an alternative to my draft #4296 (module-argument replay), which it makes unnecessary for PPSA13579; I will close #4296 once this is merged.

    On main plus the PR the title then crashes while loading Addressables bundles (SIGSEGV in core::string::reserve); this is the host fd 0 reuse fixed by #3295. With #3295 applied the title screen is stable at ~59.9 FPS for 240 s (gameplay not tested).

    If this does not match your setup, please post ls app0/Media/Plugins of the relinked output.

  5. oneandonlydean commented on Oct 13, 2026

    @oneandonlydean
    Collaborator

    #4688 is on main: sceKernelStat finds the relinked plugin, so the engine load path passes the arguments block. Does any title still print PSNCore is an old version on current main? Please post the title and the log line; if none does, this can close.

  6. fma-rc commented on Oct 13, 2026

    @fma-rc
    Contributor

    Cult of the Lamb (PPSA06464) does not print PSNCore is an old version on main b1328fc, but only because it does not get that far. Relinked again with that commit's relinker (--module-dir Media/Modules --module-dir Media/Plugins) and run on its libraries, it still dies at 1 s with an empty log, 2 of 2 runs:

    app.elf: segfault at 0 ip ...19a error 4 in UnifyPS5Plugin.prx.guest.prx[419a,...+2000]
    

    That is the same place as before: the plugin is a needed library of the relinked executable, so the dynamic linker runs its start at process start and it reads through a null argument pointer at +0x19a. So this title can't confirm the sceKernelStat fix either way.

  7. oneandonlydean commented on Oct 14, 2026

    @oneandonlydean
    Collaborator

    Closing: #4688 is on main, so sceKernelStat finds the relinked plugin and the engine passes its arguments block to PSNCore. The UnifyPS5Plugin case (a needed library started by the dynamic loader with no arguments) is a different path; please open a new issue for it. Comment here if PSNCore still prints this on current main.

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