Skip to content

fix(libc): copy count units of a strxfrm or wcsxfrm result that does not fit, as titles' libc.prx does - #11983

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
JJenkx:windows/libc-xfrm-truncation
Oct 28, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
JJenkx:windows/libc-xfrm-truncation

Conversation

@JJenkx

@JJenkx JJenkx commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

What

strxfrm and wcsxfrm wrote count - 1 characters and a terminator when the transformed string did not fit. The libc.prx that titles ship does something different: it copies the first count characters and writes no terminator. When the result fits, it copies the whole string with its terminator. Both functions now do the same. The return value, the source's length, is unchanged.

call titles' libc.prx main
strxfrm(buf, "abcdef", 1) a \0
strxfrm(buf, "abcdef", 4) abcd abc\0
strxfrm(buf, "abcdef", 6) abcdef abcde\0
strxfrm(buf, "abcdef", 7) abcdef\0 abcdef\0
wcsxfrm(buf, u"much too long", 4) much muc\0

All return 6 or 13 on both sides. C leaves the array's contents indeterminate when the result does not fit, so this changes only what a title can observe in that case.

Tested

Windows 11 Enterprise 10.0.22621, Ryzen 9 5950X, WinLibs GCC 15.2.0 UCRT r7, Ninja, Release, BUILD_TESTING=ON, on main e2a5118839.

  • Comparison: a scratch loader runs the decrypted libc.prx of PPSA07923, PPSA13934 and PPSA34008 natively. Each title got 100,000 calls each to strxfrm and wcsxfrm: random strings of 0 to 19 non-null bytes or UTF-16 units, sizes 0 to 25, and a null destination with size 0. The rule above predicts the return value and all 32 bytes or units of the pre-filled destination in every call, on all three titles.
  • guest_strings: the truncated strxfrm checks expect the measured characters: abcd for size 4, abcdefgh for size 8, and a for size 1, with the bytes after them untouched.
  • guest_wide_strings: the wcsxfrm truncation checks expect much for size 4, w for size 1, wide for size 4, and four units of the surrogate case.
  • guest_strings and guest_wide_strings pass in each of 3 runs on the exact head 7c7cc70f2e.
  • Negative control: with main's Strings.cpp and these tests, both abort (exit 0xc0000409).
  • python tools/check_conventions.py --base origin/main and git diff --check pass.
  • Not tested: a console, Linux and macOS. feat(libc): add the C locale helpers _Getctype, _Getcoll, _Getcvt, _Getdateorder, _Strcoll and _Strxfrm #9466 adds the separate Dinkumware helper _Strxfrm and doesn't change these two functions.

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: none
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

AI-assisted (Claude Code).

🤖 Generated with Claude Code

…not fit, as titles' libc.prx does

When the transformed string does not fit, strxfrm and wcsxfrm wrote
count - 1 characters and a terminator. The libc.prx bundled with titles
copies the first count characters and writes no terminator, as a plain
copy of count units would, and copies the terminator only when the whole
result fits. Both functions now do the same; the return value, the
length of the source, is unchanged. The tests that expected the
terminated prefix expect the measured units instead.

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

@MarcZX10 MarcZX10 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.

Reviewed exact head 7c7cc70f2e443d2f20e6afe1209a96b3933f716a.

The implementation and added assertions match the reported title behavior: when the source length is below count, it copies the terminating unit; otherwise it writes exactly count bytes/UTF-16 units and leaves the following destination unit untouched. A zero count avoids writing, and the return remains the source length. The tests cover fit, truncation, count one, zero count/null destination, and UTF-16 surrogate data.

Local verification: Release build of guest_string_tests and guest_wide_string_tests succeeded; guest_wide_strings passed natively; guest_strings passed under QEMU Haswell because the host Xeon lacks AVX2 and the other required guest CPU features; conventions and git diff --check passed; the current-main merge preview is clean. Hosted Windows, Ubuntu register-context, Ubuntu/Windows relinker, ASan relinker, conventions, eligibility, intake, labels, and report checks passed. Linux product was skipped; macOS relinker/register-context/write-watch checks are still queued. I did not run on a PS5 console. The native title comparison described in the PR is author-provided evidence.

@github-actions

github-actions Bot commented Oct 27, 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 5021 core/libs/prx/libc/src/Strings.cpp
PR 11664 core/libs/tests/GuestStrings.cpp

@JJenkx

JJenkx commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Linux run at this PR's exact head 7c7cc70f2e (7c7cc70f2e443d2f20e6afe1209a96b3933f716a), not merged onto anything else; its base on main is e2a5118839.

Arch Linux, kernel 7.2.9, Ryzen 9 7950X, g++ (GCC) 16.2.1 20260810, glibc 2.44, x86-64. Release, BUILD_TESTING=ON.

Passed in each of 3 runs, exit code 0 each time, with no failed, skipped or not-run tests:

  • guest_strings
  • guest_wide_strings

These are the focused tests only; no title or console was used.

AI-assisted: yes (Claude Code).

@github-actions github-actions Bot added the libc label Oct 27, 2026
@oneandonlydean oneandonlydean self-assigned this Oct 28, 2026
@oneandonlydean
oneandonlydean merged commit 6e30e67 into boykopovar:main Oct 28, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants