Skip to content

fix(libSceSaveData.native): count PS5 save data in 64 KiB blocks, at most 16384 - #7960

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
Lindamea1:fix/savedata-ps5-64k-blocks
Oct 20, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
Lindamea1:fix/savedata-ps5-64k-blocks

Conversation

@Lindamea1

Copy link
Copy Markdown
Contributor

What

libSceSaveData.native counts save data in PS4 units. SAVE_DATA_BLOCK_BYTES is 32768 and SAVE_DATA_BLOCKS_MAX is 32768: the PS4's 32 KiB block and the top of its 96 to 32768-block range (shadPS4 savedata.cpp). SharpEmu uses a 64 KiB block, and SharpEmu and KytyPS5 allow 48 to 16384 blocks per save, which at 64 KiB a block is 3 MiB to 1 GiB, the PS4's byte range:

On main, the sceSaveDataDirNameSearch infos and sceSaveDataGetMountInfo therefore count up to twice the 64 KiB blocks a save's files use. A save without a recorded size reports 32768 blocks, which is 2 GiB at 64 KiB a block. A scratch program run against main 5eba12d creates a save with sceSaveDataMount3 (blocks 48, mode 0x21), writes a 1,000,000-byte file, unmounts and searches. The search returns blocks=48 free=17, so 31 blocks are used. With 64 KiB blocks the file takes 16 and leaves 32 free.

Titles read these counts as 64 KiB blocks:

This change:

  • Sets SAVE_DATA_BLOCK_BYTES to 65536 and SAVE_DATA_BLOCKS_MAX to 16384. Each file is rounded up to 64 KiB blocks, and a save without a recorded size reports 16384.
  • A blocks above 16384 is now kept as 16384 (the existing clamp was at 32768), as SharpEmu clamps it. A save already recorded above 16384 now reports 16384.
  • Moves the block accounting out of the sceSaveDataDirNameSearch debt entry into its own entry for sceSaveDataDirNameSearch, sceSaveDataGetMountInfo and sceSaveDataMount3, which states the gaps first. Both entries stay under 1000 characters.

Not measured on a console. The console's file system overhead is not counted. A blocks above 16384 is clamped to 16384 and a non-zero one below 48 is kept as given, where KytyPS5 rejects such a creating mount; what the console does is unknown.

Interplay with #7780 (open): it builds libSceSaveData, which PS4 titles import, from these sources. With both merged, that module would also count 64 KiB blocks up to 16384, while PS4 saves use 32 KiB blocks up to 32768 (shadPS4 above; SD_BLOCKS_MAX is 32768 in the SaveDataCore.hpp that #7780 removes). On main only libSceSaveData.native is built from these sources, so the values stay constants in the module. Whichever of the two merges second can give the PS4 target its own values, for example as compile definitions set in #7780's SaveDataLibrary.cmake. #6977 (open) adds a PS4 sceSaveDataMount to this module itself, and it passes the title's blocks, counted in 32 KiB, to sceSaveDataMount3 unchanged. With this change those would be read as 64 KiB blocks.

Tested

Gentoo Linux, kernel 7.2.8, GCC 16.2.1, glibc 2.43, Ninja, Release, BUILD_TESTING=ON, on main d047690.

  • guest_savedata_search (core/libs/tests/GuestSaveDataSearch.cpp) now checks:
    • A save without a recorded size reports 16384 blocks, all free (was 32768).
    • The 96-block save with a 40,000-byte file reports 95 free blocks, both in the search infos and in sceSaveDataGetMountInfo (was 94).
    • New: a 48-block save with a 70,000-byte file reports 46 free. That is 2 blocks at 64 KiB, where 32 KiB gives 3.
    • New: a save created with 32768 blocks reports 16384.
    • The mount-info lines of the existing check move into a MountInfo helper that the new checks share.
  • Negative control, with the library rebuilt each time:
    • With Export.cpp and SaveData.hpp from main, the test fails with search info must report the save's blocks and free blocks. In a scratch copy of the test, Require reports and continues. That copy shows each of the 5 changed or new assertions failing on the main sources and every other assertion passing.
    • With only Export.cpp from main, the test fails with a save must report the blocks it was created with, less those its files take. The mount-info and 64 KiB checks fail too.
    • With only SaveData.hpp from main, it fails at the unsized-save check, and the 16384 check fails too.
    • With this change, every assertion passes.
  • The scratch program from What reports blocks=48 free=32.
  • Tests the change reaches, selected by tools/select_tests.py as CI selects them: 219 passed, 0 failed, 0 skipped. They include the six guest_savedata_* tests of libSceSaveData.native, the four savedata_memory_* tests, savedata_native_write_replacement and patched_libraries.
  • tools/check_compile.py --base upstream/main: 2 translation units compile.
  • Windows: Export.cpp and GuestSaveDataSearch.cpp compile without warnings with x86_64-w64-mingw32-g++ 16.2.0 -fsyntax-only -Wall -Wextra, as on main. This is a syntax check, not a Windows build or run.
  • tools/check_conventions.py --base upstream/main with this title and body: all checks passed.
  • tools/pr_overlap.py --branch reports no overlapping lines. It could not compare test: organize library tests and label CTest suites #6044, feat(wasm): run relinked titles in the browser with WebGPU #6879 and feat: run PS4 games on the AGC driver — Limbo (CUSA01664) in game (integration branch) #6977, which conflict with main; merged with this branch, feat(wasm): run relinked titles in the browser with WebGPU #6879 and feat: run PS4 games on the AGC driver — Limbo (CUSA01664) in game (integration branch) #6977 conflict in the same files as they do with main, and fix(savedata): build libSceSaveData and libSceSaveDataDialog from the native sources #7780 merges cleanly.

Not run on Windows, macOS or a console.

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 (see the fix(savedata): build libSceSaveData and libSceSaveDataDialog from the native sources #7780 note above)
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

🤖 Generated with Claude Code

…most 16384

The PS5 references count save data differently from the PS4: SharpEmu
uses a 64 KiB block, and SharpEmu and KytyPS5 allow 48 to 16384 blocks
per save, which at 64 KiB a block is 3 MiB to 1 GiB, the PS4's byte
range. The module used the PS4 values, 32 KiB blocks and a 32768-block
maximum. At 64 KiB a block, sceSaveDataDirNameSearch and
sceSaveDataGetMountInfo therefore counted up to twice the blocks a
save's files use, and a save without a recorded size reported 32768
blocks, which is 2 GiB.

The search test now expects 64 KiB blocks and the 16384 maximum, and
the block accounting gets its own technical debt entry.

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

github-actions Bot commented Oct 19, 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 6044 core/libs/tests/GuestSaveDataSearch.cpp
PR 6977 core/libs/prx/libSceSaveData.native/Export.cpp

@oneandonlydean oneandonlydean self-assigned this Oct 20, 2026
@oneandonlydean
oneandonlydean merged commit d243cc5 into boykopovar:main Oct 20, 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.

2 participants