early-access version 3860
This commit is contained in:
parent
3c1016ab09
commit
f8ca381d80
22 changed files with 306 additions and 80 deletions
|
@ -1,7 +1,7 @@
|
|||
yuzu emulator early access
|
||||
=============
|
||||
|
||||
This is the source code for early-access 3859.
|
||||
This is the source code for early-access 3860.
|
||||
|
||||
## Legal Notice
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -56,14 +56,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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
|
|
|
@ -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 (samples_decoded && offset < end_offset - start_offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
@ -169,6 +172,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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -561,6 +561,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
|
||||
|
@ -634,6 +635,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]() {
|
||||
|
|
|
@ -117,6 +117,7 @@ signals:
|
|||
const std::string& game_path);
|
||||
void RemovePlayTimeRequested(u64 program_id);
|
||||
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);
|
||||
|
|
|
@ -1457,6 +1457,8 @@ void GMainWindow::ConnectWidgetEvents() {
|
|||
connect(game_list, &GameList::RemovePlayTimeRequested, this,
|
||||
&GMainWindow::OnGameListRemovePlayTimeData);
|
||||
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);
|
||||
|
@ -2729,6 +2731,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)));
|
||||
|
|
|
@ -318,6 +318,7 @@ private slots:
|
|||
const std::string& game_path);
|
||||
void OnGameListRemovePlayTimeData(u64 program_id);
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue