Compare commits
21 Commits
android-65
...
android-69
Author | SHA1 | Date | |
---|---|---|---|
2e06210846 | |||
ce5320c49f | |||
66f2947854 | |||
eb9e847380 | |||
5b8fdedf4d | |||
64130d9f01 | |||
3df56dc790 | |||
3b7d112c83 | |||
b011ce023d | |||
24ab10c2f6 | |||
cad28abe61 | |||
254b2bd9df | |||
800d6f7d0d | |||
4baaaf6a99 | |||
a02d641042 | |||
716e0a126a | |||
d8943e5bac | |||
e4ebabcd5b | |||
d078cff269 | |||
ea46efd9a2 | |||
ba4b65e4bc |
@ -49,6 +49,8 @@ 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)
|
||||||
@ -61,6 +63,8 @@ option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
|
|||||||
|
|
||||||
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
|
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
|
||||||
|
|
||||||
|
option(YUZU_ENABLE_PORTABLE "Allow yuzu to enable portable mode if a user folder is found in the CWD" ON)
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
|
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
||||||
@ -77,6 +81,24 @@ 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)
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
| Pull Request | Commit | Title | Author | Merged? |
|
||||||
|
|----|----|----|----|----|
|
||||||
|
|
||||||
|
|
||||||
|
End of merge log. You can find the original README.md below the break.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
@ -85,6 +85,7 @@ 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)
|
||||||
|
@ -88,8 +88,13 @@ MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
|
|||||||
return mailbox.Receive(dir, block);
|
return mailbox.Receive(dir, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept {
|
void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||||
command_buffers[session_id] = buffer;
|
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 {
|
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
|
||||||
|
@ -75,7 +75,8 @@ 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, 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;
|
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;
|
||||||
|
@ -37,11 +37,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,6 @@ 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.
|
||||||
*
|
*
|
||||||
|
@ -20,6 +20,12 @@ 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},
|
||||||
|
@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto samples_to_process{
|
auto start_pos{req.start_offset + req.offset};
|
||||||
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
|
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 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};
|
||||||
@ -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.
|
* @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{
|
const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
|
||||||
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
|
(f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
|
||||||
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) {
|
||||||
@ -298,13 +311,13 @@ 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{.buffer{wavebuffer.buffer},
|
DecodeArg decode_arg{
|
||||||
|
.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},
|
||||||
@ -313,7 +326,8 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
|||||||
.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};
|
||||||
|
|
||||||
@ -350,31 +364,17 @@ 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 == 0 || offset >= end_offset - start_offset) {
|
if (samples_decoded && offset < end_offset - start_offset) {
|
||||||
offset = 0;
|
continue;
|
||||||
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;
|
offset = 0;
|
||||||
wavebuffers_consumed++;
|
if (wavebuffer.loop) {
|
||||||
} else {
|
|
||||||
voice_state.loop_count++;
|
voice_state.loop_count++;
|
||||||
if (wavebuffer.loop_count >= 0 &&
|
if (wavebuffer.loop_count >= 0 &&
|
||||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||||
voice_state.loop_count = 0;
|
wavebuffers_consumed);
|
||||||
|
|
||||||
if (wavebuffer.stream_ended) {
|
|
||||||
played_sample_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
|
||||||
wavebuffers_consumed++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samples_decoded == 0) {
|
if (samples_decoded == 0) {
|
||||||
@ -385,7 +385,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
|||||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||||
played_sample_count = 0;
|
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,
|
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},
|
||||||
|
@ -23,6 +23,12 @@ 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},
|
||||||
|
@ -609,17 +609,11 @@ void System::SendCommandToDsp() {
|
|||||||
time_limit_percent = 70.0f;
|
time_limit_percent = 70.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioRenderer::CommandBuffer command_buffer{
|
auto time_limit{
|
||||||
.buffer{translated_addr},
|
|
||||||
.size{command_size},
|
|
||||||
.time_limit{
|
|
||||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))},
|
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
||||||
.applet_resource_user_id{applet_resource_user_id},
|
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
|
||||||
.reset_buffer{reset_command_buffers},
|
applet_resource_user_id, 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) {
|
||||||
|
@ -151,6 +151,10 @@ add_library(common STATIC
|
|||||||
zstd_compression.h
|
zstd_compression.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (YUZU_ENABLE_PORTABLE)
|
||||||
|
add_compile_definitions(YUZU_ENABLE_PORTABLE)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_sources(common PRIVATE
|
target_sources(common PRIVATE
|
||||||
windows/timer_resolution.cpp
|
windows/timer_resolution.cpp
|
||||||
|
@ -88,8 +88,9 @@ public:
|
|||||||
fs::path yuzu_path_config;
|
fs::path yuzu_path_config;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#ifdef YUZU_ENABLE_PORTABLE
|
||||||
yuzu_path = GetExeDirectory() / PORTABLE_DIR;
|
yuzu_path = GetExeDirectory() / PORTABLE_DIR;
|
||||||
|
#endif
|
||||||
if (!IsDir(yuzu_path)) {
|
if (!IsDir(yuzu_path)) {
|
||||||
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
|
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
|
||||||
}
|
}
|
||||||
@ -101,8 +102,9 @@ public:
|
|||||||
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
||||||
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
||||||
#else
|
#else
|
||||||
|
#ifdef YUZU_ENABLE_PORTABLE
|
||||||
yuzu_path = GetCurrentDir() / PORTABLE_DIR;
|
yuzu_path = GetCurrentDir() / PORTABLE_DIR;
|
||||||
|
#endif
|
||||||
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
|
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
|
||||||
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
||||||
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
||||||
|
@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) {
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr std::array<const char*, 66> RESULT_MESSAGES{
|
constexpr std::array<const char*, 68> 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,6 +175,8 @@ constexpr std::array<const char*, 66> 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) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@ -132,6 +133,8 @@ enum class ResultStatus : u16 {
|
|||||||
ErrorBLZDecompressionFailed,
|
ErrorBLZDecompressionFailed,
|
||||||
ErrorBadINIHeader,
|
ErrorBadINIHeader,
|
||||||
ErrorINITooManyKIPs,
|
ErrorINITooManyKIPs,
|
||||||
|
ErrorIntegrityVerificationNotImplemented,
|
||||||
|
ErrorIntegrityVerificationFailed,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string GetResultStatusString(ResultStatus status);
|
std::string GetResultStatusString(ResultStatus status);
|
||||||
@ -169,6 +172,13 @@ 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
|
||||||
*
|
*
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@ -12,6 +14,7 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
@ -80,6 +83,79 @@ 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;
|
||||||
|
@ -39,6 +39,8 @@ 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;
|
||||||
|
|
||||||
|
@ -117,6 +117,42 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ 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;
|
||||||
|
@ -85,6 +85,40 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ 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;
|
||||||
|
@ -74,6 +74,11 @@ 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]};
|
||||||
@ -1271,7 +1276,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{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 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)};
|
||||||
|
@ -66,21 +66,6 @@ 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,
|
||||||
@ -103,7 +88,8 @@ 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_callback(MakeDebugCallback(instance, dld)),
|
debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
|
||||||
|
: 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),
|
||||||
|
@ -35,8 +35,6 @@ 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);
|
||||||
|
|
||||||
@ -75,7 +73,7 @@ private:
|
|||||||
vk::InstanceDispatch dld;
|
vk::InstanceDispatch dld;
|
||||||
|
|
||||||
vk::Instance instance;
|
vk::Instance instance;
|
||||||
DebugCallback debug_callback;
|
vk::DebugUtilsMessenger debug_messenger;
|
||||||
vk::SurfaceKHR surface;
|
vk::SurfaceKHR surface;
|
||||||
|
|
||||||
ScreenInfo screen_info;
|
ScreenInfo screen_info;
|
||||||
|
@ -529,17 +529,20 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
|
|||||||
buffer_handles.push_back(handle);
|
buffer_handles.push_back(handle);
|
||||||
}
|
}
|
||||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
scheduler.Record([bindings_ = std::move(bindings),
|
scheduler.Record([this, bindings_ = std::move(bindings),
|
||||||
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||||
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index,
|
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index,
|
||||||
bindings_.max_index - bindings_.min_index,
|
std::min(bindings_.max_index - bindings_.min_index,
|
||||||
|
device.GetMaxVertexInputBindings()),
|
||||||
buffer_handles_.data(), bindings_.offsets.data(),
|
buffer_handles_.data(), bindings_.offsets.data(),
|
||||||
bindings_.sizes.data(), bindings_.strides.data());
|
bindings_.sizes.data(), bindings_.strides.data());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
scheduler.Record([bindings_ = std::move(bindings),
|
scheduler.Record([this, bindings_ = std::move(bindings),
|
||||||
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||||
cmdbuf.BindVertexBuffers(bindings_.min_index, bindings_.max_index - bindings_.min_index,
|
cmdbuf.BindVertexBuffers(bindings_.min_index,
|
||||||
|
std::min(bindings_.max_index - bindings_.min_index,
|
||||||
|
device.GetMaxVertexInputBindings()),
|
||||||
buffer_handles_.data(), bindings_.offsets.data());
|
buffer_handles_.data(), bindings_.offsets.data());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,22 +63,6 @@ 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) {
|
||||||
@ -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
|
} // namespace Vulkan
|
||||||
|
@ -9,6 +9,4 @@ 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
|
||||||
|
@ -76,11 +76,9 @@ 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 &&
|
||||||
const bool debug_utils =
|
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
|
||||||
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME});
|
extensions.push_back(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;
|
||||||
}
|
}
|
||||||
|
@ -193,13 +193,9 @@ 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_present_modes[device]
|
backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device]
|
||||||
: default_present_modes;
|
: default_present_modes;
|
||||||
|
|
||||||
vsync_mode_combobox->clear();
|
vsync_mode_combobox->clear();
|
||||||
@ -497,6 +493,7 @@ 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);
|
||||||
}
|
}
|
||||||
@ -504,4 +501,11 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
|
|||||||
combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
|
combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
|
||||||
.at(api_combobox->currentIndex())
|
.at(api_combobox->currentIndex())
|
||||||
.first);
|
.first);
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (selected_backend == Settings::RendererBackend::Vulkan &&
|
||||||
|
UISettings::values.has_broken_vulkan) {
|
||||||
|
return Settings::RendererBackend::OpenGL;
|
||||||
|
}
|
||||||
|
return selected_backend;
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
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
|
||||||
@ -628,6 +629,8 @@ 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]() {
|
||||||
|
@ -113,6 +113,7 @@ 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);
|
||||||
|
@ -442,8 +442,13 @@ 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 {
|
||||||
@ -1447,6 +1452,8 @@ 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);
|
||||||
@ -2708,6 +2715,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) {
|
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)));
|
||||||
@ -3794,10 +3849,14 @@ 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::OpenGL) {
|
if (api != Settings::RendererBackend::Vulkan) {
|
||||||
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);
|
||||||
|
@ -313,6 +313,7 @@ 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);
|
||||||
|
Reference in New Issue
Block a user