summaryrefslogtreecommitdiffstats
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)
# Check for system Qt on Linux, fallback to bundled Qt
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
- if (NOT YUZU_USE_BUNDLED_QT)
- find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets QUIET)
- if (NOT Qt5_FOUND)
- set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE)
+ find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets)
+ if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
+ # Check for dependencies, then enable bundled Qt download
+
+ # Check that the system GLIBCXX version is compatible
+ find_program(OBJDUMP objdump)
+ if ("${OBJDUMP}" STREQUAL "OBJDUMP-NOTFOUND")
+ message(FATAL_ERROR "Required program `objdump` not found.")
endif()
+ find_library(LIBSTDCXX libstdc++.so.6)
+ execute_process(
+ COMMAND
+ ${OBJDUMP} -T ${LIBSTDCXX}
+ COMMAND
+ grep GLIBCXX_3.4.28
+ COMMAND
+ sed "s/[0-9a-f]*.* //"
+ COMMAND
+ sed "s/ .*//"
+ COMMAND
+ sort -u
+ OUTPUT_VARIABLE
+ GLIBCXX_MET
+ )
+ if (NOT GLIBCXX_MET)
+ message(FATAL_ERROR "Qt too old or not found, and bundled Qt package is not \
+ compatible with this system. Either install Qt ${QT_VERSION}, or provide the path \
+ to Qt by setting the variable Qt5_ROOT.")
+ endif()
+
+ # Check for headers
+ Include(FindPkgConfig REQUIRED)
+ pkg_check_modules(QT_DEP_GLU QUIET glu>=9.0.0)
+ if (NOT QT_DEP_GLU_FOUND)
+ message(FATAL_ERROR "Qt bundled pacakge dependency `glu` not found. \
+ Perhaps `libglu1-mesa-dev` needs to be installed?")
+ endif()
+ pkg_check_modules(QT_DEP_MESA QUIET dri>=20.0.8)
+ if (NOT QT_DEP_MESA_FOUND)
+ message(FATAL_ERROR "Qt bundled pacakge dependency `dri` not found. \
+ Perhaps `mesa-common-dev` needs to be installed?")
+ endif()
+
+ # Check for X libraries
+ set(BUNDLED_QT_REQUIREMENTS
+ libxcb-icccm.so.4
+ libxcb-image.so.0
+ libxcb-keysyms.so.1
+ libxcb-randr.so.0
+ libxcb-render-util.so.0
+ libxcb-render.so.0
+ libxcb-shape.so.0
+ libxcb-shm.so.0
+ libxcb-sync.so.1
+ libxcb-xfixes.so.0
+ libxcb-xinerama.so.0
+ libxcb-xkb.so.1
+ libxcb.so.1
+ libxkbcommon-x11.so.0
+ libxkbcommon.so.0
+ )
+ set(UNRESOLVED_QT_DEPS "")
+ foreach (REQUIREMENT ${BUNDLED_QT_REQUIREMENTS})
+ find_library(BUNDLED_QT_${REQUIREMENT} ${REQUIREMENT})
+ if ("${BUNDLED_QT_${REQUIREMENT}}" STREQUAL "BUNDLED_QT_${REQUIREMENT}-NOTFOUND")
+ set(UNRESOLVED_QT_DEPS ${UNRESOLVED_QT_DEPS} ${REQUIREMENT})
+ endif()
+ unset(BUNDLED_QT_${REQUIREMENT})
+ endforeach()
+ unset(BUNDLED_QT_REQUIREMENTS)
+
+ if (NOT "${UNRESOLVED_QT_DEPS}" STREQUAL "")
+ message(FATAL_ERROR "Bundled Qt package missing required dependencies: ${UNRESOLVED_QT_DEPS}")
+ endif()
+
+ set(YUZU_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE)
endif()
if (YUZU_USE_BUNDLED_QT)
# Binary package currently does not support Qt webengine, so make sure it's disabled
@@ -473,7 +544,15 @@ if (YUZU_USE_BUNDLED_FFMPEG)
# FFmpeg has source that requires one of nasm or yasm to assemble it.
# REQUIRED throws an error if not found here during configuration rather than during compilation.
- find_program(ASSEMBLER NAMES nasm yasm REQUIRED)
+ find_program(ASSEMBLER NAMES nasm yasm)
+ if ("${ASSEMBLER}" STREQUAL "ASSEMBLER-NOTFOUND")
+ message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
+ endif()
+
+ find_program(AUTOCONF autoconf)
+ if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
+ message(FATAL_ERROR "Required program `autoconf` not found.")
+ endif()
set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg)
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)
# GNU toolchains for some reason doesn't work with the later half of this CMakeLists after
# updating to 1.0.24, so we do it the old-fashioned way for now.
+ # Require autoconf and libtoolize here, rather than crash during compilation
+ find_program(AUTOCONF autoconf)
+ if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
+ message(FATAL_ERROR "Required program `autoconf` not found.")
+ endif()
+
+ find_program(LIBTOOLIZE libtoolize)
+ if ("${LIBTOOLIZE}" STREQUAL "LIBTOOLIZE-NOTFOUND")
+ message(FATAL_ERROR "Required program `libtoolize` not found.")
+ endif()
+
set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb")
set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb")
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)
/W3
/we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled
/we4101 # 'identifier': unreferenced local variable
+ /we4189 # 'identifier': local variable is initialized but not referenced
/we4265 # 'class': class has virtual functions, but destructor is not virtual
/we4388 # signed/unsigned mismatch
/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 @@
#include "audio_core/voice_context.h"
#include "common/logging/log.h"
#include "common/settings.h"
+#include "core/core_timing.h"
#include "core/memory.h"
namespace {
@@ -68,7 +69,9 @@ namespace {
} // namespace
namespace AudioCore {
-AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
+constexpr s32 NUM_BUFFERS = 2;
+
+AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
AudioCommon::AudioRendererParameter params,
Stream::ReleaseCallback&& release_callback,
std::size_t instance_number)
@@ -77,7 +80,8 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
sink_context(params.sink_count), splitter_context(),
voices(params.voice_count), memory{memory_},
command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
- memory) {
+ memory),
+ core_timing{core_timing_} {
behavior_info.SetUserRevision(params.revision);
splitter_context.Initialize(behavior_info, params.splitter_count,
params.num_splitter_send_channels);
@@ -86,16 +90,27 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
stream = audio_out->OpenStream(
core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
- audio_out->StartStream(stream);
-
- QueueMixedBuffer(0);
- QueueMixedBuffer(1);
- QueueMixedBuffer(2);
- QueueMixedBuffer(3);
+ process_event = Core::Timing::CreateEvent(
+ fmt::format("AudioRenderer-Instance{}-Process", instance_number),
+ [this](std::uintptr_t, std::chrono::nanoseconds) { ReleaseAndQueueBuffers(); });
+ for (s32 i = 0; i < NUM_BUFFERS; ++i) {
+ QueueMixedBuffer(i);
+ }
}
AudioRenderer::~AudioRenderer() = default;
+ResultCode AudioRenderer::Start() {
+ audio_out->StartStream(stream);
+ ReleaseAndQueueBuffers();
+ return ResultSuccess;
+}
+
+ResultCode AudioRenderer::Stop() {
+ audio_out->StopStream(stream);
+ return ResultSuccess;
+}
+
u32 AudioRenderer::GetSampleRate() const {
return worker_params.sample_rate;
}
@@ -114,7 +129,7 @@ Stream::State AudioRenderer::GetStreamState() const {
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params) {
-
+ std::scoped_lock lock{mutex};
InfoUpdater info_updater{input_params, output_params, behavior_info};
if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
@@ -194,9 +209,6 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_param
LOG_ERROR(Audio, "Audio buffers were not consumed!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
-
- ReleaseAndQueueBuffers();
-
return ResultSuccess;
}
@@ -220,10 +232,8 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
command_generator.PostCommand();
// Base sample size
std::size_t BUFFER_SIZE{worker_params.sample_count};
- // Samples
- std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
- // Make sure to clear our samples
- std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
+ // Samples, making sure to clear
+ std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0);
if (sink_context.InUse()) {
const auto stream_channel_count = stream->GetNumChannels();
@@ -315,10 +325,24 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
}
void AudioRenderer::ReleaseAndQueueBuffers() {
- const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
- for (const auto& tag : released_buffers) {
- QueueMixedBuffer(tag);
+ if (!stream->IsPlaying()) {
+ return;
}
+
+ {
+ std::scoped_lock lock{mutex};
+ const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
+ for (const auto& tag : released_buffers) {
+ QueueMixedBuffer(tag);
+ }
+ }
+
+ const f32 sample_rate = static_cast<f32>(GetSampleRate());
+ const f32 sample_count = static_cast<f32>(GetSampleCount());
+ const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
+ const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
+ const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
+ core_timing.ScheduleEvent(next_event_time, process_event, {});
}
} // 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 @@
#include <array>
#include <memory>
+#include <mutex>
#include <vector>
#include "audio_core/behavior_info.h"
@@ -45,6 +46,8 @@ public:
[[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params);
+ [[nodiscard]] ResultCode Start();
+ [[nodiscard]] ResultCode Stop();
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
[[nodiscard]] u32 GetSampleRate() const;
@@ -68,6 +71,9 @@ private:
Core::Memory::Memory& memory;
CommandGenerator command_generator;
std::size_t elapsed_frame_count{};
+ Core::Timing::CoreTiming& core_timing;
+ std::shared_ptr<Core::Timing::EventType> process_event;
+ std::mutex mutex;
};
} // 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
state.lowpass_1 = 0.0f;
} else {
const auto a = 1.0f - hf_gain;
- const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference /
+ const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference /
static_cast<f32>(info.sample_rate)));
const auto c = std::sqrt(b * b - 4.0f * a * a);
@@ -843,7 +843,7 @@ void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbSta
}
const auto max_early_delay = state.early_delay_line.GetMaxDelay();
- const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f);
+ const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f);
for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
const auto length = AudioCommon::CalculateDelaySamples(
sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
@@ -1004,7 +1004,8 @@ void CommandGenerator::GenerateFinalMixCommand() {
}
s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
- s32 sample_count, s32 channel, std::size_t mix_offset) {
+ s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
+ s32 channel, std::size_t mix_offset) {
const auto& in_params = voice_info.GetInParams();
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
if (wave_buffer.buffer_address == 0) {
@@ -1013,14 +1014,12 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s
if (wave_buffer.buffer_size == 0) {
return 0;
}
- if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
+ if (sample_end_offset < sample_start_offset) {
return 0;
}
- const auto samples_remaining =
- (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
+ const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
const auto start_offset =
- ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
- sizeof(s16);
+ ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(s16);
const auto buffer_pos = wave_buffer.buffer_address + start_offset;
const auto samples_processed = std::min(sample_count, samples_remaining);
@@ -1044,8 +1043,8 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s
}
s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
- s32 sample_count, [[maybe_unused]] s32 channel,
- std::size_t mix_offset) {
+ s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
+ [[maybe_unused]] s32 channel, std::size_t mix_offset) {
const auto& in_params = voice_info.GetInParams();
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
if (wave_buffer.buffer_address == 0) {
@@ -1054,7 +1053,7 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
if (wave_buffer.buffer_size == 0) {
return 0;
}
- if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
+ if (sample_end_offset < sample_start_offset) {
return 0;
}
@@ -1079,10 +1078,9 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
s32 coef1 = coeffs[idx * 2];
s32 coef2 = coeffs[idx * 2 + 1];
- const auto samples_remaining =
- (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
+ const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
const auto samples_processed = std::min(sample_count, samples_remaining);
- const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
+ const auto sample_pos = dsp_state.offset + sample_start_offset;
const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
@@ -1210,9 +1208,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
}
std::size_t temp_mix_offset{};
- bool is_buffer_completed{false};
auto samples_remaining = sample_count;
- while (samples_remaining > 0 && !is_buffer_completed) {
+ while (samples_remaining > 0) {
const auto samples_to_output = std::min(samples_remaining, min_required_samples);
const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
@@ -1229,24 +1226,38 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
// No more data can be read
if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
- is_buffer_completed = true;
break;
}
if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
- // TODO(ogniK): ADPCM loop context
+ memory.ReadBlock(wave_buffer.context_address, &dsp_state.context,
+ sizeof(ADPCMContext));
+ }
+
+ s32 samples_offset_start;
+ s32 samples_offset_end;
+ if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 &&
+ wave_buffer.loop_end_sample != 0 &&
+ wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) {
+ samples_offset_start = wave_buffer.loop_start_sample;
+ samples_offset_end = wave_buffer.loop_end_sample;
+ } else {
+ samples_offset_start = wave_buffer.start_sample_offset;
+ samples_offset_end = wave_buffer.end_sample_offset;
}
s32 samples_decoded{0};
switch (in_params.sample_format) {
case SampleFormat::Pcm16:
- samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
- channel, temp_mix_offset);
+ samples_decoded =
+ DecodePcm16(voice_info, dsp_state, samples_offset_start, samples_offset_end,
+ samples_to_read - samples_read, channel, temp_mix_offset);
break;
case SampleFormat::Adpcm:
- samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
- channel, temp_mix_offset);
+ samples_decoded =
+ DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end,
+ samples_to_read - samples_read, channel, temp_mix_offset);
break;
default:
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
@@ -1257,15 +1268,19 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
dsp_state.offset += samples_decoded;
dsp_state.played_sample_count += samples_decoded;
- if (dsp_state.offset >=
- (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
+ if (dsp_state.offset >= (samples_offset_end - samples_offset_start) ||
samples_decoded == 0) {
// Reset our sample offset
dsp_state.offset = 0;
if (wave_buffer.is_looping) {
- if (samples_decoded == 0) {
+ dsp_state.loop_count++;
+ if (wave_buffer.loop_count > 0 &&
+ (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) {
// End of our buffer
- is_buffer_completed = true;
+ voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
+ }
+
+ if (samples_decoded == 0) {
break;
}
@@ -1273,15 +1288,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
dsp_state.played_sample_count = 0;
}
} else {
-
// Update our wave buffer states
- dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
- dsp_state.wave_buffer_consumed++;
- dsp_state.wave_buffer_index =
- (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
- if (wave_buffer.end_of_stream) {
- dsp_state.played_sample_count = 0;
- }
+ voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
}
}
}
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:
std::vector<u8>& work_buffer);
void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
// DSP Code
- s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
- s32 channel, std::size_t mix_offset);
- s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
- s32 channel, std::size_t mix_offset);
+ s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
+ s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
+ s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
+ s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
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,
if (voice_in_params.is_new) {
// Default our values for our voice
voice_info.Initialize();
- if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
- continue;
- }
// Zero out our voice states
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() {
in_params.last_volume = 0.0f;
in_params.biquad_filter.fill({});
in_params.wave_buffer_count = 0;
- in_params.wave_bufffer_head = 0;
+ in_params.wave_buffer_head = 0;
in_params.mix_id = AudioCommon::NO_MIX;
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
in_params.additional_params_address = 0;
@@ -75,7 +75,7 @@ void ServerVoiceInfo::Initialize() {
out_params.played_sample_count = 0;
out_params.wave_buffer_consumed = 0;
in_params.voice_drop_flag = false;
- in_params.buffer_mapped = false;
+ in_params.buffer_mapped = true;
in_params.wave_buffer_flush_request_count = 0;
in_params.was_biquad_filter_enabled.fill(false);
@@ -126,7 +126,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
in_params.volume = voice_in.volume;
in_params.biquad_filter = voice_in.biquad_filter;
in_params.wave_buffer_count = voice_in.wave_buffer_count;
- in_params.wave_bufffer_head = voice_in.wave_buffer_head;
+ in_params.wave_buffer_head = voice_in.wave_buffer_head;
if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
const auto in_request_count = in_params.wave_buffer_flush_request_count;
const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
@@ -185,14 +185,16 @@ void ServerVoiceInfo::UpdateWaveBuffers(
wave_buffer.buffer_size = 0;
wave_buffer.context_address = 0;
wave_buffer.context_size = 0;
+ wave_buffer.loop_start_sample = 0;
+ wave_buffer.loop_end_sample = 0;
wave_buffer.sent_to_dsp = true;
}
// Mark all our wave buffers as invalid
for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
channel++) {
- for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
- is_valid = false;
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) {
+ voice_states[channel]->is_wave_buffer_valid[i] = false;
}
}
}
@@ -211,7 +213,7 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
bool is_buffer_valid,
[[maybe_unused]] BehaviorInfo& behavior_info) {
- if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
+ if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) {
out_wavebuffer.buffer_address = 0;
out_wavebuffer.buffer_size = 0;
}
@@ -219,11 +221,40 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
// Validate sample offset sizings
if (sample_format == SampleFormat::Pcm16) {
- const auto buffer_size = in_wave_buffer.buffer_size;
- if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
- (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
- (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
+ const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
+ const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset;
+ const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset;
+ if (0 > start || start > buffer_size || 0 > end || end > buffer_size) {
// TODO(ogniK): Write error info
+ LOG_ERROR(Audio,
+ "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
+ "offsets were "
+ "{:08X} - 0x{:08X}",
+ buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset,
+ sizeof(s16) * in_wave_buffer.end_sample_offset);
+ return;
+ }
+ } else if (sample_format == SampleFormat::Adpcm) {
+ const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
+ const s64 start_frames = in_wave_buffer.start_sample_offset / 14;
+ const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0
+ ? 0
+ : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 +
+ (in_wave_buffer.start_sample_offset % 2);
+ const s64 start = start_frames * 8 + start_extra;
+ const s64 end_frames = in_wave_buffer.end_sample_offset / 14;
+ const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0
+ ? 0
+ : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 +
+ (in_wave_buffer.end_sample_offset % 2);
+ const s64 end = end_frames * 8 + end_extra;
+ if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size ||
+ in_wave_buffer.end_sample_offset < 0 || end > buffer_size) {
+ LOG_ERROR(Audio,
+ "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
+ "offsets were "
+ "{:08X} - 0x{:08X}",
+ in_wave_buffer.buffer_size, start, end);
return;
}
}
@@ -239,29 +270,34 @@ void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
out_wavebuffer.context_address = in_wave_buffer.context_address;
out_wavebuffer.context_size = in_wave_buffer.context_size;
+ out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample;
+ out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample;
in_params.buffer_mapped =
in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
// TODO(ogniK): Pool mapper attachment
// TODO(ogniK): IsAdpcmLoopContextBugFixed
+ if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 &&
+ in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) {
+ } else {
+ out_wavebuffer.context_address = 0;
+ out_wavebuffer.context_size = 0;
+ }
}
}
void ServerVoiceInfo::WriteOutStatus(
VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
- if (voice_in.is_new) {
+ if (voice_in.is_new || in_params.is_new) {
in_params.is_new = true;
voice_out.wave_buffer_consumed = 0;
voice_out.played_sample_count = 0;
voice_out.voice_dropped = false;
- } else if (!in_params.is_new) {
- voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
- voice_out.played_sample_count = voice_states[0]->played_sample_count;
- voice_out.voice_dropped = in_params.voice_drop_flag;
} else {
- voice_out.wave_buffer_consumed = 0;
- voice_out.played_sample_count = 0;
- voice_out.voice_dropped = false;
+ const auto& state = voice_states[0];
+ voice_out.wave_buffer_consumed = state->wave_buffer_consumed;
+ voice_out.played_sample_count = state->played_sample_count;
+ voice_out.voice_dropped = state->voice_dropped;
}
}
@@ -283,7 +319,8 @@ ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
bool ServerVoiceInfo::ShouldSkip() const {
// TODO(ogniK): Handle unmapped wave buffers or parameters
- return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
+ return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped ||
+ in_params.voice_drop_flag;
}
bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
@@ -381,7 +418,7 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
void ServerVoiceInfo::FlushWaveBuffers(
u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count) {
- auto wave_head = in_params.wave_bufffer_head;
+ auto wave_head = in_params.wave_buffer_head;
for (u8 i = 0; i < flush_count; i++) {
in_params.wave_buffer[wave_head].sent_to_dsp = true;
@@ -401,6 +438,17 @@ bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
}
+void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state,
+ const ServerWaveBuffer& wave_buffer) {
+ dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
+ dsp_state.wave_buffer_consumed++;
+ dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ dsp_state.loop_count = 0;
+ if (wave_buffer.end_of_stream) {
+ dsp_state.played_sample_count = 0;
+ }
+}
+
VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} {
for (std::size_t i = 0; i < voice_count; i++) {
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 {
u8 is_looping{};
u8 end_of_stream{};
u8 sent_to_server{};
- INSERT_PADDING_BYTES(5);
+ INSERT_PADDING_BYTES(1);
+ s32 loop_count{};
u64 context_address{};
u64 context_size{};
- INSERT_PADDING_BYTES(8);
+ u32 loop_start_sample{};
+ u32 loop_end_sample{};
};
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
@@ -76,6 +78,9 @@ struct ServerWaveBuffer {
bool end_of_stream{};
VAddr context_address{};
std::size_t context_size{};
+ s32 loop_count{};
+ u32 loop_start_sample{};
+ u32 loop_end_sample{};
bool sent_to_dsp{true};
};
@@ -108,6 +113,7 @@ struct VoiceState {
u32 external_context_size;
bool is_external_context_used;
bool voice_dropped;
+ s32 loop_count;
};
class VoiceChannelResource {
@@ -206,7 +212,7 @@ public:
float last_volume{};
std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
s32 wave_buffer_count{};
- s16 wave_bufffer_head{};
+ s16 wave_buffer_head{};
INSERT_PADDING_BYTES(2);
BehaviorFlags behavior_flags{};
VAddr additional_params_address{};
@@ -252,6 +258,7 @@ public:
void FlushWaveBuffers(u8 flush_count,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count);
+ void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer);
private:
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(
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
const Service::FileSystem::FileSystemController& fs_controller) {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
+ const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
- load_dir == nullptr || load_dir->GetSize() <= 0) {
+ ((load_dir == nullptr || load_dir->GetSize() <= 0) &&
+ (sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) {
return;
}
@@ -356,7 +358,10 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
}
const auto& disabled = Settings::values.disabled_addons[title_id];
- auto patch_dirs = load_dir->GetSubdirectories();
+ std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
+ if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
+ patch_dirs.push_back(sdmc_load_dir);
+ }
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -402,7 +407,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
}
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
- VirtualFile update_raw) const {
+ VirtualFile update_raw, bool apply_layeredfs) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
@@ -442,7 +447,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
}
// LayeredFS
- ApplyLayeredFS(romfs, title_id, type, fs_controller);
+ if (apply_layeredfs) {
+ ApplyLayeredFS(romfs, title_id, type, fs_controller);
+ }
return romfs;
}
@@ -524,6 +531,15 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
}
}
+ // SDMC mod directory (RomFS LayeredFS)
+ const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
+ if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0 &&
+ IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs"))) {
+ const auto mod_disabled =
+ std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
+ out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", "LayeredFS");
+ }
+
// DLC
const auto dlc_entries =
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:
// - LayeredFS
[[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
ContentRecordType type = ContentRecordType::Program,
- VirtualFile update_raw = nullptr) const;
+ VirtualFile update_raw = nullptr,
+ bool apply_layeredfs = true) const;
// Returns a vector of pairs between patch names and patch versions.
// 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 {
constexpr u64 SDMC_TOTAL_SIZE = 0x10000000000; // 1 TiB
-SDMCFactory::SDMCFactory(VirtualDir dir_)
- : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
- GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
- [](const VirtualFile& file, const NcaID& id) {
- return NAX{file, id}.GetDecrypted();
- })),
+SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_)
+ : sd_dir(std::move(sd_dir_)), sd_mod_dir(std::move(sd_mod_dir_)),
+ contents(std::make_unique<RegisteredCache>(
+ GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/registered"),
+ [](const VirtualFile& file, const NcaID& id) {
+ return NAX{file, id}.GetDecrypted();
+ })),
placeholder(std::make_unique<PlaceholderCache>(
- GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {}
+ GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents/placehld"))) {}
SDMCFactory::~SDMCFactory() = default;
ResultVal<VirtualDir> SDMCFactory::Open() const {
- return MakeResult<VirtualDir>(dir);
+ return MakeResult<VirtualDir>(sd_dir);
+}
+
+VirtualDir SDMCFactory::GetSDMCModificationLoadRoot(u64 title_id) const {
+ // LayeredFS doesn't work on updates and title id-less homebrew
+ if (title_id == 0 || (title_id & 0xFFF) == 0x800) {
+ return nullptr;
+ }
+ return GetOrCreateDirectoryRelative(sd_mod_dir, fmt::format("/{:016X}", title_id));
}
VirtualDir SDMCFactory::GetSDMCContentDirectory() const {
- return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents");
+ return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Contents");
}
RegisteredCache* SDMCFactory::GetSDMCContents() const {
@@ -40,11 +49,11 @@ PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const {
}
VirtualDir SDMCFactory::GetImageDirectory() const {
- return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album");
+ return GetOrCreateDirectoryRelative(sd_dir, "/Nintendo/Album");
}
u64 SDMCFactory::GetSDMCFreeSpace() const {
- return GetSDMCTotalSpace() - dir->GetSize();
+ return GetSDMCTotalSpace() - sd_dir->GetSize();
}
u64 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;
/// File system interface to the SDCard archive
class SDMCFactory {
public:
- explicit SDMCFactory(VirtualDir dir);
+ explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_);
~SDMCFactory();
ResultVal<VirtualDir> Open() const;
+ VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
VirtualDir GetSDMCContentDirectory() const;
RegisteredCache* GetSDMCContents() const;
@@ -32,7 +33,8 @@ public:
u64 GetSDMCTotalSpace() const;
private:
- VirtualDir dir;
+ VirtualDir sd_dir;
+ VirtualDir sd_mod_dir;
std::unique_ptr<RegisteredCache> contents;
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:
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "(STUBBED) called");
- std::vector<u8> output_params(ctx.GetWriteBufferSize());
+ std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0);
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
if (result.IsSuccess()) {
@@ -110,17 +110,19 @@ private:
void Start(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
+ const auto result = renderer->Start();
- rb.Push(ResultSuccess);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
void Stop(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
+ const auto result = renderer->Stop();
- rb.Push(ResultSuccess);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
}
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)
return bis_factory->GetModificationLoadRoot(title_id);
}
+FileSys::VirtualDir FileSystemController::GetSDMCModificationLoadRoot(u64 title_id) const {
+ LOG_TRACE(Service_FS, "Opening SDMC mod load root for tid={:016X}", title_id);
+
+ if (sdmc_factory == nullptr) {
+ return nullptr;
+ }
+
+ return sdmc_factory->GetSDMCModificationLoadRoot(title_id);
+}
+
FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const {
LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id);
@@ -733,20 +743,23 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
using YuzuPath = Common::FS::YuzuPath;
+ const auto sdmc_dir_path = Common::FS::GetYuzuPath(YuzuPath::SDMCDir);
+ const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
const auto rw_mode = FileSys::Mode::ReadWrite;
auto nand_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::NANDDir), rw_mode);
- auto sd_directory =
- vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::SDMCDir), rw_mode);
+ auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode);
auto load_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::LoadDir), FileSys::Mode::Read);
+ auto sd_load_directory =
+ vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::Mode::Read);
auto dump_directory =
vfs.OpenDirectory(Common::FS::GetYuzuPathString(YuzuPath::DumpDir), rw_mode);
if (bis_factory == nullptr) {
- bis_factory =
- std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
+ bis_factory = std::make_unique<FileSys::BISFactory>(
+ nand_directory, std::move(load_directory), std::move(dump_directory));
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND,
bis_factory->GetSystemNANDContents());
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND,
@@ -759,7 +772,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
if (sdmc_factory == nullptr) {
- sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
+ sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory),
+ std::move(sd_load_directory));
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
sdmc_factory->GetSDMCContents());
}
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:
FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const;
FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const;
+ FileSys::VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const;
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const;
FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const;
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:
down->SetCallback(callbacks);
left->SetCallback(callbacks);
right->SetCallback(callbacks);
+ modifier->SetCallback(callbacks);
}
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
vulkan_uint8.comp
)
-find_program(GLSLANGVALIDATOR "glslangValidator" REQUIRED)
+find_program(GLSLANGVALIDATOR "glslangValidator")
+if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND")
+ message(FATAL_ERROR "Required program `glslangValidator` not found.")
+endif()
set(GLSL_FLAGS "")
set(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:
[[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
+ [[nodiscard]] virtual std::string GetDeviceVendor() const = 0;
+
// Getter/setter functions:
// ------------------------
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() {
LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");
throw std::runtime_error{"Insufficient version"};
}
- const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
+ vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
const std::vector extensions = GetExtensions();
- const bool is_nvidia = vendor == "NVIDIA Corporation";
- const bool is_amd = vendor == "ATI Technologies Inc.";
- const bool is_intel = vendor == "Intel";
+ const bool is_nvidia = vendor_name == "NVIDIA Corporation";
+ const bool is_amd = vendor_name == "ATI Technologies Inc.";
+ const bool is_intel = vendor_name == "Intel";
#ifdef __unix__
const bool is_linux = true;
@@ -275,6 +275,56 @@ Device::Device() {
}
}
+std::string Device::GetVendorName() const {
+ if (vendor_name == "NVIDIA Corporation") {
+ return "NVIDIA";
+ }
+ if (vendor_name == "ATI Technologies Inc.") {
+ return "AMD";
+ }
+ if (vendor_name == "Intel") {
+ // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
+ // Simply return `INTEL` for those as well as the Windows driver.
+ return "INTEL";
+ }
+ if (vendor_name == "Intel Open Source Technology Center") {
+ return "I965";
+ }
+ if (vendor_name == "Mesa Project") {
+ return "I915";
+ }
+ if (vendor_name == "Mesa/X.org") {
+ // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
+ // MESA instead of one of those driver names.
+ return "MESA";
+ }
+ if (vendor_name == "AMD") {
+ return "RADEONSI";
+ }
+ if (vendor_name == "nouveau") {
+ return "NOUVEAU";
+ }
+ if (vendor_name == "X.Org") {
+ return "R600";
+ }
+ if (vendor_name == "Collabora Ltd") {
+ return "ZINK";
+ }
+ if (vendor_name == "Intel Corporation") {
+ return "OPENSWR";
+ }
+ if (vendor_name == "Microsoft Corporation") {
+ return "D3D12";
+ }
+ if (vendor_name == "NVIDIA") {
+ // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
+ // strategy would have returned `NVIDIA` here for this driver, the same result as the
+ // proprietary driver.
+ return "TEGRA";
+ }
+ return vendor_name;
+}
+
Device::Device(std::nullptr_t) {
max_uniform_buffers.fill(std::numeric_limits<u32>::max());
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:
explicit Device();
explicit Device(std::nullptr_t);
+ [[nodiscard]] std::string GetVendorName() const;
+
u32 GetMaxUniformBuffers(Tegra::Engines::ShaderType shader_type) const noexcept {
return max_uniform_buffers[static_cast<std::size_t>(shader_type)];
}
@@ -130,6 +132,7 @@ private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
+ std::string vendor_name;
std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{};
std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{};
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
[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset,
VideoCommon::SubresourceLayers subresource, GLenum target) {
switch (target) {
+ case GL_TEXTURE_1D:
+ return CopyOrigin{
+ .level = static_cast<GLint>(subresource.base_level),
+ .x = static_cast<GLint>(offset.x),
+ .y = static_cast<GLint>(0),
+ .z = static_cast<GLint>(0),
+ };
+ case GL_TEXTURE_1D_ARRAY:
+ return CopyOrigin{
+ .level = static_cast<GLint>(subresource.base_level),
+ .x = static_cast<GLint>(offset.x),
+ .y = static_cast<GLint>(0),
+ .z = static_cast<GLint>(subresource.base_layer),
+ };
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
return CopyOrigin{
@@ -366,6 +380,18 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
VideoCommon::SubresourceLayers dst_subresource,
GLenum target) {
switch (target) {
+ case GL_TEXTURE_1D:
+ return CopyRegion{
+ .width = static_cast<GLsizei>(extent.width),
+ .height = static_cast<GLsizei>(1),
+ .depth = static_cast<GLsizei>(1),
+ };
+ case GL_TEXTURE_1D_ARRAY:
+ return CopyRegion{
+ .width = static_cast<GLsizei>(extent.width),
+ .height = static_cast<GLsizei>(1),
+ .depth = static_cast<GLsizei>(dst_subresource.num_layers),
+ };
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
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:
return &rasterizer;
}
+ [[nodiscard]] std::string GetDeviceVendor() const override {
+ return device.GetVendorName();
+ }
+
private:
/// Initializes the OpenGL state and creates persistent objects.
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:
return &rasterizer;
}
+ [[nodiscard]] std::string GetDeviceVendor() const override {
+ return device.GetDriverName();
+ }
+
private:
void Report() const;
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
std::vector<ImageId> right_aliased_ids;
std::vector<ImageId> bad_overlap_ids;
ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) {
- if (info.type != overlap.info.type) {
- return;
- }
if (info.type == ImageType::Linear) {
if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) {
// 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
return (supported_usage & wanted_usage) == wanted_usage;
}
+std::string Device::GetDriverName() const {
+ switch (driver_id) {
+ case VK_DRIVER_ID_AMD_PROPRIETARY:
+ return "AMD";
+ case VK_DRIVER_ID_AMD_OPEN_SOURCE:
+ return "AMDVLK";
+ case VK_DRIVER_ID_MESA_RADV:
+ return "RADV";
+ case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
+ return "NVIDIA";
+ case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
+ return "INTEL";
+ case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
+ return "ANV";
+ case VK_DRIVER_ID_MESA_LLVMPIPE:
+ return "LAVAPIPE";
+ default:
+ return vendor_name;
+ }
+}
+
void Device::CheckSuitability(bool requires_swapchain) const {
std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions;
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:
/// Reports a shader to Nsight Aftermath.
void SaveShader(const std::vector<u32>& spirv) const;
+ /// Returns the name of the VkDriverId reported from Vulkan.
+ std::string GetDriverName() const;
+
/// Returns the dispatch loader with direct function pointers of the device.
const vk::DeviceDispatch& GetDispatchLoader() const {
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) {
}
void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) {
- MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
+ MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0);
ev->accept();
}
void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) {
- MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
+ MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0);
MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
ev->accept();
}
void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) {
- MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, 0);
+ MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale, 0);
MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
ev->accept();
}
void MicroProfileWidget::wheelEvent(QWheelEvent* ev) {
- MicroProfileMousePosition(ev->x() / x_scale, ev->y() / y_scale, ev->delta() / 120);
+ MicroProfileMousePosition(ev->pos().x() / x_scale, ev->pos().y() / y_scale,
+ ev->angleDelta().y() / 120);
ev->accept();
}
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
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
remove_menu->addSeparator();
QAction* remove_all_content = remove_menu->addAction(tr("Remove All Installed Contents"));
- QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
+ QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
+ QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
+ QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
context_menu.addSeparator();
@@ -570,8 +572,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
});
- connect(dump_romfs, &QAction::triggered,
- [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); });
+ connect(dump_romfs, &QAction::triggered, [this, program_id, path]() {
+ emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal);
+ });
+ connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
+ emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
+ });
connect(copy_tid, &QAction::triggered,
[this, program_id]() { emit CopyTIDRequested(program_id); });
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 {
CustomConfiguration,
};
+enum class DumpRomFSTarget {
+ Normal,
+ SDMC,
+};
+
enum class InstalledEntryType {
Game,
Update,
@@ -92,7 +97,7 @@ signals:
void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
- void DumpRomFSRequested(u64 program_id, const std::string& game_path);
+ void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
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
#include "input_common/main.h"
#include "util/overlay_dialog.h"
#include "video_core/gpu.h"
+#include "video_core/renderer_base.h"
#include "video_core/shader_notify.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
@@ -1422,8 +1423,12 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index, S
title_name = Common::FS::PathToUTF8String(
std::filesystem::path{filename.toStdU16String()}.filename());
}
+ const bool is_64bit = system.Kernel().CurrentProcess()->Is64BitProcess();
+ const auto instruction_set_suffix = is_64bit ? " (64-bit)" : " (32-bit)";
+ title_name += instruction_set_suffix;
LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
- UpdateWindowTitle(title_name, title_version);
+ const auto gpu_vendor = system.GPU().Renderer().GetDeviceVendor();
+ UpdateWindowTitle(title_name, title_version, gpu_vendor);
loading_screen->Prepare(system.GetAppLoader());
loading_screen->show();
@@ -1877,7 +1882,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g
}
}
-void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
+void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path,
+ DumpRomFSTarget target) {
const auto failed = [this] {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
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
return;
}
- const auto dump_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir);
+ const auto dump_dir =
+ target == DumpRomFSTarget::Normal
+ ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
+ : Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "atmosphere" / "contents";
const auto romfs_dir = fmt::format("{:016X}/romfs", *romfs_title_id);
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
if (*romfs_title_id == program_id) {
const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed};
- romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program);
+ romfs =
+ pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false);
} else {
romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
}
@@ -2852,8 +2862,8 @@ void GMainWindow::MigrateConfigFiles() {
}
}
-void GMainWindow::UpdateWindowTitle(const std::string& title_name,
- const std::string& title_version) {
+void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version,
+ std::string_view gpu_vendor) {
const auto branch_name = std::string(Common::g_scm_branch);
const auto description = std::string(Common::g_scm_desc);
const auto build_id = std::string(Common::g_build_id);
@@ -2866,7 +2876,8 @@ void GMainWindow::UpdateWindowTitle(const std::string& title_name,
if (title_name.empty()) {
setWindowTitle(QString::fromStdString(window_title));
} else {
- const auto run_title = fmt::format("{} | {} | {}", window_title, title_name, title_version);
+ const auto run_title =
+ fmt::format("{} | {} | {} | {}", window_title, title_name, title_version, gpu_vendor);
setWindowTitle(QString::fromStdString(run_title));
}
}
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;
class WaitTreeWidget;
enum class GameListOpenTarget;
enum class GameListRemoveTarget;
+enum class DumpRomFSTarget;
enum class InstalledEntryType;
class GameListPlaceholder;
@@ -244,7 +245,7 @@ private slots:
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
- void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
+ void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
@@ -287,8 +288,8 @@ private:
InstallResult InstallNSPXCI(const QString& filename);
InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
- void UpdateWindowTitle(const std::string& title_name = {},
- const std::string& title_version = {});
+ void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
+ std::string_view gpu_vendor = {});
void UpdateStatusBar();
void UpdateStatusButtons();
void UpdateUISettings();