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:
JPikachu 2025-06-26 18:39:28 +00:00 committed by crueter
parent 77b226a162
commit b2e602325c
17 changed files with 498 additions and 40 deletions

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-License-Identifier: GPL-2.0-or-later
@ -646,6 +649,7 @@ public:
{2, &IManagerForApplication::EnsureIdTokenCacheAsync, "EnsureIdTokenCacheAsync"},
{3, &IManagerForApplication::LoadIdTokenCache, "LoadIdTokenCache"},
{130, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCacheForApplication"},
{136, &IManagerForApplication::GetNintendoAccountUserResourceCacheForApplication, "GetNintendoAccountUserResourceCache"}, // 19.0.0+
{150, nullptr, "CreateAuthorizationRequest"},
{160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
{170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
@ -986,6 +990,34 @@ void Module::Interface::CompleteUserRegistration(HLERequestContext& ctx) {
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) {
IPC::RequestParser rp{ctx};
Common::UUID user_id = rp.PopRaw<Common::UUID>();

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-License-Identifier: GPL-2.0-or-later
@ -36,6 +39,8 @@ public:
void InitializeApplicationInfoV2(HLERequestContext& ctx);
void BeginUserRegistration(HLERequestContext& ctx);
void CompleteUserRegistration(HLERequestContext& ctx);
void DeleteUser(HLERequestContext& ctx);
void SetUserPosition(HLERequestContext& ctx);
void GetProfileEditor(HLERequestContext& ctx);
void ListQualifiedUsers(HLERequestContext& ctx);
void ListOpenContextStoredUsers(HLERequestContext& ctx);

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-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"},
{201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"},
{202, nullptr, "CancelUserRegistration"},
{203, nullptr, "DeleteUser"},
{204, nullptr, "SetUserPosition"},
{203, &ACC_SU::DeleteUser, "DeleteUser"},
{204, &ACC_SU::SetUserPosition, "SetUserPosition"},
{205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
{206, nullptr, "CompleteUserRegistrationForcibly"},
{210, nullptr, "CreateFloatingRegistrationRequest"},

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-License-Identifier: GPL-2.0-or-later
@ -344,6 +347,7 @@ bool ProfileManager::RemoveUser(UUID uuid) {
[](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
is_save_needed = true;
WriteUserSaveFile();
return true;
}
@ -360,6 +364,7 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
profile.creation_time = profile_new.timestamp;
is_save_needed = true;
WriteUserSaveFile();
return true;
}
@ -370,9 +375,12 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
if (index.has_value() && SetProfileBase(uuid, profile_new)) {
profiles[*index].data = data_new;
is_save_needed = true;
WriteUserSaveFile();
return true;
} else {
LOG_ERROR(Service_ACC, "Failed to set profile base and data for user with UUID: {}",
uuid.RawString());
}
return false;
}
@ -437,6 +445,14 @@ void ProfileManager::WriteUserSaveFile() {
const auto save_path(FS::GetEdenPath(FS::EdenPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH /
"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)) {
LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
"nand/system/save/8000000000000010/su/avators to mitigate this "
@ -455,4 +471,30 @@ void ProfileManager::WriteUserSaveFile() {
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

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-License-Identifier: GPL-2.0-or-later
@ -89,6 +92,7 @@ public:
Common::UUID GetLastOpenedUser() const;
UserIDArray GetStoredOpenedUsers() const;
void StoreOpenedUsers();
void SetUserPosition(u64 position, Common::UUID uuid);
bool CanSystemRegisterUser() const;

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-License-Identifier: GPL-2.0-or-later
@ -17,7 +20,7 @@ IDisplayController::IDisplayController(Core::System& system_, std::shared_ptr<Ap
{2, nullptr, "GetLastApplicationCaptureImage"},
{3, nullptr, "GetCallerAppletCaptureImage"},
{4, nullptr, "UpdateCallerAppletCaptureImage"},
{5, nullptr, "GetLastForegroundCaptureImageEx"},
{5, D<&IDisplayController::GetLastForegroundCaptureImageEx>, "GetLastForegroundCaptureImageEx"},
{6, nullptr, "GetLastApplicationCaptureImageEx"},
{7, D<&IDisplayController::GetCallerAppletCaptureImageEx>, "GetCallerAppletCaptureImageEx"},
{8, D<&IDisplayController::TakeScreenShotOfOwnLayer>, "TakeScreenShotOfOwnLayer"},
@ -48,6 +51,13 @@ IDisplayController::IDisplayController(Core::System& system_, std::shared_ptr<Ap
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(
Out<bool> out_was_written, OutBuffer<BufferAttr_HipcMapAlias> out_image_data) {
LOG_WARNING(Service_AM, "(STUBBED) called");

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-License-Identifier: GPL-2.0-or-later
@ -16,6 +19,8 @@ public:
~IDisplayController() override;
private:
Result GetLastForegroundCaptureImageEx(Out<bool> out_was_written,
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
Result GetCallerAppletCaptureImageEx(Out<bool> out_was_written,
OutBuffer<BufferAttr_HipcMapAlias> out_image_data);
Result TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_layer_index);

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-License-Identifier: GPL-2.0-or-later
@ -107,7 +110,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{210, nullptr, "DeleteUserSystemSaveData"},
{211, nullptr, "DeleteSaveData"},
{220, nullptr, "UnregisterNetworkServiceAccount"},
{221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
{221, D<&IApplicationManagerInterface::UnregisterNetworkServiceAccountWithUserSaveDataDeletion>, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
{300, nullptr, "GetApplicationShellEvent"},
{301, nullptr, "PopApplicationShellEventInfo"},
{302, nullptr, "LaunchLibraryApplet"},
@ -312,6 +315,10 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
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(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
ApplicationControlSource application_control_source, u64 application_id) {

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-License-Identifier: GPL-2.0-or-later
@ -16,6 +19,7 @@ public:
explicit IApplicationManagerInterface(Core::System& system_);
~IApplicationManagerInterface() override;
Result UnregisterNetworkServiceAccountWithUserSaveDataDeletion(Common::UUID user_id);
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_actual_size,
ApplicationControlSource application_control_source,

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-License-Identifier: GPL-2.0-or-later
@ -34,6 +37,7 @@ IDynamicRightsInterface::IDynamicRightsInterface(Core::System& system_)
{23, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"},
{24, nullptr, "NotifyLimitedApplicationLicenseUpgradableEventForDebug"},
{25, nullptr, "RequestProceedDynamicRightsState"},
{26, D<&IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications>, "HasAccountRestrictedRightsInRunningApplications"}
};
// clang-format on
@ -59,4 +63,10 @@ Result IDynamicRightsInterface::VerifyActivatedRightsOwners(u64 rights_handle) {
R_SUCCEED();
}
Result IDynamicRightsInterface::HasAccountRestrictedRightsInRunningApplications(Out<u8> out_bool) {
LOG_WARNING(Service_NS, "(STUBBED) called");
*out_bool = 0;
R_SUCCEED();
}
} // 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-License-Identifier: GPL-2.0-or-later
@ -17,6 +20,7 @@ private:
Result NotifyApplicationRightsCheckStart();
Result GetRunningApplicationStatus(Out<u32> out_status, u64 rights_handle);
Result VerifyActivatedRightsOwners(u64 rights_handle);
Result HasAccountRestrictedRightsInRunningApplications(Out<u8> out_bool);
};
} // 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-License-Identifier: GPL-2.0-or-later
@ -21,7 +24,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
{1002, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"},
{1003, D<&IParentalControlService::ConfirmResumeApplicationPermission>, "ConfirmResumeApplicationPermission"},
{1004, D<&IParentalControlService::ConfirmSnsPostPermission>, "ConfirmSnsPostPermission"},
{1005, nullptr, "ConfirmSystemSettingsPermission"},
{1005, D<&IParentalControlService::ConfirmSystemSettingsPermission>, "ConfirmSystemSettingsPermission"},
{1006, D<&IParentalControlService::IsRestrictionTemporaryUnlocked>, "IsRestrictionTemporaryUnlocked"},
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
{1008, nullptr, "EnterRestrictedSystemSettings"},
@ -240,6 +243,11 @@ Result IParentalControlService::ConfirmSnsPostPermission() {
R_THROW(PCTL::ResultNoFreeCommunication);
}
Result IParentalControlService::ConfirmSystemSettingsPermission() {
LOG_WARNING(Service_PCTL, "(STUBBED) called");
R_SUCCEED();
}
Result IParentalControlService::IsRestrictionTemporaryUnlocked(
Out<bool> out_is_temporary_unlocked) {
*out_is_temporary_unlocked = false;

View file

@ -1,5 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// 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
@ -28,6 +31,7 @@ private:
Result ConfirmResumeApplicationPermission(InBuffer<BufferAttr_HipcPointer> restriction_bitset,
u64 nacp_flag, u64 application_id);
Result ConfirmSnsPostPermission();
Result ConfirmSystemSettingsPermission();
Result IsRestrictionTemporaryUnlocked(Out<bool> out_is_temporary_unlocked);
Result IsRestrictedSystemSettingsEntered(Out<bool> out_is_restricted_system_settings_entered);
Result ConfirmStereoVisionPermission();