diff options
Diffstat (limited to 'src')
31 files changed, 353 insertions, 149 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95d54dadc..d7f68618c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -85,6 +85,7 @@ if (MSVC) | |||
| 85 | /wd4100 # 'identifier': unreferenced formal parameter | 85 | /wd4100 # 'identifier': unreferenced formal parameter |
| 86 | /wd4324 # 'struct_name': structure was padded due to __declspec(align()) | 86 | /wd4324 # 'struct_name': structure was padded due to __declspec(align()) |
| 87 | /wd4201 # nonstandard extension used : nameless struct/union | 87 | /wd4201 # nonstandard extension used : nameless struct/union |
| 88 | /wd4702 # unreachable code (when used with LTO) | ||
| 88 | ) | 89 | ) |
| 89 | 90 | ||
| 90 | if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) | 91 | if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) |
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp index 3da342ea3..2e549bc6f 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp | |||
| @@ -88,8 +88,13 @@ MailboxMessage AudioRenderer::Receive(Direction dir, bool block) { | |||
| 88 | return mailbox.Receive(dir, block); | 88 | return mailbox.Receive(dir, block); |
| 89 | } | 89 | } |
| 90 | 90 | ||
| 91 | void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept { | 91 | void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, |
| 92 | command_buffers[session_id] = buffer; | 92 | u64 applet_resource_user_id, bool reset) noexcept { |
| 93 | command_buffers[session_id].buffer = buffer; | ||
| 94 | command_buffers[session_id].size = size; | ||
| 95 | command_buffers[session_id].time_limit = time_limit; | ||
| 96 | command_buffers[session_id].applet_resource_user_id = applet_resource_user_id; | ||
| 97 | command_buffers[session_id].reset_buffer = reset; | ||
| 93 | } | 98 | } |
| 94 | 99 | ||
| 95 | u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { | 100 | u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { |
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h index b225e10fb..3f5b7dca2 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h | |||
| @@ -75,7 +75,8 @@ public: | |||
| 75 | void Send(Direction dir, MailboxMessage message); | 75 | void Send(Direction dir, MailboxMessage message); |
| 76 | MailboxMessage Receive(Direction dir, bool block = true); | 76 | MailboxMessage Receive(Direction dir, bool block = true); |
| 77 | 77 | ||
| 78 | void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept; | 78 | void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, |
| 79 | u64 applet_resource_user_id, bool reset) noexcept; | ||
| 79 | u32 GetRemainCommandCount(s32 session_id) const noexcept; | 80 | u32 GetRemainCommandCount(s32 session_id) const noexcept; |
| 80 | void ClearRemainCommandCount(s32 session_id) noexcept; | 81 | void ClearRemainCommandCount(s32 session_id) noexcept; |
| 81 | u64 GetRenderingStartTick(s32 session_id) const noexcept; | 82 | u64 GetRenderingStartTick(s32 session_id) const noexcept; |
diff --git a/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp index acbc9100c..24e4d0496 100644 --- a/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp | |||
| @@ -37,11 +37,6 @@ u32 CommandListProcessor::GetRemainingCommandCount() const { | |||
| 37 | return command_count - processed_command_count; | 37 | return command_count - processed_command_count; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { | ||
| 41 | commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader)); | ||
| 42 | commands_buffer_size = size; | ||
| 43 | } | ||
| 44 | |||
| 45 | Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { | 40 | Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { |
| 46 | return stream; | 41 | return stream; |
| 47 | } | 42 | } |
diff --git a/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h index 9d6fe1851..4e5fb793e 100644 --- a/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h +++ b/src/audio_core/adsp/apps/audio_renderer/command_list_processor.h | |||
| @@ -57,14 +57,6 @@ public: | |||
| 57 | u32 GetRemainingCommandCount() const; | 57 | u32 GetRemainingCommandCount() const; |
| 58 | 58 | ||
| 59 | /** | 59 | /** |
| 60 | * Set the command buffer. | ||
| 61 | * | ||
| 62 | * @param buffer - The buffer to use. | ||
| 63 | * @param size - The size of the buffer. | ||
| 64 | */ | ||
| 65 | void SetBuffer(CpuAddr buffer, u64 size); | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Get the stream for this command list. | 60 | * Get the stream for this command list. |
| 69 | * | 61 | * |
| 70 | * @return The stream associated with this command list. | 62 | * @return The stream associated with this command list. |
diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp index 28e76fdcc..e7f82d3b3 100644 --- a/src/audio_core/renderer/command/data_source/adpcm.cpp +++ b/src/audio_core/renderer/command/data_source/adpcm.cpp | |||
| @@ -20,6 +20,12 @@ void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListPro | |||
| 20 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, | 20 | auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 21 | processor.sample_count)}; | 21 | processor.sample_count)}; |
| 22 | 22 | ||
| 23 | for (auto& wave_buffer : wave_buffers) { | ||
| 24 | wave_buffer.loop_start_offset = wave_buffer.start_offset; | ||
| 25 | wave_buffer.loop_end_offset = wave_buffer.end_offset; | ||
| 26 | wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; | ||
| 27 | } | ||
| 28 | |||
| 23 | DecodeFromWaveBuffersArgs args{ | 29 | DecodeFromWaveBuffersArgs args{ |
| 24 | .sample_format{SampleFormat::Adpcm}, | 30 | .sample_format{SampleFormat::Adpcm}, |
| 25 | .output{out_buffer}, | 31 | .output{out_buffer}, |
diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp index 762aec8ad..911dae3c1 100644 --- a/src/audio_core/renderer/command/data_source/decode.cpp +++ b/src/audio_core/renderer/command/data_source/decode.cpp | |||
| @@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | |||
| 123 | return 0; | 123 | return 0; |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | auto samples_to_process{ | 126 | auto start_pos{req.start_offset + req.offset}; |
| 127 | std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; | 127 | auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)}; |
| 128 | if (samples_to_process == 0) { | ||
| 129 | return 0; | ||
| 130 | } | ||
| 128 | 131 | ||
| 129 | auto samples_to_read{samples_to_process}; | 132 | auto samples_to_read{samples_to_process}; |
| 130 | auto start_pos{req.start_offset + req.offset}; | ||
| 131 | auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; | 133 | auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; |
| 132 | auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + | 134 | auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + |
| 133 | samples_remaining_in_frame}; | 135 | samples_remaining_in_frame}; |
| @@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, | |||
| 225 | * @param args - The wavebuffer data, and information for how to decode it. | 227 | * @param args - The wavebuffer data, and information for how to decode it. |
| 226 | */ | 228 | */ |
| 227 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { | 229 | void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { |
| 230 | static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, | ||
| 231 | auto& played_samples, auto& consumed) -> void { | ||
| 232 | voice_state.wave_buffer_valid[index] = false; | ||
| 233 | voice_state.loop_count = 0; | ||
| 234 | |||
| 235 | if (wavebuffer.stream_ended) { | ||
| 236 | played_samples = 0; | ||
| 237 | } | ||
| 238 | |||
| 239 | index = (index + 1) % MaxWaveBuffers; | ||
| 240 | consumed++; | ||
| 241 | }; | ||
| 228 | auto& voice_state{*args.voice_state}; | 242 | auto& voice_state{*args.voice_state}; |
| 229 | auto remaining_sample_count{args.sample_count}; | 243 | auto remaining_sample_count{args.sample_count}; |
| 230 | auto fraction{voice_state.fraction}; | 244 | auto fraction{voice_state.fraction}; |
| 231 | 245 | ||
| 232 | const auto sample_rate_ratio{ | 246 | const auto sample_rate_ratio{Common::FixedPoint<49, 15>( |
| 233 | (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * | 247 | (f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)}; |
| 234 | args.pitch}; | ||
| 235 | const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; | 248 | const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; |
| 236 | 249 | ||
| 237 | if (size_required < 0) { | 250 | if (size_required < 0) { |
| @@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf | |||
| 298 | auto end_offset{wavebuffer.end_offset}; | 311 | auto end_offset{wavebuffer.end_offset}; |
| 299 | 312 | ||
| 300 | if (wavebuffer.loop && voice_state.loop_count > 0 && | 313 | if (wavebuffer.loop && voice_state.loop_count > 0 && |
| 301 | wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && | ||
| 302 | wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { | 314 | wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { |
| 303 | start_offset = wavebuffer.loop_start_offset; | 315 | start_offset = wavebuffer.loop_start_offset; |
| 304 | end_offset = wavebuffer.loop_end_offset; | 316 | end_offset = wavebuffer.loop_end_offset; |
| 305 | } | 317 | } |
| 306 | 318 | ||
| 307 | DecodeArg decode_arg{.buffer{wavebuffer.buffer}, | 319 | DecodeArg decode_arg{ |
| 308 | .buffer_size{wavebuffer.buffer_size}, | 320 | .buffer{wavebuffer.buffer}, |
| 309 | .start_offset{start_offset}, | 321 | .buffer_size{wavebuffer.buffer_size}, |
| 310 | .end_offset{end_offset}, | 322 | .start_offset{start_offset}, |
| 311 | .channel_count{args.channel_count}, | 323 | .end_offset{end_offset}, |
| 312 | .coefficients{}, | 324 | .channel_count{args.channel_count}, |
| 313 | .adpcm_context{nullptr}, | 325 | .coefficients{}, |
| 314 | .target_channel{args.channel}, | 326 | .adpcm_context{nullptr}, |
| 315 | .offset{offset}, | 327 | .target_channel{args.channel}, |
| 316 | .samples_to_read{samples_to_read - samples_read}}; | 328 | .offset{offset}, |
| 329 | .samples_to_read{samples_to_read - samples_read}, | ||
| 330 | }; | ||
| 317 | 331 | ||
| 318 | s32 samples_decoded{0}; | 332 | s32 samples_decoded{0}; |
| 319 | 333 | ||
| @@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf | |||
| 350 | temp_buffer_pos += samples_decoded; | 364 | temp_buffer_pos += samples_decoded; |
| 351 | offset += samples_decoded; | 365 | offset += samples_decoded; |
| 352 | 366 | ||
| 353 | if (samples_decoded == 0 || offset >= end_offset - start_offset) { | 367 | if (samples_decoded && offset < end_offset - start_offset) { |
| 354 | offset = 0; | 368 | continue; |
| 355 | if (!wavebuffer.loop) { | 369 | } |
| 356 | voice_state.wave_buffer_valid[wavebuffer_index] = false; | 370 | |
| 357 | voice_state.loop_count = 0; | 371 | offset = 0; |
| 358 | 372 | if (wavebuffer.loop) { | |
| 359 | if (wavebuffer.stream_ended) { | 373 | voice_state.loop_count++; |
| 360 | played_sample_count = 0; | 374 | if (wavebuffer.loop_count >= 0 && |
| 361 | } | 375 | (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { |
| 362 | 376 | EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count, | |
| 363 | wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | 377 | wavebuffers_consumed); |
| 364 | wavebuffers_consumed++; | 378 | } |
| 365 | } else { | 379 | |
| 366 | voice_state.loop_count++; | 380 | if (samples_decoded == 0) { |
| 367 | if (wavebuffer.loop_count >= 0 && | 381 | is_buffer_starved = true; |
| 368 | (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { | 382 | break; |
| 369 | voice_state.wave_buffer_valid[wavebuffer_index] = false; | 383 | } |
| 370 | voice_state.loop_count = 0; | 384 | |
| 371 | 385 | if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { | |
| 372 | if (wavebuffer.stream_ended) { | 386 | played_sample_count = 0; |
| 373 | played_sample_count = 0; | ||
| 374 | } | ||
| 375 | |||
| 376 | wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; | ||
| 377 | wavebuffers_consumed++; | ||
| 378 | } | ||
| 379 | |||
| 380 | if (samples_decoded == 0) { | ||
| 381 | is_buffer_starved = true; | ||
| 382 | break; | ||
| 383 | } | ||
| 384 | |||
| 385 | if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { | ||
| 386 | played_sample_count = 0; | ||
| 387 | } | ||
| 388 | } | 387 | } |
| 388 | } else { | ||
| 389 | EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count, | ||
| 390 | wavebuffers_consumed); | ||
| 389 | } | 391 | } |
| 390 | } | 392 | } |
| 391 | 393 | ||
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp index 5cc0797f4..d1f685656 100644 --- a/src/audio_core/renderer/command/data_source/pcm_float.cpp +++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp | |||
| @@ -21,6 +21,12 @@ void PcmFloatDataSourceVersion1Command::Process( | |||
| 21 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | 21 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 22 | processor.sample_count); | 22 | processor.sample_count); |
| 23 | 23 | ||
| 24 | for (auto& wave_buffer : wave_buffers) { | ||
| 25 | wave_buffer.loop_start_offset = wave_buffer.start_offset; | ||
| 26 | wave_buffer.loop_end_offset = wave_buffer.end_offset; | ||
| 27 | wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; | ||
| 28 | } | ||
| 29 | |||
| 24 | DecodeFromWaveBuffersArgs args{ | 30 | DecodeFromWaveBuffersArgs args{ |
| 25 | .sample_format{SampleFormat::PcmFloat}, | 31 | .sample_format{SampleFormat::PcmFloat}, |
| 26 | .output{out_buffer}, | 32 | .output{out_buffer}, |
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp index 649993068..c89a5aaac 100644 --- a/src/audio_core/renderer/command/data_source/pcm_int16.cpp +++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp | |||
| @@ -23,6 +23,12 @@ void PcmInt16DataSourceVersion1Command::Process( | |||
| 23 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, | 23 | auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
| 24 | processor.sample_count); | 24 | processor.sample_count); |
| 25 | 25 | ||
| 26 | for (auto& wave_buffer : wave_buffers) { | ||
| 27 | wave_buffer.loop_start_offset = wave_buffer.start_offset; | ||
| 28 | wave_buffer.loop_end_offset = wave_buffer.end_offset; | ||
| 29 | wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; | ||
| 30 | } | ||
| 31 | |||
| 26 | DecodeFromWaveBuffersArgs args{ | 32 | DecodeFromWaveBuffersArgs args{ |
| 27 | .sample_format{SampleFormat::PcmInt16}, | 33 | .sample_format{SampleFormat::PcmInt16}, |
| 28 | .output{out_buffer}, | 34 | .output{out_buffer}, |
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index 8f02754c5..d29754634 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp | |||
| @@ -609,17 +609,11 @@ void System::SendCommandToDsp() { | |||
| 609 | time_limit_percent = 70.0f; | 609 | time_limit_percent = 70.0f; |
| 610 | } | 610 | } |
| 611 | 611 | ||
| 612 | AudioRenderer::CommandBuffer command_buffer{ | 612 | auto time_limit{ |
| 613 | .buffer{translated_addr}, | 613 | static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * |
| 614 | .size{command_size}, | 614 | (static_cast<f32>(render_time_limit_percent) / 100.0f))}; |
| 615 | .time_limit{ | 615 | audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit, |
| 616 | static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * | 616 | applet_resource_user_id, reset_command_buffers); |
| 617 | (static_cast<f32>(render_time_limit_percent) / 100.0f))}, | ||
| 618 | .applet_resource_user_id{applet_resource_user_id}, | ||
| 619 | .reset_buffer{reset_command_buffers}, | ||
| 620 | }; | ||
| 621 | |||
| 622 | audio_renderer.SetCommandBuffer(session_id, command_buffer); | ||
| 623 | reset_command_buffers = false; | 617 | reset_command_buffers = false; |
| 624 | command_buffer_size = command_size; | 618 | command_buffer_size = command_size; |
| 625 | if (remaining_command_count == 0) { | 619 | if (remaining_command_count == 0) { |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 6d2badf76..34877b461 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -151,6 +151,10 @@ add_library(common STATIC | |||
| 151 | zstd_compression.h | 151 | zstd_compression.h |
| 152 | ) | 152 | ) |
| 153 | 153 | ||
| 154 | if (YUZU_ENABLE_PORTABLE) | ||
| 155 | add_compile_definitions(YUZU_ENABLE_PORTABLE) | ||
| 156 | endif() | ||
| 157 | |||
| 154 | if (WIN32) | 158 | if (WIN32) |
| 155 | target_sources(common PRIVATE | 159 | target_sources(common PRIVATE |
| 156 | windows/timer_resolution.cpp | 160 | windows/timer_resolution.cpp |
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index d71cfacc6..dce219fcf 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp | |||
| @@ -88,8 +88,9 @@ public: | |||
| 88 | fs::path yuzu_path_config; | 88 | fs::path yuzu_path_config; |
| 89 | 89 | ||
| 90 | #ifdef _WIN32 | 90 | #ifdef _WIN32 |
| 91 | #ifdef YUZU_ENABLE_PORTABLE | ||
| 91 | yuzu_path = GetExeDirectory() / PORTABLE_DIR; | 92 | yuzu_path = GetExeDirectory() / PORTABLE_DIR; |
| 92 | 93 | #endif | |
| 93 | if (!IsDir(yuzu_path)) { | 94 | if (!IsDir(yuzu_path)) { |
| 94 | yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; | 95 | yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; |
| 95 | } | 96 | } |
| @@ -101,8 +102,9 @@ public: | |||
| 101 | yuzu_path_cache = yuzu_path / CACHE_DIR; | 102 | yuzu_path_cache = yuzu_path / CACHE_DIR; |
| 102 | yuzu_path_config = yuzu_path / CONFIG_DIR; | 103 | yuzu_path_config = yuzu_path / CONFIG_DIR; |
| 103 | #else | 104 | #else |
| 105 | #ifdef YUZU_ENABLE_PORTABLE | ||
| 104 | yuzu_path = GetCurrentDir() / PORTABLE_DIR; | 106 | yuzu_path = GetCurrentDir() / PORTABLE_DIR; |
| 105 | 107 | #endif | |
| 106 | if (Exists(yuzu_path) && IsDir(yuzu_path)) { | 108 | if (Exists(yuzu_path) && IsDir(yuzu_path)) { |
| 107 | yuzu_path_cache = yuzu_path / CACHE_DIR; | 109 | yuzu_path_cache = yuzu_path / CACHE_DIR; |
| 108 | yuzu_path_config = yuzu_path / CONFIG_DIR; | 110 | yuzu_path_config = yuzu_path / CONFIG_DIR; |
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 07c65dc1a..b6e355622 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) { | |||
| 108 | return "unknown"; | 108 | return "unknown"; |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | constexpr std::array<const char*, 66> RESULT_MESSAGES{ | 111 | constexpr std::array<const char*, 68> RESULT_MESSAGES{ |
| 112 | "The operation completed successfully.", | 112 | "The operation completed successfully.", |
| 113 | "The loader requested to load is already loaded.", | 113 | "The loader requested to load is already loaded.", |
| 114 | "The operation is not implemented.", | 114 | "The operation is not implemented.", |
| @@ -175,6 +175,8 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 175 | "The KIP BLZ decompression of the section failed unexpectedly.", | 175 | "The KIP BLZ decompression of the section failed unexpectedly.", |
| 176 | "The INI file has a bad header.", | 176 | "The INI file has a bad header.", |
| 177 | "The INI file contains more than the maximum allowable number of KIP files.", | 177 | "The INI file contains more than the maximum allowable number of KIP files.", |
| 178 | "Integrity verification could not be performed for this file.", | ||
| 179 | "Integrity verification failed.", | ||
| 178 | }; | 180 | }; |
| 179 | 181 | ||
| 180 | std::string GetResultStatusString(ResultStatus status) { | 182 | std::string GetResultStatusString(ResultStatus status) { |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 721eb8e8c..b4828f7cd 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <functional> | ||
| 6 | #include <iosfwd> | 7 | #include <iosfwd> |
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <optional> | 9 | #include <optional> |
| @@ -132,6 +133,8 @@ enum class ResultStatus : u16 { | |||
| 132 | ErrorBLZDecompressionFailed, | 133 | ErrorBLZDecompressionFailed, |
| 133 | ErrorBadINIHeader, | 134 | ErrorBadINIHeader, |
| 134 | ErrorINITooManyKIPs, | 135 | ErrorINITooManyKIPs, |
| 136 | ErrorIntegrityVerificationNotImplemented, | ||
| 137 | ErrorIntegrityVerificationFailed, | ||
| 135 | }; | 138 | }; |
| 136 | 139 | ||
| 137 | std::string GetResultStatusString(ResultStatus status); | 140 | std::string GetResultStatusString(ResultStatus status); |
| @@ -170,6 +173,13 @@ public: | |||
| 170 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; | 173 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; |
| 171 | 174 | ||
| 172 | /** | 175 | /** |
| 176 | * Try to verify the integrity of the file. | ||
| 177 | */ | ||
| 178 | virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 179 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 180 | } | ||
| 181 | |||
| 182 | /** | ||
| 173 | * Get the code (typically .code section) of the application | 183 | * Get the code (typically .code section) of the application |
| 174 | * | 184 | * |
| 175 | * @param[out] buffer Reference to buffer to store data | 185 | * @param[out] buffer Reference to buffer to store data |
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 09d40e695..4feb6968a 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #include <utility> | 4 | #include <utility> |
| 5 | 5 | ||
| 6 | #include "common/hex_util.h" | ||
| 7 | #include "common/scope_exit.h" | ||
| 6 | #include "core/core.h" | 8 | #include "core/core.h" |
| 7 | #include "core/file_sys/content_archive.h" | 9 | #include "core/file_sys/content_archive.h" |
| 8 | #include "core/file_sys/nca_metadata.h" | 10 | #include "core/file_sys/nca_metadata.h" |
| @@ -12,6 +14,7 @@ | |||
| 12 | #include "core/hle/service/filesystem/filesystem.h" | 14 | #include "core/hle/service/filesystem/filesystem.h" |
| 13 | #include "core/loader/deconstructed_rom_directory.h" | 15 | #include "core/loader/deconstructed_rom_directory.h" |
| 14 | #include "core/loader/nca.h" | 16 | #include "core/loader/nca.h" |
| 17 | #include "mbedtls/sha256.h" | ||
| 15 | 18 | ||
| 16 | namespace Loader { | 19 | namespace Loader { |
| 17 | 20 | ||
| @@ -80,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 80 | return load_result; | 83 | return load_result; |
| 81 | } | 84 | } |
| 82 | 85 | ||
| 86 | ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 87 | using namespace Common::Literals; | ||
| 88 | |||
| 89 | constexpr size_t NcaFileNameWithHashLength = 36; | ||
| 90 | constexpr size_t NcaFileNameHashLength = 32; | ||
| 91 | constexpr size_t NcaSha256HashLength = 32; | ||
| 92 | constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2; | ||
| 93 | |||
| 94 | // Get the file name. | ||
| 95 | const auto name = file->GetName(); | ||
| 96 | |||
| 97 | // We won't try to verify meta NCAs. | ||
| 98 | if (name.ends_with(".cnmt.nca")) { | ||
| 99 | return ResultStatus::Success; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Check if we can verify this file. NCAs should be named after their hashes. | ||
| 103 | if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) { | ||
| 104 | LOG_WARNING(Loader, "Unable to validate NCA with name {}", name); | ||
| 105 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Get the expected truncated hash of the NCA. | ||
| 109 | const auto input_hash = | ||
| 110 | Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false); | ||
| 111 | |||
| 112 | // Declare buffer to read into. | ||
| 113 | std::vector<u8> buffer(4_MiB); | ||
| 114 | |||
| 115 | // Initialize sha256 verification context. | ||
| 116 | mbedtls_sha256_context ctx; | ||
| 117 | mbedtls_sha256_init(&ctx); | ||
| 118 | mbedtls_sha256_starts_ret(&ctx, 0); | ||
| 119 | |||
| 120 | // Ensure we maintain a clean state on exit. | ||
| 121 | SCOPE_EXIT({ mbedtls_sha256_free(&ctx); }); | ||
| 122 | |||
| 123 | // Declare counters. | ||
| 124 | const size_t total_size = file->GetSize(); | ||
| 125 | size_t processed_size = 0; | ||
| 126 | |||
| 127 | // Begin iterating the file. | ||
| 128 | while (processed_size < total_size) { | ||
| 129 | // Refill the buffer. | ||
| 130 | const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size); | ||
| 131 | const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size); | ||
| 132 | |||
| 133 | // Update the hash function with the buffer contents. | ||
| 134 | mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size); | ||
| 135 | |||
| 136 | // Update counters. | ||
| 137 | processed_size += read_size; | ||
| 138 | |||
| 139 | // Call the progress function. | ||
| 140 | if (!progress_callback(processed_size, total_size)) { | ||
| 141 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | // Finalize context and compute the output hash. | ||
| 146 | std::array<u8, NcaSha256HashLength> output_hash; | ||
| 147 | mbedtls_sha256_finish_ret(&ctx, output_hash.data()); | ||
| 148 | |||
| 149 | // Compare to expected. | ||
| 150 | if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) { | ||
| 151 | LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name); | ||
| 152 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 153 | } | ||
| 154 | |||
| 155 | // File verified. | ||
| 156 | return ResultStatus::Success; | ||
| 157 | } | ||
| 158 | |||
| 83 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | 159 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { |
| 84 | if (nca == nullptr) { | 160 | if (nca == nullptr) { |
| 85 | return ResultStatus::ErrorNotInitialized; | 161 | return ResultStatus::ErrorNotInitialized; |
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index cf356ce63..96779e27f 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h | |||
| @@ -39,6 +39,8 @@ public: | |||
| 39 | 39 | ||
| 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 41 | 41 | ||
| 42 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 43 | |||
| 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 44 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 45 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | 46 | ||
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index f9b2549a3..fe2af1ae6 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp | |||
| @@ -117,6 +117,42 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S | |||
| 117 | return result; | 117 | return result; |
| 118 | } | 118 | } |
| 119 | 119 | ||
| 120 | ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 121 | // Extracted-type NSPs can't be verified. | ||
| 122 | if (nsp->IsExtractedType()) { | ||
| 123 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Get list of all NCAs. | ||
| 127 | const auto ncas = nsp->GetNCAsCollapsed(); | ||
| 128 | |||
| 129 | size_t total_size = 0; | ||
| 130 | size_t processed_size = 0; | ||
| 131 | |||
| 132 | // Loop over NCAs, collecting the total size to verify. | ||
| 133 | for (const auto& nca : ncas) { | ||
| 134 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 135 | } | ||
| 136 | |||
| 137 | // Loop over NCAs again, verifying each. | ||
| 138 | for (const auto& nca : ncas) { | ||
| 139 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 140 | |||
| 141 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 142 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 143 | }; | ||
| 144 | |||
| 145 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 146 | if (verification_result != ResultStatus::Success) { | ||
| 147 | return verification_result; | ||
| 148 | } | ||
| 149 | |||
| 150 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 151 | } | ||
| 152 | |||
| 153 | return ResultStatus::Success; | ||
| 154 | } | ||
| 155 | |||
| 120 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { | 156 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 121 | return secondary_loader->ReadRomFS(out_file); | 157 | return secondary_loader->ReadRomFS(out_file); |
| 122 | } | 158 | } |
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 79df4586a..7ce436c67 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h | |||
| @@ -45,6 +45,8 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 50 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 3a76bc788..12d72c380 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp | |||
| @@ -85,6 +85,40 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S | |||
| 85 | return result; | 85 | return result; |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 89 | // Verify secure partition, as it is the only thing we can process. | ||
| 90 | auto secure_partition = xci->GetSecurePartitionNSP(); | ||
| 91 | |||
| 92 | // Get list of all NCAs. | ||
| 93 | const auto ncas = secure_partition->GetNCAsCollapsed(); | ||
| 94 | |||
| 95 | size_t total_size = 0; | ||
| 96 | size_t processed_size = 0; | ||
| 97 | |||
| 98 | // Loop over NCAs, collecting the total size to verify. | ||
| 99 | for (const auto& nca : ncas) { | ||
| 100 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Loop over NCAs again, verifying each. | ||
| 104 | for (const auto& nca : ncas) { | ||
| 105 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 106 | |||
| 107 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 108 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 109 | }; | ||
| 110 | |||
| 111 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 112 | if (verification_result != ResultStatus::Success) { | ||
| 113 | return verification_result; | ||
| 114 | } | ||
| 115 | |||
| 116 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 117 | } | ||
| 118 | |||
| 119 | return ResultStatus::Success; | ||
| 120 | } | ||
| 121 | |||
| 88 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { | 122 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 89 | return nca_loader->ReadRomFS(out_file); | 123 | return nca_loader->ReadRomFS(out_file); |
| 90 | } | 124 | } |
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index ff05e6f62..b02e136d3 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h | |||
| @@ -45,6 +45,8 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 50 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index bec5db173..238fb40e3 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | |||
| @@ -74,6 +74,11 @@ spv::ImageFormat GetImageFormat(ImageFormat format) { | |||
| 74 | throw InvalidArgument("Invalid image format {}", format); | 74 | throw InvalidArgument("Invalid image format {}", format); |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) { | ||
| 78 | const auto spv_format = GetImageFormat(format); | ||
| 79 | return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format; | ||
| 80 | } | ||
| 81 | |||
| 77 | Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { | 82 | Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { |
| 78 | const spv::ImageFormat format{GetImageFormat(desc.format)}; | 83 | const spv::ImageFormat format{GetImageFormat(desc.format)}; |
| 79 | const Id type{ctx.U32[1]}; | 84 | const Id type{ctx.U32[1]}; |
| @@ -1271,7 +1276,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) { | |||
| 1271 | if (desc.count != 1) { | 1276 | if (desc.count != 1) { |
| 1272 | throw NotImplementedException("Array of image buffers"); | 1277 | throw NotImplementedException("Array of image buffers"); |
| 1273 | } | 1278 | } |
| 1274 | const spv::ImageFormat format{GetImageFormat(desc.format)}; | 1279 | const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; |
| 1275 | const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; | 1280 | const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; |
| 1276 | const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; | 1281 | const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; |
| 1277 | const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; | 1282 | const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 454bb66a4..c4c30d807 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp | |||
| @@ -66,21 +66,6 @@ std::string BuildCommaSeparatedExtensions( | |||
| 66 | return fmt::format("{}", fmt::join(available_extensions, ",")); | 66 | return fmt::format("{}", fmt::join(available_extensions, ",")); |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | DebugCallback MakeDebugCallback(const vk::Instance& instance, const vk::InstanceDispatch& dld) { | ||
| 70 | if (!Settings::values.renderer_debug) { | ||
| 71 | return DebugCallback{}; | ||
| 72 | } | ||
| 73 | const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld); | ||
| 74 | const auto it = std::ranges::find_if(*properties, [](const auto& prop) { | ||
| 75 | return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0; | ||
| 76 | }); | ||
| 77 | if (it != properties->end()) { | ||
| 78 | return CreateDebugUtilsCallback(instance); | ||
| 79 | } else { | ||
| 80 | return CreateDebugReportCallback(instance); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | } // Anonymous namespace | 69 | } // Anonymous namespace |
| 85 | 70 | ||
| 86 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, | 71 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, |
| @@ -103,7 +88,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, | |||
| 103 | cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), | 88 | cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), |
| 104 | instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, | 89 | instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, |
| 105 | Settings::values.renderer_debug.GetValue())), | 90 | Settings::values.renderer_debug.GetValue())), |
| 106 | debug_callback(MakeDebugCallback(instance, dld)), | 91 | debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance) |
| 92 | : vk::DebugUtilsMessenger{}), | ||
| 107 | surface(CreateSurface(instance, render_window.GetWindowInfo())), | 93 | surface(CreateSurface(instance, render_window.GetWindowInfo())), |
| 108 | device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), | 94 | device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), |
| 109 | scheduler(device, state_tracker), | 95 | scheduler(device, state_tracker), |
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 89e98425e..590bc1c64 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h | |||
| @@ -35,8 +35,6 @@ class GPU; | |||
| 35 | 35 | ||
| 36 | namespace Vulkan { | 36 | namespace Vulkan { |
| 37 | 37 | ||
| 38 | using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>; | ||
| 39 | |||
| 40 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, | 38 | Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, |
| 41 | VkSurfaceKHR surface); | 39 | VkSurfaceKHR surface); |
| 42 | 40 | ||
| @@ -75,7 +73,7 @@ private: | |||
| 75 | vk::InstanceDispatch dld; | 73 | vk::InstanceDispatch dld; |
| 76 | 74 | ||
| 77 | vk::Instance instance; | 75 | vk::Instance instance; |
| 78 | DebugCallback debug_callback; | 76 | vk::DebugUtilsMessenger debug_messenger; |
| 79 | vk::SurfaceKHR surface; | 77 | vk::SurfaceKHR surface; |
| 80 | 78 | ||
| 81 | ScreenInfo screen_info; | 79 | ScreenInfo screen_info; |
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp index 67e8065a4..448df2d3a 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp | |||
| @@ -63,22 +63,6 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, | |||
| 63 | return VK_FALSE; | 63 | return VK_FALSE; |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, | ||
| 67 | uint64_t object, size_t location, int32_t messageCode, | ||
| 68 | const char* pLayerPrefix, const char* pMessage, void* pUserData) { | ||
| 69 | const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags); | ||
| 70 | const std::string_view message{pMessage}; | ||
| 71 | if (severity & VK_DEBUG_REPORT_ERROR_BIT_EXT) { | ||
| 72 | LOG_CRITICAL(Render_Vulkan, "{}", message); | ||
| 73 | } else if (severity & VK_DEBUG_REPORT_WARNING_BIT_EXT) { | ||
| 74 | LOG_WARNING(Render_Vulkan, "{}", message); | ||
| 75 | } else if (severity & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { | ||
| 76 | LOG_INFO(Render_Vulkan, "{}", message); | ||
| 77 | } else if (severity & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { | ||
| 78 | LOG_DEBUG(Render_Vulkan, "{}", message); | ||
| 79 | } | ||
| 80 | return VK_FALSE; | ||
| 81 | } | ||
| 82 | } // Anonymous namespace | 66 | } // Anonymous namespace |
| 83 | 67 | ||
| 84 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { | 68 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { |
| @@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { | |||
| 98 | }); | 82 | }); |
| 99 | } | 83 | } |
| 100 | 84 | ||
| 101 | vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance) { | ||
| 102 | return instance.CreateDebugReportCallback({ | ||
| 103 | .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, | ||
| 104 | .pNext = nullptr, | ||
| 105 | .flags = VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT | | ||
| 106 | VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT, | ||
| 107 | .pfnCallback = DebugReportCallback, | ||
| 108 | .pUserData = nullptr, | ||
| 109 | }); | ||
| 110 | } | ||
| 111 | |||
| 112 | } // namespace Vulkan | 85 | } // namespace Vulkan |
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.h b/src/video_core/vulkan_common/vulkan_debug_callback.h index a8af7b406..5e940782f 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.h +++ b/src/video_core/vulkan_common/vulkan_debug_callback.h | |||
| @@ -9,6 +9,4 @@ namespace Vulkan { | |||
| 9 | 9 | ||
| 10 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); | 10 | vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); |
| 11 | 11 | ||
| 12 | vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance); | ||
| 13 | |||
| 14 | } // namespace Vulkan | 12 | } // namespace Vulkan |
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp index bc16145be..180657a75 100644 --- a/src/video_core/vulkan_common/vulkan_instance.cpp +++ b/src/video_core/vulkan_common/vulkan_instance.cpp | |||
| @@ -76,11 +76,9 @@ namespace { | |||
| 76 | extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); | 76 | extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); |
| 77 | } | 77 | } |
| 78 | #endif | 78 | #endif |
| 79 | if (enable_validation) { | 79 | if (enable_validation && |
| 80 | const bool debug_utils = | 80 | AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) { |
| 81 | AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}); | 81 | extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); |
| 82 | extensions.push_back(debug_utils ? VK_EXT_DEBUG_UTILS_EXTENSION_NAME | ||
| 83 | : VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | ||
| 84 | } | 82 | } |
| 85 | return extensions; | 83 | return extensions; |
| 86 | } | 84 | } |
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 8622dc184..fd6bebf0f 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp | |||
| @@ -193,14 +193,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() { | |||
| 193 | : vsync_mode_combobox_enum_map[current_index]; | 193 | : vsync_mode_combobox_enum_map[current_index]; |
| 194 | int index{}; | 194 | int index{}; |
| 195 | const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device | 195 | const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device |
| 196 | if (device == -1) { | ||
| 197 | // Invalid device | ||
| 198 | return; | ||
| 199 | } | ||
| 200 | 196 | ||
| 201 | const auto& present_modes = //< relevant vector of present modes for the selected device or API | 197 | const auto& present_modes = //< relevant vector of present modes for the selected device or API |
| 202 | backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] | 198 | backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device] |
| 203 | : default_present_modes; | 199 | : default_present_modes; |
| 204 | 200 | ||
| 205 | vsync_mode_combobox->clear(); | 201 | vsync_mode_combobox->clear(); |
| 206 | vsync_mode_combobox_enum_map.clear(); | 202 | vsync_mode_combobox_enum_map.clear(); |
| @@ -497,11 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() { | |||
| 497 | } | 493 | } |
| 498 | 494 | ||
| 499 | Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { | 495 | Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { |
| 500 | if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { | 496 | const auto selected_backend = [&]() { |
| 501 | return Settings::values.renderer_backend.GetValue(true); | 497 | if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { |
| 498 | return Settings::values.renderer_backend.GetValue(true); | ||
| 499 | } | ||
| 500 | return static_cast<Settings::RendererBackend>( | ||
| 501 | combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index()) | ||
| 502 | .at(api_combobox->currentIndex()) | ||
| 503 | .first); | ||
| 504 | }(); | ||
| 505 | |||
| 506 | if (selected_backend == Settings::RendererBackend::Vulkan && | ||
| 507 | UISettings::values.has_broken_vulkan) { | ||
| 508 | return Settings::RendererBackend::OpenGL; | ||
| 502 | } | 509 | } |
| 503 | return static_cast<Settings::RendererBackend>( | 510 | return selected_backend; |
| 504 | combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index()) | ||
| 505 | .at(api_combobox->currentIndex()) | ||
| 506 | .first); | ||
| 507 | } | 511 | } |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index c29d762c2..f254c1e1c 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -557,6 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 557 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); | 557 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); |
| 558 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); | 558 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); |
| 559 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); | 559 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); |
| 560 | QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); | ||
| 560 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | 561 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); |
| 561 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 562 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 562 | #ifndef WIN32 | 563 | #ifndef WIN32 |
| @@ -630,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 630 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { | 631 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { |
| 631 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); | 632 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); |
| 632 | }); | 633 | }); |
| 634 | connect(verify_integrity, &QAction::triggered, | ||
| 635 | [this, path]() { emit VerifyIntegrityRequested(path); }); | ||
| 633 | connect(copy_tid, &QAction::triggered, | 636 | connect(copy_tid, &QAction::triggered, |
| 634 | [this, program_id]() { emit CopyTIDRequested(program_id); }); | 637 | [this, program_id]() { emit CopyTIDRequested(program_id); }); |
| 635 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { | 638 | 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 45d6dee88..1fcbbf0ba 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -114,6 +114,7 @@ signals: | |||
| 114 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 114 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 115 | const std::string& game_path); | 115 | const std::string& game_path); |
| 116 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 116 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 117 | void VerifyIntegrityRequested(const std::string& game_path); | ||
| 117 | void CopyTIDRequested(u64 program_id); | 118 | void CopyTIDRequested(u64 program_id); |
| 118 | void CreateShortcut(u64 program_id, const std::string& game_path, | 119 | void CreateShortcut(u64 program_id, const std::string& game_path, |
| 119 | GameListShortcutTarget target); | 120 | GameListShortcutTarget target); |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 538174462..1540fe1c1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -442,8 +442,13 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan | |||
| 442 | "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" | 442 | "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" |
| 443 | "here for instructions to fix the issue</a>.")); | 443 | "here for instructions to fix the issue</a>.")); |
| 444 | 444 | ||
| 445 | #ifdef HAS_OPENGL | ||
| 445 | Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; | 446 | Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; |
| 447 | #else | ||
| 448 | Settings::values.renderer_backend = Settings::RendererBackend::Null; | ||
| 449 | #endif | ||
| 446 | 450 | ||
| 451 | UpdateAPIText(); | ||
| 447 | renderer_status_button->setDisabled(true); | 452 | renderer_status_button->setDisabled(true); |
| 448 | renderer_status_button->setChecked(false); | 453 | renderer_status_button->setChecked(false); |
| 449 | } else { | 454 | } else { |
| @@ -1447,6 +1452,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 1447 | &GMainWindow::OnGameListRemoveInstalledEntry); | 1452 | &GMainWindow::OnGameListRemoveInstalledEntry); |
| 1448 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | 1453 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); |
| 1449 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 1454 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 1455 | connect(game_list, &GameList::VerifyIntegrityRequested, this, | ||
| 1456 | &GMainWindow::OnGameListVerifyIntegrity); | ||
| 1450 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 1457 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 1451 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 1458 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 1452 | &GMainWindow::OnGameListNavigateToGamedbEntry); | 1459 | &GMainWindow::OnGameListNavigateToGamedbEntry); |
| @@ -2713,6 +2720,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2713 | } | 2720 | } |
| 2714 | } | 2721 | } |
| 2715 | 2722 | ||
| 2723 | void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | ||
| 2724 | const auto NotImplemented = [this] { | ||
| 2725 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), | ||
| 2726 | tr("File contents were not checked for validity.")); | ||
| 2727 | }; | ||
| 2728 | const auto Failed = [this] { | ||
| 2729 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2730 | tr("File contents may be corrupt.")); | ||
| 2731 | }; | ||
| 2732 | |||
| 2733 | const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 2734 | if (loader == nullptr) { | ||
| 2735 | NotImplemented(); | ||
| 2736 | return; | ||
| 2737 | } | ||
| 2738 | |||
| 2739 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||
| 2740 | progress.setWindowModality(Qt::WindowModal); | ||
| 2741 | progress.setMinimumDuration(100); | ||
| 2742 | progress.setAutoClose(false); | ||
| 2743 | progress.setAutoReset(false); | ||
| 2744 | |||
| 2745 | const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { | ||
| 2746 | if (progress.wasCanceled()) { | ||
| 2747 | return false; | ||
| 2748 | } | ||
| 2749 | |||
| 2750 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | ||
| 2751 | return true; | ||
| 2752 | }; | ||
| 2753 | |||
| 2754 | const auto status = loader->VerifyIntegrity(QtProgressCallback); | ||
| 2755 | if (progress.wasCanceled() || | ||
| 2756 | status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||
| 2757 | NotImplemented(); | ||
| 2758 | return; | ||
| 2759 | } | ||
| 2760 | |||
| 2761 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 2762 | Failed(); | ||
| 2763 | return; | ||
| 2764 | } | ||
| 2765 | |||
| 2766 | progress.close(); | ||
| 2767 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2768 | tr("The operation completed successfully.")); | ||
| 2769 | } | ||
| 2770 | |||
| 2716 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | 2771 | void GMainWindow::OnGameListCopyTID(u64 program_id) { |
| 2717 | QClipboard* clipboard = QGuiApplication::clipboard(); | 2772 | QClipboard* clipboard = QGuiApplication::clipboard(); |
| 2718 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | 2773 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); |
| @@ -3800,10 +3855,14 @@ void GMainWindow::OnToggleAdaptingFilter() { | |||
| 3800 | 3855 | ||
| 3801 | void GMainWindow::OnToggleGraphicsAPI() { | 3856 | void GMainWindow::OnToggleGraphicsAPI() { |
| 3802 | auto api = Settings::values.renderer_backend.GetValue(); | 3857 | auto api = Settings::values.renderer_backend.GetValue(); |
| 3803 | if (api == Settings::RendererBackend::OpenGL) { | 3858 | if (api != Settings::RendererBackend::Vulkan) { |
| 3804 | api = Settings::RendererBackend::Vulkan; | 3859 | api = Settings::RendererBackend::Vulkan; |
| 3805 | } else { | 3860 | } else { |
| 3861 | #ifdef HAS_OPENGL | ||
| 3806 | api = Settings::RendererBackend::OpenGL; | 3862 | api = Settings::RendererBackend::OpenGL; |
| 3863 | #else | ||
| 3864 | api = Settings::RendererBackend::Null; | ||
| 3865 | #endif | ||
| 3807 | } | 3866 | } |
| 3808 | Settings::values.renderer_backend.SetValue(api); | 3867 | Settings::values.renderer_backend.SetValue(api); |
| 3809 | renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); | 3868 | renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index fd8b196c3..3b0e7f2fe 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -320,6 +320,7 @@ private slots: | |||
| 320 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 320 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 321 | const std::string& game_path); | 321 | const std::string& game_path); |
| 322 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 322 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 323 | void OnGameListVerifyIntegrity(const std::string& game_path); | ||
| 323 | void OnGameListCopyTID(u64 program_id); | 324 | void OnGameListCopyTID(u64 program_id); |
| 324 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 325 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 325 | const CompatibilityList& compatibility_list); | 326 | const CompatibilityList& compatibility_list); |