Compare commits

..

3 commits

Author SHA1 Message Date
a3ef2cc183
[audio_core/hid] Audio REV12+15 support + HID fixes (#2719)
This fixes newer updates / games.

Implements partial audio rev15, rev13, rev12 and HID issues on SDK20+ games.

Credits to LotP (Ryubing) and Zephyron (Citron) for their research and implementation.

Co-authored-by: Zephyron <zephyron@citron-emu.org>
Co-authored-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-on: #2719
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: unknown <sahyno1996@gmail.com>
Co-committed-by: unknown <sahyno1996@gmail.com>
2025-10-12 17:03:14 +02:00
1e1b8ad33f
[common] Properly skip Custom Web Applet if YUZU_USE_QT_WEB_ENGINE is not defined (#2717)
This restores the Offline Web Applet LLE setting as default and properly skip Custom Web Applet if YUZU_USE_QT_WEB_ENGINE is not defined preventing crashes.

Reviewed-on: #2717
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-11 14:45:14 +02:00
91493fa39b
[vk] Fast UBO: fix tracking (#2712)
Fixes or mitigates memory errors in TOTK and possibly other games as well.

Credit: Ribbit
Reviewed-on: #2712
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-11 06:34:21 +02:00
82 changed files with 887 additions and 1620 deletions

View file

@ -13,13 +13,12 @@ Copyright: yuzu Emulator Project
License: GPL-2.0-or-later License: GPL-2.0-or-later
Files: dist/qt_themes/default/icons/256x256/eden.png Files: dist/qt_themes/default/icons/256x256/eden.png
dist/qt_themes/default/icons/256x256/eden_named.png
dist/yuzu.bmp dist/yuzu.bmp
dist/eden.icns dist/yuzu.icns
dist/eden.ico dist/eden.ico
dist/dev.eden_emu.eden.svg dist/eden.svg
Copyright: 2025 Eden Emulator Project Copyright: yuzu Emulator Project
License: GPL-3.0-or-later License: GPL-2.0-or-later
Files: dist/qt_themes/qdarkstyle*/LICENSE.* Files: dist/qt_themes/qdarkstyle*/LICENSE.*
dist/qt_themes/qdarkstyle*/style.qrc dist/qt_themes/qdarkstyle*/style.qrc
@ -156,39 +155,3 @@ License: BSD-3-Clause
Files: src/android/app/debug.keystore Files: src/android/app/debug.keystore
Copyright: 2023 yuzu Emulator Project Copyright: 2023 yuzu Emulator Project
License: GPL-3.0-or-later License: GPL-3.0-or-later
Files: dist/qt_themes/colorful/icons/48x48/user-trash.png
dist/qt_themes/colorful/icons/48x48/upload.png
dist/qt_themes/colorful/icons/48x48/download.png
Copyright: 2014 Uri Herrera
1996-2025 KDE Software Foundation
License: LGPL-2.0-or-later
Files: dist/qt_themes/default/icons/48x48/user-trash.png
dist/qt_themes/default/icons/48x48/upload.png
dist/qt_themes/default/icons/48x48/download.png
dist/qt_themes/default_dark/icons/48x48/user-trash.png
dist/qt_themes/default_dark/icons/48x48/upload.png
dist/qt_themes/default_dark/icons/48x48/download.png
Copyright: 2025 Fonticons, Inc.
License: CC-BY-4.0
Comment: All of these icons have been modified by crueter <crueter@crueter.xyz>
Files: CMakeModules/CPM.cmake
Copyright: 2019-2023 Lars Melchior
License: MIT
Files: CMakeModules/CPMUtil.cmake
CMakeModules/CPM.cmake
CMakeModules/GetSCMRev.cmake
CMakeModules/DetectArchitecture.cmake
tools/cpm/*
tools/update-cpm.sh
tools/shellcheck.sh
docs/CPMUtil.md
**cpmfile.json
Copyright: 2025 crueter <crueter@crueter.xyz>
License: GPL-3.0-or-later
Comment: CPM.cmake has had additional modifications from crueter to better work with CPMUtil
https://git.crueter.xyz/CMake/CPMUtil
https://git.crueter.xyz/CMake/Modules

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -18,9 +18,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file> <file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file> <file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">icons/48x48/download.png</file>
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file> <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>

View file

@ -11,9 +11,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file> <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file> <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file> <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>

View file

@ -14,9 +14,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file> <file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file> <file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">icons/48x48/download.png</file>
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file> <file alias="48x48/star.png">icons/48x48/star.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

View file

@ -13,9 +13,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file> <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file> <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file> <file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

View file

@ -9,9 +9,6 @@
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file> <file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file> <file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">icons/48x48/download.png</file>
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file> <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>

View file

@ -6,9 +6,6 @@
<file alias="48x48/bad_folder.png">../qdarkstyle/icons/48x48/bad_folder.png</file> <file alias="48x48/bad_folder.png">../qdarkstyle/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../qdarkstyle/icons/48x48/chip.png</file> <file alias="48x48/chip.png">../qdarkstyle/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../qdarkstyle/icons/48x48/folder.png</file> <file alias="48x48/folder.png">../qdarkstyle/icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">../qdarkstyle/icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">../qdarkstyle/icons/48x48/download.png</file>
<file alias="48x48/upload.png">../qdarkstyle/icons/48x48/upload.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file> <file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../qdarkstyle/icons/48x48/list-add.png</file> <file alias="48x48/list-add.png">../qdarkstyle/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../qdarkstyle/icons/48x48/sd_card.png</file> <file alias="48x48/sd_card.png">../qdarkstyle/icons/48x48/sd_card.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 438 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before After
Before After

View file

@ -16,7 +16,7 @@
#include <ranges> #include <ranges>
namespace AudioCore { namespace AudioCore {
constexpr u32 CurrentRevision = 16; constexpr u32 CurrentRevision = 15;
enum class SupportTags { enum class SupportTags {
CommandProcessingTimeEstimatorVersion4, CommandProcessingTimeEstimatorVersion4,
@ -47,6 +47,10 @@ enum class SupportTags {
DelayChannelMappingChange, DelayChannelMappingChange,
ReverbChannelMappingChange, ReverbChannelMappingChange,
I3dl2ReverbChannelMappingChange, I3dl2ReverbChannelMappingChange,
SplitterPrevVolumeReset,
SplitterBiquadFilterParameter,
SplitterDestinationV2b,
VoiceInParameterV2,
// Not a real tag, just here to get the count. // Not a real tag, just here to get the count.
Size Size
@ -91,6 +95,10 @@ constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) {
{SupportTags::DelayChannelMappingChange, 11}, {SupportTags::DelayChannelMappingChange, 11},
{SupportTags::ReverbChannelMappingChange, 11}, {SupportTags::ReverbChannelMappingChange, 11},
{SupportTags::I3dl2ReverbChannelMappingChange, 11}, {SupportTags::I3dl2ReverbChannelMappingChange, 11},
{SupportTags::SplitterBiquadFilterParameter, 12},
{SupportTags::SplitterPrevVolumeReset, 13},
{SupportTags::SplitterDestinationV2b, 15},
{SupportTags::VoiceInParameterV2, 15},
}}; }};
const auto& feature = const auto& feature =

View file

@ -193,4 +193,20 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
} }
bool BehaviorInfo::IsSplitterPrevVolumeResetSupported() const {
return CheckFeatureSupported(SupportTags::SplitterPrevVolumeReset, user_revision);
}
bool BehaviorInfo::IsSplitterDestinationV2bSupported() const {
return CheckFeatureSupported(SupportTags::SplitterDestinationV2b, user_revision);
}
bool BehaviorInfo::IsVoiceInParameterV2Supported() const {
return CheckFeatureSupported(SupportTags::VoiceInParameterV2, user_revision);
}
bool BehaviorInfo::IsBiquadFilterParameterForSplitterEnabled() const {
return CheckFeatureSupported(SupportTags::SplitterBiquadFilterParameter, user_revision);
}
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -361,6 +364,38 @@ public:
*/ */
bool IsI3dl2ReverbChannelMappingChanged() const; bool IsI3dl2ReverbChannelMappingChanged() const;
/**
* Check if explicit previous mix volume reset is supported for splitters.
* This allows splitters to explicitly reset their previous mix volumes instead of
* doing so implicitly on first use.
*
* @return True if supported, otherwise false.
*/
bool IsSplitterPrevVolumeResetSupported() const;
/**
* Check if splitter destination v2b parameter format is supported (revision 15+).
* This uses the extended parameter format with biquad filter fields.
*
* @return True if supported, otherwise false.
*/
bool IsSplitterDestinationV2bSupported() const;
/**
* Check if voice input parameter v2 format is supported (revision 15+).
* This uses the extended parameter format with float biquad filters.
*
* @return True if supported, otherwise false.
*/
bool IsVoiceInParameterV2Supported() const;
/**
* Check if splitter destinations can carry biquad filter parameters (revision 12+).
*
* @return True if supported, otherwise false.
*/
bool IsBiquadFilterParameterForSplitterEnabled() const;
/// Host version /// Host version
u32 process_revision; u32 process_revision;
/// User version /// User version

View file

@ -64,8 +64,6 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
behaviour.IsMemoryForceMappingEnabled()); behaviour.IsMemoryForceMappingEnabled());
const auto voice_count{voice_context.GetCount()}; const auto voice_count{voice_context.GetCount()};
std::span<const VoiceInfo::InParameter> in_params{
reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output), std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
voice_count}; voice_count};
@ -76,8 +74,104 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
u32 new_voice_count{0}; u32 new_voice_count{0};
// Two input formats exist: legacy (0x170) and v2 with float biquad (0x188).
const bool use_v2 = behaviour.IsVoiceInParameterV2Supported();
const u32 in_stride = use_v2 ? 0x188u : static_cast<u32>(sizeof(VoiceInfo::InParameter));
for (u32 i = 0; i < voice_count; i++) { for (u32 i = 0; i < voice_count; i++) {
const auto& in_param{in_params[i]}; VoiceInfo::InParameter local_in{};
std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> float_biquads{};
if (!use_v2) {
const auto* in_param_ptr = reinterpret_cast<const VoiceInfo::InParameter*>(
input + i * sizeof(VoiceInfo::InParameter));
local_in = *in_param_ptr;
} else {
struct VoiceInParameterV2 {
u32 id;
u32 node_id;
bool is_new;
bool in_use;
PlayState play_state;
SampleFormat sample_format;
u32 sample_rate;
u32 priority;
u32 sort_order;
u32 channel_count;
f32 pitch;
f32 volume;
// Two BiquadFilterParameter2 (0x18 each) -> ignored/converted
struct BiquadV2 {
bool enable;
u8 r1;
u8 r2;
u8 r3;
std::array<f32, 3> b;
std::array<f32, 2> a;
} biquads[2];
u32 wave_buffer_count;
u32 wave_buffer_index;
u32 reserved1;
u64 src_data_address;
u64 src_data_size;
s32 mix_id;
u32 splitter_id;
std::array<VoiceInfo::WaveBufferInternal, MaxWaveBuffers> wavebuffers;
std::array<u32, MaxChannels> channel_resource_ids;
bool clear_voice_drop;
u8 flush_wave_buffer_count;
u16 reserved2;
VoiceInfo::Flags flags;
SrcQuality src_quality;
u32 external_ctx;
u32 external_ctx_size;
u32 reserved3[2];
};
const auto* vin = reinterpret_cast<const VoiceInParameterV2*>(input + i * in_stride);
local_in.id = vin->id;
local_in.node_id = vin->node_id;
local_in.is_new = vin->is_new;
local_in.in_use = vin->in_use;
local_in.play_state = vin->play_state;
local_in.sample_format = vin->sample_format;
local_in.sample_rate = vin->sample_rate;
local_in.priority = static_cast<s32>(vin->priority);
local_in.sort_order = static_cast<s32>(vin->sort_order);
local_in.channel_count = vin->channel_count;
local_in.pitch = vin->pitch;
local_in.volume = vin->volume;
// For REV15+, we keep float coefficients separate and only convert for compatibility
for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) {
const auto& src = vin->biquads[filter_idx];
auto& dst = local_in.biquads[filter_idx];
dst.enabled = src.enable;
// Convert float coefficients to fixed-point Q2.14 for legacy path
dst.b[0] = static_cast<s16>(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f));
dst.b[1] = static_cast<s16>(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f));
dst.b[2] = static_cast<s16>(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f));
dst.a[0] = static_cast<s16>(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f));
dst.a[1] = static_cast<s16>(std::clamp(src.a[1] * 16384.0f, -32768.0f, 32767.0f));
// Also store the native float version
float_biquads[filter_idx].enabled = src.enable;
float_biquads[filter_idx].numerator = src.b;
float_biquads[filter_idx].denominator = src.a;
}
local_in.wave_buffer_count = vin->wave_buffer_count;
local_in.wave_buffer_index = static_cast<u16>(vin->wave_buffer_index);
local_in.src_data_address = static_cast<CpuAddr>(vin->src_data_address);
local_in.src_data_size = vin->src_data_size;
local_in.mix_id = static_cast<u32>(vin->mix_id);
local_in.splitter_id = vin->splitter_id;
local_in.wave_buffer_internal = vin->wavebuffers;
local_in.channel_resource_ids = vin->channel_resource_ids;
local_in.clear_voice_drop = vin->clear_voice_drop;
local_in.flush_buffer_count = vin->flush_wave_buffer_count;
local_in.flags = vin->flags;
local_in.src_quality = vin->src_quality;
}
const auto& in_param = local_in;
std::array<VoiceState*, MaxChannels> voice_states{}; std::array<VoiceState*, MaxChannels> voice_states{};
if (!in_param.in_use) { if (!in_param.in_use) {
@ -101,6 +195,14 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
BehaviorInfo::ErrorInfo update_error{}; BehaviorInfo::ErrorInfo update_error{};
voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
// For REV15+, store the native float biquad coefficients
if (use_v2) {
voice_info.use_float_biquads = true;
voice_info.biquads_float = float_biquads;
} else {
voice_info.use_float_biquads = false;
}
if (!update_error.error_code.IsSuccess()) { if (!update_error.error_code.IsSuccess()) {
behaviour.AppendError(update_error); behaviour.AppendError(update_error);
} }
@ -121,7 +223,7 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
new_voice_count += in_param.channel_count; new_voice_count += in_param.channel_count;
} }
auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))}; auto consumed_input_size{voice_count * in_stride};
auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))}; auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
if (consumed_input_size != in_header->voices_size) { if (consumed_input_size != in_header->voices_size) {
LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
@ -257,18 +359,31 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co
EffectContext& effect_context, SplitterContext& splitter_context) { EffectContext& effect_context, SplitterContext& splitter_context) {
s32 mix_count{0}; s32 mix_count{0};
u32 consumed_input_size{0}; u32 consumed_input_size{0};
u32 input_mix_size{0};
if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)}; auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
mix_count = in_dirty_params->count; mix_count = in_dirty_params->count;
// Validate against expected header size to ensure structure is correct
if (mix_count < 0 || mix_count > 0x100) {
LOG_ERROR(
Service_Audio,
"Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}",
mix_count, in_dirty_params->magic, in_header->mix_size);
return Service::Audio::ResultInvalidUpdateInfo;
}
consumed_input_size += static_cast<u32>(sizeof(MixInfo::InDirtyParameter));
input += sizeof(MixInfo::InDirtyParameter); input += sizeof(MixInfo::InDirtyParameter);
consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
mix_count * sizeof(MixInfo::InParameter));
} else { } else {
mix_count = mix_context.GetCount(); mix_count = mix_context.GetCount();
consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
} }
input_mix_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
consumed_input_size += input_mix_size;
if (mix_buffer_count == 0) { if (mix_buffer_count == 0) {
return Service::Audio::ResultInvalidUpdateInfo; return Service::Audio::ResultInvalidUpdateInfo;
} }

View file

@ -237,6 +237,13 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& vo
cmd.biquad = voice_info.biquads[biquad_index]; cmd.biquad = voice_info.biquads[biquad_index];
if (voice_info.use_float_biquads) {
cmd.biquad_float = voice_info.biquads_float[biquad_index];
cmd.use_float_coefficients = true;
} else {
cmd.use_float_coefficients = false;
}
cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
@ -263,6 +270,9 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas
cmd.biquad.b = parameter.b; cmd.biquad.b = parameter.b;
cmd.biquad.a = parameter.a; cmd.biquad.a = parameter.a;
// Effects use legacy fixed-point format
cmd.use_float_coefficients = false;
cmd.state = memory_pool->Translate(CpuAddr(state), cmd.state = memory_pool->Translate(CpuAddr(state),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
@ -658,6 +668,13 @@ void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, Voice
cmd.output = buffer_count + channel; cmd.output = buffer_count + channel;
cmd.biquads = voice_info.biquads; cmd.biquads = voice_info.biquads;
if (voice_info.use_float_biquads) {
cmd.biquads_float = voice_info.biquads_float;
cmd.use_float_coefficients = true;
} else {
cmd.use_float_coefficients = false;
}
cmd.states[0] = cmd.states[0] =
memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));

View file

@ -51,6 +51,40 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
state.s3 = Common::BitCast<s64>(s[3]); state.s3 = Common::BitCast<s64>(s[3]);
} }
/**
* Biquad filter float implementation with native float coefficients.
*/
void ApplyBiquadFilterFloat2(std::span<s32> output, std::span<const s32> input,
std::array<f32, 3>& b, std::array<f32, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count) {
constexpr f64 min{std::numeric_limits<s32>::min()};
constexpr f64 max{std::numeric_limits<s32>::max()};
std::array<f64, 3> b_double{static_cast<f64>(b[0]), static_cast<f64>(b[1]),
static_cast<f64>(b[2])};
std::array<f64, 2> a_double{static_cast<f64>(a[0]), static_cast<f64>(a[1])};
std::array<f64, 4> s{Common::BitCast<f64>(state.s0), Common::BitCast<f64>(state.s1),
Common::BitCast<f64>(state.s2), Common::BitCast<f64>(state.s3)};
for (u32 i = 0; i < sample_count; i++) {
f64 in_sample{static_cast<f64>(input[i])};
auto sample{in_sample * b_double[0] + s[0] * b_double[1] + s[1] * b_double[2] +
s[2] * a_double[0] + s[3] * a_double[1]};
output[i] = static_cast<s32>(std::clamp(sample, min, max));
s[1] = s[0];
s[0] = in_sample;
s[3] = s[2];
s[2] = sample;
}
state.s0 = Common::BitCast<s64>(s[0]);
state.s1 = Common::BitCast<s64>(s[1]);
state.s2 = Common::BitCast<s64>(s[2]);
state.s3 = Common::BitCast<s64>(s[3]);
}
/** /**
* Biquad filter s32 implementation. * Biquad filter s32 implementation.
* *
@ -98,8 +132,14 @@ void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& pro
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
if (use_float_processing) { if (use_float_processing) {
// REV15+: Use native float coefficients if available
if (use_float_coefficients) {
ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquad_float.numerator,
biquad_float.denominator, *state_, processor.sample_count);
} else {
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
processor.sample_count); processor.sample_count);
}
} else { } else {
ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
processor.sample_count); processor.sample_count);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -50,12 +53,16 @@ struct BiquadFilterCommand : ICommand {
s16 output; s16 output;
/// Input parameters for biquad /// Input parameters for biquad
VoiceInfo::BiquadFilterParameter biquad; VoiceInfo::BiquadFilterParameter biquad;
/// Input parameters for biquad (REV15+ native float)
VoiceInfo::BiquadFilterParameter2 biquad_float;
/// Biquad state, updated each call /// Biquad state, updated each call
CpuAddr state; CpuAddr state;
/// If true, reset the state /// If true, reset the state
bool needs_init; bool needs_init;
/// If true, use float processing rather than int /// If true, use float processing rather than int
bool use_float_processing; bool use_float_processing;
/// If true, use native float coefficients (REV15+)
bool use_float_coefficients;
}; };
/** /**
@ -72,4 +79,18 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
std::array<s16, 3>& b, std::array<s16, 2>& a, std::array<s16, 3>& b, std::array<s16, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count); VoiceState::BiquadFilterState& state, const u32 sample_count);
/**
* Biquad filter float implementation with native float coefficients (SDK REV15+).
*
* @param output - Output container for filtered samples.
* @param input - Input container for samples to be filtered.
* @param b - Feedforward coefficients (float).
* @param a - Feedback coefficients (float).
* @param state - State to track previous samples.
* @param sample_count - Number of samples to process.
*/
void ApplyBiquadFilterFloat2(std::span<s32> output, std::span<const s32> input,
std::array<f32, 3>& b, std::array<f32, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count);
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -33,9 +36,15 @@ void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProces
*state = {}; *state = {};
} }
// REV15+: Use native float coefficients if available
if (use_float_coefficients) {
ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquads_float[i].numerator,
biquads_float[i].denominator, *state, processor.sample_count);
} else {
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
processor.sample_count); processor.sample_count);
} }
}
} }
bool MultiTapBiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) { bool MultiTapBiquadFilterCommand::Verify(const AudioRenderer::CommandListProcessor& processor) {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -49,12 +52,16 @@ struct MultiTapBiquadFilterCommand : ICommand {
s16 output; s16 output;
/// Biquad parameters /// Biquad parameters
std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads; std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
/// Biquad parameters (REV15+ native float)
std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> biquads_float;
/// Biquad states, updated each call /// Biquad states, updated each call
std::array<CpuAddr, MaxBiquadFilters> states; std::array<CpuAddr, MaxBiquadFilters> states;
/// If each biquad needs initialisation /// If each biquad needs initialisation
std::array<bool, MaxBiquadFilters> needs_init; std::array<bool, MaxBiquadFilters> needs_init;
/// Number of active biquads /// Number of active biquads
u8 filter_tap_count; u8 filter_tap_count;
/// If true, use native float coefficients (REV15+)
bool use_float_coefficients;
}; };
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer

View file

@ -35,12 +35,16 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) {
void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_, void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_,
SplitterDestinationData* splitter_destinations_, SplitterDestinationData* splitter_destinations_,
const u32 destination_count_, const bool splitter_bug_fixed_) { const u32 destination_count_, const bool splitter_bug_fixed_,
const BehaviorInfo& behavior) {
splitter_infos = splitter_infos_; splitter_infos = splitter_infos_;
info_count = splitter_info_count_; info_count = splitter_info_count_;
splitter_destinations = splitter_destinations_; splitter_destinations = splitter_destinations_;
destinations_count = destination_count_; destinations_count = destination_count_;
splitter_bug_fixed = splitter_bug_fixed_; splitter_bug_fixed = splitter_bug_fixed_;
splitter_prev_volume_reset_supported = behavior.IsSplitterPrevVolumeResetSupported();
splitter_biquad_param_supported = behavior.IsBiquadFilterParameterForSplitterEnabled();
splitter_float_coeff_supported = behavior.IsSplitterDestinationV2bSupported();
} }
bool SplitterContext::UsingSplitter() const { bool SplitterContext::UsingSplitter() const {
@ -84,7 +88,7 @@ bool SplitterContext::Initialize(const BehaviorInfo& behavior,
} }
Setup(splitter_infos, params.splitter_infos, splitter_destinations, Setup(splitter_infos, params.splitter_infos, splitter_destinations,
params.splitter_destinations, behavior.IsSplitterBugFixed()); params.splitter_destinations, behavior.IsSplitterBugFixed(), behavior);
} }
return true; return true;
} }
@ -137,19 +141,104 @@ u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_
u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) {
for (u32 i = 0; i < count; i++) { for (u32 i = 0; i < count; i++) {
auto data_header{ // Version selection based on feature flags:
reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)}; // - REV12: integer biquad params (Version2a)
// - REV15: float coeff/biquad v2b
// - older: no biquad fields
if (!splitter_biquad_param_supported) {
const auto* data_header =
reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset);
if (data_header->magic != GetSplitterSendDataMagic()) { if (data_header->magic != GetSplitterSendDataMagic()) {
continue; continue;
} }
if (data_header->id < 0 || data_header->id > destinations_count) { if (data_header->id < 0 || data_header->id > destinations_count) {
continue; continue;
} }
splitter_destinations[data_header->id].Update(*data_header); auto modified_params = *data_header;
if (!splitter_prev_volume_reset_supported) {
modified_params.reset_prev_volume = false;
}
splitter_destinations[data_header->id].Update(modified_params);
offset += sizeof(SplitterDestinationData::InParameter); offset += sizeof(SplitterDestinationData::InParameter);
} else if (!splitter_float_coeff_supported) {
// Version 2a: struct contains legacy fixed-point biquad filter fields (REV12+)
const auto* data_header_v2a =
reinterpret_cast<const SplitterDestinationData::InParameterVersion2a*>(input +
offset);
if (data_header_v2a->magic != GetSplitterSendDataMagic()) {
continue;
}
if (data_header_v2a->id < 0 || data_header_v2a->id > destinations_count) {
continue;
}
// Map common fields to the base format
SplitterDestinationData::InParameter mapped{};
mapped.magic = data_header_v2a->magic;
mapped.id = data_header_v2a->id;
mapped.mix_volumes = data_header_v2a->mix_volumes;
mapped.mix_id = data_header_v2a->mix_id;
mapped.in_use = data_header_v2a->in_use;
mapped.reset_prev_volume =
splitter_prev_volume_reset_supported ? data_header_v2a->reset_prev_volume : false;
auto& destination = splitter_destinations[data_header_v2a->id];
destination.Update(mapped);
// Convert legacy fixed-point biquad params into float representation
auto biquad_filters = destination.GetBiquadFilters();
for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) {
const auto& legacy = data_header_v2a->biquad_filters[filter_idx];
auto& out = biquad_filters[filter_idx];
out.enabled = legacy.enabled;
// s16 fixed-point scale: use Q14 like voices (b and a are s16, 1.0 ~= 1<<14)
constexpr float scale = 1.0f / static_cast<float>(1 << 14);
out.numerator[0] = static_cast<float>(legacy.b[0]) * scale;
out.numerator[1] = static_cast<float>(legacy.b[1]) * scale;
out.numerator[2] = static_cast<float>(legacy.b[2]) * scale;
out.denominator[0] = static_cast<float>(legacy.a[0]) * scale;
out.denominator[1] = static_cast<float>(legacy.a[1]) * scale;
}
offset += static_cast<u32>(sizeof(SplitterDestinationData::InParameterVersion2a));
} else {
// Version 2b: struct contains extra biquad filter fields with float coeffs
const auto* data_header_v2b =
reinterpret_cast<const SplitterDestinationData::InParameterVersion2b*>(input +
offset);
if (data_header_v2b->magic != GetSplitterSendDataMagic()) {
continue;
}
if (data_header_v2b->id < 0 || data_header_v2b->id > destinations_count) {
continue;
}
// Map common fields to the old format
SplitterDestinationData::InParameter mapped{};
mapped.magic = data_header_v2b->magic;
mapped.id = data_header_v2b->id;
mapped.mix_volumes = data_header_v2b->mix_volumes;
mapped.mix_id = data_header_v2b->mix_id;
mapped.in_use = data_header_v2b->in_use;
mapped.reset_prev_volume =
splitter_prev_volume_reset_supported ? data_header_v2b->reset_prev_volume : false;
// Store biquad filters from V2b (REV15+)
auto& destination = splitter_destinations[data_header_v2b->id];
destination.Update(mapped);
// Copy biquad filter parameters
auto biquad_filters = destination.GetBiquadFilters();
for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) {
biquad_filters[filter_idx] = data_header_v2b->biquad_filters[filter_idx];
}
offset += static_cast<u32>(sizeof(SplitterDestinationData::InParameterVersion2b));
}
} }
return offset; return offset;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -168,10 +171,11 @@ private:
* @param splitter_destinations - Workbuffer for splitter destinations. * @param splitter_destinations - Workbuffer for splitter destinations.
* @param destination_count - Number of destinations in the workbuffer. * @param destination_count - Number of destinations in the workbuffer.
* @param splitter_bug_fixed - Is the splitter bug fixed? * @param splitter_bug_fixed - Is the splitter bug fixed?
* @param behavior - Behavior info for feature support.
*/ */
void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count, void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count,
SplitterDestinationData* splitter_destinations, u32 destination_count, SplitterDestinationData* splitter_destinations, u32 destination_count,
bool splitter_bug_fixed); bool splitter_bug_fixed, const BehaviorInfo& behavior);
/// Workbuffer for splitters /// Workbuffer for splitters
std::span<SplitterInfo> splitter_infos{}; std::span<SplitterInfo> splitter_infos{};
@ -183,6 +187,12 @@ private:
s32 destinations_count{}; s32 destinations_count{};
/// Is the splitter bug fixed? /// Is the splitter bug fixed?
bool splitter_bug_fixed{}; bool splitter_bug_fixed{};
/// Is explicit previous mix volume reset supported?
bool splitter_prev_volume_reset_supported{};
/// Is biquad filter parameter for splitter (REV12) supported?
bool splitter_biquad_param_supported{};
/// Is float coefficient/biquad filter v2b parameter supported?
bool splitter_float_coeff_supported{};
}; };
} // namespace Renderer } // namespace Renderer

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -84,4 +87,14 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
next = next_; next = next_;
} }
std::span<SplitterDestinationData::BiquadFilterParameter2>
SplitterDestinationData::GetBiquadFilters() {
return biquad_filters;
}
std::span<const SplitterDestinationData::BiquadFilterParameter2>
SplitterDestinationData::GetBiquadFilters() const {
return biquad_filters;
}
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -16,16 +19,72 @@ namespace AudioCore::Renderer {
*/ */
class SplitterDestinationData { class SplitterDestinationData {
public: public:
/**
* Biquad filter parameter with float coefficients (SDK REV15+).
* Defined here to avoid circular dependency with VoiceInfo.
*/
struct BiquadFilterParameter2 {
/* 0x00 */ bool enabled;
/* 0x01 */ u8 reserved1;
/* 0x02 */ u8 reserved2;
/* 0x03 */ u8 reserved3;
/* 0x04 */ std::array<f32, 3> numerator; // b0, b1, b2
/* 0x10 */ std::array<f32, 2> denominator; // a1, a2 (a0 = 1)
};
static_assert(sizeof(BiquadFilterParameter2) == 0x18,
"BiquadFilterParameter2 has the wrong size!");
/**
* Legacy biquad filter parameter with fixed-point coefficients (SDK REV12+ for splitters).
* Matches the old voice biquad format.
*/
struct BiquadFilterParameterLegacy {
/* 0x00 */ bool enabled;
/* 0x02 */ std::array<s16, 3> b; // numerator
/* 0x08 */ std::array<s16, 2> a; // denominator (a0 = 1)
};
static_assert(sizeof(BiquadFilterParameterLegacy) == 0xC,
"BiquadFilterParameterLegacy has the wrong size!");
struct InParameter { struct InParameter {
/* 0x00 */ u32 magic; // 'SNDD' /* 0x00 */ u32 magic; // 'SNDD'
/* 0x04 */ s32 id; /* 0x04 */ s32 id;
/* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes; /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
/* 0x68 */ u32 mix_id; /* 0x68 */ u32 mix_id;
/* 0x6C */ bool in_use; /* 0x6C */ bool in_use;
/* 0x6D */ bool reset_prev_volume;
}; };
static_assert(sizeof(InParameter) == 0x70, static_assert(sizeof(InParameter) == 0x70,
"SplitterDestinationData::InParameter has the wrong size!"); "SplitterDestinationData::InParameter has the wrong size!");
struct InParameterVersion2a {
/* 0x00 */ u32 magic; // 'SNDD'
/* 0x04 */ s32 id;
/* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
/* 0x68 */ u32 mix_id;
/* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameterLegacy, MaxBiquadFilters>
biquad_filters;
/* 0x84 */ bool in_use;
/* 0x85 */ bool reset_prev_volume; // only effective if supported
/* 0x86 */ u8 reserved[10];
};
static_assert(sizeof(InParameterVersion2a) == 0x90,
"SplitterDestinationData::InParameterVersion2a has the wrong size!");
struct InParameterVersion2b {
/* 0x00 */ u32 magic; // 'SNDD'
/* 0x04 */ s32 id;
/* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
/* 0x68 */ u32 mix_id;
/* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameter2, MaxBiquadFilters>
biquad_filters;
/* 0x9C */ bool in_use;
/* 0x9D */ bool reset_prev_volume;
/* 0x9E */ u8 reserved[10];
};
static_assert(sizeof(InParameterVersion2b) == 0xA8,
"SplitterDestinationData::InParameterVersion2b has the wrong size!");
SplitterDestinationData(s32 id); SplitterDestinationData(s32 id);
/** /**
@ -78,7 +137,7 @@ public:
f32 GetMixVolumePrev(u32 index) const; f32 GetMixVolumePrev(u32 index) const;
/** /**
* Get the previous mix volumes for all mix buffers in this destination. * Get the previous mix volumes for all mix buffers.
* *
* @return Span of previous mix buffer volumes. * @return Span of previous mix buffer volumes.
*/ */
@ -115,6 +174,20 @@ public:
*/ */
void SetNext(SplitterDestinationData* next); void SetNext(SplitterDestinationData* next);
/**
* Get biquad filter parameters for this destination (REV15+ or mapped from REV12).
*
* @return Span of biquad filter parameters.
*/
std::span<BiquadFilterParameter2> GetBiquadFilters();
/**
* Get const biquad filter parameters for this destination (REV15+ or mapped from REV12).
*
* @return Const span of biquad filter parameters.
*/
std::span<const BiquadFilterParameter2> GetBiquadFilters() const;
private: private:
/// Id of this destination /// Id of this destination
const s32 id; const s32 id;
@ -124,6 +197,8 @@ private:
std::array<f32, MaxMixBuffers> mix_volumes{0.0f}; std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
/// Previous mix volumes /// Previous mix volumes
std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f}; std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
/// Biquad filter parameters (REV15+ or mapped from REV12)
std::array<BiquadFilterParameter2, MaxBiquadFilters> biquad_filters{};
/// Next destination in the mix chain /// Next destination in the mix chain
SplitterDestinationData* next{}; SplitterDestinationData* next{};
/// Is this destination in use? /// Is this destination in use?

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -135,6 +138,17 @@ public:
static_assert(sizeof(BiquadFilterParameter) == 0xC, static_assert(sizeof(BiquadFilterParameter) == 0xC,
"VoiceInfo::BiquadFilterParameter has the wrong size!"); "VoiceInfo::BiquadFilterParameter has the wrong size!");
struct BiquadFilterParameter2 {
/* 0x00 */ bool enabled;
/* 0x01 */ u8 reserved1;
/* 0x02 */ u8 reserved2;
/* 0x03 */ u8 reserved3;
/* 0x04 */ std::array<f32, 3> numerator; // b0, b1, b2
/* 0x10 */ std::array<f32, 2> denominator; // a1, a2 (a0 = 1)
};
static_assert(sizeof(BiquadFilterParameter2) == 0x18,
"VoiceInfo::BiquadFilterParameter2 has the wrong size!");
struct InParameter { struct InParameter {
/* 0x000 */ u32 id; /* 0x000 */ u32 id;
/* 0x004 */ u32 node_id; /* 0x004 */ u32 node_id;
@ -168,6 +182,43 @@ public:
}; };
static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
struct InParameter2 {
/* 0x000 */ u32 id;
/* 0x004 */ u32 node_id;
/* 0x008 */ bool is_new;
/* 0x009 */ bool in_use;
/* 0x00A */ PlayState play_state;
/* 0x00B */ SampleFormat sample_format;
/* 0x00C */ u32 sample_rate;
/* 0x010 */ s32 priority;
/* 0x014 */ s32 sort_order;
/* 0x018 */ u32 channel_count;
/* 0x01C */ f32 pitch;
/* 0x020 */ f32 volume;
/* 0x024 */ std::array<BiquadFilterParameter2, MaxBiquadFilters> biquads;
/* 0x054 */ u32 wave_buffer_count;
/* 0x058 */ u32 wave_buffer_index;
/* 0x05C */ u32 reserved1;
/* 0x060 */ CpuAddr src_data_address;
/* 0x068 */ u64 src_data_size;
/* 0x070 */ u32 mix_id;
/* 0x074 */ u32 splitter_id;
/* 0x078 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
/* 0x158 */ std::array<s32, MaxChannels> channel_resource_ids;
/* 0x170 */ bool clear_voice_drop;
/* 0x171 */ u8 flush_buffer_count;
/* 0x172 */ u16 reserved2;
/* 0x174 */ Flags flags;
/* 0x175 */ u8 reserved3;
/* 0x176 */ SrcQuality src_quality;
/* 0x177 */ u8 reserved4;
/* 0x178 */ u32 external_context;
/* 0x17C */ u32 external_context_size;
/* 0x180 */ u32 reserved5;
/* 0x184 */ u32 reserved6;
};
static_assert(sizeof(InParameter2) == 0x188, "VoiceInfo::InParameter2 has the wrong size!");
struct OutStatus { struct OutStatus {
/* 0x00 */ u64 played_sample_count; /* 0x00 */ u64 played_sample_count;
/* 0x08 */ u32 wave_buffers_consumed; /* 0x08 */ u32 wave_buffers_consumed;
@ -349,6 +400,10 @@ public:
f32 prev_volume{}; f32 prev_volume{};
/// Biquad filters for generating filter commands on this voice /// Biquad filters for generating filter commands on this voice
std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{}; std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
/// Float biquad filters for REV15+ (native float coefficients)
std::array<BiquadFilterParameter2, MaxBiquadFilters> biquads_float{};
/// Use float biquad coefficients (REV15+)
bool use_float_biquads{};
/// Number of active wavebuffers /// Number of active wavebuffers
u32 wave_buffer_count{}; u32 wave_buffer_count{};
/// Current playing wavebuffer index /// Current playing wavebuffer index

View file

@ -161,7 +161,7 @@ struct Values {
Category::LibraryApplet}; Category::LibraryApplet};
Setting<AppletMode> photo_viewer_applet_mode{ Setting<AppletMode> photo_viewer_applet_mode{
linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet}; linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode", Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
Category::LibraryApplet}; Category::LibraryApplet};
Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode", Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
Category::LibraryApplet}; Category::LibraryApplet};

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -68,42 +71,6 @@ std::string ResolveURL(const std::string& url) {
return url.substr(0, index) + "lp1" + url.substr(index + 1); return url.substr(0, index) + "lp1" + url.substr(index + 1);
} }
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
if (web_arg.size() == sizeof(WebArgHeader)) {
return {};
}
WebArgInputTLVMap input_tlv_map;
u64 current_offset = sizeof(WebArgHeader);
for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
return input_tlv_map;
}
WebArgInputTLV input_tlv;
std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
current_offset += sizeof(WebArgInputTLV);
if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
return input_tlv_map;
}
std::vector<u8> data(input_tlv.arg_data_size);
std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
current_offset += input_tlv.arg_data_size;
input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
}
return input_tlv_map;
}
FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
FileSys::ContentRecordType nca_type) { FileSys::ContentRecordType nca_type) {
if (nca_type == FileSys::ContentRecordType::Data) { if (nca_type == FileSys::ContentRecordType::Data) {
@ -144,6 +111,43 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
} }
} }
#ifdef YUZU_USE_QT_WEB_ENGINE
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
if (web_arg.size() == sizeof(WebArgHeader)) {
return {};
}
WebArgInputTLVMap input_tlv_map;
u64 current_offset = sizeof(WebArgHeader);
for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
return input_tlv_map;
}
WebArgInputTLV input_tlv;
std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
current_offset += sizeof(WebArgInputTLV);
if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
return input_tlv_map;
}
std::vector<u8> data(input_tlv.arg_data_size);
std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
current_offset += input_tlv.arg_data_size;
input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
}
return input_tlv_map;
}
void ExtractSharedFonts(Core::System& system) { void ExtractSharedFonts(Core::System& system) {
static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{ static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{
"FontStandard.ttf", "FontStandard.ttf",
@ -221,6 +225,7 @@ void ExtractSharedFonts(Core::System& system) {
FileSys::VfsRawCopy(decrypted_font, out_file); FileSys::VfsRawCopy(decrypted_font, out_file);
} }
} }
#endif
} // namespace } // namespace
@ -232,6 +237,7 @@ WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr<Applet> applet_,
WebBrowser::~WebBrowser() = default; WebBrowser::~WebBrowser() = default;
void WebBrowser::Initialize() { void WebBrowser::Initialize() {
#ifdef YUZU_USE_QT_WEB_ENGINE
FrontendApplet::Initialize(); FrontendApplet::Initialize();
LOG_INFO(Service_AM, "Initializing Web Browser Applet."); LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
@ -284,6 +290,7 @@ void WebBrowser::Initialize() {
ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind);
break; break;
} }
#endif
} }
Result WebBrowser::GetStatus() const { Result WebBrowser::GetStatus() const {
@ -295,6 +302,7 @@ void WebBrowser::ExecuteInteractive() {
} }
void WebBrowser::Execute() { void WebBrowser::Execute() {
#ifdef YUZU_USE_QT_WEB_ENGINE
switch (web_arg_header.shim_kind) { switch (web_arg_header.shim_kind) {
case ShimKind::Shop: case ShimKind::Shop:
ExecuteShop(); ExecuteShop();
@ -322,6 +330,10 @@ void WebBrowser::Execute() {
WebBrowserExit(WebExitReason::EndButtonPressed); WebBrowserExit(WebExitReason::EndButtonPressed);
break; break;
} }
#else
LOG_INFO(Service_AM, "Web Browser Applet disabled, skipping.");
WebBrowserExit(WebExitReason::EndButtonPressed);
#endif
} }
void WebBrowser::ExtractOfflineRomFS() { void WebBrowser::ExtractOfflineRomFS() {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -175,6 +178,7 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_, std::shared_
{0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"}, {0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"}, {1, nullptr, "TerminateAllLibraryApplets"},
{2, nullptr, "AreAnyLibraryAppletsLeft"}, {2, nullptr, "AreAnyLibraryAppletsLeft"},
{3, D<&ILibraryAppletCreator::CreateLibraryAppletEx>, "CreateLibraryAppletEx"},
{10, D<&ILibraryAppletCreator::CreateStorage>, "CreateStorage"}, {10, D<&ILibraryAppletCreator::CreateStorage>, "CreateStorage"},
{11, D<&ILibraryAppletCreator::CreateTransferMemoryStorage>, "CreateTransferMemoryStorage"}, {11, D<&ILibraryAppletCreator::CreateTransferMemoryStorage>, "CreateTransferMemoryStorage"},
{12, D<&ILibraryAppletCreator::CreateHandleStorage>, "CreateHandleStorage"}, {12, D<&ILibraryAppletCreator::CreateHandleStorage>, "CreateHandleStorage"},
@ -210,6 +214,32 @@ Result ILibraryAppletCreator::CreateLibraryApplet(
R_SUCCEED(); R_SUCCEED();
} }
Result ILibraryAppletCreator::CreateLibraryAppletEx(
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
LibraryAppletMode library_applet_mode, u64 thread_id) {
LOG_DEBUG(Service_AM, "called with applet_id={} applet_mode={} thread_id={}", applet_id,
library_applet_mode, thread_id);
std::shared_ptr<ILibraryAppletAccessor> library_applet;
if (ShouldCreateGuestApplet(applet_id)) {
library_applet =
CreateGuestApplet(system, m_window_system, m_applet, applet_id, library_applet_mode);
}
if (!library_applet) {
library_applet =
CreateFrontendApplet(system, m_window_system, m_applet, applet_id, library_applet_mode);
}
if (!library_applet) {
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
R_THROW(ResultUnknown);
}
// Applet is created, can now be launched.
m_applet->library_applet_launchable_event.Signal();
*out_library_applet_accessor = library_applet;
R_SUCCEED();
}
Result ILibraryAppletCreator::CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size) { Result ILibraryAppletCreator::CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size) {
LOG_DEBUG(Service_AM, "called, size={}", size); LOG_DEBUG(Service_AM, "called, size={}", size);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -24,6 +27,9 @@ private:
Result CreateLibraryApplet( Result CreateLibraryApplet(
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id, Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
LibraryAppletMode library_applet_mode); LibraryAppletMode library_applet_mode);
Result CreateLibraryAppletEx(
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
LibraryAppletMode library_applet_mode, u64 thread_id);
Result CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size); Result CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size);
Result CreateTransferMemoryStorage( Result CreateTransferMemoryStorage(
Out<SharedPointer<IStorage>> out_storage, bool is_writable, s64 size, Out<SharedPointer<IStorage>> out_storage, bool is_writable, s64 size,

View file

@ -128,6 +128,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{406, nullptr, "GetApplicationControlProperty"}, {406, nullptr, "GetApplicationControlProperty"},
{407, nullptr, "ListApplicationTitle"}, {407, nullptr, "ListApplicationTitle"},
{408, nullptr, "ListApplicationIcon"}, {408, nullptr, "ListApplicationIcon"},
{419, D<&IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground>, "RequestDownloadApplicationControlDataInBackground"},
{502, nullptr, "RequestCheckGameCardRegistration"}, {502, nullptr, "RequestCheckGameCardRegistration"},
{503, nullptr, "RequestGameCardRegistrationGoldPoint"}, {503, nullptr, "RequestGameCardRegistrationGoldPoint"},
{504, nullptr, "RequestRegisterGameCard"}, {504, nullptr, "RequestRegisterGameCard"},
@ -210,6 +211,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{1703, nullptr, "GetApplicationViewDownloadErrorContext"}, {1703, nullptr, "GetApplicationViewDownloadErrorContext"},
{1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"}, {1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"},
{1705, nullptr, "IsPatchAutoDeletableApplication"}, {1705, nullptr, "IsPatchAutoDeletableApplication"},
{1706, D<&IApplicationManagerInterface::Unknown1706>, "Unknown1706"},
{1800, nullptr, "IsNotificationSetupCompleted"}, {1800, nullptr, "IsNotificationSetupCompleted"},
{1801, nullptr, "GetLastNotificationInfoCount"}, {1801, nullptr, "GetLastNotificationInfoCount"},
{1802, nullptr, "ListLastNotificationInfo"}, {1802, nullptr, "ListLastNotificationInfo"},
@ -309,6 +311,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"}, {4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"},
{4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"}, {4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"},
{4088, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4088"}, {4088, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4088"},
{4053, D<&IApplicationManagerInterface::Unknown4053>, "Unknown4053"},
{9999, nullptr, "GetApplicationCertificate"}, {9999, nullptr, "GetApplicationCertificate"},
}; };
// clang-format on // clang-format on
@ -526,6 +529,37 @@ Result IApplicationManagerInterface::GetApplicationTerminateResult(Out<Result> o
R_SUCCEED(); R_SUCCEED();
} }
Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground(
u64 unk, u64 application_id) {
LOG_WARNING(Service_NS, "(STUBBED), app={:016X} unk={}", application_id, unk);
R_SUCCEED();
}
Result IApplicationManagerInterface::Unknown1706(
OutBuffer<BufferAttr_HipcAutoSelect> out_buffer_58,
InBuffer<BufferAttr_HipcMapAlias> in_buffer_8) {
LOG_WARNING(Service_NS, "(STUBBED) Unknown1706 called: out_size={} in_size={}",
out_buffer_58.size(), in_buffer_8.size());
if (out_buffer_58.size() < 0x58 || in_buffer_8.size() < 0x8) {
R_THROW(ResultUnknown);
}
u64 application_id = 0;
std::memcpy(&application_id, in_buffer_8.data(), sizeof(u64));
ApplicationView view{};
view.application_id = application_id;
view.unk = 0x70000;
view.flags = 0x401f17;
std::memset(out_buffer_58.data(), 0, out_buffer_58.size());
std::memcpy(out_buffer_58.data(), &view, sizeof(ApplicationView));
R_SUCCEED();
}
Result IApplicationManagerInterface::Unknown4022( Result IApplicationManagerInterface::Unknown4022(
OutCopyHandle<Kernel::KReadableEvent> out_event) { OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called"); LOG_WARNING(Service_NS, "(STUBBED) called");
@ -539,4 +573,9 @@ Result IApplicationManagerInterface::Unknown4023(Out<u64> out_result) {
R_SUCCEED(); R_SUCCEED();
} }
Result IApplicationManagerInterface::Unknown4053() {
LOG_WARNING(Service_NS, "(STUBBED) called.");
R_SUCCEED();
}
} // namespace Service::NS } // namespace Service::NS

View file

@ -55,6 +55,12 @@ public:
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id); Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event); Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result Unknown4023(Out<u64> out_result); Result Unknown4023(Out<u64> out_result);
Result Unknown4053();
Result RequestDownloadApplicationControlDataInBackground(u64 unk,
u64 application_id);
Result Unknown1706(OutBuffer<BufferAttr_HipcAutoSelect> out_buffer_58,
InBuffer<BufferAttr_HipcMapAlias> in_buffer_8);
private: private:
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -83,8 +86,9 @@ static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size."
struct ApplicationViewWithPromotionInfo { struct ApplicationViewWithPromotionInfo {
ApplicationView view; ///< \ref NsApplicationView ApplicationView view; ///< \ref NsApplicationView
PromotionInfo promotion; ///< \ref NsPromotionInfo PromotionInfo promotion; ///< \ref NsPromotionInfo
std::array<u8, 0x8> padding{}; ///< Extra padding for newer HOS versions
}; };
static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70, static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x78,
"ApplicationViewWithPromotionInfo has incorrect size."); "ApplicationViewWithPromotionInfo has incorrect size.");
struct ApplicationOccupiedSizeEntity { struct ApplicationOccupiedSizeEntity {
@ -113,4 +117,10 @@ struct Uid {
}; };
static_assert(sizeof(Uid) == 0x10, "Uid has incorrect size."); static_assert(sizeof(Uid) == 0x10, "Uid has incorrect size.");
struct ApplicationDisplayData {
std::array<char, 0x200> application_name;
std::array<char, 0x100> developer_name;
};
static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size.");
} // namespace Service::NS } // namespace Service::NS

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -7,6 +10,7 @@
#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs.h"
#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/language.h" #include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/ns/ns_results.h" #include "core/hle/service/ns/ns_results.h"
#include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h"
#include "core/hle/service/set/settings_server.h" #include "core/hle/service/set/settings_server.h"
@ -23,6 +27,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
{4, nullptr, "SelectApplicationDesiredLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"},
{5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData>, "GetApplicationDisplayData"},
}; };
// clang-format on // clang-format on
@ -119,4 +124,33 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan
R_SUCCEED(); R_SUCCEED();
} }
Result IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u64> out_size, u64 language_code,
u64 application_id) {
LOG_INFO(Service_NS, "called with application_id={:016X}, language_code={:016X}",
application_id, language_code);
constexpr u64 payload_size = sizeof(ApplicationDisplayData);
if (out_buffer.size() < payload_size) {
LOG_ERROR(Service_NS, "output buffer is too small! (actual={}, expected_min={})",
out_buffer.size(), payload_size);
R_THROW(ResultUnknown);
}
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
ApplicationDisplayData display_data{};
std::memset(display_data.application_name.data(), 0, display_data.application_name.size());
std::memset(display_data.developer_name.data(), 0, display_data.developer_name.size());
std::memcpy(out_buffer.data(), &display_data, payload_size);
*out_size = payload_size;
R_SUCCEED();
}
} // namespace Service::NS } // namespace Service::NS

View file

@ -1,12 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "core/hle/service/cmif_types.h" #include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h"
#include "core/hle/service/ns/language.h" #include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/service.h"
namespace Service::NS { namespace Service::NS {
@ -16,7 +19,6 @@ public:
explicit IReadOnlyApplicationControlDataInterface(Core::System& system_); explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
~IReadOnlyApplicationControlDataInterface() override; ~IReadOnlyApplicationControlDataInterface() override;
public:
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_actual_size, Out<u32> out_actual_size,
ApplicationControlSource application_control_source, ApplicationControlSource application_control_source,
@ -25,6 +27,10 @@ public:
u32 supported_languages); u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code, Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language); ApplicationLanguage application_language);
Result GetApplicationDisplayData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u64> out_size, u64 language_code,
u64 application_id);
}; };
} // namespace Service::NS } // namespace Service::NS

View file

@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/read_only_application_record_interface.h" #include "core/hle/service/ns/read_only_application_record_interface.h"
#include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/ns/application_manager_interface.h"
namespace Service::NS { namespace Service::NS {
@ -13,6 +18,8 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S
{1, nullptr, "NotifyApplicationFailure"}, {1, nullptr, "NotifyApplicationFailure"},
{2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>, {2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>,
"IsDataCorruptedResult"}, "IsDataCorruptedResult"},
{3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>,
"ListApplicationRecord"},
}; };
// clang-format on // clang-format on
@ -35,4 +42,14 @@ Result IReadOnlyApplicationRecordInterface::IsDataCorruptedResult(
R_SUCCEED(); R_SUCCEED();
} }
Result IReadOnlyApplicationRecordInterface::ListApplicationRecord(
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
s32 entry_offset) {
LOG_DEBUG(Service_NS, "delegating to IApplicationManagerInterface::ListApplicationRecord, offset={} limit={}",
entry_offset, out_records.size());
R_RETURN(IApplicationManagerInterface(system).ListApplicationRecord(out_records, out_count,
entry_offset));
}
} // namespace Service::NS } // namespace Service::NS

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -5,6 +8,7 @@
#include "core/hle/service/cmif_types.h" #include "core/hle/service/cmif_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/hle/service/ns/ns_types.h"
namespace Service::NS { namespace Service::NS {
@ -17,6 +21,9 @@ public:
private: private:
Result HasApplicationRecord(Out<bool> out_has_application_record, u64 program_id); Result HasApplicationRecord(Out<bool> out_has_application_record, u64 program_id);
Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result); Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result);
Result ListApplicationRecord(
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
s32 entry_offset);
}; };
} // namespace Service::NS } // namespace Service::NS

View file

@ -38,6 +38,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
{1016, nullptr, "ConfirmShowNewsPermission"}, {1016, nullptr, "ConfirmShowNewsPermission"},
{1017, D<&IParentalControlService::EndFreeCommunication>, "EndFreeCommunication"}, {1017, D<&IParentalControlService::EndFreeCommunication>, "EndFreeCommunication"},
{1018, D<&IParentalControlService::IsFreeCommunicationAvailable>, "IsFreeCommunicationAvailable"}, {1018, D<&IParentalControlService::IsFreeCommunicationAvailable>, "IsFreeCommunicationAvailable"},
{1019, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"},
{1031, D<&IParentalControlService::IsRestrictionEnabled>, "IsRestrictionEnabled"}, {1031, D<&IParentalControlService::IsRestrictionEnabled>, "IsRestrictionEnabled"},
{1032, D<&IParentalControlService::GetSafetyLevel>, "GetSafetyLevel"}, {1032, D<&IParentalControlService::GetSafetyLevel>, "GetSafetyLevel"},
{1033, nullptr, "SetSafetyLevel"}, {1033, nullptr, "SetSafetyLevel"},

View file

@ -1,6 +1,3 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
@ -10,7 +7,6 @@ add_library(frontend_common STATIC
content_manager.h content_manager.h
firmware_manager.h firmware_manager.h
firmware_manager.cpp firmware_manager.cpp
data_manager.h data_manager.cpp
) )
create_target_directory_groups(frontend_common) create_target_directory_groups(frontend_common)

View file

@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "data_manager.h"
#include "common/assert.h"
#include "common/fs/path_util.h"
#include <filesystem>
#include <fmt/format.h>
namespace FrontendCommon::DataManager {
namespace fs = std::filesystem;
const std::string GetDataDir(DataDir dir, const std::string &user_id)
{
const fs::path nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
switch (dir) {
case DataDir::Saves:
return (nand_dir / "user" / "save" / "0000000000000000" / user_id).string();
case DataDir::UserNand:
return (nand_dir / "user" / "Contents" / "registered").string();
case DataDir::SysNand:
// NB: do NOT delete save
// that contains profile data and other stuff
return (nand_dir / "system" / "Contents" / "registered").string();
case DataDir::Mods:
return Common::FS::GetEdenPathString(Common::FS::EdenPath::LoadDir);
case DataDir::Shaders:
return Common::FS::GetEdenPathString(Common::FS::EdenPath::ShaderDir);
default:
UNIMPLEMENTED();
}
return "";
}
u64 ClearDir(DataDir dir, const std::string &user_id)
{
fs::path data_dir = GetDataDir(dir, user_id);
u64 result = fs::remove_all(data_dir);
// mkpath at the end just so it actually exists
fs::create_directories(data_dir);
return result;
}
const std::string ReadableBytesSize(u64 size)
{
static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
if (size == 0) {
return "0 B";
}
const int digit_groups = (std::min) (static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size()));
return fmt::format("{:.1f} {}", size / std::pow(1024, digit_groups), units[digit_groups]);
}
u64 DataDirSize(DataDir dir)
{
fs::path data_dir = GetDataDir(dir);
u64 size = 0;
if (!fs::exists(data_dir))
return 0;
for (const auto& entry : fs::recursive_directory_iterator(data_dir)) {
if (!entry.is_directory()) {
size += entry.file_size();
}
}
return size;
}
}

View file

@ -1,24 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DATA_MANAGER_H
#define DATA_MANAGER_H
#include "common/common_types.h"
#include <string>
namespace FrontendCommon::DataManager {
enum class DataDir { Saves, UserNand, SysNand, Mods, Shaders };
const std::string GetDataDir(DataDir dir, const std::string &user_id = "");
u64 ClearDir(DataDir dir, const std::string &user_id = "");
const std::string ReadableBytesSize(u64 size);
u64 DataDirSize(DataDir dir);
}; // namespace FrontendCommon::DataManager
#endif // DATA_MANAGER_H

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -159,6 +162,17 @@ struct NpadGcTriggerState {
}; };
static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
// This is nn::hid::NpadCondition (global controller condition structure)
struct NpadCondition {
u32 _00{};
u32 is_initialized{1};
u32 hold_type{static_cast<u32>(NpadJoyHoldType::Horizontal)};
u32 is_valid{1};
};
static_assert(sizeof(NpadCondition) == 0x10, "NpadCondition is an invalid size");
// This is nn::hid::NpadSystemProperties // This is nn::hid::NpadSystemProperties
struct NPadSystemProperties { struct NPadSystemProperties {
union { union {

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -44,8 +47,7 @@ struct Lifo {
buffer_count++; buffer_count++;
} }
buffer_tail = GetNextEntryIndex(); buffer_tail = GetNextEntryIndex();
const auto& previous_entry = ReadPreviousEntry(); entries[buffer_tail].sampling_number = new_state.sampling_number << 1;
entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1;
entries[buffer_tail].state = new_state; entries[buffer_tail].state = new_state;
} }
}; };

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@ -201,7 +204,9 @@ static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20,
// This is nn::hid::detail::SharedMemoryFormat // This is nn::hid::detail::SharedMemoryFormat
struct SharedMemoryFormat { struct SharedMemoryFormat {
void Initialize() {} void Initialize() {
npad_condition = NpadCondition{};
}
DebugPadSharedMemoryFormat debug_pad; DebugPadSharedMemoryFormat debug_pad;
TouchScreenSharedMemoryFormat touch_screen; TouchScreenSharedMemoryFormat touch_screen;
@ -218,7 +223,9 @@ struct SharedMemoryFormat {
ConsoleSixAxisSensorSharedMemoryFormat console; ConsoleSixAxisSensorSharedMemoryFormat console;
INSERT_PADDING_BYTES(0x19E0); INSERT_PADDING_BYTES(0x19E0);
MouseSharedMemoryFormat debug_mouse; MouseSharedMemoryFormat debug_mouse;
INSERT_PADDING_BYTES(0x2000); INSERT_PADDING_BYTES(0x200);
NpadCondition npad_condition;
INSERT_PADDING_BYTES(0x1DF0);
}; };
static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset"); static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset");
static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset"); static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset");
@ -236,6 +243,8 @@ static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offs
static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset"); static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset");
static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset"); static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset");
static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset"); static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset");
static_assert(offsetof(SharedMemoryFormat, npad_condition) == 0x3E200,
"npad_condition has wrong offset");
static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size"); static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size");
} // namespace Service::HID } // namespace Service::HID

View file

@ -4,6 +4,9 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project # SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS Core)
add_library(qt_common STATIC add_library(qt_common STATIC
qt_common.h qt_common.h
qt_common.cpp qt_common.cpp
@ -24,8 +27,6 @@ add_library(qt_common STATIC
qt_rom_util.h qt_rom_util.cpp qt_rom_util.h qt_rom_util.cpp
qt_applet_util.h qt_applet_util.cpp qt_applet_util.h qt_applet_util.cpp
qt_progress_dialog.h qt_progress_dialog.cpp qt_progress_dialog.h qt_progress_dialog.cpp
qt_string_lookup.h
qt_compress.h qt_compress.cpp
) )
@ -36,29 +37,9 @@ if (ENABLE_QT)
target_link_libraries(qt_common PRIVATE Qt6::Widgets) target_link_libraries(qt_common PRIVATE Qt6::Widgets)
endif() endif()
target_compile_definitions(qt_common PUBLIC
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
QT_USE_QSTRINGBUILDER
# Disable implicit conversions from/to C strings
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
# Disable implicit type narrowing in signal/slot connect() calls.
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
QT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
QT_NO_URL_CAST_FROM_STRING
)
add_subdirectory(externals) add_subdirectory(externals)
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip) target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip)
target_link_libraries(qt_common PUBLIC frozen::frozen)
if (NOT APPLE AND ENABLE_OPENGL) if (NOT APPLE AND ENABLE_OPENGL)
target_compile_definitions(qt_common PUBLIC HAS_OPENGL) target_compile_definitions(qt_common PUBLIC HAS_OPENGL)

View file

@ -16,4 +16,5 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
AddJsonPackage(quazip) AddJsonPackage(quazip)
# frozen # frozen
AddJsonPackage(frozen) # TODO(crueter): Qt String Lookup
# AddJsonPackage(frozen)

View file

@ -1,354 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "qt_compress.h"
#include "quazipfileinfo.h"
#include <QDirIterator>
/** This is a modified version of JlCompress **/
namespace QtCommon::Compress {
bool compressDir(QString fileCompressed,
QString dir,
const JlCompress::Options &options,
QtCommon::QtProgressCallback callback)
{
// Create zip
QuaZip zip(fileCompressed);
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
if (!zip.open(QuaZip::mdCreate)) {
QFile::remove(fileCompressed);
return false;
}
// See how big the overall fs structure is
// good approx. of total progress
// TODO(crueter): QDirListing impl
QDirIterator iter(dir,
QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files,
QDirIterator::Subdirectories);
std::size_t total = 0;
while (iter.hasNext()) {
total += iter.nextFileInfo().size();
}
std::size_t progress = 0;
callback(total, progress);
// Add the files and subdirectories
if (!compressSubDir(&zip, dir, dir, options, total, progress, callback)) {
QFile::remove(fileCompressed);
return false;
}
// Close zip
zip.close();
if (zip.getZipError() != 0) {
QFile::remove(fileCompressed);
return false;
}
return true;
}
bool compressSubDir(QuaZip *zip,
QString dir,
QString origDir,
const JlCompress::Options &options,
std::size_t total,
std::size_t &progress,
QtProgressCallback callback)
{
// zip: object where to add the file
// dir: current real directory
// origDir: original real directory
// (path(dir)-path(origDir)) = path inside the zip object
if (!zip)
return false;
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend
&& zip->getMode() != QuaZip::mdAdd)
return false;
QDir directory(dir);
if (!directory.exists())
return false;
QDir origDirectory(origDir);
if (dir != origDir) {
QuaZipFile dirZipFile(zip);
std::unique_ptr<QuaZipNewInfo> qzni;
if (options.getDateTime().isNull()) {
qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir)
+ QLatin1String("/"),
dir);
} else {
qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir)
+ QLatin1String("/"),
dir,
options.getDateTime());
}
if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) {
return false;
}
dirZipFile.close();
}
// For each subfolder
QFileInfoList subfiles = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot
| QDir::Hidden | QDir::Dirs);
for (const auto &file : std::as_const(subfiles)) {
if (!compressSubDir(
zip, file.absoluteFilePath(), origDir, options, total, progress, callback)) {
return false;
}
}
// For each file in directory
QFileInfoList files = directory.entryInfoList(QDir::Hidden | QDir::Files);
for (const auto &file : std::as_const(files)) {
// If it's not a file or it's the compressed file being created
if (!file.isFile() || file.absoluteFilePath() == zip->getZipName())
continue;
// Create relative name for the compressed file
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
// Compress the file
if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, callback)) {
return false;
}
}
return true;
}
bool compressFile(QuaZip *zip,
QString fileName,
QString fileDest,
const JlCompress::Options &options,
std::size_t total,
std::size_t &progress,
QtCommon::QtProgressCallback callback)
{
// zip: object where to add the file
// fileName: real file name
// fileDest: file name inside the zip object
if (!zip)
return false;
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend
&& zip->getMode() != QuaZip::mdAdd)
return false;
QuaZipFile outFile(zip);
if (options.getDateTime().isNull()) {
if (!outFile.open(QIODevice::WriteOnly,
QuaZipNewInfo(fileDest, fileName),
nullptr,
0,
options.getCompressionMethod(),
options.getCompressionLevel()))
return false;
} else {
if (!outFile.open(QIODevice::WriteOnly,
QuaZipNewInfo(fileDest, fileName, options.getDateTime()),
nullptr,
0,
options.getCompressionMethod(),
options.getCompressionLevel()))
return false;
}
QFileInfo input(fileName);
if (quazip_is_symlink(input)) {
// Not sure if we should use any specialized codecs here.
// After all, a symlink IS just a byte array. And
// this is mostly for Linux, where UTF-8 is ubiquitous these days.
QString path = quazip_symlink_target(input);
QString relativePath = input.dir().relativeFilePath(path);
outFile.write(QFile::encodeName(relativePath));
} else {
QFile inFile;
inFile.setFileName(fileName);
if (!inFile.open(QIODevice::ReadOnly)) {
return false;
}
if (!copyData(inFile, outFile, total, progress, callback) || outFile.getZipError() != UNZ_OK) {
return false;
}
inFile.close();
}
outFile.close();
return outFile.getZipError() == UNZ_OK;
}
bool copyData(QIODevice &inFile,
QIODevice &outFile,
std::size_t total,
std::size_t &progress,
QtProgressCallback callback)
{
while (!inFile.atEnd()) {
char buf[4096];
qint64 readLen = inFile.read(buf, 4096);
if (readLen <= 0)
return false;
if (outFile.write(buf, readLen) != readLen)
return false;
progress += readLen;
if (!callback(total, progress)) {
return false;
}
}
return true;
}
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback)
{
// Open zip
QuaZip zip(fileCompressed);
return extractDir(zip, dir, callback);
}
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback)
{
if (!zip.open(QuaZip::mdUnzip)) {
return QStringList();
}
QString cleanDir = QDir::cleanPath(dir);
QDir directory(cleanDir);
QString absCleanDir = directory.absolutePath();
if (!absCleanDir.endsWith(QLatin1Char('/'))) // It only ends with / if it's the FS root.
absCleanDir += QLatin1Char('/');
QStringList extracted;
if (!zip.goToFirstFile()) {
return QStringList();
}
std::size_t total = 0;
for (const QuaZipFileInfo64 &info : zip.getFileInfoList64()) {
total += info.uncompressedSize;
}
std::size_t progress = 0;
callback(total, progress);
do {
QString name = zip.getCurrentFileName();
QString absFilePath = directory.absoluteFilePath(name);
QString absCleanPath = QDir::cleanPath(absFilePath);
if (!absCleanPath.startsWith(absCleanDir))
continue;
if (!extractFile(&zip, QLatin1String(""), absFilePath, total, progress, callback)) {
removeFile(extracted);
return QStringList();
}
extracted.append(absFilePath);
} while (zip.goToNextFile());
// Close zip
zip.close();
if (zip.getZipError() != 0) {
removeFile(extracted);
return QStringList();
}
return extracted;
}
bool extractFile(QuaZip *zip,
QString fileName,
QString fileDest,
std::size_t total,
std::size_t &progress,
QtCommon::QtProgressCallback callback)
{
// zip: object where to add the file
// filename: real file name
// fileincompress: file name of the compressed file
if (!zip)
return false;
if (zip->getMode() != QuaZip::mdUnzip)
return false;
if (!fileName.isEmpty())
zip->setCurrentFile(fileName);
QuaZipFile inFile(zip);
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
return false;
// Check existence of resulting file
QDir curDir;
if (fileDest.endsWith(QLatin1String("/"))) {
if (!curDir.mkpath(fileDest)) {
return false;
}
} else {
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) {
return false;
}
}
QuaZipFileInfo64 info;
if (!zip->getCurrentFileInfo(&info))
return false;
QFile::Permissions srcPerm = info.getPermissions();
if (fileDest.endsWith(QLatin1String("/")) && QFileInfo(fileDest).isDir()) {
if (srcPerm != 0) {
QFile(fileDest).setPermissions(srcPerm);
}
return true;
}
if (info.isSymbolicLink()) {
QString target = QFile::decodeName(inFile.readAll());
return QFile::link(target, fileDest);
}
// Open resulting file
QFile outFile;
outFile.setFileName(fileDest);
if (!outFile.open(QIODevice::WriteOnly))
return false;
// Copy data
if (!copyData(inFile, outFile, total, progress, callback) || inFile.getZipError() != UNZ_OK) {
outFile.close();
removeFile(QStringList(fileDest));
return false;
}
outFile.close();
// Close file
inFile.close();
if (inFile.getZipError() != UNZ_OK) {
removeFile(QStringList(fileDest));
return false;
}
if (srcPerm != 0) {
outFile.setPermissions(srcPerm);
}
return true;
}
bool removeFile(QStringList listFile)
{
bool ret = true;
// For each file
for (int i = 0; i < listFile.count(); i++) {
// Remove
ret = ret && QFile::remove(listFile.at(i));
}
return ret;
}
} // namespace QtCommon::Compress

View file

@ -1,71 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QDir>
#include <QString>
#include <JlCompress.h>
#include "qt_common/qt_common.h"
/** This is a modified version of JlCompress **/
namespace QtCommon::Compress {
/**
* @brief Compress an entire directory and report its progress.
* @param fileCompressed Destination file
* @param dir The directory to compress
* @param options Compression level, etc
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled.
*/
bool compressDir(QString fileCompressed,
QString dir,
const JlCompress::Options& options = JlCompress::Options(),
QtCommon::QtProgressCallback callback = {});
// Internal //
bool compressSubDir(QuaZip *zip,
QString dir,
QString origDir,
const JlCompress::Options &options,
std::size_t total,
std::size_t &progress,
QtCommon::QtProgressCallback callback);
bool compressFile(QuaZip *zip,
QString fileName,
QString fileDest,
const JlCompress::Options &options,
std::size_t total,
std::size_t &progress,
QtCommon::QtProgressCallback callback);
bool copyData(QIODevice &inFile,
QIODevice &outFile,
std::size_t total,
std::size_t &progress,
QtCommon::QtProgressCallback callback);
// Extract //
/**
* @brief Extract a zip file and report its progress.
* @param fileCompressed Compressed file
* @param dir The directory to push the results to
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled.
*/
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback = {});
// Internal //
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback);
bool extractFile(QuaZip *zip,
QString fileName,
QString fileDest,
std::size_t total,
std::size_t &progress,
QtCommon::QtProgressCallback callback);
bool removeFile(QStringList listFile);
}

View file

@ -1,22 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "qt_common/qt_game_util.h"
#include "qt_content_util.h" #include "qt_content_util.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/acc/profile_manager.h"
#include "frontend_common/content_manager.h" #include "frontend_common/content_manager.h"
#include "frontend_common/data_manager.h"
#include "frontend_common/firmware_manager.h" #include "frontend_common/firmware_manager.h"
#include "qt_common/qt_common.h" #include "qt_common/qt_common.h"
#include "qt_common/qt_compress.h"
#include "qt_common/qt_game_util.h"
#include "qt_common/qt_progress_dialog.h" #include "qt_common/qt_progress_dialog.h"
#include "qt_frontend_util.h" #include "qt_frontend_util.h"
#include <QFuture>
#include <QtConcurrentRun>
#include <JlCompress.h> #include <JlCompress.h>
#include <qfuturewatcher.h>
namespace QtCommon::Content { namespace QtCommon::Content {
@ -26,10 +21,10 @@ bool CheckGameFirmware(u64 program_id, QObject* parent)
&& !FirmwareManager::CheckFirmwarePresence(*system)) { && !FirmwareManager::CheckFirmwarePresence(*system)) {
auto result = QtCommon::Frontend::ShowMessage( auto result = QtCommon::Frontend::ShowMessage(
QMessageBox::Warning, QMessageBox::Warning,
tr("Game Requires Firmware"), "Game Requires Firmware",
tr("The game you are trying to launch requires firmware to boot or to get past the " "The game you are trying to launch requires firmware to boot or to get past the "
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>" "opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
"dump and install firmware</a>, or press \"OK\" to launch anyways."), "dump and install firmware</a>, or press \"OK\" to launch anyways.",
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok | QMessageBox::Cancel,
parent); parent);
@ -65,8 +60,8 @@ void InstallFirmware(const QString& location, bool recursive)
const auto ShowMessage = [&]() { const auto ShowMessage = [&]() {
QtCommon::Frontend::ShowMessage(icon, QtCommon::Frontend::ShowMessage(icon,
tr(failedTitle), failedTitle,
tr(GetFirmwareInstallResultString(result))); GetFirmwareInstallResultString(result));
}; };
LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
@ -130,8 +125,8 @@ void InstallFirmware(const QString& location, bool recursive)
i++; i++;
auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(), auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(),
FileSys::OpenMode::Read); FileSys::OpenMode::Read);
auto firmware_dst_vfile = firmware_vdir->CreateFileRelative( auto firmware_dst_vfile = firmware_vdir
firmware_src_path.filename().string()); ->CreateFileRelative(firmware_src_path.filename().string());
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
LOG_ERROR(Frontend, LOG_ERROR(Frontend,
@ -173,9 +168,9 @@ void InstallFirmware(const QString& location, bool recursive)
const auto failed_names = QString::fromStdString( const auto failed_names = QString::fromStdString(
fmt::format("{}", fmt::join(results, "\n"))); fmt::format("{}", fmt::join(results, "\n")));
progress.close(); progress.close();
QtCommon::Frontend::Critical( QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"),
tr("Firmware integrity verification failed!"), tr("Verification failed for the following files:\n\n%1")
tr("Verification failed for the following files:\n\n%1").arg(failed_names)); .arg(failed_names));
return; return;
} }
@ -186,10 +181,10 @@ void InstallFirmware(const QString& location, bool recursive)
const std::string display_version(firmware_data.display_version.data()); const std::string display_version(firmware_data.display_version.data());
result = FirmwareInstallResult::Success; result = FirmwareInstallResult::Success;
QtCommon::Frontend::Information( QtCommon::Frontend::Information(rootObject,
rootObject,
tr(successTitle), tr(successTitle),
tr(GetFirmwareInstallResultString(result)).arg(QString::fromStdString(display_version))); tr(GetFirmwareInstallResultString(result))
.arg(QString::fromStdString(display_version)));
} }
QString UnzipFirmwareToTmp(const QString& location) QString UnzipFirmwareToTmp(const QString& location)
@ -198,7 +193,7 @@ QString UnzipFirmwareToTmp(const QString& location)
fs::path tmp{fs::temp_directory_path()}; fs::path tmp{fs::temp_directory_path()};
if (!fs::create_directories(tmp / "eden" / "firmware")) { if (!fs::create_directories(tmp / "eden" / "firmware")) {
return QString(); return "";
} }
tmp /= "eden"; tmp /= "eden";
@ -210,7 +205,7 @@ QString UnzipFirmwareToTmp(const QString& location)
QStringList result = JlCompress::extractDir(&zip, qCacheDir); QStringList result = JlCompress::extractDir(&zip, qCacheDir);
if (result.isEmpty()) { if (result.isEmpty()) {
return QString(); return "";
} }
return qCacheDir; return qCacheDir;
@ -269,8 +264,9 @@ void InstallKeys()
return; return;
} }
FirmwareManager::KeyInstallResult result FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location
= FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys"); .toStdString(),
"keys");
system->GetFileSystemController().CreateFactories(*QtCommon::vfs); system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
@ -286,14 +282,9 @@ void InstallKeys()
} }
} }
void VerifyInstalledContents() void VerifyInstalledContents() {
{
// Initialize a progress dialog. // Initialize a progress dialog.
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject);
tr("Cancel"),
0,
100,
QtCommon::rootObject);
progress.setWindowModality(Qt::WindowModal); progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100); progress.setMinimumDuration(100);
progress.setAutoClose(false); progress.setAutoClose(false);
@ -305,17 +296,16 @@ void VerifyInstalledContents()
return progress.wasCanceled(); return progress.wasCanceled();
}; };
const std::vector<std::string> result const std::vector<std::string> result =
= ContentManager::VerifyInstalledContents(*QtCommon::system, ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback);
*QtCommon::provider,
QtProgressCallback);
progress.close(); progress.close();
if (result.empty()) { if (result.empty()) {
QtCommon::Frontend::Information(tr("Integrity verification succeeded!"), QtCommon::Frontend::Information(tr("Integrity verification succeeded!"),
tr("The operation completed successfully.")); tr("The operation completed successfully."));
} else { } else {
const auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); const auto failed_names =
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
QtCommon::Frontend::Critical( QtCommon::Frontend::Critical(
tr("Integrity verification failed!"), tr("Integrity verification failed!"),
tr("Verification failed for the following files:\n\n%1").arg(failed_names)); tr("Verification failed for the following files:\n\n%1").arg(failed_names));
@ -342,7 +332,7 @@ void FixProfiles()
qorphaned.reserve(8 * 33); qorphaned.reserve(8 * 33);
for (const std::string& s : orphaned) { for (const std::string& s : orphaned) {
qorphaned = qorphaned % QStringLiteral("\n") % QString::fromStdString(s); qorphaned += "\n" + QString::fromStdString(s);
} }
QtCommon::Frontend::Critical( QtCommon::Frontend::Critical(
@ -358,183 +348,4 @@ void FixProfiles()
QtCommon::Game::OpenSaveFolder(); QtCommon::Game::OpenSaveFolder();
} }
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id)
{
auto result = QtCommon::Frontend::Warning(tr("Really clear data?"),
tr("Important data may be lost!"),
QMessageBox::Yes | QMessageBox::No);
if (result != QMessageBox::Yes)
return;
result = QtCommon::Frontend::Warning(
tr("Are you REALLY sure?"),
tr("Once deleted, your data will NOT come back!\n"
"Only do this if you're 100% sure you want to delete this data."),
QMessageBox::Yes | QMessageBox::No);
if (result != QMessageBox::Yes)
return;
QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0);
dialog.show();
FrontendCommon::DataManager::ClearDir(dir, user_id);
dialog.close();
}
void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir,
const std::string& user_id,
std::function<void()> callback)
{
using namespace QtCommon::Frontend;
const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir, user_id);
const QString zip_dump_location = GetSaveFileName(tr("Select Export Location"),
QStringLiteral("export.zip"),
tr("Zipped Archives (*.zip)"));
if (zip_dump_location.isEmpty())
return;
QtProgressDialog* progress = new QtProgressDialog(
tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100, rootObject);
progress->setWindowTitle(tr("Exporting"));
progress->setWindowModality(Qt::WindowModal);
progress->setMinimumDuration(100);
progress->setAutoClose(false);
progress->setAutoReset(false);
progress->show();
QGuiApplication::processEvents();
auto progress_callback = [=](size_t total_size, size_t processed_size) {
QMetaObject::invokeMethod(progress,
&QtProgressDialog::setValue,
static_cast<int>((processed_size * 100) / total_size));
return !progress->wasCanceled();
};
QFuture<bool> future = QtConcurrent::run([=]() {
return QtCommon::Compress::compressDir(zip_dump_location,
QString::fromStdString(dir),
JlCompress::Options(),
progress_callback);
});
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
progress->close();
if (watcher->result()) {
Information(tr("Exported Successfully"), tr("Data was exported successfully."));
} else if (progress->wasCanceled()) {
Information(tr("Export Cancelled"), tr("Export was cancelled by the user."));
} else {
Critical(
tr("Export Failed"),
tr("Ensure you have write permissions on the targeted directory and try again."));
}
progress->deleteLater();
watcher->deleteLater();
if (callback)
callback();
});
watcher->setFuture(future);
}
void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir,
const std::string& user_id,
std::function<void()> callback)
{
const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir, user_id);
using namespace QtCommon::Frontend;
const QString zip_dump_location = GetOpenFileName(tr("Select Import Location"),
{},
tr("Zipped Archives (*.zip)"));
if (zip_dump_location.isEmpty())
return;
StandardButton button = Warning(
tr("Import Warning"),
tr("All previous data in this directory will be deleted. Are you sure you wish to "
"proceed?"),
StandardButton::Yes | StandardButton::No);
if (button != QMessageBox::Yes)
return;
QtProgressDialog* progress = new QtProgressDialog(
tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100, rootObject);
progress->setWindowTitle(tr("Importing"));
progress->setWindowModality(Qt::WindowModal);
progress->setMinimumDuration(100);
progress->setAutoClose(false);
progress->setAutoReset(false);
progress->show();
progress->setValue(0);
QGuiApplication::processEvents();
// to prevent GUI mangling we have to run this in a thread as well
QFuture<bool> delete_future = QtConcurrent::run([=]() {
FrontendCommon::DataManager::ClearDir(data_dir, user_id);
return !progress->wasCanceled();
});
QFutureWatcher<bool>* delete_watcher = new QFutureWatcher<bool>(rootObject);
delete_watcher->setFuture(delete_future);
QObject::connect(delete_watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
auto progress_callback = [=](size_t total_size, size_t processed_size) {
QMetaObject::invokeMethod(progress,
&QtProgressDialog::setValue,
static_cast<int>((processed_size * 100) / total_size));
return !progress->wasCanceled();
};
QFuture<bool> future = QtConcurrent::run([=]() {
return !QtCommon::Compress::extractDir(zip_dump_location,
QString::fromStdString(dir),
progress_callback)
.empty();
});
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
progress->close();
// this sucks
if (watcher->result()) {
Information(tr("Imported Successfully"), tr("Data was imported successfully."));
} else if (progress->wasCanceled()) {
Information(tr("Import Cancelled"), tr("Import was cancelled by the user."));
} else {
Critical(tr("Import Failed"),
tr("Ensure you have read permissions on the targeted directory and try "
"again."));
}
progress->deleteLater();
watcher->deleteLater();
if (callback)
callback();
});
watcher->setFuture(future);
});
}
} // namespace QtCommon::Content } // namespace QtCommon::Content

View file

@ -6,7 +6,6 @@
#include <QObject> #include <QObject>
#include "common/common_types.h" #include "common/common_types.h"
#include "frontend_common/data_manager.h"
namespace QtCommon::Content { namespace QtCommon::Content {
@ -47,10 +46,6 @@ void InstallKeys();
void VerifyGameContents(const std::string &game_path); void VerifyGameContents(const std::string &game_path);
void VerifyInstalledContents(); void VerifyInstalledContents();
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "");
void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "", std::function<void()> callback = {});
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "", std::function<void()> callback = {});
// Profiles // // Profiles //
void FixProfiles(); void FixProfiles();
} }

View file

@ -32,15 +32,4 @@ const QString GetOpenFileName(const QString &title,
#endif #endif
} }
const QString GetSaveFileName(const QString &title,
const QString &dir,
const QString &filter,
QString *selectedFilter,
Options options)
{
#ifdef YUZU_QT_WIDGETS
return QFileDialog::getSaveFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
#endif
}
} // namespace QtCommon::Frontend } // namespace QtCommon::Frontend

View file

@ -110,6 +110,21 @@ StandardButton ShowMessage(Icon icon,
{ \ { \
return ShowMessage(Icon::level, title, text, buttons, parent); \ return ShowMessage(Icon::level, title, text, buttons, parent); \
} \ } \
inline StandardButton level(QObject *parent, \
const char *title, \
const char *text, \
StandardButtons buttons \
= StandardButton::Ok) \
{ \
return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \
} \
inline StandardButton level(const char *title, \
const char *text, \
StandardButtons buttons \
= StandardButton::Ok) \
{ \
return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \
} \
inline StandardButton level(const QString title, \ inline StandardButton level(const QString title, \
const QString &text, \ const QString &text, \
StandardButtons buttons \ StandardButtons buttons \
@ -129,11 +144,5 @@ const QString GetOpenFileName(const QString &title,
QString *selectedFilter = nullptr, QString *selectedFilter = nullptr,
Options options = Options()); Options options = Options());
const QString GetSaveFileName(const QString &title,
const QString &dir,
const QString &filter,
QString *selectedFilter = nullptr,
Options options = Options());
} // namespace QtCommon::Frontend } // namespace QtCommon::Frontend
#endif // QT_FRONTEND_UTIL_H #endif // QT_FRONTEND_UTIL_H

View file

@ -220,8 +220,8 @@ void RemoveBaseContent(u64 program_id, InstalledEntryType type)
program_id); program_id);
if (res) { if (res) {
QtCommon::Frontend::Information(rootObject, QtCommon::Frontend::Information(rootObject,
tr("Successfully Removed"), "Successfully Removed",
tr("Successfully removed the installed base game.")); "Successfully removed the installed base game.");
} else { } else {
QtCommon::Frontend::Warning( QtCommon::Frontend::Warning(
rootObject, rootObject,
@ -235,8 +235,8 @@ void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
if (res) { if (res) {
QtCommon::Frontend::Information(rootObject, QtCommon::Frontend::Information(rootObject,
tr("Successfully Removed"), "Successfully Removed",
tr("Successfully removed the installed update.")); "Successfully removed the installed update.");
} else { } else {
QtCommon::Frontend::Warning(rootObject, QtCommon::Frontend::Warning(rootObject,
GetGameListErrorRemoving(type), GetGameListErrorRemoving(type),

View file

@ -17,12 +17,7 @@ bool OpenShaderCache(u64 program_id, QObject *parent)
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)}; const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)};
if (!Common::FS::CreateDirs(shader_cache_folder_path)) { if (!Common::FS::CreateDirs(shader_cache_folder_path)) {
QtCommon::Frontend::ShowMessage(QMessageBox::Warning, QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent);
tr("Error Opening Shader Cache"),
tr("Failed to create or open shader cache for this title, "
"ensure your app data directory has write permissions."),
QMessageBox::Ok,
parent);
} }
const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)}; const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};

View file

@ -1,38 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QString>
#include <frozen/string.h>
#include <frozen/unordered_map.h>
#include <qobjectdefs.h>
#include <qtmetamacros.h>
namespace QtCommon::StringLookup {
Q_NAMESPACE
// TODO(crueter): QML interface
enum StringKey {
SavesTooltip,
ShadersTooltip,
UserNandTooltip,
SysNandTooltip,
ModsTooltip,
};
static constexpr const frozen::unordered_map<StringKey, frozen::string, 5> strings = {
{SavesTooltip, "Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"},
{ShadersTooltip, "Contains Vulkan and OpenGL pipeline caches. Generally safe to remove."},
{UserNandTooltip, "Contains updates and DLC for games."},
{SysNandTooltip, "Contains firmware and applet data."},
{ModsTooltip, "Contains game mods, patches, and cheats."},
};
static inline const QString Lookup(StringKey key)
{
return QString::fromStdString(strings.at(key).data());
}
}

View file

@ -386,11 +386,9 @@ void BufferCache<P>::BindHostComputeBuffers() {
template <class P> template <class P>
void BufferCache<P>::SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask, void BufferCache<P>::SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
const UniformBufferSizes* sizes) { const UniformBufferSizes* sizes) {
if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
if (channel_state->enabled_uniform_buffer_masks != mask) { if (channel_state->enabled_uniform_buffer_masks != mask) {
if constexpr (IS_OPENGL) {
channel_state->fast_bound_uniform_buffers.fill(0); channel_state->fast_bound_uniform_buffers.fill(0);
} if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
channel_state->dirty_uniform_buffers.fill(~u32{0}); channel_state->dirty_uniform_buffers.fill(~u32{0});
channel_state->uniform_buffer_binding_sizes.fill({}); channel_state->uniform_buffer_binding_sizes.fill({});
} }
@ -806,7 +804,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size;
if (should_fast_bind) { if (should_fast_bind) {
// We only have to bind when the currently bound buffer is not the fast version // We only have to bind when the currently bound buffer is not the fast version
channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index;
channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
runtime.BindFastUniformBuffer(stage, binding_index, size); runtime.BindFastUniformBuffer(stage, binding_index, size);
} }
@ -815,10 +813,8 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
return; return;
} }
} }
if constexpr (IS_OPENGL) { channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index;
channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index;
channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size;
}
// Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan
const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size); const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
device_memory.ReadBlockUnsafe(device_addr, span.data(), size); device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
@ -839,9 +835,6 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
} }
const u32 offset = buffer.Offset(device_addr); const u32 offset = buffer.Offset(device_addr);
if constexpr (IS_OPENGL) { if constexpr (IS_OPENGL) {
// Fast buffer will be unbound
channel_state->fast_bound_uniform_buffers[stage] &= ~(1U << binding_index);
// Mark the index as dirty if offset doesn't match // Mark the index as dirty if offset doesn't match
const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset(); const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset();
channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index; channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index;
@ -855,6 +848,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
} else { } else {
runtime.BindUniformBuffer(buffer, offset, size); runtime.BindUniformBuffer(buffer, offset, size);
} }
channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index);
} }
template <class P> template <class P>
@ -1789,12 +1783,7 @@ std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) {
template <class P> template <class P>
bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept {
if constexpr (IS_OPENGL) { return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1u) != 0;
return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0;
} else {
// Only OpenGL has fast uniform buffers
return false;
}
} }
template <class P> template <class P>

View file

@ -54,6 +54,8 @@ constexpr u32 NUM_STORAGE_BUFFERS = 16;
constexpr u32 NUM_TEXTURE_BUFFERS = 32; constexpr u32 NUM_TEXTURE_BUFFERS = 32;
constexpr u32 NUM_STAGES = 5; constexpr u32 NUM_STAGES = 5;
static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32");
using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>; using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>;
using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>; using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
@ -137,8 +139,8 @@ public:
u32 written_compute_texture_buffers = 0; u32 written_compute_texture_buffers = 0;
u32 image_compute_texture_buffers = 0; u32 image_compute_texture_buffers = 0;
std::array<u32, 16> uniform_cache_hits{}; std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS> uniform_cache_hits{};
std::array<u32, 16> uniform_cache_shots{}; std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS> uniform_cache_shots{};
u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE; u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE;

View file

@ -234,9 +234,6 @@ add_executable(yuzu
deps_dialog.cpp deps_dialog.cpp
deps_dialog.h deps_dialog.h
deps_dialog.ui deps_dialog.ui
data_dialog.h data_dialog.cpp data_dialog.ui
data_widget.ui
) )
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
@ -393,13 +390,32 @@ endif()
target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common) target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Concurrent) target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
target_link_libraries(yuzu PRIVATE Qt6::DBus) target_link_libraries(yuzu PRIVATE Qt6::DBus)
endif() endif()
target_compile_definitions(yuzu PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
QT_USE_QSTRINGBUILDER
# Disable implicit conversions from/to C strings
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
# Disable implicit type narrowing in signal/slot connect() calls.
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
QT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
QT_NO_URL_CAST_FROM_STRING
)
if (YUZU_ENABLE_COMPATIBILITY_REPORTING) if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
target_compile_definitions(yuzu PRIVATE YUZU_ENABLE_COMPATIBILITY_REPORTING) target_compile_definitions(yuzu PRIVATE YUZU_ENABLE_COMPATIBILITY_REPORTING)
endif() endif()

View file

@ -1,13 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <QButtonGroup> #include <QButtonGroup>
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <qtconcurrentrun.h> #include <QtConcurrent/qtconcurrentrun.h>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "ui_compatdb.h" #include "ui_compatdb.h"
#include "yuzu/compatdb.h" #include "yuzu/compatdb.h"

View file

@ -14,7 +14,7 @@
#include <QRegExpValidator> #include <QRegExpValidator>
#endif #endif
#include <QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/settings.h" #include "common/settings.h"
#include "ui_configure_web.h" #include "ui_configure_web.h"
#include "qt_common/uisettings.h" #include "qt_common/uisettings.h"

View file

@ -1,165 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "data_dialog.h"
#include "core/hle/service/acc/profile_manager.h"
#include "frontend_common/data_manager.h"
#include "qt_common/qt_content_util.h"
#include "qt_common/qt_frontend_util.h"
#include "qt_common/qt_progress_dialog.h"
#include "qt_common/qt_string_lookup.h"
#include "ui_data_dialog.h"
#include <QDesktopServices>
#include <QFileDialog>
#include <QFutureWatcher>
#include <QProgressDialog>
#include <QtConcurrentRun>
#include <core/frontend/applets/profile_select.h>
#include <applets/qt_profile_select.h>
DataDialog::DataDialog(QWidget *parent)
: QDialog(parent)
, ui(std::make_unique<Ui::DataDialog>())
{
ui->setupUi(this);
// TODO: Should we make this a single widget that pulls data from a model?
#define WIDGET(name) \
ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \
QtCommon::StringLookup::name##Tooltip, \
this));
WIDGET(Saves)
WIDGET(Shaders)
WIDGET(UserNand)
WIDGET(SysNand)
WIDGET(Mods)
#undef WIDGET
connect(ui->labels, &QListWidget::itemSelectionChanged, this, [this]() {
const auto items = ui->labels->selectedItems();
if (items.isEmpty()) {
return;
}
ui->page->setCurrentIndex(ui->labels->row(items[0]));
});
}
DataDialog::~DataDialog() = default;
DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir,
QtCommon::StringLookup::StringKey tooltip,
QWidget *parent)
: QWidget(parent)
, ui(std::make_unique<Ui::DataWidget>())
, m_dir(data_dir)
{
ui->setupUi(this);
ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip));
ui->clear->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
ui->open->setIcon(QIcon::fromTheme(QStringLiteral("folder")));
ui->upload->setIcon(QIcon::fromTheme(QStringLiteral("upload")));
ui->download->setIcon(QIcon::fromTheme(QStringLiteral("download")));
connect(ui->clear, &QPushButton::clicked, this, &DataWidget::clear);
connect(ui->open, &QPushButton::clicked, this, &DataWidget::open);
connect(ui->upload, &QPushButton::clicked, this, &DataWidget::upload);
connect(ui->download, &QPushButton::clicked, this, &DataWidget::download);
scan();
}
void DataWidget::clear()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
}
QtCommon::Content::ClearDataDir(m_dir, user_id);
scan();
}
void DataWidget::open()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
}
QDesktopServices::openUrl(QUrl::fromLocalFile(
QString::fromStdString(FrontendCommon::DataManager::GetDataDir(m_dir, user_id))));
}
void DataWidget::upload()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
}
QtCommon::Content::ExportDataDir(m_dir, user_id);
}
void DataWidget::download()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
}
QtCommon::Content::ImportDataDir(m_dir, user_id, std::bind(&DataWidget::scan, this));
}
void DataWidget::scan() {
ui->size->setText(tr("Calculating..."));
QFutureWatcher<u64> *watcher = new QFutureWatcher<u64>(this);
connect(watcher, &QFutureWatcher<u64>::finished, this, [=, this]() {
u64 size = watcher->result();
ui->size->setText(
QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size)));
watcher->deleteLater();
});
watcher->setFuture(
QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); }));
}
std::string DataWidget::selectProfile()
{
const auto select_profile = [this] {
const Core::Frontend::ProfileSelectParameters parameters{
.mode = Service::AM::Frontend::UiMode::UserSelector,
.invalid_uid_list = {},
.display_options = {},
.purpose = Service::AM::Frontend::UserSelectionPurpose::General,
};
QtProfileSelectionDialog dialog(*QtCommon::system, this, parameters);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint
| Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
return -1;
}
return dialog.GetIndex();
};
const auto index = select_profile();
if (index == -1) {
return "";
}
const auto uuid = QtCommon::system->GetProfileManager().GetUser(static_cast<std::size_t>(index));
ASSERT(uuid);
const auto user_id = uuid->AsU128();
return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]);
}

View file

@ -1,52 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DATA_DIALOG_H
#define DATA_DIALOG_H
#include <QDialog>
#include "frontend_common/data_manager.h"
#include "qt_common/qt_string_lookup.h"
#include "ui_data_widget.h"
namespace Ui {
class DataDialog;
}
class DataDialog : public QDialog
{
Q_OBJECT
public:
explicit DataDialog(QWidget *parent = nullptr);
~DataDialog();
private:
std::unique_ptr<Ui::DataDialog> ui;
};
class DataWidget : public QWidget
{
Q_OBJECT
public:
explicit DataWidget(FrontendCommon::DataManager::DataDir data_dir,
QtCommon::StringLookup::StringKey tooltip,
QWidget *parent = nullptr);
public slots:
void clear();
void open();
void upload();
void download();
void scan();
private:
std::unique_ptr<Ui::DataWidget> ui;
FrontendCommon::DataManager::DataDir m_dir;
std::string selectProfile();
};
#endif // DATA_DIALOG_H

View file

@ -1,148 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DataDialog</class>
<widget class="QDialog" name="DataDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>320</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>320</height>
</size>
</property>
<property name="windowTitle">
<string>Data Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<widget class="QListWidget" name="labels">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Saves</string>
</property>
</item>
<item>
<property name="text">
<string>Shaders</string>
</property>
</item>
<item>
<property name="text">
<string>UserNAND</string>
</property>
</item>
<item>
<property name="text">
<string>SysNAND</string>
</property>
</item>
<item>
<property name="text">
<string>Mods</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="page">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>275</width>
<height>200</height>
</size>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Deleting ANY data is IRREVERSABLE!</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DataDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DataDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -1,205 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DataWidget</class>
<widget class="QWidget" name="DataWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>275</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="3,2">
<item>
<widget class="QLabel" name="tooltip">
<property name="text">
<string>Tooltip</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="size">
<property name="font">
<font>
<pointsize>10</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true">Size</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="open">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Open with your system file manager</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clear">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Delete all data in this directory. THIS IS 100% IRREVERSABLE!</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="upload">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Export all data in this directory. This may take a while!</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="download">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Import data for this directory. This may take a while, and will delete ALL EXISTING DATA!</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -156,7 +156,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/debugger/console.h" #include "yuzu/debugger/console.h"
#include "yuzu/debugger/controller.h" #include "yuzu/debugger/controller.h"
#include "yuzu/debugger/wait_tree.h" #include "yuzu/debugger/wait_tree.h"
#include "yuzu/data_dialog.h"
#include "yuzu/deps_dialog.h" #include "yuzu/deps_dialog.h"
#include "yuzu/discord.h" #include "yuzu/discord.h"
#include "yuzu/game_list.h" #include "yuzu/game_list.h"
@ -1706,7 +1705,6 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Install_Keys, &GMainWindow::OnInstallDecryptionKeys); connect_menu(ui->action_Install_Keys, &GMainWindow::OnInstallDecryptionKeys);
connect_menu(ui->action_About, &GMainWindow::OnAbout); connect_menu(ui->action_About, &GMainWindow::OnAbout);
connect_menu(ui->action_Eden_Dependencies, &GMainWindow::OnEdenDependencies); connect_menu(ui->action_Eden_Dependencies, &GMainWindow::OnEdenDependencies);
connect_menu(ui->action_Data_Manager, &GMainWindow::OnDataDialog);
} }
void GMainWindow::UpdateMenuState() { void GMainWindow::UpdateMenuState() {
@ -2408,6 +2406,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?"); ASSERT_MSG(has_user_save != has_device_save, "Game uses both user and device savedata?");
// TODO(alekpop): It returns the wrong user
switch (target) { switch (target) {
case GameListOpenTarget::SaveData: { case GameListOpenTarget::SaveData: {
open_target = tr("Save Data"); open_target = tr("Save Data");
@ -2793,7 +2792,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
} }
void GMainWindow::OnGameListOpenDirectory(const QString& directory) { void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
// TODO(crueter): QtCommon
std::filesystem::path fs_path; std::filesystem::path fs_path;
if (directory == QStringLiteral("SDMC")) { if (directory == QStringLiteral("SDMC")) {
fs_path = fs_path =
@ -3936,15 +3934,6 @@ void GMainWindow::OnEdenDependencies() {
depsDialog.exec(); depsDialog.exec();
} }
void GMainWindow::OnDataDialog() {
DataDialog dataDialog(this);
dataDialog.exec();
// refresh stuff in case it was cleared
OnGameListRefresh();
}
void GMainWindow::OnToggleFilterBar() { void GMainWindow::OnToggleFilterBar() {
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked()); game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
if (ui->action_Show_Filter_Bar->isChecked()) { if (ui->action_Show_Filter_Bar->isChecked()) {
@ -4485,15 +4474,11 @@ void GMainWindow::SetFirmwareVersion() {
if (result.IsError() || !CheckFirmwarePresence()) { if (result.IsError() || !CheckFirmwarePresence()) {
LOG_INFO(Frontend, "Installed firmware: No firmware available"); LOG_INFO(Frontend, "Installed firmware: No firmware available");
ui->menu_Applets->setEnabled(false);
ui->menu_Create_Shortcuts->setEnabled(false);
firmware_label->setVisible(false); firmware_label->setVisible(false);
return; return;
} }
firmware_label->setVisible(true); firmware_label->setVisible(true);
ui->menu_Applets->setEnabled(true);
ui->menu_Create_Shortcuts->setEnabled(true);
const std::string display_version(firmware_data.display_version.data()); const std::string display_version(firmware_data.display_version.data());
const std::string display_title(firmware_data.display_title.data()); const std::string display_title(firmware_data.display_title.data());

View file

@ -387,7 +387,6 @@ private slots:
void OnInstallDecryptionKeys(); void OnInstallDecryptionKeys();
void OnAbout(); void OnAbout();
void OnEdenDependencies(); void OnEdenDependencies();
void OnDataDialog();
void OnToggleFilterBar(); void OnToggleFilterBar();
void OnToggleStatusBar(); void OnToggleStatusBar();
void OnGameListRefresh(); void OnGameListRefresh();

View file

@ -158,23 +158,13 @@
</property> </property>
<widget class="QMenu" name="menu_cabinet_applet"> <widget class="QMenu" name="menu_cabinet_applet">
<property name="title"> <property name="title">
<string>Am&amp;iibo</string> <string>&amp;Amiibo</string>
</property> </property>
<addaction name="action_Load_Cabinet_Nickname_Owner"/> <addaction name="action_Load_Cabinet_Nickname_Owner"/>
<addaction name="action_Load_Cabinet_Eraser"/> <addaction name="action_Load_Cabinet_Eraser"/>
<addaction name="action_Load_Cabinet_Restorer"/> <addaction name="action_Load_Cabinet_Restorer"/>
<addaction name="action_Load_Cabinet_Formatter"/> <addaction name="action_Load_Cabinet_Formatter"/>
</widget> </widget>
<widget class="QMenu" name="menu_Applets">
<property name="title">
<string>&amp;Applets</string>
</property>
<addaction name="action_Load_Home_Menu"/>
<addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
<addaction name="action_Open_Controller_Menu"/>
<addaction name="action_Open_Setup"/>
</widget>
<widget class="QMenu" name="menuTAS"> <widget class="QMenu" name="menuTAS">
<property name="title"> <property name="title">
<string>&amp;TAS</string> <string>&amp;TAS</string>
@ -194,7 +184,7 @@
</widget> </widget>
<widget class="QMenu" name="menuInstall_Firmware"> <widget class="QMenu" name="menuInstall_Firmware">
<property name="title"> <property name="title">
<string>Install &amp;Firmware</string> <string>Install Firmware</string>
</property> </property>
<addaction name="action_Firmware_From_Folder"/> <addaction name="action_Firmware_From_Folder"/>
<addaction name="action_Firmware_From_ZIP"/> <addaction name="action_Firmware_From_ZIP"/>
@ -202,10 +192,13 @@
<addaction name="action_Install_Keys"/> <addaction name="action_Install_Keys"/>
<addaction name="menuInstall_Firmware"/> <addaction name="menuInstall_Firmware"/>
<addaction name="action_Verify_installed_contents"/> <addaction name="action_Verify_installed_contents"/>
<addaction name="action_Data_Manager"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menu_cabinet_applet"/> <addaction name="menu_cabinet_applet"/>
<addaction name="menu_Applets"/> <addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
<addaction name="action_Open_Controller_Menu"/>
<addaction name="action_Load_Home_Menu"/>
<addaction name="action_Open_Setup"/>
<addaction name="menu_Create_Shortcuts"/> <addaction name="menu_Create_Shortcuts"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/> <addaction name="action_Capture_Screenshot"/>
@ -504,12 +497,12 @@
</action> </action>
<action name="action_Install_Keys"> <action name="action_Install_Keys">
<property name="text"> <property name="text">
<string>Install Decryption &amp;Keys</string> <string>Install Decryption Keys</string>
</property> </property>
</action> </action>
<action name="action_Load_Home_Menu"> <action name="action_Load_Home_Menu">
<property name="text"> <property name="text">
<string>Open &amp;Home Menu</string> <string>Open Home Menu</string>
</property> </property>
</action> </action>
<action name="action_Discord"> <action name="action_Discord">
@ -600,11 +593,6 @@
<string>&amp;Eden Dependencies</string> <string>&amp;Eden Dependencies</string>
</property> </property>
</action> </action>
<action name="action_Data_Manager">
<property name="text">
<string>&amp;Data Manager</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="yuzu.qrc"/> <include location="yuzu.qrc"/>

View file

@ -16,7 +16,7 @@
#include <QMetaType> #include <QMetaType>
#include <QTime> #include <QTime>
#include <QUrl> #include <QUrl>
#include <QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "network/announce_multiplayer_session.h" #include "network/announce_multiplayer_session.h"
#include "ui_chat_room.h" #include "ui_chat_room.h"

View file

@ -10,7 +10,7 @@
#include <QLocale> #include <QLocale>
#include <QMetaType> #include <QMetaType>
#include <QTime> #include <QTime>
#include <QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "network/announce_multiplayer_session.h" #include "network/announce_multiplayer_session.h"
#include "ui_client_room.h" #include "ui_client_room.h"

View file

@ -9,7 +9,7 @@
#include <QIntValidator> #include <QIntValidator>
#include <QRegularExpressionValidator> #include <QRegularExpressionValidator>
#include <QString> #include <QString>
#include <QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/internal_network/network_interface.h" #include "core/internal_network/network_interface.h"

View file

@ -12,7 +12,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QMetaType> #include <QMetaType>
#include <QTime> #include <QTime>
#include <QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"

View file

@ -6,7 +6,7 @@
#include <QInputDialog> #include <QInputDialog>
#include <QList> #include <QList>
#include <QtConcurrentRun> #include <QtConcurrent/QtConcurrentRun>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"