diff --git a/.gitmodules b/.gitmodules index c5d05edd3..c0ba5e79d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -120,3 +120,6 @@ [submodule "externals/miniz"] path = externals/miniz url = https://github.com/richgel999/miniz +[submodule "externals/aacdec/fdk-aac"] + path = externals/aacdec/fdk-aac + url = https://android.googlesource.com/platform/external/aac diff --git a/CMakeLists.txt b/CMakeLists.txt index 04534ec26..99aca5268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,8 +202,8 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "12") -set(EMULATOR_VERSION_PATCH "6") +set(EMULATOR_VERSION_MINOR "13") +set(EMULATOR_VERSION_PATCH "1") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") @@ -262,6 +262,8 @@ include_directories(src) set(AJM_LIB src/core/libraries/ajm/ajm.cpp src/core/libraries/ajm/ajm.h + src/core/libraries/ajm/ajm_aac.cpp + src/core/libraries/ajm/ajm_aac.h src/core/libraries/ajm/ajm_at9.cpp src/core/libraries/ajm/ajm_at9.h src/core/libraries/ajm/ajm_batch.cpp @@ -1085,7 +1087,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index c85fcf003..d2a6747d9 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -11,6 +11,7 @@ GPL-2.0 net.shadps4.shadPS4.desktop https://shadps4.net/ + https://github.com/shadps4-emu/shadPS4

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);