summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp9
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h3
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_list_processor.cpp5
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/command_list_processor.h8
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.cpp6
-rw-r--r--src/audio_core/renderer/command/data_source/decode.cpp106
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.cpp6
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.cpp6
-rw-r--r--src/audio_core/renderer/system.cpp16
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/fs/path_util.cpp6
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/core/loader/loader.h10
-rw-r--r--src/core/loader/nca.cpp76
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nsp.cpp36
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp34
-rw-r--r--src/core/loader/xci.h2
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp7
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp18
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h4
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.cpp27
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.h2
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp28
-rw-r--r--src/yuzu/game_list.cpp3
-rw-r--r--src/yuzu/game_list.h1
-rw-r--r--src/yuzu/main.cpp61
-rw-r--r--src/yuzu/main.h1
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
91void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept { 91void 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
95u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { 100u32 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
40void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
41 commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
42 commands_buffer_size = size;
43}
44
45Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { 40Sink::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 */
227void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { 229void 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
154if (YUZU_ENABLE_PORTABLE)
155 add_compile_definitions(YUZU_ENABLE_PORTABLE)
156endif()
157
154if (WIN32) 158if (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
111constexpr std::array<const char*, 66> RESULT_MESSAGES{ 111constexpr 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
180std::string GetResultStatusString(ResultStatus status) { 182std::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
137std::string GetResultStatusString(ResultStatus status); 140std::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
16namespace Loader { 19namespace 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
86ResultStatus 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
83ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { 159ResultStatus 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
120ResultStatus 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
120ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { 156ResultStatus 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
88ResultStatus 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
88ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { 122ResultStatus 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
77spv::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
77Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { 82Id 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
69DebugCallback 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
86Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, 71Device 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
36namespace Vulkan { 36namespace Vulkan {
37 37
38using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>;
39
40Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, 38Device 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
66VkBool32 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
84vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { 68vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
@@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
98 }); 82 });
99} 83}
100 84
101vk::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
10vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); 10vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance);
11 11
12vk::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
499Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { 495Settings::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
2723void 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
2716void GMainWindow::OnGameListCopyTID(u64 program_id) { 2771void 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
3801void GMainWindow::OnToggleGraphicsAPI() { 3856void 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);