Compare commits

..

1 Commits

Author SHA1 Message Date
f75e18caf0 Android #66 2023-09-10 00:57:41 +00:00
30 changed files with 144 additions and 362 deletions

View File

@ -49,8 +49,6 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF) CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
@ -79,24 +77,6 @@ if (ANDROID OR WIN32 OR APPLE)
endif() endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL}) option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
set(vvl_version "sdk-1.3.261.1")
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
if (NOT EXISTS "${vvl_zip_file}")
# Download and extract validation layer release to externals directory
set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version}-android.zip"
"${vvl_zip_file}" SHOW_PROGRESS)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif()
# Copy the arm64 binary to src/android/app/main/jniLibs
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
DESTINATION "${vvl_lib_path}")
endif()
# On Android, fetch and compile libcxx before doing anything else # On Android, fetch and compile libcxx before doing anything else
if (ANDROID) if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON) set(CMAKE_SKIP_INSTALL_RULES ON)

View File

@ -85,7 +85,6 @@ if (MSVC)
/wd4100 # 'identifier': unreferenced formal parameter /wd4100 # 'identifier': unreferenced formal parameter
/wd4324 # 'struct_name': structure was padded due to __declspec(align()) /wd4324 # 'struct_name': structure was padded due to __declspec(align())
/wd4201 # nonstandard extension used : nameless struct/union /wd4201 # nonstandard extension used : nameless struct/union
/wd4702 # unreachable code (when used with LTO)
) )
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)

View File

@ -88,13 +88,8 @@ MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
return mailbox.Receive(dir, block); return mailbox.Receive(dir, block);
} }
void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept {
u64 applet_resource_user_id, bool reset) noexcept { command_buffers[session_id] = buffer;
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 { u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {

View File

@ -75,8 +75,7 @@ public:
void Send(Direction dir, MailboxMessage message); void Send(Direction dir, MailboxMessage message);
MailboxMessage Receive(Direction dir, bool block = true); MailboxMessage Receive(Direction dir, bool block = true);
void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept;
u64 applet_resource_user_id, bool reset) noexcept;
u32 GetRemainCommandCount(s32 session_id) const noexcept; u32 GetRemainCommandCount(s32 session_id) const noexcept;
void ClearRemainCommandCount(s32 session_id) noexcept; void ClearRemainCommandCount(s32 session_id) noexcept;
u64 GetRenderingStartTick(s32 session_id) const noexcept; u64 GetRenderingStartTick(s32 session_id) const noexcept;

View File

@ -37,6 +37,11 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
return command_count - processed_command_count; 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 { Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
return stream; return stream;
} }

View File

@ -56,6 +56,14 @@ public:
*/ */
u32 GetRemainingCommandCount() const; 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. * Get the stream for this command list.
* *

View File

@ -20,12 +20,6 @@ void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListPro
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
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{ DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm}, .sample_format{SampleFormat::Adpcm},
.output{out_buffer}, .output{out_buffer},

View File

@ -123,13 +123,11 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
return 0; return 0;
} }
auto start_pos{req.start_offset + req.offset}; auto samples_to_process{
auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)}; std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
if (samples_to_process == 0) {
return 0;
}
auto samples_to_read{samples_to_process}; auto samples_to_read{samples_to_process};
auto start_pos{req.start_offset + req.offset};
auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
samples_remaining_in_frame}; samples_remaining_in_frame};
@ -227,24 +225,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
* @param args - The wavebuffer data, and information for how to decode it. * @param args - The wavebuffer data, and information for how to decode it.
*/ */
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { 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& voice_state{*args.voice_state};
auto remaining_sample_count{args.sample_count}; auto remaining_sample_count{args.sample_count};
auto fraction{voice_state.fraction}; auto fraction{voice_state.fraction};
const auto sample_rate_ratio{Common::FixedPoint<49, 15>( const auto sample_rate_ratio{
(f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)}; (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
args.pitch};
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
if (size_required < 0) { if (size_required < 0) {
@ -311,23 +298,22 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
auto end_offset{wavebuffer.end_offset}; auto end_offset{wavebuffer.end_offset};
if (wavebuffer.loop && voice_state.loop_count > 0 && 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) { wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
start_offset = wavebuffer.loop_start_offset; start_offset = wavebuffer.loop_start_offset;
end_offset = wavebuffer.loop_end_offset; end_offset = wavebuffer.loop_end_offset;
} }
DecodeArg decode_arg{ DecodeArg decode_arg{.buffer{wavebuffer.buffer},
.buffer{wavebuffer.buffer}, .buffer_size{wavebuffer.buffer_size},
.buffer_size{wavebuffer.buffer_size}, .start_offset{start_offset},
.start_offset{start_offset}, .end_offset{end_offset},
.end_offset{end_offset}, .channel_count{args.channel_count},
.channel_count{args.channel_count}, .coefficients{},
.coefficients{}, .adpcm_context{nullptr},
.adpcm_context{nullptr}, .target_channel{args.channel},
.target_channel{args.channel}, .offset{offset},
.offset{offset}, .samples_to_read{samples_to_read - samples_read}};
.samples_to_read{samples_to_read - samples_read},
};
s32 samples_decoded{0}; s32 samples_decoded{0};
@ -364,30 +350,42 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
temp_buffer_pos += samples_decoded; temp_buffer_pos += samples_decoded;
offset += samples_decoded; offset += samples_decoded;
if (samples_decoded && offset < end_offset - start_offset) { if (samples_decoded == 0 || offset >= end_offset - start_offset) {
continue; offset = 0;
} if (!wavebuffer.loop) {
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
offset = 0; if (wavebuffer.stream_ended) {
if (wavebuffer.loop) { played_sample_count = 0;
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) { wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
is_buffer_starved = true; wavebuffers_consumed++;
break; } 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 (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { if (wavebuffer.stream_ended) {
played_sample_count = 0; 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;
}
} }
} else {
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
wavebuffers_consumed);
} }
} }

View File

@ -21,12 +21,6 @@ void PcmFloatDataSourceVersion1Command::Process(
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
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{ DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmFloat}, .sample_format{SampleFormat::PcmFloat},
.output{out_buffer}, .output{out_buffer},

View File

@ -23,12 +23,6 @@ void PcmInt16DataSourceVersion1Command::Process(
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
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{ DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmInt16}, .sample_format{SampleFormat::PcmInt16},
.output{out_buffer}, .output{out_buffer},

View File

@ -609,11 +609,17 @@ void System::SendCommandToDsp() {
time_limit_percent = 70.0f; time_limit_percent = 70.0f;
} }
auto time_limit{ AudioRenderer::CommandBuffer command_buffer{
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * .buffer{translated_addr},
(static_cast<f32>(render_time_limit_percent) / 100.0f))}; .size{command_size},
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit, .time_limit{
applet_resource_user_id, reset_command_buffers); 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);
reset_command_buffers = false; reset_command_buffers = false;
command_buffer_size = command_size; command_buffer_size = command_size;
if (remaining_command_count == 0) { if (remaining_command_count == 0) {

View File

@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown"; return "unknown";
} }
constexpr std::array<const char*, 68> RESULT_MESSAGES{ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The operation completed successfully.", "The operation completed successfully.",
"The loader requested to load is already loaded.", "The loader requested to load is already loaded.",
"The operation is not implemented.", "The operation is not implemented.",
@ -175,8 +175,6 @@ constexpr std::array<const char*, 68> RESULT_MESSAGES{
"The KIP BLZ decompression of the section failed unexpectedly.", "The KIP BLZ decompression of the section failed unexpectedly.",
"The INI file has a bad header.", "The INI file has a bad header.",
"The INI file contains more than the maximum allowable number of KIP files.", "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) { std::string GetResultStatusString(ResultStatus status) {

View File

@ -3,7 +3,6 @@
#pragma once #pragma once
#include <functional>
#include <iosfwd> #include <iosfwd>
#include <memory> #include <memory>
#include <optional> #include <optional>
@ -133,8 +132,6 @@ enum class ResultStatus : u16 {
ErrorBLZDecompressionFailed, ErrorBLZDecompressionFailed,
ErrorBadINIHeader, ErrorBadINIHeader,
ErrorINITooManyKIPs, ErrorINITooManyKIPs,
ErrorIntegrityVerificationNotImplemented,
ErrorIntegrityVerificationFailed,
}; };
std::string GetResultStatusString(ResultStatus status); std::string GetResultStatusString(ResultStatus status);
@ -172,13 +169,6 @@ public:
*/ */
virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; 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 * Get the code (typically .code section) of the application
* *

View File

@ -3,8 +3,6 @@
#include <utility> #include <utility>
#include "common/hex_util.h"
#include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
@ -14,7 +12,6 @@
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h" #include "core/loader/nca.h"
#include "mbedtls/sha256.h"
namespace Loader { namespace Loader {
@ -83,79 +80,6 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return load_result; 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) { ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
if (nca == nullptr) { if (nca == nullptr) {
return ResultStatus::ErrorNotInitialized; return ResultStatus::ErrorNotInitialized;

View File

@ -39,8 +39,6 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 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 ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;

View File

@ -117,42 +117,6 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
return result; 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) { ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
return secondary_loader->ReadRomFS(out_file); return secondary_loader->ReadRomFS(out_file);
} }

View File

@ -45,8 +45,6 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 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 ReadRomFS(FileSys::VirtualFile& out_file) override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;

View File

@ -85,40 +85,6 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
return result; 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) { ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
return nca_loader->ReadRomFS(out_file); return nca_loader->ReadRomFS(out_file);
} }

View File

@ -45,8 +45,6 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; 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 ReadRomFS(FileSys::VirtualFile& out_file) override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;

View File

@ -74,11 +74,6 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
throw InvalidArgument("Invalid image format {}", 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) { Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
const spv::ImageFormat format{GetImageFormat(desc.format)}; const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id type{ctx.U32[1]}; const Id type{ctx.U32[1]};
@ -1276,7 +1271,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
if (desc.count != 1) { if (desc.count != 1) {
throw NotImplementedException("Array of image buffers"); throw NotImplementedException("Array of image buffers");
} }
const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)}; const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, 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 pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};

View File

@ -66,6 +66,21 @@ std::string BuildCommaSeparatedExtensions(
return fmt::format("{}", fmt::join(available_extensions, ",")); 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 } // Anonymous namespace
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
@ -88,8 +103,7 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
Settings::values.renderer_debug.GetValue())), Settings::values.renderer_debug.GetValue())),
debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance) debug_callback(MakeDebugCallback(instance, dld)),
: vk::DebugUtilsMessenger{}),
surface(CreateSurface(instance, render_window.GetWindowInfo())), surface(CreateSurface(instance, render_window.GetWindowInfo())),
device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(),
scheduler(device, state_tracker), scheduler(device, state_tracker),

View File

@ -35,6 +35,8 @@ class GPU;
namespace Vulkan { namespace Vulkan {
using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>;
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
VkSurfaceKHR surface); VkSurfaceKHR surface);
@ -73,7 +75,7 @@ private:
vk::InstanceDispatch dld; vk::InstanceDispatch dld;
vk::Instance instance; vk::Instance instance;
vk::DebugUtilsMessenger debug_messenger; DebugCallback debug_callback;
vk::SurfaceKHR surface; vk::SurfaceKHR surface;
ScreenInfo screen_info; ScreenInfo screen_info;

View File

@ -63,6 +63,22 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
return VK_FALSE; 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 } // Anonymous namespace
vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
@ -82,4 +98,15 @@ 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 } // namespace Vulkan

View File

@ -9,4 +9,6 @@ namespace Vulkan {
vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance);
vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance);
} // namespace Vulkan } // namespace Vulkan

View File

@ -76,9 +76,11 @@ namespace {
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
} }
#endif #endif
if (enable_validation && if (enable_validation) {
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) { const bool debug_utils =
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 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);
} }
return extensions; return extensions;
} }

View File

@ -193,10 +193,14 @@ void ConfigureGraphics::PopulateVSyncModeSelection() {
: vsync_mode_combobox_enum_map[current_index]; : vsync_mode_combobox_enum_map[current_index];
int index{}; int index{};
const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device 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 const auto& present_modes = //< relevant vector of present modes for the selected device or API
backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device] backend == Settings::RendererBackend::Vulkan ? device_present_modes[device]
: default_present_modes; : default_present_modes;
vsync_mode_combobox->clear(); vsync_mode_combobox->clear();
vsync_mode_combobox_enum_map.clear(); vsync_mode_combobox_enum_map.clear();
@ -493,19 +497,11 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
} }
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
const auto selected_backend = [&]() { if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { return Settings::values.renderer_backend.GetValue(true);
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 selected_backend; return static_cast<Settings::RendererBackend>(
combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
.at(api_combobox->currentIndex())
.first);
} }

View File

@ -557,7 +557,6 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); 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* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
#ifndef WIN32 #ifndef WIN32
@ -629,8 +628,6 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
}); });
connect(verify_integrity, &QAction::triggered,
[this, path]() { emit VerifyIntegrityRequested(path); });
connect(copy_tid, &QAction::triggered, connect(copy_tid, &QAction::triggered,
[this, program_id]() { emit CopyTIDRequested(program_id); }); [this, program_id]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {

View File

@ -113,7 +113,6 @@ signals:
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path); const std::string& game_path);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 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 CopyTIDRequested(u64 program_id);
void CreateShortcut(u64 program_id, const std::string& game_path, void CreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target); GameListShortcutTarget target);

View File

@ -442,13 +442,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
"here for instructions to fix the issue</a>.")); "here for instructions to fix the issue</a>."));
#ifdef HAS_OPENGL
Settings::values.renderer_backend = Settings::RendererBackend::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->setDisabled(true);
renderer_status_button->setChecked(false); renderer_status_button->setChecked(false);
} else { } else {
@ -1452,8 +1447,6 @@ void GMainWindow::ConnectWidgetEvents() {
&GMainWindow::OnGameListRemoveInstalledEntry); &GMainWindow::OnGameListRemoveInstalledEntry);
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); 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::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry); &GMainWindow::OnGameListNavigateToGamedbEntry);
@ -2715,54 +2708,6 @@ 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) { void GMainWindow::OnGameListCopyTID(u64 program_id) {
QClipboard* clipboard = QGuiApplication::clipboard(); QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
@ -3849,14 +3794,10 @@ void GMainWindow::OnToggleAdaptingFilter() {
void GMainWindow::OnToggleGraphicsAPI() { void GMainWindow::OnToggleGraphicsAPI() {
auto api = Settings::values.renderer_backend.GetValue(); auto api = Settings::values.renderer_backend.GetValue();
if (api != Settings::RendererBackend::Vulkan) { if (api == Settings::RendererBackend::OpenGL) {
api = Settings::RendererBackend::Vulkan; api = Settings::RendererBackend::Vulkan;
} else { } else {
#ifdef HAS_OPENGL
api = Settings::RendererBackend::OpenGL; api = Settings::RendererBackend::OpenGL;
#else
api = Settings::RendererBackend::Null;
#endif
} }
Settings::values.renderer_backend.SetValue(api); Settings::values.renderer_backend.SetValue(api);
renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan);

View File

@ -313,7 +313,6 @@ private slots:
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path); const std::string& game_path);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 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 OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id, void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list); const CompatibilityList& compatibility_list);