summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/acc/acc.cpp31
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp101
-rw-r--r--src/core/hle/service/acc/profile_manager.h15
-rw-r--r--src/core/hle/service/am/am.cpp8
-rw-r--r--src/core/settings.h2
-rw-r--r--src/yuzu/configuration/config.cpp30
-rw-r--r--src/yuzu/configuration/configure_system.cpp179
-rw-r--r--src/yuzu/configuration/configure_system.h18
-rw-r--r--src/yuzu/configuration/configure_system.ui10
-rw-r--r--src/yuzu/main.cpp27
-rw-r--r--src/yuzu_cmd/config.cpp20
11 files changed, 308 insertions, 133 deletions
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 0149ea8b3..cee309cb1 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
5#include <array> 6#include <array>
6#include "common/common_paths.h" 7#include "common/common_paths.h"
7#include "common/common_types.h" 8#include "common/common_types.h"
@@ -33,9 +34,9 @@ struct UserData {
33}; 34};
34static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); 35static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
35 36
36static std::string GetImagePath(const std::string& username) { 37static std::string GetImagePath(UUID uuid) {
37 return FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "users" + DIR_SEP + username + 38 return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
38 ".jpg"; 39 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
39} 40}
40 41
41class IProfile final : public ServiceFramework<IProfile> { 42class IProfile final : public ServiceFramework<IProfile> {
@@ -49,15 +50,6 @@ public:
49 {11, &IProfile::LoadImage, "LoadImage"}, 50 {11, &IProfile::LoadImage, "LoadImage"},
50 }; 51 };
51 RegisterHandlers(functions); 52 RegisterHandlers(functions);
52
53 ProfileBase profile_base{};
54 if (profile_manager.GetProfileBase(user_id, profile_base)) {
55 image = std::make_unique<FileUtil::IOFile>(
56 GetImagePath(Common::StringFromFixedZeroTerminatedBuffer(
57 reinterpret_cast<const char*>(profile_base.username.data()),
58 profile_base.username.size())),
59 "rb");
60 }
61 } 53 }
62 54
63private: 55private:
@@ -111,13 +103,15 @@ private:
111 IPC::ResponseBuilder rb{ctx, 3}; 103 IPC::ResponseBuilder rb{ctx, 3};
112 rb.Push(RESULT_SUCCESS); 104 rb.Push(RESULT_SUCCESS);
113 105
114 if (image == nullptr) { 106 const FileUtil::IOFile image(GetImagePath(user_id), "rb");
107
108 if (!image.IsOpen()) {
115 ctx.WriteBuffer(backup_jpeg); 109 ctx.WriteBuffer(backup_jpeg);
116 rb.Push<u32>(backup_jpeg_size); 110 rb.Push<u32>(backup_jpeg_size);
117 } else { 111 } else {
118 const auto size = std::min<u32>(image->GetSize(), MAX_JPEG_IMAGE_SIZE); 112 const auto size = std::min<u32>(image.GetSize(), MAX_JPEG_IMAGE_SIZE);
119 std::vector<u8> buffer(size); 113 std::vector<u8> buffer(size);
120 image->ReadBytes(buffer.data(), buffer.size()); 114 image.ReadBytes(buffer.data(), buffer.size());
121 115
122 ctx.WriteBuffer(buffer.data(), buffer.size()); 116 ctx.WriteBuffer(buffer.data(), buffer.size());
123 rb.Push<u32>(buffer.size()); 117 rb.Push<u32>(buffer.size());
@@ -130,15 +124,16 @@ private:
130 IPC::ResponseBuilder rb{ctx, 3}; 124 IPC::ResponseBuilder rb{ctx, 3};
131 rb.Push(RESULT_SUCCESS); 125 rb.Push(RESULT_SUCCESS);
132 126
133 if (image == nullptr) 127 const FileUtil::IOFile image(GetImagePath(user_id), "rb");
128
129 if (!image.IsOpen())
134 rb.Push<u32>(backup_jpeg_size); 130 rb.Push<u32>(backup_jpeg_size);
135 else 131 else
136 rb.Push<u32>(std::min<u32>(image->GetSize(), MAX_JPEG_IMAGE_SIZE)); 132 rb.Push<u32>(std::min<u32>(image.GetSize(), MAX_JPEG_IMAGE_SIZE));
137 } 133 }
138 134
139 const ProfileManager& profile_manager; 135 const ProfileManager& profile_manager;
140 UUID user_id; ///< The user id this profile refers to. 136 UUID user_id; ///< The user id this profile refers to.
141 std::unique_ptr<FileUtil::IOFile> image = nullptr;
142}; 137};
143 138
144class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { 139class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index b4b4b52b7..b0ea06b48 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -4,10 +4,27 @@
4 4
5#include <random> 5#include <random>
6#include <boost/optional.hpp> 6#include <boost/optional.hpp>
7#include "common/file_util.h"
7#include "core/hle/service/acc/profile_manager.h" 8#include "core/hle/service/acc/profile_manager.h"
8#include "core/settings.h" 9#include "core/settings.h"
9 10
10namespace Service::Account { 11namespace Service::Account {
12
13struct UserRaw {
14 UUID uuid;
15 UUID uuid2;
16 u64 timestamp;
17 ProfileUsername username;
18 INSERT_PADDING_BYTES(0x80);
19};
20static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
21
22struct ProfileDataRaw {
23 INSERT_PADDING_BYTES(0x10);
24 std::array<UserRaw, MAX_USERS> users;
25};
26static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
27
11// TODO(ogniK): Get actual error codes 28// TODO(ogniK): Get actual error codes
12constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1); 29constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1);
13constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2); 30constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2);
@@ -23,15 +40,21 @@ const UUID& UUID::Generate() {
23} 40}
24 41
25ProfileManager::ProfileManager() { 42ProfileManager::ProfileManager() {
26 for (std::size_t i = 0; i < Settings::values.users.size(); ++i) { 43 ParseUserSaveFile();
27 const auto& val = Settings::values.users[i]; 44
28 ASSERT(CreateNewUser(val.second, val.first).IsSuccess()); 45 if (user_count == 0)
29 } 46 CreateNewUser(UUID{}.Generate(), "yuzu");
47
48 auto current = Settings::values.current_user;
49 if (!GetAllUsers()[current])
50 current = 0;
30 51
31 OpenUser(Settings::values.users[Settings::values.current_user].second); 52 OpenUser(GetAllUsers()[current]);
32} 53}
33 54
34ProfileManager::~ProfileManager() = default; 55ProfileManager::~ProfileManager() {
56 WriteUserSaveFile();
57}
35 58
36/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the 59/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
37/// internal management of the users profiles 60/// internal management of the users profiles
@@ -241,4 +264,70 @@ bool ProfileManager::CanSystemRegisterUser() const {
241 // emulate qlaunch. Update this to dynamically change. 264 // emulate qlaunch. Update this to dynamically change.
242} 265}
243 266
267bool ProfileManager::RemoveUser(UUID uuid) {
268 auto index = GetUserIndex(uuid);
269 if (index == boost::none) {
270 return false;
271 }
272
273 profiles[*index] = ProfileInfo{};
274 std::stable_partition(profiles.begin(), profiles.end(),
275 [](const ProfileInfo& profile) { return profile.user_uuid; });
276 return true;
277}
278
279bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
280 auto index = GetUserIndex(uuid);
281 if (profile_new.user_uuid == UUID(INVALID_UUID) || index == boost::none) {
282 return false;
283 }
284
285 auto& profile = profiles[*index];
286 profile.user_uuid = profile_new.user_uuid;
287 profile.username = profile_new.username;
288 profile.creation_time = profile_new.timestamp;
289
290 return true;
291}
292
293void ProfileManager::ParseUserSaveFile() {
294 FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
295 "/system/save/8000000000000010/su/avators/profiles.dat",
296 "rb");
297
298 ProfileDataRaw data;
299 save.Seek(0, SEEK_SET);
300 if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw))
301 return;
302
303 for (std::size_t i = 0; i < MAX_USERS; ++i) {
304 const auto& user = data.users[i];
305
306 if (user.uuid != UUID(INVALID_UUID))
307 AddUser({user.uuid, user.username, user.timestamp, {}, false});
308 }
309
310 std::stable_partition(profiles.begin(), profiles.end(),
311 [](const ProfileInfo& profile) { return profile.user_uuid; });
312}
313
314void ProfileManager::WriteUserSaveFile() {
315 ProfileDataRaw raw{};
316
317 for (std::size_t i = 0; i < MAX_USERS; ++i) {
318 raw.users[i].username = profiles[i].username;
319 raw.users[i].uuid2 = profiles[i].user_uuid;
320 raw.users[i].uuid = profiles[i].user_uuid;
321 raw.users[i].timestamp = profiles[i].creation_time;
322 }
323
324 FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
325 "/system/save/8000000000000010/su/avators/profiles.dat",
326 "rb");
327
328 save.Resize(sizeof(ProfileDataRaw));
329 save.Seek(0, SEEK_SET);
330 save.WriteBytes(&raw, sizeof(ProfileDataRaw));
331}
332
244}; // namespace Service::Account 333}; // namespace Service::Account
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 9ce3eb47c..1e5c2460e 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -45,6 +45,15 @@ struct UUID {
45 std::string Format() const { 45 std::string Format() const {
46 return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]); 46 return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
47 } 47 }
48
49 std::string FormatSwitch() const {
50 std::array<u8, 16> s{};
51 std::memcpy(s.data(), uuid.data(), sizeof(u128));
52 return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
53 ":02x}{:02x}{:02x}{:02x}{:02x}",
54 s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
55 s[12], s[13], s[14], s[15]);
56 }
48}; 57};
49static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); 58static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
50 59
@@ -108,7 +117,13 @@ public:
108 117
109 bool CanSystemRegisterUser() const; 118 bool CanSystemRegisterUser() const;
110 119
120 bool RemoveUser(UUID uuid);
121 bool SetProfileBase(UUID uuid, const ProfileBase& profile);
122
111private: 123private:
124 void ParseUserSaveFile();
125 void WriteUserSaveFile();
126
112 std::array<ProfileInfo, MAX_USERS> profiles{}; 127 std::array<ProfileInfo, MAX_USERS> profiles{};
113 std::size_t user_count = 0; 128 std::size_t user_count = 0;
114 boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); 129 boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 2dc647ec8..9dfcec59b 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -4,11 +4,13 @@
4 4
5#include <array> 5#include <array>
6#include <cinttypes> 6#include <cinttypes>
7#include <cstring>
7#include <stack> 8#include <stack>
8#include "core/core.h" 9#include "core/core.h"
9#include "core/hle/ipc_helpers.h" 10#include "core/hle/ipc_helpers.h"
10#include "core/hle/kernel/event.h" 11#include "core/hle/kernel/event.h"
11#include "core/hle/kernel/process.h" 12#include "core/hle/kernel/process.h"
13#include "core/hle/service/acc/profile_manager.h"
12#include "core/hle/service/am/am.h" 14#include "core/hle/service/am/am.h"
13#include "core/hle/service/am/applet_ae.h" 15#include "core/hle/service/am/applet_ae.h"
14#include "core/hle/service/am/applet_oe.h" 16#include "core/hle/service/am/applet_oe.h"
@@ -734,8 +736,10 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
734 std::vector<u8> buffer(POP_LAUNCH_PARAMETER_BUFFER_SIZE); 736 std::vector<u8> buffer(POP_LAUNCH_PARAMETER_BUFFER_SIZE);
735 737
736 std::memcpy(buffer.data(), header_data.data(), header_data.size()); 738 std::memcpy(buffer.data(), header_data.data(), header_data.size());
737 const auto current_uuid = Settings::values.users[Settings::values.current_user].second.uuid; 739
738 std::memcpy(buffer.data() + header_data.size(), current_uuid.data(), sizeof(u128)); 740 Account::ProfileManager profile_manager{};
741 const auto uuid = profile_manager.GetAllUsers()[Settings::values.current_user].uuid;
742 std::memcpy(buffer.data() + header_data.size(), uuid.data(), sizeof(u128));
739 743
740 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 744 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
741 745
diff --git a/src/core/settings.h b/src/core/settings.h
index 0fa726d5d..b5aeff29b 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -8,7 +8,6 @@
8#include <atomic> 8#include <atomic>
9#include <string> 9#include <string>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/hle/service/acc/profile_manager.h"
12 11
13namespace Settings { 12namespace Settings {
14 13
@@ -116,7 +115,6 @@ struct Values {
116 bool use_docked_mode; 115 bool use_docked_mode;
117 bool enable_nfc; 116 bool enable_nfc;
118 int current_user; 117 int current_user;
119 std::vector<std::pair<std::string, Service::Account::UUID>> users;
120 int language_index; 118 int language_index;
121 119
122 // Controls 120 // Controls
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 36f0c4f4c..f7a9a8dd4 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -4,6 +4,7 @@
4 4
5#include <QSettings> 5#include <QSettings>
6#include "common/file_util.h" 6#include "common/file_util.h"
7#include "core/hle/service/acc/profile_manager.h"
7#include "input_common/main.h" 8#include "input_common/main.h"
8#include "yuzu/configuration/config.h" 9#include "yuzu/configuration/config.h"
9#include "yuzu/ui_settings.h" 10#include "yuzu/ui_settings.h"
@@ -124,23 +125,7 @@ void Config::ReadValues() {
124 Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); 125 Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
125 Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); 126 Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
126 127
127 Settings::values.users.clear(); 128 Settings::values.current_user = std::clamp(qt_config->value("current_user", 0).toInt(), 0, 7);
128 const auto size = qt_config->beginReadArray("users");
129 for (int i = 0; i < size; ++i) {
130 qt_config->setArrayIndex(i);
131 const Service::Account::UUID uuid(qt_config->value("uuid_low").toULongLong(),
132 qt_config->value("uuid_high").toULongLong());
133 Settings::values.users.emplace_back(qt_config->value("username").toString().toStdString(),
134 uuid);
135 }
136
137 qt_config->endArray();
138
139 if (Settings::values.users.empty())
140 Settings::values.users.emplace_back("yuzu", Service::Account::UUID{}.Generate());
141
142 Settings::values.current_user =
143 std::clamp(qt_config->value("current_user", 0).toInt(), 0, size);
144 129
145 Settings::values.language_index = qt_config->value("language_index", 1).toInt(); 130 Settings::values.language_index = qt_config->value("language_index", 1).toInt();
146 qt_config->endGroup(); 131 qt_config->endGroup();
@@ -280,17 +265,6 @@ void Config::SaveValues() {
280 qt_config->setValue("enable_nfc", Settings::values.enable_nfc); 265 qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
281 qt_config->setValue("current_user", Settings::values.current_user); 266 qt_config->setValue("current_user", Settings::values.current_user);
282 267
283 qt_config->beginWriteArray("users", Settings::values.users.size());
284 for (std::size_t i = 0; i < Settings::values.users.size(); ++i) {
285 qt_config->setArrayIndex(i);
286 const auto& user = Settings::values.users[i];
287 qt_config->setValue("uuid_low", user.second.uuid[0]);
288 qt_config->setValue("uuid_high", user.second.uuid[1]);
289 qt_config->setValue("username", QString::fromStdString(user.first));
290 }
291
292 qt_config->endArray();
293
294 qt_config->setValue("language_index", Settings::values.language_index); 268 qt_config->setValue("language_index", Settings::values.language_index);
295 qt_config->endGroup(); 269 qt_config->endGroup();
296 270
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 9a41c1f6c..af2acdd45 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -2,10 +2,15 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
6#include <QFileDialog>
5#include <QGraphicsItem> 7#include <QGraphicsItem>
6#include <QList> 8#include <QGraphicsScene>
9#include <QInputDialog>
7#include <QMessageBox> 10#include <QMessageBox>
8#include <qinputdialog.h> 11#include <QStandardItemModel>
12#include <QTreeView>
13#include <QVBoxLayout>
9#include "common/common_paths.h" 14#include "common/common_paths.h"
10#include "common/logging/backend.h" 15#include "common/logging/backend.h"
11#include "core/core.h" 16#include "core/core.h"
@@ -14,6 +19,11 @@
14#include "yuzu/configuration/configure_system.h" 19#include "yuzu/configuration/configure_system.h"
15#include "yuzu/main.h" 20#include "yuzu/main.h"
16 21
22static std::string GetImagePath(Service::Account::UUID uuid) {
23 return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
24 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
25}
26
17static const std::array<int, 12> days_in_month = {{ 27static const std::array<int, 12> days_in_month = {{
18 31, 28 31,
19 29, 29 29,
@@ -40,7 +50,9 @@ static constexpr std::array<u8, 107> backup_jpeg{
40 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, 50 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
41}; 51};
42 52
43ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { 53ConfigureSystem::ConfigureSystem(QWidget* parent)
54 : QWidget(parent), ui(new Ui::ConfigureSystem),
55 profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
44 ui->setupUi(this); 56 ui->setupUi(this);
45 connect(ui->combo_birthmonth, 57 connect(ui->combo_birthmonth,
46 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 58 static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
@@ -82,6 +94,7 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
82 connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser); 94 connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser);
83 connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser); 95 connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser);
84 connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser); 96 connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);
97 connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage);
85 98
86 scene = new QGraphicsScene; 99 scene = new QGraphicsScene;
87 ui->current_user_icon->setScene(scene); 100 ui->current_user_icon->setScene(scene);
@@ -99,49 +112,69 @@ void ConfigureSystem::setConfiguration() {
99 item_model->removeRows(0, item_model->rowCount()); 112 item_model->removeRows(0, item_model->rowCount());
100 list_items.clear(); 113 list_items.clear();
101 114
102 std::transform(Settings::values.users.begin(), Settings::values.users.end(), 115 ui->pm_add->setEnabled(profile_manager->GetUserCount() < 8);
103 std::back_inserter(list_items),
104 [](const std::pair<std::string, Service::Account::UUID>& user) {
105 const auto icon_url = QString::fromStdString(
106 FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "users" +
107 DIR_SEP + user.first + ".jpg");
108 QPixmap icon{icon_url};
109
110 if (!icon) {
111 icon.fill(QColor::fromRgb(0, 0, 0));
112 icon.loadFromData(backup_jpeg.data(), backup_jpeg.size());
113 }
114
115 return QList{new QStandardItem{
116 icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
117 QString::fromStdString(user.first + "\n" + user.second.Format())}};
118 });
119
120 for (const auto& item : list_items)
121 item_model->appendRow(item);
122 116
117 PopulateUserList();
123 UpdateCurrentUser(); 118 UpdateCurrentUser();
124} 119}
125 120
126void ConfigureSystem::UpdateCurrentUser() { 121static QPixmap GetIcon(Service::Account::UUID uuid) {
127 const auto& current_user = Settings::values.users[Settings::values.current_user]; 122 const auto icon_url = QString::fromStdString(GetImagePath(uuid));
128 const auto icon_url =
129 QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "users" +
130 DIR_SEP + current_user.first + ".jpg");
131 QPixmap icon{icon_url}; 123 QPixmap icon{icon_url};
132 124
133 if (!icon) { 125 if (!icon) {
134 icon.fill(QColor::fromRgb(0, 0, 0)); 126 icon.fill(Qt::black);
135 icon.loadFromData(backup_jpeg.data(), backup_jpeg.size()); 127 icon.loadFromData(backup_jpeg.data(), backup_jpeg.size());
136 } 128 }
137 129
130 return icon;
131}
132
133void ConfigureSystem::PopulateUserList() {
134 const auto& profiles = profile_manager->GetAllUsers();
135 std::transform(
136 profiles.begin(), profiles.end(), std::back_inserter(list_items),
137 [this](const Service::Account::UUID& user) {
138 Service::Account::ProfileBase profile;
139 if (!profile_manager->GetProfileBase(user, profile))
140 return QList<QStandardItem*>{};
141 const auto username = Common::StringFromFixedZeroTerminatedBuffer(
142 reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
143
144 return QList<QStandardItem*>{new QStandardItem{
145 GetIcon(user).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
146 QString::fromStdString(username + '\n' + user.FormatSwitch())}};
147 });
148
149 list_items.erase(
150 std::remove_if(list_items.begin(), list_items.end(),
151 [](const auto& list) { return list == QList<QStandardItem*>{}; }),
152 list_items.end());
153
154 for (const auto& item : list_items)
155 item_model->appendRow(item);
156}
157
158void ConfigureSystem::UpdateCurrentUser() {
159 const auto& current_user = profile_manager->GetAllUsers()[Settings::values.current_user];
160 const auto username = GetAccountUsername(current_user);
161
138 scene->clear(); 162 scene->clear();
139 scene->addPixmap(icon.scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); 163 scene->addPixmap(
140 ui->current_user_username->setText(QString::fromStdString(current_user.first)); 164 GetIcon(current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
165 ui->current_user_username->setText(QString::fromStdString(username));
141} 166}
142 167
143void ConfigureSystem::ReadSystemSettings() {} 168void ConfigureSystem::ReadSystemSettings() {}
144 169
170std::string ConfigureSystem::GetAccountUsername(Service::Account::UUID uuid) {
171 Service::Account::ProfileBase profile;
172 if (!profile_manager->GetProfileBase(uuid, profile))
173 return "";
174 return Common::StringFromFixedZeroTerminatedBuffer(
175 reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
176}
177
145void ConfigureSystem::applyConfiguration() { 178void ConfigureSystem::applyConfiguration() {
146 if (!enabled) 179 if (!enabled)
147 return; 180 return;
@@ -192,16 +225,16 @@ void ConfigureSystem::refreshConsoleID() {
192 225
193void ConfigureSystem::SelectUser(const QModelIndex& index) { 226void ConfigureSystem::SelectUser(const QModelIndex& index) {
194 Settings::values.current_user = 227 Settings::values.current_user =
195 std::clamp<std::size_t>(index.row(), 0, Settings::values.users.size() - 1); 228 std::clamp<std::size_t>(index.row(), 0, profile_manager->GetUserCount() - 1);
196 229
197 UpdateCurrentUser(); 230 UpdateCurrentUser();
198 231
199 if (Settings::values.users.size() >= 2) 232 ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
200 ui->pm_remove->setEnabled(true); 233 ui->pm_remove->setEnabled(false);
201 else
202 ui->pm_remove->setEnabled(false);
203 234
204 ui->pm_rename->setEnabled(true); 235 ui->pm_rename->setEnabled(true);
236
237 ui->pm_set_image->setEnabled(true);
205} 238}
206 239
207void ConfigureSystem::AddUser() { 240void ConfigureSystem::AddUser() {
@@ -212,33 +245,57 @@ void ConfigureSystem::AddUser() {
212 const auto username = 245 const auto username =
213 QInputDialog::getText(this, tr("Enter Username"), tr("Enter a username for the new user:"), 246 QInputDialog::getText(this, tr("Enter Username"), tr("Enter a username for the new user:"),
214 QLineEdit::Normal, QString(), &ok); 247 QLineEdit::Normal, QString(), &ok);
248 if (!ok)
249 return;
215 250
216 Settings::values.users.emplace_back(username.toStdString(), uuid); 251 profile_manager->CreateNewUser(uuid, username.toStdString());
217 252
218 setConfiguration(); 253 item_model->appendRow(new QStandardItem{
254 GetIcon(uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
255 QString::fromStdString(username.toStdString() + '\n' + uuid.FormatSwitch())});
219} 256}
220 257
221void ConfigureSystem::RenameUser() { 258void ConfigureSystem::RenameUser() {
222 const auto user = tree_view->currentIndex().row(); 259 const auto user = tree_view->currentIndex().row();
260 ASSERT(user < 8);
261
262 const auto uuid = profile_manager->GetAllUsers()[user];
263 const auto username = GetAccountUsername(uuid);
264
265 Service::Account::ProfileBase profile;
266 if (!profile_manager->GetProfileBase(uuid, profile))
267 return;
223 268
224 bool ok = false; 269 bool ok = false;
225 const auto new_username = QInputDialog::getText( 270 const auto new_username =
226 this, tr("Enter Username"), tr("Enter a new username:"), QLineEdit::Normal, 271 QInputDialog::getText(this, tr("Enter Username"), tr("Enter a new username:"),
227 QString::fromStdString(Settings::values.users[user].first), &ok); 272 QLineEdit::Normal, QString::fromStdString(username), &ok);
228 273
229 if (!ok) 274 if (!ok)
230 return; 275 return;
231 276
232 Settings::values.users[user].first = new_username.toStdString(); 277 const auto username_std = new_username.toStdString();
278 if (username_std.size() > profile.username.size())
279 std::copy_n(username_std.begin(), profile.username.size(), profile.username.begin());
280 else
281 std::copy(username_std.begin(), username_std.end(), profile.username.begin());
233 282
234 setConfiguration(); 283 profile_manager->SetProfileBase(uuid, profile);
284
285 list_items[user][0] = new QStandardItem{
286 GetIcon(uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
287 QString::fromStdString(username_std + '\n' + uuid.FormatSwitch())};
235} 288}
236 289
237void ConfigureSystem::DeleteUser() { 290void ConfigureSystem::DeleteUser() {
238 const auto user = Settings::values.users.begin() + tree_view->currentIndex().row(); 291 const auto index = tree_view->currentIndex().row();
292 ASSERT(index < 8);
293 const auto uuid = profile_manager->GetAllUsers()[index];
294 const auto username = GetAccountUsername(uuid);
295
239 const auto confirm = QMessageBox::question( 296 const auto confirm = QMessageBox::question(
240 this, tr("Confirm Delete"), 297 this, tr("Confirm Delete"),
241 tr("You are about to delete user with name %1. Are you sure?").arg(user->first.c_str())); 298 tr("You are about to delete user with name %1. Are you sure?").arg(username.c_str()));
242 299
243 if (confirm == QMessageBox::No) 300 if (confirm == QMessageBox::No)
244 return; 301 return;
@@ -246,10 +303,38 @@ void ConfigureSystem::DeleteUser() {
246 if (Settings::values.current_user == tree_view->currentIndex().row()) 303 if (Settings::values.current_user == tree_view->currentIndex().row())
247 Settings::values.current_user = 0; 304 Settings::values.current_user = 0;
248 305
249 Settings::values.users.erase(user); 306 if (!profile_manager->RemoveUser(uuid))
307 return;
250 308
251 setConfiguration(); 309 item_model->removeRows(tree_view->currentIndex().row(), 1);
252 310
253 ui->pm_remove->setEnabled(false); 311 ui->pm_remove->setEnabled(false);
254 ui->pm_rename->setEnabled(false); 312 ui->pm_rename->setEnabled(false);
255} 313}
314
315void ConfigureSystem::SetUserImage() {
316 const auto index = tree_view->currentIndex().row();
317 ASSERT(index < 8);
318 const auto uuid = profile_manager->GetAllUsers()[index];
319 const auto username = GetAccountUsername(uuid);
320
321 const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
322 "JPEG Images (*.jpg *.jpeg)");
323
324 if (file.isEmpty())
325 return;
326
327 FileUtil::Delete(GetImagePath(uuid));
328
329 const auto raw_path =
330 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
331 if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
332 FileUtil::Delete(raw_path);
333
334 FileUtil::CreateFullPath(GetImagePath(uuid));
335 FileUtil::Copy(file.toStdString(), GetImagePath(uuid));
336
337 list_items[index][0] = new QStandardItem{
338 GetIcon(uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
339 QString::fromStdString(username + '\n' + uuid.FormatSwitch())};
340}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index aa20a3c30..868bb8bdf 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -5,12 +5,16 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <QGraphicsScene> 8
9#include <QList> 9#include <QList>
10#include <QStandardItemModel>
11#include <QTreeView>
12#include <QVBoxLayout>
13#include <QWidget> 10#include <QWidget>
11#include "core/hle/service/acc/profile_manager.h"
12
13class QVBoxLayout;
14class QTreeView;
15class QStandardItemModel;
16class QGraphicsScene;
17class QStandardItem;
14 18
15namespace Ui { 19namespace Ui {
16class ConfigureSystem; 20class ConfigureSystem;
@@ -26,6 +30,7 @@ public:
26 void applyConfiguration(); 30 void applyConfiguration();
27 void setConfiguration(); 31 void setConfiguration();
28 32
33 void PopulateUserList();
29 void UpdateCurrentUser(); 34 void UpdateCurrentUser();
30 35
31public slots: 36public slots:
@@ -36,9 +41,11 @@ public slots:
36 void AddUser(); 41 void AddUser();
37 void RenameUser(); 42 void RenameUser();
38 void DeleteUser(); 43 void DeleteUser();
44 void SetUserImage();
39 45
40private: 46private:
41 void ReadSystemSettings(); 47 void ReadSystemSettings();
48 std::string GetAccountUsername(Service::Account::UUID uuid);
42 49
43 QVBoxLayout* layout; 50 QVBoxLayout* layout;
44 QTreeView* tree_view; 51 QTreeView* tree_view;
@@ -50,8 +57,9 @@ private:
50 std::unique_ptr<Ui::ConfigureSystem> ui; 57 std::unique_ptr<Ui::ConfigureSystem> ui;
51 bool enabled; 58 bool enabled;
52 59
53 std::u16string username;
54 int birthmonth, birthday; 60 int birthmonth, birthday;
55 int language_index; 61 int language_index;
56 int sound_index; 62 int sound_index;
63
64 std::unique_ptr<Service::Account::ProfileManager> profile_manager;
57}; 65};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 2a6dcdb24..020b32a37 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -334,6 +334,16 @@
334 <item row="2" column="0"> 334 <item row="2" column="0">
335 <layout class="QHBoxLayout" name="horizontalLayout_3"> 335 <layout class="QHBoxLayout" name="horizontalLayout_3">
336 <item> 336 <item>
337 <widget class="QPushButton" name="pm_set_image">
338 <property name="enabled">
339 <bool>false</bool>
340 </property>
341 <property name="text">
342 <string>Set Image</string>
343 </property>
344 </widget>
345 </item>
346 <item>
337 <spacer name="horizontalSpacer"> 347 <spacer name="horizontalSpacer">
338 <property name="orientation"> 348 <property name="orientation">
339 <enum>Qt::Horizontal</enum> 349 <enum>Qt::Horizontal</enum>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1de3b817f..9a3535e77 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,6 +10,7 @@
10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
11#include "core/file_sys/vfs.h" 11#include "core/file_sys/vfs.h"
12#include "core/file_sys/vfs_real.h" 12#include "core/file_sys/vfs_real.h"
13#include "core/hle/service/acc/profile_manager.h"
13 14
14// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows 15// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
15// defines. 16// defines.
@@ -758,10 +759,22 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
758 const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); 759 const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
759 ASSERT(program_id != 0); 760 ASSERT(program_id != 0);
760 761
761 QStringList list{}; 762 Service::Account::ProfileManager manager{};
762 std::transform(Settings::values.users.begin(), Settings::values.users.end(), 763 const auto user_ids = manager.GetAllUsers();
763 std::back_inserter(list), 764 QStringList list;
764 [](const auto& user) { return QString::fromStdString(user.first); }); 765 std::transform(
766 user_ids.begin(), user_ids.end(), std::back_inserter(list),
767 [&manager](const auto& user_id) -> QString {
768 if (user_id == Service::Account::UUID{})
769 return "";
770 Service::Account::ProfileBase base;
771 if (!manager.GetProfileBase(user_id, base))
772 return "";
773
774 return QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
775 reinterpret_cast<const char*>(base.username.data()), base.username.size()));
776 });
777 list.removeAll("");
765 778
766 bool ok = false; 779 bool ok = false;
767 const auto index_string = 780 const auto index_string =
@@ -772,12 +785,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
772 return; 785 return;
773 786
774 const auto index = list.indexOf(index_string); 787 const auto index = list.indexOf(index_string);
775 ASSERT(index != -1); 788 ASSERT(index != -1 && index < 8);
776 789
777 const auto user_id = Settings::values.users[index].second.uuid; 790 const auto user_id = manager.GetAllUsers()[index];
778 path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser, 791 path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
779 FileSys::SaveDataType::SaveData, 792 FileSys::SaveDataType::SaveData,
780 program_id, user_id, 0); 793 program_id, user_id.uuid, 0);
781 794
782 if (!FileUtil::Exists(path)) { 795 if (!FileUtil::Exists(path)) {
783 FileUtil::CreateFullPath(path); 796 FileUtil::CreateFullPath(path);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 613894449..f6083dcb3 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -128,24 +128,8 @@ void Config::ReadValues() {
128 Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true); 128 Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true);
129 const auto size = sdl2_config->GetInteger("System", "users_size", 0); 129 const auto size = sdl2_config->GetInteger("System", "users_size", 0);
130 130
131 Settings::values.users.clear(); 131 Settings::values.current_user =
132 for (std::size_t i = 0; i < size; ++i) { 132 std::clamp<int>(sdl2_config->GetInteger("System", "current_user", 0), 0, 7);
133 const auto uuid_low = std::stoull(
134 sdl2_config->Get("System", fmt::format("users_{}_uuid_low", i), "0"), nullptr, 0);
135 const auto uuid_high = std::stoull(
136 sdl2_config->Get("System", fmt::format("users_{}_uuid_high", i), "0"), nullptr, 0);
137 Settings::values.users.emplace_back(
138 sdl2_config->Get("System", fmt::format("users_{}_username", i), ""),
139 Service::Account::UUID{uuid_low, uuid_high});
140 }
141
142 if (Settings::values.users.empty()) {
143 Settings::values.users.emplace_back("yuzu", Service::Account::UUID{1, 0});
144 LOG_WARNING(
145 Config,
146 "You are using the default UUID of {1, 0}! This might cause issues down the road! "
147 "Please consider randomizing a UUID and adding it to the sdl2_config.ini file.");
148 }
149 133
150 // Miscellaneous 134 // Miscellaneous
151 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); 135 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");