Skip to content

feat(libs): implement C runtime re-exports for libSceAmpr - #545

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
Pouare514:feat/ampr-c-runtime
Oct 11, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
Pouare514:feat/ampr-c-runtime

Conversation

@Pouare514

@Pouare514 Pouare514 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

What

libSceAmpr and libc re-export part of the C runtime (statically linked CRT). 12 of the 36 unknown libSceAmpr NIDs are identified by NID hash (verified by recomputation) as standard C functions and implemented with real semantics instead of throwing stubs: localeconv (Sony's 0x90-byte Dinkumware struct), __udivti3 (manual 128-bit division, no libgcc dependency; a zero divisor throws), strtok_s, ctime_s, wcscpy_s, mbstowcs, asctime_s (takes the guest GuestTm), __fpclassifyd (Dinkumware codes), _Sinh, _Fltrounds (C FLT_ROUNDS values), set_constraint_handler_s and ignore_handler_s. The semantics live once in SonyCrt.hpp and are exported both by libc (real names) and libSceAmpr (sceAmpr* forwarders). This unblocks Deathloop past its first Ampr call (128-bit division during init).

The remaining 24 (C++ runtime: _Pad, num_get/time_get facets, shared_ptr locks, _Thrd/_Cnd, vtables) keep throwing stubs for follow-ups.

Tested

  • Windows Release Ninja: libs target builds clean; Deathloop run gets past Ampr init to the next wall (sysmodule 272 load request) instead of aborting in __udivti3.

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 in TechnicalDebt
  • 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: feat(libs): Deathloop loading support (Ampr, NetCtlApDialog + stubs) #407 (adds the libSceAmpr module)
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Progress report (5c4bc7d → af9dfbf)

📈 System libraries: 85.55% (+0.11%, +24 implemented functions)

✅ 24 implemented
Library Name
libSceAmpr sceAmprAsctimeS
libSceAmpr sceAmprCtimeS
libSceAmpr sceAmprFltrounds
libSceAmpr sceAmprFpclassifyd
libSceAmpr sceAmprIgnoreHandlerS
libSceAmpr sceAmprLocaleconv
libSceAmpr sceAmprMbstowcs
libSceAmpr sceAmprSetConstraintHandlerS
libSceAmpr sceAmprSinh
libSceAmpr sceAmprStrtokS
libSceAmpr sceAmprUdivti3
libSceAmpr sceAmprWcscpyS
libc _Fltrounds_nid_postfix
libc _Sinh_nid_postfix
libc __fpclassifyd_nid_postfix
libc __udivti3_nid_postfix
libc asctime_s_nid_postfix
libc ctime_s_nid_postfix
libc ignore_handler_s_nid_postfix
libc localeconv_nid_postfix
libc mbstowcs_nid_postfix
libc set_constraint_handler_s_nid_postfix
libc strtok_s_nid_postfix
libc wcscpy_s_nid_postfix

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks. This one carries #407's commit, so it can only go in after #407 (or split from it). I also compared the twelve functions with Sony's own CRT: the libc.prx that PPSA21564 ships exports all twelve of these NIDs, and several behave differently from this PR:

  1. __fpclassifyd returns the Dinkumware codes: NaN 2, inf 1, zero 0, subnormal -2, normal -1. Here zero, subnormal and normal come out as 0x10, 0x08 and 0x04.
  2. _Fltrounds reads the x87 rounding field and maps it through {1, 3, 2, 0}: nearest 1, down 3, up 2, toward zero 0 (the C FLT_ROUNDS values). Here nearest is 0, down 1 and toward zero 3.
  3. _Sinh takes two doubles and returns y * sinh(x) (it reads xmm1 and multiplies by it). Here it takes one.
  4. localeconv returns Sony's static lconv, which has decimal_point at offset 0x48 (seven pointers and fourteen CHAR_MAX bytes come first). The host's struct has it at 0, so a guest reading decimal_point gets a different field.
  5. wcscpy_s reads 16-bit characters and calls the constraint handler on errors. The guest wchar_t is 16 bits (see WindowsFormatting.hpp), so host wchar_t is wrong on Linux here and in mbstowcs.
  6. set_constraint_handler_s takes a lock and installs the default handler when passed NULL; here NULL is stored and the initial handler is NULL.
  7. __udivti3 returning 0 for a zero divisor: the real one traps, so this should throw.

These are libc exports. If the title's import table really names libSceAmpr for them, could you check that, and implement them once where both libraries can share them instead of keeping a second copy here?

Unrelated to the CRT part, #407 exports T4ucGB8CsnM and 5tRaBjtdTzY from libSceWebBrowserDialog, but they are sceVideoOutVrrUnpegFromFixedRate and sceVideoOutVrrPegToFixedRate (recomputed), which #518 adds to VideoOut. They should go from #407.

@Pouare514

Copy link
Copy Markdown
Contributor Author

Reworked per review (rebased onto the new #407, so this is now Ampr-only on top of it; forced update after the rebase): checked all twelve NIDs against the Deathloop ELF — each is imported only from libSceAmpr (IMPORT_LIB 48 / NEEDED 43), so Ampr keeps the exports. The implementations now live once in tools-visible SonyCrt.hpp, compiled into both libc (as real-name exports) and libSceAmpr (as sceAmpr forwarders), which also avoids cross-module calls for the same NID. All seven correctness items addressed: Dinkumware __fpclassifyd codes, C FLT_ROUNDS mapping via fegetround, two-argument _Sinh, Sony lconv layout with decimal_point at 0x48, 16-bit wcscpy_s/mbstowcs with constraint-handler invocation, locked handler with aborting default installed on NULL, and __udivti3 throwing on a zero divisor. New guest_crt test covers all twelve through the Ampr interface (udivti3 values plus the throw, handler counting, layout offset, formats). Notes: the test links libc for __gxx_personality_v0 and uses configure_windows_unwind like the other guest tests (a -fasynchronous-unwind-tables variant broke unwinding with our personality); udivti3 is exercised through the shared header because cross-prx __int128 calls from a MinGW caller come back garbled. 184/184 ctest green locally.

@Pouare514

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (this should also restart CI, which never ran for the previous push): kept both sides where main added code around this PR's areas, and dropped my two Http throwing stubs whose NIDs #555 now implements for real. Full ctest (222) passes locally.

@Pouare514
Pouare514 force-pushed the feat/ampr-c-runtime branch from d4e5819 to a0b2683 Compare October 5, 2026 00:54
@Pouare514
Pouare514 force-pushed the feat/ampr-c-runtime branch 2 times, most recently from 27e0cc4 to 5570341 Compare October 5, 2026 01:12
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, this covers all seven points: the Dinkumware __fpclassifyd codes, the FLT_ROUNDS mapping, two-argument _Sinh, the lconv layout with decimal_point at 0x48, 16-bit wcscpy_s/mbstowcs with the constraint handler, the locked handler with an aborting default for NULL, and __udivti3 throwing on zero. Implementing them once in SonyCrt.hpp for both libc and libSceAmpr is a good way to do it, and the TechnicalDebt lines record the parts that are assumed. The twelve Ampr NIDs are the ones #407 used to stub, and none is exported elsewhere.

Two small, optional points:

  • ctime_s and asctime_s return EINVAL without calling the constraint handler, where wcscpy_s calls it. If Sony's do the same as the other _s functions, they would call it too.
  • mbstowcs widens each byte as it is, with no multibyte decoding. That's what the C locale would do, but a TechnicalDebt line saying it's assumed would help.

This carries #407's commits, so it inherits the Linux CI failure I described on #407 (eight libSceAmpr names that interpose libstdc++). guest_crt links libSceAmpr too, so it would hit the same problem. Once #407 has the fix and this is rebased on it, it's ready to merge from my side, after #407.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

This carries #407's fd3b24e, so the libstdc++ interposition is fixed here too. The Linux CI job, which failed on the previous head, is green now.

Since #338 landed, two files conflict with main:

Your own commit is the same as the one I looked at last time (only the __cxa_call_unexpected_nid_postfix context differs), and the two optional points from my last comment are still optional. After #407 and this rebase, it's ready to merge from my side.

@Pouare514

Copy link
Copy Markdown
Contributor Author

Rebased onto the new #407 (fork/feat/deathloop-loading @ 993ccfa). Git auto-skipped the #407 commits; I skipped the remaining redundant 4231855 (already superseded by fae345a + _nid_postfix fix) and replayed only our own commit as 2a3a2a7.

TechnicalDebt.md: kept main/#338 lines first (Http/Http2/Net/SessionSignaling/Trophy/SystemService/Voice + snwprintf_s + Ult queues), then our 3 CRT lines (localeconv, set_constraint_handler_s, __udivti3) after them, before the kernel UnknownNN lines.

RuntimeSupport.cpp: auto-merged cleanly - kept upstream state plus our __udivti3_nid_postfix (SonyUdivti3); snwprintf_s stays in FormattingWide.cpp as _nid_postfix, so both functions are preserved. libSceAmpr/Export.cpp: kept both sides - upstream _nid_postfix runtime renames plus our 12 sceAmprXxx forwarders calling SonyXxx with SonyCrt.hpp include; no cross-prx _nid_postfix calls, no externs.

Optional points (ctime_s/asctime_s constraint handler, mbstowcs TechnicalDebt line) left out as agreed. Ready after #407.

@Pouare514

Copy link
Copy Markdown
Contributor Author

Rebased onto the new #407 (998016e, itself rebased for the #676/#369/TD points; forced update 2a3a2a7...0647694). Rebase --onto old #407 base, so only our own commit replayed — no stale #407 commits carried.

Resolution keeps both sides everywhere: TechnicalDebt.md has main's lines, then our 3 CRT lines (localeconv, set_constraint_handler_s, __udivti3), then #407's kernel lines. RuntimeSupport.cpp only adds the SonyCrt.hpp include + __udivti3_nid_postfix (snwprintf_s untouched upstream in FormattingWide.cpp). Ampr keeps upstream _nid_postfix renames plus our 12 sceAmprXxx forwarders calling SonyXxx, no cross-prx _nid_postfix calls.

#676: nothing to drop here — the libSceNpCppWebApi replacement lived in #407's commits, which are gone from this branch after the --onto; diff vs new #407 is Ampr/SonyCrt/libc-wrappers/GuestCrt test/TD only. Optional points left out as agreed. Ready after #407.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the rework; the semantics were already covered by Dean's review. What blocks it now is a conflict with main that needs a logic change:

  • core/libs/prx/libc/src/Time.cpp: main now converts to the guest's 56-byte GuestTm (GuestTime.hpp, toHostTm, TZ init). asctime_s_nid_postfix (Time.cpp:127) and sceAmprAsctimeS take a host std::tm*, which is 36 bytes on Windows and does not match the guest layout. Take const GuestTm* and convert with toHostTm before formatting.
  • SonyCtimeS (SonyCrt.hpp:73) calls std::localtime directly, bypassing the guest TZ handling localtime_s has on main. Reuse the same path as libc_localtime, and take the time as const int64_t* like the other time functions.
  • core/libs/prx/libc/src/Strings.cpp: conflict at the end of the file, keep main's lines and add strtok_s_nid_postfix after them.
  • core/libs/tests/GuestCrt.cpp:59-68 calls SonyUdivti3 directly; test it through the sceAmpr export and the libc one so the registered exports are covered. Add an asctime_s case with a GuestTm of the guest layout.
  • The PR description is out of date: it says divide-by-zero yields 0 (the code throws) and mentions only Ampr, while the diff also adds libc exports. Please update What.

Please rebase on current main and rerun guest_crt.

@babyhuey

Copy link
Copy Markdown
Contributor

For priority: on main 06da117 this PR is the next stop for four catalog titles on Linux (catalog builds with sce_module/libc.prx unwrapped as #2605 requires; tracked in #1578):

Title Line Export
SymphonyStation5 (PPSA17641) undefined symbol: 0hlfW1O4Aa4 localeconv
Cloud Play (PPSA99600) undefined symbol: 0hlfW1O4Aa4 localeconv
PICO-8 (PPSA99808) undefined symbol: 802pFCwC9w0 __udivti3
XPSemu (PPSA97358) undefined symbol: qlWiRfOJx1A __fpclassifyd

Each line is app.elf: symbol lookup error: app.elf: undefined symbol: <NID>.

@adriadam10

Copy link
Copy Markdown
Collaborator

A data point for localeconv, read from Sony's own libc.prx, the one Astro's Playroom (PPSA01325) ships in sce_module/. Its localeconv (0hlfW1O4Aa4, at 0x2570) is lea 0x114050(%rip),%rax; ret, a static struct. Reading that struct with its RELA relocations gives the full Dinkumware layout, 0x90 bytes:

Offset Field Value
0x00–0x30 7 monetary char* (currency_symbol, int_curr_symbol, mon_decimal_point, mon_grouping, mon_thousands_sep, negative_sign, positive_sign) each points to "" (relocated), not null
0x38–0x45 14 char (frac_digits … int_p_sign_posn) all 0x7f (CHAR_MAX), including the int_* ones
0x48 decimal_point "."
0x50, 0x58 grouping, thousands_sep ""
0x60, 0x68 _Frac_grouping, _Frac_sep ""
0x70, 0x78 _False, _True "false", "true"
0x80, 0x88 _No, _Yes ""

Compared with the TechnicalDebt line here ("seven null pointers ... the int_p/int_n tail fields are left zero and the rest follows the host layout"), three things differ:

  • The monetary strings are empty strings, not null. A title that does strlen(localeconv()->currency_symbol), which is valid in the C locale, would crash on null.
  • The int_* chars are CHAR_MAX, not 0.
  • After thousands_sep the layout is Dinkumware's (_Frac_grouping, _Frac_sep, _False, _True, _No, _Yes), not the host's.

The four catalog titles @babyhuey listed import localeconv from libSceLibcInternal. Their bundled libc.prx is a stub library (every export at 0x50), so on the console they get this Sony struct.

I have a branch with just this struct, a static_assert on the layout and a byte-offset test, but I'm not opening it since this PR covers localeconv. Happy to share it if useful.

@renzipego-cpu renzipego-cpu mentioned this pull request Oct 11, 2026
9 tasks done
@Amoenus Amoenus mentioned this pull request Oct 11, 2026
9 tasks done
@Amoenus

Amoenus commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

PPSA99902 original caller ABI evidence (2026-10-11, c34f5b08)

I checked this PR's actual libc implementation at head 0647694ad5b559856beb1ba80bbe0e78bc0cea80, not only its Ampr re-export description. SonyLconv puts seven reserved pointers and 16 bytes before decimal_point (offset 0x48), and SonyLocaleconv() sets the reserved pointers to null. The following original title caller evidence instead expects its decimal-point pointer at 0x00.

Target: PPSA99902 Sonic Unleashed Recompiled 01.003.000, original ELF SHA-256 c34f5b0848ef49e96a855076d8254254047e966e4fe56589d0e504fe13828479. All addresses below are original guest ELF VAs, not relinked/host addresses. The five known direct references to localeconv (0hlfW1O4Aa4, libSceLibcInternal) resolve to GOT 0x4ba43a0:

Import call Following consumption
0x29b5c9b 0x29b5ca1: 48 8b 00, mov rax,[rax]; reads the pointed byte and compares it with '.'
0x29b6524 0x29b652a: 48 8b 00, mov rax,[rax]; returns its first byte or falls back to '.' for null
0x33f5fea 0x33f5ff5: 4c 8b 38, mov r15,[rax]; tests the string against '.' and its terminator
0x33f61ba 0x33f61c5: 4c 8b 38, mov r15,[rax]; same decimal-string checks
0x33f637d 0x33f6383/87/8c: three 32-byte loads at result+0/result+0x20/result+0x40; copies a 96-byte prefix

Verification: current original SHA checked, independent ELF header/PT_LOAD parsing and original unwind FDE/CIE boundaries for all 5/5 callers; Capstone and GNU objdump agree on every instruction's bytes in all five bounded functions. Ghidra 12.1.4 read-only recheck matches all 5/5 full PT_LOAD byte hashes and 5/5 import references. Its existing analysis has no functions/instructions at these sites, so failed decompiler queries are not used as supporting evidence; no project mutation or inferred type is used to corroborate the layout.

This supports #3245's FreeBSD-style decimal offset 0 for this title. It does not establish all 24 field semantics or one universal PS5 ABI. Several callers tolerate a null decimal pointer, so I am not claiming a measured crash from your layout; the 96-byte copy and non-null locale result still need a compatible prefix. Please keep the differing layout evidence explicit when this work is carried. I have not built or run #545 itself.

Independent #3245 adaptation/tests: fork commit 1642b5c8. Its controlled title replay removes this unresolved import and reveals fminf before guest entry; no localeconv() runtime invocation or gameplay claim. Details on #3245 / #3249. No new PR; AI-assisted investigation: Codex.

Merged Sony CRT retained in fresh startup validation (2026-10-11,main0f863)

The earlier head0647694a null-prefix observation is historical. Current main0f863 retains the corrected 0x90-byte SonyLconv, seven monetary pointers to non-null empty strings,14CHAR_MAX chars, decimal pointer at0x48 and Dinkumware tail. This aligns with adriadam10's reported Astro's Playroom library observation; I did not independently measure that console library.

Compiled the unchanged current SonyLocaleconv helper in a standalone Windows source probe5times: sizeof=144,decimal offset=72,prefix-first-byte=0,decimal-first-byte=46,prefix-empty=true. Upstream guest_crt passes5repeats, also in the full835-test integration, and5repeats afterward. This confirms current implementation/test behavior, not PS5 equivalence.

Our refreshed integrationd8fd7c7 keeps this merged layout and omits our old FreeBSD locale override. With validated math/kernel prerequisites it enters PPSA99902 guest and hits the color guard; changing only the D64 driver advances to the existing MSAA descriptor refusal5/5. Controlled details. Thus this Sony implementation does not prevent the observed startup, and the earlier locale override is not established as necessary on this newer stack.

The original ELF caller evidence remains: known libSceLibcInternal localeconv consumers load result+0 as a decimal string or copy96bytes. With today's helper, result+0 points to an empty monetary string, not null; the earlier null-fallback reasoning therefore cannot be reused for this version. These differing guest assumptions need investigation; they do not justify globally replacing the Sony layout, supported by current source and the reported vendor observation, with FreeBSD's. They could involve guest/toolchain ABI assumptions. Actual invocation of these specific locale callers and resulting formatting behavior were not traced in the new replays. No uniform-PS5-ABI, measured locale crash or gameplay claim. AI-assisted validation/report: Codex.

… per module

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

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rebased on current main and fixed the points from the previous review on your branch (no push from you in 24 h):

  • Time.cpp/SonyCrt.hpp: asctime_s takes const GuestTm* (no host std::tm); ctime_s takes const int64_t* and goes through localtime_s_nid_postfix, so it uses the guest time zone. Both exports (libc and sceAmpr*) share SonyFormatAsctime.
  • Strings.cpp/Math.cpp/Time.cpp: conflicts resolved, keeping main's lines plus strtok_s_nid_postfix.
  • localeconv: rewritten to the 0x90-byte Dinkumware layout read from the libc.prx of Astro's Playroom (PPSA01325): empty monetary strings instead of null, CHAR_MAX in all 14 char fields, decimal_point at 0x48, then grouping, thousands_sep, _Frac_grouping, _Frac_sep, _False, _True, _No, _Yes. static_asserts pin the size and offset. TechnicalDebt line updated.
  • GuestCrt.cpp: also calls the libc exports (ctime_s, asctime_s, mbstowcs, strtok_s, __fpclassifyd, __udivti3, localeconv), adds GuestTm asctime cases (valid, bad weekday, null) and ctime buffer-size cases; Require prints the failing line.
  • PR description updated (zero divisor throws; libc exports).

Checked: scoped build and ctest (164 tests) green, guest_crt passes; merging once CI is green.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 11, 2026
@adriadam10
adriadam10 merged commit a4d5d3c into boykopovar:main Oct 11, 2026
14 checks passed
AlxcBTW added a commit to AlxcBTW/anyps5-roadmap that referenced this pull request Oct 12, 2026
- #3249 stays open: localeconv is on main since boykopovar/AnyPS5#545 (a4d5d3ce, merged 2026-10-11); the issue waits for a runtime check of the struct layout. New next step, #545 as related PR.
- Cloud Play (PPSA99600): first stop on main is getservbyname (#3734, fix PR #3798, open), then sysctl MIB 1.37 (#3700) with #3798 applied. #3249 excluded.
- vkQuake (PPSA99010): #3249 excluded; it exits cleanly for lack of game data on main 2ccbd376.
- No stage added: the sources name stops, not a stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

5 participants