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;