Merge branch 'shadps4-emu:main' into fontlib

This commit is contained in:
Valdis Bogdāns 2026-01-06 14:25:59 +02:00 committed by GitHub
commit cfcd365158
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 878 additions and 199 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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")

View File

@ -11,6 +11,7 @@
<project_license translate="no">GPL-2.0</project_license>
<launchable type="desktop-id" translate="no">net.shadps4.shadPS4.desktop</launchable>
<url type="homepage" translate="no">https://shadps4.net/</url>
<url type="vcs-browser" translate="no">https://github.com/shadps4-emu/shadPS4</url>
<description>
<p>shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.</p>
<p>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.</p>
@ -37,6 +38,9 @@
<category translate="no">Game</category>
</categories>
<releases>
<release version="0.13.0" date="2025-12-24">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0</url>
</release>
<release version="0.12.5" date="2025-11-07">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</url>
</release>

View File

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

154
externals/aacdec/CMakeLists.txt vendored Normal file
View File

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

1
externals/aacdec/fdk-aac vendored Submodule

@ -0,0 +1 @@
Subproject commit ee76460efbdb147e26d804c798949c23f174460b

View File

@ -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 <array>
@ -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) {

View File

@ -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<unsigned char> byteArray =
std::vector<unsigned char>(valueStr.begin(), valueStr.end());

View File

@ -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 <ctime>
#include <string>
#include <thread>
#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<std::chrono::nanoseconds>(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 "<unknown name>";
}
return std::string{name};
#endif
}
} // namespace Common

View File

@ -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

View File

@ -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<AjmCodecType>((instance_id >> 14) & 0x1F);
}
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,

View File

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

View File

@ -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 <aacdecoder_lib.h>
#include <magic_enum/magic_enum.hpp>
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<const InitializeParameters*>(buffer);
Reset();
}
void AjmAacDecoder::GetInfo(void* out_info) const {
auto* codec_info = reinterpret_cast<AjmSidebandDecM4aacCodecInfo*>(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<u32>(info->numChannels),
.channel_mask = GetChannelMask(info->numChannels),
.sampl_freq = static_cast<u32>(info->sampleRate),
.sample_encoding = m_format, // AjmFormatEncoding
.bitrate = static_cast<u32>(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<u32>(gapless.current.skip_samples, info->frameSize);
const auto samples =
gapless.init.total_samples != 0
? std::min<u32>(gapless.current.total_samples, info->frameSize - skip_samples)
: info->frameSize - skip_samples;
return samples * info->numChannels * GetPCMSize(m_format);
}
DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& 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<UINT>(input.size())};
UINT valid = sizes[0];
aacDecoder_Fill(m_decoder, buffers, sizes, &valid);
auto ret = aacDecoder_DecodeFrame(m_decoder, reinterpret_cast<s16*>(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<u32>(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<u16>(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<s16>(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

View File

@ -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 <span>
#include <vector>
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<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
private:
struct InitializeParameters {
ConfigType config_type;
u32 sampling_freq_type;
};
template <class T>
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_pcm, u32 max_pcm) {
std::span<T> pcm_data{reinterpret_cast<T*>(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<u8> m_pcm_buffer;
u32 m_skip_frames = 0;
InitializeParameters m_init_params = {};
AAC_DECODER_INSTANCE* m_decoder = nullptr;
};
} // namespace Libraries::Ajm

View File

@ -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<AjmSidebandDecAt9CodecInfo*>(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<Atrac9Handle*>(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<u8>& in_buf, AjmInstanceGapless& g
}
}
std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
bool is_reset = false;
u32 AjmAt9Decoder::GetMinimumInputSize() const {
return m_superframe_bytes_remain;
}
DecoderResult AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
DecoderResult result{};
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
*reinterpret_cast<u32*>(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<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& 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<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& 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<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
}
m_superframe_bytes_remain = m_codec_info.superframeSize;
m_num_frames = 0;
} else if (gapless.IsEnd()) {
// Drain the remaining superframe
std::vector<s16> 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<u32>(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);
}

View File

@ -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<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
private:
template <class T>

View File

@ -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)) {

View File

@ -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<AjmJob, 16> jobs;

View File

@ -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<AjmJob> jobs) {
std::shared_ptr<AjmInstance> 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<u32>(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;

View File

@ -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 <magic_enum/magic_enum.hpp>
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<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
AjmAt9CodecFlags(flags.codec));
m_codec = std::make_unique<AjmAt9Decoder>(
AjmFormatEncoding(flags.format), AjmAt9CodecFlags(flags.codec), u32(flags.channels));
break;
}
case AjmCodecType::Mp3Dec: {
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
AjmMp3CodecFlags(flags.codec));
m_codec = std::make_unique<AjmMp3Decoder>(
AjmFormatEncoding(flags.format), AjmMp3CodecFlags(flags.codec), u32(flags.channels));
break;
}
case AjmCodecType::M4aacDec: {
m_codec = std::make_unique<AjmAacDecoder>(
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<u8> in_buf(job.input.buffer);
SparseOutputBuffer out_buf(job.output.buffers);
std::span<u8> 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);
}

View File

@ -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<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) = 0;
virtual DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) = 0;
};
class AjmInstance {
@ -94,7 +103,6 @@ public:
private:
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
std::optional<u32> GetNumRemainingSamples() const;
void Reset();
AjmInstanceFlags m_flags{};

View File

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

View File

@ -10,6 +10,7 @@ namespace Libraries::Ajm {
class AjmInstanceStatistics {
public:
void ExecuteJob(AjmJob& job);
void Reset();
static AjmInstanceStatistics& Getinstance();
};

View File

@ -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<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
u32 AjmMp3Decoder::GetMinimumInputSize() const {
// 4 bytes is for mp3 header that contains frame_size
return std::max<u32>(m_frame_size, 4);
}
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& 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<u32*>(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<u16>(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<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& 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<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& 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<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& 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<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& 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<u32>(gapless.current.skip_samples, m_frame_samples);
const auto samples =
gapless.init.total_samples != 0
? std::min<u32>(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<u16>(16);
// crc = reader.Read<u16>(16);
reader.Skip(16);
}
if (header->version == Mp3AudioVersion::V1) {

View File

@ -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<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
DecoderResult ProcessData(std::span<u8>& 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<u32> m_header;
u32 m_frame_samples = 0;
u32 m_frame_size = 0;
};
} // namespace Libraries::Ajm

View File

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

View File

@ -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<char16_t>::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<std::size_t>(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<uint16_t>(src_keycode.character));
src_keycode.keycode = src_keycode.character; // TODO set this to the correct value

View File

@ -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:

View File

@ -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 <vector>
@ -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<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(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<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
&bytesSent, nullptr, nullptr);
return totalSent;
if (res == SOCKET_ERROR) {
return ConvertReturnErrorCode(-1);
}
return static_cast<int>(bytesSent);
#else
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(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<LPWSAMSG>(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<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
return totalReceived;
if (res == SOCKET_ERROR) {
return ConvertReturnErrorCode(-1);
}
return static_cast<int>(bytesReceived);
#else
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
int res = recvmsg(sock, reinterpret_cast<msghdr*>(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) {

View File

@ -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<net_socket> Native() = 0;
std::mutex m_mutex;
std::mutex receive_mutex;
int socket_type;
};
struct PosixSocket : public Socket {

View File

@ -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 "<unknown name>";
}
return std::string{name};
}
static std::string DisassembleInstruction(void* code_address) {
char buffer[256] = "<unable to decode>";
@ -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

View File

@ -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<std::string> args,
std::optional<std::filesystem::path> p_game_folder) {
Common::SetCurrentThreadName("Main Thread");
if (waitForDebuggerBeforeRun) {
Debugger::WaitForDebuggerAttach();
}

View File

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