From 108cefaf537728b3d654457eb8c70b4b3afb4d00 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:30:56 -0600 Subject: [PATCH 1/5] Better message for missing game trophies directory (#3922) Also decreased the log level from critical to warning, as all this affects is the ability to earn trophies. --- src/core/file_format/trp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/file_format/trp.cpp b/src/core/file_format/trp.cpp index 9d37b957e..ab873d451 100644 --- a/src/core/file_format/trp.cpp +++ b/src/core/file_format/trp.cpp @@ -59,7 +59,7 @@ static void hexToBytes(const char* hex, unsigned char* dst) { bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) { std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/"; if (!std::filesystem::exists(gameSysDir)) { - LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist"); + LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist"); return false; } From acb8d066366ce44fc9b92d612f40bf56d64f4c63 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:32:17 -0600 Subject: [PATCH 2/5] Lib.Audio3d: sceAudio3dGetDefaultOpenParameters fix (#3923) * OrbisAudio3dOpenParameters struct fix Not sure why we have the extra filler, but decomp suggests it shouldn't exist. This fixes stack_chk_fail issues in audio3d using titles. * Bring back filler, only copy 0x20 bytes. The library accepts variations on struct size, with the maximum size being the 0x28 size our current struct has. This fixes the issue without potentially breaking the struct. * Fix memcpy Prevent OOB read --- src/core/libraries/audio3d/audio3d.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/audio3d/audio3d.cpp b/src/core/libraries/audio3d/audio3d.cpp index bac497840..2ddbcd890 100644 --- a/src/core/libraries/audio3d/audio3d.cpp +++ b/src/core/libraries/audio3d/audio3d.cpp @@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() { s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) { LOG_DEBUG(Lib_Audio3d, "called"); if (params) { - *params = OrbisAudio3dOpenParameters{ + auto default_params = OrbisAudio3dOpenParameters{ .size_this = 0x20, .granularity = 0x100, .rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000, @@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* .queue_depth = 2, .buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH, }; + memcpy(params, &default_params, 0x20); } return ORBIS_OK; } @@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id, } *port_id = id; - std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters)); + std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this); return ORBIS_OK; } From 1a99ab7b098680bf9b4d6539d02f548cc24b89e3 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Tue, 13 Jan 2026 12:08:46 +0300 Subject: [PATCH 3/5] ajm: fix init params initialization (#3924) --- src/core/libraries/ajm/ajm_batch.cpp | 4 +--- src/core/libraries/ajm/ajm_batch.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 61f80e482..3ab2ed4ab 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { job.input.resample_parameters = input_batch.Consume(); } if (True(control_flags & AjmJobControlFlags::Initialize)) { - job.input.init_params = AjmDecAt9InitializeParameters{}; - std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(), - input_batch.BytesRemaining()); + job.input.init_params = input_batch.Consume(); } } diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h index 7f8890898..c18e9efbf 100644 --- a/src/core/libraries/ajm/ajm_batch.h +++ b/src/core/libraries/ajm/ajm_batch.h @@ -21,7 +21,7 @@ namespace Libraries::Ajm { struct AjmJob { struct Input { - std::optional init_params; + std::optional init_params; std::optional resample_parameters; std::optional statistics_engine_parameters; std::optional format; From cdf3c468b6a8119db41507fd2a84a7c7824cbe75 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 14 Jan 2026 18:07:44 +0200 Subject: [PATCH 4/5] Added libSceAudiodec to lle modules list (#3916) * added libSceAudiodec to lle modules list * crappy float resample , use it at your own risk * clang * adjustments to aac --------- Co-authored-by: Vladislav Mikhalin --- README.md | 2 +- src/core/libraries/ajm/ajm.cpp | 4 +-- src/core/libraries/ajm/ajm_aac.cpp | 48 ++++++++++++++++++++++-------- src/core/libraries/ajm/ajm_aac.h | 14 ++++----- src/emulator.cpp | 1 + 5 files changed, 45 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 69ee64b13..e43a2408d 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ The following firmware modules are supported and must be placed in shadPS4's `sy | libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx | | libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx | | libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx | -| libSceUlt.sprx | | | | +| libSceUlt.sprx | libSceAudiodec.sprx | | | > [!Caution] diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index b64bb47fd..2bec1bf0f 100644 --- a/src/core/libraries/ajm/ajm.cpp +++ b/src/core/libraries/ajm/ajm.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" @@ -34,7 +34,7 @@ u32 GetChannelMask(u32 num_channels) { case 8: return ORBIS_AJM_CHANNELMASK_7POINT1; default: - UNREACHABLE(); + UNREACHABLE_MSG("Unexpected number of channels: {}", num_channels); } } diff --git a/src/core/libraries/ajm/ajm_aac.cpp b/src/core/libraries/ajm/ajm_aac.cpp index b96394b72..061b77890 100644 --- a/src/core/libraries/ajm/ajm_aac.cpp +++ b/src/core/libraries/ajm/ajm_aac.cpp @@ -5,20 +5,45 @@ #include "ajm_aac.h" #include "ajm_result.h" +#include // using this internal header to manually configure the decoder in RAW mode #include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h" -#include -#include +#include // std::transform +#include // std::back_inserter +#include namespace Libraries::Ajm { +std::span AjmAacDecoder::GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const { + const auto pcm_data = std::span(m_pcm_buffer).subspan(skipped_pcm); + return pcm_data.subspan(0, std::min(pcm_data.size(), max_pcm)); +} + +template <> +size_t AjmAacDecoder::WriteOutputSamples(SparseOutputBuffer& out, std::span pcm) { + if (pcm.empty()) { + return 0; + } + + m_resample_buffer.clear(); + constexpr float inv_scale = 1.0f / std::numeric_limits::max(); + std::transform(pcm.begin(), pcm.end(), std::back_inserter(m_resample_buffer), + [](auto sample) { return float(sample) * inv_scale; }); + + return out.Write(std::span(m_resample_buffer)); +} + AjmAacDecoder::AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels) - : m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(2048 * 8), - m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {} + : m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(1024 * 8), + m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) { + m_resample_buffer.reserve(m_pcm_buffer.size()); +} AjmAacDecoder::~AjmAacDecoder() { - aacDecoder_Close(m_decoder); + if (m_decoder) { + aacDecoder_Close(m_decoder); + } } TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) { @@ -98,7 +123,7 @@ AjmSidebandFormat AjmAacDecoder::GetFormat() const { .num_channels = static_cast(info->numChannels), .channel_mask = GetChannelMask(info->numChannels), .sampl_freq = static_cast(info->sampleRate), - .sample_encoding = m_format, // AjmFormatEncoding + .sample_encoding = m_format, .bitrate = static_cast(info->bitRate), }; } @@ -130,8 +155,7 @@ DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffe const UINT sizes[] = {static_cast(input.size())}; UINT valid = sizes[0]; aacDecoder_Fill(m_decoder, buffers, sizes, &valid); - auto ret = aacDecoder_DecodeFrame(m_decoder, reinterpret_cast(m_pcm_buffer.data()), - m_pcm_buffer.size() / 2, 0); + auto ret = aacDecoder_DecodeFrame(m_decoder, m_pcm_buffer.data(), m_pcm_buffer.size(), 0); switch (ret) { case AAC_DEC_OK: @@ -167,16 +191,16 @@ DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffe gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame; size_t pcm_written = 0; + auto pcm = GetOuputPcm(skip_samples * info->numChannels, max_samples * info->numChannels); switch (m_format) { case AjmFormatEncoding::S16: - pcm_written = WriteOutputSamples(output, skip_samples * info->numChannels, - max_samples * info->numChannels); + pcm_written = output.Write(pcm); break; case AjmFormatEncoding::S32: UNREACHABLE_MSG("NOT IMPLEMENTED"); break; case AjmFormatEncoding::Float: - UNREACHABLE_MSG("NOT IMPLEMENTED"); + pcm_written = WriteOutputSamples(output, pcm); break; default: UNREACHABLE(); @@ -191,4 +215,4 @@ DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffe return result; } -} // namespace Libraries::Ajm +} // namespace Libraries::Ajm \ No newline at end of file diff --git a/src/core/libraries/ajm/ajm_aac.h b/src/core/libraries/ajm/ajm_aac.h index 7ca8ecbf8..4ff55d843 100644 --- a/src/core/libraries/ajm/ajm_aac.h +++ b/src/core/libraries/ajm/ajm_aac.h @@ -52,22 +52,18 @@ private: }; template - size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_pcm, u32 max_pcm) { - std::span pcm_data{reinterpret_cast(m_pcm_buffer.data()), - m_pcm_buffer.size() / sizeof(T)}; - pcm_data = pcm_data.subspan(skipped_pcm); - const auto pcm_size = std::min(u32(pcm_data.size()), max_pcm); - return output.Write(pcm_data.subspan(0, pcm_size)); - } + size_t WriteOutputSamples(SparseOutputBuffer& output, std::span pcm); + std::span GetOuputPcm(u32 skipped_pcm, u32 max_pcm) const; const AjmFormatEncoding m_format; const AjmAacCodecFlags m_flags; const u32 m_channels; - std::vector m_pcm_buffer; + std::vector m_pcm_buffer; + std::vector m_resample_buffer; u32 m_skip_frames = 0; InitializeParameters m_init_params = {}; AAC_DECODER_INSTANCE* m_decoder = nullptr; }; -} // namespace Libraries::Ajm +} // namespace Libraries::Ajm \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 44f8b0e72..263bd9c2b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -532,6 +532,7 @@ void Emulator::LoadSystemModules(const std::string& game_serial) { {"libSceJson2.sprx", nullptr}, {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib}, {"libSceCesCs.sprx", nullptr}, + {"libSceAudiodec.sprx", nullptr}, {"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont}, {"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt}, {"libSceFreeTypeOt.sprx", nullptr}}); From 11ee79a33348de7a55ab8d7b688144d5b8a0ce55 Mon Sep 17 00:00:00 2001 From: baggins183 Date: Wed, 14 Jan 2026 23:25:09 -0800 Subject: [PATCH 5/5] shader_recompiler: some fixes for tess shaders (#3926) When walking the users of special constants which form LDS addresses: -ignore when a user contributes to the wrong operand of an LDS inst, for example the data operand of WriteShared* instead of the address operand. This can mistakenly happen due to phi nodes. -don't use flags to stash temp info about phis, since flags may already be in use. Use a separate map. --- src/shader_recompiler/ir/attribute.cpp | 4 +- .../ir/passes/hull_shader_transform.cpp | 62 +++++++++++-------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/shader_recompiler/ir/attribute.cpp b/src/shader_recompiler/ir/attribute.cpp index 382f9b1d9..84a9fafeb 100644 --- a/src/shader_recompiler/ir/attribute.cpp +++ b/src/shader_recompiler/ir/attribute.cpp @@ -153,9 +153,9 @@ std::string NameOf(Attribute attribute) { case Attribute::TessellationEvaluationPointV: return "TessellationEvaluationPointV"; case Attribute::PackedHullInvocationInfo: - return "OffChipLdsBase"; - case Attribute::OffChipLdsBase: return "PackedHullInvocationInfo"; + case Attribute::OffChipLdsBase: + return "OffChipLdsBase"; case Attribute::TessFactorsBufferBase: return "TessFactorsBufferBase"; case Attribute::PointSize: diff --git a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp index 48b496727..d975c47ea 100644 --- a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp +++ b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "common/assert.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/attribute.h" @@ -189,7 +190,7 @@ std::optional FindTessConstantSharp(IR::Inst* read_const_buff // Walker that helps deduce what type of attribute a DS instruction is reading // or writing, which could be an input control point, output control point, // or per-patch constant (PatchConst). -// For certain ReadConstBuffer instructions using the tess constants V#,, we visit the users +// For certain ReadConstBuffer instructions using the tess constants V#, we visit the users // recursively and increment a counter on the Load/WriteShared users. // Namely NumPatch (from m_hsNumPatch), HsOutputBase (m_hsOutputBase), // and PatchConstBase (m_patchConstBase). @@ -200,9 +201,11 @@ std::optional FindTessConstantSharp(IR::Inst* read_const_buff // // TODO: this will break if AMD compiler used distributive property like // TcsNumPatches * (ls_stride * #input_cp_in_patch + hs_cp_stride * #output_cp_in_patch) +// +// Assert if the region is ambiguous due to phi nodes in the addr calculation for a DS instruction, class TessConstantUseWalker { public: - void MarkTessAttributeUsers(IR::Inst* read_const_buffer, TessConstantAttribute attr) { + void WalkUsersOfTessConstant(IR::Inst* read_const_buffer, TessConstantAttribute attr) { u32 inc; switch (attr) { case TessConstantAttribute::HsNumPatch: @@ -217,14 +220,19 @@ public: } for (IR::Use use : read_const_buffer->Uses()) { - MarkTessAttributeUsersHelper(use, inc); + WalkUsersOfTessConstantHelper(use, inc, false); } ++seq_num; } private: - void MarkTessAttributeUsersHelper(IR::Use use, u32 inc) { + struct PhiInfo { + u32 seq_num; + u32 unique_edge; + }; + + void WalkUsersOfTessConstantHelper(IR::Use use, u32 inc, bool propagateError) { IR::Inst* inst = use.user; switch (use.user->GetOpcode()) { @@ -232,38 +240,37 @@ private: case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU32: case IR::Opcode::WriteSharedU64: { - u32 counter = inst->Flags(); - inst->SetFlags(counter + inc); - // Stop here - return; + bool is_addr_operand = use.operand == 0; + if (is_addr_operand) { + u32 counter = inst->Flags(); + inst->SetFlags(counter + inc); + ASSERT_MSG(!propagateError, "LDS instruction {} accesses ambiguous attribute type", + fmt::ptr(use.user)); + // Stop here + return; + } } case IR::Opcode::Phi: { - struct PhiCounter { - u16 seq_num; - u16 unique_edge; - }; - - PhiCounter count = inst->Flags(); - ASSERT_MSG(count.seq_num == 0 || count.unique_edge == use.operand); + auto it = phi_infos.find(use.user); // the point of seq_num is to tell us if we've already traversed this - // phi on the current walk. Alternatively we could keep a set of phi's - // seen on the current walk. This is to handle phi cycles - if (count.seq_num == 0) { + // phi on the current walk to handle phi cycles + if (it == phi_infos.end()) { // First time we've encountered this phi - count.seq_num = seq_num; // Mark the phi as having been traversed originally through this edge - count.unique_edge = use.operand; - } else if (count.seq_num < seq_num) { - count.seq_num = seq_num; + phi_infos[inst] = {.seq_num = seq_num, + .unique_edge = static_cast(use.operand)}; + } else if (it->second.seq_num < seq_num) { + it->second.seq_num = seq_num; // For now, assume we are visiting this phi via the same edge // as on other walks. If not, some dataflow analysis might be necessary - ASSERT(count.unique_edge == use.operand); + if (it->second.unique_edge != use.operand) { + propagateError = true; + } } else { - // count.seq_num == seq_num + ASSERT(it->second.seq_num == seq_num); // there's a cycle, and we've already been here on this walk return; } - inst->SetFlags(count); break; } default: @@ -271,10 +278,11 @@ private: } for (IR::Use use : inst->Uses()) { - MarkTessAttributeUsersHelper(use, inc); + WalkUsersOfTessConstantHelper(use, inc, propagateError); } } + std::unordered_map phi_infos; u32 seq_num{1u}; }; @@ -690,7 +698,7 @@ void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info) { case TessConstantAttribute::HsNumPatch: case TessConstantAttribute::HsOutputBase: case TessConstantAttribute::PatchConstBase: - walker.MarkTessAttributeUsers(&inst, tess_const_attr); + walker.WalkUsersOfTessConstant(&inst, tess_const_attr); // We should be able to safely set these to 0 so that indexing happens only // within the local patch in the recompiled Vulkan shader. This assumes // these values only contribute to address calculations for in/out