Skip to content

fix(libkernel): report a 64 KiB st_blksize for directories - #12922

Merged
oneandonlydean merged 4 commits into
boykopovar:mainfrom
ntamas94:fix/ps4-dir-blksize
Nov 1, 2026
Merged

oneandonlydean merged 4 commits into
boykopovar:mainfrom
ntamas94:fix/ps4-dir-blksize

Conversation

@ntamas94

Copy link
Copy Markdown
Contributor

What

stat and fstat reported st_blksize 512 for directories on Windows. A PS4 Pro (firmware 10.71) reports 0x10000 for /app0, /app0/sce_sys, /app0/sce_module and the system library directory, through both sceKernelStat and sceKernelFstat of a directory opened with O_DIRECTORY. Titles size their directory reads from it: Crysis Remastered (CUSA18659)'s CryPak allocates st_blksize + 264 bytes and calls getdents once, so with 512 it listed only 33 of the 54 entries in engine/shaders, missed the shader flag files listed after them, and drew characters' skin and eyes black because those shader lookups then missed its cache.

Directories now report 0x10000, app0.zar directories included. The console reports 0x8000 for /data directories, which the host paths do not tell apart, and /app0 files report 0x10000 where files here keep the host's value; both are in the new libkernel.md entry.

Console rows (probe buffer prefilled with 0xAA, read at FreeBSD 9 offsets):

stat  /app0                mode=040555 size=65536 blocks=128 blksize=0x10000
fstat /app0                mode=040555 size=65536 blocks=128 blksize=0x10000
stat  /app0/sce_sys        mode=040555 size=65536 blocks=128 blksize=0x10000
stat  /app0/sce_module     mode=040555 size=65536 blocks=128 blksize=0x10000
stat  /<sandbox>/common/lib mode=040777 size=65536 blocks=128 blksize=0x10000
stat  /data                mode=040777 size=2048  blocks=8   blksize=0x8000
stat  /data/ps4probe       mode=040777 size=512   blocks=8   blksize=0x8000
stat  /app0/eboot.bin      mode=0100555 size=104896 blocks=256 blksize=0x10000

Tested

Windows 11, WinLibs GCC 15.2, NVIDIA GeForce RTX 2080, on main bb04a4f: guest_filesystem checks st_blksize of a directory through sceKernelFstat and sceKernelStat, and guest_kernel_app_archive_archive checks an app0.zar directory; with the previous NativeStat.cpp and ArchivedFile.cpp both fail, with this change all four guest_filesystem / guest_kernel_app_archive_* tests pass 3 of 3. python tools/check_conventions.py --base origin/main passes. Crysis Remastered (CUSA18659) lists all 54 entries and draws faces with this change on a local branch; not run on current main, where the title does not boot yet.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

A PS4 Pro (firmware 10.71) reports st_blksize 0x10000 from stat and fstat
for /app0, its subdirectories and the system library directory. Titles
size one getdents buffer from it: CUSA18659's CryPak reads st_blksize +
264 bytes once, so the 512 reported here cut its shader directory listing
short. Directories, app0.zar ones included, now report 0x10000; /data
directories (0x8000 on the console) and files are recorded as debt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 4630 docs/dev/debt/libkernel.md
PR 5021 core/libs/prx/libkernel/File/src/NativeStat.cpp
docs/dev/debt/libkernel.md
PR 6936 core/libs/prx/libkernel/File/src/ArchivedFile.cpp
core/libs/tests/GuestKernelAppArchive.cpp
docs/dev/debt/libkernel.md
PR 7088 docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 docs/dev/debt/libkernel.md
PR 7135 docs/dev/debt/libkernel.md
PR 7149 docs/dev/debt/libkernel.md
PR 7192 docs/dev/debt/libkernel.md
PR 7447 core/libs/prx/libkernel/File/src/ArchivedFile.cpp
docs/dev/debt/libkernel.md
PR 7464 docs/dev/debt/libkernel.md
PR 7509 docs/dev/debt/libkernel.md
PR 7804 docs/dev/debt/libkernel.md
PR 9156 docs/dev/debt/libkernel.md
PR 9199 docs/dev/debt/libkernel.md
PR 9293 docs/dev/debt/libkernel.md
PR 9297 docs/dev/debt/libkernel.md
PR 9302 docs/dev/debt/libkernel.md
PR 9326 docs/dev/debt/libkernel.md
PR 9371 core/libs/tests/GuestFilesystem.cpp
PR 9389 docs/dev/debt/libkernel.md
PR 9557 docs/dev/debt/libkernel.md
PR 9566 docs/dev/debt/libkernel.md
PR 9843 docs/dev/debt/libkernel.md
PR 9938 docs/dev/debt/libkernel.md
PR 10025 docs/dev/debt/libkernel.md
PR 10319 core/libs/tests/GuestFilesystem.cpp
docs/dev/debt/libkernel.md
PR 10442 docs/dev/debt/libkernel.md
PR 11002 docs/dev/debt/libkernel.md
PR 11624 docs/dev/debt/libkernel.md
PR 11625 core/libs/prx/libkernel/File/src/ArchivedFile.cpp
core/libs/tests/GuestKernelAppArchive.cpp
docs/dev/debt/libkernel.md
PR 11628 docs/dev/debt/libkernel.md
PR 11661 docs/dev/debt/libkernel.md
PR 11689 docs/dev/debt/libkernel.md
PR 11698 docs/dev/debt/libkernel.md
PR 11831 core/libs/tests/GuestKernelAppArchive.cpp
PR 11896 docs/dev/debt/libkernel.md
PR 11939 docs/dev/debt/libkernel.md
PR 12560 core/libs/tests/GuestFilesystem.cpp
docs/dev/debt/libkernel.md
PR 12682 core/libs/tests/GuestKernelAppArchive.cpp
docs/dev/debt/libkernel.md
PR 12688 core/libs/tests/GuestKernelAppArchive.cpp
docs/dev/debt/libkernel.md
PR 12700 core/libs/tests/GuestKernelAppArchive.cpp
docs/dev/debt/libkernel.md
PR 12820 core/libs/tests/GuestFilesystem.cpp
PR 12826 docs/dev/debt/libkernel.md
PR 12835 core/libs/tests/GuestFilesystem.cpp
PR 12965 core/libs/tests/GuestKernelAppArchive.cpp
docs/dev/debt/libkernel.md
PR 14108 docs/dev/debt/libkernel.md
PR 14111 docs/dev/debt/libkernel.md
PR 14128 docs/dev/debt/libkernel.md
PR 14212 docs/dev/debt/libkernel.md

@oneandonlydean oneandonlydean self-assigned this Oct 29, 2026
@JJenkx

JJenkx commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Windows run of this head (da7581eec4), merged onto main (clean merge, local only). Windows 11 Enterprise 10.0.22621, WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON, no Vulkan driver visible.

  • guest_kernel_app_archive_archive, _mmap and _plain pass 3 of 3. With this PR's two source files reverted to main, guest_kernel_app_archive_archive fails, so the PR fixes what it says.
  • The new st_blksize == 0x10000 checks in GuestFilesystem.cpp (around line 1429) pass.
  • guest_filesystem as a whole still fails here, both with and without this PR. I patched Check locally to record the failing line: original line 1552, unlink_nid_postfix(danglingDirectoryLink...) == 0, guest errno 13. That is a pre-existing main failure on this Windows build (22621; the CI image, 26100, doesn't hit it). Unlinking a dangling directory symlink is fixed separately in fix(libkernel): unlink a dangling Windows directory link #12820. It isn't related to this PR.

AI-assisted: yes (Claude Code).

@rooklz rooklz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Host-side check of head da7581eec4 on main bb04a4f. I did not run anything on a console.

What I ran (macOS arm64, Apple clang, ASan+UBSan)

  • check_conventions.py --base upstream/main: passed. git diff --check: clean. git merge-tree against bb04a4f: clean.
  • pr_overlap.py --branch: 1451 open PRs, no textual conflicts. The only ones it could not compare are old PRs that already conflict with main and touch libkernel.md / GuestFilesystem.cpp / GuestKernelAppArchive.cpp. #11625 touches ArchivedFile.cpp but not FillNode's last line.
  • clang++ -fsyntax-only on NativeStat.cpp and ArchivedFile.cpp: clean.
  • I built a standalone harness: NativeStat.cpp from the PR, unchanged except the macOS st_atimespec field rename on the host struct reads, plus stubs for the archived and directory-descriptor helpers. It has 12 rows, each against the host's own stat:
    • These must be 0x10000: directory through TryFillFileStat, FillLinkStat on a directory, FillFileStatFromDescriptor on an O_DIRECTORY fd, FillFileStat(int), and stat through a symlink to a directory.
    • These must keep the host value: lstat of a symlink to a directory, a regular file by path, through a symlink, and by fd, a pipe, a unix socket, and /dev/null.
    • Result: the PR head passes 12/12. With main's NativeStat.cpp, exactly the 5 directory rows fail.

Mutants of the CopyNativeStat line

Mutant Harness PR's own test rows
M1: always 0x10000 killed (6 rows) survives: only directories are asserted
M2: st_mode & 0040000 bit test (also matches sockets) killed (socket row only) survives
M3: every non-regular file killed (symlink lstat, /dev/null, socket) survives

Optional rows to close the gap:

  • A regular-file row next to the new ones kills M1. For example, sceKernelStat(presentName.c_str(), &fileStatus) == 0 && fileStatus.st_blksize != 0x10000. On Windows the code fixes this at 512. On APFS and ext4 the host reports 4096.
  • M2 and M3 leave regular files alone, so only a non-regular, non-directory descriptor kills them. In my harness the socketpair fstat (8192 on macOS) kills both; /dev/null and an lstat of a symlink also kill M3. The existing GuestSocketPair.cpp:203 row checks only st_blksize != 0. Both kinds of row are optional.

Notes (not blocking)

  • On macOS, /dev/null reports st_blksize 65536 from the host, so a "not 0x10000" check is only valid for regular files, not devices.
  • The mode test is applied after the copy, so lstat of a symlink to a directory correctly keeps the host value (S_IFLNK). The 0140000 socket paths (Udp.cpp:943) do not go through CopyNativeStat.
  • The debt entry already records the evidence limits: the measurement is from a PS4 Pro on firmware 10.71, not a PS5, and /data reports 0x8000 there.

@ntamas94

Copy link
Copy Markdown
Contributor Author

@rooklz Thanks for the harness and the mutants. 8604e22 adds the regular-file row next to the directory ones: sceKernelStat(presentName, ...) must not report 0x10000, which kills M1 (on Windows the file keeps 512). I left the socket row out, as you marked both optional and the socket paths do not go through CopyNativeStat. guest_filesystem passes 3 of 3 here (Windows 11 26200, WinLibs GCC 15.2) and check_conventions.py --base origin/main passes.

@JJenkx Thanks for the Windows run and for tracking the line-1552 failure to #12820; it is not hit on this machine's build (26200).

AI-assisted: yes (Claude Code)

As rooklz suggested in review: a row for a regular file next to the
directory rows, so a change that reports 0x10000 for every stat fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@blawesom

Copy link
Copy Markdown
Contributor

Static review plus an independent Linux run (CachyOS, kernel 7.2.10, GCC 16.2.1, Ninja 1.13.2, Release, BUILD_TESTING=ON, Ryzen 7 9800X3D). No findings.

What I checked

  • Both host paths are patched consistently: NativeStat.cpp (CopyNativeStat, mode S_IFDIR/0040000) and ArchivedFile.cpp (FillNode, node.Directory) — so app0.zar directories and host directories agree, matching the title-observed CryPak behavior.
  • The test asserts directories report 0x10000 and, importantly, pins the negative: a regular file does not get 0x10000 (st_blksize != 0x10000), so the fix can't accidentally spill onto file paths.
  • The debt entry is honest about the unmodeled /data case (console reports 0x8000, not distinguished here) and files keeping the host value — exactly the kind of "known gap recorded, not faked" the repo requires.

Focused test run

ctest --test-dir build -R 'guest_filesystem|guest_kernel_app_archive' --output-on-failure
tree 511 guest_filesystem_error 2591 guest_filesystem 2594 app_archive_archive 2595 app_archive_plain 2596 app_archive_mmap
this head da7581eec4 Passed Passed Passed Passed Passed

Not an AGC/shader/VideoOut change — no paired validation-layer run required.

@rooklz rooklz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of head 8604e227c9.

  • git range-diff: da7581eec4 is unchanged. The new commit adds one regular-file row, the one I suggested: sceKernelStat(presentName) ... st_blksize != 0x10000 at GuestFilesystem.cpp:1432. present.txt is created at L1037 and nothing removes it before this row (the only unlink near it targets viaMissing and asserts that present still exists).
  • Mutant M1 (always 0x10000) is now killed by the PR's own rows. A regular file gets 512 from the Windows branch, NativeStat.cpp:102, and the host value on POSIX (4096 for a fresh file on APFS here; ext4/xfs/btrfs/tmpfs are also 4096). M2 and M3 (socket / other non-regular) still survive. I had marked them optional, and as you note the socket paths do not go through CopyNativeStat. That leaves /dev/null and an lstat of a symlink, which remain optional.
  • One small caveat, not blocking: != 0x10000 depends on the host. A POSIX filesystem whose preferred I/O size really is 64 KiB (some network or ZFS setups) would make the row fail even though the code is correct. CI and dev hosts use 4096/512, so this is fine in practice.
  • Checks: git diff --check clean; git merge-tree vs upstream main bb04a4f1ea clean; check_conventions.py --base upstream/main passes; CI windows + conventions pass, other lanes skipped (path-filtered).

LGTM.

AI-assisted: yes

@bunbeans2-arch

Copy link
Copy Markdown
Contributor

A Windows run with GCC 16: Windows 11 Pro 10.0.22631, i9-9900K, MSYS2 UCRT64 GCC 16.1.0, Release, main 673fa86 with this PR's diff (8604e227c9) applied.

  • With this PR: guest_filesystem and the three guest_kernel_app_archive_* tests passed 5 of 5.
  • Same tree with only ArchivedFile.cpp and NativeStat.cpp put back to main (this PR's tests kept): in 5 of 5 runs guest_filesystem aborted (0xc0000409) and guest_kernel_app_archive_archive failed with app0.zar kernel check failed at line 390, the new /app0/data st_blksize check.
  • main 673fa86 alone (its own tests and source): the same tests passed 3 of 3, so the failures above come from the checks this PR adds.

AI-assisted: yes (Claude Code)

@oneandonlydean

Copy link
Copy Markdown
Collaborator

At 8604e227c9, could you make the new regular-file block-size assertion compare against the value the host actually supplies, rather than require st_blksize != 0x10000? CopyNativeStat intentionally keeps the host block size for regular files on POSIX, and a valid host value of 64 KiB makes the new GuestFilesystem.cpp row fail with correct production code. The review already notes this for some network/ZFS setups. A native-stat comparison (with the existing Windows convention accounted for), or a controlled conversion fixture with a distinct chosen host value, would still catch an unconditional directory-size assignment without assuming a filesystem value.

ntamas94 and others added 2 commits November 1, 2026 07:53
CopyNativeStat keeps the host block size for regular files on POSIX, so a
64 KiB host (ZFS, some network mounts) makes the st_blksize != 0x10000
assertion fail with correct production code. Compare against the host's
actual st_blksize (POSIX) or the 512 Windows convention, which still
catches an unconditional directory-size assignment spilling onto files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ntamas94

ntamas94 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean good catch — fixed at 144d17f220 with the native-stat comparison you suggested.

The regular-file row now reads the host's own st_blksize (::stat on POSIX) and asserts the guest value matches it, instead of st_blksize != 0x10000. On Windows CopyNativeStat sets a fixed 512 for every non-directory, so that branch asserts == 512. A 64 KiB host (ZFS, some network mounts) therefore no longer fails, since both the host and the guest report 0x10000 there; an unconditional directory-size assignment spilling onto files is still caught on any host whose file block size differs from 0x10000 (the common case).

Latest main merged in; guest_filesystem and the three guest_kernel_app_archive_* pass 3 of 3 here (Windows, MinGW-w64 GCC 15.2.0, Ninja, Release, BUILD_TESTING=ON).

AI-assisted: yes (Claude Code)

@oneandonlydean
oneandonlydean merged commit c51c471 into boykopovar:main Nov 1, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants