shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.
The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games.
@@ -37,6 +38,9 @@
Game
+
+ https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0
+
https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index eb3723f2c..8e96f9bec 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -258,6 +258,10 @@ if (WIN32)
add_subdirectory(ext-wepoll)
endif()
+if (NOT TARGET fdk-aac)
+add_subdirectory(aacdec)
+endif()
+
#nlohmann json
set(JSON_BuildTests OFF CACHE INTERNAL "")
add_subdirectory(json)
diff --git a/externals/aacdec/CMakeLists.txt b/externals/aacdec/CMakeLists.txt
new file mode 100644
index 000000000..2adfa032b
--- /dev/null
+++ b/externals/aacdec/CMakeLists.txt
@@ -0,0 +1,154 @@
+# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(AACDEC_SRC
+ fdk-aac/libAACdec/src/FDK_delay.cpp
+ fdk-aac/libAACdec/src/aac_ram.cpp
+ fdk-aac/libAACdec/src/aac_rom.cpp
+ fdk-aac/libAACdec/src/aacdec_drc.cpp
+ fdk-aac/libAACdec/src/aacdec_hcr.cpp
+ fdk-aac/libAACdec/src/aacdec_hcr_bit.cpp
+ fdk-aac/libAACdec/src/aacdec_hcrs.cpp
+ fdk-aac/libAACdec/src/aacdec_pns.cpp
+ fdk-aac/libAACdec/src/aacdec_tns.cpp
+ fdk-aac/libAACdec/src/aacdecoder.cpp
+ fdk-aac/libAACdec/src/aacdecoder_lib.cpp
+ fdk-aac/libAACdec/src/block.cpp
+ fdk-aac/libAACdec/src/channel.cpp
+ fdk-aac/libAACdec/src/channelinfo.cpp
+ fdk-aac/libAACdec/src/conceal.cpp
+ fdk-aac/libAACdec/src/ldfiltbank.cpp
+ fdk-aac/libAACdec/src/pulsedata.cpp
+ fdk-aac/libAACdec/src/rvlc.cpp
+ fdk-aac/libAACdec/src/rvlcbit.cpp
+ fdk-aac/libAACdec/src/rvlcconceal.cpp
+ fdk-aac/libAACdec/src/stereo.cpp
+ fdk-aac/libAACdec/src/usacdec_ace_d4t64.cpp
+ fdk-aac/libAACdec/src/usacdec_ace_ltp.cpp
+ fdk-aac/libAACdec/src/usacdec_acelp.cpp
+ fdk-aac/libAACdec/src/usacdec_fac.cpp
+ fdk-aac/libAACdec/src/usacdec_lpc.cpp
+ fdk-aac/libAACdec/src/usacdec_lpd.cpp
+ fdk-aac/libAACdec/src/usacdec_rom.cpp
+)
+
+set(FDK_SRC
+ fdk-aac/libFDK/src/FDK_bitbuffer.cpp
+ fdk-aac/libFDK/src/FDK_core.cpp
+ fdk-aac/libFDK/src/FDK_crc.cpp
+ fdk-aac/libFDK/src/FDK_decorrelate.cpp
+ fdk-aac/libFDK/src/FDK_hybrid.cpp
+ fdk-aac/libFDK/src/FDK_lpc.cpp
+ fdk-aac/libFDK/src/FDK_matrixCalloc.cpp
+ fdk-aac/libFDK/src/FDK_qmf_domain.cpp
+ fdk-aac/libFDK/src/FDK_tools_rom.cpp
+ fdk-aac/libFDK/src/FDK_trigFcts.cpp
+ fdk-aac/libFDK/src/autocorr2nd.cpp
+ fdk-aac/libFDK/src/dct.cpp
+ fdk-aac/libFDK/src/fft.cpp
+ fdk-aac/libFDK/src/fft_rad2.cpp
+ fdk-aac/libFDK/src/fixpoint_math.cpp
+ fdk-aac/libFDK/src/huff_nodes.cpp
+ fdk-aac/libFDK/src/mdct.cpp
+ fdk-aac/libFDK/src/nlc_dec.cpp
+ fdk-aac/libFDK/src/qmf.cpp
+ fdk-aac/libFDK/src/scale.cpp
+)
+
+set(SYS_SRC
+ fdk-aac/libSYS/src/genericStds.cpp
+ fdk-aac/libSYS/src/syslib_channelMapDescr.cpp
+)
+
+set(ARITHCODING_SRC
+ fdk-aac/libArithCoding/src/ac_arith_coder.cpp
+)
+
+set(MPEGTPDEC_SRC
+ fdk-aac/libMpegTPDec/src/tpdec_adif.cpp
+ fdk-aac/libMpegTPDec/src/tpdec_adts.cpp
+ fdk-aac/libMpegTPDec/src/tpdec_asc.cpp
+ fdk-aac/libMpegTPDec/src/tpdec_drm.cpp
+ fdk-aac/libMpegTPDec/src/tpdec_latm.cpp
+ fdk-aac/libMpegTPDec/src/tpdec_lib.cpp
+)
+
+set(SBRDEC_SRC
+ fdk-aac/libSBRdec/src/HFgen_preFlat.cpp
+ fdk-aac/libSBRdec/src/env_calc.cpp
+ fdk-aac/libSBRdec/src/env_dec.cpp
+ fdk-aac/libSBRdec/src/env_extr.cpp
+ fdk-aac/libSBRdec/src/hbe.cpp
+ fdk-aac/libSBRdec/src/huff_dec.cpp
+ fdk-aac/libSBRdec/src/lpp_tran.cpp
+ fdk-aac/libSBRdec/src/psbitdec.cpp
+ fdk-aac/libSBRdec/src/psdec.cpp
+ fdk-aac/libSBRdec/src/psdec_drm.cpp
+ fdk-aac/libSBRdec/src/psdecrom_drm.cpp
+ fdk-aac/libSBRdec/src/pvc_dec.cpp
+ fdk-aac/libSBRdec/src/sbr_deb.cpp
+ fdk-aac/libSBRdec/src/sbr_dec.cpp
+ fdk-aac/libSBRdec/src/sbr_ram.cpp
+ fdk-aac/libSBRdec/src/sbr_rom.cpp
+ fdk-aac/libSBRdec/src/sbrdec_drc.cpp
+ fdk-aac/libSBRdec/src/sbrdec_freq_sca.cpp
+ fdk-aac/libSBRdec/src/sbrdecoder.cpp
+)
+
+set(PCMUTILS_SRC
+ fdk-aac/libPCMutils/src/limiter.cpp
+ fdk-aac/libPCMutils/src/pcm_utils.cpp
+ fdk-aac/libPCMutils/src/pcmdmx_lib.cpp
+)
+
+set(DRCDEC_SRC
+ fdk-aac/libDRCdec/src/FDK_drcDecLib.cpp
+ fdk-aac/libDRCdec/src/drcDec_gainDecoder.cpp
+ fdk-aac/libDRCdec/src/drcDec_reader.cpp
+ fdk-aac/libDRCdec/src/drcDec_rom.cpp
+ fdk-aac/libDRCdec/src/drcDec_selectionProcess.cpp
+ fdk-aac/libDRCdec/src/drcDec_tools.cpp
+ fdk-aac/libDRCdec/src/drcGainDec_init.cpp
+ fdk-aac/libDRCdec/src/drcGainDec_preprocess.cpp
+ fdk-aac/libDRCdec/src/drcGainDec_process.cpp
+)
+
+set(SACDEC_SRC
+ fdk-aac/libSACdec/src/sac_bitdec.cpp
+ fdk-aac/libSACdec/src/sac_calcM1andM2.cpp
+ fdk-aac/libSACdec/src/sac_dec.cpp
+ fdk-aac/libSACdec/src/sac_dec_conceal.cpp
+ fdk-aac/libSACdec/src/sac_dec_lib.cpp
+ fdk-aac/libSACdec/src/sac_process.cpp
+ fdk-aac/libSACdec/src/sac_qmf.cpp
+ fdk-aac/libSACdec/src/sac_reshapeBBEnv.cpp
+ fdk-aac/libSACdec/src/sac_rom.cpp
+ fdk-aac/libSACdec/src/sac_smoothing.cpp
+ fdk-aac/libSACdec/src/sac_stp.cpp
+ fdk-aac/libSACdec/src/sac_tsd.cpp
+)
+
+add_library(fdk-aac
+ ${AACDEC_SRC}
+ ${FDK_SRC}
+ ${SYS_SRC}
+ ${ARITHCODING_SRC}
+ ${MPEGTPDEC_SRC}
+ ${SBRDEC_SRC}
+ ${PCMUTILS_SRC}
+ ${DRCDEC_SRC}
+ ${SACDEC_SRC}
+)
+
+target_include_directories(fdk-aac
+ PUBLIC
+ fdk-aac/libAACdec/include
+ fdk-aac/libFDK/include
+ fdk-aac/libSYS/include
+ fdk-aac/libArithCoding/include
+ fdk-aac/libMpegTPDec/include
+ fdk-aac/libSBRdec/include
+ fdk-aac/libPCMutils/include
+ fdk-aac/libDRCdec/include
+ fdk-aac/libSACdec/include
+)
diff --git a/externals/aacdec/fdk-aac b/externals/aacdec/fdk-aac
new file mode 160000
index 000000000..ee76460ef
--- /dev/null
+++ b/externals/aacdec/fdk-aac
@@ -0,0 +1 @@
+Subproject commit ee76460efbdb147e26d804c798949c23f174460b
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index b4fa204bc..e7a786396 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
@@ -13,6 +14,7 @@
#include "common/logging/log.h"
#include "common/logging/log_entry.h"
#include "common/logging/text_formatter.h"
+#include "common/thread.h"
namespace Common::Log {
@@ -23,8 +25,9 @@ std::string FormatLogMessage(const Entry& entry) {
const char* class_name = GetLogClassName(entry.log_class);
const char* level_name = GetLevelName(entry.log_level);
- return fmt::format("[{}] <{}> {}:{} {}: {}", class_name, level_name, entry.filename,
- entry.line_num, entry.function, entry.message);
+ return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name,
+ Common::GetCurrentThreadName(), entry.filename, entry.line_num,
+ entry.function, entry.message);
}
void PrintMessage(const Entry& entry) {
diff --git a/src/common/memory_patcher.cpp b/src/common/memory_patcher.cpp
index 045a530cb..ad737dab4 100644
--- a/src/common/memory_patcher.cpp
+++ b/src/common/memory_patcher.cpp
@@ -51,14 +51,14 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
uint32_t i;
} floatUnion;
floatUnion.f = std::stof(valueStr);
- result = toHex(floatUnion.i, sizeof(floatUnion.i));
+ result = toHex(std::byteswap(floatUnion.i), sizeof(floatUnion.i));
} else if (type == "float64") {
union {
double d;
uint64_t i;
} doubleUnion;
doubleUnion.d = std::stod(valueStr);
- result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
+ result = toHex(std::byteswap(doubleUnion.i), sizeof(doubleUnion.i));
} else if (type == "utf8") {
std::vector byteArray =
std::vector(valueStr.begin(), valueStr.end());
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 982041ebb..54194186c 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include
#include
#include
+#include "core/libraries/kernel/threads/pthread.h"
+
#include "common/error.h"
#include "common/logging/log.h"
#include "common/thread.h"
@@ -237,4 +240,22 @@ void AccurateTimer::End() {
target_interval - std::chrono::duration_cast(now - start_time);
}
+std::string GetCurrentThreadName() {
+ using namespace Libraries::Kernel;
+ if (g_curthread && !g_curthread->name.empty()) {
+ return g_curthread->name;
+ }
+#ifdef _WIN32
+ PWSTR name;
+ GetThreadDescription(GetCurrentThread(), &name);
+ return Common::UTF16ToUTF8(name);
+#else
+ char name[256];
+ if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
+ return "";
+ }
+ return std::string{name};
+#endif
+}
+
} // namespace Common
diff --git a/src/common/thread.h b/src/common/thread.h
index 5bd83d35c..a300d10c3 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -46,4 +47,6 @@ public:
}
};
+std::string GetCurrentThreadName();
+
} // namespace Common
diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp
index 83620250b..b64bb47fd 100644
--- a/src/core/libraries/ajm/ajm.cpp
+++ b/src/core/libraries/ajm/ajm.cpp
@@ -144,9 +144,8 @@ int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
return ORBIS_OK;
}
-int PS4_SYSV_ABI sceAjmInstanceCodecType() {
- LOG_ERROR(Lib_Ajm, "(STUBBED) called");
- return ORBIS_OK;
+AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id) {
+ return static_cast((instance_id >> 14) & 0x1F);
}
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,
diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h
index 2c529cd4b..1bfd88351 100644
--- a/src/core/libraries/ajm/ajm.h
+++ b/src/core/libraries/ajm/ajm.h
@@ -82,8 +82,6 @@ enum class AjmStatisticsFlags : u64 {
DECLARE_ENUM_FLAG_OPERATORS(AjmStatisticsFlags)
union AjmStatisticsJobFlags {
- AjmStatisticsJobFlags(AjmJobFlags job_flags) : raw(job_flags.raw) {}
-
u64 raw;
struct {
u64 version : 3;
@@ -133,7 +131,7 @@ struct AjmSidebandGaplessDecode {
struct AjmSidebandResampleParameters {
float ratio;
- uint32_t flags;
+ u32 flags;
};
struct AjmDecAt9InitializeParameters {
@@ -217,7 +215,7 @@ int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int p
AjmDecMp3ParseFrame* frame);
int PS4_SYSV_ABI sceAjmFinalize();
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context);
-int PS4_SYSV_ABI sceAjmInstanceCodecType();
+AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id);
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags,
u32* instance);
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance);
diff --git a/src/core/libraries/ajm/ajm_aac.cpp b/src/core/libraries/ajm/ajm_aac.cpp
new file mode 100644
index 000000000..b96394b72
--- /dev/null
+++ b/src/core/libraries/ajm/ajm_aac.cpp
@@ -0,0 +1,194 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "ajm.h"
+#include "ajm_aac.h"
+#include "ajm_result.h"
+
+// using this internal header to manually configure the decoder in RAW mode
+#include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h"
+
+#include
+#include
+
+namespace Libraries::Ajm {
+
+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) {}
+
+AjmAacDecoder::~AjmAacDecoder() {
+ aacDecoder_Close(m_decoder);
+}
+
+TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) {
+ switch (config_type) {
+ case ConfigType::ADTS:
+ return TT_MP4_ADTS;
+ case ConfigType::RAW:
+ return TT_MP4_RAW;
+ default:
+ UNREACHABLE();
+ }
+}
+
+static UINT g_freq[] = {
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000,
+};
+
+void AjmAacDecoder::Reset() {
+ if (m_decoder) {
+ aacDecoder_Close(m_decoder);
+ }
+
+ m_decoder = aacDecoder_Open(TransportTypeFromConfigType(m_init_params.config_type), 1);
+ if (m_init_params.config_type == ConfigType::RAW) {
+ // Manually configure the decoder
+ // Things may be incorrect due to limited documentation
+ CSAudioSpecificConfig asc{};
+ asc.m_aot = AOT_AAC_LC;
+ asc.m_samplingFrequency = g_freq[m_init_params.sampling_freq_type];
+ asc.m_samplingFrequencyIndex = m_init_params.sampling_freq_type;
+ asc.m_samplesPerFrame = 1024;
+ asc.m_epConfig = -1;
+ switch (m_channels) {
+ case 0:
+ asc.m_channelConfiguration = 2;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ asc.m_channelConfiguration = m_channels;
+ break;
+ case 7:
+ asc.m_channelConfiguration = 11;
+ break;
+ case 8:
+ asc.m_channelConfiguration = 12; // 7, 12 or 14 ?
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ UCHAR changed = 1;
+ CAacDecoder_Init(m_decoder, &asc, AC_CM_ALLOC_MEM, &changed);
+ }
+ m_skip_frames = True(m_flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2;
+}
+
+void AjmAacDecoder::Initialize(const void* buffer, u32 buffer_size) {
+ ASSERT(buffer_size == 8);
+ m_init_params = *reinterpret_cast(buffer);
+ Reset();
+}
+
+void AjmAacDecoder::GetInfo(void* out_info) const {
+ auto* codec_info = reinterpret_cast(out_info);
+ *codec_info = {
+ .heaac = True(m_flags & AjmAacCodecFlags::EnableSbrDecode),
+ };
+}
+
+AjmSidebandFormat AjmAacDecoder::GetFormat() const {
+ const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
+ return {
+ .num_channels = static_cast(info->numChannels),
+ .channel_mask = GetChannelMask(info->numChannels),
+ .sampl_freq = static_cast(info->sampleRate),
+ .sample_encoding = m_format, // AjmFormatEncoding
+ .bitrate = static_cast(info->bitRate),
+ };
+}
+
+u32 AjmAacDecoder::GetMinimumInputSize() const {
+ return 0;
+}
+
+u32 AjmAacDecoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
+ const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
+ if (info->aacSamplesPerFrame <= 0) {
+ return 0;
+ }
+ const auto skip_samples = std::min(gapless.current.skip_samples, info->frameSize);
+ const auto samples =
+ gapless.init.total_samples != 0
+ ? std::min(gapless.current.total_samples, info->frameSize - skip_samples)
+ : info->frameSize - skip_samples;
+ return samples * info->numChannels * GetPCMSize(m_format);
+}
+
+DecoderResult AjmAacDecoder::ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) {
+ DecoderResult result{};
+
+ // Discard the previous contents of the internal buffer and replace them with new ones
+ aacDecoder_SetParam(m_decoder, AAC_TPDEC_CLEAR_BUFFER, 1);
+ UCHAR* buffers[] = {input.data()};
+ 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);
+
+ switch (ret) {
+ case AAC_DEC_OK:
+ break;
+ case AAC_DEC_NOT_ENOUGH_BITS:
+ result.result = ORBIS_AJM_RESULT_PARTIAL_INPUT;
+ return result;
+ default:
+ LOG_ERROR(Lib_Ajm, "aacDecoder_DecodeFrame failed ret = {:#x}", static_cast(ret));
+ result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
+ result.internal_result = ret;
+ return result;
+ }
+
+ const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
+ auto bytes_used = info->numTotalBytes;
+
+ result.frames_decoded += 1;
+ input = input.subspan(bytes_used);
+
+ if (m_skip_frames > 0) {
+ --m_skip_frames;
+ return result;
+ }
+
+ u32 skip_samples = 0;
+ if (gapless.current.skip_samples > 0) {
+ skip_samples = std::min(info->frameSize, gapless.current.skip_samples);
+ gapless.current.skip_samples -= skip_samples;
+ }
+
+ const auto max_samples =
+ gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame;
+
+ size_t pcm_written = 0;
+ switch (m_format) {
+ case AjmFormatEncoding::S16:
+ pcm_written = WriteOutputSamples(output, skip_samples * info->numChannels,
+ max_samples * info->numChannels);
+ break;
+ case AjmFormatEncoding::S32:
+ UNREACHABLE_MSG("NOT IMPLEMENTED");
+ break;
+ case AjmFormatEncoding::Float:
+ UNREACHABLE_MSG("NOT IMPLEMENTED");
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ result.samples_written = pcm_written / info->numChannels;
+ gapless.current.skipped_samples += info->frameSize - result.samples_written;
+ if (gapless.init.total_samples != 0) {
+ gapless.current.total_samples -= result.samples_written;
+ }
+
+ return result;
+}
+
+} // namespace Libraries::Ajm
diff --git a/src/core/libraries/ajm/ajm_aac.h b/src/core/libraries/ajm/ajm_aac.h
new file mode 100644
index 000000000..7ca8ecbf8
--- /dev/null
+++ b/src/core/libraries/ajm/ajm_aac.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/enum.h"
+#include "common/types.h"
+#include "core/libraries/ajm/ajm_instance.h"
+
+#include
+#include
+
+struct AAC_DECODER_INSTANCE;
+
+namespace Libraries::Ajm {
+
+enum ConfigType : u32 {
+ ADTS = 1,
+ RAW = 2,
+};
+
+enum AjmAacCodecFlags : u32 {
+ EnableSbrDecode = 1 << 0,
+ EnableNondelayOutput = 1 << 1,
+ SurroundChannelInterleaveOrderExtlExtrLsRs = 1 << 2,
+ SurroundChannelInterleaveOrderLsRsExtlExtr = 1 << 3,
+};
+DECLARE_ENUM_FLAG_OPERATORS(AjmAacCodecFlags)
+
+struct AjmSidebandDecM4aacCodecInfo {
+ u32 heaac;
+ u32 reserved;
+};
+
+struct AjmAacDecoder final : AjmCodec {
+ explicit AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels);
+ ~AjmAacDecoder() override;
+
+ void Reset() override;
+ void Initialize(const void* buffer, u32 buffer_size) override;
+ void GetInfo(void* out_info) const override;
+ AjmSidebandFormat GetFormat() const override;
+ u32 GetMinimumInputSize() const override;
+ u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
+ DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) override;
+
+private:
+ struct InitializeParameters {
+ ConfigType config_type;
+ u32 sampling_freq_type;
+ };
+
+ 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));
+ }
+
+ const AjmFormatEncoding m_format;
+ const AjmAacCodecFlags m_flags;
+ const u32 m_channels;
+ std::vector m_pcm_buffer;
+
+ u32 m_skip_frames = 0;
+ InitializeParameters m_init_params = {};
+ AAC_DECODER_INSTANCE* m_decoder = nullptr;
+};
+
+} // namespace Libraries::Ajm
diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp
index 014d1a4e5..4452d032d 100644
--- a/src/core/libraries/ajm/ajm_at9.cpp
+++ b/src/core/libraries/ajm/ajm_at9.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "ajm_result.h"
#include "common/assert.h"
#include "core/libraries/ajm/ajm_at9.h"
#include "error_codes.h"
@@ -53,7 +54,7 @@ struct RIFFHeader {
};
static_assert(sizeof(RIFFHeader) == 12);
-AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
+AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32)
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {}
AjmAt9Decoder::~AjmAt9Decoder() {
@@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const {
auto* info = reinterpret_cast(out_info);
info->super_frame_size = m_codec_info.superframeSize;
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
+ info->next_frame_size = m_superframe_bytes_remain;
info->frame_samples = m_codec_info.frameSamples;
- info->next_frame_size = static_cast(m_handle)->Config.FrameBytes;
}
u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
@@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span& in_buf, AjmInstanceGapless& g
}
}
-std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
- SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) {
- bool is_reset = false;
+u32 AjmAt9Decoder::GetMinimumInputSize() const {
+ return m_superframe_bytes_remain;
+}
+
+DecoderResult AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) {
+ DecoderResult result{};
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
*reinterpret_cast(in_buf.data()) == 'FFIR') {
ParseRIFFHeader(in_buf, gapless);
- is_reset = true;
+ result.is_reset = true;
}
if (!m_is_initialized) {
- return {0, 0, is_reset};
+ result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
+ return result;
}
int ret = 0;
@@ -166,7 +171,14 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
default:
UNREACHABLE();
}
- ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
+ if (ret != At9Status::ERR_SUCCESS) {
+ LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret);
+ result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
+ result.internal_result = ret;
+ return result;
+ }
+
+ result.frames_decoded += 1;
in_buf = in_buf.subspan(bytes_used);
m_superframe_bytes_remain -= bytes_used;
@@ -196,10 +208,10 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
UNREACHABLE();
}
- const auto samples_written = pcm_written / m_codec_info.channels;
- gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
+ result.samples_written = pcm_written / m_codec_info.channels;
+ gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written;
if (gapless.init.total_samples != 0) {
- gapless.current.total_samples -= samples_written;
+ gapless.current.total_samples -= result.samples_written;
}
m_num_frames += 1;
@@ -209,9 +221,23 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf,
}
m_superframe_bytes_remain = m_codec_info.superframeSize;
m_num_frames = 0;
+ } else if (gapless.IsEnd()) {
+ // Drain the remaining superframe
+ std::vector buf(m_codec_info.frameSamples * m_codec_info.channels, 0);
+ while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) {
+ ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used,
+ True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
+ in_buf = in_buf.subspan(bytes_used);
+ m_superframe_bytes_remain -= bytes_used;
+ result.frames_decoded += 1;
+ m_num_frames += 1;
+ }
+ in_buf = in_buf.subspan(m_superframe_bytes_remain);
+ m_superframe_bytes_remain = m_codec_info.superframeSize;
+ m_num_frames = 0;
}
- return {1, m_codec_info.frameSamples, is_reset};
+ return result;
}
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
@@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
const auto samples =
gapless.init.total_samples != 0
? std::min(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples)
- : m_codec_info.frameSamples;
+ : m_codec_info.frameSamples - skip_samples;
return samples * m_codec_info.channels * GetPCMSize(m_format);
}
diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h
index 3262f1aa0..8eb6166e2 100644
--- a/src/core/libraries/ajm/ajm_at9.h
+++ b/src/core/libraries/ajm/ajm_at9.h
@@ -3,6 +3,7 @@
#pragma once
+#include "common/enum.h"
#include "common/types.h"
#include "core/libraries/ajm/ajm_instance.h"
@@ -13,8 +14,6 @@
namespace Libraries::Ajm {
-constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
-
enum AjmAt9CodecFlags : u32 {
ParseRiffHeader = 1 << 0,
NonInterleavedOutput = 1 << 8,
@@ -29,16 +28,17 @@ struct AjmSidebandDecAt9CodecInfo {
};
struct AjmAt9Decoder final : AjmCodec {
- explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
+ explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32 channels);
~AjmAt9Decoder() override;
void Reset() override;
void Initialize(const void* buffer, u32 buffer_size) override;
void GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() const override;
+ u32 GetMinimumInputSize() const override;
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
- std::tuple ProcessData(std::span& input, SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) override;
+ DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) override;
private:
template
diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp
index 30e1deb71..61f80e482 100644
--- a/src/core/libraries/ajm/ajm_batch.cpp
+++ b/src/core/libraries/ajm/ajm_batch.cpp
@@ -165,7 +165,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf
ASSERT(job_flags.has_value());
job.flags = job_flags.value();
- AjmStatisticsJobFlags flags(job.flags);
+ AjmStatisticsJobFlags flags{.raw = job.flags.raw};
if (input_control_buffer.has_value()) {
AjmBatchBuffer input_batch(input_control_buffer.value());
if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) {
diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h
index 09daa630d..7f8890898 100644
--- a/src/core/libraries/ajm/ajm_batch.h
+++ b/src/core/libraries/ajm/ajm_batch.h
@@ -52,6 +52,7 @@ struct AjmBatch {
u32 id{};
std::atomic_bool waiting{};
std::atomic_bool canceled{};
+ std::atomic_bool processed{};
std::binary_semaphore finished{0};
boost::container::small_vector jobs;
diff --git a/src/core/libraries/ajm/ajm_context.cpp b/src/core/libraries/ajm/ajm_context.cpp
index 0e2915f32..8ce8f3434 100644
--- a/src/core/libraries/ajm/ajm_context.cpp
+++ b/src/core/libraries/ajm/ajm_context.cpp
@@ -18,7 +18,8 @@
namespace Libraries::Ajm {
-static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
+constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
+constexpr int INSTANCE_ID_MASK = 0x3FFF;
AjmContext::AjmContext() {
worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); });
@@ -39,7 +40,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) {
batch = *p_batch;
}
- batch->canceled = true;
+ if (batch->processed) {
+ return ORBIS_OK;
+ }
+
+ bool expected = false;
+ batch->canceled.compare_exchange_strong(expected, true);
return ORBIS_OK;
}
@@ -58,7 +64,9 @@ void AjmContext::WorkerThread(std::stop_token stop) {
Common::SetCurrentThreadName("shadPS4:AjmWorker");
while (!stop.stop_requested()) {
auto batch = batch_queue.PopWait(stop);
- if (batch != nullptr) {
+ if (batch != nullptr && !batch->canceled) {
+ bool expected = false;
+ batch->processed.compare_exchange_strong(expected, true);
ProcessBatch(batch->id, batch->jobs);
batch->finished.release();
}
@@ -77,7 +85,7 @@ void AjmContext::ProcessBatch(u32 id, std::span jobs) {
std::shared_ptr instance;
{
std::shared_lock lock(instances_mutex);
- auto* p_instance = instances.Get(job.instance_id);
+ auto* p_instance = instances.Get(job.instance_id & INSTANCE_ID_MASK);
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
instance = *p_instance;
}
@@ -169,15 +177,15 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
if (!opt_index.has_value()) {
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
}
- *out_instance = opt_index.value();
+ *out_instance = opt_index.value() | (static_cast(codec_type) << 14);
LOG_INFO(Lib_Ajm, "instance = {}", *out_instance);
return ORBIS_OK;
}
-s32 AjmContext::InstanceDestroy(u32 instance) {
+s32 AjmContext::InstanceDestroy(u32 instance_id) {
std::unique_lock lock(instances_mutex);
- if (!instances.Destroy(instance)) {
+ if (!instances.Destroy(instance_id & INSTANCE_ID_MASK)) {
return ORBIS_AJM_ERROR_INVALID_INSTANCE;
}
return ORBIS_OK;
diff --git a/src/core/libraries/ajm/ajm_instance.cpp b/src/core/libraries/ajm/ajm_instance.cpp
index c4ea395b9..d25517c81 100644
--- a/src/core/libraries/ajm/ajm_instance.cpp
+++ b/src/core/libraries/ajm/ajm_instance.cpp
@@ -1,27 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "core/libraries/ajm/ajm_at9.h"
-#include "core/libraries/ajm/ajm_instance.h"
-#include "core/libraries/ajm/ajm_mp3.h"
+#include "ajm_aac.h"
+#include "ajm_at9.h"
+#include "ajm_instance.h"
+#include "ajm_mp3.h"
+#include "ajm_result.h"
#include
namespace Libraries::Ajm {
-constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
-constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
-constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
-constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
-constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
-constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
-constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
-constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
-constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
-constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
-constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
-constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
-
u8 GetPCMSize(AjmFormatEncoding format) {
switch (format) {
case AjmFormatEncoding::S16:
@@ -38,13 +27,18 @@ u8 GetPCMSize(AjmFormatEncoding format) {
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
switch (codec_type) {
case AjmCodecType::At9Dec: {
- m_codec = std::make_unique(AjmFormatEncoding(flags.format),
- AjmAt9CodecFlags(flags.codec));
+ m_codec = std::make_unique(
+ AjmFormatEncoding(flags.format), AjmAt9CodecFlags(flags.codec), u32(flags.channels));
break;
}
case AjmCodecType::Mp3Dec: {
- m_codec = std::make_unique(AjmFormatEncoding(flags.format),
- AjmMp3CodecFlags(flags.codec));
+ m_codec = std::make_unique(
+ AjmFormatEncoding(flags.format), AjmMp3CodecFlags(flags.codec), u32(flags.channels));
+ break;
+ }
+ case AjmCodecType::M4aacDec: {
+ m_codec = std::make_unique(
+ AjmFormatEncoding(flags.format), AjmAacCodecFlags(flags.codec), u32(flags.channels));
break;
}
default:
@@ -60,6 +54,7 @@ void AjmInstance::Reset() {
void AjmInstance::ExecuteJob(AjmJob& job) {
const auto control_flags = job.flags.control_flags;
+ job.output.p_result->result = 0;
if (True(control_flags & AjmJobControlFlags::Reset)) {
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
Reset();
@@ -91,8 +86,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.current.total_samples -= sample_difference;
} else {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
- job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
- return;
+ job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
}
}
@@ -106,61 +100,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.current.skip_samples -= sample_difference;
} else {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
- job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
- return;
+ job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
}
}
}
- if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
- std::span in_buf(job.input.buffer);
- SparseOutputBuffer out_buf(job.output.buffers);
+ std::span in_buf(job.input.buffer);
+ SparseOutputBuffer out_buf(job.output.buffers);
+ auto in_size = in_buf.size();
+ auto out_size = out_buf.Size();
+ u32 frames_decoded = 0;
- u32 frames_decoded = 0;
- auto in_size = in_buf.size();
- auto out_size = out_buf.Size();
- while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
- if (!HasEnoughSpace(out_buf)) {
- if (job.output.p_mframe == nullptr || frames_decoded == 0) {
- LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})",
- out_buf.Size(), m_codec->GetNextFrameSize(m_gapless));
- job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
- break;
- }
- }
-
- const auto [nframes, nsamples, reset] =
- m_codec->ProcessData(in_buf, out_buf, m_gapless);
- if (reset) {
+ if (!job.input.buffer.empty()) {
+ for (;;) {
+ if (m_flags.gapless_loop && m_gapless.IsEnd()) {
+ m_gapless.Reset();
m_total_samples = 0;
}
- if (!nframes) {
- LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED");
- job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
+ if (!HasEnoughSpace(out_buf)) {
+ LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(),
+ m_codec->GetNextFrameSize(m_gapless));
+ job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
+ }
+ if (in_buf.size() < m_codec->GetMinimumInputSize()) {
+ job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
+ }
+ if (job.output.p_result->result != 0) {
+ break;
+ }
+ const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless);
+ if (result.is_reset) {
+ m_total_samples = 0;
+ } else {
+ m_total_samples += result.samples_written;
+ }
+ frames_decoded += result.frames_decoded;
+ if (result.result != 0) {
+ job.output.p_result->result |= result.result;
+ job.output.p_result->internal_result = result.internal_result;
break;
}
- frames_decoded += nframes;
- m_total_samples += nsamples;
-
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
break;
}
}
+ }
- const auto total_decoded_samples = m_total_samples;
- if (m_flags.gapless_loop && m_gapless.IsEnd()) {
- in_buf = in_buf.subspan(in_buf.size());
- m_gapless.Reset();
- m_codec->Reset();
- }
- if (job.output.p_mframe) {
- job.output.p_mframe->num_frames = frames_decoded;
- }
- if (job.output.p_stream) {
- job.output.p_stream->input_consumed = in_size - in_buf.size();
- job.output.p_stream->output_written = out_size - out_buf.Size();
- job.output.p_stream->total_decoded_samples = total_decoded_samples;
- }
+ if (job.output.p_mframe) {
+ job.output.p_mframe->num_frames = frames_decoded;
+ }
+ if (job.output.p_stream) {
+ job.output.p_stream->input_consumed = in_size - in_buf.size();
+ job.output.p_stream->output_written = out_size - out_buf.Size();
+ job.output.p_stream->total_decoded_samples = m_total_samples;
}
if (job.output.p_format != nullptr) {
@@ -175,6 +167,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
}
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
+ if (m_gapless.IsEnd()) {
+ return true;
+ }
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
}
diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h
index ad0a82f29..db53add4d 100644
--- a/src/core/libraries/ajm/ajm_instance.h
+++ b/src/core/libraries/ajm/ajm_instance.h
@@ -73,6 +73,14 @@ struct AjmInstanceGapless {
}
};
+struct DecoderResult {
+ s32 result = 0;
+ s32 internal_result = 0;
+ u32 frames_decoded = 0;
+ u32 samples_written = 0;
+ bool is_reset = false;
+};
+
class AjmCodec {
public:
virtual ~AjmCodec() = default;
@@ -81,9 +89,10 @@ public:
virtual void Reset() = 0;
virtual void GetInfo(void* out_info) const = 0;
virtual AjmSidebandFormat GetFormat() const = 0;
+ virtual u32 GetMinimumInputSize() const = 0;
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
- virtual std::tuple ProcessData(std::span& input, SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) = 0;
+ virtual DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) = 0;
};
class AjmInstance {
@@ -94,7 +103,6 @@ public:
private:
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
- std::optional GetNumRemainingSamples() const;
void Reset();
AjmInstanceFlags m_flags{};
diff --git a/src/core/libraries/ajm/ajm_instance_statistics.cpp b/src/core/libraries/ajm/ajm_instance_statistics.cpp
index c0c1af8bb..2e4a65914 100644
--- a/src/core/libraries/ajm/ajm_instance_statistics.cpp
+++ b/src/core/libraries/ajm/ajm_instance_statistics.cpp
@@ -8,7 +8,7 @@ namespace Libraries::Ajm {
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
if (job.output.p_engine) {
- job.output.p_engine->usage_batch = 0.01;
+ job.output.p_engine->usage_batch = 0.05;
const auto ic = job.input.statistics_engine_parameters->interval_count;
for (u32 idx = 0; idx < ic; ++idx) {
job.output.p_engine->usage_interval[idx] = 0.01;
@@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
job.output.p_memory->batch_size = 0x4200;
job.output.p_memory->input_size = 0x2000;
job.output.p_memory->output_size = 0x2000;
- job.output.p_memory->small_size = 0x200;
+ job.output.p_memory->small_size = 0x400;
}
}
+void AjmInstanceStatistics::Reset() {}
+
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
static AjmInstanceStatistics instance;
return instance;
diff --git a/src/core/libraries/ajm/ajm_instance_statistics.h b/src/core/libraries/ajm/ajm_instance_statistics.h
index ea70c9d56..0ec79aeac 100644
--- a/src/core/libraries/ajm/ajm_instance_statistics.h
+++ b/src/core/libraries/ajm/ajm_instance_statistics.h
@@ -10,6 +10,7 @@ namespace Libraries::Ajm {
class AjmInstanceStatistics {
public:
void ExecuteJob(AjmJob& job);
+ void Reset();
static AjmInstanceStatistics& Getinstance();
};
diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp
index f17f53d51..f4ce22b8b 100644
--- a/src/core/libraries/ajm/ajm_mp3.cpp
+++ b/src/core/libraries/ajm/ajm_mp3.cpp
@@ -105,7 +105,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
return new_frame;
}
-AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags)
+AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32)
: m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) {
int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
@@ -122,6 +122,7 @@ void AjmMp3Decoder::Reset() {
avcodec_flush_buffers(m_codec_context);
m_header.reset();
m_frame_samples = 0;
+ m_frame_size = 0;
}
void AjmMp3Decoder::GetInfo(void* out_info) const {
@@ -138,16 +139,28 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
}
}
-std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
- SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) {
+u32 AjmMp3Decoder::GetMinimumInputSize() const {
+ // 4 bytes is for mp3 header that contains frame_size
+ return std::max(m_frame_size, 4);
+}
+
+DecoderResult AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) {
+ DecoderResult result{};
AVPacket* pkt = av_packet_alloc();
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
m_header = std::byteswap(*reinterpret_cast(in_buf.data()));
AjmDecMp3ParseFrame info{};
- ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
+ ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
m_frame_samples = info.samples_per_channel;
+ m_frame_size = info.frame_size;
+ gapless.init = {
+ .total_samples = info.total_samples,
+ .skip_samples = static_cast(info.encoder_delay),
+ .skipped_samples = 0,
+ };
+ gapless.current = gapless.init;
}
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
@@ -155,9 +168,6 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
in_buf = in_buf.subspan(ret);
- u32 frames_decoded = 0;
- u32 samples_decoded = 0;
-
if (pkt->size) {
// Send the packet with the compressed data to the decoder
pkt->pts = m_parser->pts;
@@ -177,9 +187,8 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
UNREACHABLE_MSG("Error during decoding");
}
frame = ConvertAudioFrame(frame);
- samples_decoded += u32(frame->nb_samples);
- frames_decoded += 1;
+ result.frames_decoded += 1;
u32 skip_samples = 0;
if (gapless.current.skip_samples > 0) {
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
@@ -211,6 +220,7 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
if (gapless.init.total_samples != 0) {
gapless.current.total_samples -= samples;
}
+ result.samples_written += samples;
av_frame_free(&frame);
}
@@ -218,16 +228,16 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf,
av_packet_free(&pkt);
- return {frames_decoded, samples_decoded, false};
+ return result;
}
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
- const auto max_samples = gapless.init.total_samples != 0
- ? std::min(gapless.current.total_samples, m_frame_samples)
- : m_frame_samples;
- const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
- return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
- GetPCMSize(m_format);
+ const auto skip_samples = std::min(gapless.current.skip_samples, m_frame_samples);
+ const auto samples =
+ gapless.init.total_samples != 0
+ ? std::min(gapless.current.total_samples, m_frame_samples - skip_samples)
+ : m_frame_samples - skip_samples;
+ return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format);
}
class BitReader {
@@ -264,7 +274,7 @@ private:
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame) {
- LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
+ LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
@@ -301,7 +311,8 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
BitReader reader(p_current);
if (header->protection_type == 0) {
- reader.Skip(16); // crc = reader.Read(16);
+ // crc = reader.Read(16);
+ reader.Skip(16);
}
if (header->version == Mp3AudioVersion::V1) {
diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h
index 7ac65fdba..ecbc77051 100644
--- a/src/core/libraries/ajm/ajm_mp3.h
+++ b/src/core/libraries/ajm/ajm_mp3.h
@@ -3,6 +3,7 @@
#pragma once
+#include "common/enum.h"
#include "common/types.h"
#include "core/libraries/ajm/ajm_instance.h"
@@ -63,16 +64,17 @@ struct AjmSidebandDecMp3CodecInfo {
class AjmMp3Decoder : public AjmCodec {
public:
- explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags);
+ explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32 channels);
~AjmMp3Decoder() override;
void Reset() override;
void Initialize(const void* buffer, u32 buffer_size) override {}
void GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() const override;
+ u32 GetMinimumInputSize() const override;
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
- std::tuple ProcessData(std::span& input, SparseOutputBuffer& output,
- AjmInstanceGapless& gapless) override;
+ DecoderResult ProcessData(std::span& input, SparseOutputBuffer& output,
+ AjmInstanceGapless& gapless) override;
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame);
@@ -97,6 +99,7 @@ private:
SwrContext* m_swr_context = nullptr;
std::optional m_header;
u32 m_frame_samples = 0;
+ u32 m_frame_size = 0;
};
} // namespace Libraries::Ajm
diff --git a/src/core/libraries/ajm/ajm_result.h b/src/core/libraries/ajm/ajm_result.h
new file mode 100644
index 000000000..d4e6d1147
--- /dev/null
+++ b/src/core/libraries/ajm/ajm_result.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
+constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
+constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
+constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
+constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
+constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
+constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
+constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
+constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
+constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
+constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
+constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp
index 1d1638bd0..4a95c60c9 100644
--- a/src/core/libraries/ime/ime_dialog_ui.cpp
+++ b/src/core/libraries/ime/ime_dialog_ui.cpp
@@ -48,7 +48,7 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
title.resize(title_len * 4 + 1);
title[title_len * 4] = '\0';
- if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) {
+ if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4 + 1)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding");
}
}
@@ -59,14 +59,14 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
placeholder[placeholder_len * 4] = '\0';
if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0],
- placeholder_len * 4)) {
+ placeholder_len * 4 + 1)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding");
}
}
std::size_t text_len = std::char_traits::length(text_buffer);
if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
- ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) {
+ ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4 + 1)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
}
}
@@ -110,7 +110,7 @@ bool ImeDialogState::CopyTextToOrbisBuffer() {
}
return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer,
- max_text_length);
+ static_cast(max_text_length) + 1);
}
bool ImeDialogState::CallTextFilter() {
@@ -380,10 +380,12 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
.timestamp = {0},
};
- if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) {
+ char16_t tmp_char[2] = {0};
+ if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, tmp_char, 2)) {
LOG_ERROR(Lib_ImeDialog, "InputTextCallback: ConvertUTF8ToOrbis failed");
return 0;
}
+ src_keycode.character = tmp_char[0];
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: converted to Orbis char={:#X}",
static_cast(src_keycode.character));
src_keycode.keycode = src_keycode.character; // TODO set this to the correct value
diff --git a/src/core/libraries/network/net.h b/src/core/libraries/network/net.h
index 2f1339d0a..4cb7afdda 100644
--- a/src/core/libraries/network/net.h
+++ b/src/core/libraries/network/net.h
@@ -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
#pragma once
@@ -110,6 +110,15 @@ enum OrbisNetSocketSoOption : u32 {
ORBIS_NET_SO_PRIORITY = 0x1203
};
+enum OrbisNetFlags : u32 {
+ ORBIS_NET_MSG_PEEK = 0x00000002,
+ ORBIS_NET_MSG_WAITALL = 0x00000040,
+ ORBIS_NET_MSG_DONTWAIT = 0x00000080,
+ ORBIS_NET_MSG_USECRYPTO = 0x00100000,
+ ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
+ ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
+};
+
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
switch (o) {
case ORBIS_NET_SO_REUSEADDR:
diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp
index 3ffb42528..7992fa217 100644
--- a/src/core/libraries/network/posix_sockets.cpp
+++ b/src/core/libraries/network/posix_sockets.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
@@ -184,28 +184,103 @@ int PosixSocket::Listen(int backlog) {
return ConvertReturnErrorCode(::listen(sock, backlog));
}
+static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
+ int posix_flags = 0;
+
+ if (sce_flags & ORBIS_NET_MSG_PEEK)
+ posix_flags |= MSG_PEEK;
+#ifndef _WIN32
+ if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
+ posix_flags |= MSG_DONTWAIT;
+#endif
+ // MSG_WAITALL is only valid for stream sockets
+ if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
+ ((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
+ posix_flags |= MSG_WAITALL;
+
+ return posix_flags;
+}
+
+// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
+// This function uses select() with zero timeout to simulate non-blocking behavior.
+static int socket_is_ready(int sock, bool is_read = true) {
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(sock, &fds);
+ timeval timeout{0, 0};
+ int res =
+ select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
+ if (res == 0)
+ return ORBIS_NET_ERROR_EWOULDBLOCK;
+ else if (res < 0)
+ return ConvertReturnErrorCode(res);
+
+ return res;
+}
+
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{m_mutex};
+
#ifdef _WIN32
- DWORD bytesSent = 0;
- LPFN_WSASENDMSG wsasendmsg = nullptr;
- GUID guid = WSAID_WSASENDMSG;
- DWORD bytes = 0;
+ int totalSent = 0;
+ bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
+ bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
- if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
- sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
- return ConvertReturnErrorCode(-1);
+ // stream socket with multiple buffers
+ bool use_wsamsg =
+ (socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
+ msg->msg_iovlen > 1;
+
+ for (int i = 0; i < msg->msg_iovlen; ++i) {
+ char* buf = (char*)msg->msg_iov[i].iov_base;
+ size_t remaining = msg->msg_iov[i].iov_len;
+
+ while (remaining > 0) {
+ if (dontWait) {
+ int ready = socket_is_ready(sock, false);
+ if (ready <= 0)
+ return ready;
+ }
+
+ int sent = 0;
+ if (use_wsamsg) {
+ // only call WSASendMsg if we have multiple buffers
+ LPFN_WSASENDMSG wsasendmsg = nullptr;
+ GUID guid = WSAID_WSASENDMSG;
+ DWORD bytes = 0;
+ if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
+ &wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
+ // fallback to send()
+ sent = ::send(sock, buf, remaining, 0);
+ } else {
+ DWORD bytesSent = 0;
+ int res = wsasendmsg(
+ sock, reinterpret_cast(const_cast(msg)), 0,
+ &bytesSent, nullptr, nullptr);
+ if (res == SOCKET_ERROR)
+ return ConvertReturnErrorCode(WSAGetLastError());
+ sent = bytesSent;
+ }
+ } else {
+ sent = ::send(sock, buf, remaining, 0);
+ if (sent == SOCKET_ERROR)
+ return ConvertReturnErrorCode(WSAGetLastError());
+ }
+
+ totalSent += sent;
+ remaining -= sent;
+ buf += sent;
+
+ if (!waitAll)
+ break;
+ }
}
- int res = wsasendmsg(sock, reinterpret_cast(const_cast(msg)), flags,
- &bytesSent, nullptr, nullptr);
+ return totalSent;
- if (res == SOCKET_ERROR) {
- return ConvertReturnErrorCode(-1);
- }
- return static_cast(bytesSent);
#else
- int res = sendmsg(sock, reinterpret_cast(msg), flags);
+ int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
+ int res = sendmsg(sock, reinterpret_cast(msg), native_flags);
return ConvertReturnErrorCode(res);
#endif
}
@@ -213,37 +288,92 @@ int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
u32 tolen) {
std::scoped_lock lock{m_mutex};
- if (to != nullptr) {
- sockaddr addr;
- convertOrbisNetSockaddrToPosix(to, &addr);
- return ConvertReturnErrorCode(
- sendto(sock, (const char*)msg, len, flags, &addr, sizeof(sockaddr_in)));
- } else {
- return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
+ int res = 0;
+#ifdef _WIN32
+ if (flags & ORBIS_NET_MSG_DONTWAIT) {
+ res = socket_is_ready(sock, false);
+ if (res <= 0)
+ return res;
}
+#endif
+ const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
+ if (to == nullptr) {
+ res = send(sock, (const char*)msg, len, posix_flags);
+ } else {
+ sockaddr addr{};
+ convertOrbisNetSockaddrToPosix(to, &addr);
+ res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
+ }
+ return ConvertReturnErrorCode(res);
}
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
std::scoped_lock lock{receive_mutex};
+
#ifdef _WIN32
- LPFN_WSARECVMSG wsarecvmsg = nullptr;
- GUID guid = WSAID_WSARECVMSG;
- DWORD bytes = 0;
+ int totalReceived = 0;
+ bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
+ bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
- if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
- sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
- return ConvertReturnErrorCode(-1);
+ // stream socket with multiple buffers
+ bool use_wsarecvmsg =
+ (socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
+ msg->msg_iovlen > 1;
+
+ for (int i = 0; i < msg->msg_iovlen; ++i) {
+ char* buf = (char*)msg->msg_iov[i].iov_base;
+ size_t remaining = msg->msg_iov[i].iov_len;
+
+ while (remaining > 0) {
+ // emulate DONTWAIT
+ if (dontWait) {
+ int ready = socket_is_ready(sock, true);
+ if (ready <= 0)
+ return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
+ }
+
+ int received = 0;
+ if (use_wsarecvmsg) {
+ // only call WSARecvMsg if multiple buffers + stream
+ LPFN_WSARECVMSG wsarecvmsg = nullptr;
+ GUID guid = WSAID_WSARECVMSG;
+ DWORD bytes = 0;
+ if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
+ &wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
+ // fallback to recv()
+ received = ::recv(sock, buf, remaining, 0);
+ if (received == SOCKET_ERROR)
+ return ConvertReturnErrorCode(WSAGetLastError());
+ } else {
+ DWORD bytesReceived = 0;
+ int res = wsarecvmsg(sock, reinterpret_cast(msg), &bytesReceived,
+ nullptr, nullptr);
+ if (res == SOCKET_ERROR)
+ return ConvertReturnErrorCode(WSAGetLastError());
+ received = bytesReceived;
+ }
+ } else {
+ // fallback to recv() for UDP or single-buffer
+ received = ::recv(sock, buf, remaining, 0);
+ if (received == SOCKET_ERROR)
+ return ConvertReturnErrorCode(WSAGetLastError());
+ }
+
+ totalReceived += received;
+ remaining -= received;
+ buf += received;
+
+ // stop after first receive if WAITALL is not set
+ if (!waitAll)
+ break;
+ }
}
- DWORD bytesReceived = 0;
- int res = wsarecvmsg(sock, reinterpret_cast(msg), &bytesReceived, nullptr, nullptr);
+ return totalReceived;
- if (res == SOCKET_ERROR) {
- return ConvertReturnErrorCode(-1);
- }
- return static_cast(bytesReceived);
#else
- int res = recvmsg(sock, reinterpret_cast(msg), flags);
+ int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
+ int res = recvmsg(sock, reinterpret_cast(msg), native_flags);
return ConvertReturnErrorCode(res);
#endif
}
@@ -251,15 +381,27 @@ int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
u32* fromlen) {
std::scoped_lock lock{receive_mutex};
- if (from != nullptr) {
- sockaddr addr;
- int res = recvfrom(sock, (char*)buf, len, flags, &addr, (socklen_t*)fromlen);
- convertPosixSockaddrToOrbis(&addr, from);
- *fromlen = sizeof(OrbisNetSockaddrIn);
- return ConvertReturnErrorCode(res);
- } else {
- return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
+ int res = 0;
+#ifdef _WIN32
+ if (flags & ORBIS_NET_MSG_DONTWAIT) {
+ res = socket_is_ready(sock);
+ if (res <= 0)
+ return res;
}
+#endif
+ const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
+ if (from == nullptr) {
+ res = recv(sock, (char*)buf, len, posix_flags);
+ } else {
+ sockaddr addr{};
+ socklen_t addrlen = sizeof(addr);
+ res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
+ (fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
+ if (res > 0)
+ convertPosixSockaddrToOrbis(&addr, from);
+ }
+
+ return ConvertReturnErrorCode(res);
}
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
diff --git a/src/core/libraries/network/sockets.h b/src/core/libraries/network/sockets.h
index 281c83ab4..86661c71c 100644
--- a/src/core/libraries/network/sockets.h
+++ b/src/core/libraries/network/sockets.h
@@ -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
#pragma once
@@ -62,7 +62,7 @@ struct OrbisNetLinger {
s32 l_linger;
};
struct Socket {
- explicit Socket(int domain, int type, int protocol) {}
+ explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
virtual ~Socket() = default;
virtual bool IsValid() const = 0;
virtual int Close() = 0;
@@ -84,6 +84,7 @@ struct Socket {
virtual std::optional Native() = 0;
std::mutex m_mutex;
std::mutex receive_mutex;
+ int socket_type;
};
struct PosixSocket : public Socket {
diff --git a/src/core/signals.cpp b/src/core/signals.cpp
index 4099ac237..db6e4b6cc 100644
--- a/src/core/signals.cpp
+++ b/src/core/signals.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/arch.h"
@@ -42,14 +42,6 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
#else
-static std::string GetThreadName() {
- char name[256];
- if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
- return "";
- }
- return std::string{name};
-}
-
static std::string DisassembleInstruction(void* code_address) {
char buffer[256] = "";
@@ -80,18 +72,16 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
case SIGBUS: {
const bool is_write = Common::IsWriteError(raw_context);
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
- UNREACHABLE_MSG(
- "Unhandled access violation in thread '{}' at code address {}: {} address {}",
- GetThreadName(), fmt::ptr(code_address), is_write ? "Write to" : "Read from",
- fmt::ptr(info->si_addr));
+ UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}",
+ fmt::ptr(code_address), is_write ? "Write to" : "Read from",
+ fmt::ptr(info->si_addr));
}
break;
}
case SIGILL:
if (!signals->DispatchIllegalInstruction(raw_context)) {
- UNREACHABLE_MSG("Unhandled illegal instruction in thread '{}' at code address {}: {}",
- GetThreadName(), fmt::ptr(code_address),
- DisassembleInstruction(code_address));
+ UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
+ fmt::ptr(code_address), DisassembleInstruction(code_address));
}
break;
case SIGUSR1: { // Sleep thread until signal is received
diff --git a/src/emulator.cpp b/src/emulator.cpp
index f0026068c..6f199649f 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -14,6 +14,7 @@
#include "common/debug.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
+#include "common/thread.h"
#include "core/ipc/ipc.h"
#ifdef ENABLE_DISCORD_RPC
#include "common/discord_rpc_handler.h"
@@ -75,6 +76,7 @@ Emulator::~Emulator() {}
void Emulator::Run(std::filesystem::path file, std::vector args,
std::optional p_game_folder) {
+ Common::SetCurrentThreadName("Main Thread");
if (waitForDebuggerBeforeRun) {
Debugger::WaitForDebuggerAttach();
}
diff --git a/src/main.cpp b/src/main.cpp
index b3a8586ba..e19e8a938 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -188,7 +188,9 @@ int main(int argc, char* argv[]) {
if (argc == 1) {
if (!SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_INFORMATION, "shadPS4",
- "This is a CLI application. Please use the QTLauncher for a GUI.", nullptr))
+ "This is a CLI application. Please use the QTLauncher for a GUI: "
+ "https://github.com/shadps4-emu/shadps4-qtlauncher/releases",
+ nullptr))
std::cerr << "Could not display SDL message box! Error: " << SDL_GetError() << "\n";
int dummy = 0; // one does not simply pass 0 directly
arg_map.at("-h")(dummy);