diff --git a/.gitmodules b/.gitmodules index 410b862e5..f11a6079d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -132,3 +132,7 @@ path = rpcs3/3rdparty/GPUOpen/VulkanMemoryAllocator url = ../../Megamouse/VulkanMemoryAllocator.git ignore = dirty +[submodule "3rdparty/json"] + path = 3rdparty/json + url = ../../nlohmann/json.git + ignore = dirty diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 20a18fba0..c6d576a8b 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -76,3 +76,297 @@ import_ffmpeg_library(swscale) import_ffmpeg_library(swresample) import_ffmpeg_library(postproc) +# yaml-cpp +add_subdirectory(yaml-cpp) + + +# OpenGL + +if (NOT ANDROID) + find_package(OpenGL REQUIRED OPTIONAL_COMPONENTS EGL) + + add_library(3rdparty_opengl INTERFACE) + target_include_directories(3rdparty_opengl INTERFACE GL) + + if (WIN32) + if(NOT MSVC) + target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU) + else() + target_link_libraries(3rdparty_opengl INTERFACE dxgi.lib d2d1.lib dwrite.lib) + endif() + elseif(APPLE) + target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU) + else() + target_link_libraries(3rdparty_opengl INTERFACE OpenGL::GL OpenGL::GLU OpenGL::GLX) + endif() +else() + add_library(3rdparty_opengl INTERFACE) + target_compile_definitions(3rdparty_opengl INTERFACE WITHOUT_OPENGL=1) +endif() + +# stblib +add_subdirectory(stblib) + +# DiscordRPC +add_subdirectory(discord-rpc) + +# Cubeb +add_subdirectory(cubeb EXCLUDE_FROM_ALL) + +# SoundTouch +add_subdirectory(SoundTouch EXCLUDE_FROM_ALL) + +# libevdev +set(LIBEVDEV_TARGET 3rdparty_dummy_lib) +if(USE_LIBEVDEV) + pkg_check_modules(LIBEVDEV libevdev libudev) + if(LIBEVDEV_FOUND) + add_library(3rdparty_libevdev INTERFACE) + target_compile_definitions(3rdparty_libevdev INTERFACE -DHAVE_LIBEVDEV) + target_include_directories(3rdparty_libevdev SYSTEM + INTERFACE ${LIBEVDEV_INCLUDE_DIRS}) + target_link_libraries(3rdparty_libevdev INTERFACE ${LIBEVDEV_LDFLAGS}) + + set(LIBEVDEV_TARGET 3rdparty_libevdev) + endif() +endif() + + +# Vulkan +set(VULKAN_TARGET 3rdparty_dummy_lib) +if(USE_VULKAN) + if(APPLE) + if(USE_SYSTEM_MVK) + message(STATUS "RPCS3: Using system MoltenVK") + else() + message(STATUS "RPCS3: MoltenVK submodule") + + execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK" + ) + execute_process(COMMAND "${CMAKE_COMMAND}" --build . + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK" + ) + + add_library(moltenvk_lib SHARED IMPORTED) + add_dependencies(moltenvk_lib moltenvk) + set_target_properties(moltenvk_lib + PROPERTIES IMPORTED_LOCATION "{Vulkan_LIBRARY}" + ) + + set(VULKAN_SDK "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/MoltenVK") + set(VK_ICD_FILENAMES "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json") + set(Vulkan_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/MoltenVK/include") + set(Vulkan_LIBRARY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/Build/Products/Release/dynamic/libMoltenVK.dylib") + set(Vulkan_TOOLS "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/Build/Products/Release") + endif() + endif() + + find_package(Vulkan) + if(VULKAN_FOUND) + add_library(3rdparty_vulkan INTERFACE) + target_compile_definitions(3rdparty_vulkan INTERFACE -DHAVE_VULKAN) + target_link_libraries(3rdparty_vulkan INTERFACE Vulkan::Vulkan) + + if(UNIX AND NOT APPLE AND NOT ANDROID) + find_package(Wayland) + if (WAYLAND_FOUND) + target_include_directories(3rdparty_vulkan + INTERFACE ${WAYLAND_INCLUDE_DIR}) + + target_compile_definitions(3rdparty_vulkan + INTERFACE -DVK_USE_PLATFORM_WAYLAND_KHR) + endif() + endif() + + set(VULKAN_TARGET 3rdparty_vulkan) + else() + message(WARNING "USE_VULKAN was enabled, but libvulkan was not found. RPCS3 will be compiled without Vulkan support.") + if(APPLE) + message(FATAL_ERROR "To build without Vulkan support on macOS, please disable USE_VULKAN.") + endif() + endif() +endif() + +# AsmJit +add_subdirectory(asmjit EXCLUDE_FROM_ALL) + +# OpenAL +if (NOT ANDROID) + add_subdirectory(OpenAL EXCLUDE_FROM_ALL) +else() + add_library(3rdparty_openal INTERFACE) + target_compile_definitions(3rdparty_openal INTERFACE WITHOUT_OPENAL=1) +endif() + +# FAudio +set(FAUDIO_TARGET 3rdparty_dummy_lib) +if(USE_FAUDIO) + # FAudio depends on SDL3 + find_package(SDL3) + if (USE_SYSTEM_FAUDIO) + if (NOT SDL3_FOUND OR SDL3_VERSION VERSION_LESS 3.2.0) + message(WARNING + "RPCS3: System FAudio requires SDL 3.2.0 or newer. Since a valid SDL3" + ">=3.2.0 version cannot be found, building with FAudio will be skipped.") + set(USE_FAUDIO OFF CACHE BOOL "Disabled using system FAudio with SDL < 3.2.0" FORCE) + else() + message(STATUS "RPCS3: Using system FAudio") + find_package(FAudio REQUIRED CONFIGS FAudioConfig.cmake FAudio-config.cmake) + add_library(3rdparty_FAudio INTERFACE) + target_link_libraries(3rdparty_FAudio INTERFACE FAudio) + target_compile_definitions(3rdparty_FAudio INTERFACE -DHAVE_FAUDIO) + set(FAUDIO_TARGET 3rdparty_FAudio) + endif() + else() + if (NOT SDL3_FOUND OR SDL3_VERSION VERSION_LESS 3.2.0) + message(WARNING + "-- RPCS3: 3rdparty FAudio requires SDL 3.2.0 or newer. Since a valid SDL3" + ">=3.2.0 version cannot be found, building with FAudio will be skipped.") + set(USE_FAUDIO OFF CACHE BOOL "Disabled FAudio with SDL < 3.2.0" FORCE) + else() + message(STATUS "RPCS3: Using builtin FAudio") + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared library") + add_subdirectory(FAudio EXCLUDE_FROM_ALL) + target_compile_definitions(FAudio-static INTERFACE -DHAVE_FAUDIO) + set(FAUDIO_TARGET FAudio-static) + endif() + endif() +endif() + +set_property(TARGET ${FAUDIO_TARGET} PROPERTY FOLDER "3rdparty/") + + +# FFMPEG +if(NOT ANDROID) + add_library(3rdparty_ffmpeg INTERFACE) + + # Select the version of ffmpeg to use, default is builtin + if(USE_SYSTEM_FFMPEG) + message(STATUS "RPCS3: using shared ffmpeg") + find_package(FFMPEG REQUIRED) + + target_include_directories(3rdparty_ffmpeg INTERFACE ${FFMPEG_INCLUDE_DIR}) + target_link_libraries(3rdparty_ffmpeg INTERFACE ${FFMPEG_LIBRARIES}) + else() + message(STATUS "RPCS3: using builtin ffmpeg") + add_subdirectory(ffmpeg EXCLUDE_FROM_ALL) + # ffmpeg-core libraries are extracted to CMAKE_BINARY_DIR + set(FFMPEG_LIB_DIR "${CMAKE_BINARY_DIR}/3rdparty/ffmpeg/lib") + + if (WIN32) + target_link_libraries(3rdparty_ffmpeg INTERFACE "Bcrypt.lib") + endif() + + find_library(FFMPEG_LIB_AVFORMAT avformat PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) + find_library(FFMPEG_LIB_AVCODEC avcodec PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) + find_library(FFMPEG_LIB_AVUTIL avutil PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) + find_library(FFMPEG_LIB_SWSCALE swscale PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) + find_library(FFMPEG_LIB_SWRESAMPLE swresample PATHS ${FFMPEG_LIB_DIR} NO_DEFAULT_PATH) + + if (FFMPEG_LIB_AVFORMAT MATCHES "FFMPEG_LIB_AVFORMAT-NOTFOUND") + message(FATAL_ERROR "@#$%! FFMPEG NOT FOUND! ${FFMPEG_LIB_DIR}") + endif() + + target_link_libraries(3rdparty_ffmpeg + INTERFACE + ${FFMPEG_LIB_AVFORMAT} + ${FFMPEG_LIB_AVCODEC} + ${FFMPEG_LIB_AVUTIL} + ${FFMPEG_LIB_SWSCALE} + ${FFMPEG_LIB_SWRESAMPLE} + ) + target_include_directories(3rdparty_ffmpeg INTERFACE "ffmpeg/include") + endif() +endif() + + +# GLEW +add_library(3rdparty_glew INTERFACE) +if(NOT MSVC AND NOT ANDROID) + find_package(GLEW REQUIRED) + target_link_libraries(3rdparty_glew INTERFACE GLEW::GLEW) +endif() + + +# LLVM +add_subdirectory(llvm EXCLUDE_FROM_ALL) + +# WOLFSSL +add_subdirectory(wolfssl EXCLUDE_FROM_ALL) + +# CURL +add_subdirectory(curl EXCLUDE_FROM_ALL) + +# SDL3 +set(SDL3_TARGET 3rdparty_dummy_lib) +if(USE_SDL) + if(USE_SYSTEM_SDL) + find_package(SDL3) + if(SDL3_FOUND AND NOT SDL3_VERSION VERSION_LESS 3.2.0) + message(STATUS "Using system SDL3 version '${SDL3_VERSION}'") + add_library(3rdparty_sdl3 INTERFACE) + target_compile_definitions(3rdparty_sdl3 INTERFACE -DHAVE_SDL3=1) + target_link_libraries(3rdparty_sdl3 INTERFACE SDL3::SDL3) + set(SDL3_TARGET 3rdparty_sdl3) + else() + message(FATAL_ERROR "SDL3 is not available on this system") + endif() + else() + message(STATUS "Using static SDL3 from 3rdparty") + add_library(3rdparty_sdl3 INTERFACE) + target_compile_definitions(3rdparty_sdl3 INTERFACE -DHAVE_SDL3=1) + add_subdirectory(libsdl-org EXCLUDE_FROM_ALL) + set(SDL3_TARGET 3rdparty_sdl3) + endif() +endif() + +# MINIUPNP +add_subdirectory(miniupnp EXCLUDE_FROM_ALL) + +# RTMIDI +add_subdirectory(rtmidi EXCLUDE_FROM_ALL) + +# OPENCV +add_subdirectory(opencv EXCLUDE_FROM_ALL) + +# FUSION +add_subdirectory(fusion EXCLUDE_FROM_ALL) + +# nlohmann json +add_library(3rdparty_json INTERFACE) +target_include_directories(3rdparty_json INTERFACE json/include) + +# add nice ALIAS targets for ease of use +if(USE_SYSTEM_LIBUSB) + add_library(3rdparty::libusb ALIAS usb-1.0-shared) +else() + add_library(3rdparty::libusb ALIAS usb-1.0-static) +endif() +add_library(3rdparty::zlib ALIAS 3rdparty_zlib) +add_library(3rdparty::zstd ALIAS 3rdparty_zstd) +add_library(3rdparty::7zip ALIAS 3rdparty_7zip) +add_library(3rdparty::flatbuffers ALIAS 3rdparty_flatbuffers) +add_library(3rdparty::pugixml ALIAS pugixml) +add_library(3rdparty::glslang ALIAS 3rdparty_glslang) +add_library(3rdparty::yaml-cpp ALIAS yaml-cpp) +add_library(3rdparty::hidapi ALIAS 3rdparty_hidapi) +add_library(3rdparty::libpng ALIAS ${LIBPNG_TARGET}) +add_library(3rdparty::opengl ALIAS 3rdparty_opengl) +add_library(3rdparty::stblib ALIAS 3rdparty_stblib) +add_library(3rdparty::discordRPC ALIAS 3rdparty_discordRPC) +add_library(3rdparty::faudio ALIAS ${FAUDIO_TARGET}) +add_library(3rdparty::libevdev ALIAS ${LIBEVDEV_TARGET}) +add_library(3rdparty::vulkan ALIAS ${VULKAN_TARGET}) +add_library(3rdparty::openal ALIAS 3rdparty_openal) +add_library(3rdparty::ffmpeg ALIAS 3rdparty_ffmpeg) +add_library(3rdparty::glew ALIAS 3rdparty_glew) +add_library(3rdparty::wolfssl ALIAS wolfssl) +add_library(3rdparty::libcurl ALIAS 3rdparty_libcurl) +add_library(3rdparty::soundtouch ALIAS soundtouch) +add_library(3rdparty::sdl3 ALIAS ${SDL3_TARGET}) +add_library(3rdparty::miniupnpc ALIAS libminiupnpc-static) +add_library(3rdparty::rtmidi ALIAS rtmidi) +add_library(3rdparty::opencv ALIAS ${OPENCV_TARGET}) +add_library(3rdparty::fusion ALIAS Fusion) +add_library(3rdparty::json ALIAS 3rdparty_json) diff --git a/rpcs3/Utilities/Config.cpp b/rpcs3/Utilities/Config.cpp index 0c2a1fa2a..d9798e707 100644 --- a/rpcs3/Utilities/Config.cpp +++ b/rpcs3/Utilities/Config.cpp @@ -531,6 +531,54 @@ std::string cfg::node::to_string() const return {out.c_str(), out.size()}; } +nlohmann::json cfg::node::to_json() const +{ + auto result = nlohmann::json::object(); + + for (const auto& node : get_nodes()) + { + result[node->get_name()] = node->to_json(); + } + + return result; +} + +bool cfg::node::from_json(const nlohmann::json &json, bool dynamic) +{ + if (!json.is_object()) + { + return false; + } + + auto find_node = [this](std::string_view name) -> _base * + { + for (const auto& node : get_nodes()) + { + if (node->get_name() == name) + { + return node; + } + } + + return nullptr; + }; + + + for (auto &[key, value] : json.get()) + { + auto keyNode = find_node(key); + + if (keyNode == nullptr || (dynamic && !keyNode->get_is_dynamic())) + { + continue; + } + + keyNode->from_json(value, dynamic); + } + + return false; +} + bool cfg::node::from_string(std::string_view value, bool dynamic) { auto [result, error] = yaml_load(std::string(value)); diff --git a/rpcs3/Utilities/Config.h b/rpcs3/Utilities/Config.h index bd05c2522..09fdb527b 100644 --- a/rpcs3/Utilities/Config.h +++ b/rpcs3/Utilities/Config.h @@ -5,6 +5,7 @@ #include "util/logs.hpp" #include "util/atomic.hpp" #include "util/shared_ptr.hpp" +#include "nlohmann/json.hpp" #include #include @@ -96,6 +97,9 @@ namespace cfg return {}; } + virtual nlohmann::json to_json() const = 0; + virtual bool from_json(const nlohmann::json &, bool dynamic = false) = 0; + // Convert default to string (optional) virtual std::string def_to_string() const { @@ -145,9 +149,11 @@ namespace cfg // Serialize node std::string to_string() const override; + nlohmann::json to_json() const override; // Deserialize node bool from_string(std::string_view value, bool dynamic = false) override; + bool from_json(const nlohmann::json &, bool dynamic = false) override; // Set default values void from_default() override; @@ -184,6 +190,16 @@ namespace cfg return m_value ? "true" : "false"; } + nlohmann::json to_json() const override + { + return + { + {"type", "bool"}, + {"value", m_value.load()}, + {"default", def}, + }; + } + std::string def_to_string() const override { return def ? "true" : "false"; @@ -209,6 +225,17 @@ namespace cfg return true; } + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_boolean()) + { + return false; + } + + m_value = json.get(); + return true; + } + void set(const bool& value) { m_value = value; @@ -263,6 +290,17 @@ namespace cfg return result; // TODO: ??? } + nlohmann::json to_json() const override + { + return + { + {"type", "enum"}, + {"value", to_string()}, + {"default", def_to_string()}, + {"variants", to_list()}, + }; + } + std::string def_to_string() const override { std::string result; @@ -284,6 +322,16 @@ namespace cfg return false; } + bool from_json(const nlohmann::json &json, bool dynamic) override + { + if (!json.is_string()) + { + return false; + } + + return from_string(json.get(), dynamic); + } + std::vector to_list() const override { return try_to_enum_list(&fmt_class_string::format); @@ -335,6 +383,18 @@ namespace cfg return std::to_string(m_value); } + nlohmann::json to_json() const override + { + return + { + {"type", "int"}, + {"value", m_value.load()}, + {"default", def}, + {"min", min}, + {"man", max}, + }; + } + std::string def_to_string() const override { return std::to_string(def); @@ -352,6 +412,23 @@ namespace cfg return false; } + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_number_integer()) + { + return false; + } + + auto value = json.get(); + if (value < min || value > max) + { + return false; + } + + m_value = value; + return true; + } + void set(const s64& value) { ensure(value >= Min && value <= Max); @@ -413,6 +490,18 @@ namespace cfg return "0.0"; } + nlohmann::json to_json() const override + { + return + { + {"type", "float"}, + {"value", m_value.load()}, + {"default", def}, + {"min", min}, + {"man", max}, + }; + } + std::string def_to_string() const override { std::string result; @@ -436,6 +525,23 @@ namespace cfg return false; } + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_number_float()) + { + return false; + } + + auto value = json.get(); + if (value < min || value > max) + { + return false; + } + + m_value = value; + return true; + } + void set(const f64& value) { ensure(value >= Min && value <= Max); @@ -499,6 +605,18 @@ namespace cfg return std::to_string(m_value); } + nlohmann::json to_json() const override + { + return + { + {"type", "uint"}, + {"value", m_value.load()}, + {"default", def}, + {"min", min}, + {"man", max}, + }; + } + std::string def_to_string() const override { return std::to_string(def); @@ -516,6 +634,23 @@ namespace cfg return false; } + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_number_unsigned()) + { + return false; + } + + auto value = json.get(); + if (value < min || value > max) + { + return false; + } + + m_value = value; + return true; + } + void set(const u64& value) { ensure(value >= Min && value <= Max); @@ -561,6 +696,16 @@ namespace cfg return *m_value.load().get(); } + nlohmann::json to_json() const override + { + return + { + {"type", "string"}, + {"value", to_string()}, + {"default", def_to_string()}, + }; + } + std::string def_to_string() const override { return def; @@ -571,6 +716,17 @@ namespace cfg m_value = std::string(value); return true; } + + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_string()) + { + return false; + } + + m_value = json.get(); + return true; + } }; // Simple set entry (TODO: template for various types) @@ -602,12 +758,46 @@ namespace cfg return { m_set.begin(), m_set.end() }; } + nlohmann::json to_json() const override + { + return + { + {"type", "set"}, + {"value", to_list()}, + }; + } + bool from_list(std::vector&& list) override { m_set = { std::make_move_iterator(list.begin()), std::make_move_iterator(list.end()) }; return true; } + + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_array()) + { + return false; + } + + auto array = json.get(); + + std::vector string_array; + string_array.reserve(array.size()); + + for (auto &elem : array) + { + if (!elem.is_string()) + { + return false; + } + + string_array.push_back(elem.get()); + } + + return from_list(std::move(string_array)); + } }; template @@ -628,6 +818,31 @@ namespace cfg return m_map; } + nlohmann::json to_json() const override + { + return + { + {"type", "map"}, + {"value", m_map}, + }; + } + + + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_object()) + { + return false; + } + + for (auto &elem : json.get()) + { + set_value(elem.first, elem.second); + } + + return true; + } + std::string get_value(std::string_view key); void set_value(std::string key, std::string value); @@ -662,6 +877,57 @@ namespace cfg return m_map; } + nlohmann::json to_json() const override + { + auto levels = try_to_enum_list(&fmt_class_string::format); + auto values = nlohmann::json::object(); + for (auto [key, level] : m_map) + { + std::string level_string; + fmt_class_string::format(level_string, fmt_unveil::get(level)); + values[key] = level_string; + } + + return + { + {"type", "log_map"}, + {"values", values}, + {"levels", levels}, + }; + } + + bool from_json(const nlohmann::json &json, bool) override + { + if (!json.is_object()) + { + return false; + } + + for (auto [key, valueString] : json.get()) + { + if (!valueString.is_string()) + { + continue; + } + + logs::level value; + + if (u64 int_value; + try_to_enum_value(&int_value, &fmt_class_string::format, valueString.get())) + { + value = static_cast(static_cast>(int_value)); + } + else + { + continue; + } + + m_map[key] = value; + } + + return true; + } + void set_map(map_of_type&& map); void from_default() override; @@ -674,6 +940,61 @@ namespace cfg std::string vid; std::string pid; std::pair get_usb_ids() const; + + nlohmann::json to_json() const + { + return { + {"path", path}, + {"serial", serial}, + {"vid", vid}, + {"pid", pid}, + }; + } + + bool from_json(const nlohmann::json &json) + { + if (json.contains("path")) + { + if (!json["path"].is_string()) + { + return false; + } + + path = json["path"]; + } + + if (json.contains("serial")) + { + if (!json["serial"].is_string()) + { + return false; + } + + path = json["serial"]; + } + + if (json.contains("vid")) + { + if (!json["vid"].is_string()) + { + return false; + } + + path = json["vid"]; + } + + if (json.contains("pid")) + { + if (!json["pid"].is_string()) + { + return false; + } + + path = json["pid"]; + } + + return true; + } }; class device_entry final : public _base @@ -689,6 +1010,16 @@ namespace cfg m_default = m_map; } + nlohmann::json to_json() const override + { + return {}; + } + + bool from_json(const nlohmann::json &, bool) override + { + return false; + } + const map_of_type& get_map() const { return m_map; diff --git a/rpcs3/rpcs3/Emu/CMakeLists.txt b/rpcs3/rpcs3/Emu/CMakeLists.txt index d68ab8c79..e2f4e320e 100644 --- a/rpcs3/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/rpcs3/Emu/CMakeLists.txt @@ -173,19 +173,12 @@ endif() target_link_libraries(rpcs3_emu PUBLIC - 3rdparty::openal) - -target_link_libraries(rpcs3_emu - PUBLIC - 3rdparty::cubeb) - -target_link_libraries(rpcs3_emu - PUBLIC - 3rdparty::soundtouch) - -target_link_libraries(rpcs3_emu - PUBLIC - 3rdparty::miniupnpc) + 3rdparty::openal + 3rdparty::cubeb + 3rdparty::soundtouch + 3rdparty::miniupnpc + 3rdparty::json +) # Cell target_sources(rpcs3_emu PRIVATE