Skip to content

feat(libSceNgs2.native): implement sceNgs2ParseWaveformFile and parse 16-bit PCM waveforms - #1020

Merged
oneandonlydean merged 5 commits into
boykopovar:mainfrom
gjonn:feat/ngs2-parse-waveform-file
Oct 7, 2026
Merged

oneandonlydean merged 5 commits into
boykopovar:mainfrom
gjonn:feat/ngs2-parse-waveform-file

Conversation

@gjonn

@gjonn gjonn commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What

Implements sceNgs2ParseWaveformFile(path, offset, info) (resolves the guest path, reads the file from offset to its end, and reuses sceNgs2ParseWaveformData) and adds 16-bit PCM RIFF parsing to sceNgs2ParseWaveformData. Previously only ATRAC9 was parsed and any other format tag threw. A data chunk that runs past the buffer or ends inside a frame returns SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA (assumed, recorded in TechnicalDebt); looped PCM and other PCM formats still throw.

PPSA21402 (Balatro) imports sceNgs2ParseWaveformFile. Its call site sets only three argument registers (path, offset, info), which is where the signature comes from. It loads its sound effects as 16-bit PCM .wav files (80 of 80 have a data chunk that exactly fills the file). It failed at startup with an unresolved import, then with parsing waveform format tag 0x1 is not implemented.

Tested

  • Windows 11, NVIDIA GeForce RTX 5090 Mobile (laptop GPU), MinGW-w64 GCC 15.2.0: ctest -R ngs2 passes (render, atrac9, filter, custom), including new tests for PCM parsing (a data chunk past the buffer or ending inside a frame, 8-bit and zero-channel files are rejected) and file parsing (offset, offset past the end, null path and info).
  • PPSA21402 (same machine): the title parses its sound files through this call and gets past the waveform parse; progress beyond that depends on other changes that are not part of this PR.
  • Not verified on PS5 hardware.

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: none
  • AI-assisted: yes

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks. A named title with the exact stop on main is what we look for, and the change fits the existing ATRAC9 parser well (linking libc and libkernel for ResolvePath_nid_no_patch matches libSceAjm.native).

Two small things before it can go in:

  • In TestParseFile, out.write("", 3) reads three bytes from a one-byte string literal, which is undefined behaviour. Something like const char pad[3]{}; out.write(pad, sizeof(pad)); does the same thing safely.
  • ParsePcm16 quietly shortens a data chunk that runs past the end of the buffer (your truncated test expects SCE_NGS2_OK with 50 samples). We don't know that the console does that. Could you either return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA there, or add the truncation to your TechnicalDebt line as an assumption? If Balatro's files hit that case, please say so; that decides it.

The CI run needs a maintainer's approval because this is your first PR here; that's on our side.

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Progress report (3c05400 → fb56ff2)

📈 System libraries: 86.14% (+0.01%, +1 implemented functions)

✅ 1 implemented
Library Name
libSceNgs2.native sceNgs2ParseWaveformFile

@gjonn

gjonn commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Pushed an update (e59b99b):

  • TestParseFile: now const char pad[3]{}; out.write(pad, sizeof(pad));. (For the record, the original literal held raw 0x01 0x02 0x03 bytes that GitHub does not render, so it wrote 3 of its 4 bytes, but raw control characters in source are fragile anyway.)
  • Truncated data chunk: none of Balatro's 80 .wav files hit it (every data chunk exactly fills its file), so I dropped the truncation. A data chunk that runs past the buffer or ends inside a frame now returns SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA, with tests, and the error code is listed as an assumption in TechnicalDebt.
  • A correction on my side: sceNgs2ParseWaveformFile takes three arguments, (path, offset, info), not four. PPSA21402's call site only sets rdi, rsi and rdx. I had added a size argument, which made the title's info pointer come from a stale register. Fixed, with the test, the test header and the TechnicalDebt entry updated.

ctest -R ngs2 passes on Windows 11. I also updated the PR description, which still described the four-argument version.

@gjonn

gjonn commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

The Linux ngs2_atrac9 failure was the new test, not the implementation. It wrote its temporary file to the host's /tmp, but sceNgs2ParseWaveformFile resolves /tmp/... as a guest path, and on Linux every absolute guest path is mapped under the working directory (on Windows a path with a drive letter is passed through unchanged, which is why that job passed).

Pushed 13f0370: the test now asks ResolvePath_nid_no_patch for the host path of a guest path (/aps5_ngs2_parse_file.wav) and writes the file there. I could only run it on Windows, where a driveless guest path takes the same working-directory mapping route, so the Linux run is the real check. This push supersedes the run still waiting for approval on e59b99b.

gjonn added 3 commits October 6, 2026 22:14
…formFile and reject PCM data that does not fit

The call site of PPSA21402 sets only three argument registers, so the function has no size parameter. A data chunk that runs past the buffer or ends inside a frame returns INVALID_WAVEFORM_DATA instead of being shortened. The test no longer writes padding from a string literal.
…the guest path resolves

On Linux every absolute guest path is resolved under the working directory, so the temporary file in the host temp directory was not found. The test now asks ResolvePath_nid_no_patch for the host path of a guest path and writes the file there.
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks for the fixes. The Linux CI job still fails on the new head, in the ATRAC9 test this PR changes: ngs2_atrac9 aborts ("Subprocess aborted", after 0.09 s), while the Windows job passes. The log is here: https://github.com/boykopovar/AnyPS5/actions/runs/37561141516/job/112620804339

Could you take a look? It can go in once both jobs are green.

…ecutable

On Linux ngs2_atrac9 aborted without any output once TestParseFile was part of
its main(). The exception that TestCorruptSuperframeAtPageEnd throws in the
Ngs2 module and catches in main() has to resume through a cleanup landing pad
inside main(). That resume goes through the unwinder statically linked into the
test executable, which then fails with _URC_FATAL_PHASE2_ERROR and aborts
silently. Whether main() gets such a pad depends on what the compiler inlines
into it, so adding any function with non-trivial cleanups to that main() is
enough to trigger it.

Move the PCM and file parse tests into a new ngs2_parse executable and restore
Ngs2Atrac9.cpp to its upstream content, so the test that catches exceptions is
unchanged. The new executable has no catch and never relies on that path.
@oneandonlydean
oneandonlydean merged commit 76543c2 into boykopovar:main Oct 7, 2026
3 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