Skip to content

fix(libSceSaveData.native): report a directory's own block count and free blocks - #5173

Closed
Mouad-Scriptz-0 wants to merge 1 commit into
boykopovar:mainfrom
Mouad-Scriptz-0:fix/savedata-directory-blocks
Closed

Mouad-Scriptz-0 wants to merge 1 commit into
boykopovar:mainfrom
Mouad-Scriptz-0:fix/savedata-directory-blocks

Conversation

@Mouad-Scriptz-0

Copy link
Copy Markdown
Contributor

What

sceSaveDataGetMountInfo and sceSaveDataDirNameSearch reported every directory as 32768 blocks with 32768 free. A mount that creates a directory now records the block count it asked for in a .blocks file next to the directory's .param file, and both calls report that count with the free blocks reduced by the directory's files in 32 KiB blocks. sceSaveDataDelete removes the record with the directory. A directory with no record (made before this change, or by hand) reports its files rounded up to the 96-block minimum as its allocation; opening one does not record the opening mount's count, which is not the creation size.

Minecraft (PPSA17221) sums the allocations of its save directories (the search's blocks, taken as 64 KiB each) against a 9 GiB budget and sets its low-storage state when less than 100 MiB is left (full below 10 MiB): five directories at the 32768-block maximum summed to 10 GiB, so it showed "You are almost out of data storage space" and refused to open or create worlds (read out of the title with gdb: used 10 GiB, total 9 GiB, available 0). With the recorded sizes (80 blocks for its settings, 1535 for its level cache) the sum is a few hundred blocks, worlds open and create, and the world list shows a world's real size (1.4 MB where it showed 0.01 MB).

Tested

Fedora 44 (kernel 7.2.8, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON), worktree on main 5d50886: new guest_savedata_blocks (a created directory reports its requested blocks, free blocks drop with files rounded up to 32 KiB blocks, the search agrees with the mount info, reopening with another count keeps the created size, delete removes the record, opening a directory without a record records nothing); a directory without a record reports its files rounded up to the minimum and grows with them; guest_savedata_search and guest_savedata_delete still pass; tools/check_conventions.py --base origin/main and git diff --check pass. Minecraft: worlds open and create again, and the world list shows a world's real size (1.4 MB where it showed 0.01 MB); the dialog stayed away through a play session. Not tested on Windows.

Checklist

  • Follows docs/dev/CONVENTIONS.md
  • Tests added or updated
  • No new technical debt without an entry
  • Unblocks: PPSA17221 (no Game bring-up discussion yet)
  • AI-assisted: yes (Claude Code).

🤖 Generated with Claude Code

…free blocks

Mount info and the directory search reported every directory as 32768
blocks with 32768 free. A creating mount now records the block count it
asked for next to the directory's parameters, and both calls report that
count less the directory's files in 32 KiB blocks; delete removes the
record. A directory without a record reports the maximum as before.
Minecraft sums its directories before opening a world and refused with
"almost out of data storage space" at five times the maximum.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
EagleBearerYS added a commit to EagleBearerYS/AnyPS5 that referenced this pull request Oct 13, 2026
boykopovar#5173 changes the create condition right after the last error check, and boykopovar#3950 removes the comment after the create block, so clearing the result in either place made the pull requests conflict. The result is still cleared only after every error return and before anything is written to it.
@johnmaia

Copy link
Copy Markdown
Contributor

Windows run, since the description says Windows is untested. Setup: Windows 11 (26300), WinLibs GCC 15.2.0 r7, Python 3.12, Git for Windows, Release, BUILD_TESTING=ON, ctest -j4 --timeout 300, RTX 4080. Head e8d947d merged into main 69b4ca5.

  • guest_savedata_blocks, guest_savedata_search and guest_savedata_delete pass.
  • The full suite passes 1082 of 1082.

Not tested: a negative control on Windows, and a title.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
RINNECODER added a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 14, 2026
… nid and others) (#489)

* fix(nid): validate ELF table layouts before patching

(cherry picked from commit abe8fb8)

* test(nid): avoid competing hash test registration

(cherry picked from commit 617e7ac)

* fix(avplayer): resume the presentation clock when the source restarts after a pause

Clock::Reset left the paused flag set by Clock::Pause, so a Start issued
from the paused state (stop and restart of the source) kept the clock
frozen at the start position. Only the frame at that position was
delivered; later frames were held back by the default A/V sync.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit a671dc4)

* fix(libSceNet): accept MSG_DONTWAIT in the send and receive calls

sceNetSend, sceNetSendto, sceNetRecv, sceNetRecvfrom, sceNetRecvmsg and
sceNetSendmsg failed with EOPNOTSUPP for any flag other than MSG_PEEK on
a receive, so a game polling a socket with MSG_DONTWAIT got an error on
every call instead of its data or EWOULDBLOCK.

FreeBSD 11.4 defines the flag in sys/sys/socket.h:

	#define	MSG_DONTWAIT	0x80		/* this message should be nonblocking */

and soreceive_generic() (sys/kern/uipc_socket.c) treats it like a
non-blocking socket for that one call:

		if ((so->so_state & SS_NBIO) ||
		    (flags & (MSG_DONTWAIT|MSG_NBIO))) {
			SOCKBUF_UNLOCK(&so->so_rcv);
			error = EWOULDBLOCK;
			goto release;

shadPS4's sceNet uses the same value (ORBIS_NET_MSG_DONTWAIT 0x80).

On Linux and macOS the flag now maps to the host's MSG_DONTWAIT, alone
or with MSG_PEEK on a receive. Winsock has no per-call non-blocking
flag, so Windows keeps failing with EOPNOTSUPP for it; TechnicalDebt
records that. Other flags still fail with EOPNOTSUPP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 44d46ee)

* fix(audioout): serialize shared SDL audio initialization

(cherry picked from commit c35572e)

* test(audioout): detect overlapping backend initialization

(cherry picked from commit 5031432)

* feat(net): implement memory-pool netstat diagnostic

(cherry picked from commit 565fafa)

* fix(libSceVideoOut): return RESOURCE_BUSY when a bus is opened twice

(cherry picked from commit c15e9f2)

* fix(savedata): leave the mount result untouched when sceSaveDataMount3 fails

A title checks for its save with a CREATE2 mount it never
unmounts, then saves with a second mount of the same directory. That
mount returns BUSY, and the title keeps using the first mount point for
sceSaveDataPrepare, its save file and sceSaveDataUmount2.
sceSaveDataMount3 cleared the result before its checks, so the save was
written to "/save" in the game folder instead of the save directory, and
the first mount was never released. The result is now written only when
the mount succeeds, as shadPS4 does.

(cherry picked from commit 4ccf888)

* fix(libSceNet): report SO_ERROR from sceNetGetsockopt

(cherry picked from commit 0daf1d5)

* fix(savedata): report a save's size as the mount that created it gave

Every native save reported SAVE_DATA_BLOCKS_MAX blocks, all of them free,
in the sceSaveDataDirNameSearch infos and in sceSaveDataGetMountInfo,
whatever the title created it with and whatever its files held. A save's
size is now the blocks of the sceSaveDataMount3 call that created it,
kept beside the save; a save without a kept size takes that of its next
mount that names one, and otherwise keeps SAVE_DATA_BLOCKS_MAX. Free
blocks are what the save's files leave of it, each file rounded up to
32 KiB blocks, and the blocks and free-blocks sort keys use both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4b7a357)

* fix(savedata): return no saves for a search with another title's ID

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d34b788)

* feat(libScePosixForWebKit): implement getservbyname

Implement getservbyname (H1bArJPagV0) in libScePosixForWebKit. A title imports it from this library only (registry index 408,
call site 0x1042124); once fnmatch (boykopovar#3545) is in, its predicted load
stop is:

    FAIL: unresolved ELF import H1bArJPagV0 from libScePosixForWebKit.prx

The ps5-payload-dev SDK stubs (f5d40f02, sce_stubs/) declare
getservbyname in libScePosixForWebKit.c and not in libkernel.c or
libSceLibcInternal.c. The call site is the service-name branch of a
getaddrinfo inside the title: a non-numeric service is looked up with
protocol "tcp", "udp" or "sctp" and the port is read big-endian at
offset 16 of the result, which fixes the servent layout used here
(name, aliases, 32-bit port, protocol at 0, 8, 16, 24).

The guest has no /etc/services and the console's file is unknown, so the
function searches 37 lines of FreeBSD 11.4 etc/services (8c639945):
ftp-data, ftp, ssh, telnet, smtp, domain, tftp, http, pop3, ntp, imap,
https, rtsp, submission, imaps and pop3s with their aliases and
protocols, in file order. None of their names or aliases appears on an
earlier line of the full file, so these lookups answer as the full file
would. The search follows files_servent in lib/libc/net/getservent.c
(8c639945): exact, case-sensitive match on the name or an alias, a null
protocol takes the first match, the port in network byte order, the
result in per-thread storage like getserv's NSS TLS state. Any other
name returns null without touching errno, as for a name the file lacks.
The repository's getaddrinfo still resolves service names through the
host, so there was no table to share; this is recorded in
TechnicalDebt.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 15bbc19)

* fix(libScePosixForWebKit): keep the getservbyname result trivially destructible

The per-thread servent storage held std::string and std::vector
members, so getservbyname_nid_postfix registered a thread_local
destructor through __cxa_thread_atexit. On Windows that destructor runs
after emulated TLS freed the object, and prx_thread_local_destructors
(boykopovar#4029) rejects libScePosixForWebKit.prx for it.

The result now lives in a thread_local struct of fixed-size char arrays
and an alias pointer array, sized for the longest name (submission),
alias (www-http), protocol (sctp) and alias count in the embedded table;
static_asserts check the sizes against the table and that the struct is
trivially destructible. The guest servent layout and the per-thread
result FreeBSD's getservent.c keeps in its NSS TLS state are unchanged.
Nothing is allocated, so the ENOMEM path is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 8b9529b)

* test(libSceConvertKeycode): cover sceConvertKeycodeGetImeKeyboardType

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0afa191)

* feat(ces): implement sceCesUcsProfileInitSJis1997Cp932

(cherry picked from commit e5f0938)

* refactor(savedata): clear the mount result once the mount point is made

boykopovar#5173 changes the create condition right after the last error check, and boykopovar#3950 removes the comment after the create block, so clearing the result in either place made the pull requests conflict. The result is still cleared only after every error return and before anything is written to it.

(cherry picked from commit 6a46563)

* fix(libSceKeyboard): report one key slot when no key is held

A keyboard record on the console never counts fewer than one key: with
nothing held, and with no keyboard connected, the count is 1 and that
slot holds code 0. SharpProspero's keyboard reader, PS5_Quake3e's notes
on records read from a console and the PS5 input research notes all say
so. AnyPS5 reported a count of 0. The count is now at least 1; the key
slots themselves are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 8ea8ea3)

* fix(libSceNpAuth): report an async request as running on its first poll

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit cb8b0ca)

* feat(libSceConvertKeycode): implement sceConvertKeycodeGetCharacter for the US layout

The signature is inferred from a title call: a 16-bit keycode, a second
argument the title passes as 0, the type from
sceConvertKeycodeGetImeKeyboardType and a UTF-16 output. The keycode is a
USB HID keyboard usage and the unshifted US character is returned; a
keycode without a character returns 0. A second argument or keyboard type
other than 0 throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5ca6487)

* feat(libSceNet): implement sceNetResolverStartNtoa6

Resolve a host name to its first IPv6 address through the host resolver
(getaddrinfo with AF_INET6) and write the 16-byte address. The lookup
shares the IPv4 path's resolver bookkeeping (abort preservation, error
state); a name without an IPv6 address fails with RESOLVER_ENODNS like a
failed lookup. Nonzero flags throw.

A title imports it and failed to
load without it (unresolved zl35YNs9jnI from libSceNet.prx).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit df32a44)

* fix(libSceNet): dissolve a datagram association on connect to AF_UNSPEC

sceNetConnect() on a connected datagram socket rejected an address
whose family is AF_UNSPEC (a null address) with EINVAL before touching
the socket, so the socket stayed connected: sceNetGetpeername() still
reported the old peer and datagrams from any other sender were dropped
by the host.

FreeBSD 11.4 disconnects a connected datagram socket first and only then
looks at the address. sys/kern/uipc_socket.c, soconnectat():

	 * If protocol is connection-based, can only connect once.
	 * Otherwise, if connected, try to disconnect first.  This allows
	 * user to disconnect by connecting to, e.g., a null address.
	 */
	if (so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING) &&
	    ((so->so_proto->pr_flags & PR_CONNREQUIRED) ||
	    (error = sodisconnect(so)))) {

udp_disconnect() (sys/netinet/udp_usrreq.c) clears the peer and keeps
the local port. connect(2): "Datagram sockets may dissolve the
association by connecting to an invalid address, such as a null
address." libkernel's connect gets the same change in boykopovar#4498.

A connected datagram socket given an address whose family byte is 0 is
now disconnected on the host first (AF_UNSPEC on Linux, a zeroed
address of the socket's family on Windows), and the call then fails
with EINVAL as before. Linux also drops an ephemeral local port on that
disconnect, so the port is bound again on the wildcard address, which
is what FreeBSD leaves. A host failure during the disconnect is
returned as the call's error. Stream sockets keep the previous path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 65d56b3)

* fix(libSceNet): fail the AF_UNSPEC connect with EAFNOSUPPORT as FreeBSD does

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit fce9538)

* fix(libSceOpusDec): decode narrowband SILK packets

FFmpeg resamples narrowband SILK (TOC configurations 0-3) from 8 kHz and
holds back 24 samples of the first narrowband packet after the decoder
starts or after a packet of another bandwidth: a 20 ms packet decodes to
936 samples and the next wider packet to 984. sceOpusDecDecode required
exactly the packet's duration and threw, so ProsperoRadio aborted on a
narrowband Opus station.

The decoder now keeps FFmpeg's output in a queue and returns exactly the
packet's duration, as libopus does: the first narrowband packet starts
with 24 samples of silence and the held-back samples follow in the next
calls. The new test decodes three libopus 6 kb/s narrowband packets and
a wideband packet after them; it aborts on main.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit fb41f10)

* fix(http2): allow completion waits without an event queue

(cherry picked from commit 28bcd6b)

* feat(libSceNgs2.native): implement custom sampler rack id 0x4001 (boykopovar#4334)

(cherry picked from commit 855823e)

* feat(libSceNgs2.native): add custom voice filter support and related tests for 0x13 issue case

(cherry picked from commit ef52b16)

* test(libSceNgs2): fix custom filter regression expectations

(cherry picked from commit 852c808)

* fix(ngs2): resolve voice patches to rack handles

(cherry picked from commit 8b0da18)

* docs: drop title IDs from ported test and debt text

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

---------

Co-authored-by: MiniRML <134226498+MiniRML@users.noreply.github.com>
Co-authored-by: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: milescoviello <kitslayer29@gmail.com>
Co-authored-by: PrintOrb <donhou45@gmail.com>
Co-authored-by: krishalivaibhav <krishalivaibhav@gmail.com>
Co-authored-by: babyhuey <7751607+babyhuey@users.noreply.github.com>
Co-authored-by: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com>
Co-authored-by: bubblegg <86165363+bubblegg@users.noreply.github.com>
Co-authored-by: SandstromPL <paritoshlahre@gmail.com>
Co-authored-by: screamm <davidrydgren@gmail.com>
Co-authored-by: melihcan <melihcan.abay@students.fhnw.ch>
Co-authored-by: Kikoarl <7354010+Kikoarl@users.noreply.github.com>
Co-authored-by: Rick <99503318+0xRickx@users.noreply.github.com>
Co-authored-by: Sasha Papo <sasha.papo@gmail.com>
Co-authored-by: codingreapermaps <233954859+codingreapermaps@users.noreply.github.com>
Co-authored-by: ps54all <ps5@4.all>
Co-authored-by: Adrià Franch <adriadam10@gmail.com>
Co-authored-by: eliasbruno124-dev <eliasbruno124@gmail.com>
Co-authored-by: Jnbla6 <badrsha321@gmail.com>
Co-authored-by: MarcZX100 <marczxrr@gmail.com>
@Zenodeon

Copy link
Copy Markdown
Contributor

Windows validation of the submitted head e8d947d31cfdde0d8e27b4563e2a73a416fdad25:

  • Native Windows x86-64 (10.0.26200), WinLibs MinGW-w64 GCC 15.2.0 r7, Ninja Release, Python 3.12.14, BUILD_TESTING=ON, pinned submodules.
  • Configured the normal runtime project and built the save-data targets with the actual libc/libkernel/save-data/dialog libraries.
  • All 8 selected tests passed: guest_savedata_blocks, guest_savedata_search, guest_savedata_delete, guest_savedata_convert, guest_savedata_mount_status, guest_savedata_ps4, guest_savedata_cancel, and guest_savedata_dialog (0.21 s total, 30 s per-test limit).

An additional compatibility probe found a problem with allocation records produced by current main. Reproduction through the actual library:

  1. Mount a new save with CREATE2|RDWR and 80 blocks.
  2. Replace its adjacent .blocks file with the binary bytes 80\r\n.
  3. Call sceSaveDataGetMountInfo.

On this PR, the process terminates with:

SaveData: blocks file _sd/NEWLINE.blocks is not a number

Exit code: 0xC0000409. The same probe executable against rebuilt main 31bfc4e86 succeeds and reports blocks=80 free_blocks=80. Main's keep_save_blocks writes the number followed by a newline using a text-mode stream, producing this CRLF format on Windows; this PR's directory_blocks rejects every non-digit byte.

Main has also merged #5094, which already records allocations, calculates free blocks, and deletes the sidecar. This PR currently conflicts in Export.cpp. The legacy-save behavior still differs: this PR infers a minimum 96-block allocation from usage, whereas main reports the maximum until a mount supplies an allocation. That difference needs to be reconciled separately from the newline compatibility fix.

For comparison, main's 7 corresponding save-data/dialog tests passed (0.57 s total); it does not register guest_savedata_blocks. This is targeted native Windows validation, not a complete runtime suite or Minecraft/title validation.

AI-assisted: yes (OpenAI Codex).

@adriadam10 adriadam10 self-assigned this Oct 14, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Closing: #5094 (merged today) already records the creating mount's block count in the same <dir>.blocks file and reports it from both sceSaveDataGetMountInfo and sceSaveDataDirNameSearch (save_blocks/search_info in Export.cpp), so this PR now conflicts with main in Export.cpp and duplicates that mechanism.

What is not on main is the fallback for a directory with no .blocks record (its files rounded up to the 96-block minimum instead of 32768) and create2 && !exists when recording. If you still want those, please reopen or send a small PR on top of current main with just that part, reusing the existing search_info helpers and a test that fails without it. The Conventions check on the current head is also red, so please check it when you rebase.

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

Labels

build docs needs rebase Conflicts with main, rebase needed prx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants