diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh
index 2f43e05e74..f386866264 100755
--- a/.ci/build-mac.sh
+++ b/.ci/build-mac.sh
@@ -17,7 +17,7 @@ brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER"
brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER"
if [ "$AARCH64" -eq 1 ]; then
brew install -f --overwrite --quiet googletest opencv@4 sdl3 vulkan-headers vulkan-loader molten-vk
- brew unlink --quiet ffmpeg fmt qtbase qtsvg qtdeclarative protobuf
+ brew unlink --quiet ffmpeg fmt qtbase qtsvg qtdeclarative
else
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader molten-vk
@@ -122,6 +122,7 @@ cmake .. \
-DUSE_SYSTEM_MVK=ON \
-DUSE_SYSTEM_SDL=ON \
-DUSE_SYSTEM_OPENCV=ON \
+ -DUSE_SYSTEM_PROTOBUF=ON \
-G Ninja
fi
diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml
index 0132095f41..740e844d04 100644
--- a/.github/workflows/rpcs3.yml
+++ b/.github/workflows/rpcs3.yml
@@ -30,23 +30,23 @@ jobs:
matrix:
include:
- os: ubuntu-24.04
- docker_img: "rpcs3/rpcs3-ci-jammy:1.8"
+ docker_img: "rpcs3/rpcs3-ci-jammy:1.9"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: clang
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
- os: ubuntu-24.04
- docker_img: "rpcs3/rpcs3-ci-jammy:1.8"
+ docker_img: "rpcs3/rpcs3-ci-jammy:1.9"
build_sh: "/rpcs3/.ci/build-linux.sh"
compiler: gcc
- os: ubuntu-24.04-arm
- docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8"
+ docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: clang
UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64"
- os: ubuntu-24.04-arm
- docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8"
+ docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9"
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
compiler: gcc
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}
diff --git a/3rdparty/FAudio b/3rdparty/FAudio
index e67d761ead..dc034fc671 160000
--- a/3rdparty/FAudio
+++ b/3rdparty/FAudio
@@ -1 +1 @@
-Subproject commit e67d761ead486de3e69fa11705456bf94df734ca
+Subproject commit dc034fc671b07bbd14e8410d5dd6be6da38fdf6d
diff --git a/3rdparty/OpenAL/openal-soft b/3rdparty/OpenAL/openal-soft
index 75c0059630..c41d64c6a3 160000
--- a/3rdparty/OpenAL/openal-soft
+++ b/3rdparty/OpenAL/openal-soft
@@ -1 +1 @@
-Subproject commit 75c00596307bf05ba7bbc8c7022836bf52f17477
+Subproject commit c41d64c6a35f6174bf4a27010aeac52a8d3bb2c6
diff --git a/3rdparty/curl/curl b/3rdparty/curl/curl
index 400fffa90f..8c908d2d0a 160000
--- a/3rdparty/curl/curl
+++ b/3rdparty/curl/curl
@@ -1 +1 @@
-Subproject commit 400fffa90f30c7a2dc762fa33009d24851bd2016
+Subproject commit 8c908d2d0a6d32abdedda2c52e90bd56ec76c24d
diff --git a/3rdparty/curl/libcurl.vcxproj b/3rdparty/curl/libcurl.vcxproj
index dae28be346..4db28782a3 100644
--- a/3rdparty/curl/libcurl.vcxproj
+++ b/3rdparty/curl/libcurl.vcxproj
@@ -79,12 +79,16 @@
+
+
+
+
@@ -106,6 +110,7 @@
+
@@ -169,14 +174,13 @@
-
+
-
@@ -184,10 +188,8 @@
-
-
@@ -204,6 +206,7 @@
+
@@ -224,13 +227,11 @@
-
-
@@ -272,6 +273,7 @@
+
@@ -280,6 +282,9 @@
+
+
+
@@ -300,9 +305,7 @@
-
-
@@ -312,6 +315,7 @@
+
@@ -352,7 +356,6 @@
-
@@ -367,7 +370,7 @@
-
+
@@ -376,7 +379,6 @@
-
@@ -384,7 +386,6 @@
-
@@ -405,6 +406,7 @@
+
@@ -418,12 +420,10 @@
-
-
diff --git a/3rdparty/curl/libcurl.vcxproj.filters b/3rdparty/curl/libcurl.vcxproj.filters
index 17f760c54b..d38316e767 100644
--- a/3rdparty/curl/libcurl.vcxproj.filters
+++ b/3rdparty/curl/libcurl.vcxproj.filters
@@ -204,9 +204,6 @@
Source Files
-
- Source Files
-
Source Files
@@ -222,9 +219,6 @@
Source Files
-
- Source Files
-
Source Files
@@ -246,18 +240,12 @@
Source Files
-
- Source Files
-
Source Files
Source Files
-
- Source Files
-
Source Files
@@ -318,9 +306,6 @@
Source Files
-
- Source Files
-
Source Files
@@ -333,9 +318,6 @@
Source Files
-
- Source Files
-
Source Files
@@ -549,6 +531,27 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
@@ -653,9 +656,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -758,9 +758,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -791,9 +788,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -812,9 +806,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -836,9 +827,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -887,9 +875,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -899,9 +884,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -1103,9 +1085,6 @@
Header Files
-
- Header Files
-
Header Files
@@ -1121,6 +1100,27 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL
index a962f40bbb..683181b47c 160000
--- a/3rdparty/libsdl-org/SDL
+++ b/3rdparty/libsdl-org/SDL
@@ -1 +1 @@
-Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3
+Subproject commit 683181b47cfabd293e3ea409f838915b8297a4fd
diff --git a/3rdparty/yaml-cpp/yaml-cpp b/3rdparty/yaml-cpp/yaml-cpp
index 05c44fcd18..51a5d623e3 160000
--- a/3rdparty/yaml-cpp/yaml-cpp
+++ b/3rdparty/yaml-cpp/yaml-cpp
@@ -1 +1 @@
-Subproject commit 05c44fcd18074836e21e1eda9fc02b3a4a1529b5
+Subproject commit 51a5d623e3fde1f58829a56ba910f1cb33596222
diff --git a/3rdparty/zlib/zlib b/3rdparty/zlib/zlib
index 51b7f2abda..da607da739 160000
--- a/3rdparty/zlib/zlib
+++ b/3rdparty/zlib/zlib
@@ -1 +1 @@
-Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf
+Subproject commit da607da739fa6047df13e66a2af6b8bec7c2a498
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 65e415bdb7..738d9bbf17 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,12 +13,12 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11)
- message(FATAL_ERROR "RPCS3 requires at least gcc-11.")
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13)
+ message(FATAL_ERROR "RPCS3 requires at least gcc-13.")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
- message(FATAL_ERROR "RPCS3 requires at least clang-12.0.")
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.0)
+ message(FATAL_ERROR "RPCS3 requires at least clang-19.0.")
endif()
endif()
diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp
index cee928def7..381ee01079 100644
--- a/Utilities/Config.cpp
+++ b/Utilities/Config.cpp
@@ -416,7 +416,7 @@ void cfg::encode(YAML::Emitter& out, const cfg::_base& rhs)
out << YAML::BeginMap;
for (const auto& np : static_cast(rhs).get_map())
{
- if (np.second == logs::level::notice) continue;
+ if (np.second == logs::level::_default) continue;
out << YAML::Key << np.first;
out << YAML::Value << fmt::format("%s", np.second);
}
diff --git a/Utilities/File.cpp b/Utilities/File.cpp
index 30e6414675..490605c792 100644
--- a/Utilities/File.cpp
+++ b/Utilities/File.cpp
@@ -117,6 +117,7 @@ static fs::error to_error(DWORD e)
case ERROR_NEGATIVE_SEEK: return fs::error::inval;
case ERROR_DIRECTORY: return fs::error::inval;
case ERROR_INVALID_NAME: return fs::error::inval;
+ case ERROR_INVALID_FUNCTION: return fs::error::inval;
case ERROR_SHARING_VIOLATION: return fs::error::acces;
case ERROR_DIR_NOT_EMPTY: return fs::error::notempty;
case ERROR_NOT_READY: return fs::error::noent;
@@ -398,12 +399,11 @@ namespace fs
class windows_file final : public file_base
{
HANDLE m_handle;
- atomic_t m_pos;
+ atomic_t m_pos {0};
public:
windows_file(HANDLE handle)
: m_handle(handle)
- , m_pos(0)
{
}
@@ -417,10 +417,10 @@ namespace fs
stat_t get_stat() override
{
- FILE_BASIC_INFO basic_info;
+ FILE_BASIC_INFO basic_info {};
ensure(GetFileInformationByHandleEx(m_handle, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))); // "file::stat"
- stat_t info;
+ stat_t info {};
info.is_directory = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info.is_writable = (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0;
info.size = this->size();
@@ -441,7 +441,7 @@ namespace fs
bool trunc(u64 length) override
{
- FILE_END_OF_FILE_INFO _eof;
+ FILE_END_OF_FILE_INFO _eof {};
_eof.EndOfFile.QuadPart = length;
if (!SetFileInformationByHandle(m_handle, FileEndOfFileInfo, &_eof, sizeof(_eof)))
@@ -563,6 +563,7 @@ namespace fs
u64 size() override
{
+ // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso).
LARGE_INTEGER size;
ensure(GetFileSizeEx(m_handle, &size)); // "file::size"
@@ -579,12 +580,12 @@ namespace fs
file_id id{"windows_file"};
id.data.resize(sizeof(FILE_ID_INFO));
- FILE_ID_INFO info;
+ FILE_ID_INFO info {};
if (!GetFileInformationByHandleEx(m_handle, FileIdInfo, &info, sizeof(info)))
{
// Try GetFileInformationByHandle as a fallback
- BY_HANDLE_FILE_INFORMATION info2;
+ BY_HANDLE_FILE_INFORMATION info2{};
ensure(GetFileInformationByHandle(m_handle, &info2));
info = {};
@@ -625,7 +626,7 @@ namespace fs
struct ::stat file_info;
ensure(::fstat(m_fd, &file_info) == 0); // "file::stat"
- stat_t info;
+ stat_t info {};
info.is_directory = S_ISDIR(file_info.st_mode);
info.is_writable = file_info.st_mode & 0200; // HACK: approximation
info.size = file_info.st_size;
@@ -1656,6 +1657,45 @@ fs::file::file(const std::string& path, bs_t mode)
return;
}
+ // Check if the handle is actually valid.
+ // This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION).
+ BY_HANDLE_FILE_INFORMATION info{};
+ if (!GetFileInformationByHandle(handle, &info))
+ {
+ const DWORD last_error = GetLastError();
+ CloseHandle(handle);
+
+ if (last_error == ERROR_INVALID_FUNCTION)
+ {
+ g_tls_error = fs::error::isdir;
+ return;
+ }
+
+ g_tls_error = to_error(last_error);
+ return;
+ }
+
+ if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ CloseHandle(handle);
+ g_tls_error = fs::error::isdir;
+ return;
+ }
+
+ if (info.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
+ {
+ CloseHandle(handle);
+ g_tls_error = fs::error::acces;
+ return;
+ }
+
+ if ((mode & fs::write) && (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
+ {
+ CloseHandle(handle);
+ g_tls_error = fs::error::readonly;
+ return;
+ }
+
m_file = std::make_unique(handle);
#else
int flags = O_CLOEXEC; // Ensures all files are closed on execl for auto updater
@@ -2595,7 +2635,7 @@ bool fs::pending_file::commit(bool overwrite)
while (file_handle != INVALID_HANDLE_VALUE)
{
// Get file ID (used to check for hardlinks)
- BY_HANDLE_FILE_INFORMATION file_info;
+ BY_HANDLE_FILE_INFORMATION file_info{};
if (!GetFileInformationByHandle(file_handle, &file_info) || file_info.nNumberOfLinks == 1)
{
diff --git a/Utilities/File.h b/Utilities/File.h
index 7e6356da7b..dd2db42a46 100644
--- a/Utilities/File.h
+++ b/Utilities/File.h
@@ -66,13 +66,13 @@ namespace fs
// File attributes (TODO)
struct stat_t
{
- bool is_directory;
- bool is_symlink;
- bool is_writable;
- u64 size;
- s64 atime;
- s64 mtime;
- s64 ctime;
+ bool is_directory = false;
+ bool is_symlink = false;
+ bool is_writable = false;
+ u64 size = 0;
+ s64 atime = 0;
+ s64 mtime = 0;
+ s64 ctime = 0;
using enable_bitcopy = std::true_type;
diff --git a/Utilities/JITASM.cpp b/Utilities/JITASM.cpp
index acb5f40b04..90c09bb0bf 100644
--- a/Utilities/JITASM.cpp
+++ b/Utilities/JITASM.cpp
@@ -14,6 +14,10 @@
#define CAN_OVERCOMMIT
#endif
+#if defined(__APPLE__)
+#include
+#endif
+
LOG_CHANNEL(jit_log, "JIT");
void jit_announce(uptr func, usz size, std::string_view name)
diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp
index 4431769f3a..d68ef51cb5 100644
--- a/Utilities/StrFmt.cpp
+++ b/Utilities/StrFmt.cpp
@@ -16,12 +16,12 @@
#include
#endif
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable : 4996)
-#elif defined(__clang__)
+#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4996)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
diff --git a/Utilities/StrUtil.h b/Utilities/StrUtil.h
index d274cc074d..66c351d60f 100644
--- a/Utilities/StrUtil.h
+++ b/Utilities/StrUtil.h
@@ -13,13 +13,13 @@ std::string wchar_to_utf8(std::wstring_view src);
std::string utf16_to_utf8(std::u16string_view src);
std::u16string utf8_to_utf16(std::string_view src);
-// Copy null-terminated string from a std::string or a char array to a char array with truncation
-template
+// Copy null-terminated string from a std::basic_string or a char array to a char array with truncation
+template requires requires (D& d, T& t) { std::declval() = &d[0]; }
inline void strcpy_trunc(D&& dst, const T& src)
{
const usz count = std::size(src) >= std::size(dst) ? std::max(std::size(dst), 1) - 1 : std::size(src);
- std::memcpy(std::data(dst), std::data(src), count);
- std::memset(std::data(dst) + count, 0, std::size(dst) - count);
+ std::copy_n(std::data(src), count, std::data(dst));
+ std::fill_n(std::data(dst) + count, std::size(dst) - count, std::remove_cvref_t{});
}
// Convert string to signed integer
diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp
index 810b8fd7c5..6395c32505 100644
--- a/Utilities/Thread.cpp
+++ b/Utilities/Thread.cpp
@@ -8,13 +8,17 @@
#include "Emu/RSX/RSXThread.h"
#include "Thread.h"
#include "Utilities/JIT.h"
-#include
#include
#ifdef ARCH_ARM64
#include "Emu/CPU/Backends/AArch64/AArch64Signal.h"
#endif
+#ifdef __cpp_lib_stacktrace
+#include "rpcs3_version.h"
+#include
+#endif
+
#ifdef _WIN32
#include
#include
@@ -2801,6 +2805,16 @@ void thread_base::exec()
[[noreturn]] void thread_ctrl::emergency_exit(std::string_view reason)
{
+ // Print stacktrace
+#ifdef __cpp_lib_stacktrace
+ if (rpcs3::is_local_build())
+ {
+ std::ostringstream oss;
+ oss << std::stacktrace::current();
+ sys_log.notice("StackTrace\n\n%s\n", oss.str());
+ }
+#endif
+
if (const std::string info = dump_useful_thread_info(); !info.empty())
{
sys_log.notice("\n%s", info);
diff --git a/Utilities/Thread.h b/Utilities/Thread.h
index 02e5db56ff..7cd9a7c7ea 100644
--- a/Utilities/Thread.h
+++ b/Utilities/Thread.h
@@ -4,6 +4,7 @@
#include "util/atomic.hpp"
#include "util/shared_ptr.hpp"
+#include
#include
// Hardware core layout
diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp
index 9449d808c0..fd94b830e0 100644
--- a/Utilities/bin_patch.cpp
+++ b/Utilities/bin_patch.cpp
@@ -329,7 +329,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, std::st
is_valid = false;
continue;
}
- else if (serial.size() != 9 || !std::all_of(serial.begin(), serial.end(), [](char c) { return std::isalnum(c); }))
+ else if (serial.size() != 9 || !std::all_of(serial.begin(), serial.end(), [](char c) { return std::isalnum(static_cast(c)); }))
{
append_log_message(log_messages, fmt::format("Error: Serial '%s' invalid (patch: %s, key: %s, location: %s, file: %s)", serial, description, main_key, get_yaml_node_location(serial_node), path), &patch_log.error);
is_valid = false;
diff --git a/Utilities/geometry.h b/Utilities/geometry.h
index faace6c77e..3ffbc04dd3 100644
--- a/Utilities/geometry.h
+++ b/Utilities/geometry.h
@@ -821,6 +821,14 @@ struct color4_base
a *= rhs;
}
+ void operator += (const color4_base& rhs)
+ {
+ r += rhs.r;
+ g += rhs.g;
+ b += rhs.b;
+ a += rhs.a;
+ }
+
constexpr color4_base operator * (const color4_base& rhs) const
{
return { r * rhs.r, g * rhs.g, b * rhs.b, a * rhs.a };
diff --git a/buildfiles/msvc/common_default.props b/buildfiles/msvc/common_default.props
index 04f6502839..bfddbb5465 100644
--- a/buildfiles/msvc/common_default.props
+++ b/buildfiles/msvc/common_default.props
@@ -13,7 +13,7 @@
stdcpplatest
- stdcpp20
+ stdcpp23
_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING=1;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)
false
-d2FH4- %(AdditionalOptions)
diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt
index e32597f792..2aa8120752 100644
--- a/rpcs3/CMakeLists.txt
+++ b/rpcs3/CMakeLists.txt
@@ -8,7 +8,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/git-version.cmake)
include(ConfigureCompiler)
include(CheckFunctionExists)
-set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD 23)
if(UNIX AND NOT APPLE AND NOT ANDROID)
add_compile_definitions(DATADIR="${CMAKE_INSTALL_FULL_DATADIR}/rpcs3")
@@ -195,6 +195,7 @@ if(BUILD_RPCS3_TESTS)
tests/test_address_range.cpp
tests/test_rsx_cfg.cpp
tests/test_rsx_fp_asm.cpp
+ tests/test_dmux_pamf.cpp
)
target_link_libraries(rpcs3_test
@@ -202,6 +203,7 @@ if(BUILD_RPCS3_TESTS)
rpcs3_lib
rpcs3_emu
GTest::gtest
+ GTest::gmock
)
target_include_directories(rpcs3_test
diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt
index e42fe70cf6..900e7cff44 100644
--- a/rpcs3/Emu/CMakeLists.txt
+++ b/rpcs3/Emu/CMakeLists.txt
@@ -501,6 +501,7 @@ target_sources(rpcs3_emu PRIVATE
RSX/Overlays/overlays.cpp
RSX/Overlays/overlay_animated_icon.cpp
RSX/Overlays/overlay_animation.cpp
+ RSX/Overlays/overlay_audio.cpp
RSX/Overlays/overlay_compile_notification.cpp
RSX/Overlays/overlay_controls.cpp
RSX/Overlays/overlay_cursor.cpp
diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h
index dff06dfb81..2ce4fa68b3 100644
--- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h
+++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h
@@ -20,19 +20,19 @@ namespace aarch64
sp
};
- static const char* gpr_names[] =
+ [[maybe_unused]] static const char* gpr_names[] =
{
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9",
"x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19",
"x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30"
};
- static const char* spr_names[] =
+ [[maybe_unused]] static const char* spr_names[] =
{
"xzr", "pc", "sp"
};
- static const char* spr_asm_names[] =
+ [[maybe_unused]] static const char* spr_asm_names[] =
{
"xzr", ".", "sp"
};
diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp
index afec56f7e1..3ab011aa04 100644
--- a/rpcs3/Emu/CPU/CPUThread.cpp
+++ b/rpcs3/Emu/CPU/CPUThread.cpp
@@ -206,11 +206,7 @@ struct cpu_prof
// Print only 7 hash characters out of 11 (which covers roughly 48 bits)
if (type_id == 2)
{
- fmt::append(results, "\n\t[%s", fmt::base57(be_t{name}));
- results.resize(results.size() - 4);
-
- // Print chunk address from lowest 16 bits
- fmt::append(results, "...chunk-0x%05x]: %.4f%% (%u)", (name & 0xffff) * 4, _frac * 100., count);
+ fmt::append(results, "\n\t[%s]: %.4f%% (%u)", spu_block_hash{name}, _frac * 100., count);
}
else
{
@@ -1377,7 +1373,7 @@ std::vector> cpu_thread::dump_callstack_list() const
std::string cpu_thread::dump_misc() const
{
- return fmt::format("Type: %s; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", state.load());
+ return fmt::format("%s[0x%x]; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", id, state.load());
}
bool cpu_thread::suspend_work::push(cpu_thread* _this) noexcept
diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp
index f799e4b6be..08e8e9ad30 100644
--- a/rpcs3/Emu/CPU/CPUTranslator.cpp
+++ b/rpcs3/Emu/CPU/CPUTranslator.cpp
@@ -201,6 +201,13 @@ void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngin
m_use_vnni = true;
m_use_gfni = true;
}
+
+#ifdef ARCH_ARM64
+ if (utils::has_dotprod())
+ {
+ m_use_dotprod = true;
+ }
+#endif
}
llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type) const
diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h
index 99ddafde0a..9b9804fd39 100644
--- a/rpcs3/Emu/CPU/CPUTranslator.h
+++ b/rpcs3/Emu/CPU/CPUTranslator.h
@@ -3090,6 +3090,9 @@ protected:
// For now, setting this flag will speed up SPU verification
// but I will remove this later with explicit parralelism - Whatcookie
bool m_use_avx = true;
+
+ // ARMv8 SDOT/UDOT
+ bool m_use_dotprod = false;
#else
// Allow FMA
bool m_use_fma = false;
@@ -3647,10 +3650,59 @@ public:
const auto data0 = a.eval(m_ir);
const auto data1 = b.eval(m_ir);
const auto data2 = c.eval(m_ir);
+
+#if LLVM_VERSION_MAJOR >= 22
+ // LLVM 22+ changed the intrinsic signature from v4i32 to v16i8 for operands 2 and 3
+ result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_avx512_vpdpbusd_128),
+ {data0, m_ir->CreateBitCast(data1, get_type()), m_ir->CreateBitCast(data2, get_type())});
+#else
result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_avx512_vpdpbusd_128), {data0, data1, data2});
+#endif
return result;
}
+#ifdef ARCH_ARM64
+template
+ value_t udot(T1 a, T2 b, T3 c)
+ {
+ value_t result;
+
+ const auto data0 = a.eval(m_ir);
+ const auto data1 = b.eval(m_ir);
+ const auto data2 = c.eval(m_ir);
+
+ result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_udot), {data0, data1, data2});
+ return result;
+ }
+
+ template
+ value_t sdot(T1 a, T2 b, T3 c)
+ {
+ value_t result;
+
+ const auto data0 = a.eval(m_ir);
+ const auto data1 = b.eval(m_ir);
+ const auto data2 = c.eval(m_ir);
+
+ result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_sdot), {data0, data1, data2});
+ return result;
+ }
+
+template
+ auto addp(T1 a, T2 b)
+ {
+ using T_vector = typename is_llvm_expr::type;
+ const auto data1 = a.eval(m_ir);
+ const auto data2 = b.eval(m_ir);
+
+ const auto func = get_intrinsic(llvm::Intrinsic::aarch64_neon_addp);
+
+ value_t result;
+ result.value = m_ir->CreateCall(func, {data1, data2});
+ return result;
+ }
+#endif
+
template
value_t vpermb(T1 a, T2 b)
{
diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.cpp b/rpcs3/Emu/Cell/Modules/cellCamera.cpp
index 096f9330f4..10f9a89cf0 100644
--- a/rpcs3/Emu/Cell/Modules/cellCamera.cpp
+++ b/rpcs3/Emu/Cell/Modules/cellCamera.cpp
@@ -919,7 +919,7 @@ error_code cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm
if (!check_dev_num(dev_num))
{
- return CELL_CAMERA_ERROR_PARAM;
+ return { CELL_CAMERA_ERROR_PARAM, "dev_num=%d", dev_num };
}
if (g_cfg.io.camera == camera_handler::null)
@@ -935,7 +935,7 @@ error_code cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm
if (!arg1)
{
- return CELL_CAMERA_ERROR_PARAM;
+ return { CELL_CAMERA_ERROR_PARAM, "arg1=null" };
}
if (error_code error = check_resolution(dev_num))
@@ -952,7 +952,7 @@ error_code cellCameraGetAttribute(s32 dev_num, s32 attrib, vm::ptr arg1, vm
if (!attr_name) // invalid attributes don't have a name
{
- return CELL_CAMERA_ERROR_PARAM;
+ return { CELL_CAMERA_ERROR_PARAM, "attrib=0x%x", attrib };
}
if (arg1)
@@ -983,7 +983,7 @@ error_code cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2)
if (!check_dev_num(dev_num))
{
- return CELL_CAMERA_ERROR_PARAM;
+ return { CELL_CAMERA_ERROR_PARAM, "dev_num=%d", dev_num };
}
if (g_cfg.io.camera == camera_handler::null)
@@ -1004,7 +1004,7 @@ error_code cellCameraSetAttribute(s32 dev_num, s32 attrib, u32 arg1, u32 arg2)
if (!attr_name) // invalid attributes don't have a name
{
- return CELL_CAMERA_ERROR_PARAM;
+ return { CELL_CAMERA_ERROR_PARAM, "attrib=0x%x", attrib };
}
g_camera.set_attr(attrib, arg1, arg2);
diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.cpp b/rpcs3/Emu/Cell/Modules/cellDmux.cpp
index d7f6f84f3f..fb1f32837d 100644
--- a/rpcs3/Emu/Cell/Modules/cellDmux.cpp
+++ b/rpcs3/Emu/Cell/Modules/cellDmux.cpp
@@ -169,18 +169,18 @@ public:
static const u32 id_count = 1023;
SAVESTATE_INIT_POS(34);
- ElementaryStream(Demuxer* dmux, u32 addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, u32 cbArg, u32 spec);
+ ElementaryStream(Demuxer* dmux, vm::ptr addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, vm::ptr cbArg, u32 spec);
Demuxer* dmux;
const u32 id = idm::last_id();
- const u32 memAddr;
+ const vm::ptr memAddr;
const u32 memSize;
const u32 fidMajor;
const u32 fidMinor;
const u32 sup1;
const u32 sup2;
const vm::ptr cbFunc;
- const u32 cbArg;
+ const vm::ptr cbArg;
const u32 spec; //addr
std::vector raw_data; // demultiplexed data stream (managed by demuxer thread)
@@ -208,13 +208,13 @@ public:
const u32 memAddr;
const u32 memSize;
const vm::ptr cbFunc;
- const u32 cbArg;
+ const vm::ptr cbArg;
volatile bool is_finished = false;
volatile bool is_closed = false;
atomic_t is_running = false;
atomic_t is_working = false;
- Demuxer(u32 addr, u32 size, vm::ptr func, u32 arg)
+ Demuxer(u32 addr, u32 size, vm::ptr func, vm::ptr arg)
: ppu_thread({}, "", 0)
, memAddr(addr)
, memSize(size)
@@ -755,11 +755,11 @@ PesHeader::PesHeader(DemuxerStream& stream)
is_ok = true;
}
-ElementaryStream::ElementaryStream(Demuxer* dmux, u32 addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, u32 cbArg, u32 spec)
- : put(utils::align(addr, 128))
+ElementaryStream::ElementaryStream(Demuxer* dmux, vm::ptr addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, vm::ptr cbArg, u32 spec)
+ : put(utils::align(addr.addr(), 128))
, dmux(dmux)
- , memAddr(utils::align(addr, 128))
- , memSize(size - (addr - memAddr))
+ , memAddr(vm::ptr::make(utils::align(addr.addr(), 128)))
+ , memSize(size - (addr.addr() - memAddr.addr()))
, fidMajor(fidMajor)
, fidMinor(fidMinor)
, sup1(sup1)
@@ -788,9 +788,9 @@ bool ElementaryStream::is_full(u32 space)
{
return first - put < space + 128;
}
- else if (put + space + 128 > memAddr + memSize)
+ else if (put + space + 128 > memAddr.addr() + memSize)
{
- return first - memAddr < space + 128;
+ return first - memAddr.addr() < space + 128;
}
else
{
@@ -816,35 +816,35 @@ void ElementaryStream::push_au(u32 size, u64 dts, u64 pts, u64 userdata, bool ra
std::lock_guard lock(m_mutex);
ensure(!is_full(size));
- if (put + size + 128 > memAddr + memSize)
+ if (put + size + 128 > memAddr.addr() + memSize)
{
- put = memAddr;
+ put = memAddr.addr();
}
std::memcpy(vm::base(put + 128), raw_data.data(), size);
raw_data.erase(raw_data.begin(), raw_data.begin() + size);
auto info = vm::ptr::make(put);
- info->auAddr = put + 128;
+ info->auAddr.set(put + 128);
info->auSize = size;
info->dts.lower = static_cast(dts);
info->dts.upper = static_cast(dts >> 32);
info->pts.lower = static_cast(pts);
info->pts.upper = static_cast(pts >> 32);
info->isRap = rap;
- info->reserved = 0;
+ info->auMaxSize = 0;
info->userData = userdata;
auto spec = vm::ptr::make(put + u32{sizeof(CellDmuxAuInfoEx)});
*spec = specific;
auto inf = vm::ptr::make(put + 64);
- inf->auAddr = put + 128;
+ inf->auAddr.set(put + 128);
inf->auSize = size;
- inf->dtsLower = static_cast(dts);
- inf->dtsUpper = static_cast(dts >> 32);
- inf->ptsLower = static_cast(pts);
- inf->ptsUpper = static_cast(pts >> 32);
+ inf->dts.lower = static_cast(dts);
+ inf->dts.upper = static_cast(dts >> 32);
+ inf->pts.lower = static_cast(pts);
+ inf->pts.upper = static_cast(pts >> 32);
inf->auMaxSize = 0; // ?????
inf->userData = userdata;
@@ -927,7 +927,7 @@ bool ElementaryStream::peek(u32& out_data, bool no_ex, u32& out_spec, bool updat
void ElementaryStream::reset()
{
std::lock_guard lock(m_mutex);
- put = memAddr;
+ put = memAddr.addr();
entries.clear();
put_count = 0;
got_count = 0;
diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.h b/rpcs3/Emu/Cell/Modules/cellDmux.h
index 1767165283..dc17cb3314 100644
--- a/rpcs3/Emu/Cell/Modules/cellDmux.h
+++ b/rpcs3/Emu/Cell/Modules/cellDmux.h
@@ -33,118 +33,6 @@ enum CellDmuxEsMsgType : s32
CELL_DMUX_ES_MSG_TYPE_FLUSH_DONE = 1,
};
-enum CellDmuxPamfM2vLevel : s32
-{
- CELL_DMUX_PAMF_M2V_MP_LL = 0,
- CELL_DMUX_PAMF_M2V_MP_ML,
- CELL_DMUX_PAMF_M2V_MP_H14,
- CELL_DMUX_PAMF_M2V_MP_HL,
-};
-
-enum CellDmuxPamfAvcLevel : s32
-{
- CELL_DMUX_PAMF_AVC_LEVEL_2P1 = 21,
- CELL_DMUX_PAMF_AVC_LEVEL_3P0 = 30,
- CELL_DMUX_PAMF_AVC_LEVEL_3P1 = 31,
- CELL_DMUX_PAMF_AVC_LEVEL_3P2 = 32,
- CELL_DMUX_PAMF_AVC_LEVEL_4P1 = 41,
- CELL_DMUX_PAMF_AVC_LEVEL_4P2 = 42,
-};
-
-struct CellDmuxPamfAuSpecificInfoM2v
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfAuSpecificInfoAvc
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfAuSpecificInfoLpcm
-{
- u8 channelAssignmentInfo;
- u8 samplingFreqInfo;
- u8 bitsPerSample;
-};
-
-struct CellDmuxPamfAuSpecificInfoAc3
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfAuSpecificInfoAtrac3plus
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfAuSpecificInfoUserData
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfEsSpecificInfoM2v
-{
- be_t profileLevel;
-};
-
-struct CellDmuxPamfEsSpecificInfoAvc
-{
- be_t level;
-};
-
-struct CellDmuxPamfEsSpecificInfoLpcm
-{
- be_t samplingFreq;
- be_t numOfChannels;
- be_t bitsPerSample;
-};
-
-struct CellDmuxPamfEsSpecificInfoAc3
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfEsSpecificInfoAtrac3plus
-{
- be_t reserved1;
-};
-
-struct CellDmuxPamfEsSpecificInfoUserData
-{
- be_t reserved1;
-};
-
-enum CellDmuxPamfSamplingFrequency : s32
-{
- CELL_DMUX_PAMF_FS_48K = 48000,
-};
-
-enum CellDmuxPamfBitsPerSample : s32
-{
- CELL_DMUX_PAMF_BITS_PER_SAMPLE_16 = 16,
- CELL_DMUX_PAMF_BITS_PER_SAMPLE_24 = 24,
-};
-
-enum CellDmuxPamfLpcmChannelAssignmentInfo : s32
-{
- CELL_DMUX_PAMF_LPCM_CH_M1 = 1,
- CELL_DMUX_PAMF_LPCM_CH_LR = 3,
- CELL_DMUX_PAMF_LPCM_CH_LRCLSRSLFE = 9,
- CELL_DMUX_PAMF_LPCM_CH_LRCLSCS1CS2RSLFE = 11,
-};
-
-enum CellDmuxPamfLpcmFs : s32
-{
- CELL_DMUX_PAMF_LPCM_FS_48K = 1,
-};
-
-enum CellDmuxPamfLpcmBitsPerSamples : s32
-{
- CELL_DMUX_PAMF_LPCM_BITS_PER_SAMPLE_16 = 1,
- CELL_DMUX_PAMF_LPCM_BITS_PER_SAMPLE_24 = 3,
-};
-
struct CellDmuxMsg
{
be_t msgType; // CellDmuxMsgType
@@ -163,12 +51,6 @@ struct CellDmuxType
be_t reserved[2];
};
-struct CellDmuxPamfSpecificInfo
-{
- be_t thisSize;
- b8 programEndCodeCb;
-};
-
struct CellDmuxType2
{
be_t streamType; // CellDmuxStreamType
@@ -177,7 +59,7 @@ struct CellDmuxType2
struct CellDmuxResource
{
- be_t memAddr;
+ vm::bptr memAddr;
be_t memSize;
be_t ppuThreadPriority;
be_t ppuThreadStackSize;
@@ -187,7 +69,7 @@ struct CellDmuxResource
struct CellDmuxResourceEx
{
- be_t memAddr;
+ vm::bptr memAddr;
be_t memSize;
be_t ppuThreadPriority;
be_t ppuThreadStackSize;
@@ -227,16 +109,16 @@ struct CellDmuxResource2
be_t shit[4];
};
-using CellDmuxCbMsg = u32(u32 demuxerHandle, vm::ptr demuxerMsg, u32 cbArg);
+using CellDmuxCbMsg = u32(u32 demuxerHandle, vm::cptr demuxerMsg, vm::ptr cbArg);
-using CellDmuxCbEsMsg = u32(u32 demuxerHandle, u32 esHandle, vm::ptr esMsg, u32 cbArg);
+using CellDmuxCbEsMsg = u32(u32 demuxerHandle, u32 esHandle, vm::cptr esMsg, vm::ptr cbArg);
// Used for internal callbacks as well
template
struct DmuxCb
{
vm::bptr cbFunc;
- be_t cbArg;
+ vm::bptr cbArg;
};
using CellDmuxCb = DmuxCb;
@@ -250,42 +132,50 @@ struct CellDmuxAttr
be_t demuxerVerLower;
};
+struct CellDmuxPamfAttr
+{
+ be_t maxEnabledEsNum;
+ be_t version;
+ be_t memSize;
+};
+
struct CellDmuxEsAttr
{
be_t memSize;
};
+struct CellDmuxPamfEsAttr
+{
+ be_t auQueueMaxSize;
+ be_t memSize;
+ be_t specificInfoSize;
+};
+
struct CellDmuxEsResource
{
- be_t memAddr;
+ vm::bptr memAddr;
be_t memSize;
};
struct CellDmuxAuInfo
{
- be_t auAddr;
+ vm::bptr auAddr;
be_t auSize;
be_t auMaxSize;
- be_t userData;
- be_t ptsUpper;
- be_t ptsLower;
- be_t dtsUpper;
- be_t dtsLower;
-};
-
-struct CellDmuxAuInfoEx
-{
- be_t auAddr;
- be_t auSize;
- be_t reserved;
b8 isRap;
be_t userData;
CellCodecTimeStamp pts;
CellCodecTimeStamp dts;
};
-struct CellDmuxPamfAttr;
-struct CellDmuxPamfEsAttr;
+using CellDmuxAuInfoEx = CellDmuxAuInfo;
+
+struct DmuxAuInfo
+{
+ CellDmuxAuInfo info;
+ vm::bptr specific_info;
+ be_t specific_info_size;
+};
using DmuxNotifyDemuxDone = error_code(vm::ptr, u32, vm::ptr);
using DmuxNotifyFatalErr = error_code(vm::ptr, u32, vm::ptr);
@@ -301,7 +191,7 @@ using CellDmuxCoreOpResetStream = error_code(vm::ptr);
using CellDmuxCoreOpCreateThread = error_code(vm::ptr);
using CellDmuxCoreOpJoinThread = error_code(vm::ptr);
using CellDmuxCoreOpSetStream = error_code(vm::ptr, vm::cptr, u32, b8, u64);
-using CellDmuxCoreOpFreeMemory = error_code(vm::ptr, vm::ptr, u32);
+using CellDmuxCoreOpReleaseAu = error_code(vm::ptr, vm::ptr, u32);
using CellDmuxCoreOpQueryEsAttr = error_code(vm::cptr, vm::cptr, vm::ptr);
using CellDmuxCoreOpEnableEs = error_code(vm::ptr, vm::cptr, vm::cptr, vm::cptr>, vm::cptr>, vm::cptr, vm::pptr);
using CellDmuxCoreOpDisableEs = u32(vm::ptr);
@@ -318,7 +208,7 @@ struct CellDmuxCoreOps
vm::bptr createThread;
vm::bptr joinThread;
vm::bptr setStream;
- vm::bptr freeMemory;
+ vm::bptr releaseAu;
vm::bptr queryEsAttr;
vm::bptr enableEs;
vm::bptr disableEs;
diff --git a/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp b/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp
index 70162d4031..91ee7e2426 100644
--- a/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp
+++ b/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp
@@ -1,121 +1,2842 @@
#include "stdafx.h"
#include "Emu/Cell/PPUModule.h"
-#include "Emu/IdManager.h"
+#include "Emu/Cell/lv2/sys_cond.h"
+#include "Emu/Cell/lv2/sys_memory.h"
+#include "Emu/Cell/lv2/sys_mutex.h"
+#include "Emu/Cell/lv2/sys_ppu_thread.h"
+#include "Emu/Cell/lv2/sys_sync.h"
+#include "sysPrxForUser.h"
+#include "util/asm.hpp"
-#include "cellDmux.h"
#include "cellDmuxPamf.h"
-
+#include
vm::gvar g_cell_dmux_core_ops_pamf;
vm::gvar g_cell_dmux_core_ops_raw_es;
LOG_CHANNEL(cellDmuxPamf)
+template <>
+void fmt_class_string::format(std::string& out, u64 arg)
+{
+ format_enum(out, arg, [](CellDmuxPamfError value)
+ {
+ switch (value)
+ {
+ STR_CASE(CELL_DMUX_PAMF_ERROR_BUSY);
+ STR_CASE(CELL_DMUX_PAMF_ERROR_ARG);
+ STR_CASE(CELL_DMUX_PAMF_ERROR_UNKNOWN_STREAM);
+ STR_CASE(CELL_DMUX_PAMF_ERROR_NO_MEMORY);
+ STR_CASE(CELL_DMUX_PAMF_ERROR_FATAL);
+ }
+
+ return unknown;
+ });
+}
+
+inline std::pair dmuxPamfStreamIdToTypeChannel(u16 stream_id, u16 private_stream_id)
+{
+ if ((stream_id & 0xf0) == 0xe0)
+ {
+ return { DMUX_PAMF_STREAM_TYPE_INDEX_VIDEO, stream_id & 0x0f };
+ }
+
+ if ((stream_id & 0xff) != 0xbd)
+ {
+ return { DMUX_PAMF_STREAM_TYPE_INDEX_INVALID, 0 };
+ }
+
+ switch (private_stream_id & 0xf0)
+ {
+ case 0x40: return { DMUX_PAMF_STREAM_TYPE_INDEX_LPCM, private_stream_id & 0x0f };
+ case 0x30: return { DMUX_PAMF_STREAM_TYPE_INDEX_AC3, private_stream_id & 0x0f };
+ case 0x00: return { DMUX_PAMF_STREAM_TYPE_INDEX_ATRACX, private_stream_id & 0x0f };
+ case 0x20: return { DMUX_PAMF_STREAM_TYPE_INDEX_USER_DATA, private_stream_id & 0x0f };
+ default: return { DMUX_PAMF_STREAM_TYPE_INDEX_INVALID, 0 };
+ }
+}
+
+
+// SPU thread
+
+void dmux_pamf_base::output_queue::pop_back(u32 au_size)
+{
+ ensure(back - au_size >= buffer.data(), "Invalid au_size");
+ back -= au_size;
+}
+
+void dmux_pamf_base::output_queue::pop_back(u8* au_addr)
+{
+ ensure(au_addr >= buffer.data() && au_addr < std::to_address(buffer.end()), "Invalid au_addr");
+
+ // If au_begin is in front of the back pointer, unwrap the back pointer (there are no more access units behind the back pointer)
+ if (au_addr > back)
+ {
+ wrap_pos = buffer.data();
+ }
+
+ back = au_addr;
+}
+
+void dmux_pamf_base::output_queue::pop_front(u32 au_size)
+{
+ ensure(front + au_size <= std::to_address(buffer.end()), "Invalid au_size");
+ front += au_size;
+
+ // When front reaches wrap_pos, unwrap the queue
+ if (wrap_pos != buffer.data() && wrap_pos <= front)
+ {
+ ensure(wrap_pos == front, "Invalid au_size");
+ front = buffer.data();
+ wrap_pos = buffer.data();
+ }
+}
+
+void dmux_pamf_base::output_queue::push_unchecked(const access_unit_chunk& au_chunk)
+{
+ std::ranges::copy(au_chunk.cached_data, back);
+ std::ranges::copy(au_chunk.data, back + au_chunk.cached_data.size());
+ back += au_chunk.data.size() + au_chunk.cached_data.size();
+}
+
+bool dmux_pamf_base::output_queue::push(const access_unit_chunk& au_chunk, const std::function& on_fatal_error)
+{
+ // If there are any unconsumed access units behind the back pointer, the distance between the front and back pointers is the remaining capacity,
+ // otherwise the distance between the end of the buffer and the back pointer is the remaining capacity
+ if (wrap_pos == buffer.data())
+ {
+ // Since it was already checked if there is enough space for au_max_size, this can only occur if the current access unit is larger than au_max_size
+ if (au_chunk.data.size() + au_chunk.cached_data.size() > static_cast(std::to_address(buffer.end()) - back))
+ {
+ cellDmuxPamf.error("Access unit larger than specified maximum access unit size");
+ on_fatal_error();
+ return false;
+ }
+ }
+ else if (au_chunk.data.size() + au_chunk.cached_data.size() + 0x10 > static_cast(front - back)) // + sizeof(v128) because of SPU shenanigans probably
+ {
+ return false;
+ }
+
+ push_unchecked(au_chunk);
+ return true;
+}
+
+bool dmux_pamf_base::output_queue::prepare_next_au(u32 au_max_size)
+{
+ // LLE always checks the distance between the end of the buffer and the back pointer, even if the back pointer is wrapped around and there are unconsumed access units behind it
+ if (std::to_address(buffer.end()) - back < au_max_size)
+ {
+ // Can't wrap the back pointer around again as long as there are unconsumed access units behind it
+ if (wrap_pos != buffer.data())
+ {
+ return false;
+ }
+
+ wrap_pos = back;
+ back = buffer.data();
+ }
+
+ return true;
+}
+
+void dmux_pamf_base::elementary_stream::flush_es()
+{
+ if (current_au.accumulated_size != 0)
+ {
+ ensure(au_queue.get_free_size() >= cache.size());
+ au_queue.push_unchecked({ {}, cache });
+
+ current_au.accumulated_size += static_cast(cache.size());
+
+ ctx.on_au_found(get_stream_id().first, get_stream_id().second, user_data, { au_queue.peek_back(current_au.accumulated_size), current_au.accumulated_size }, current_au.pts, current_au.dts,
+ current_au.rap, au_specific_info_size, current_au.au_specific_info_buf);
+ }
+
+ reset();
+
+ while (!ctx.on_flush_done(get_stream_id().first, get_stream_id().second, user_data)) {} // The flush_done event is repeatedly fired until it succeeds
+}
+
+void dmux_pamf_base::elementary_stream::reset_es(u8* au_addr)
+{
+ if (!au_addr)
+ {
+ reset();
+ au_queue.clear();
+ }
+ else
+ {
+ au_queue.pop_back(au_addr);
+ }
+}
+
+void dmux_pamf_base::elementary_stream::discard_access_unit()
+{
+ au_queue.pop_back(current_au.accumulated_size - static_cast(au_chunk.data.size() + au_chunk.cached_data.size()));
+ reset();
+ cache.clear();
+}
+
+template
+u32 dmux_pamf_base::elementary_stream::parse_audio_stream_header(std::span pes_packet_data)
+{
+ u32 extra_header_size_unk = 0; // No clue what this is, I have not found a single instance in any PAMF stream where it is something other than zero
+
+ if (!au_size_unk) // For some reason, LLE uses the member that stores the size of user data access units here as bool
+ {
+ // Not checked on LLE
+ if (pes_packet_data.size() < sizeof(u32))
+ {
+ return umax;
+ }
+
+ extra_header_size_unk = read_from_ptr>(pes_packet_data) & extra_header_size_unk_mask;
+ au_size_unk = true;
+ }
+
+ return extra_header_size_unk + sizeof(u32);
+}
+
+bool dmux_pamf_base::elementary_stream::process_pes_packet_data()
+{
+ ensure(pes_packet_data, "set_pes_packet_data() should be used before process_stream()");
+
+ for (;;)
+ {
+ switch (state)
+ {
+ case state::initial:
+ if (stream_chunk.empty())
+ {
+ pes_packet_data.reset();
+ return true;
+ }
+
+ // Parse the current stream section and increment the reading position by the amount that was consumed
+ stream_chunk = stream_chunk.subspan(parse_stream(stream_chunk));
+
+ current_au.accumulated_size += static_cast(au_chunk.data.size() + au_chunk.cached_data.size());
+
+ // If the beginning of a new access unit was found, set the current timestamps and rap indicator
+ if (!current_au.timestamps_rap_set && (current_au.state == access_unit::state::commenced || current_au.state == access_unit::state::m2v_sequence
+ || (current_au.state == access_unit::state::complete && au_chunk.cached_data.empty())))
+ {
+ set_au_timestamps_rap();
+ }
+
+ state = state::pushing_au_queue;
+ [[fallthrough]];
+
+ case state::pushing_au_queue:
+ if (!au_chunk.data.empty() || !au_chunk.cached_data.empty())
+ {
+ if (!au_queue.push(au_chunk, std::bind_front(&dmux_pamf_base::on_fatal_error, &ctx)))
+ {
+ ctx.on_au_queue_full();
+ return false;
+ }
+
+ au_chunk.data = {};
+ au_chunk.cached_data.clear();
+ }
+
+ // This happens if the distance between two delimiters is greater than the size indicated in the info header of the stream.
+ if (current_au.state == access_unit::state::size_mismatch)
+ {
+ // LLE cuts off one byte from the beginning of the current PES packet data and then starts over.
+ pes_packet_data = pes_packet_data->subspan<1>();
+ stream_chunk = *pes_packet_data;
+
+ // It also removes the entire current access unit from the queue, even if it began in an earlier PES packet
+ au_queue.pop_back(current_au.accumulated_size);
+ current_au.accumulated_size = 0;
+
+ state = state::initial;
+ continue;
+ }
+
+ state = state::notifying_au_found;
+ [[fallthrough]];
+
+ case state::notifying_au_found:
+ if (current_au.state == access_unit::state::complete && !ctx.on_au_found(get_stream_id().first, get_stream_id().second, user_data,
+ { au_queue.peek_back(current_au.accumulated_size), current_au.accumulated_size }, current_au.pts, current_au.dts, current_au.rap, au_specific_info_size, current_au.au_specific_info_buf))
+ {
+ return false;
+ }
+
+ state = state::preparing_for_next_au;
+ [[fallthrough]];
+
+ case state::preparing_for_next_au:
+ if (current_au.state == access_unit::state::complete)
+ {
+ if (!au_queue.prepare_next_au(au_max_size))
+ {
+ ctx.on_au_queue_full();
+ return false;
+ }
+
+ current_au = {};
+ }
+
+ state = state::initial;
+ }
+ }
+}
+
+template
+u32 dmux_pamf_base::video_stream::parse_stream(std::span stream)
+{
+ if (current_au.state != access_unit::state::none && (avc || current_au.state != access_unit::state::m2v_sequence))
+ {
+ current_au.state = access_unit::state::incomplete;
+ }
+
+ // Concatenate the cache of the previous stream section and the beginning of the current section
+ std::array buf{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; // Prevent false positives (M2V pic start code ends with 0x00)
+ ensure(cache.size() <= 3, "The size of the cache should never exceed three bytes");
+ std::ranges::copy(cache, buf.begin());
+ std::copy_n(stream.begin(), std::min(sizeof(u32) - 1, stream.size()), buf.begin() + cache.size()); // Not entirely accurate: LLE always reads three bytes from the stream, even if it is smaller than that
+
+ auto au_chunk_begin = stream.begin();
+ s32 cache_idx = 0;
+ auto stream_it = stream.begin();
+ [&]
+ {
+ // Search for delimiter in cache
+ for (; cache_idx < static_cast(cache.size()); cache_idx++)
+ {
+ if (const be_t code = read_from_ptr>(buf.data(), cache_idx);
+ (avc && code == AVC_AU_DELIMITER) || (!avc && (code == M2V_PIC_START || code == M2V_SEQUENCE_HEADER || code == M2V_SEQUENCE_END)))
+ {
+ if (current_au.state != access_unit::state::none && (avc || current_au.state != access_unit::state::m2v_sequence))
+ {
+ // The sequence end code is included in the access unit
+ // LLE increments the stream pointer instead of the cache index, which will cause the access unit to be corrupted at the end
+ if (!avc && code == M2V_SEQUENCE_END)
+ {
+ cellDmuxPamf.warning("M2V sequence end code in cache");
+ stream_it += std::min(sizeof(u32), stream.size()); // Not accurate, LLE always increments by four, regardless of the stream size
+ }
+
+ current_au.state = access_unit::state::complete;
+ return;
+ }
+
+ // If current_au.state is none and there was a delimiter found here, then LLE outputs the entire cache, even if the access unit starts at cache_idx > 0
+
+ current_au.state = avc || code == M2V_PIC_START ? access_unit::state::commenced : access_unit::state::m2v_sequence;
+ }
+ }
+
+ // Search for delimiter in stream
+ for (; stream_it <= stream.end() - sizeof(u32); stream_it++)
+ {
+ if (const be_t code = read_from_ptr>(stream_it);
+ (avc && code == AVC_AU_DELIMITER) || (!avc && (code == M2V_PIC_START || code == M2V_SEQUENCE_HEADER || code == M2V_SEQUENCE_END)))
+ {
+ if (current_au.state != access_unit::state::none && (avc || current_au.state != access_unit::state::m2v_sequence))
+ {
+ stream_it += !avc && code == M2V_SEQUENCE_END ? sizeof(u32) : 0; // The sequence end code is included in the access unit
+ current_au.state = access_unit::state::complete;
+ return;
+ }
+
+ au_chunk_begin = avc || current_au.state == access_unit::state::none ? stream_it : au_chunk_begin;
+ current_au.state = avc || code == M2V_PIC_START ? access_unit::state::commenced : access_unit::state::m2v_sequence;
+ }
+ }
+ }();
+
+ if (current_au.state != access_unit::state::none)
+ {
+ au_chunk.data = { au_chunk_begin, stream_it };
+ std::copy_n(cache.begin(), cache_idx, std::back_inserter(au_chunk.cached_data));
+ }
+
+ cache.erase(cache.begin(), cache.begin() + cache_idx);
+
+ // Cache the end of the stream if an access unit wasn't completed. There could be the beginning of a delimiter in the last three bytes
+ if (current_au.state != access_unit::state::complete)
+ {
+ std::copy(stream_it, stream.end(), std::back_inserter(cache));
+ }
+
+ return static_cast((current_au.state != access_unit::state::complete || stream_it > stream.end() ? stream.end() : stream_it) - stream.begin());
+}
+
+u32 dmux_pamf_base::lpcm_stream::parse_stream_header(std::span pes_packet_data, [[maybe_unused]] s8 pts_dts_flag)
+{
+ // Not checked on LLE
+ if (pes_packet_data.size() < sizeof(u8) + 0x10)
+ {
+ return umax;
+ }
+
+ std::memcpy(au_specific_info_buf.data(), &pes_packet_data[1], au_specific_info_buf.size());
+ return parse_audio_stream_header<0x7ff>(pes_packet_data);
+}
+
+u32 dmux_pamf_base::lpcm_stream::parse_stream(std::span stream)
+{
+ if (current_au.state == access_unit::state::none)
+ {
+ current_au.au_specific_info_buf = au_specific_info_buf;
+ }
+
+ if (au_max_size - current_au.accumulated_size > stream.size())
+ {
+ au_chunk.data = stream;
+ current_au.state = current_au.state == access_unit::state::none ? access_unit::state::commenced : access_unit::state::incomplete;
+ }
+ else
+ {
+ au_chunk.data = stream.first(au_max_size - current_au.accumulated_size);
+ current_au.state = access_unit::state::complete;
+ }
+
+ return static_cast(au_chunk.data.size());
+}
+
+template
+u32 dmux_pamf_base::audio_stream::parse_stream(std::span stream)
+{
+ const auto parse_au_size = [](be_t data) -> u16
+ {
+ if constexpr (ac3)
+ {
+ if (const u8 fscod = data >> 14, frmsizecod = data >> 8 & 0x3f; fscod < 3 && frmsizecod < 38)
+ {
+ return AC3_FRMSIZE_TABLE[fscod][frmsizecod] * sizeof(s16);
+ }
+ }
+ else if ((data & 0x3ff) < 0x200)
+ {
+ return ((data & 0x3ff) + 1) * 8 + ATRACX_ATS_HEADER_SIZE;
+ }
+
+ return 0;
+ };
+
+ if (current_au.state != access_unit::state::none)
+ {
+ current_au.state = access_unit::state::incomplete;
+ }
+
+ // Concatenate the cache of the previous stream section and the beginning of the current section
+ std::array buf{};
+ ensure(cache.size() <= 3, "The size of the cache should never exceed three bytes");
+ std::ranges::copy(cache, buf.begin());
+ std::copy_n(stream.begin(), std::min(sizeof(u16) - 1, stream.size()), buf.begin() + cache.size());
+
+ auto au_chunk_begin = stream.begin();
+ s32 cache_idx = 0;
+ auto stream_it = stream.begin();
+ [&]
+ {
+ // Search for delimiter in cache
+ for (; cache_idx <= static_cast(cache.size() + std::min(sizeof(u16) - 1, stream.size()) - sizeof(u16)); cache_idx++)
+ {
+ if (const be_t tmp = read_from_ptr>(buf.data(), cache_idx); current_au.size_info_offset != 0)
+ {
+ if (--current_au.size_info_offset == 0)
+ {
+ current_au.parsed_size = parse_au_size(tmp);
+ }
+ }
+ else if (tmp == SYNC_WORD)
+ {
+ if (current_au.state == access_unit::state::none)
+ {
+ // If current_au.state is none and there was a delimiter found here, then LLE outputs the entire cache, even if the access unit starts at cache_idx > 0
+
+ current_au.size_info_offset = ac3 ? sizeof(u16) * 2 : sizeof(u16);
+ current_au.state = access_unit::state::commenced;
+ }
+ else if (const u32 au_size = current_au.accumulated_size + cache_idx; au_size >= current_au.parsed_size)
+ {
+ current_au.state = au_size == current_au.parsed_size ? access_unit::state::complete : access_unit::state::size_mismatch;
+ return;
+ }
+ }
+ }
+
+ // As long as the current access unit hasn't reached the size indicated in its header, we don't need to parse the stream
+ if (current_au.state != access_unit::state::none && current_au.size_info_offset == 0 && current_au.accumulated_size + cache.size() < current_au.parsed_size)
+ {
+ stream_it += std::min(current_au.parsed_size - current_au.accumulated_size - cache.size(), stream.size() - sizeof(u32));
+ }
+
+ // Search for delimiter in stream
+ for (; stream_it <= stream.end() - sizeof(u32); stream_it++) // LLE uses sizeof(u32), even though the delimiter is only two bytes large
+ {
+ if (const be_t tmp = read_from_ptr>(stream_it); current_au.size_info_offset != 0)
+ {
+ if (--current_au.size_info_offset == 0)
+ {
+ current_au.parsed_size = parse_au_size(tmp);
+ }
+ }
+ else if (tmp == SYNC_WORD)
+ {
+ if (current_au.state == access_unit::state::none)
+ {
+ au_chunk_begin = stream_it;
+ current_au.size_info_offset = ac3 ? sizeof(u16) * 2 : sizeof(u16);
+ current_au.state = access_unit::state::commenced;
+ }
+ else if (const u32 au_size = static_cast(current_au.accumulated_size + stream_it - au_chunk_begin + cache.size()); au_size >= current_au.parsed_size)
+ {
+ current_au.state = au_size == current_au.parsed_size ? access_unit::state::complete : access_unit::state::size_mismatch;
+ return;
+ }
+ }
+ }
+ }();
+
+ if (current_au.state != access_unit::state::none)
+ {
+ au_chunk.data = { au_chunk_begin, stream_it };
+ std::copy_n(cache.begin(), cache_idx, std::back_inserter(au_chunk.cached_data));
+ }
+
+ cache.erase(cache.begin(), cache.begin() + cache_idx);
+
+ // Cache the end of the stream if an access unit wasn't completed. There could be the beginning of a delimiter in the last three bytes
+ if (current_au.state != access_unit::state::complete && current_au.state != access_unit::state::size_mismatch)
+ {
+ std::copy(stream_it, stream.end(), std::back_inserter(cache));
+ }
+
+ return static_cast((current_au.state != access_unit::state::complete ? stream.end() : stream_it) - stream.begin());
+}
+
+u32 dmux_pamf_base::user_data_stream::parse_stream_header(std::span pes_packet_data, s8 pts_dts_flag)
+{
+ if (pts_dts_flag < 0) // PTS field exists
+ {
+ // Not checked on LLE
+ if (pes_packet_data.size() < 2 + sizeof(u32))
+ {
+ return umax;
+ }
+
+ au_size_unk = read_from_ptr>(pes_packet_data.begin(), 2) - sizeof(u32);
+ return 10;
+ }
+
+ return 2;
+}
+
+u32 dmux_pamf_base::user_data_stream::parse_stream(std::span stream)
+{
+ if (au_size_unk > stream.size())
+ {
+ au_chunk.data = stream;
+ au_size_unk -= static_cast(stream.size());
+ current_au.state = access_unit::state::commenced; // User data streams always use commenced
+ }
+ else
+ {
+ au_chunk.data = stream.first(au_size_unk);
+ au_size_unk = 0;
+ current_au.state = access_unit::state::complete;
+ }
+
+ return static_cast(stream.size()); // Always consume the entire stream
+}
+
+bool dmux_pamf_base::enable_es(u32 stream_id, u32 private_stream_id, bool is_avc, std::span au_queue_buffer, u32 au_max_size, bool raw_es, u32 user_data)
+{
+ const auto [type_idx, channel] = dmuxPamfStreamIdToTypeChannel(stream_id, private_stream_id);
+
+ if (type_idx == DMUX_PAMF_STREAM_TYPE_INDEX_INVALID || elementary_stream::is_enabled(elementary_streams[type_idx][channel]))
+ {
+ return false;
+ }
+
+ this->raw_es = raw_es;
+ pack_es_type_idx = type_idx;
+
+ switch (type_idx)
+ {
+ case DMUX_PAMF_STREAM_TYPE_INDEX_VIDEO:
+ elementary_streams[0][channel] = is_avc ? static_cast>(std::make_unique>(channel, au_max_size, *this, user_data, au_queue_buffer))
+ : std::make_unique>(channel, au_max_size, *this, user_data, au_queue_buffer);
+ return true;
+
+ case DMUX_PAMF_STREAM_TYPE_INDEX_LPCM: elementary_streams[1][channel] = std::make_unique(channel, au_max_size, *this, user_data, au_queue_buffer); return true;
+ case DMUX_PAMF_STREAM_TYPE_INDEX_AC3: elementary_streams[2][channel] = std::make_unique>(channel, au_max_size, *this, user_data, au_queue_buffer); return true;
+ case DMUX_PAMF_STREAM_TYPE_INDEX_ATRACX: elementary_streams[3][channel] = std::make_unique>(channel, au_max_size, *this, user_data, au_queue_buffer); return true;
+ case DMUX_PAMF_STREAM_TYPE_INDEX_USER_DATA: elementary_streams[4][channel] = std::make_unique(channel, au_max_size, *this, user_data, au_queue_buffer); return true;
+ default: fmt::throw_exception("Unreachable");
+ }
+}
+
+bool dmux_pamf_base::disable_es(u32 stream_id, u32 private_stream_id)
+{
+ const auto [type_idx, channel] = dmuxPamfStreamIdToTypeChannel(stream_id, private_stream_id);
+
+ if (type_idx == DMUX_PAMF_STREAM_TYPE_INDEX_INVALID || !elementary_stream::is_enabled(elementary_streams[type_idx][channel]))
+ {
+ return false;
+ }
+
+ elementary_streams[type_idx][channel] = nullptr;
+ return true;
+}
+
+bool dmux_pamf_base::release_au(u32 stream_id, u32 private_stream_id, u32 au_size) const
+{
+ const auto [type_idx, channel] = dmuxPamfStreamIdToTypeChannel(stream_id, private_stream_id);
+
+ if (type_idx == DMUX_PAMF_STREAM_TYPE_INDEX_INVALID || !elementary_stream::is_enabled(elementary_streams[type_idx][channel]))
+ {
+ return false;
+ }
+
+ elementary_streams[type_idx][channel]->release_au(au_size);
+ return true;
+}
+
+bool dmux_pamf_base::flush_es(u32 stream_id, u32 private_stream_id)
+{
+ const auto [type_idx, channel] = dmuxPamfStreamIdToTypeChannel(stream_id, private_stream_id);
+
+ if (type_idx == DMUX_PAMF_STREAM_TYPE_INDEX_INVALID || !elementary_stream::is_enabled(elementary_streams[type_idx][channel]))
+ {
+ return false;
+ }
+
+ state = state::initial;
+ elementary_streams[type_idx][channel]->flush_es();
+ return true;
+}
+
+void dmux_pamf_base::set_stream(std::span stream, bool continuity)
+{
+ if (!continuity)
+ {
+ std::ranges::for_each(elementary_streams | std::views::join | std::views::filter(elementary_stream::is_enabled), &elementary_stream::discard_access_unit);
+ }
+
+ state = state::initial;
+
+ // Not checked on LLE, it would parse old memory contents or uninitialized memory if the size of the input stream set by the user is not a multiple of 0x800.
+ // Valid PAMF streams are always a multiple of 0x800 bytes large.
+ if ((stream.size() & 0x7ff) != 0)
+ {
+ cellDmuxPamf.warning("Invalid stream size");
+ }
+
+ this->stream = stream;
+ demux_done_notified = false;
+}
+
+void dmux_pamf_base::reset_stream()
+{
+ std::ranges::for_each(elementary_streams | std::views::join | std::views::filter(elementary_stream::is_enabled), &elementary_stream::discard_access_unit);
+ state = state::initial;
+ stream.reset();
+}
+
+bool dmux_pamf_base::reset_es(u32 stream_id, u32 private_stream_id, u8* au_addr)
+{
+ const auto [type_idx, channel] = dmuxPamfStreamIdToTypeChannel(stream_id, private_stream_id);
+
+ if (type_idx == DMUX_PAMF_STREAM_TYPE_INDEX_INVALID || !elementary_stream::is_enabled(elementary_streams[type_idx][channel]))
+ {
+ return false;
+ }
+
+ if (!au_addr)
+ {
+ state = state::initial;
+ }
+
+ elementary_streams[type_idx][channel]->reset_es(au_addr);
+ return true;
+}
+
+bool dmux_pamf_base::process_next_pack()
+{
+ if (!stream)
+ {
+ demux_done_notified = demux_done_notified || on_demux_done();
+ return true;
+ }
+
+ switch (state)
+ {
+ case state::initial:
+ {
+ // Search for the next pack start code or prog end code
+ std::span pack;
+
+ for (;;)
+ {
+ if (stream->size() < PACK_STUFFING_LENGTH_OFFSET + sizeof(u8))
+ {
+ stream.reset();
+ demux_done_notified = on_demux_done();
+ return true;
+ }
+
+ pack = stream->first(std::min(stream->size(), PACK_SIZE));
+ stream = stream->subspan(std::min(stream->size(), PACK_SIZE));
+
+ // If the input stream is a raw elementary stream, skip everything MPEG-PS related and go straight to elementary stream parsing
+ if (raw_es)
+ {
+ if (elementary_stream::is_enabled(elementary_streams[pack_es_type_idx][0]))
+ {
+ elementary_streams[pack_es_type_idx][0]->set_pes_packet_data(pack);
+ }
+
+ state = state::elementary_stream;
+ return true;
+ }
+
+ // While LLE is actually searching the entire section for a pack start code or program end code,
+ // it doesn't set its current reading position to the address where it found the code, so it would bug out if there isn't one at the start of the section
+
+ if (const be_t code = read_from_ptr>(pack); code == PACK_START)
+ {
+ break;
+ }
+ else if (code == PROG_END)
+ {
+ if (!on_prog_end())
+ {
+ state = state::prog_end;
+ }
+
+ return true;
+ }
+
+ cellDmuxPamf.warning("No start code found at the beginning of the current section");
+ }
+
+ // Skip over pack header
+ const u8 pack_stuffing_length = read_from_ptr>(pack, PACK_STUFFING_LENGTH_OFFSET);
+
+ // Not checked on LLE, the SPU task would just increment the reading position and read random data in the SPU local store
+ if (PACK_STUFFING_LENGTH_OFFSET + sizeof(u8) + pack_stuffing_length + PES_HEADER_DATA_LENGTH_OFFSET + sizeof(u8) > pack.size())
+ {
+ cellDmuxPamf.error("Invalid pack stuffing length");
+ return false;
+ }
+
+ std::span current_pes_packet = pack.subspan(PACK_STUFFING_LENGTH_OFFSET + sizeof(u8) + pack_stuffing_length);
+
+ if (read_from_ptr, 8, 24>>(current_pes_packet) != PACKET_START_CODE_PREFIX)
+ {
+ cellDmuxPamf.error("Invalid start code after pack header");
+ return false;
+ }
+
+ // Skip over system header if present
+ if (read_from_ptr>(current_pes_packet) == SYSTEM_HEADER)
+ {
+ const u32 system_header_length = read_from_ptr>(current_pes_packet.begin(), PES_PACKET_LENGTH_OFFSET) + PES_PACKET_LENGTH_OFFSET + sizeof(u16);
+
+ // Not checked on LLE, the SPU task would just increment the reading position and read random data in the SPU local store
+ if (system_header_length + PES_HEADER_DATA_LENGTH_OFFSET + sizeof(u8) > current_pes_packet.size())
+ {
+ cellDmuxPamf.error("Invalid system header length");
+ return false;
+ }
+
+ current_pes_packet = current_pes_packet.subspan(system_header_length);
+
+ // The SPU thread isn't doing load + rotate here for 4-byte loading (in valid PAMF streams, the next start code after a system header is always 0x10 byte aligned)
+ const u32 offset_low = (current_pes_packet.data() - pack.data()) & 0xf;
+ current_pes_packet = { current_pes_packet.begin() - offset_low, current_pes_packet.end() };
+
+ if (const be_t code = read_from_ptr>(current_pes_packet); code >> 8 != PACKET_START_CODE_PREFIX)
+ {
+ cellDmuxPamf.error("Invalid start code after system header");
+ return false;
+ }
+ else if (code == PRIVATE_STREAM_2)
+ {
+ // A system header is optionally followed by a private stream 2
+ // The first two bytes of the stream are the stream id of a video stream. The next access unit of that stream is a random access point/keyframe
+
+ const u16 pes_packet_length = read_from_ptr>(current_pes_packet.begin(), PES_PACKET_LENGTH_OFFSET) + PES_PACKET_LENGTH_OFFSET + sizeof(u16);
+
+ // Not checked on LLE, the SPU task would just increment the reading position and read random data in the SPU local store
+ if (pes_packet_length + PES_HEADER_DATA_LENGTH_OFFSET + sizeof(u8) > current_pes_packet.size())
+ {
+ cellDmuxPamf.error("Invalid private stream 2 length");
+ return false;
+ }
+
+ if (const u8 channel = read_from_ptr>(current_pes_packet.begin(), PES_PACKET_LENGTH_OFFSET + sizeof(u16)) & 0xf;
+ elementary_stream::is_enabled(elementary_streams[0][channel]))
+ {
+ elementary_streams[0][channel]->set_rap();
+ }
+
+ current_pes_packet = current_pes_packet.subspan(pes_packet_length);
+ }
+ }
+
+ // Parse PES packet
+ // LLE only parses the first PES packet per pack (valid PAMF streams only have one PES packet per pack, not including the system header + private stream 2)
+
+ const u32 pes_packet_start_code = read_from_ptr>(current_pes_packet);
+
+ if (pes_packet_start_code >> 8 != PACKET_START_CODE_PREFIX)
+ {
+ cellDmuxPamf.error("Invalid start code");
+ return false;
+ }
+
+ const u16 pes_packet_length = read_from_ptr>(current_pes_packet.begin(), PES_PACKET_LENGTH_OFFSET) + PES_PACKET_LENGTH_OFFSET + sizeof(u16);
+ const u8 pes_header_data_length = read_from_ptr(current_pes_packet.begin(), PES_HEADER_DATA_LENGTH_OFFSET) + PES_HEADER_DATA_LENGTH_OFFSET + sizeof(u8);
+
+ // Not checked on LLE, the SPU task would just increment the reading position and read random data in the SPU local store
+ if (pes_packet_length > current_pes_packet.size() || pes_packet_length <= pes_header_data_length)
+ {
+ cellDmuxPamf.error("Invalid pes packet length");
+ return false;
+ }
+
+ const std::span pes_packet_data = current_pes_packet.subspan(pes_header_data_length, pes_packet_length - pes_header_data_length);
+
+ const auto [type_idx, channel] = dmuxPamfStreamIdToTypeChannel(pes_packet_start_code, read_from_ptr(pes_packet_data));
+
+ if (type_idx == DMUX_PAMF_STREAM_TYPE_INDEX_INVALID)
+ {
+ cellDmuxPamf.error("Invalid stream type");
+ return false;
+ }
+
+ pack_es_type_idx = type_idx;
+ pack_es_channel = channel;
+
+ if (elementary_stream::is_enabled(elementary_streams[type_idx][channel]))
+ {
+ const s8 pts_dts_flag = read_from_ptr(current_pes_packet.begin(), PTS_DTS_FLAG_OFFSET);
+
+ if (pts_dts_flag < 0)
+ {
+ // The timestamps should be unsigned, but are sign-extended from s32 to u64 on LLE. They probably forgot about integer promotion
+ const s32 PTS_32_30 = read_from_ptr>(current_pes_packet.begin(), 9);
+ const s32 PTS_29_15 = read_from_ptr