mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-07-20 03:35:46 +00:00
profile_manager: Implement firmware avatar selector (#205)
Adds an option to set a user's profile image from the avatars in the firmware. Background color can be changed with a color picker. Also modifies profile image saving to account for this, and as a result images are now saved as JPEG with 100% quality. Any PNG, JPEG, or BMP can now also be used in the image file picker instead of just JPEG. Using ryujinx's implementation and other parts of the yuzu codebase for reference. Credit: Torzu, lui Reviewed-on: http://vub63vv26q6v27xzv2dtcd25xumubshogm67yrpaz2rculqxs7jlfqad.onion/torzu-emu/torzu/pulls/56 Co-authored-by: lui <lui@vub63vv26q6v27xzv2dtcd25xumubshogm67yrpaz2rculqxs7jlfqad.onion> Co-committed-by: lui <lui@vub63vv26q6v27xzv2dtcd25xumubshogm67yrpaz2rculqxs7jlfqad.onion> Co-authored-by: lui <lui@vub63vv26q6v27xzv2dtcd25xumubshogm67yrpaz2rculqxs7jlfqad.onion> Co-authored-by: Maufeat <sahyno1996@gmail.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/205 Co-authored-by: JPikachu <jpikachu@noreply.localhost> Co-committed-by: JPikachu <jpikachu@noreply.localhost>
This commit is contained in:
parent
77b226a162
commit
b2e602325c
17 changed files with 498 additions and 40 deletions
|
@ -150,7 +150,7 @@ struct Values {
|
||||||
Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode",
|
Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode",
|
||||||
Category::LibraryApplet};
|
Category::LibraryApplet};
|
||||||
Setting<AppletMode> player_select_applet_mode{
|
Setting<AppletMode> player_select_applet_mode{
|
||||||
linkage, AppletMode::HLE, "player_select_applet_mode", Category::LibraryApplet};
|
linkage, AppletMode::LLE, "player_select_applet_mode", Category::LibraryApplet};
|
||||||
Setting<AppletMode> swkbd_applet_mode{linkage, AppletMode::LLE, "swkbd_applet_mode",
|
Setting<AppletMode> swkbd_applet_mode{linkage, AppletMode::LLE, "swkbd_applet_mode",
|
||||||
Category::LibraryApplet};
|
Category::LibraryApplet};
|
||||||
Setting<AppletMode> mii_edit_applet_mode{linkage, AppletMode::LLE, "mii_edit_applet_mode",
|
Setting<AppletMode> mii_edit_applet_mode{linkage, AppletMode::LLE, "mii_edit_applet_mode",
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -646,6 +649,7 @@ public:
|
||||||
{2, &IManagerForApplication::EnsureIdTokenCacheAsync, "EnsureIdTokenCacheAsync"},
|
{2, &IManagerForApplication::EnsureIdTokenCacheAsync, "EnsureIdTokenCacheAsync"},
|
||||||
{3, &IManagerForApplication::LoadIdTokenCache, "LoadIdTokenCache"},
|
{3, &IManagerForApplication::LoadIdTokenCache, "LoadIdTokenCache"},
|
||||||
{130, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCacheForApplication"},
|
{130, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCacheForApplication"},
|
||||||
|
{136, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCache"}, // 19.0.0+
|
||||||
{150, nullptr, "CreateAuthorizationRequest"},
|
{150, nullptr, "CreateAuthorizationRequest"},
|
||||||
{160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
|
{160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
|
||||||
{170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
|
{170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
|
||||||
|
@ -986,6 +990,34 @@ void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) {
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::Interface::DeleteUser(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
Common::UUID user_id = rp.PopRaw<Common::UUID>();
|
||||||
|
LOG_INFO(Service_ACC, "called, uuid={}", user_id.FormattedString());
|
||||||
|
if (!profile_manager->RemoveUser(user_id)) {
|
||||||
|
LOG_ERROR(Service_ACC, "Failed to delete user with uuid={}", user_id.RawString());
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(1U);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::SetUserPosition(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
|
||||||
|
u64 position = rp.Pop<u64>();
|
||||||
|
Common::UUID user_id = rp.PopRaw<Common::UUID>();
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_ACC, "called, position={} user_id={}", position, user_id.FormattedString());
|
||||||
|
|
||||||
|
profile_manager->SetUserPosition(position, user_id);
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
|
void Module::Interface::GetProfileEditor(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
Common::UUID user_id = rp.PopRaw<Common::UUID>();
|
Common::UUID user_id = rp.PopRaw<Common::UUID>();
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -36,6 +39,8 @@ public:
|
||||||
void InitializeApplicationInfoV2(HLERequestContext& ctx);
|
void InitializeApplicationInfoV2(HLERequestContext& ctx);
|
||||||
void BeginUserRegistration(HLERequestContext& ctx);
|
void BeginUserRegistration(HLERequestContext& ctx);
|
||||||
void CompleteUserRegistration(HLERequestContext& ctx);
|
void CompleteUserRegistration(HLERequestContext& ctx);
|
||||||
|
void DeleteUser(HLERequestContext& ctx);
|
||||||
|
void SetUserPosition(HLERequestContext& ctx);
|
||||||
void GetProfileEditor(HLERequestContext& ctx);
|
void GetProfileEditor(HLERequestContext& ctx);
|
||||||
void ListQualifiedUsers(HLERequestContext& ctx);
|
void ListQualifiedUsers(HLERequestContext& ctx);
|
||||||
void ListOpenContextStoredUsers(HLERequestContext& ctx);
|
void ListOpenContextStoredUsers(HLERequestContext& ctx);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -44,8 +47,8 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
|
||||||
{200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
|
{200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"},
|
||||||
{201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
|
{201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
|
||||||
{202, nullptr, "CancelUserRegistration"},
|
{202, nullptr, "CancelUserRegistration"},
|
||||||
{203, nullptr, "DeleteUser"},
|
{203, &ACC_SU::DeleteUser, "DeleteUser"},
|
||||||
{204, nullptr, "SetUserPosition"},
|
{204, &ACC_SU::SetUserPosition, "SetUserPosition"},
|
||||||
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
|
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
|
||||||
{206, nullptr, "CompleteUserRegistrationForcibly"},
|
{206, nullptr, "CompleteUserRegistrationForcibly"},
|
||||||
{210, nullptr, "CreateFloatingRegistrationRequest"},
|
{210, nullptr, "CreateFloatingRegistrationRequest"},
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -344,6 +347,7 @@ bool ProfileManager::RemoveUser(UUID uuid) {
|
||||||
[](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
|
[](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
|
||||||
|
|
||||||
is_save_needed = true;
|
is_save_needed = true;
|
||||||
|
WriteUserSaveFile();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -360,6 +364,7 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
|
||||||
profile.creation_time = profile_new.timestamp;
|
profile.creation_time = profile_new.timestamp;
|
||||||
|
|
||||||
is_save_needed = true;
|
is_save_needed = true;
|
||||||
|
WriteUserSaveFile();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -370,9 +375,12 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
|
||||||
if (index.has_value() && SetProfileBase(uuid, profile_new)) {
|
if (index.has_value() && SetProfileBase(uuid, profile_new)) {
|
||||||
profiles[*index].data = data_new;
|
profiles[*index].data = data_new;
|
||||||
is_save_needed = true;
|
is_save_needed = true;
|
||||||
|
WriteUserSaveFile();
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Service_ACC, "Failed to set profile base and data for user with UUID: {}",
|
||||||
|
uuid.RawString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,6 +445,14 @@ void ProfileManager::WriteUserSaveFile() {
|
||||||
const auto save_path(FS::GetEdenPath(FS::EdenPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH /
|
const auto save_path(FS::GetEdenPath(FS::EdenPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH /
|
||||||
"profiles.dat");
|
"profiles.dat");
|
||||||
|
|
||||||
|
if (FS::IsFile(save_path) && !FS::RemoveFile(save_path)) {
|
||||||
|
LOG_WARNING(Service_ACC, "Could not remove existing profiles.dat");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Service_ACC, "Writing profiles.dat to {}",
|
||||||
|
Common::FS::PathToUTF8String(save_path));
|
||||||
|
}
|
||||||
|
|
||||||
if (!FS::CreateParentDirs(save_path)) {
|
if (!FS::CreateParentDirs(save_path)) {
|
||||||
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
|
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
|
||||||
"nand/system/save/8000000000000010/su/avators to mitigate this "
|
"nand/system/save/8000000000000010/su/avators to mitigate this "
|
||||||
|
@ -455,4 +471,30 @@ void ProfileManager::WriteUserSaveFile() {
|
||||||
is_save_needed = false;
|
is_save_needed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProfileManager::SetUserPosition(u64 position, Common::UUID uuid) {
|
||||||
|
auto idxOpt = GetUserIndex(uuid);
|
||||||
|
if (!idxOpt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t oldIdx = *idxOpt;
|
||||||
|
if (position >= user_count || position == oldIdx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProfileInfo moving = profiles[oldIdx];
|
||||||
|
|
||||||
|
if (position < oldIdx) {
|
||||||
|
for (size_t i = oldIdx; i > position; --i)
|
||||||
|
profiles[i] = profiles[i - 1];
|
||||||
|
} else {
|
||||||
|
for (size_t i = oldIdx; i < position; ++i)
|
||||||
|
profiles[i] = profiles[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles[position] = std::move(moving);
|
||||||
|
|
||||||
|
is_save_needed = true;
|
||||||
|
WriteUserSaveFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}; // namespace Service::Account
|
}; // namespace Service::Account
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -89,6 +92,7 @@ public:
|
||||||
Common::UUID GetLastOpenedUser() const;
|
Common::UUID GetLastOpenedUser() const;
|
||||||
UserIDArray GetStoredOpenedUsers() const;
|
UserIDArray GetStoredOpenedUsers() const;
|
||||||
void StoreOpenedUsers();
|
void StoreOpenedUsers();
|
||||||
|
void SetUserPosition(u64 position, Common::UUID uuid);
|
||||||
|
|
||||||
bool CanSystemRegisterUser() const;
|
bool CanSystemRegisterUser() const;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -17,7 +20,7 @@ IDisplayController::IDisplayController(Core::System& system_, std::shared_ptr<Ap
|
||||||
{2, nullptr, "GetLastApplicationCaptureImage"},
|
{2, nullptr, "GetLastApplicationCaptureImage"},
|
||||||
{3, nullptr, "GetCallerAppletCaptureImage"},
|
{3, nullptr, "GetCallerAppletCaptureImage"},
|
||||||
{4, nullptr, "UpdateCallerAppletCaptureImage"},
|
{4, nullptr, "UpdateCallerAppletCaptureImage"},
|
||||||
{5, nullptr, "GetLastForegroundCaptureImageEx"},
|
{5, D<&IDisplayController::GetLastForegroundCaptureImageEx>, "GetLastForegroundCaptureImageEx"},
|
||||||
{6, nullptr, "GetLastApplicationCaptureImageEx"},
|
{6, nullptr, "GetLastApplicationCaptureImageEx"},
|
||||||
{7, D<&IDisplayController::GetCallerAppletCaptureImageEx>, "GetCallerAppletCaptureImageEx"},
|
{7, D<&IDisplayController::GetCallerAppletCaptureImageEx>, "GetCallerAppletCaptureImageEx"},
|
||||||
{8, D<&IDisplayController::TakeScreenShotOfOwnLayer>, "TakeScreenShotOfOwnLayer"},
|
{8, D<&IDisplayController::TakeScreenShotOfOwnLayer>, "TakeScreenShotOfOwnLayer"},
|
||||||
|
@ -48,6 +51,13 @@ IDisplayController::IDisplayController(Core::System& system_, std::shared_ptr<Ap
|
||||||
|
|
||||||
IDisplayController::~IDisplayController() = default;
|
IDisplayController::~IDisplayController() = default;
|
||||||
|
|
||||||
|
Result IDisplayController::GetLastForegroundCaptureImageEx(
|
||||||
|
Out<bool> out_was_written, OutBuffer<BufferAttr_HipcMapAlias> out_image_data) {
|
||||||
|
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||||
|
*out_was_written = true;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
Result IDisplayController::GetCallerAppletCaptureImageEx(
|
Result IDisplayController::GetCallerAppletCaptureImageEx(
|
||||||
Out<bool> out_was_written, OutBuffer<BufferAttr_HipcMapAlias> out_image_data) {
|
Out<bool> out_was_written, OutBuffer<BufferAttr_HipcMapAlias> out_image_data) {
|
||||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -16,6 +19,8 @@ public:
|
||||||
~IDisplayController() override;
|
~IDisplayController() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Result GetLastForegroundCaptureImageEx(Out<bool> out_was_written,
|
||||||
|
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
|
||||||
Result GetCallerAppletCaptureImageEx(Out<bool> out_was_written,
|
Result GetCallerAppletCaptureImageEx(Out<bool> out_was_written,
|
||||||
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
|
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
|
||||||
Result TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_layer_index);
|
Result TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_layer_index);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -107,7 +110,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
||||||
{210, nullptr, "DeleteUserSystemSaveData"},
|
{210, nullptr, "DeleteUserSystemSaveData"},
|
||||||
{211, nullptr, "DeleteSaveData"},
|
{211, nullptr, "DeleteSaveData"},
|
||||||
{220, nullptr, "UnregisterNetworkServiceAccount"},
|
{220, nullptr, "UnregisterNetworkServiceAccount"},
|
||||||
{221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
|
{221, D<&IApplicationManagerInterface::UnregisterNetworkServiceAccountWithUserSaveDataDeletion>, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
|
||||||
{300, nullptr, "GetApplicationShellEvent"},
|
{300, nullptr, "GetApplicationShellEvent"},
|
||||||
{301, nullptr, "PopApplicationShellEventInfo"},
|
{301, nullptr, "PopApplicationShellEventInfo"},
|
||||||
{302, nullptr, "LaunchLibraryApplet"},
|
{302, nullptr, "LaunchLibraryApplet"},
|
||||||
|
@ -312,6 +315,10 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
||||||
|
|
||||||
IApplicationManagerInterface::~IApplicationManagerInterface() = default;
|
IApplicationManagerInterface::~IApplicationManagerInterface() = default;
|
||||||
|
|
||||||
|
Result IApplicationManagerInterface::UnregisterNetworkServiceAccountWithUserSaveDataDeletion(Common::UUID user_id) {
|
||||||
|
LOG_DEBUG(Service_NS, "called, user_id={}", user_id.FormattedString());
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
Result IApplicationManagerInterface::GetApplicationControlData(
|
Result IApplicationManagerInterface::GetApplicationControlData(
|
||||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
|
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
|
||||||
ApplicationControlSource application_control_source, u64 application_id) {
|
ApplicationControlSource application_control_source, u64 application_id) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -16,6 +19,7 @@ public:
|
||||||
explicit IApplicationManagerInterface(Core::System& system_);
|
explicit IApplicationManagerInterface(Core::System& system_);
|
||||||
~IApplicationManagerInterface() override;
|
~IApplicationManagerInterface() override;
|
||||||
|
|
||||||
|
Result UnregisterNetworkServiceAccountWithUserSaveDataDeletion(Common::UUID user_id);
|
||||||
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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -34,6 +37,7 @@ IDynamicRightsInterface::IDynamicRightsInterface(Core::System& system_)
|
||||||
{23, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"},
|
{23, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"},
|
||||||
{24, nullptr, "NotifyLimitedApplicationLicenseUpgradableEventForDebug"},
|
{24, nullptr, "NotifyLimitedApplicationLicenseUpgradableEventForDebug"},
|
||||||
{25, nullptr, "RequestProceedDynamicRightsState"},
|
{25, nullptr, "RequestProceedDynamicRightsState"},
|
||||||
|
{26, D<&IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications>, "HasAccountRestrictedRightsInRunningApplications"}
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -59,4 +63,10 @@ Result IDynamicRightsInterface::VerifyActivatedRightsOwners(u64 rights_handle) {
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications(Out<u8> out_bool) {
|
||||||
|
LOG_WARNING(Service_NS, "(STUBBED) called");
|
||||||
|
*out_bool = 0;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::NS
|
} // namespace Service::NS
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -17,6 +20,7 @@ private:
|
||||||
Result NotifyApplicationRightsCheckStart();
|
Result NotifyApplicationRightsCheckStart();
|
||||||
Result GetRunningApplicationStatus(Out<u32> out_status, u64 rights_handle);
|
Result GetRunningApplicationStatus(Out<u32> out_status, u64 rights_handle);
|
||||||
Result VerifyActivatedRightsOwners(u64 rights_handle);
|
Result VerifyActivatedRightsOwners(u64 rights_handle);
|
||||||
|
Result HasAccountRestrictedRightsInRunningApplications(Out<u8> out_bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::NS
|
} // namespace Service::NS
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -21,7 +24,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
|
||||||
{1002, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"},
|
{1002, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"},
|
||||||
{1003, D<&IParentalControlService::ConfirmResumeApplicationPermission>, "ConfirmResumeApplicationPermission"},
|
{1003, D<&IParentalControlService::ConfirmResumeApplicationPermission>, "ConfirmResumeApplicationPermission"},
|
||||||
{1004, D<&IParentalControlService::ConfirmSnsPostPermission>, "ConfirmSnsPostPermission"},
|
{1004, D<&IParentalControlService::ConfirmSnsPostPermission>, "ConfirmSnsPostPermission"},
|
||||||
{1005, nullptr, "ConfirmSystemSettingsPermission"},
|
{1005, D<&IParentalControlService::ConfirmSystemSettingsPermission>, "ConfirmSystemSettingsPermission"},
|
||||||
{1006, D<&IParentalControlService::IsRestrictionTemporaryUnlocked>, "IsRestrictionTemporaryUnlocked"},
|
{1006, D<&IParentalControlService::IsRestrictionTemporaryUnlocked>, "IsRestrictionTemporaryUnlocked"},
|
||||||
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
|
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
|
||||||
{1008, nullptr, "EnterRestrictedSystemSettings"},
|
{1008, nullptr, "EnterRestrictedSystemSettings"},
|
||||||
|
@ -240,6 +243,11 @@ Result IParentalControlService::ConfirmSnsPostPermission() {
|
||||||
R_THROW(PCTL::ResultNoFreeCommunication);
|
R_THROW(PCTL::ResultNoFreeCommunication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result IParentalControlService::ConfirmSystemSettingsPermission() {
|
||||||
|
LOG_WARNING(Service_PCTL, "(STUBBED) called");
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
Result IParentalControlService::IsRestrictionTemporaryUnlocked(
|
Result IParentalControlService::IsRestrictionTemporaryUnlocked(
|
||||||
Out<bool> out_is_temporary_unlocked) {
|
Out<bool> out_is_temporary_unlocked) {
|
||||||
*out_is_temporary_unlocked = false;
|
*out_is_temporary_unlocked = false;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator
|
||||||
|
// Project// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -28,6 +31,7 @@ private:
|
||||||
Result ConfirmResumeApplicationPermission(InBuffer<BufferAttr_HipcPointer> restriction_bitset,
|
Result ConfirmResumeApplicationPermission(InBuffer<BufferAttr_HipcPointer> restriction_bitset,
|
||||||
u64 nacp_flag, u64 application_id);
|
u64 nacp_flag, u64 application_id);
|
||||||
Result ConfirmSnsPostPermission();
|
Result ConfirmSnsPostPermission();
|
||||||
|
Result ConfirmSystemSettingsPermission();
|
||||||
Result IsRestrictionTemporaryUnlocked(Out<bool> out_is_temporary_unlocked);
|
Result IsRestrictionTemporaryUnlocked(Out<bool> out_is_temporary_unlocked);
|
||||||
Result IsRestrictedSystemSettingsEntered(Out<bool> out_is_restricted_system_settings_entered);
|
Result IsRestrictedSystemSettingsEntered(Out<bool> out_is_restricted_system_settings_entered);
|
||||||
Result ConfirmStereoVisionPermission();
|
Result ConfirmStereoVisionPermission();
|
||||||
|
|
|
@ -4,26 +4,36 @@
|
||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "yuzu/configuration/configure_profile_manager.h"
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <QColorDialog>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QGraphicsItem>
|
#include <QGraphicsItem>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QListWidget>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
#include "common/swap.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/content_archive.h"
|
||||||
|
#include "core/file_sys/nca_metadata.h"
|
||||||
|
#include "core/file_sys/registered_cache.h"
|
||||||
|
#include "core/file_sys/romfs.h"
|
||||||
|
#include "core/file_sys/vfs/vfs.h"
|
||||||
#include "core/hle/service/acc/profile_manager.h"
|
#include "core/hle/service/acc/profile_manager.h"
|
||||||
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
#include "ui_configure_profile_manager.h"
|
#include "ui_configure_profile_manager.h"
|
||||||
#include "yuzu/util/limitable_input_dialog.h"
|
#include "yuzu/util/limitable_input_dialog.h"
|
||||||
#include <algorithm>
|
#include "yuzu/configuration/configure_profile_manager.h"
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
|
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
|
||||||
|
@ -117,8 +127,12 @@ ConfigureProfileManager::ConfigureProfileManager(Core::System& system_, QWidget*
|
||||||
connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser);
|
connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser);
|
||||||
connect(ui->pm_remove, &QPushButton::clicked, this,
|
connect(ui->pm_remove, &QPushButton::clicked, this,
|
||||||
&ConfigureProfileManager::ConfirmDeleteUser);
|
&ConfigureProfileManager::ConfirmDeleteUser);
|
||||||
connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage);
|
connect(ui->pm_set_image, &QPushButton::clicked, this,
|
||||||
|
&ConfigureProfileManager::SelectImageFile);
|
||||||
|
connect(ui->pm_select_avatar, &QPushButton::clicked, this,
|
||||||
|
&ConfigureProfileManager::SelectFirmwareAvatar);
|
||||||
|
|
||||||
|
avatar_dialog = new ConfigureProfileManagerAvatarDialog(this);
|
||||||
confirm_dialog = new ConfigureProfileManagerDeleteDialog(this);
|
confirm_dialog = new ConfigureProfileManagerDeleteDialog(this);
|
||||||
|
|
||||||
scene = new QGraphicsScene;
|
scene = new QGraphicsScene;
|
||||||
|
@ -198,6 +212,7 @@ void ConfigureProfileManager::SelectUser(const QModelIndex& index) {
|
||||||
ui->pm_remove->setEnabled(profile_manager.GetUserCount() >= 2);
|
ui->pm_remove->setEnabled(profile_manager.GetUserCount() >= 2);
|
||||||
ui->pm_rename->setEnabled(true);
|
ui->pm_rename->setEnabled(true);
|
||||||
ui->pm_set_image->setEnabled(true);
|
ui->pm_set_image->setEnabled(true);
|
||||||
|
ui->pm_select_avatar->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureProfileManager::AddUser() {
|
void ConfigureProfileManager::AddUser() {
|
||||||
|
@ -271,18 +286,11 @@ void ConfigureProfileManager::DeleteUser(const Common::UUID& uuid) {
|
||||||
ui->pm_rename->setEnabled(false);
|
ui->pm_rename->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureProfileManager::SetUserImage() {
|
void ConfigureProfileManager::SetUserImage(const QImage& image) {
|
||||||
const auto index = tree_view->currentIndex().row();
|
const auto index = tree_view->currentIndex().row();
|
||||||
const auto uuid = profile_manager.GetUser(index);
|
const auto uuid = profile_manager.GetUser(index);
|
||||||
ASSERT(uuid);
|
ASSERT(uuid);
|
||||||
|
|
||||||
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
|
|
||||||
tr("JPEG Images (*.jpg *.jpeg)"));
|
|
||||||
|
|
||||||
if (file.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto image_path = GetImagePath(*uuid);
|
const auto image_path = GetImagePath(*uuid);
|
||||||
if (QFile::exists(image_path) && !QFile::remove(image_path)) {
|
if (QFile::exists(image_path) && !QFile::remove(image_path)) {
|
||||||
QMessageBox::warning(
|
QMessageBox::warning(
|
||||||
|
@ -308,29 +316,230 @@ void ConfigureProfileManager::SetUserImage() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!QFile::copy(file, image_path)) {
|
if (!image.save(image_path, "JPEG", 100)) {
|
||||||
QMessageBox::warning(this, tr("Error copying user image"),
|
QMessageBox::warning(this, tr("Error saving user image"),
|
||||||
tr("Unable to copy image from %1 to %2").arg(file, image_path));
|
tr("Unable to save image to file"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile image must be 256x256
|
|
||||||
QImage image(image_path);
|
|
||||||
if (image.width() != 256 || image.height() != 256) {
|
|
||||||
image = image.scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
|
||||||
if (!image.save(image_path)) {
|
|
||||||
QMessageBox::warning(this, tr("Error resizing user image"),
|
|
||||||
tr("Unable to resize image"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto username = GetAccountUsername(profile_manager, *uuid);
|
const auto username = GetAccountUsername(profile_manager, *uuid);
|
||||||
item_model->setItem(index, 0,
|
item_model->setItem(index, 0,
|
||||||
new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
|
new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
|
||||||
UpdateCurrentUser();
|
UpdateCurrentUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigureProfileManager::SelectImageFile() {
|
||||||
|
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
|
||||||
|
tr("Image Formats (*.jpg *.jpeg *.png *.bmp)"));
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Profile image must be 256x256
|
||||||
|
QImage image(file);
|
||||||
|
if (image.width() != 256 || image.height() != 256) {
|
||||||
|
image = image.scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
SetUserImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureProfileManager::SelectFirmwareAvatar() {
|
||||||
|
if (!avatar_dialog->AreImagesLoaded()) {
|
||||||
|
if (!LoadAvatarData()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (avatar_dialog->exec() == QDialog::Accepted) {
|
||||||
|
SetUserImage(avatar_dialog->GetSelectedAvatar().toImage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigureProfileManager::LoadAvatarData() {
|
||||||
|
constexpr u64 AvatarImageDataId = 0x010000000000080AULL;
|
||||||
|
|
||||||
|
// Attempt to load avatar data archive from installed firmware
|
||||||
|
auto* bis_system = system.GetFileSystemController().GetSystemNANDContents();
|
||||||
|
if (!bis_system) {
|
||||||
|
QMessageBox::warning(this, tr("No firmware available"),
|
||||||
|
tr("Please install the firmware to use firmware avatars."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto nca = bis_system->GetEntry(AvatarImageDataId, FileSys::ContentRecordType::Data);
|
||||||
|
if (!nca) {
|
||||||
|
QMessageBox::warning(this, tr("Error loading archive"),
|
||||||
|
tr("Archive is not available. Please install/reinstall firmware."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto romfs = nca->GetRomFS();
|
||||||
|
if (!romfs) {
|
||||||
|
QMessageBox::warning(this, tr("Error loading archive"),
|
||||||
|
tr("Archive does not contain romfs. It is probably corrupt."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto extracted = FileSys::ExtractRomFS(romfs);
|
||||||
|
if (!extracted) {
|
||||||
|
QMessageBox::warning(this, tr("Error extracting archive"),
|
||||||
|
tr("Archive could not be extracted. It is probably corrupt."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto chara_dir = extracted->GetSubdirectory("chara");
|
||||||
|
if (!chara_dir) {
|
||||||
|
QMessageBox::warning(this, tr("Error finding image directory"),
|
||||||
|
tr("Failed to find image directory in the archive."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QPixmap> images;
|
||||||
|
for (const auto& item : chara_dir->GetFiles()) {
|
||||||
|
if (item->GetExtension() != "szs") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto image_data = DecompressYaz0(item);
|
||||||
|
if (image_data.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QImage image(reinterpret_cast<const uchar*>(image_data.data()), 256, 256,
|
||||||
|
QImage::Format_RGBA8888);
|
||||||
|
images.append(QPixmap::fromImage(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (images.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, tr("No images found"),
|
||||||
|
tr("No avatar images were found in the archive."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the image data into the dialog
|
||||||
|
avatar_dialog->LoadImages(images);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureProfileManagerAvatarDialog::ConfigureProfileManagerAvatarDialog(QWidget* parent)
|
||||||
|
: QDialog{parent}, avatar_list{new QListWidget(this)}, bg_color_button{new QPushButton(this)} {
|
||||||
|
auto* main_layout = new QVBoxLayout(this);
|
||||||
|
auto* button_layout = new QHBoxLayout(this);
|
||||||
|
auto* select_button = new QPushButton(tr("Select"), this);
|
||||||
|
auto* cancel_button = new QPushButton(tr("Cancel"), this);
|
||||||
|
auto* bg_color_label = new QLabel(tr("Background Color"), this);
|
||||||
|
|
||||||
|
SetBackgroundColor(Qt::white);
|
||||||
|
|
||||||
|
avatar_list->setViewMode(QListView::IconMode);
|
||||||
|
avatar_list->setIconSize(QSize(64, 64));
|
||||||
|
avatar_list->setSpacing(4);
|
||||||
|
avatar_list->setResizeMode(QListView::Adjust);
|
||||||
|
avatar_list->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
avatar_list->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
avatar_list->setDragDropMode(QAbstractItemView::NoDragDrop);
|
||||||
|
avatar_list->setDragEnabled(false);
|
||||||
|
avatar_list->setDropIndicatorShown(false);
|
||||||
|
avatar_list->setAcceptDrops(false);
|
||||||
|
|
||||||
|
button_layout->addWidget(bg_color_button);
|
||||||
|
button_layout->addWidget(bg_color_label);
|
||||||
|
button_layout->addStretch();
|
||||||
|
button_layout->addWidget(select_button);
|
||||||
|
button_layout->addWidget(cancel_button);
|
||||||
|
|
||||||
|
this->setLayout(main_layout);
|
||||||
|
this->setWindowTitle(tr("Select Firmware Avatar"));
|
||||||
|
main_layout->addWidget(avatar_list);
|
||||||
|
main_layout->addLayout(button_layout);
|
||||||
|
|
||||||
|
connect(bg_color_button, &QPushButton::clicked, this, [this]() {
|
||||||
|
const auto new_color = QColorDialog::getColor(avatar_bg_color);
|
||||||
|
if (new_color.isValid()) {
|
||||||
|
SetBackgroundColor(new_color);
|
||||||
|
RefreshAvatars();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(select_button, &QPushButton::clicked, this, [this]() { accept(); });
|
||||||
|
connect(cancel_button, &QPushButton::clicked, this, [this]() { reject(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureProfileManagerAvatarDialog::~ConfigureProfileManagerAvatarDialog() = default;
|
||||||
|
|
||||||
|
void ConfigureProfileManagerAvatarDialog::SetBackgroundColor(const QColor& color) {
|
||||||
|
avatar_bg_color = color;
|
||||||
|
|
||||||
|
bg_color_button->setStyleSheet(
|
||||||
|
QStringLiteral("background-color: %1; min-width: 60px;").arg(avatar_bg_color.name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap ConfigureProfileManagerAvatarDialog::CreateAvatar(const QPixmap& avatar) {
|
||||||
|
QPixmap output(avatar.size());
|
||||||
|
output.fill(avatar_bg_color);
|
||||||
|
|
||||||
|
// Scale the image and fill it black to become our shadow
|
||||||
|
QPixmap shadow_pixmap = avatar.transformed(QTransform::fromScale(1.04, 1.04));
|
||||||
|
QPainter shadow_painter(&shadow_pixmap);
|
||||||
|
shadow_painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||||
|
shadow_painter.fillRect(shadow_pixmap.rect(), Qt::black);
|
||||||
|
shadow_painter.end();
|
||||||
|
|
||||||
|
QPainter painter(&output);
|
||||||
|
painter.setOpacity(0.10);
|
||||||
|
painter.drawPixmap(0, 0, shadow_pixmap);
|
||||||
|
painter.setOpacity(1.0);
|
||||||
|
painter.drawPixmap(0, 0, avatar);
|
||||||
|
painter.end();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureProfileManagerAvatarDialog::RefreshAvatars() {
|
||||||
|
if (avatar_list->count() != avatar_image_store.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < avatar_image_store.size(); ++i) {
|
||||||
|
const auto icon =
|
||||||
|
QIcon(CreateAvatar(avatar_image_store[i])
|
||||||
|
.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||||
|
avatar_list->item(i)->setIcon(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureProfileManagerAvatarDialog::LoadImages(const QVector<QPixmap>& avatar_images) {
|
||||||
|
avatar_image_store = avatar_images;
|
||||||
|
avatar_list->clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < avatar_image_store.size(); ++i) {
|
||||||
|
avatar_list->addItem(new QListWidgetItem);
|
||||||
|
}
|
||||||
|
RefreshAvatars();
|
||||||
|
|
||||||
|
// Determine window size now that avatars are loaded into the grid
|
||||||
|
// There is probably a better way to handle this that I'm unaware of
|
||||||
|
const auto* style = avatar_list->style();
|
||||||
|
|
||||||
|
const int icon_size = avatar_list->iconSize().width();
|
||||||
|
const int icon_spacing = avatar_list->spacing() * 2;
|
||||||
|
const int icon_margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
|
||||||
|
const int icon_full_size = icon_size + icon_spacing + icon_margin;
|
||||||
|
|
||||||
|
const int horizontal_margin = style->pixelMetric(QStyle::PM_LayoutLeftMargin) +
|
||||||
|
style->pixelMetric(QStyle::PM_LayoutRightMargin) +
|
||||||
|
style->pixelMetric(QStyle::PM_ScrollBarExtent);
|
||||||
|
const int vertical_margin = style->pixelMetric(QStyle::PM_LayoutTopMargin) +
|
||||||
|
style->pixelMetric(QStyle::PM_LayoutBottomMargin);
|
||||||
|
|
||||||
|
// Set default list size so that it is 6 icons wide and 4.5 tall
|
||||||
|
const int columns = 6;
|
||||||
|
const double rows = 4.5;
|
||||||
|
const int total_width = icon_full_size * columns + horizontal_margin;
|
||||||
|
const int total_height = icon_full_size * rows + vertical_margin;
|
||||||
|
avatar_list->setMinimumSize(total_width, total_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigureProfileManagerAvatarDialog::AreImagesLoaded() const {
|
||||||
|
return !avatar_image_store.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap ConfigureProfileManagerAvatarDialog::GetSelectedAvatar() {
|
||||||
|
return CreateAvatar(avatar_image_store[avatar_list->currentRow()]);
|
||||||
|
}
|
||||||
|
|
||||||
ConfigureProfileManagerDeleteDialog::ConfigureProfileManagerDeleteDialog(QWidget* parent)
|
ConfigureProfileManagerDeleteDialog::ConfigureProfileManagerDeleteDialog(QWidget* parent)
|
||||||
: QDialog{parent} {
|
: QDialog{parent} {
|
||||||
auto dialog_vbox_layout = new QVBoxLayout(this);
|
auto dialog_vbox_layout = new QVBoxLayout(this);
|
||||||
|
@ -374,3 +583,75 @@ void ConfigureProfileManagerDeleteDialog::SetInfo(const QString& username, const
|
||||||
accept_callback();
|
accept_callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> ConfigureProfileManager::DecompressYaz0(const FileSys::VirtualFile& file) {
|
||||||
|
if (!file) {
|
||||||
|
throw std::invalid_argument("Null file pointer passed to DecompressYaz0");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t magic{};
|
||||||
|
file->ReadObject(&magic, 0);
|
||||||
|
if (magic != Common::MakeMagic('Y', 'a', 'z', '0')) {
|
||||||
|
return std::vector<uint8_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t decoded_length{};
|
||||||
|
file->ReadObject(&decoded_length, 4);
|
||||||
|
decoded_length = Common::swap32(decoded_length);
|
||||||
|
|
||||||
|
std::size_t input_size = file->GetSize() - 16;
|
||||||
|
std::vector<uint8_t> input(input_size);
|
||||||
|
file->ReadBytes(input.data(), input_size, 16);
|
||||||
|
|
||||||
|
uint32_t input_offset{};
|
||||||
|
uint32_t output_offset{};
|
||||||
|
std::vector<uint8_t> output(decoded_length);
|
||||||
|
|
||||||
|
uint16_t mask{};
|
||||||
|
uint8_t header{};
|
||||||
|
|
||||||
|
while (output_offset < decoded_length) {
|
||||||
|
if ((mask >>= 1) == 0) {
|
||||||
|
header = input[input_offset++];
|
||||||
|
mask = 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((header & mask) != 0) {
|
||||||
|
if (output_offset == output.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output[output_offset++] = input[input_offset++];
|
||||||
|
} else {
|
||||||
|
uint8_t byte1 = input[input_offset++];
|
||||||
|
uint8_t byte2 = input[input_offset++];
|
||||||
|
|
||||||
|
uint32_t dist = ((byte1 & 0xF) << 8) | byte2;
|
||||||
|
uint32_t position = output_offset - (dist + 1);
|
||||||
|
|
||||||
|
uint32_t length = byte1 >> 4;
|
||||||
|
if (length == 0) {
|
||||||
|
length = static_cast<uint32_t>(input[input_offset++]) + 0x12;
|
||||||
|
} else {
|
||||||
|
length += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t gap = output_offset - position;
|
||||||
|
uint32_t non_overlapping_length = length;
|
||||||
|
|
||||||
|
if (non_overlapping_length > gap) {
|
||||||
|
non_overlapping_length = gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(&output[output_offset], &output[position], non_overlapping_length);
|
||||||
|
output_offset += non_overlapping_length;
|
||||||
|
position += non_overlapping_length;
|
||||||
|
length -= non_overlapping_length;
|
||||||
|
|
||||||
|
while (length-- > 0) {
|
||||||
|
output[output_offset++] = output[position++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
@ -25,6 +28,7 @@ class QStandardItem;
|
||||||
class QStandardItemModel;
|
class QStandardItemModel;
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
|
class QListWidget;
|
||||||
|
|
||||||
namespace Service::Account {
|
namespace Service::Account {
|
||||||
class ProfileManager;
|
class ProfileManager;
|
||||||
|
@ -34,6 +38,26 @@ namespace Ui {
|
||||||
class ConfigureProfileManager;
|
class ConfigureProfileManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ConfigureProfileManagerAvatarDialog : public QDialog {
|
||||||
|
public:
|
||||||
|
explicit ConfigureProfileManagerAvatarDialog(QWidget* parent);
|
||||||
|
~ConfigureProfileManagerAvatarDialog();
|
||||||
|
|
||||||
|
void LoadImages(const QVector<QPixmap>& avatar_images);
|
||||||
|
bool AreImagesLoaded() const;
|
||||||
|
QPixmap GetSelectedAvatar();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetBackgroundColor(const QColor& color);
|
||||||
|
QPixmap CreateAvatar(const QPixmap& avatar);
|
||||||
|
void RefreshAvatars();
|
||||||
|
|
||||||
|
QVector<QPixmap> avatar_image_store;
|
||||||
|
QListWidget* avatar_list;
|
||||||
|
QColor avatar_bg_color;
|
||||||
|
QPushButton* bg_color_button;
|
||||||
|
};
|
||||||
|
|
||||||
class ConfigureProfileManagerDeleteDialog : public QDialog {
|
class ConfigureProfileManagerDeleteDialog : public QDialog {
|
||||||
public:
|
public:
|
||||||
explicit ConfigureProfileManagerDeleteDialog(QWidget* parent);
|
explicit ConfigureProfileManagerDeleteDialog(QWidget* parent);
|
||||||
|
@ -71,13 +95,18 @@ private:
|
||||||
void RenameUser();
|
void RenameUser();
|
||||||
void ConfirmDeleteUser();
|
void ConfirmDeleteUser();
|
||||||
void DeleteUser(const Common::UUID& uuid);
|
void DeleteUser(const Common::UUID& uuid);
|
||||||
void SetUserImage();
|
void SetUserImage(const QImage& image);
|
||||||
|
void SelectImageFile();
|
||||||
|
void SelectFirmwareAvatar();
|
||||||
|
bool LoadAvatarData();
|
||||||
|
std::vector<uint8_t> DecompressYaz0(const FileSys::VirtualFile& file);
|
||||||
|
|
||||||
QVBoxLayout* layout;
|
QVBoxLayout* layout;
|
||||||
QTreeView* tree_view;
|
QTreeView* tree_view;
|
||||||
QStandardItemModel* item_model;
|
QStandardItemModel* item_model;
|
||||||
QGraphicsScene* scene;
|
QGraphicsScene* scene;
|
||||||
|
|
||||||
|
ConfigureProfileManagerAvatarDialog* avatar_dialog;
|
||||||
ConfigureProfileManagerDeleteDialog* confirm_dialog;
|
ConfigureProfileManagerDeleteDialog* confirm_dialog;
|
||||||
|
|
||||||
std::vector<QList<QStandardItem*>> list_items;
|
std::vector<QList<QStandardItem*>> list_items;
|
||||||
|
|
|
@ -117,6 +117,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pm_select_avatar">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select Avatar</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
|
@ -176,6 +186,6 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources />
|
||||||
<connections/>
|
<connections />
|
||||||
</ui>
|
</ui>
|
Loading…
Add table
Add a link
Reference in a new issue