mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #11149 from EagleBearerYS/feat/ajm-batch-builders-done
fix(libSceAjm.native): reject a null batch buffer list with a nonzero count in sceAjmBatchJobRunSplit
This commit is contained in:
3 files changed
+59
-5
No files matched your search
@@ -234,10 +234,14 @@ struct PackedJob {
|
||||
};
|
||||
static_assert(sizeof(PackedJob) == 32);
|
||||
|
||||
int Append(AjmBatchInfo* info, const JobHeader& header, const AjmBuffer* inputs, const AjmBuffer* outputs) {
|
||||
int JobRejected(const AjmBatchInfo* info, const JobHeader& header) {
|
||||
if (!info || !info->p_buffer) return SCE_AJM_ERROR_INVALID_PARAMETER;
|
||||
if (header.inputCount > 0xffu || header.outputCount > 0xffu || header.parameterSize > sizeof(header.parameters) || header.sidebandSize > 0xffffffffu) return SCE_AJM_ERROR_INVALID_PARAMETER;
|
||||
if ((header.inputCount && !inputs) || (header.outputCount && !outputs)) NotImplemented_nid_no_patch("AJM batch job with a null buffer list and a nonzero buffer count");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Append(AjmBatchInfo* info, const JobHeader& header, const AjmBuffer* inputs, const AjmBuffer* outputs) {
|
||||
if (const int rejected = JobRejected(info, header)) return rejected;
|
||||
const std::size_t parameterBytes = (header.parameterSize + 7u) & ~std::size_t{7};
|
||||
const std::size_t bytes = sizeof(PackedJob) + (header.inputCount + header.outputCount) * sizeof(AjmBuffer) + parameterBytes;
|
||||
if (info->offset > info->size || bytes > info->size - info->offset) return SCE_AJM_ERROR_OUT_OF_RESOURCES;
|
||||
@@ -254,6 +258,12 @@ int Append(AjmBatchInfo* info, const JobHeader& header, const AjmBuffer* inputs,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CheckedAppend(AjmBatchInfo* info, const JobHeader& header, const AjmBuffer* inputs, const AjmBuffer* outputs) {
|
||||
if (const int rejected = JobRejected(info, header)) return rejected;
|
||||
if ((header.inputCount && !inputs) || (header.outputCount && !outputs)) NotImplemented_nid_no_patch("AJM batch job with a null buffer list and a nonzero buffer count");
|
||||
return Append(info, header, inputs, outputs);
|
||||
}
|
||||
|
||||
JobHeader MakeHeader(JobKind kind, std::uint32_t instance, void* sideband, std::uint64_t sidebandSize) {
|
||||
JobHeader header{};
|
||||
header.kind = kind;
|
||||
@@ -2006,7 +2016,7 @@ int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo* info, uint32_t instance, uint6
|
||||
header.flags = flags;
|
||||
header.inputCount = sideband_input_size != 0 ? 1 : 0;
|
||||
const AjmBuffer input{const_cast<void*>(sideband_input), sideband_input_size};
|
||||
return Append(info, header, &input, nullptr);
|
||||
return CheckedAppend(info, header, &input, nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmBatchJobRunSplit(AjmBatchInfo* info, uint32_t instance, uint64_t flags, const AjmBuffer* input_buffers, size_t input_buffers_num, const AjmBuffer* output_buffers, size_t output_buffers_num, void* sideband_output, size_t sideband_output_size) {
|
||||
@@ -2014,7 +2024,7 @@ int APS5_VABI sceAjmBatchJobRunSplit(AjmBatchInfo* info, uint32_t instance, uint
|
||||
header.flags = flags;
|
||||
header.inputCount = static_cast<std::uint32_t>(input_buffers_num);
|
||||
header.outputCount = static_cast<std::uint32_t>(output_buffers_num);
|
||||
return Append(info, header, input_buffers, output_buffers);
|
||||
return CheckedAppend(info, header, input_buffers, output_buffers);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmBatchJobRun(AjmBatchInfo* info, uint32_t instance, uint64_t flags, const void* data_input, size_t data_input_size, void* data_output, size_t data_output_size, void* sideband_output, size_t sideband_output_size) {
|
||||
@@ -2072,7 +2082,7 @@ int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo* info, uint32_t instance,
|
||||
|
||||
int APS5_VABI sceAjmBatchJobGetStatistics(AjmBatchInfo* info, float interval, void* result) {
|
||||
(void)interval;
|
||||
return Append(info, MakeHeader(JobKind::GetStatistics, 0, result, 24), nullptr, nullptr);
|
||||
return CheckedAppend(info, MakeHeader(JobKind::GetStatistics, 0, result, 24), nullptr, nullptr);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmBatchStart(uint32_t context, const AjmBatchInfo* info, int priority, AjmBatchError* error, uint32_t* batch) {
|
||||
|
||||
@@ -1556,6 +1556,48 @@ ResampleInfo GetResampleInfo(std::uint32_t context, std::uint32_t instance) {
|
||||
return resample;
|
||||
}
|
||||
|
||||
void TestBuildersWithoutBuffers(std::uint32_t context) {
|
||||
const auto instance = CreateOpus(context);
|
||||
const std::uint32_t parameters[3] = {2, 48000, 0};
|
||||
const GaplessDecode gapless{2000, 100, 0};
|
||||
std::int64_t results[5] = {-1, -1, -1, -1, -1};
|
||||
ResampleInfo resample;
|
||||
std::memset(&resample, 0xAA, sizeof(resample));
|
||||
std::vector<std::uint8_t> batch(4096);
|
||||
AjmBatchInfo info{};
|
||||
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
|
||||
std::size_t offset = 0;
|
||||
const auto built = [&](int status) {
|
||||
const bool grew = status == 0 && info.offset > offset;
|
||||
offset = info.offset;
|
||||
return grew;
|
||||
};
|
||||
Require(built(sceAjmBatchJobInitialize(&info, instance, parameters, sizeof(parameters), &results[0])));
|
||||
Require(built(sceAjmBatchJobClearContext(&info, instance, &results[1])));
|
||||
Require(built(sceAjmBatchJobSetGaplessDecode(&info, instance, &gapless, 1, &results[2])));
|
||||
Require(built(sceAjmBatchJobSetResampleParameters(&info, instance, 1.0f, 1, &results[3])));
|
||||
Require(built(sceAjmBatchJobSetResampleParametersEx(&info, instance, 1.0f, 0.0f, 1, &results[4])));
|
||||
Require(built(sceAjmBatchJobGetResampleInfo(&info, instance, &resample)));
|
||||
Submit(context, info);
|
||||
Require(std::all_of(std::begin(results), std::end(results), [](std::int64_t result) { return result == 0; }));
|
||||
Require(resample.result == 0 && resample.internalResult == 0 && resample.ratio == 1.0f && resample.samples == 0);
|
||||
|
||||
const auto nullListThrows = [&](std::size_t inputs, std::size_t outputs) {
|
||||
const auto before = info.offset;
|
||||
try {
|
||||
sceAjmBatchJobRunSplit(&info, instance, 0, nullptr, inputs, nullptr, outputs, nullptr, 0);
|
||||
} catch (const std::runtime_error&) {
|
||||
return info.offset == before;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
|
||||
Require(nullListThrows(1, 0) && nullListThrows(0, 1) && nullListThrows(2, 3));
|
||||
offset = 0;
|
||||
Require(built(sceAjmBatchJobRunSplit(&info, instance, 0, nullptr, 0, nullptr, 0, nullptr, 0)));
|
||||
Require(sceAjmInstanceDestroy(context, instance) == 0);
|
||||
}
|
||||
|
||||
struct ResampledStream {
|
||||
std::vector<std::int16_t> pcm;
|
||||
std::size_t jobs = 0;
|
||||
@@ -2396,6 +2438,7 @@ int main() {
|
||||
TestResampleOpus(context);
|
||||
TestResampleMp3(context);
|
||||
TestResampleParametersEx(context);
|
||||
TestBuildersWithoutBuffers(context);
|
||||
TestResampleAt9(context);
|
||||
TestAt9GaplessSegments(context);
|
||||
TestAt9RunDecodesWholeInput(context);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- [AJM Opus decoder](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native, codec 24) - the initialize parameter layout and little-endian `u16` packet byte count come from The Smurfs Dreams (`2, 48000, 0`) and Lost Soul Aside (`4, 48000, 1`). The third word is interpreted as the mapping family, as in [KytyPS5's initialize structure](https://github.com/KytyPS5/KytyPS5/blob/984cfcebd141b211aa9e0a8cde63b0ebe652d07a/src/libs/ajm/opus_decoder.h#L11-L15). Family 1 uses the conventional stream counts, coupled counts and channel map from [libopus 1.5.2's surround layout](https://github.com/xiph/opus/blob/v1.5.2/src/opus_multistream_encoder.c#L47-L55), passed to FFmpeg through an [RFC 7845 section 5.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1) header with zero pre-skip and gain; gapless skipping remains controlled by AJM sideband parameters. FFmpeg returns PCM in its native speaker order. The implicit stream layout, PCM channel order and invalid family-0 channel count result are not verified on a console. NHL 27 (PPSA34063) passes `1, 48000, 255`. Family 255 with one channel is decoded as one mono stream, the same as family 0; this is assumed, since the stream count, coupled stream count and channel mapping that a family-255 Opus header carries ([RFC 7845 section 5.1.1](https://datatracker.ietf.org/doc/html/rfc7845#section-5.1.1)) are not passed. Only 48000 Hz, mapping families 0 and 1, and family 255 with one channel are implemented; other rates and families, and family 255 with more channels, throw
|
||||
- [AJM codec 23](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - taken as interleaved `s16` PCM that only passes through the resampler, from Broken Sword - Shadow of the Templars: Reforged (PPSA21480); no source names a codec 23 ([shadPS4's PS4 header](https://github.com/shadps4-emu/shadPS4/blob/5aa7ffa3f4f21bba827d7865ed8f69f98f993542/src/core/libraries/ajm/ajm.h#L19-L25) ends its codec list with `Max = 23`) and the name `CODEC_PCM` is ours. The title decodes its Ogg Opus and Vorbis audio itself and, for every block, initializes the instance (flags 0x1002) with `0, 2, 44100`, sets the ratio 0.91875 (44100/48000) and decodes 235 stereo frames into a 256-frame output. The 12 initialize bytes are taken as input format (0 is `s16`, other values throw), channel count and sample rate; the rate is only reported in the format sideband; other sizes throw; zero or more than 8 channels or a zero rate return the invalid-parameter result as for Opus, not verified. A run consumes its whole input and emits every output frame whose position (output index × ratio) is below the input frame count, with the 4-point Catmull-Rom of the other codecs' resampler and the first and last input frames repeated past the edges, so each run restarts at position 0; `totalDecodedSamples` counts input frames; `s32` and float outputs are accepted although only `s16` was seen. Throwing because the console's behaviour is unknown: a run after a resampled run without an initialize or context clear in between (whether the console carries the resampler position across runs), a ratio change per sample, gapless counts, an empty input or one that is not a whole number of frames, an output that cannot hold the resampled input, run flags other than `SIDEBAND_STREAM` and initialization through `sceAjmBatchJobControl`
|
||||
- [sceAjmBatchJobSetResampleParametersEx](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the `(info, instance, float ratio_start, float ratio_change_per_sample, flags, result)` signature is inferred from the name and from `sceAjmBatchJobSetResampleParameters`, and PPSA02225's one call agrees with that layout (`rdi`, `esi`, `xmm0` and `xmm1` as two single floats, `edx`, `rcx`) without showing `flags` or the float roles. With a zero change per sample it sets the ratio as `sceAjmBatchJobSetResampleParameters` does (Watch Dogs: Legion, PPSA01500, calls it so). How a non-zero change ramps the ratio is not confirmed on a console (no source: KytyPS5 ignores it, prosper stubs it, the PS5 firmware does not export the batch builders to a payload): it is assumed to be added to the ratio after each emitted output frame, clamped at zero, and kept across batches. RIDE 4 (PPSA01599) passes one call with a change of 4.6e-7, so the exact rule matters little there.
|
||||
- [sceAjmBatchJobGetStatistics](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - the job zero-fills the first 24 bytes of its sideband and ignores the interval; no source gives this function's result layout or values and no test covers it. [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/984cfcebd141b211aa9e0a8cde63b0ebe652d07a/src/libs/ajm.cpp#L973-L979) zero-fills a 48-byte result instead (the 8-byte result, a batch engine usage, three interval engine usages and six memory words), and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/ajm/ajm.h#L147-L170) (PS4) names engine and memory statistics sidebands but not this builder
|
||||
- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET (bit 13), INITIALIZE (bit 14) and the sideband input order (gapless decode, then initialize parameters) come from [shadPS4](https://github.com/shadps4-emu/shadPS4) (PS4); the one call in PPSA25936 passes the flags 0x4000 (bit 14 only), an 8-byte input and an 8-byte output, which agrees with INITIALIZE alone and the 8-byte parameters, without showing the codec or the meaning of the words; other flags throw
|
||||
- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - RESET is assumed to clear the context as sceAjmBatchJobClearContext does
|
||||
- [sceAjmBatchJobControl](../../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - initialize parameters are 8 bytes for ATRAC9 and M4AAC, 12 for Opus and none for MP3, as one title passes them; other codecs and sizes, and an output other than the 8-byte result, throw
|
||||
|
||||
Reference in new issue
Block a user