summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt89
-rw-r--r--externals/libusb/CMakeLists.txt11
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/audio_core/audio_renderer.cpp62
-rw-r--r--src/audio_core/audio_renderer.h6
-rw-r--r--src/audio_core/command_generator.cpp76
-rw-r--r--src/audio_core/command_generator.h8
-rw-r--r--src/audio_core/info_updater.cpp3
-rw-r--r--src/audio_core/voice_context.cpp88
-rw-r--r--src/audio_core/voice_context.h13
-rw-r--r--src/core/file_sys/patch_manager.cpp24
-rw-r--r--src/core/file_sys/patch_manager.h3
-rw-r--r--src/core/file_sys/sdmc_factory.cpp31
-rw-r--r--src/core/file_sys/sdmc_factory.h6
-rw-r--r--src/core/hle/service/audio/audren_u.cpp12
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp24
-rw-r--r--src/core/hle/service/filesystem/filesystem.h1
-rwxr-xr-xsrc/input_common/analog_from_button.cpp1
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt5
-rw-r--r--src/video_core/renderer_base.h2
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp58
-rw-r--r--src/video_core/renderer_opengl/gl_device.h3
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp26
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h4
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h4
-rw-r--r--src/video_core/texture_cache/texture_cache.h3
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp21
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h3
-rw-r--r--src/yuzu/debugger/profiler.cpp9
-rw-r--r--src/yuzu/game_list.cpp12
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/main.cpp25
-rw-r--r--src/yuzu/main.h7
33 files changed, 506 insertions, 142 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 60ec58eda..8b1734f36 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -253,11 +253,82 @@ if(ENABLE_QT)
253 253
254 # Check for system Qt on Linux, fallback to bundled Qt 254 # Check for system Qt on Linux, fallback to bundled Qt
255 if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 255 if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
256 if (NOT YUZU_USE_BUNDLED_QT) 256 find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets)
257 find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets QUIET) 257 if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
258 if (NOT Qt5_FOUND) 258 # Check for dependencies, then enable bundled Qt download
259 set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE) 259
260 # Check that the system GLIBCXX version is compatible
261 find_program(OBJDUMP objdump)
262 if ("${OBJDUMP}" STREQUAL "OBJDUMP-NOTFOUND")
263 message(FATAL_ERROR "Required program `objdump` not found.")
260 endif() 264 endif()
265 find_library(LIBSTDCXX libstdc++.so.6)
266 execute_process(
267 COMMAND
268 ${OBJDUMP} -T ${LIBSTDCXX}
269 COMMAND
270 grep GLIBCXX_3.4.28
271 COMMAND
272 sed "s/[0-9a-f]*.* //"
273 COMMAND
274 sed "s/ .*//"
275 COMMAND
276 sort -u
277 OUTPUT_VARIABLE
278 GLIBCXX_MET
279 )
280 if (NOT GLIBCXX_MET)
281 message(FATAL_ERROR "Qt too old or not found, and bundled Qt package is not \
282 compatible with this system. Either install Qt ${QT_VERSION}, or provide the path \
283 to Qt by setting the variable Qt5_ROOT.")
284 endif()
285
286 # Check for headers
287 Include(FindPkgConfig REQUIRED)
288 pkg_check_modules(QT_DEP_GLU QUIET glu>=9.0.0)
289 if (NOT QT_DEP_GLU_FOUND)
290 message(FATAL_ERROR "Qt bundled pacakge dependency `glu` not found. \
291 Perhaps `libglu1-mesa-dev` needs to be installed?")
292 endif()
293 pkg_check_modules(QT_DEP_MESA QUIET dri>=20.0.8)
294 if (NOT QT_DEP_MESA_FOUND)
295 message(FATAL_ERROR "Qt bundled pacakge dependency `dri` not found. \
296 Perhaps `mesa-common-dev` needs to be installed?")
297 endif()
298
299 # Check for X libraries
300 set(BUNDLED_QT_REQUIREMENTS
301 libxcb-icccm.so.4
302 libxcb-image.so.0
303 libxcb-keysyms.so.1
304 libxcb-randr.so.0
305 libxcb-render-util.so.0
306 libxcb-render.so.0
307 libxcb-shape.so.0
308 libxcb-shm.so.0
309 libxcb-sync.so.1
310 libxcb-xfixes.so.0
311 libxcb-xinerama.so.0
312 libxcb-xkb.so.1
313 libxcb.so.1
314 libxkbcommon-x11.so.0
315 libxkbcommon.so.0
316 )
317 set(UNRESOLVED_QT_DEPS "")
318 foreach (REQUIREMENT ${BUNDLED_QT_REQUIREMENTS})
319 find_library(BUNDLED_QT_${REQUIREMENT} ${REQUIREMENT})
320 if ("${BUNDLED_QT_${REQUIREMENT}}" STREQUAL "BUNDLED_QT_${REQUIREMENT}-NOTFOUND")
321 set(UNRESOLVED_QT_DEPS ${UNRESOLVED_QT_DEPS} ${REQUIREMENT})
322 endif()
323 unset(BUNDLED_QT_${REQUIREMENT})
324 endforeach()
325 unset(BUNDLED_QT_REQUIREMENTS)
326
327 if (NOT "${UNRESOLVED_QT_DEPS}" STREQUAL "")
328 message(FATAL_ERROR "Bundled Qt package missing required dependencies: ${UNRESOLVED_QT_DEPS}")
329 endif()
330
331 set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE)
261 endif() 332 endif()
262 if (YUZU_USE_BUNDLED_QT) 333 if (YUZU_USE_BUNDLED_QT)
263 # Binary package currently does not support Qt webengine, so make sure it's disabled 334 # Binary package currently does not support Qt webengine, so make sure it's disabled
@@ -473,7 +544,15 @@ if (YUZU_USE_BUNDLED_FFMPEG)
473 544
474 # FFmpeg has source that requires one of nasm or yasm to assemble it. 545 # FFmpeg has source that requires one of nasm or yasm to assemble it.
475 # REQUIRED throws an error if not found here during configuration rather than during compilation. 546 # REQUIRED throws an error if not found here during configuration rather than during compilation.
476 find_program(ASSEMBLER NAMES nasm yasm REQUIRED) 547 find_program(ASSEMBLER NAMES nasm yasm)
548 if ("${ASSEMBLER}" STREQUAL "ASSEMBLER-NOTFOUND")
549 message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
550 endif()
551
552 find_program(AUTOCONF autoconf)
553 if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
554 message(FATAL_ERROR "Required program `autoconf` not found.")
555 endif()
477 556
478 set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg) 557 set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg)
479 set(FFmpeg_BUILD_DIR ${PROJECT_BINARY_DIR}/externals/ffmpeg) 558 set(FFmpeg_BUILD_DIR ${PROJECT_BINARY_DIR}/externals/ffmpeg)
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt
index 8b9e6433c..151ddc462 100644
--- a/externals/libusb/CMakeLists.txt
+++ b/externals/libusb/CMakeLists.txt
@@ -5,6 +5,17 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
5 # GNU toolchains for some reason doesn't work with the later half of this CMakeLists after 5 # GNU toolchains for some reason doesn't work with the later half of this CMakeLists after
6 # updating to 1.0.24, so we do it the old-fashioned way for now. 6 # updating to 1.0.24, so we do it the old-fashioned way for now.
7 7
8 # Require autoconf and libtoolize here, rather than crash during compilation
9 find_program(AUTOCONF autoconf)
10 if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
11 message(FATAL_ERROR "Required program `autoconf` not found.")
12 endif()
13
14 find_program(LIBTOOLIZE libtoolize)
15 if ("${LIBTOOLIZE}" STREQUAL "LIBTOOLIZE-NOTFOUND")
16 message(FATAL_ERROR "Required program `libtoolize` not found.")
17 endif()
18
8 set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb") 19 set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb")
9 set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb") 20 set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb")
10 21
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f30dd49a3..1c3dde31d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -49,6 +49,7 @@ if (MSVC)
49 /W3 49 /W3
50 /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled 50 /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled
51 /we4101 # 'identifier': unreferenced local variable 51 /we4101 # 'identifier': unreferenced local variable
52 /we4189 # 'identifier': local variable is initialized but not referenced
52 /we4265 # 'class': class has virtual functions, but destructor is not virtual 53 /we4265 # 'class': class has virtual functions, but destructor is not virtual
53 /we4388 # signed/unsigned mismatch 54 /we4388 # signed/unsigned mismatch
54 /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect 55 /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 80ffddb10..ccd5ca6cc 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -12,6 +12,7 @@
12#include "audio_core/voice_context.h" 12#include "audio_core/voice_context.h"
13#include "common/logging/log.h" 13#include "common/logging/log.h"
14#include "common/settings.h" 14#include "common/settings.h"
15#include "core/core_timing.h"
15#include "core/memory.h" 16#include "core/memory.h"
16 17
17namespace { 18namespace {
@@ -68,7 +69,9 @@ namespace {
68} // namespace 69} // namespace
69 70
70namespace AudioCore { 71namespace AudioCore {
71AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, 72constexpr s32 NUM_BUFFERS = 2;
73
74AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
72 AudioCommon::AudioRendererParameter params, 75 AudioCommon::AudioRendererParameter params,
73 Stream::ReleaseCallback&& release_callback, 76 Stream::ReleaseCallback&& release_callback,
74 std::size_t instance_number) 77 std::size_t instance_number)
@@ -77,7 +80,8 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
77 sink_context(params.sink_count), splitter_context(), 80 sink_context(params.sink_count), splitter_context(),
78 voices(params.voice_count), memory{memory_}, 81 voices(params.voice_count), memory{memory_},
79 command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, 82 command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
80 memory) { 83 memory),
84 core_timing{core_timing_} {
81 behavior_info.SetUserRevision(params.revision); 85 behavior_info.SetUserRevision(params.revision);
82 splitter_context.Initialize(behavior_info, params.splitter_count, 86 splitter_context.Initialize(behavior_info, params.splitter_count,
83 params.num_splitter_send_channels); 87 params.num_splitter_send_channels);
@@ -86,16 +90,27 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
86 stream = audio_out->OpenStream( 90 stream = audio_out->OpenStream(
87 core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, 91 core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
88 fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); 92 fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
89 audio_out->StartStream(stream); 93 process_event = Core::Timing::CreateEvent(
90 94 fmt::format("AudioRenderer-Instance{}-Process", instance_number),
91 QueueMixedBuffer(0); 95 [this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); });
92 QueueMixedBuffer(1); 96 for (s32 i = 0; i < NUM_BUFFERS; ++i) {
93 QueueMixedBuffer(2); 97 QueueMixedBuffer(i);
94 QueueMixedBuffer(3); 98 }
95} 99}
96 100
97AudioRenderer::~AudioRenderer() = default; 101AudioRenderer::~AudioRenderer() = default;
98 102
103ResultCode AudioRenderer::Start() {
104 audio_out->StartStream(stream);
105 ReleaseAndQueueBuffers();
106 return ResultSuccess;
107}
108
109ResultCode AudioRenderer::Stop() {
110 audio_out->StopStream(stream);
111 return ResultSuccess;
112}
113
99u32 AudioRenderer::GetSampleRate() const { 114u32 AudioRenderer::GetSampleRate() const {
100 return worker_params.sample_rate; 115 return worker_params.sample_rate;
101} 116}
@@ -114,7 +129,7 @@ Stream::State AudioRenderer::GetStreamState() const {
114 129
115ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, 130ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
116 std::vector<u8>& output_params) { 131 std::vector<u8>& output_params) {
117 132 std::scoped_lock lock{mutex};
118 InfoUpdater info_updater{input_params, output_params, behavior_info}; 133 InfoUpdater info_updater{input_params, output_params, behavior_info};
119 134
120 if (!info_updater.UpdateBehaviorInfo(behavior_info)) { 135 if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
@@ -194,9 +209,6 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_param
194 LOG_ERROR(Audio, "Audio buffers were not consumed!"); 209 LOG_ERROR(Audio, "Audio buffers were not consumed!");
195 return AudioCommon::Audren::ERR_INVALID_PARAMETERS; 210 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
196 } 211 }
197
198 ReleaseAndQueueBuffers();
199
200 return ResultSuccess; 212 return ResultSuccess;
201} 213}
202 214
@@ -220,10 +232,8 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
220 command_generator.PostCommand(); 232 command_generator.PostCommand();
221 // Base sample size 233 // Base sample size
222 std::size_t BUFFER_SIZE{worker_params.sample_count}; 234 std::size_t BUFFER_SIZE{worker_params.sample_count};
223 // Samples 235 // Samples, making sure to clear
224 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); 236 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0);
225 // Make sure to clear our samples
226 std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
227 237
228 if (sink_context.InUse()) { 238 if (sink_context.InUse()) {
229 const auto stream_channel_count = stream->GetNumChannels(); 239 const auto stream_channel_count = stream->GetNumChannels();
@@ -315,10 +325,24 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
315} 325}
316 326
317void AudioRenderer::ReleaseAndQueueBuffers() { 327void AudioRenderer::ReleaseAndQueueBuffers() {
318 const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; 328 if (!stream->IsPlaying()) {
319 for (const auto& tag : released_buffers) { 329 return;
320 QueueMixedBuffer(tag);
321 } 330 }
331
332 {
333 std::scoped_lock lock{mutex};
334 const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
335 for (const auto& tag : released_buffers) {
336 QueueMixedBuffer(tag);
337 }
338 }
339
340 const f32 sample_rate = static_cast<f32>(GetSampleRate());
341 const f32 sample_count = static_cast<f32>(GetSampleCount());
342 const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
343 const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
344 const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
345 core_timing.ScheduleEvent(next_event_time, process_event, {});
322} 346}
323 347
324} // namespace AudioCore 348} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index 18567f618..88fdd13dd 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <memory> 8#include <memory>
9#include <mutex>
9#include <vector> 10#include <vector>
10 11
11#include "audio_core/behavior_info.h" 12#include "audio_core/behavior_info.h"
@@ -45,6 +46,8 @@ public:
45 46
46 [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params, 47 [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
47 std::vector<u8>& output_params); 48 std::vector<u8>& output_params);
49 [[nodiscard]] ResultCode Start();
50 [[nodiscard]] ResultCode Stop();
48 void QueueMixedBuffer(Buffer::Tag tag); 51 void QueueMixedBuffer(Buffer::Tag tag);
49 void ReleaseAndQueueBuffers(); 52 void ReleaseAndQueueBuffers();
50 [[nodiscard]] u32 GetSampleRate() const; 53 [[nodiscard]] u32 GetSampleRate() const;
@@ -68,6 +71,9 @@ private:
68 Core::Memory::Memory& memory; 71 Core::Memory::Memory& memory;
69 CommandGenerator command_generator; 72 CommandGenerator command_generator;
70 std::size_t elapsed_frame_count{}; 73 std::size_t elapsed_frame_count{};
74 Core::Timing::CoreTiming& core_timing;
75 std::shared_ptr<Core::Timing::EventType> process_event;
76 std::mutex mutex;
71}; 77};
72 78
73} // namespace AudioCore 79} // namespace AudioCore
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
index 437cc5ccd..27437f1ea 100644
--- a/src/audio_core/command_generator.cpp
+++ b/src/audio_core/command_generator.cpp
@@ -795,7 +795,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta
795 state.lowpass_1 = 0.0f; 795 state.lowpass_1 = 0.0f;
796 } else { 796 } else {
797 const auto a = 1.0f - hf_gain; 797 const auto a = 1.0f - hf_gain;
798 const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference / 798 const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference /
799 static_cast<f32>(info.sample_rate))); 799 static_cast<f32>(info.sample_rate)));
800 const auto c = std::sqrt(b * b - 4.0f * a * a); 800 const auto c = std::sqrt(b * b - 4.0f * a * a);
801 801
@@ -843,7 +843,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta
843 } 843 }
844 844
845 const auto max_early_delay = state.early_delay_line.GetMaxDelay(); 845 const auto max_early_delay = state.early_delay_line.GetMaxDelay();
846 const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f); 846 const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f);
847 for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { 847 for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
848 const auto length = AudioCommon::CalculateDelaySamples( 848 const auto length = AudioCommon::CalculateDelaySamples(
849 sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); 849 sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
@@ -1004,7 +1004,8 @@ void CommandGenerator::GenerateFinalMixCommand() {
1004} 1004}
1005 1005
1006s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, 1006s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
1007 s32 sample_count, s32 channel, std::size_t mix_offset) { 1007 s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
1008 s32 channel, std::size_t mix_offset) {
1008 const auto& in_params = voice_info.GetInParams(); 1009 const auto& in_params = voice_info.GetInParams();
1009 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; 1010 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
1010 if (wave_buffer.buffer_address == 0) { 1011 if (wave_buffer.buffer_address == 0) {
@@ -1013,14 +1014,12 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s
1013 if (wave_buffer.buffer_size == 0) { 1014 if (wave_buffer.buffer_size == 0) {
1014 return 0; 1015 return 0;
1015 } 1016 }
1016 if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { 1017 if (sample_end_offset < sample_start_offset) {
1017 return 0; 1018 return 0;
1018 } 1019 }
1019 const auto samples_remaining = 1020 const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
1020 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
1021 const auto start_offset = 1021 const auto start_offset =
1022 ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) * 1022 ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(s16);
1023 sizeof(s16);
1024 const auto buffer_pos = wave_buffer.buffer_address + start_offset; 1023 const auto buffer_pos = wave_buffer.buffer_address + start_offset;
1025 const auto samples_processed = std::min(sample_count, samples_remaining); 1024 const auto samples_processed = std::min(sample_count, samples_remaining);
1026 1025
@@ -1044,8 +1043,8 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s
1044} 1043}
1045 1044
1046s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, 1045s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
1047 s32 sample_count, [[maybe_unused]] s32 channel, 1046 s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
1048 std::size_t mix_offset) { 1047 [[maybe_unused]] s32 channel, std::size_t mix_offset) {
1049 const auto& in_params = voice_info.GetInParams(); 1048 const auto& in_params = voice_info.GetInParams();
1050 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; 1049 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
1051 if (wave_buffer.buffer_address == 0) { 1050 if (wave_buffer.buffer_address == 0) {
@@ -1054,7 +1053,7 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
1054 if (wave_buffer.buffer_size == 0) { 1053 if (wave_buffer.buffer_size == 0) {
1055 return 0; 1054 return 0;
1056 } 1055 }
1057 if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { 1056 if (sample_end_offset < sample_start_offset) {
1058 return 0; 1057 return 0;
1059 } 1058 }
1060 1059
@@ -1079,10 +1078,9 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
1079 s32 coef1 = coeffs[idx * 2]; 1078 s32 coef1 = coeffs[idx * 2];
1080 s32 coef2 = coeffs[idx * 2 + 1]; 1079 s32 coef2 = coeffs[idx * 2 + 1];
1081 1080
1082 const auto samples_remaining = 1081 const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
1083 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
1084 const auto samples_processed = std::min(sample_count, samples_remaining); 1082 const auto samples_processed = std::min(sample_count, samples_remaining);
1085 const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset; 1083 const auto sample_pos = dsp_state.offset + sample_start_offset;
1086 1084
1087 const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; 1085 const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
1088 auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + 1086 auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
@@ -1210,9 +1208,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
1210 } 1208 }
1211 1209
1212 std::size_t temp_mix_offset{}; 1210 std::size_t temp_mix_offset{};
1213 bool is_buffer_completed{false};
1214 auto samples_remaining = sample_count; 1211 auto samples_remaining = sample_count;
1215 while (samples_remaining > 0 && !is_buffer_completed) { 1212 while (samples_remaining > 0) {
1216 const auto samples_to_output = std::min(samples_remaining, min_required_samples); 1213 const auto samples_to_output = std::min(samples_remaining, min_required_samples);
1217 const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; 1214 const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
1218 1215
@@ -1229,24 +1226,38 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
1229 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; 1226 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
1230 // No more data can be read 1227 // No more data can be read
1231 if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { 1228 if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
1232 is_buffer_completed = true;
1233 break; 1229 break;
1234 } 1230 }
1235 1231
1236 if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && 1232 if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
1237 wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { 1233 wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
1238 // TODO(ogniK): ADPCM loop context 1234 memory.ReadBlock(wave_buffer.context_address, &dsp_state.context,
1235 sizeof(ADPCMContext));
1236 }
1237
1238 s32 samples_offset_start;
1239 s32 samples_offset_end;
1240 if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 &&
1241 wave_buffer.loop_end_sample != 0 &&
1242 wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) {
1243 samples_offset_start = wave_buffer.loop_start_sample;
1244 samples_offset_end = wave_buffer.loop_end_sample;
1245 } else {
1246 samples_offset_start = wave_buffer.start_sample_offset;
1247 samples_offset_end = wave_buffer.end_sample_offset;
1239 } 1248 }
1240 1249
1241 s32 samples_decoded{0}; 1250 s32 samples_decoded{0};
1242 switch (in_params.sample_format) { 1251 switch (in_params.sample_format) {
1243 case SampleFormat::Pcm16: 1252 case SampleFormat::Pcm16:
1244 samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read, 1253 samples_decoded =
1245 channel, temp_mix_offset); 1254 DecodePcm16(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1255 samples_to_read - samples_read, channel, temp_mix_offset);
1246 break; 1256 break;
1247 case SampleFormat::Adpcm: 1257 case SampleFormat::Adpcm:
1248 samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read, 1258 samples_decoded =
1249 channel, temp_mix_offset); 1259 DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1260 samples_to_read - samples_read, channel, temp_mix_offset);
1250 break; 1261 break;
1251 default: 1262 default:
1252 UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); 1263 UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
@@ -1257,15 +1268,19 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
1257 dsp_state.offset += samples_decoded; 1268 dsp_state.offset += samples_decoded;
1258 dsp_state.played_sample_count += samples_decoded; 1269 dsp_state.played_sample_count += samples_decoded;
1259 1270
1260 if (dsp_state.offset >= 1271 if (dsp_state.offset >= (samples_offset_end - samples_offset_start) ||
1261 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
1262 samples_decoded == 0) { 1272 samples_decoded == 0) {
1263 // Reset our sample offset 1273 // Reset our sample offset
1264 dsp_state.offset = 0; 1274 dsp_state.offset = 0;
1265 if (wave_buffer.is_looping) { 1275 if (wave_buffer.is_looping) {
1266 if (samples_decoded == 0) { 1276 dsp_state.loop_count++;
1277 if (wave_buffer.loop_count > 0 &&
1278 (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) {
1267 // End of our buffer 1279 // End of our buffer
1268 is_buffer_completed = true; 1280 voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
1281 }
1282
1283 if (samples_decoded == 0) {
1269 break; 1284 break;
1270 } 1285 }
1271 1286
@@ -1273,15 +1288,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
1273 dsp_state.played_sample_count = 0; 1288 dsp_state.played_sample_count = 0;
1274 } 1289 }
1275 } else { 1290 } else {
1276
1277 // Update our wave buffer states 1291 // Update our wave buffer states
1278 dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; 1292 voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
1279 dsp_state.wave_buffer_consumed++;
1280 dsp_state.wave_buffer_index =
1281 (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
1282 if (wave_buffer.end_of_stream) {
1283 dsp_state.played_sample_count = 0;
1284 }
1285 } 1293 }
1286 } 1294 }
1287 } 1295 }
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
index 2ebb755b0..673e4fbef 100644
--- a/src/audio_core/command_generator.h
+++ b/src/audio_core/command_generator.h
@@ -86,10 +86,10 @@ private:
86 std::vector<u8>& work_buffer); 86 std::vector<u8>& work_buffer);
87 void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); 87 void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
88 // DSP Code 88 // DSP Code
89 s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, 89 s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
90 s32 channel, std::size_t mix_offset); 90 s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
91 s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, 91 s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
92 s32 channel, std::size_t mix_offset); 92 s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
93 void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, 93 void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
94 s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); 94 s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
95 95
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
index 4a5b1b4ab..9b4ca1851 100644
--- a/src/audio_core/info_updater.cpp
+++ b/src/audio_core/info_updater.cpp
@@ -189,9 +189,6 @@ bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
189 if (voice_in_params.is_new) { 189 if (voice_in_params.is_new) {
190 // Default our values for our voice 190 // Default our values for our voice
191 voice_info.Initialize(); 191 voice_info.Initialize();
192 if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
193 continue;
194 }
195 192
196 // Zero out our voice states 193 // Zero out our voice states
197 for (std::size_t channel = 0; channel < channel_count; channel++) { 194 for (std::size_t channel = 0; channel < channel_count; channel++) {
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
index 867b8fc6b..d8c954b60 100644
--- a/src/audio_core/voice_context.cpp
+++ b/src/audio_core/voice_context.cpp
@@ -66,7 +66,7 @@ void ServerVoiceInfo::Initialize() {
66 in_params.last_volume = 0.0f; 66 in_params.last_volume = 0.0f;
67 in_params.biquad_filter.fill({}); 67 in_params.biquad_filter.fill({});
68 in_params.wave_buffer_count = 0; 68 in_params.wave_buffer_count = 0;
69 in_params.wave_bufffer_head = 0; 69 in_params.wave_buffer_head = 0;
70 in_params.mix_id = AudioCommon::NO_MIX; 70 in_params.mix_id = AudioCommon::NO_MIX;
71 in_params.splitter_info_id = AudioCommon::NO_SPLITTER; 71 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
72 in_params.additional_params_address = 0; 72 in_params.additional_params_address = 0;
@@ -75,7 +75,7 @@ void ServerVoiceInfo::Initialize() {
75 out_params.played_sample_count = 0; 75 out_params.played_sample_count = 0;
76 out_params.wave_buffer_consumed = 0; 76 out_params.wave_buffer_consumed = 0;
77 in_params.voice_drop_flag = false; 77 in_params.voice_drop_flag = false;
78 in_params.buffer_mapped = false; 78 in_params.buffer_mapped = true;
79 in_params.wave_buffer_flush_request_count = 0; 79 in_params.wave_buffer_flush_request_count = 0;
80 in_params.was_biquad_filter_enabled.fill(false); 80 in_params.was_biquad_filter_enabled.fill(false);
81 81
@@ -126,7 +126,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
126 in_params.volume = voice_in.volume; 126 in_params.volume = voice_in.volume;
127 in_params.biquad_filter = voice_in.biquad_filter; 127 in_params.biquad_filter = voice_in.biquad_filter;
128 in_params.wave_buffer_count = voice_in.wave_buffer_count; 128 in_params.wave_buffer_count = voice_in.wave_buffer_count;
129 in_params.wave_bufffer_head = voice_in.wave_buffer_head; 129 in_params.wave_buffer_head = voice_in.wave_buffer_head;
130 if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { 130 if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
131 const auto in_request_count = in_params.wave_buffer_flush_request_count; 131 const auto in_request_count = in_params.wave_buffer_flush_request_count;
132 const auto voice_request_count = voice_in.wave_buffer_flush_request_count; 132 const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
@@ -185,14 +185,16 @@ void ServerVoiceInfo::UpdateWaveBuffers(
185 wave_buffer.buffer_size = 0; 185 wave_buffer.buffer_size = 0;
186 wave_buffer.context_address = 0; 186 wave_buffer.context_address = 0;
187 wave_buffer.context_size = 0; 187 wave_buffer.context_size = 0;
188 wave_buffer.loop_start_sample = 0;
189 wave_buffer.loop_end_sample = 0;
188 wave_buffer.sent_to_dsp = true; 190 wave_buffer.sent_to_dsp = true;
189 } 191 }
190 192
191 // Mark all our wave buffers as invalid 193 // Mark all our wave buffers as invalid
192 for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count); 194 for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
193 channel++) { 195 channel++) {
194 for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) { 196 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) {
195 is_valid = false; 197 voice_states[channel]->is_wave_buffer_valid[i] = false;
196 } 198 }
197 } 199 }
198 } 200 }
@@ -211,7 +213,7 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
211 const WaveBuffer& in_wave_buffer, SampleFormat sample_format, 213 const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
212 bool is_buffer_valid, 214 bool is_buffer_valid,
213 [[maybe_unused]] BehaviorInfo& behavior_info) { 215 [[maybe_unused]] BehaviorInfo& behavior_info) {
214 if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) { 216 if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) {
215 out_wavebuffer.buffer_address = 0; 217 out_wavebuffer.buffer_address = 0;
216 out_wavebuffer.buffer_size = 0; 218 out_wavebuffer.buffer_size = 0;
217 } 219 }
@@ -219,11 +221,40 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
219 if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { 221 if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
220 // Validate sample offset sizings 222 // Validate sample offset sizings
221 if (sample_format == SampleFormat::Pcm16) { 223 if (sample_format == SampleFormat::Pcm16) {
222 const auto buffer_size = in_wave_buffer.buffer_size; 224 const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
223 if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 || 225 const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset;
224 (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) || 226 const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset;
225 (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) { 227 if (0 > start || start > buffer_size || 0 > end || end > buffer_size) {
226 // TODO(ogniK): Write error info 228 // TODO(ogniK): Write error info
229 LOG_ERROR(Audio,
230 "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
231 "offsets were "
232 "{:08X} - 0x{:08X}",
233 buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset,
234 sizeof(s16) * in_wave_buffer.end_sample_offset);
235 return;
236 }
237 } else if (sample_format == SampleFormat::Adpcm) {
238 const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
239 const s64 start_frames = in_wave_buffer.start_sample_offset / 14;
240 const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0
241 ? 0
242 : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 +
243 (in_wave_buffer.start_sample_offset % 2);
244 const s64 start = start_frames * 8 + start_extra;
245 const s64 end_frames = in_wave_buffer.end_sample_offset / 14;
246 const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0
247 ? 0
248 : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 +
249 (in_wave_buffer.end_sample_offset % 2);
250 const s64 end = end_frames * 8 + end_extra;
251 if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size ||
252 in_wave_buffer.end_sample_offset < 0 || end > buffer_size) {
253 LOG_ERROR(Audio,
254 "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
255 "offsets were "
256 "{:08X} - 0x{:08X}",
257 in_wave_buffer.buffer_size, start, end);
227 return; 258 return;
228 } 259 }
229 } 260 }
@@ -239,29 +270,34 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
239 out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; 270 out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
240 out_wavebuffer.context_address = in_wave_buffer.context_address; 271 out_wavebuffer.context_address = in_wave_buffer.context_address;
241 out_wavebuffer.context_size = in_wave_buffer.context_size; 272 out_wavebuffer.context_size = in_wave_buffer.context_size;
273 out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample;
274 out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample;
242 in_params.buffer_mapped = 275 in_params.buffer_mapped =
243 in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; 276 in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
244 // TODO(ogniK): Pool mapper attachment 277 // TODO(ogniK): Pool mapper attachment
245 // TODO(ogniK): IsAdpcmLoopContextBugFixed 278 // TODO(ogniK): IsAdpcmLoopContextBugFixed
279 if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 &&
280 in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) {
281 } else {
282 out_wavebuffer.context_address = 0;
283 out_wavebuffer.context_size = 0;
284 }
246 } 285 }
247} 286}
248 287
249void ServerVoiceInfo::WriteOutStatus( 288void ServerVoiceInfo::WriteOutStatus(
250 VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, 289 VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
251 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) { 290 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
252 if (voice_in.is_new) { 291 if (voice_in.is_new || in_params.is_new) {
253 in_params.is_new = true; 292 in_params.is_new = true;
254 voice_out.wave_buffer_consumed = 0; 293 voice_out.wave_buffer_consumed = 0;
255 voice_out.played_sample_count = 0; 294 voice_out.played_sample_count = 0;
256 voice_out.voice_dropped = false; 295 voice_out.voice_dropped = false;
257 } else if (!in_params.is_new) {
258 voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
259 voice_out.played_sample_count = voice_states[0]->played_sample_count;
260 voice_out.voice_dropped = in_params.voice_drop_flag;
261 } else { 296 } else {
262 voice_out.wave_buffer_consumed = 0; 297 const auto& state = voice_states[0];
263 voice_out.played_sample_count = 0; 298 voice_out.wave_buffer_consumed = state->wave_buffer_consumed;
264 voice_out.voice_dropped = false; 299 voice_out.played_sample_count = state->played_sample_count;
300 voice_out.voice_dropped = state->voice_dropped;
265 } 301 }
266} 302}
267 303
@@ -283,7 +319,8 @@ ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
283 319
284bool ServerVoiceInfo::ShouldSkip() const { 320bool ServerVoiceInfo::ShouldSkip() const {
285 // TODO(ogniK): Handle unmapped wave buffers or parameters 321 // TODO(ogniK): Handle unmapped wave buffers or parameters
286 return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag; 322 return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped ||
323 in_params.voice_drop_flag;
287} 324}
288 325
289bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { 326bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
@@ -381,7 +418,7 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
381void ServerVoiceInfo::FlushWaveBuffers( 418void ServerVoiceInfo::FlushWaveBuffers(
382 u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, 419 u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
383 s32 channel_count) { 420 s32 channel_count) {
384 auto wave_head = in_params.wave_bufffer_head; 421 auto wave_head = in_params.wave_buffer_head;
385 422
386 for (u8 i = 0; i < flush_count; i++) { 423 for (u8 i = 0; i < flush_count; i++) {
387 in_params.wave_buffer[wave_head].sent_to_dsp = true; 424 in_params.wave_buffer[wave_head].sent_to_dsp = true;
@@ -401,6 +438,17 @@ bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
401 return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); 438 return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
402} 439}
403 440
441void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state,
442 const ServerWaveBuffer& wave_buffer) {
443 dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
444 dsp_state.wave_buffer_consumed++;
445 dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
446 dsp_state.loop_count = 0;
447 if (wave_buffer.end_of_stream) {
448 dsp_state.played_sample_count = 0;
449 }
450}
451
404VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { 452VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} {
405 for (std::size_t i = 0; i < voice_count; i++) { 453 for (std::size_t i = 0; i < voice_count; i++) {
406 voice_channel_resources.emplace_back(static_cast<s32>(i)); 454 voice_channel_resources.emplace_back(static_cast<s32>(i));
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h
index 70359cadb..e1050897b 100644
--- a/src/audio_core/voice_context.h
+++ b/src/audio_core/voice_context.h
@@ -60,10 +60,12 @@ struct WaveBuffer {
60 u8 is_looping{}; 60 u8 is_looping{};
61 u8 end_of_stream{}; 61 u8 end_of_stream{};
62 u8 sent_to_server{}; 62 u8 sent_to_server{};
63 INSERT_PADDING_BYTES(5); 63 INSERT_PADDING_BYTES(1);
64 s32 loop_count{};
64 u64 context_address{}; 65 u64 context_address{};
65 u64 context_size{}; 66 u64 context_size{};
66 INSERT_PADDING_BYTES(8); 67 u32 loop_start_sample{};
68 u32 loop_end_sample{};
67}; 69};
68static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); 70static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
69 71
@@ -76,6 +78,9 @@ struct ServerWaveBuffer {
76 bool end_of_stream{}; 78 bool end_of_stream{};
77 VAddr context_address{}; 79 VAddr context_address{};
78 std::size_t context_size{}; 80 std::size_t context_size{};
81 s32 loop_count{};
82 u32 loop_start_sample{};
83 u32 loop_end_sample{};
79 bool sent_to_dsp{true}; 84 bool sent_to_dsp{true};
80}; 85};
81 86
@@ -108,6 +113,7 @@ struct VoiceState {
108 u32 external_context_size; 113 u32 external_context_size;
109 bool is_external_context_used; 114 bool is_external_context_used;
110 bool voice_dropped; 115 bool voice_dropped;
116 s32 loop_count;
111}; 117};
112 118
113class VoiceChannelResource { 119class VoiceChannelResource {
@@ -206,7 +212,7 @@ public:
206 float last_volume{}; 212 float last_volume{};
207 std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{}; 213 std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
208 s32 wave_buffer_count{}; 214 s32 wave_buffer_count{};
209 s16 wave_bufffer_head{}; 215 s16 wave_buffer_head{};
210 INSERT_PADDING_BYTES(2); 216 INSERT_PADDING_BYTES(2);
211 BehaviorFlags behavior_flags{}; 217 BehaviorFlags behavior_flags{};
212 VAddr additional_params_address{}; 218 VAddr additional_params_address{};
@@ -252,6 +258,7 @@ public:
252 void FlushWaveBuffers(u8 flush_count, 258 void FlushWaveBuffers(u8 flush_count,
253 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states, 259 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
254 s32 channel_count); 260 s32 channel_count);
261 void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer);
255 262
256private: 263private:
257 std::vector<s16> stored_samples; 264 std::vector<s16> stored_samples;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 53b8b7ca0..7c0950bb0 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -345,8 +345,10 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
345static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, 345static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
346 const Service::FileSystem::FileSystemController& fs_controller) { 346 const Service::FileSystem::FileSystemController& fs_controller) {
347 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 347 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
348 const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
348 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || 349 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
349 load_dir == nullptr || load_dir->GetSize() <= 0) { 350 ((load_dir == nullptr || load_dir->GetSize() <= 0) &&
351 (sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) {
350 return; 352 return;
351 } 353 }
352 354
@@ -356,7 +358,10 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
356 } 358 }
357 359
358 const auto& disabled = Settings::values.disabled_addons[title_id]; 360 const auto& disabled = Settings::values.disabled_addons[title_id];
359 auto patch_dirs = load_dir->GetSubdirectories(); 361 std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
362 if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
363 patch_dirs.push_back(sdmc_load_dir);
364 }
360 std::sort(patch_dirs.begin(), patch_dirs.end(), 365 std::sort(patch_dirs.begin(), patch_dirs.end(),
361 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 366 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
362 367
@@ -402,7 +407,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
402} 407}
403 408
404VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, 409VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
405 VirtualFile update_raw) const { 410 VirtualFile update_raw, bool apply_layeredfs) const {
406 const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", 411 const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
407 title_id, static_cast<u8>(type)); 412 title_id, static_cast<u8>(type));
408 413
@@ -442,7 +447,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
442 } 447 }
443 448
444 // LayeredFS 449 // LayeredFS
445 ApplyLayeredFS(romfs, title_id, type, fs_controller); 450 if (apply_layeredfs) {
451 ApplyLayeredFS(romfs, title_id, type, fs_controller);
452 }
446 453
447 return romfs; 454 return romfs;
448} 455}
@@ -524,6 +531,15 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
524 } 531 }
525 } 532 }
526 533
534 // SDMC mod directory (RomFS LayeredFS)
535 const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
536 if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0 &&
537 IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
538 const auto mod_disabled =
539 std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
540 out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", "LayeredFS");
541 }
542
527 // DLC 543 // DLC
528 const auto dlc_entries = 544 const auto dlc_entries =
529 content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); 545 content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index fb1853035..3be871f35 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -64,7 +64,8 @@ public:
64 // - LayeredFS 64 // - LayeredFS
65 [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, 65 [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
66 ContentRecordType type = ContentRecordType::Program, 66 ContentRecordType type = ContentRecordType::Program,
67 VirtualFile update_raw = nullptr) const; 67 VirtualFile update_raw = nullptr,
68 bool apply_layeredfs = true) const;
68 69
69 // Returns a vector of pairs between patch names and patch versions. 70 // Returns a vector of pairs between patch names and patch versions.
70 // i.e. Update 3.2.2 will return {"Update", "3.2.2"} 71 // i.e. Update 3.2.2 will return {"Update", "3.2.2"}
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index cb56d8f2d..e5c72cd4d 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -12,23 +12,32 @@ namespace FileSys {
12 12
13constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB 13constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB
14 14
15SDMCFactory::SDMCFactory(VirtualDir dir_) 15SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_)
16 : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>( 16 : sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)),
17 GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), 17 contents(std::make_unique<RegisteredCache>(
18 [](const VirtualFile& file, const NcaID& id) { 18 GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/registered"),
19 return NAX{file, id}.GetDecrypted(); 19 [](const VirtualFile& file, const NcaID& id) {
20 })), 20 return NAX{file, id}.GetDecrypted();
21 })),
21 placeholder(std::make_unique<PlaceholderCache>( 22 placeholder(std::make_unique<PlaceholderCache>(
22 GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {} 23 GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/placehld"))) {}
23 24
24SDMCFactory::~SDMCFactory() = default; 25SDMCFactory::~SDMCFactory() = default;
25 26
26ResultVal<VirtualDir> SDMCFactory::Open() const { 27ResultVal<VirtualDir> SDMCFactory::Open() const {
27 return MakeResult<VirtualDir>(dir); 28 return MakeResult<VirtualDir>(sd_dir);
29}
30
31VirtualDir SDMCFactory::GetSDMCModificationLoadRoot(u64 title_id) const {
32 // LayeredFS doesn't work on updates and title id-less homebrew
33 if (title_id == 0 || (title_id & 0xFFF) == 0x800) {
34 return nullptr;
35 }
36 return GetOrCreateDirectoryRelative(sd_mod_dir, fmt::format("/{:016X}", title_id));
28} 37}
29 38
30VirtualDir SDMCFactory::GetSDMCContentDirectory() const { 39VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
31 return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents"); 40 return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents");
32} 41}
33 42
34RegisteredCache* SDMCFactory::GetSDMCContents() const { 43RegisteredCache* SDMCFactory::GetSDMCContents() const {
@@ -40,11 +49,11 @@ PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
40} 49}
41 50
42VirtualDir SDMCFactory::GetImageDirectory() const { 51VirtualDir SDMCFactory::GetImageDirectory() const {
43 return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album"); 52 return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Album");
44} 53}
45 54
46u64 SDMCFactory::GetSDMCFreeSpace() const { 55u64 SDMCFactory::GetSDMCFreeSpace() const {
47 return GetSDMCTotalSpace() - dir->GetSize(); 56 return GetSDMCTotalSpace() - sd_dir->GetSize();
48} 57}
49 58
50u64 SDMCFactory::GetSDMCTotalSpace() const { 59u64 SDMCFactory::GetSDMCTotalSpace() const {
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 2bb92ba93..3a3d11f3a 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -16,11 +16,12 @@ class PlaceholderCache;
16/// File system interface to the SDCard archive 16/// File system interface to the SDCard archive
17class SDMCFactory { 17class SDMCFactory {
18public: 18public:
19 explicit SDMCFactory(VirtualDir dir); 19 explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);
20 ~SDMCFactory(); 20 ~SDMCFactory();
21 21
22 ResultVal<VirtualDir> Open() const; 22 ResultVal<VirtualDir> Open() const;
23 23
24 VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
24 VirtualDir GetSDMCContentDirectory() const; 25 VirtualDir GetSDMCContentDirectory() const;
25 26
26 RegisteredCache* GetSDMCContents() const; 27 RegisteredCache* GetSDMCContents() const;
@@ -32,7 +33,8 @@ public:
32 u64 GetSDMCTotalSpace() const; 33 u64 GetSDMCTotalSpace() const;
33 34
34private: 35private:
35 VirtualDir dir; 36 VirtualDir sd_dir;
37 VirtualDir sd_mod_dir;
36 38
37 std::unique_ptr<RegisteredCache> contents; 39 std::unique_ptr<RegisteredCache> contents;
38 std::unique_ptr<PlaceholderCache> placeholder; 40 std::unique_ptr<PlaceholderCache> placeholder;
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 800feba6e..7583d68b2 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -96,7 +96,7 @@ private:
96 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { 96 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
97 LOG_DEBUG(Service_Audio, "(STUBBED) called"); 97 LOG_DEBUG(Service_Audio, "(STUBBED) called");
98 98
99 std::vector<u8> output_params(ctx.GetWriteBufferSize()); 99 std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0);
100 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); 100 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
101 101
102 if (result.IsSuccess()) { 102 if (result.IsSuccess()) {
@@ -110,17 +110,19 @@ private:
110 void Start(Kernel::HLERequestContext& ctx) { 110 void Start(Kernel::HLERequestContext& ctx) {
111 LOG_WARNING(Service_Audio, "(STUBBED) called"); 111 LOG_WARNING(Service_Audio, "(STUBBED) called");
112 112
113 IPC::ResponseBuilder rb{ctx, 2}; 113 const auto result = renderer->Start();
114 114
115 rb.Push(ResultSuccess); 115 IPC::ResponseBuilder rb{ctx, 2};
116 rb.Push(result);
116 } 117 }
117 118
118 void Stop(Kernel::HLERequestContext& ctx) { 119 void Stop(Kernel::HLERequestContext& ctx) {
119 LOG_WARNING(Service_Audio, "(STUBBED) called"); 120 LOG_WARNING(Service_Audio, "(STUBBED) called");
120 121
121 IPC::ResponseBuilder rb{ctx, 2}; 122 const auto result = renderer->Stop();
122 123
123 rb.Push(ResultSuccess); 124 IPC::ResponseBuilder rb{ctx, 2};
125 rb.Push(result);
124 } 126 }
125 127
126 void QuerySystemEvent(Kernel::HLERequestContext& ctx) { 128 void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 3c16fe6c7..4a9b13e45 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -703,6 +703,16 @@ FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id)
703 return bis_factory->GetModificationLoadRoot(title_id); 703 return bis_factory->GetModificationLoadRoot(title_id);
704} 704}
705 705
706FileSys::VirtualDir FileSystemController::GetSDMCModificationLoadRoot(u64 title_id) const {
707 LOG_TRACE(Service_FS, "Opening SDMC mod load root for tid={:016X}", title_id);
708
709 if (sdmc_factory == nullptr) {
710 return nullptr;
711 }
712
713 return sdmc_factory->GetSDMCModificationLoadRoot(title_id);
714}
715
706FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const { 716FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const {
707 LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id); 717 LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id);
708 718
@@ -733,20 +743,23 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
733 } 743 }
734 744
735 using YuzuPath = Common::FS::YuzuPath; 745 using YuzuPath = Common::FS::YuzuPath;
746 const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir);
747 const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
736 const auto rw_mode = FileSys::Mode::ReadWrite; 748 const auto rw_mode = FileSys::Mode::ReadWrite;
737 749
738 auto nand_directory = 750 auto nand_directory =
739 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode); 751 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
740 auto sd_directory = 752 auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode);
741 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode);
742 auto load_directory = 753 auto load_directory =
743 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read); 754 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read);
755 auto sd_load_directory =
756 vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read);
744 auto dump_directory = 757 auto dump_directory =
745 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode); 758 vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode);
746 759
747 if (bis_factory == nullptr) { 760 if (bis_factory == nullptr) {
748 bis_factory = 761 bis_factory = std::make_unique<FileSys::BISFactory>(
749 std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory); 762 nand_directory, std::move(load_directory), std::move(dump_directory));
750 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, 763 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,
751 bis_factory->GetSystemNANDContents()); 764 bis_factory->GetSystemNANDContents());
752 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, 765 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND,
@@ -759,7 +772,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
759 } 772 }
760 773
761 if (sdmc_factory == nullptr) { 774 if (sdmc_factory == nullptr) {
762 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); 775 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
776 std::move(sd_load_directory));
763 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, 777 system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
764 sdmc_factory->GetSDMCContents()); 778 sdmc_factory->GetSDMCContents());
765 } 779 }
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index b6b1b9220..d387af3cb 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -115,6 +115,7 @@ public:
115 FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const; 115 FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const;
116 FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const; 116 FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const;
117 117
118 FileSys::VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
118 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; 119 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
119 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; 120 FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
120 121
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp
index 100138d11..2fafd077f 100755
--- a/src/input_common/analog_from_button.cpp
+++ b/src/input_common/analog_from_button.cpp
@@ -27,6 +27,7 @@ public:
27 down->SetCallback(callbacks); 27 down->SetCallback(callbacks);
28 left->SetCallback(callbacks); 28 left->SetCallback(callbacks);
29 right->SetCallback(callbacks); 29 right->SetCallback(callbacks);
30 modifier->SetCallback(callbacks);
30 } 31 }
31 32
32 bool IsAngleGreater(float old_angle, float new_angle) const { 33 bool IsAngleGreater(float old_angle, float new_angle) const {
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 2208e1922..c9cff7450 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -18,7 +18,10 @@ set(SHADER_FILES
18 vulkan_uint8.comp 18 vulkan_uint8.comp
19) 19)
20 20
21find_program(GLSLANGVALIDATOR "glslangValidator" REQUIRED) 21find_program(GLSLANGVALIDATOR "glslangValidator")
22if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
23 message(FATAL_ERROR "Required program `glslangValidator` not found.")
24endif()
22 25
23set(GLSL_FLAGS "") 26set(GLSL_FLAGS "")
24set(QUIET_FLAG "--quiet") 27set(QUIET_FLAG "--quiet")
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 320ee8d30..63d8ad42a 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -42,6 +42,8 @@ public:
42 42
43 [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0; 43 [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
44 44
45 [[nodiscard]] virtual std::string GetDeviceVendor() const = 0;
46
45 // Getter/setter functions: 47 // Getter/setter functions:
46 // ------------------------ 48 // ------------------------
47 49
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 3f4532ca7..3b00614e7 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -202,13 +202,13 @@ Device::Device() {
202 LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available"); 202 LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");
203 throw std::runtime_error{"Insufficient version"}; 203 throw std::runtime_error{"Insufficient version"};
204 } 204 }
205 const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); 205 vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
206 const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); 206 const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
207 const std::vector extensions = GetExtensions(); 207 const std::vector extensions = GetExtensions();
208 208
209 const bool is_nvidia = vendor == "NVIDIA Corporation"; 209 const bool is_nvidia = vendor_name == "NVIDIA Corporation";
210 const bool is_amd = vendor == "ATI Technologies Inc."; 210 const bool is_amd = vendor_name == "ATI Technologies Inc.";
211 const bool is_intel = vendor == "Intel"; 211 const bool is_intel = vendor_name == "Intel";
212 212
213#ifdef __unix__ 213#ifdef __unix__
214 const bool is_linux = true; 214 const bool is_linux = true;
@@ -275,6 +275,56 @@ Device::Device() {
275 } 275 }
276} 276}
277 277
278std::string Device::GetVendorName() const {
279 if (vendor_name == "NVIDIA Corporation") {
280 return "NVIDIA";
281 }
282 if (vendor_name == "ATI Technologies Inc.") {
283 return "AMD";
284 }
285 if (vendor_name == "Intel") {
286 // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
287 // Simply return `INTEL` for those as well as the Windows driver.
288 return "INTEL";
289 }
290 if (vendor_name == "Intel Open Source Technology Center") {
291 return "I965";
292 }
293 if (vendor_name == "Mesa Project") {
294 return "I915";
295 }
296 if (vendor_name == "Mesa/X.org") {
297 // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
298 // MESA instead of one of those driver names.
299 return "MESA";
300 }
301 if (vendor_name == "AMD") {
302 return "RADEONSI";
303 }
304 if (vendor_name == "nouveau") {
305 return "NOUVEAU";
306 }
307 if (vendor_name == "X.Org") {
308 return "R600";
309 }
310 if (vendor_name == "Collabora Ltd") {
311 return "ZINK";
312 }
313 if (vendor_name == "Intel Corporation") {
314 return "OPENSWR";
315 }
316 if (vendor_name == "Microsoft Corporation") {
317 return "D3D12";
318 }
319 if (vendor_name == "NVIDIA") {
320 // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
321 // strategy would have returned `NVIDIA` here for this driver, the same result as the
322 // proprietary driver.
323 return "TEGRA";
324 }
325 return vendor_name;
326}
327
278Device::Device(std::nullptr_t) { 328Device::Device(std::nullptr_t) {
279 max_uniform_buffers.fill(std::numeric_limits<u32>::max()); 329 max_uniform_buffers.fill(std::numeric_limits<u32>::max());
280 uniform_buffer_alignment = 4; 330 uniform_buffer_alignment = 4;
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index f24bd0c7b..2c2b13767 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -22,6 +22,8 @@ public:
22 explicit Device(); 22 explicit Device();
23 explicit Device(std::nullptr_t); 23 explicit Device(std::nullptr_t);
24 24
25 [[nodiscard]] std::string GetVendorName() const;
26
25 u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept { 27 u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept {
26 return max_uniform_buffers[static_cast<std::size_t>(shader_type)]; 28 return max_uniform_buffers[static_cast<std::size_t>(shader_type)];
27 } 29 }
@@ -130,6 +132,7 @@ private:
130 static bool TestVariableAoffi(); 132 static bool TestVariableAoffi();
131 static bool TestPreciseBug(); 133 static bool TestPreciseBug();
132 134
135 std::string vendor_name;
133 std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{}; 136 std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{};
134 std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{}; 137 std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{};
135 size_t uniform_buffer_alignment{}; 138 size_t uniform_buffer_alignment{};
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 23948feed..a2c1599f7 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -341,6 +341,20 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
341[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset, 341[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset,
342 VideoCommon::SubresourceLayers subresource, GLenum target) { 342 VideoCommon::SubresourceLayers subresource, GLenum target) {
343 switch (target) { 343 switch (target) {
344 case GL_TEXTURE_1D:
345 return CopyOrigin{
346 .level = static_cast<GLint>(subresource.base_level),
347 .x = static_cast<GLint>(offset.x),
348 .y = static_cast<GLint>(0),
349 .z = static_cast<GLint>(0),
350 };
351 case GL_TEXTURE_1D_ARRAY:
352 return CopyOrigin{
353 .level = static_cast<GLint>(subresource.base_level),
354 .x = static_cast<GLint>(offset.x),
355 .y = static_cast<GLint>(0),
356 .z = static_cast<GLint>(subresource.base_layer),
357 };
344 case GL_TEXTURE_2D_ARRAY: 358 case GL_TEXTURE_2D_ARRAY:
345 case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: 359 case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
346 return CopyOrigin{ 360 return CopyOrigin{
@@ -366,6 +380,18 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
366 VideoCommon::SubresourceLayers dst_subresource, 380 VideoCommon::SubresourceLayers dst_subresource,
367 GLenum target) { 381 GLenum target) {
368 switch (target) { 382 switch (target) {
383 case GL_TEXTURE_1D:
384 return CopyRegion{
385 .width = static_cast<GLsizei>(extent.width),
386 .height = static_cast<GLsizei>(1),
387 .depth = static_cast<GLsizei>(1),
388 };
389 case GL_TEXTURE_1D_ARRAY:
390 return CopyRegion{
391 .width = static_cast<GLsizei>(extent.width),
392 .height = static_cast<GLsizei>(1),
393 .depth = static_cast<GLsizei>(dst_subresource.num_layers),
394 };
369 case GL_TEXTURE_2D_ARRAY: 395 case GL_TEXTURE_2D_ARRAY:
370 case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: 396 case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
371 return CopyRegion{ 397 return CopyRegion{
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index cc19a110f..0b66f8332 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -70,6 +70,10 @@ public:
70 return &rasterizer; 70 return &rasterizer;
71 } 71 }
72 72
73 [[nodiscard]] std::string GetDeviceVendor() const override {
74 return device.GetVendorName();
75 }
76
73private: 77private:
74 /// Initializes the OpenGL state and creates persistent objects. 78 /// Initializes the OpenGL state and creates persistent objects.
75 void InitOpenGLObjects(); 79 void InitOpenGLObjects();
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 72071316c..d7d17e110 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -47,6 +47,10 @@ public:
47 return &rasterizer; 47 return &rasterizer;
48 } 48 }
49 49
50 [[nodiscard]] std::string GetDeviceVendor() const override {
51 return device.GetDriverName();
52 }
53
50private: 54private:
51 void Report() const; 55 void Report() const;
52 56
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index c7cfd02b6..d8dbd3824 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1057,9 +1057,6 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
1057 std::vector<ImageId> right_aliased_ids; 1057 std::vector<ImageId> right_aliased_ids;
1058 std::vector<ImageId> bad_overlap_ids; 1058 std::vector<ImageId> bad_overlap_ids;
1059 ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) { 1059 ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) {
1060 if (info.type != overlap.info.type) {
1061 return;
1062 }
1063 if (info.type == ImageType::Linear) { 1060 if (info.type == ImageType::Linear) {
1064 if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) { 1061 if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) {
1065 // Alias linear images with the same pitch 1062 // Alias linear images with the same pitch
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 23814afd2..f214510da 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -532,6 +532,27 @@ bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags want
532 return (supported_usage & wanted_usage) == wanted_usage; 532 return (supported_usage & wanted_usage) == wanted_usage;
533} 533}
534 534
535std::string Device::GetDriverName() const {
536 switch (driver_id) {
537 case VK_DRIVER_ID_AMD_PROPRIETARY:
538 return "AMD";
539 case VK_DRIVER_ID_AMD_OPEN_SOURCE:
540 return "AMDVLK";
541 case VK_DRIVER_ID_MESA_RADV:
542 return "RADV";
543 case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
544 return "NVIDIA";
545 case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
546 return "INTEL";
547 case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
548 return "ANV";
549 case VK_DRIVER_ID_MESA_LLVMPIPE:
550 return "LAVAPIPE";
551 default:
552 return vendor_name;
553 }
554}
555
535void Device::CheckSuitability(bool requires_swapchain) const { 556void Device::CheckSuitability(bool requires_swapchain) const {
536 std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions; 557 std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions;
537 bool has_swapchain = false; 558 bool has_swapchain = false;
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 88b298196..96c0f8c60 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -45,6 +45,9 @@ public:
45 /// Reports a shader to Nsight Aftermath. 45 /// Reports a shader to Nsight Aftermath.
46 void SaveShader(const std::vector<u32>& spirv) const; 46 void SaveShader(const std::vector<u32>& spirv) const;
47 47
48 /// Returns the name of the VkDriverId reported from Vulkan.
49 std::string GetDriverName() const;
50
48 /// Returns the dispatch loader with direct function pointers of the device. 51 /// Returns the dispatch loader with direct function pointers of the device.
49 const vk::DeviceDispatch& GetDispatchLoader() const { 52 const vk::DeviceDispatch& GetDispatchLoader() const {
50 return dld; 53 return dld;
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index efdc6aa50..7a6f84d96 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -143,24 +143,25 @@ void MicroProfileWidget::hideEvent(QHideEvent* ev) {
143} 143}
144 144
145void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) { 145void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) {
146 MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); 146 MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0);
147 ev->accept(); 147 ev->accept();
148} 148}
149 149
150void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) { 150void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) {
151 MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); 151 MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0);
152 MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); 152 MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
153 ev->accept(); 153 ev->accept();
154} 154}
155 155
156void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) { 156void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) {
157 MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0); 157 MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0);
158 MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton); 158 MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
159 ev->accept(); 159 ev->accept();
160} 160}
161 161
162void MicroProfileWidget::wheelEvent(QWheelEvent* ev) { 162void MicroProfileWidget::wheelEvent(QWheelEvent* ev) {
163 MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120); 163 MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale,
164 ev->angleDelta().y() / 120);
164 ev->accept(); 165 ev->accept();
165} 166}
166 167
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index da956c99b..e44907be8 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -521,7 +521,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
521 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); 521 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
522 remove_menu->addSeparator(); 522 remove_menu->addSeparator();
523 QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents")); 523 QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents"));
524 QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); 524 QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
525 QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
526 QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
525 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 527 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
526 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 528 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
527 context_menu.addSeparator(); 529 context_menu.addSeparator();
@@ -570,8 +572,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
570 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { 572 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
571 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); 573 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
572 }); 574 });
573 connect(dump_romfs, &QAction::triggered, 575 connect(dump_romfs, &QAction::triggered, [this, program_id, path]() {
574 [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); 576 emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal);
577 });
578 connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
579 emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
580 });
575 connect(copy_tid, &QAction::triggered, 581 connect(copy_tid, &QAction::triggered,
576 [this, program_id]() { emit CopyTIDRequested(program_id); }); 582 [this, program_id]() { emit CopyTIDRequested(program_id); });
577 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 583 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index b630e34ff..50402da51 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -45,6 +45,11 @@ enum class GameListRemoveTarget {
45 CustomConfiguration, 45 CustomConfiguration,
46}; 46};
47 47
48enum class DumpRomFSTarget {
49 Normal,
50 SDMC,
51};
52
48enum class InstalledEntryType { 53enum class InstalledEntryType {
49 Game, 54 Game,
50 Update, 55 Update,
@@ -92,7 +97,7 @@ signals:
92 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); 97 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
93 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, 98 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
94 const std::string& game_path); 99 const std::string& game_path);
95 void DumpRomFSRequested(u64 program_id, const std::string& game_path); 100 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
96 void CopyTIDRequested(u64 program_id); 101 void CopyTIDRequested(u64 program_id);
97 void NavigateToGamedbEntryRequested(u64 program_id, 102 void NavigateToGamedbEntryRequested(u64 program_id,
98 const CompatibilityList& compatibility_list); 103 const CompatibilityList& compatibility_list);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 6f5b2f6d6..f462cd072 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -104,6 +104,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
104#include "input_common/main.h" 104#include "input_common/main.h"
105#include "util/overlay_dialog.h" 105#include "util/overlay_dialog.h"
106#include "video_core/gpu.h" 106#include "video_core/gpu.h"
107#include "video_core/renderer_base.h"
107#include "video_core/shader_notify.h" 108#include "video_core/shader_notify.h"
108#include "yuzu/about_dialog.h" 109#include "yuzu/about_dialog.h"
109#include "yuzu/bootmanager.h" 110#include "yuzu/bootmanager.h"
@@ -1422,8 +1423,12 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index, S
1422 title_name = Common::FS::PathToUTF8String( 1423 title_name = Common::FS::PathToUTF8String(
1423 std::filesystem::path{filename.toStdU16String()}.filename()); 1424 std::filesystem::path{filename.toStdU16String()}.filename());
1424 } 1425 }
1426 const bool is_64bit = system.Kernel().CurrentProcess()->Is64BitProcess();
1427 const auto instruction_set_suffix = is_64bit ? " (64-bit)" : " (32-bit)";
1428 title_name += instruction_set_suffix;
1425 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); 1429 LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
1426 UpdateWindowTitle(title_name, title_version); 1430 const auto gpu_vendor = system.GPU().Renderer().GetDeviceVendor();
1431 UpdateWindowTitle(title_name, title_version, gpu_vendor);
1427 1432
1428 loading_screen->Prepare(system.GetAppLoader()); 1433 loading_screen->Prepare(system.GetAppLoader());
1429 loading_screen->show(); 1434 loading_screen->show();
@@ -1877,7 +1882,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g
1877 } 1882 }
1878} 1883}
1879 1884
1880void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { 1885void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path,
1886 DumpRomFSTarget target) {
1881 const auto failed = [this] { 1887 const auto failed = [this] {
1882 QMessageBox::warning(this, tr("RomFS Extraction Failed!"), 1888 QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
1883 tr("There was an error copying the RomFS files or the user " 1889 tr("There was an error copying the RomFS files or the user "
@@ -1905,7 +1911,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
1905 return; 1911 return;
1906 } 1912 }
1907 1913
1908 const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir); 1914 const auto dump_dir =
1915 target == DumpRomFSTarget::Normal
1916 ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
1917 : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";
1909 const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id); 1918 const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id);
1910 1919
1911 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); 1920 const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir);
@@ -1915,7 +1924,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
1915 if (*romfs_title_id == program_id) { 1924 if (*romfs_title_id == program_id) {
1916 const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); 1925 const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
1917 const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed}; 1926 const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed};
1918 romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program); 1927 romfs =
1928 pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false);
1919 } else { 1929 } else {
1920 romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); 1930 romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
1921 } 1931 }
@@ -2852,8 +2862,8 @@ void GMainWindow::MigrateConfigFiles() {
2852 } 2862 }
2853} 2863}
2854 2864
2855void GMainWindow::UpdateWindowTitle(const std::string& title_name, 2865void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version,
2856 const std::string& title_version) { 2866 std::string_view gpu_vendor) {
2857 const auto branch_name = std::string(Common::g_scm_branch); 2867 const auto branch_name = std::string(Common::g_scm_branch);
2858 const auto description = std::string(Common::g_scm_desc); 2868 const auto description = std::string(Common::g_scm_desc);
2859 const auto build_id = std::string(Common::g_build_id); 2869 const auto build_id = std::string(Common::g_build_id);
@@ -2866,7 +2876,8 @@ void GMainWindow::UpdateWindowTitle(const std::string& title_name,
2866 if (title_name.empty()) { 2876 if (title_name.empty()) {
2867 setWindowTitle(QString::fromStdString(window_title)); 2877 setWindowTitle(QString::fromStdString(window_title));
2868 } else { 2878 } else {
2869 const auto run_title = fmt::format("{} | {} | {}", window_title, title_name, title_version); 2879 const auto run_title =
2880 fmt::format("{} | {} | {} | {}", window_title, title_name, title_version, gpu_vendor);
2870 setWindowTitle(QString::fromStdString(run_title)); 2881 setWindowTitle(QString::fromStdString(run_title));
2871 } 2882 }
2872} 2883}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 11f152cbe..45c8310e1 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -34,6 +34,7 @@ class QProgressDialog;
34class WaitTreeWidget; 34class WaitTreeWidget;
35enum class GameListOpenTarget; 35enum class GameListOpenTarget;
36enum class GameListRemoveTarget; 36enum class GameListRemoveTarget;
37enum class DumpRomFSTarget;
37enum class InstalledEntryType; 38enum class InstalledEntryType;
38class GameListPlaceholder; 39class GameListPlaceholder;
39 40
@@ -244,7 +245,7 @@ private slots:
244 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); 245 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
245 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 246 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
246 const std::string& game_path); 247 const std::string& game_path);
247 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); 248 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
248 void OnGameListCopyTID(u64 program_id); 249 void OnGameListCopyTID(u64 program_id);
249 void OnGameListNavigateToGamedbEntry(u64 program_id, 250 void OnGameListNavigateToGamedbEntry(u64 program_id,
250 const CompatibilityList& compatibility_list); 251 const CompatibilityList& compatibility_list);
@@ -287,8 +288,8 @@ private:
287 InstallResult InstallNSPXCI(const QString& filename); 288 InstallResult InstallNSPXCI(const QString& filename);
288 InstallResult InstallNCA(const QString& filename); 289 InstallResult InstallNCA(const QString& filename);
289 void MigrateConfigFiles(); 290 void MigrateConfigFiles();
290 void UpdateWindowTitle(const std::string& title_name = {}, 291 void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
291 const std::string& title_version = {}); 292 std::string_view gpu_vendor = {});
292 void UpdateStatusBar(); 293 void UpdateStatusBar();
293 void UpdateStatusButtons(); 294 void UpdateStatusButtons();
294 void UpdateUISettings(); 295 void UpdateUISettings();