From c813c5e9531f7c8a9e3cc049427048a7514da741 Mon Sep 17 00:00:00 2001 From: capriots <29807355+capriots@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:20:26 +0200 Subject: [PATCH] cellAtracXdec implementation --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/Modules/cellAdec.cpp | 59 +- rpcs3/Emu/Cell/Modules/cellAdec.h | 101 --- rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp | 854 +++++++++++++++++++++++ rpcs3/Emu/Cell/Modules/cellAtracXdec.h | 300 ++++++++ rpcs3/Emu/Cell/PPUModule.cpp | 1 + rpcs3/Emu/Cell/PPUModule.h | 1 + rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 + 9 files changed, 1166 insertions(+), 159 deletions(-) create mode 100644 rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp create mode 100644 rpcs3/Emu/Cell/Modules/cellAtracXdec.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index ea91dd1dec..5cd19b300f 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -239,6 +239,7 @@ target_sources(rpcs3_emu PRIVATE Cell/Modules/cellAdec.cpp Cell/Modules/cellAtrac.cpp Cell/Modules/cellAtracMulti.cpp + Cell/Modules/cellAtracXdec.cpp Cell/Modules/cellAudio.cpp Cell/Modules/cellAudioOut.cpp Cell/Modules/cellAuthDialog.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellAdec.cpp b/rpcs3/Emu/Cell/Modules/cellAdec.cpp index 031337a5b2..5eb80638f7 100644 --- a/rpcs3/Emu/Cell/Modules/cellAdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAdec.cpp @@ -28,6 +28,7 @@ extern "C" #endif #include "cellPamf.h" +#include "cellAtracXdec.h" #include "cellAdec.h" #include @@ -136,63 +137,6 @@ void fmt_class_string::format(std::string& out, u64 arg) STR_CASE(CELL_ADEC_ERROR_AT3_BUSY); STR_CASE(CELL_ADEC_ERROR_AT3_EMPTY); STR_CASE(CELL_ADEC_ERROR_AT3_ERROR); - STR_CASE(CELL_ADEC_ERROR_ATX_OK); // CELL_ADEC_ERROR_ATX_OFFSET, CELL_ADEC_ERROR_ATX_NONE - STR_CASE(CELL_ADEC_ERROR_ATX_BUSY); - STR_CASE(CELL_ADEC_ERROR_ATX_EMPTY); - STR_CASE(CELL_ADEC_ERROR_ATX_ATSHDR); - STR_CASE(CELL_ADEC_ERROR_ATX_NON_FATAL); - STR_CASE(CELL_ADEC_ERROR_ATX_NOT_IMPLE); - STR_CASE(CELL_ADEC_ERROR_ATX_PACK_CE_OVERFLOW); - STR_CASE(CELL_ADEC_ERROR_ATX_ILLEGAL_NPROCQUS); - STR_CASE(CELL_ADEC_ERROR_ATX_FATAL); - STR_CASE(CELL_ADEC_ERROR_ATX_ENC_OVERFLOW); - STR_CASE(CELL_ADEC_ERROR_ATX_PACK_CE_UNDERFLOW); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDCT); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GAINADJ); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_SPECTRA); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GHWAVE); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_SHEADER); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_A); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_B); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_C); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_D); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_E); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_A); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_B); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_C); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_D); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDCT_A); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_NGC); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_A); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_A); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_B); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_B); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_SN_NWVS); - STR_CASE(CELL_ADEC_ERROR_ATX_FATAL_HANDLE); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_SAMPLING_FREQ); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_CH_CONFIG_INDEX); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_NBYTES); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_NUM); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_ID); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_CHANNELS); - STR_CASE(CELL_ADEC_ERROR_ATX_UNINIT_BLOCK_SPECIFIED); - STR_CASE(CELL_ADEC_ERROR_ATX_POSCFG_PRESENT); - STR_CASE(CELL_ADEC_ERROR_ATX_BUFFER_OVERFLOW); - STR_CASE(CELL_ADEC_ERROR_ATX_ILL_BLK_TYPE_ID); - STR_CASE(CELL_ADEC_ERROR_ATX_UNPACK_CHANNEL_BLK_FAILED); - STR_CASE(CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_1); - STR_CASE(CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_2); - STR_CASE(CELL_ADEC_ERROR_ATX_ILLEGAL_ENC_SETTING); - STR_CASE(CELL_ADEC_ERROR_ATX_ILLEGAL_DEC_SETTING); - STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_NSAMPLES); - STR_CASE(CELL_ADEC_ERROR_ATX_ILL_SYNCWORD); - STR_CASE(CELL_ADEC_ERROR_ATX_ILL_SAMPLING_FREQ); - STR_CASE(CELL_ADEC_ERROR_ATX_ILL_CH_CONFIG_INDEX); - STR_CASE(CELL_ADEC_ERROR_ATX_RAW_DATA_FRAME_SIZE_OVER); - STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_ENHANCE_LENGTH_OVER); - STR_CASE(CELL_ADEC_ERROR_ATX_SPU_INTERNAL_FAIL); STR_CASE(CELL_ADEC_ERROR_LPCM_FATAL); STR_CASE(CELL_ADEC_ERROR_LPCM_SEQ); STR_CASE(CELL_ADEC_ERROR_LPCM_ARG); @@ -1119,7 +1063,6 @@ DECLARE(ppu_module_manager::cellAdec)("cellAdec", []() { static ppu_static_module cell_libac3dec("cell_libac3dec"); static ppu_static_module cellAtrac3dec("cellAtrac3dec"); - static ppu_static_module cellAtracXdec("cellAtracXdec"); static ppu_static_module cellCelpDec("cellCelpDec"); static ppu_static_module cellDTSdec("cellDTSdec"); static ppu_static_module cellM2AACdec("cellM2AACdec"); diff --git a/rpcs3/Emu/Cell/Modules/cellAdec.h b/rpcs3/Emu/Cell/Modules/cellAdec.h index c11a432106..8313b45b8d 100644 --- a/rpcs3/Emu/Cell/Modules/cellAdec.h +++ b/rpcs3/Emu/Cell/Modules/cellAdec.h @@ -111,68 +111,6 @@ enum CellAdecError : u32 CELL_ADEC_ERROR_AT3_ERROR = 0x80612180, - CELL_ADEC_ERROR_ATX_OFFSET = 0x80612200, - CELL_ADEC_ERROR_ATX_NONE = 0x80612200, - CELL_ADEC_ERROR_ATX_OK = 0x80612200, - CELL_ADEC_ERROR_ATX_BUSY = 0x80612264, - CELL_ADEC_ERROR_ATX_EMPTY = 0x80612265, - CELL_ADEC_ERROR_ATX_ATSHDR = 0x80612266, - CELL_ADEC_ERROR_ATX_NON_FATAL = 0x80612281, - CELL_ADEC_ERROR_ATX_NOT_IMPLE = 0x80612282, - CELL_ADEC_ERROR_ATX_PACK_CE_OVERFLOW = 0x80612283, - CELL_ADEC_ERROR_ATX_ILLEGAL_NPROCQUS = 0x80612284, - CELL_ADEC_ERROR_ATX_FATAL = 0x8061228c, - CELL_ADEC_ERROR_ATX_ENC_OVERFLOW = 0x8061228d, - CELL_ADEC_ERROR_ATX_PACK_CE_UNDERFLOW = 0x8061228e, - CELL_ADEC_ERROR_ATX_SYNTAX_IDCT = 0x8061228f, - CELL_ADEC_ERROR_ATX_SYNTAX_GAINADJ = 0x80612290, - CELL_ADEC_ERROR_ATX_SYNTAX_IDSF = 0x80612291, - CELL_ADEC_ERROR_ATX_SYNTAX_SPECTRA = 0x80612292, - CELL_ADEC_ERROR_ATX_SYNTAX_IDWL = 0x80612293, - CELL_ADEC_ERROR_ATX_SYNTAX_GHWAVE = 0x80612294, - CELL_ADEC_ERROR_ATX_SYNTAX_SHEADER = 0x80612295, - CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_A = 0x80612296, - CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_B = 0x80612297, - CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_C = 0x80612298, - CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_D = 0x80612299, - CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_E = 0x8061229a, - CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_A = 0x8061229b, - CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_B = 0x8061229c, - CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_C = 0x8061229d, - CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_D = 0x8061229e, - CELL_ADEC_ERROR_ATX_SYNTAX_IDCT_A = 0x8061229f, - CELL_ADEC_ERROR_ATX_SYNTAX_GC_NGC = 0x806122a0, - CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_A = 0x806122a1, - CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_A = 0x806122a2, - CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_B = 0x806122a3, - CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_B = 0x806122a4, - CELL_ADEC_ERROR_ATX_SYNTAX_SN_NWVS = 0x806122a5, - CELL_ADEC_ERROR_ATX_FATAL_HANDLE = 0x806122aa, - CELL_ADEC_ERROR_ATX_ASSERT_SAMPLING_FREQ = 0x806122ab, - CELL_ADEC_ERROR_ATX_ASSERT_CH_CONFIG_INDEX = 0x806122ac, - CELL_ADEC_ERROR_ATX_ASSERT_NBYTES = 0x806122ad, - CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_NUM = 0x806122ae, - CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_ID = 0x806122af, - CELL_ADEC_ERROR_ATX_ASSERT_CHANNELS = 0x806122b0, - CELL_ADEC_ERROR_ATX_UNINIT_BLOCK_SPECIFIED = 0x806122b1, - CELL_ADEC_ERROR_ATX_POSCFG_PRESENT = 0x806122b2, - CELL_ADEC_ERROR_ATX_BUFFER_OVERFLOW = 0x806122b3, - CELL_ADEC_ERROR_ATX_ILL_BLK_TYPE_ID = 0x806122b4, - CELL_ADEC_ERROR_ATX_UNPACK_CHANNEL_BLK_FAILED = 0x806122b5, - CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_1 = 0x806122b6, - CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_2 = 0x806122b7, - CELL_ADEC_ERROR_ATX_ILLEGAL_ENC_SETTING = 0x806122b8, - CELL_ADEC_ERROR_ATX_ILLEGAL_DEC_SETTING = 0x806122b9, - CELL_ADEC_ERROR_ATX_ASSERT_NSAMPLES = 0x806122ba, - - CELL_ADEC_ERROR_ATX_ILL_SYNCWORD = 0x806122bb, - CELL_ADEC_ERROR_ATX_ILL_SAMPLING_FREQ = 0x806122bc, - CELL_ADEC_ERROR_ATX_ILL_CH_CONFIG_INDEX = 0x806122bd, - CELL_ADEC_ERROR_ATX_RAW_DATA_FRAME_SIZE_OVER = 0x806122be, - CELL_ADEC_ERROR_ATX_SYNTAX_ENHANCE_LENGTH_OVER = 0x806122bf, - CELL_ADEC_ERROR_ATX_SPU_INTERNAL_FAIL = 0x806122c8, - - CELL_ADEC_ERROR_LPCM_FATAL = 0x80612001, CELL_ADEC_ERROR_LPCM_SEQ = 0x80612002, CELL_ADEC_ERROR_LPCM_ARG = 0x80612003, @@ -903,45 +841,6 @@ struct CellAdecAtrac3Info be_t nbytes; }; -enum ATRACX_WordSize : s32 -{ - CELL_ADEC_ATRACX_WORD_SZ_16BIT = 0x02, - CELL_ADEC_ATRACX_WORD_SZ_24BIT = 0x03, - CELL_ADEC_ATRACX_WORD_SZ_32BIT = 0x04, - CELL_ADEC_ATRACX_WORD_SZ_FLOAT = 0x84, -}; - -enum ATRACX_ATSHeaderInclude : u8 -{ - CELL_ADEC_ATRACX_ATS_HDR_NOTINC = 0, - CELL_ADEC_ATRACX_ATS_HDR_INC = 1, -}; - -enum ATRACX_DownmixFlag : u8 -{ - ATRACX_DOWNMIX_OFF = 0, - ATRACX_DOWNMIX_ON = 1, -}; - -struct CellAdecParamAtracX -{ - be_t sampling_freq; - be_t ch_config_idx; - be_t nch_out; - be_t nbytes; - std::array extra_config_data; // downmix coefficients - be_t bw_pcm; // ATRACX_WordSize - ATRACX_DownmixFlag downmix_flag; - ATRACX_ATSHeaderInclude au_includes_ats_hdr_flg; -}; - -struct CellAdecAtracXInfo -{ - be_t samplingFreq; // [Hz] - be_t channelConfigIndex; - be_t nbytes; -}; - enum MP3_WordSize : s32 { CELL_ADEC_MP3_WORD_SZ_16BIT = 3, diff --git a/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp b/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp new file mode 100644 index 0000000000..ac94007770 --- /dev/null +++ b/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp @@ -0,0 +1,854 @@ +#include "stdafx.h" +#include "Emu/perf_meter.hpp" +#include "Emu/Cell/PPUModule.h" +#include "Emu/Cell/lv2/sys_sync.h" +#include "Emu/Cell/lv2/sys_ppu_thread.h" +#include "Emu/savestate_utils.hpp" +#include "sysPrxForUser.h" +#include "util/asm.hpp" +#include "util/media_utils.h" + +#include "cellAtracXdec.h" + +vm::gvar g_cell_adec_core_ops_atracx2ch; +vm::gvar g_cell_adec_core_ops_atracx6ch; +vm::gvar g_cell_adec_core_ops_atracx8ch; +vm::gvar g_cell_adec_core_ops_atracx; + +LOG_CHANNEL(cellAtracXdec); + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](CellAtracXdecError value) + { + switch (value) + { + STR_CASE(CELL_ADEC_ERROR_ATX_OK); // CELL_ADEC_ERROR_ATX_OFFSET, CELL_ADEC_ERROR_ATX_NONE + STR_CASE(CELL_ADEC_ERROR_ATX_BUSY); + STR_CASE(CELL_ADEC_ERROR_ATX_EMPTY); + STR_CASE(CELL_ADEC_ERROR_ATX_ATSHDR); + STR_CASE(CELL_ADEC_ERROR_ATX_NON_FATAL); + STR_CASE(CELL_ADEC_ERROR_ATX_NOT_IMPLE); + STR_CASE(CELL_ADEC_ERROR_ATX_PACK_CE_OVERFLOW); + STR_CASE(CELL_ADEC_ERROR_ATX_ILLEGAL_NPROCQUS); + STR_CASE(CELL_ADEC_ERROR_ATX_FATAL); + STR_CASE(CELL_ADEC_ERROR_ATX_ENC_OVERFLOW); + STR_CASE(CELL_ADEC_ERROR_ATX_PACK_CE_UNDERFLOW); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDCT); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GAINADJ); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_SPECTRA); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GHWAVE); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_SHEADER); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_A); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_B); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_C); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_D); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_E); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_A); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_B); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_C); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_D); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_IDCT_A); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_NGC); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_A); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_A); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_B); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_B); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_SN_NWVS); + STR_CASE(CELL_ADEC_ERROR_ATX_FATAL_HANDLE); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_SAMPLING_FREQ); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_CH_CONFIG_INDEX); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_NBYTES); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_NUM); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_ID); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_CHANNELS); + STR_CASE(CELL_ADEC_ERROR_ATX_UNINIT_BLOCK_SPECIFIED); + STR_CASE(CELL_ADEC_ERROR_ATX_POSCFG_PRESENT); + STR_CASE(CELL_ADEC_ERROR_ATX_BUFFER_OVERFLOW); + STR_CASE(CELL_ADEC_ERROR_ATX_ILL_BLK_TYPE_ID); + STR_CASE(CELL_ADEC_ERROR_ATX_UNPACK_CHANNEL_BLK_FAILED); + STR_CASE(CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_1); + STR_CASE(CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_2); + STR_CASE(CELL_ADEC_ERROR_ATX_ILLEGAL_ENC_SETTING); + STR_CASE(CELL_ADEC_ERROR_ATX_ILLEGAL_DEC_SETTING); + STR_CASE(CELL_ADEC_ERROR_ATX_ASSERT_NSAMPLES); + STR_CASE(CELL_ADEC_ERROR_ATX_ILL_SYNCWORD); + STR_CASE(CELL_ADEC_ERROR_ATX_ILL_SAMPLING_FREQ); + STR_CASE(CELL_ADEC_ERROR_ATX_ILL_CH_CONFIG_INDEX); + STR_CASE(CELL_ADEC_ERROR_ATX_RAW_DATA_FRAME_SIZE_OVER); + STR_CASE(CELL_ADEC_ERROR_ATX_SYNTAX_ENHANCE_LENGTH_OVER); + STR_CASE(CELL_ADEC_ERROR_ATX_SPU_INTERNAL_FAIL); + } + + return unknown; + }); +} + +constexpr u32 atracXdecGetSpursMemSize(u32 nch_in) +{ + switch (nch_in) + { + case 1: return 0x6000; + case 2: return 0x6000; + case 3: return 0x12880; + case 4: return 0x19c80; + case 5: return -1; + case 6: return 0x23080; + case 7: return 0x2a480; + case 8: return 0x2c480; + default: return -1; + } +} + +void AtracXdecDecoder::alloc_avcodec() +{ + codec = avcodec_find_decoder(AV_CODEC_ID_ATRAC3P); + if (!codec) + { + fmt::throw_exception("avcodec_find_decoder() failed"); + } + + ctx = avcodec_alloc_context3(codec); + if (!ctx) + { + fmt::throw_exception("avcodec_alloc_context3() failed"); + } + + // Allows FFmpeg to output directly into guest memory + ctx->opaque = this; + ctx->get_buffer2 = [](AVCodecContext* s, AVFrame* frame, int /*flags*/) -> int + { + for (s32 i = 0; i < frame->ch_layout.nb_channels; i++) + { + frame->data[i] = static_cast(s->opaque)->work_mem.get_ptr() + ATXDEC_MAX_FRAME_LENGTH + ATXDEC_SAMPLES_PER_FRAME * sizeof(f32) * i; + frame->linesize[i] = ATXDEC_SAMPLES_PER_FRAME * sizeof(f32); + } + + frame->buf[0] = av_buffer_create(frame->data[0], ATXDEC_SAMPLES_PER_FRAME * sizeof(f32) * frame->ch_layout.nb_channels, [](void*, uint8_t*){}, nullptr, 0); + return 0; + }; + + packet = av_packet_alloc(); + if (!packet) + { + fmt::throw_exception("av_packet_alloc() failed"); + } + + frame = av_frame_alloc(); + if (!frame) + { + fmt::throw_exception("av_frame_alloc() failed"); + } +} + +void AtracXdecDecoder::free_avcodec() +{ + av_packet_free(&packet); + av_frame_free(&frame); + avcodec_free_context(&ctx); +} + +void AtracXdecDecoder::init_avcodec() +{ + avcodec_close(ctx); + + ctx->block_align = nbytes; + ctx->ch_layout.nb_channels = nch_in; + ctx->sample_rate = sampling_freq; + + if (int err = avcodec_open2(ctx, codec, nullptr); err) + { + fmt::throw_exception("avcodec_open2() failed (err=0x%x='%s')", err, utils::av_error_to_string(err)); + } + + packet->data = work_mem.get_ptr(); + packet->size = nbytes; + packet->buf = av_buffer_create(work_mem.get_ptr(), nbytes, [](void*, uint8_t*){}, nullptr, 0); +} + +error_code AtracXdecDecoder::set_config_info(u32 sampling_freq, u32 ch_config_idx, u32 nbytes) +{ + cellAtracXdec.notice("AtracXdecDecoder::set_config_info(sampling_freq=%d, ch_config_idx=%d, nbytes=0x%x)", sampling_freq, ch_config_idx, nbytes); + + this->sampling_freq = sampling_freq; + this->ch_config_idx = ch_config_idx; + this->nbytes = nbytes; + this->nbytes_128_aligned = utils::align(nbytes, 0x80); + this->nch_in = ch_config_idx <= 4 ? ch_config_idx : ch_config_idx + 1; + + if (ch_config_idx > 7u) + { + this->config_is_set = false; + return { 0x80004005, "AtracXdecDecoder::set_config_info() failed: Invalid channel configuration: %d", ch_config_idx }; + } + + this->nch_blocks = ATXDEC_NCH_BLOCKS_MAP[ch_config_idx]; + + // These checks are performed on the LLE SPU thread + if (ch_config_idx == 0u) + { + this->config_is_set = false; + return { 0x80004005, "AtracXdecDecoder::set_config_info() failed: Invalid channel configuration: %d", ch_config_idx }; + } + + if (sampling_freq != 48000u && sampling_freq != 44100u) // 32kHz is not supported, even though official docs claim it is + { + this->config_is_set = false; + return { 0x80004005, "AtracXdecDecoder::set_config_info() failed: Invalid sample rate: %d", sampling_freq }; + } + + if (nbytes == 0u || nbytes > ATXDEC_MAX_FRAME_LENGTH) + { + this->config_is_set = false; + return { 0x80004005, "AtracXdecDecoder::set_config_info() failed: Invalid frame length: 0x%x", nbytes }; + } + + this->config_is_set = true; + return CELL_OK; +} + +error_code AtracXdecDecoder::init_decode(u32 bw_pcm, u32 nch_out) +{ + if (bw_pcm < CELL_ADEC_ATRACX_WORD_SZ_16BIT || (bw_pcm > CELL_ADEC_ATRACX_WORD_SZ_32BIT && bw_pcm != CELL_ADEC_ATRACX_WORD_SZ_FLOAT)) + { + return { 0x80004005, "AtracXdecDecoder::init_decode() failed: Invalid PCM output format" }; + } + + this->bw_pcm = bw_pcm; + this->nch_out = nch_out; // Not checked for invalid values on LLE + this->pcm_output_size = (bw_pcm == CELL_ADEC_ATRACX_WORD_SZ_16BIT ? sizeof(s16) : sizeof(f32)) * nch_in * ATXDEC_SAMPLES_PER_FRAME; + + init_avcodec(); + return CELL_OK; +} + +error_code AtracXdecDecoder::parse_ats_header(vm::cptr au_start_addr) +{ + const auto ats = std::bit_cast(vm::read64(au_start_addr.addr())); + + if (ats.sync_word != 0x0fd0) + { + return { CELL_ADEC_ERROR_ATX_ATSHDR, "AtracXdecDecoder::parse_ats_header() failed: Invalid sync word: 0x%x", ats.sync_word }; + } + + const u8 sample_rate_idx = ats.params >> 13; + const u8 ch_config_idx = ats.params >> 10 & 7; + const u16 nbytes = ((ats.params & 0x3ff) + 1) * 8; + + if (ch_config_idx == 0u) + { + return { CELL_ADEC_ERROR_ATX_ATSHDR, "AtracXdecDecoder::parse_ats_header() failed: Invalid channel configuration: %d", ch_config_idx }; + } + + u32 sampling_freq; + switch (sample_rate_idx) + { + case 1: sampling_freq = 44100; break; + case 2: sampling_freq = 48000; break; + default: return { CELL_ADEC_ERROR_ATX_ATSHDR, "AtracXdecDecoder::parse_ats_header() failed: Invalid sample rate index: %d", sample_rate_idx }; + } + + return set_config_info(sampling_freq, ch_config_idx, nbytes); // Cannot return error here, values were already checked +} + +void AtracXdecContext::exec(ppu_thread& ppu) +{ + perf_meter<"ATXDEC"_u64> perf0; + + // Savestates + if (decoder.config_is_set) + { + decoder.init_avcodec(); + } + + for (;;cmd_counter++) + { + cellAtracXdec.trace("Command counter: %llu, waiting for next command...", cmd_counter); + + if (!skip_getting_command) + { + lv2_obj::sleep(ppu); + std::lock_guard lock{queue_mutex}; + + while (cmd_queue.empty() && thread_ctrl::state() != thread_state::aborting) + { + lv2_obj::sleep(ppu); + queue_not_empty.wait(queue_mutex, 20000); + } + + if (!run_thread || thread_ctrl::state() == thread_state::aborting) + { + return; + } + + cmd_queue.pop(cmd); + } + + cellAtracXdec.trace("Command type: %d", static_cast(cmd.type.get())); + + switch (cmd.type) + { + case AtracXdecCmdType::start_seq: + first_decode = true; + skip_next_frame = true; + + // Skip if access units contain an ATS header, the parameters are included in the header and we need to wait for the first decode command to parse them + if (cmd.atracx_param.au_includes_ats_hdr_flg == CELL_ADEC_ATRACX_ATS_HDR_NOTINC) + { + if (decoder.set_config_info(cmd.atracx_param.sampling_freq, cmd.atracx_param.ch_config_idx, cmd.atracx_param.nbytes) == static_cast(0x80004005)) + { + break; + } + + if (decoder.init_decode(cmd.atracx_param.bw_pcm, cmd.atracx_param.nch_out) == static_cast(0x80004005)) + { + break; + } + } + + atracx_param = cmd.atracx_param; + break; + + case AtracXdecCmdType::end_seq: + { + skip_getting_command = true; + + // Block savestate creation during callbacks + std::unique_lock savestate_lock{g_fxo->get(), std::try_to_lock}; + + if (!savestate_lock.owns_lock()) + { + return; + } + + skip_getting_command = false; + + // Doesn't do anything else + notify_seq_done.cbFunc(ppu, notify_seq_done.cbArg); + break; + } + + case AtracXdecCmdType::decode_au: + { + skip_getting_command = true; + + ensure(!!cmd.au_start_addr); // Not checked on LLE + + cellAtracXdec.trace("Waiting for output to be consumed..."); + + lv2_obj::sleep(ppu); + std::unique_lock output_mutex_lock{output_mutex}; + + while (output_locked && thread_ctrl::state() != thread_state::aborting) + { + lv2_obj::sleep(ppu); + output_consumed.wait(output_mutex, 20000); + } + + if (!run_thread || thread_ctrl::state() == thread_state::aborting) + { + return; + } + + cellAtracXdec.trace("Output consumed"); + + u32 error = CELL_OK; + + // Only the first valid ATS header after starting a sequence is parsed. It is ignored on all subsequent access units + if (first_decode && atracx_param.au_includes_ats_hdr_flg == CELL_ADEC_ATRACX_ATS_HDR_INC) + { + // Block savestate creation during callbacks + std::unique_lock savestate_lock{g_fxo->get(), std::try_to_lock}; + + if (!savestate_lock.owns_lock()) + { + return; + } + + if (error = decoder.parse_ats_header(cmd.au_start_addr); error != CELL_OK) + { + notify_error.cbFunc(ppu, error, notify_error.cbArg); + } + else if (decoder.init_decode(atracx_param.bw_pcm, atracx_param.nch_out) != CELL_OK) + { + notify_error.cbFunc(ppu, CELL_ADEC_ERROR_ATX_FATAL, notify_error.cbArg); + } + } + + // LLE does not initialize the output address if parsing the ATS header fails + vm::ptr output = vm::null; + u32 decoded_samples_num = 0; + + if (error != CELL_ADEC_ERROR_ATX_ATSHDR) + { + // The LLE SPU thread would crash if you attempt to decode without a valid configuration + ensure(decoder.config_is_set, "Attempted to decode with invalid configuration"); + + output.set(work_mem.addr() + atracXdecGetSpursMemSize(decoder.nch_in)); + + const auto au_start_addr = atracx_param.au_includes_ats_hdr_flg == CELL_ADEC_ATRACX_ATS_HDR_INC ? cmd.au_start_addr.get_ptr() + sizeof(AtracXdecAtsHeader) : cmd.au_start_addr.get_ptr(); + std::memcpy(work_mem.get_ptr(), au_start_addr, decoder.nbytes); + + if (int err = avcodec_send_packet(decoder.ctx, decoder.packet); err) + { + cellAtracXdec.error("avcodec_send_packet() failed (err=0x%x='%s')", err, utils::av_error_to_string(err)); + error = CELL_ADEC_ERROR_ATX_NON_FATAL; // Not accurate, FFmpeg doesn't provide detailed errors like LLE + } + + // Always call avcodec_receive_frame() to clear the AVFrame, even if avcodec_send_packet() returned an error + if (int err = avcodec_receive_frame(decoder.ctx, decoder.frame); err) + { + cellAtracXdec.error("avcodec_receive_frame() failed (err=0x%x='%s')", err, utils::av_error_to_string(err)); + error = CELL_ADEC_ERROR_ATX_NON_FATAL; // Not accurate, LLE uses an error code dependant on which part of the access unit is invalid + } + + decoded_samples_num = decoder.frame->nb_samples; + ensure(decoded_samples_num == 0u || decoded_samples_num == ATXDEC_SAMPLES_PER_FRAME); + + // The first frame after a starting a new sequence or after an error is replaced with silence + if (skip_next_frame && error == CELL_OK) + { + skip_next_frame = false; + decoded_samples_num = 0; + std::memset(output.get_ptr(), 0, ATXDEC_SAMPLES_PER_FRAME * (decoder.bw_pcm & 0x7full) * decoder.nch_out); + } + + // Convert FFmpeg output to LLE output + const auto output_f32 = vm::static_ptr_cast(output); + const auto output_s16 = vm::static_ptr_cast(output); + const auto output_s32 = vm::static_ptr_cast(output); + + switch (decoder.bw_pcm) + { + case CELL_ADEC_ATRACX_WORD_SZ_FLOAT: + for (u32 channel_idx = 0; channel_idx < decoder.nch_in; channel_idx++) + { + for (u32 sample_idx = 0; sample_idx < decoded_samples_num; sample_idx++) + { + const f32 sample = reinterpret_cast(decoder.frame->data[channel_idx])[sample_idx]; + + if (sample >= std::bit_cast(std::bit_cast(1.f) - 1)) + { + output_f32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = std::bit_cast>(std::array{ 0x3f, 0x7f, 0xff, 0xff }); // Prevents an unnecessary endian swap + } + else if (sample <= -1.f) + { + output_f32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = -1.f; + } + else + { + output_f32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = sample; + } + } + } + break; + + case CELL_ADEC_ATRACX_WORD_SZ_16BIT: + for (u32 channel_idx = 0; channel_idx < decoder.nch_in; channel_idx++) + { + for (u32 sample_idx = 0; sample_idx < decoded_samples_num; sample_idx++) + { + const f32 sample = reinterpret_cast(decoder.frame->data[channel_idx])[sample_idx]; + + if (sample >= 1.f) + { + output_s16[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = INT16_MAX; + } + else if (sample <= -1.f) + { + output_s16[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = INT16_MIN; + } + else + { + output_s16[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = static_cast(std::floor(sample * 0x8000u)); + } + } + } + break; + + case CELL_ADEC_ATRACX_WORD_SZ_24BIT: + for (u32 channel_idx = 0; channel_idx < decoder.nch_in; channel_idx++) + { + for (u32 sample_idx = 0; sample_idx < decoded_samples_num; sample_idx++) + { + const f32 sample = reinterpret_cast(decoder.frame->data[channel_idx])[sample_idx]; + + if (sample >= 1.f) + { + output_s32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = 0x007fffff; + } + else if (sample <= -1.f) + { + output_s32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = 0x00800000; + } + else + { + output_s32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = static_cast(std::floor(sample * 0x00800000u)) & 0x00ffffff; + } + } + } + break; + + case CELL_ADEC_ATRACX_WORD_SZ_32BIT: + for (u32 channel_idx = 0; channel_idx < decoder.nch_in; channel_idx++) + { + for (u32 sample_idx = 0; sample_idx < decoded_samples_num; sample_idx++) + { + const f32 sample = reinterpret_cast(decoder.frame->data[channel_idx])[sample_idx]; + + if (sample >= 1.f) + { + output_s32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = INT32_MAX; + } + else if (sample <= -1.f) + { + output_s32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = INT32_MIN; + } + else + { + output_s32[sample_idx * decoder.nch_in + ATXDEC_AVCODEC_CH_MAP[decoder.ch_config_idx - 1][channel_idx]] = static_cast(std::floor(sample * 0x80000000u)); + } + } + } + } + + first_decode = false; + + if (error != CELL_OK) + { + // Block savestate creation during callbacks + std::unique_lock savestate_lock{g_fxo->get(), std::try_to_lock}; + + if (!savestate_lock.owns_lock()) + { + return; + } + + skip_next_frame = true; + notify_error.cbFunc(ppu, error, notify_error.cbArg); + } + } + + // Block savestate creation during callbacks + std::unique_lock savestate_lock{g_fxo->get(), std::try_to_lock}; + + if (!savestate_lock.owns_lock()) + { + return; + } + + skip_getting_command = false; + + // au_done and pcm_out callbacks are always called after a decode command, even if an error occurred + // The output always has to be consumed as well + notify_au_done.cbFunc(ppu, cmd.pcm_handle, notify_au_done.cbArg); + + output_locked = true; + output_mutex_lock.unlock(); + + const u32 output_size = decoded_samples_num * (decoder.bw_pcm & 0x7fu) * decoder.nch_out; + + const vm::var bsi_info{{ decoder.sampling_freq, decoder.ch_config_idx, decoder.nbytes }}; + + AdecCorrectPtsValueType correct_pts_type = ADEC_CORRECT_PTS_VALUE_TYPE_UNSPECIFIED; + switch (decoder.sampling_freq) + { + case 32000u: correct_pts_type = ADEC_CORRECT_PTS_VALUE_TYPE_ATRACX_32000Hz; break; + case 44100u: correct_pts_type = ADEC_CORRECT_PTS_VALUE_TYPE_ATRACX_44100Hz; break; + case 48000u: correct_pts_type = ADEC_CORRECT_PTS_VALUE_TYPE_ATRACX_48000Hz; break; + } + + notify_pcm_out.cbFunc(ppu, cmd.pcm_handle, output, output_size, notify_pcm_out.cbArg, vm::make_var>(bsi_info), correct_pts_type, error); + break; + } + default: + fmt::throw_exception("Invalid command"); + } + } +} + +template +error_code AtracXdecContext::send_command(ppu_thread& ppu, auto&&... args) +{ + ppu.state += cpu_flag::wait; + + { + std::lock_guard lock{queue_mutex}; + + if constexpr (type == AtracXdecCmdType::close) + { + // Close command is only sent if the queue is empty on LLE + if (!cmd_queue.empty()) + { + return {}; + } + } + + if (cmd_queue.full()) + { + return CELL_ADEC_ERROR_ATX_BUSY; + } + + cmd_queue.emplace(std::forward(type), std::forward(args)...); + } + + queue_not_empty.notify_one(); + + return CELL_OK; +} + +void atracXdecEntry(ppu_thread& ppu, vm::ptr atxdec) +{ + atxdec->decoder.alloc_avcodec(); + + atxdec->exec(ppu); + + atxdec->decoder.free_avcodec(); + + if (thread_ctrl::state() == thread_state::aborting) + { + // For savestates, restore argument + idm::get>(static_cast(atxdec->thread_id))->cmd_list + ({ + { ppu_cmd::set_args, 1 }, static_cast(atxdec.addr()), + }); + + ppu.state += cpu_flag::again; + return; + } + + ppu_execute<&sys_ppu_thread_exit>(ppu, CELL_OK); +} + +template +error_code _CellAdecCoreOpGetMemSize_atracx(vm::ptr attr) +{ + cellAtracXdec.notice("_CellAdecCoreOpGetMemSize_atracx(attr=*0x%x)", nch_in, attr); + + ensure(!!attr); // Not checked on LLE + + constexpr u32 mem_size = + sizeof(AtracXdecContext) + 0x7f + + ATXDEC_SPURS_STRUCTS_SIZE + 0x1d8 + + atracXdecGetSpursMemSize(nch_in) + + ATXDEC_SAMPLES_PER_FRAME * sizeof(f32) * nch_in; + + attr->workMemSize = utils::align(mem_size, 0x80); + + return CELL_OK; +} + +error_code _CellAdecCoreOpOpenExt_atracx(ppu_thread& ppu, vm::ptr handle, vm::ptr notifyAuDone, vm::ptr notifyAuDoneArg, vm::ptr notifyPcmOut, vm::ptr notifyPcmOutArg, + vm::ptr notifyError, vm::ptr notifyErrorArg, vm::ptr notifySeqDone, vm::ptr notifySeqDoneArg, vm::cptr res, vm::cptr spursRes) +{ + std::unique_lock savestate_lock{g_fxo->get(), std::try_to_lock}; + + if (!savestate_lock.owns_lock()) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellAtracXdec.notice("_CellAdecCoreOpOpenExt_atracx(handle=*0x%x, notifyAuDone=*0x%x, notifyAuDoneArg=*0x%x, notifyPcmOut=*0x%x, notifyPcmOutArg=*0x%x, notifyError=*0x%x, notifyErrorArg=*0x%x, notifySeqDone=*0x%x, notifySeqDoneArg=*0x%x, res=*0x%x, spursRes=*0x%x)", + handle, notifyAuDone, notifyAuDoneArg, notifyPcmOut, notifyPcmOutArg, notifyError, notifyErrorArg, notifySeqDone, notifySeqDoneArg, res, spursRes); + + ensure(!!handle && !!res); // Not checked on LLE + ensure(handle.aligned(0x80)); // LLE cellAdec aligns the address to 128 bytes + ensure(!!notifyAuDone && !!notifyAuDoneArg && !!notifyPcmOut && !!notifyPcmOutArg && !!notifyError && !!notifyErrorArg && !!notifySeqDone && !!notifySeqDoneArg); // These should always be set by LLE cellAdec + + new (handle.get_ptr()) AtracXdecContext(notifyAuDone, notifyAuDoneArg, notifyPcmOut, notifyPcmOutArg, notifyError, notifyErrorArg, notifySeqDone, notifySeqDoneArg, + vm::bptr::make(handle.addr() + utils::align(static_cast(sizeof(AtracXdecContext)), 0x80) + ATXDEC_SPURS_STRUCTS_SIZE)); + + const vm::var _name = vm::make_str("HLE ATRAC3plus decoder"); + const auto entry = g_fxo->get().func_addr(FIND_FUNC(atracXdecEntry)); + ppu_execute<&sys_ppu_thread_create>(ppu, handle.ptr(&AtracXdecContext::thread_id), entry, handle.addr(), +res->ppuThreadPriority, +res->ppuThreadStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, +_name); + + return CELL_OK; +} + +error_code _CellAdecCoreOpOpen_atracx(ppu_thread& ppu, vm::ptr handle, vm::ptr notifyAuDone, vm::ptr notifyAuDoneArg, vm::ptr notifyPcmOut, vm::ptr notifyPcmOutArg, + vm::ptr notifyError, vm::ptr notifyErrorArg, vm::ptr notifySeqDone, vm::ptr notifySeqDoneArg, vm::cptr res) +{ + cellAtracXdec.notice("_CellAdecCoreOpOpen_atracx(handle=*0x%x, notifyAuDone=*0x%x, notifyAuDoneArg=*0x%x, notifyPcmOut=*0x%x, notifyPcmOutArg=*0x%x, notifyError=*0x%x, notifyErrorArg=*0x%x, notifySeqDone=*0x%x, notifySeqDoneArg=*0x%x, res=*0x%x)", + handle, notifyAuDone, notifyAuDoneArg, notifyPcmOut, notifyPcmOutArg, notifyError, notifyErrorArg, notifySeqDone, notifySeqDoneArg, res); + + return _CellAdecCoreOpOpenExt_atracx(ppu, handle, notifyAuDone, notifyAuDoneArg, notifyPcmOut, notifyPcmOutArg, notifyError, notifyErrorArg, notifySeqDone, notifySeqDoneArg, res, vm::null); +} + +error_code _CellAdecCoreOpClose_atracx(ppu_thread& ppu, vm::ptr handle) +{ + std::unique_lock savestate_lock{g_fxo->get(), std::try_to_lock}; + + if (!savestate_lock.owns_lock()) + { + ppu.state += cpu_flag::again; + return {}; + } + + ppu.state += cpu_flag::wait; + + cellAtracXdec.notice("_CellAdecCoreOpClose_atracx(handle=*0x%x)", handle); + + ensure(!!handle); // Not checked on LLE + + handle->run_thread = false; + handle->send_command(ppu); + + { + std::lock_guard lock{handle->output_mutex}; + handle->output_locked = false; + } + + handle->output_consumed.notify_one(); + + if (vm::var ret; sys_ppu_thread_join(ppu, static_cast(handle->thread_id), +ret) != CELL_OK) + { + // Other thread already closed the decoder + return CELL_ADEC_ERROR_FATAL; + } + + return CELL_OK; +} + +error_code _CellAdecCoreOpStartSeq_atracx(ppu_thread& ppu, vm::ptr handle, vm::cptr atracxParam) +{ + cellAtracXdec.notice("_CellAdecCoreOpStartSeq_atracx(handle=*0x%x, atracxParam=*0x%x)", handle, atracxParam); + + ensure(!!handle && !!atracxParam); // Not checked on LLE + + cellAtracXdec.notice("_CellAdecCoreOpStartSeq_atracx(): sampling_freq=%d, ch_config_idx=%d, nch_out=%d, nbytes=0x%x, extra_config_data=0x%08x, bw_pcm=0x%x, downmix_flag=%d, au_includes_ats_hdr_flg=%d", + atracxParam->sampling_freq, atracxParam->ch_config_idx, atracxParam->nch_out, atracxParam->nbytes, std::bit_cast(atracxParam->extra_config_data), atracxParam->bw_pcm, atracxParam->downmix_flag, atracxParam->au_includes_ats_hdr_flg); + + return handle->send_command(ppu, *atracxParam); +} + +error_code _CellAdecCoreOpEndSeq_atracx(ppu_thread& ppu, vm::ptr handle) +{ + cellAtracXdec.notice("_CellAdecCoreOpEndSeq_atracx(handle=*0x%x)", handle); + + ensure(!!handle); // Not checked on LLE + + return handle->send_command(ppu); +} + +error_code _CellAdecCoreOpDecodeAu_atracx(ppu_thread& ppu, vm::ptr handle, s32 pcmHandle, vm::cptr auInfo) +{ + cellAtracXdec.trace("_CellAdecCoreOpDecodeAu_atracx(handle=*0x%x, pcmHandle=%d, auInfo=*0x%x)", handle, pcmHandle, auInfo); + + ensure(!!handle && !!auInfo); // Not checked on LLE + + cellAtracXdec.trace("_CellAdecCoreOpDecodeAu_atracx(): startAddr=*0x%x, size=0x%x, pts=%lld, userData=0x%llx", auInfo->startAddr, auInfo->size, std::bit_cast>(auInfo->pts), auInfo->userData); + + return handle->send_command(ppu, pcmHandle, *auInfo); +} + +void _CellAdecCoreOpGetVersion_atracx(vm::ptr> version) +{ + cellAtracXdec.notice("_CellAdecCoreOpGetVersion_atracx(version=*0x%x)", version); + + ensure(!!version); // Not checked on LLE + + *version = { 0x01, 0x02, 0x00, 0x00 }; +} + +error_code _CellAdecCoreOpRealign_atracx(vm::ptr handle, vm::ptr outBuffer, vm::cptr pcmStartAddr) +{ + cellAtracXdec.trace("_CellAdecCoreOpRealign_atracx(handle=*0x%x, outBuffer=*0x%x, pcmStartAddr=*0x%x)", handle, outBuffer, pcmStartAddr); + + if (outBuffer) + { + ensure(!!handle && !!pcmStartAddr); // Not checked on LLE + + std::memcpy(outBuffer.get_ptr(), pcmStartAddr.get_ptr(), handle->decoder.pcm_output_size); + } + + return CELL_OK; +} + +error_code _CellAdecCoreOpReleasePcm_atracx(ppu_thread& ppu, vm::ptr handle, s32 pcmHandle, vm::cptr outBuffer) +{ + ppu.state += cpu_flag::wait; + + cellAtracXdec.trace("_CellAdecCoreOpReleasePcm_atracx(handle=*0x%x, pcmHandle=%d, outBuffer=*0x%x)", handle, pcmHandle, outBuffer); + + ensure(!!handle); // Not checked on LLE + + std::lock_guard lock{handle->output_mutex}; + handle->output_locked = false; + handle->output_consumed.notify_one(); + + return CELL_OK; +} + +s32 _CellAdecCoreOpGetPcmHandleNum_atracx() +{ + cellAtracXdec.notice("_CellAdecCoreOpGetPcmHandleNum_atracx()"); + + return 3; +} + +u32 _CellAdecCoreOpGetBsiInfoSize_atracx() +{ + cellAtracXdec.notice("_CellAdecCoreOpGetBsiInfoSize_atracx()"); + + return sizeof(CellAdecAtracXInfo); +} + +static void init_gvar(vm::gvar& var) +{ + var->open.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpOpen_atracx))); + var->close.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpClose_atracx))); + var->startSeq.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpStartSeq_atracx))); + var->endSeq.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpEndSeq_atracx))); + var->decodeAu.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpDecodeAu_atracx))); + var->getVersion.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetVersion_atracx))); + var->realign.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpRealign_atracx))); + var->releasePcm.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpReleasePcm_atracx))); + var->getPcmHandleNum.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetPcmHandleNum_atracx))); + var->getBsiInfoSize.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetBsiInfoSize_atracx))); + var->openExt.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpOpenExt_atracx))); +} + +DECLARE(ppu_module_manager::cellAtracXdec)("cellAtracXdec", []() +{ + REG_VNID(cellAtracXdec, 0x076b33ab, g_cell_adec_core_ops_atracx2ch).init = []() + { + g_cell_adec_core_ops_atracx2ch->getMemSize.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetMemSize_atracx<2>))); + init_gvar(g_cell_adec_core_ops_atracx2ch); + }; + REG_VNID(cellAtracXdec, 0x1d210eaa, g_cell_adec_core_ops_atracx6ch).init = []() + { + g_cell_adec_core_ops_atracx6ch->getMemSize.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetMemSize_atracx<6>))); + init_gvar(g_cell_adec_core_ops_atracx6ch); + }; + REG_VNID(cellAtracXdec, 0xe9a86e54, g_cell_adec_core_ops_atracx8ch).init = []() + { + g_cell_adec_core_ops_atracx8ch->getMemSize.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetMemSize_atracx<8>))); + init_gvar(g_cell_adec_core_ops_atracx8ch); + }; + REG_VNID(cellAtracXdec, 0x4944af9a, g_cell_adec_core_ops_atracx).init = []() + { + g_cell_adec_core_ops_atracx->getMemSize.set(g_fxo->get().func_addr(FIND_FUNC(_CellAdecCoreOpGetMemSize_atracx<8>))); + init_gvar(g_cell_adec_core_ops_atracx); + }; + + REG_HIDDEN_FUNC(_CellAdecCoreOpGetMemSize_atracx<2>); + REG_HIDDEN_FUNC(_CellAdecCoreOpGetMemSize_atracx<6>); + REG_HIDDEN_FUNC(_CellAdecCoreOpGetMemSize_atracx<8>); + REG_HIDDEN_FUNC(_CellAdecCoreOpOpen_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpClose_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpStartSeq_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpEndSeq_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpDecodeAu_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpGetVersion_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpRealign_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpReleasePcm_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpGetPcmHandleNum_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpGetBsiInfoSize_atracx); + REG_HIDDEN_FUNC(_CellAdecCoreOpOpenExt_atracx); + + REG_HIDDEN_FUNC(atracXdecEntry); +}); diff --git a/rpcs3/Emu/Cell/Modules/cellAtracXdec.h b/rpcs3/Emu/Cell/Modules/cellAtracXdec.h new file mode 100644 index 0000000000..b3ea7cb996 --- /dev/null +++ b/rpcs3/Emu/Cell/Modules/cellAtracXdec.h @@ -0,0 +1,300 @@ +#pragma once + +#ifdef _MSC_VER +#pragma warning(push, 0) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wextra" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +extern "C" +{ +#include "libavcodec/avcodec.h" +} +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +#include "Utilities/cond.h" +#include "cellPamf.h" +#include "cellAdec.h" + +enum CellAtracXdecError : u32 +{ + CELL_ADEC_ERROR_ATX_OFFSET = 0x80612200, + CELL_ADEC_ERROR_ATX_NONE = 0x80612200, + CELL_ADEC_ERROR_ATX_OK = 0x80612200, + CELL_ADEC_ERROR_ATX_BUSY = 0x80612264, + CELL_ADEC_ERROR_ATX_EMPTY = 0x80612265, + CELL_ADEC_ERROR_ATX_ATSHDR = 0x80612266, + CELL_ADEC_ERROR_ATX_NON_FATAL = 0x80612281, + CELL_ADEC_ERROR_ATX_NOT_IMPLE = 0x80612282, + CELL_ADEC_ERROR_ATX_PACK_CE_OVERFLOW = 0x80612283, + CELL_ADEC_ERROR_ATX_ILLEGAL_NPROCQUS = 0x80612284, + CELL_ADEC_ERROR_ATX_FATAL = 0x8061228c, + CELL_ADEC_ERROR_ATX_ENC_OVERFLOW = 0x8061228d, + CELL_ADEC_ERROR_ATX_PACK_CE_UNDERFLOW = 0x8061228e, + CELL_ADEC_ERROR_ATX_SYNTAX_IDCT = 0x8061228f, + CELL_ADEC_ERROR_ATX_SYNTAX_GAINADJ = 0x80612290, + CELL_ADEC_ERROR_ATX_SYNTAX_IDSF = 0x80612291, + CELL_ADEC_ERROR_ATX_SYNTAX_SPECTRA = 0x80612292, + CELL_ADEC_ERROR_ATX_SYNTAX_IDWL = 0x80612293, + CELL_ADEC_ERROR_ATX_SYNTAX_GHWAVE = 0x80612294, + CELL_ADEC_ERROR_ATX_SYNTAX_SHEADER = 0x80612295, + CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_A = 0x80612296, + CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_B = 0x80612297, + CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_C = 0x80612298, + CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_D = 0x80612299, + CELL_ADEC_ERROR_ATX_SYNTAX_IDWL_E = 0x8061229a, + CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_A = 0x8061229b, + CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_B = 0x8061229c, + CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_C = 0x8061229d, + CELL_ADEC_ERROR_ATX_SYNTAX_IDSF_D = 0x8061229e, + CELL_ADEC_ERROR_ATX_SYNTAX_IDCT_A = 0x8061229f, + CELL_ADEC_ERROR_ATX_SYNTAX_GC_NGC = 0x806122a0, + CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_A = 0x806122a1, + CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_A = 0x806122a2, + CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLEV_B = 0x806122a3, + CELL_ADEC_ERROR_ATX_SYNTAX_GC_IDLOC_B = 0x806122a4, + CELL_ADEC_ERROR_ATX_SYNTAX_SN_NWVS = 0x806122a5, + CELL_ADEC_ERROR_ATX_FATAL_HANDLE = 0x806122aa, + CELL_ADEC_ERROR_ATX_ASSERT_SAMPLING_FREQ = 0x806122ab, + CELL_ADEC_ERROR_ATX_ASSERT_CH_CONFIG_INDEX = 0x806122ac, + CELL_ADEC_ERROR_ATX_ASSERT_NBYTES = 0x806122ad, + CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_NUM = 0x806122ae, + CELL_ADEC_ERROR_ATX_ASSERT_BLOCK_ID = 0x806122af, + CELL_ADEC_ERROR_ATX_ASSERT_CHANNELS = 0x806122b0, + CELL_ADEC_ERROR_ATX_UNINIT_BLOCK_SPECIFIED = 0x806122b1, + CELL_ADEC_ERROR_ATX_POSCFG_PRESENT = 0x806122b2, + CELL_ADEC_ERROR_ATX_BUFFER_OVERFLOW = 0x806122b3, + CELL_ADEC_ERROR_ATX_ILL_BLK_TYPE_ID = 0x806122b4, + CELL_ADEC_ERROR_ATX_UNPACK_CHANNEL_BLK_FAILED = 0x806122b5, + CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_1 = 0x806122b6, + CELL_ADEC_ERROR_ATX_ILL_BLK_ID_USED_2 = 0x806122b7, + CELL_ADEC_ERROR_ATX_ILLEGAL_ENC_SETTING = 0x806122b8, + CELL_ADEC_ERROR_ATX_ILLEGAL_DEC_SETTING = 0x806122b9, + CELL_ADEC_ERROR_ATX_ASSERT_NSAMPLES = 0x806122ba, + CELL_ADEC_ERROR_ATX_ILL_SYNCWORD = 0x806122bb, + CELL_ADEC_ERROR_ATX_ILL_SAMPLING_FREQ = 0x806122bc, + CELL_ADEC_ERROR_ATX_ILL_CH_CONFIG_INDEX = 0x806122bd, + CELL_ADEC_ERROR_ATX_RAW_DATA_FRAME_SIZE_OVER = 0x806122be, + CELL_ADEC_ERROR_ATX_SYNTAX_ENHANCE_LENGTH_OVER = 0x806122bf, + CELL_ADEC_ERROR_ATX_SPU_INTERNAL_FAIL = 0x806122c8, +}; + +enum : u32 +{ + CELL_ADEC_ATRACX_WORD_SZ_16BIT = 0x02, + CELL_ADEC_ATRACX_WORD_SZ_24BIT = 0x03, + CELL_ADEC_ATRACX_WORD_SZ_32BIT = 0x04, + CELL_ADEC_ATRACX_WORD_SZ_FLOAT = 0x84, +}; + +enum : u8 +{ + CELL_ADEC_ATRACX_ATS_HDR_NOTINC = 0, + CELL_ADEC_ATRACX_ATS_HDR_INC = 1, +}; + +enum : u8 +{ + ATRACX_DOWNMIX_OFF = 0, + ATRACX_DOWNMIX_ON = 1, +}; + +struct CellAdecParamAtracX +{ + be_t sampling_freq; + be_t ch_config_idx; + be_t nch_out; + be_t nbytes; + std::array extra_config_data; + be_t bw_pcm; + u8 downmix_flag; + u8 au_includes_ats_hdr_flg; +}; + +struct CellAdecAtracXInfo +{ + be_t samplingFreq; + be_t channelConfigIndex; + be_t nbytes; +}; + +CHECK_SIZE(CellAdecAtracXInfo, 12); + +struct AtracXdecAtsHeader +{ + be_t sync_word; // 0x0fd0 + be_t params; // 3 bits: sample rate, 3 bits: channel config, 10 bits: (nbytes / 8) - 1 + u8 extra_config_data[4]; +}; + +CHECK_SIZE(AtracXdecAtsHeader, 8); + +enum class AtracXdecCmdType : u32 +{ + invalid, + start_seq, + decode_au, + end_seq, + close, +}; + +struct AtracXdecCmd +{ + be_t type; + be_t pcm_handle; + vm::bcptr au_start_addr; + be_t au_size; + vm::bptr pcm_start_addr; // Unused + be_t pcm_size; // Unused + CellAdecParamAtracX atracx_param; + + AtracXdecCmd() = default; // cellAdecOpen() + + AtracXdecCmd(AtracXdecCmdType&& type) // cellAdecEndSeq(), cellAdecClose() + : type(type) + { + } + + AtracXdecCmd(AtracXdecCmdType&& type, const CellAdecParamAtracX& atracx_param) // cellAdecStartSeq() + : type(type), atracx_param(atracx_param) + { + } + + AtracXdecCmd(AtracXdecCmdType&& type, const s32& pcm_handle, const CellAdecAuInfo& au_info) // cellAdecDecodeAu() + : type(type), pcm_handle(pcm_handle), au_start_addr(au_info.startAddr), au_size(au_info.size) + { + } +}; + +CHECK_SIZE(AtracXdecCmd, 0x34); + +struct AtracXdecDecoder +{ + be_t sampling_freq; + be_t ch_config_idx; + be_t nch_in; + be_t nch_blocks; + be_t nbytes; + be_t nch_out; + be_t bw_pcm; + be_t nbytes_128_aligned; + be_t status; + be_t pcm_output_size; + + const vm::bptr work_mem; + + // HLE exclusive + b8 config_is_set = false; // For savestates + const AVCodec* codec; + AVCodecContext* ctx; + AVPacket* packet; + AVFrame* frame; + + u8 spurs_stuff[84]; // 120 bytes on LLE, pointers to CellSpurs, CellSpursTaskset, etc. + + be_t spurs_task_id; // CellSpursTaskId + + AtracXdecDecoder(vm::ptr work_mem) : work_mem(work_mem) {} + + void alloc_avcodec(); + void free_avcodec(); + void init_avcodec(); + + error_code set_config_info(u32 sampling_freq, u32 ch_config_idx, u32 nbytes); + error_code init_decode(u32 bw_pcm, u32 nch_out); + error_code parse_ats_header(vm::cptr au_start_addr); +}; + +CHECK_SIZE(AtracXdecDecoder, 0xa8); + +struct AtracXdecContext +{ + be_t thread_id; // sys_ppu_thread_t + + shared_mutex queue_mutex; // sys_mutex_t + cond_variable queue_not_empty; // sys_cond_t + AdecCmdQueue cmd_queue; + + shared_mutex output_mutex; // sys_mutex_t + cond_variable output_consumed; // sys_cond_t + be_t output_locked = false; + + be_t run_thread_mutex; // sys_mutex_t + be_t run_thread_cond; // sys_cond_t + be_t run_thread = true; + + const AdecCb notify_au_done; + const AdecCb notify_pcm_out; + const AdecCb notify_error; + const AdecCb notify_seq_done; + + const vm::bptr work_mem; + + // HLE exclusive + u64 cmd_counter = 0; // For debugging + AtracXdecCmd cmd; // For savestates; if savestate was created while processing a decode command, we need to save the current command + b8 skip_getting_command = false; // For savestates; skips getting a new command from the queue + b8 skip_next_frame; // Needed to emulate behavior of LLE SPU program, it doesn't output the first frame after a sequence reset or error + + u8 spurs_stuff[58]; // 120 bytes on LLE, pointers to CellSpurs, CellSpursTaskset, etc. + + CellAdecParamAtracX atracx_param; + + u8 reserved; + b8 first_decode; + + AtracXdecDecoder decoder; + + AtracXdecContext(vm::ptr notifyAuDone, vm::ptr notifyAuDoneArg, vm::ptr notifyPcmOut, vm::ptr notifyPcmOutArg, + vm::ptr notifyError, vm::ptr notifyErrorArg, vm::ptr notifySeqDone, vm::ptr notifySeqDoneArg, vm::bptr work_mem) + : notify_au_done{ notifyAuDone, notifyAuDoneArg } + , notify_pcm_out{ notifyPcmOut, notifyPcmOutArg } + , notify_error{ notifyError, notifyErrorArg } + , notify_seq_done{ notifySeqDone, notifySeqDoneArg } + , work_mem(work_mem) + , decoder(work_mem) + { + } + + void exec(ppu_thread& ppu); + + template + error_code send_command(ppu_thread& ppu, auto&&... args); +}; + +static_assert(std::is_standard_layout_v); +CHECK_SIZE_ALIGN(AtracXdecContext, 0x268, 8); + +constexpr u32 ATXDEC_SPURS_STRUCTS_SIZE = 0x1cf00; // CellSpurs, CellSpursTaskset, context, etc. +constexpr u16 ATXDEC_SAMPLES_PER_FRAME = 0x800; +constexpr u16 ATXDEC_MAX_FRAME_LENGTH = 0x2000; +constexpr std::array ATXDEC_NCH_BLOCKS_MAP = { 0, 1, 1, 2, 3, 4, 5, 5 }; + +// Expected output channel order +// - for 1 to 7 channels: Front Left, Center, Front Right, Rear Left, Rear Right, Rear Center, LFE +// - for 8 channels: Front Left, Front Right, Center, LFE, Rear Left, Rear Right, Side Left, Side Right +// FFmpeg output +// - ver <= 5.1.2: Front Left, Front Right, Center, Rear Left, Rear Right, Rear Center, Side Left, Side Right, LFE +// - ver >= 5.1.3: Front Left, Front Right, Center, LFE, Rear Left, Rear Right, Rear Center, Side Left, Side Right +constexpr u8 ATXDEC_AVCODEC_CH_MAP[7][8] = +{ + { 0 }, + { 0, 1 }, + { 0, 2, 1 }, + { 0, 2, 1, 3 }, +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 51, 101) + { 0, 2, 1, 3, 4, 5 }, + { 0, 2, 1, 3, 4, 5, 6 }, + { 0, 1, 2, 4, 5, 6, 7, 3 } +#else + { 0, 2, 1, 4, 5, 3 }, + { 0, 2, 1, 4, 5, 6, 3 }, + { 0, 1, 2, 3, 4, 5, 6, 7 } +#endif +}; diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 1a7eaf9394..ea78939456 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -184,6 +184,7 @@ static void ppu_initialize_modules(ppu_linkage_info* link, utils::serial* ar = n &ppu_module_manager::cellAdec, &ppu_module_manager::cellAtrac, &ppu_module_manager::cellAtracMulti, + &ppu_module_manager::cellAtracXdec, &ppu_module_manager::cellAudio, &ppu_module_manager::cellAvconfExt, &ppu_module_manager::cellAuthDialogUtility, diff --git a/rpcs3/Emu/Cell/PPUModule.h b/rpcs3/Emu/Cell/PPUModule.h index 454694ddcf..405fe76d45 100644 --- a/rpcs3/Emu/Cell/PPUModule.h +++ b/rpcs3/Emu/Cell/PPUModule.h @@ -168,6 +168,7 @@ public: static const ppu_static_module cellAdec; static const ppu_static_module cellAtrac; static const ppu_static_module cellAtracMulti; + static const ppu_static_module cellAtracXdec; static const ppu_static_module cellAudio; static const ppu_static_module cellAvconfExt; static const ppu_static_module cellAuthDialogUtility; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 7b46c57b9f..3ea574f5f8 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -283,6 +283,7 @@ + @@ -804,6 +805,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 151028d85d..d3902de896 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -351,6 +351,9 @@ Emu\Cell\Modules + + Emu\Cell\Modules + Emu\Cell\Modules @@ -1599,6 +1602,9 @@ Emu\Cell\Modules + + Emu\Cell\Modules + Emu\Cell\Modules