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) /wd4100 # 'identifier': unreferenced formal parameter /wd4324 # 'struct_name': structure was padded due to __declspec(align()) /wd4201 # nonstandard extension used : nameless struct/union + /wd4702 # unreachable code (when used with LTO) ) 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) { return mailbox.Receive(dir, block); } -void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept { - command_buffers[session_id] = buffer; +void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, + u64 applet_resource_user_id, bool reset) noexcept { + command_buffers[session_id].buffer = buffer; + command_buffers[session_id].size = size; + command_buffers[session_id].time_limit = time_limit; + command_buffers[session_id].applet_resource_user_id = applet_resource_user_id; + command_buffers[session_id].reset_buffer = reset; } 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: void Send(Direction dir, MailboxMessage message); MailboxMessage Receive(Direction dir, bool block = true); - void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept; + void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, + u64 applet_resource_user_id, bool reset) noexcept; u32 GetRemainCommandCount(s32 session_id) const noexcept; void ClearRemainCommandCount(s32 session_id) noexcept; 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 { return command_count - processed_command_count; } -void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { - commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader)); - commands_buffer_size = size; -} - Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { return stream; } 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: u32 GetRemainingCommandCount() const; /** - * Set the command buffer. - * - * @param buffer - The buffer to use. - * @param size - The size of the buffer. - */ - void SetBuffer(CpuAddr buffer, u64 size); - - /** * Get the stream for this command list. * * @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 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, processor.sample_count)}; + for (auto& wave_buffer : wave_buffers) { + wave_buffer.loop_start_offset = wave_buffer.start_offset; + wave_buffer.loop_end_offset = wave_buffer.end_offset; + wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; + } + DecodeFromWaveBuffersArgs args{ .sample_format{SampleFormat::Adpcm}, .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, return 0; } - auto samples_to_process{ - std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; + auto start_pos{req.start_offset + req.offset}; + auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)}; + if (samples_to_process == 0) { + return 0; + } auto samples_to_read{samples_to_process}; - auto start_pos{req.start_offset + req.offset}; auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + samples_remaining_in_frame}; @@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, * @param args - The wavebuffer data, and information for how to decode it. */ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { + static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, + auto& played_samples, auto& consumed) -> void { + voice_state.wave_buffer_valid[index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_samples = 0; + } + + index = (index + 1) % MaxWaveBuffers; + consumed++; + }; auto& voice_state{*args.voice_state}; auto remaining_sample_count{args.sample_count}; auto fraction{voice_state.fraction}; - const auto sample_rate_ratio{ - (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * - args.pitch}; + const auto sample_rate_ratio{Common::FixedPoint<49, 15>( + (f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)}; const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; if (size_required < 0) { @@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf auto end_offset{wavebuffer.end_offset}; if (wavebuffer.loop && voice_state.loop_count > 0 && - wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { start_offset = wavebuffer.loop_start_offset; end_offset = wavebuffer.loop_end_offset; } - DecodeArg decode_arg{.buffer{wavebuffer.buffer}, - .buffer_size{wavebuffer.buffer_size}, - .start_offset{start_offset}, - .end_offset{end_offset}, - .channel_count{args.channel_count}, - .coefficients{}, - .adpcm_context{nullptr}, - .target_channel{args.channel}, - .offset{offset}, - .samples_to_read{samples_to_read - samples_read}}; + DecodeArg decode_arg{ + .buffer{wavebuffer.buffer}, + .buffer_size{wavebuffer.buffer_size}, + .start_offset{start_offset}, + .end_offset{end_offset}, + .channel_count{args.channel_count}, + .coefficients{}, + .adpcm_context{nullptr}, + .target_channel{args.channel}, + .offset{offset}, + .samples_to_read{samples_to_read - samples_read}, + }; s32 samples_decoded{0}; @@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf temp_buffer_pos += samples_decoded; offset += samples_decoded; - if (samples_decoded == 0 || offset >= end_offset - start_offset) { - offset = 0; - if (!wavebuffer.loop) { - voice_state.wave_buffer_valid[wavebuffer_index] = false; - voice_state.loop_count = 0; - - if (wavebuffer.stream_ended) { - played_sample_count = 0; - } - - wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; - wavebuffers_consumed++; - } else { - voice_state.loop_count++; - if (wavebuffer.loop_count >= 0 && - (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { - voice_state.wave_buffer_valid[wavebuffer_index] = false; - voice_state.loop_count = 0; - - if (wavebuffer.stream_ended) { - played_sample_count = 0; - } - - wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; - wavebuffers_consumed++; - } - - if (samples_decoded == 0) { - is_buffer_starved = true; - break; - } - - if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { - played_sample_count = 0; - } + if (samples_decoded && offset < end_offset - start_offset) { + continue; + } + + offset = 0; + if (wavebuffer.loop) { + voice_state.loop_count++; + if (wavebuffer.loop_count >= 0 && + (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { + EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count, + wavebuffers_consumed); + } + + if (samples_decoded == 0) { + is_buffer_starved = true; + break; + } + + if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { + played_sample_count = 0; } + } else { + EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count, + wavebuffers_consumed); } } 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( auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, processor.sample_count); + for (auto& wave_buffer : wave_buffers) { + wave_buffer.loop_start_offset = wave_buffer.start_offset; + wave_buffer.loop_end_offset = wave_buffer.end_offset; + wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; + } + DecodeFromWaveBuffersArgs args{ .sample_format{SampleFormat::PcmFloat}, .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( auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, processor.sample_count); + for (auto& wave_buffer : wave_buffers) { + wave_buffer.loop_start_offset = wave_buffer.start_offset; + wave_buffer.loop_end_offset = wave_buffer.end_offset; + wave_buffer.loop_count = wave_buffer.loop ? -1 : 0; + } + DecodeFromWaveBuffersArgs args{ .sample_format{SampleFormat::PcmInt16}, .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() { time_limit_percent = 70.0f; } - AudioRenderer::CommandBuffer command_buffer{ - .buffer{translated_addr}, - .size{command_size}, - .time_limit{ - static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * - (static_cast<f32>(render_time_limit_percent) / 100.0f))}, - .applet_resource_user_id{applet_resource_user_id}, - .reset_buffer{reset_command_buffers}, - }; - - audio_renderer.SetCommandBuffer(session_id, command_buffer); + auto time_limit{ + static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * + (static_cast<f32>(render_time_limit_percent) / 100.0f))}; + audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit, + applet_resource_user_id, reset_command_buffers); reset_command_buffers = false; command_buffer_size = command_size; 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 zstd_compression.h ) +if (YUZU_ENABLE_PORTABLE) + add_compile_definitions(YUZU_ENABLE_PORTABLE) +endif() + if (WIN32) target_sources(common PRIVATE 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: fs::path yuzu_path_config; #ifdef _WIN32 +#ifdef YUZU_ENABLE_PORTABLE yuzu_path = GetExeDirectory() / PORTABLE_DIR; - +#endif if (!IsDir(yuzu_path)) { yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; } @@ -101,8 +102,9 @@ public: yuzu_path_cache = yuzu_path / CACHE_DIR; yuzu_path_config = yuzu_path / CONFIG_DIR; #else +#ifdef YUZU_ENABLE_PORTABLE yuzu_path = GetCurrentDir() / PORTABLE_DIR; - +#endif if (Exists(yuzu_path) && IsDir(yuzu_path)) { yuzu_path_cache = yuzu_path / CACHE_DIR; 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) { return "unknown"; } -constexpr std::array<const char*, 66> RESULT_MESSAGES{ +constexpr std::array<const char*, 68> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -175,6 +175,8 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The KIP BLZ decompression of the section failed unexpectedly.", "The INI file has a bad header.", "The INI file contains more than the maximum allowable number of KIP files.", + "Integrity verification could not be performed for this file.", + "Integrity verification failed.", }; 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 @@ #pragma once +#include <functional> #include <iosfwd> #include <memory> #include <optional> @@ -132,6 +133,8 @@ enum class ResultStatus : u16 { ErrorBLZDecompressionFailed, ErrorBadINIHeader, ErrorINITooManyKIPs, + ErrorIntegrityVerificationNotImplemented, + ErrorIntegrityVerificationFailed, }; std::string GetResultStatusString(ResultStatus status); @@ -170,6 +173,13 @@ public: virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; /** + * Try to verify the integrity of the file. + */ + virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + return ResultStatus::ErrorIntegrityVerificationNotImplemented; + } + + /** * Get the code (typically .code section) of the application * * @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 @@ #include <utility> +#include "common/hex_util.h" +#include "common/scope_exit.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" @@ -12,6 +14,7 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/nca.h" +#include "mbedtls/sha256.h" namespace Loader { @@ -80,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S return load_result; } +ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + using namespace Common::Literals; + + constexpr size_t NcaFileNameWithHashLength = 36; + constexpr size_t NcaFileNameHashLength = 32; + constexpr size_t NcaSha256HashLength = 32; + constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2; + + // Get the file name. + const auto name = file->GetName(); + + // We won't try to verify meta NCAs. + if (name.ends_with(".cnmt.nca")) { + return ResultStatus::Success; + } + + // Check if we can verify this file. NCAs should be named after their hashes. + if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) { + LOG_WARNING(Loader, "Unable to validate NCA with name {}", name); + return ResultStatus::ErrorIntegrityVerificationNotImplemented; + } + + // Get the expected truncated hash of the NCA. + const auto input_hash = + Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false); + + // Declare buffer to read into. + std::vector<u8> buffer(4_MiB); + + // Initialize sha256 verification context. + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts_ret(&ctx, 0); + + // Ensure we maintain a clean state on exit. + SCOPE_EXIT({ mbedtls_sha256_free(&ctx); }); + + // Declare counters. + const size_t total_size = file->GetSize(); + size_t processed_size = 0; + + // Begin iterating the file. + while (processed_size < total_size) { + // Refill the buffer. + const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size); + const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size); + + // Update the hash function with the buffer contents. + mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size); + + // Update counters. + processed_size += read_size; + + // Call the progress function. + if (!progress_callback(processed_size, total_size)) { + return ResultStatus::ErrorIntegrityVerificationFailed; + } + } + + // Finalize context and compute the output hash. + std::array<u8, NcaSha256HashLength> output_hash; + mbedtls_sha256_finish_ret(&ctx, output_hash.data()); + + // Compare to expected. + if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) { + LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name); + return ResultStatus::ErrorIntegrityVerificationFailed; + } + + // File verified. + return ResultStatus::Success; +} + ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { if (nca == nullptr) { 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: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadProgramId(u64& out_program_id) override; 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 return result; } +ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + // Extracted-type NSPs can't be verified. + if (nsp->IsExtractedType()) { + return ResultStatus::ErrorIntegrityVerificationNotImplemented; + } + + // Get list of all NCAs. + const auto ncas = nsp->GetNCAsCollapsed(); + + size_t total_size = 0; + size_t processed_size = 0; + + // Loop over NCAs, collecting the total size to verify. + for (const auto& nca : ncas) { + total_size += nca->GetBaseFile()->GetSize(); + } + + // Loop over NCAs again, verifying each. + for (const auto& nca : ncas) { + AppLoader_NCA loader_nca(nca->GetBaseFile()); + + const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { + return progress_callback(processed_size + nca_processed_size, total_size); + }; + + const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); + if (verification_result != ResultStatus::Success) { + return verification_result; + } + + processed_size += nca->GetBaseFile()->GetSize(); + } + + return ResultStatus::Success; +} + ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { return secondary_loader->ReadRomFS(out_file); } 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: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; 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 return result; } +ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + // Verify secure partition, as it is the only thing we can process. + auto secure_partition = xci->GetSecurePartitionNSP(); + + // Get list of all NCAs. + const auto ncas = secure_partition->GetNCAsCollapsed(); + + size_t total_size = 0; + size_t processed_size = 0; + + // Loop over NCAs, collecting the total size to verify. + for (const auto& nca : ncas) { + total_size += nca->GetBaseFile()->GetSize(); + } + + // Loop over NCAs again, verifying each. + for (const auto& nca : ncas) { + AppLoader_NCA loader_nca(nca->GetBaseFile()); + + const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { + return progress_callback(processed_size + nca_processed_size, total_size); + }; + + const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); + if (verification_result != ResultStatus::Success) { + return verification_result; + } + + processed_size += nca->GetBaseFile()->GetSize(); + } + + return ResultStatus::Success; +} + ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { return nca_loader->ReadRomFS(out_file); } 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: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; 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) { throw InvalidArgument("Invalid image format {}", format); } +spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) { + const auto spv_format = GetImageFormat(format); + return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format; +} + Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { const spv::ImageFormat format{GetImageFormat(desc.format)}; const Id type{ctx.U32[1]}; @@ -1271,7 +1276,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) { if (desc.count != 1) { throw NotImplementedException("Array of image buffers"); } - const spv::ImageFormat format{GetImageFormat(desc.format)}; + const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; 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( return fmt::format("{}", fmt::join(available_extensions, ",")); } -DebugCallback MakeDebugCallback(const vk::Instance& instance, const vk::InstanceDispatch& dld) { - if (!Settings::values.renderer_debug) { - return DebugCallback{}; - } - const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld); - const auto it = std::ranges::find_if(*properties, [](const auto& prop) { - return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0; - }); - if (it != properties->end()) { - return CreateDebugUtilsCallback(instance); - } else { - return CreateDebugReportCallback(instance); - } -} - } // Anonymous namespace Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, @@ -103,7 +88,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, Settings::values.renderer_debug.GetValue())), - debug_callback(MakeDebugCallback(instance, dld)), + debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance) + : vk::DebugUtilsMessenger{}), surface(CreateSurface(instance, render_window.GetWindowInfo())), device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), 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; namespace Vulkan { -using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>; - Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, VkSurfaceKHR surface); @@ -75,7 +73,7 @@ private: vk::InstanceDispatch dld; vk::Instance instance; - DebugCallback debug_callback; + vk::DebugUtilsMessenger debug_messenger; vk::SurfaceKHR surface; 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, return VK_FALSE; } -VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, - uint64_t object, size_t location, int32_t messageCode, - const char* pLayerPrefix, const char* pMessage, void* pUserData) { - const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags); - const std::string_view message{pMessage}; - if (severity & VK_DEBUG_REPORT_ERROR_BIT_EXT) { - LOG_CRITICAL(Render_Vulkan, "{}", message); - } else if (severity & VK_DEBUG_REPORT_WARNING_BIT_EXT) { - LOG_WARNING(Render_Vulkan, "{}", message); - } else if (severity & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { - LOG_INFO(Render_Vulkan, "{}", message); - } else if (severity & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { - LOG_DEBUG(Render_Vulkan, "{}", message); - } - return VK_FALSE; -} } // Anonymous namespace vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { @@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { }); } -vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance) { - return instance.CreateDebugReportCallback({ - .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, - .pNext = nullptr, - .flags = VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT | - VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT, - .pfnCallback = DebugReportCallback, - .pUserData = nullptr, - }); -} - } // 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 { vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); -vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance); - } // 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 { extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); } #endif - if (enable_validation) { - const bool debug_utils = - AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}); - extensions.push_back(debug_utils ? VK_EXT_DEBUG_UTILS_EXTENSION_NAME - : VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + if (enable_validation && + AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; } 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() { : vsync_mode_combobox_enum_map[current_index]; int index{}; const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device - if (device == -1) { - // Invalid device - return; - } const auto& present_modes = //< relevant vector of present modes for the selected device or API - backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] - : default_present_modes; + backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device] + : default_present_modes; vsync_mode_combobox->clear(); vsync_mode_combobox_enum_map.clear(); @@ -497,11 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() { } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { - if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { - return Settings::values.renderer_backend.GetValue(true); + const auto selected_backend = [&]() { + if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { + return Settings::values.renderer_backend.GetValue(true); + } + return static_cast<Settings::RendererBackend>( + combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index()) + .at(api_combobox->currentIndex()) + .first); + }(); + + if (selected_backend == Settings::RendererBackend::Vulkan && + UISettings::values.has_broken_vulkan) { + return Settings::RendererBackend::OpenGL; } - return static_cast<Settings::RendererBackend>( - combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index()) - .at(api_combobox->currentIndex()) - .first); + return selected_backend; } 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 QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); + QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); #ifndef WIN32 @@ -630,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); }); + connect(verify_integrity, &QAction::triggered, + [this, path]() { emit VerifyIntegrityRequested(path); }); connect(copy_tid, &QAction::triggered, [this, program_id]() { emit CopyTIDRequested(program_id); }); connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 45d6dee88..1fcbbf0ba 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -114,6 +114,7 @@ signals: void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, const std::string& game_path); void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); + void VerifyIntegrityRequested(const std::string& game_path); void CopyTIDRequested(u64 program_id); void CreateShortcut(u64 program_id, const std::string& game_path, 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 "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" "here for instructions to fix the issue</a>.")); +#ifdef HAS_OPENGL Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; +#else + Settings::values.renderer_backend = Settings::RendererBackend::Null; +#endif + UpdateAPIText(); renderer_status_button->setDisabled(true); renderer_status_button->setChecked(false); } else { @@ -1447,6 +1452,8 @@ void GMainWindow::ConnectWidgetEvents() { &GMainWindow::OnGameListRemoveInstalledEntry); connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); + connect(game_list, &GameList::VerifyIntegrityRequested, this, + &GMainWindow::OnGameListVerifyIntegrity); connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); @@ -2713,6 +2720,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } } +void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { + const auto NotImplemented = [this] { + QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), + tr("File contents were not checked for validity.")); + }; + const auto Failed = [this] { + QMessageBox::critical(this, tr("Integrity verification failed!"), + tr("File contents may be corrupt.")); + }; + + const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); + if (loader == nullptr) { + NotImplemented(); + return; + } + + QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { + if (progress.wasCanceled()) { + return false; + } + + progress.setValue(static_cast<int>((processed_size * 100) / total_size)); + return true; + }; + + const auto status = loader->VerifyIntegrity(QtProgressCallback); + if (progress.wasCanceled() || + status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { + NotImplemented(); + return; + } + + if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { + Failed(); + return; + } + + progress.close(); + QMessageBox::information(this, tr("Integrity verification succeeded!"), + tr("The operation completed successfully.")); +} + void GMainWindow::OnGameListCopyTID(u64 program_id) { QClipboard* clipboard = QGuiApplication::clipboard(); clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); @@ -3800,10 +3855,14 @@ void GMainWindow::OnToggleAdaptingFilter() { void GMainWindow::OnToggleGraphicsAPI() { auto api = Settings::values.renderer_backend.GetValue(); - if (api == Settings::RendererBackend::OpenGL) { + if (api != Settings::RendererBackend::Vulkan) { api = Settings::RendererBackend::Vulkan; } else { +#ifdef HAS_OPENGL api = Settings::RendererBackend::OpenGL; +#else + api = Settings::RendererBackend::Null; +#endif } Settings::values.renderer_backend.SetValue(api); 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: void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, const std::string& game_path); void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); + void OnGameListVerifyIntegrity(const std::string& game_path); void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); |