summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/acc/profile_manager.cpp
blob: 0071ca61318487206c04c15e7dd2e6f75a1bf229 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#include <random>
#include <boost/optional.hpp>
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"

namespace Service::Account {
// TODO(ogniK): Get actual error codes
constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1);
constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2);
constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);

const UUID& UUID::Generate() {
    std::random_device device;
    std::mt19937 gen(device());
    std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
    uuid[0] = distribution(gen);
    uuid[1] = distribution(gen);
    return *this;
}

ProfileManager::ProfileManager() {
    // TODO(ogniK): Create the default user we have for now until loading/saving users is added
    auto user_uuid = UUID{1, 0};
    CreateNewUser(user_uuid, Settings::values.username);
    OpenUser(user_uuid);
}

ProfileManager::~ProfileManager() = default;

/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles
boost::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
    if (user_count >= MAX_USERS) {
        return boost::none;
    }
    profiles[user_count] = user;
    return user_count++;
}

/// Deletes a specific profile based on it's profile index
bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
    if (index >= MAX_USERS || index >= user_count) {
        return false;
    }
    if (index < user_count - 1) {
        std::rotate(profiles.begin() + index, profiles.begin() + index + 1, profiles.end());
    }
    profiles.back() = {};
    user_count--;
    return true;
}

/// Helper function to register a user to the system
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
    if (AddToProfiles(user) == boost::none) {
        return ERROR_TOO_MANY_USERS;
    }
    return RESULT_SUCCESS;
}

/// Create a new user on the system. If the uuid of the user already exists, the user is not
/// created.
ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username) {
    if (user_count == MAX_USERS) {
        return ERROR_TOO_MANY_USERS;
    }
    if (!uuid) {
        return ERROR_ARGUMENT_IS_NULL;
    }
    if (username[0] == 0x0) {
        return ERROR_ARGUMENT_IS_NULL;
    }
    if (std::any_of(profiles.begin(), profiles.end(),
                    [&uuid](const ProfileInfo& profile) { return uuid == profile.user_uuid; })) {
        return ERROR_USER_ALREADY_EXISTS;
    }
    ProfileInfo profile;
    profile.user_uuid = uuid;
    profile.username = username;
    profile.data = {};
    profile.creation_time = 0x0;
    profile.is_open = false;
    return AddUser(profile);
}

/// Creates a new user on the system. This function allows a much simpler method of registration
/// specifically by allowing an std::string for the username. This is required specifically since
/// we're loading a string straight from the config
ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) {
    ProfileUsername username_output;
    if (username.size() > username_output.size()) {
        std::copy_n(username.begin(), username_output.size(), username_output.begin());
    } else {
        std::copy(username.begin(), username.end(), username_output.begin());
    }
    return CreateNewUser(uuid, username_output);
}

/// Returns a users profile index based on their user id.
boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
    if (!uuid) {
        return boost::none;
    }
    auto iter = std::find_if(profiles.begin(), profiles.end(),
                             [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
    if (iter == profiles.end()) {
        return boost::none;
    }
    return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
}

/// Returns a users profile index based on their profile
boost::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
    return GetUserIndex(user.user_uuid);
}

/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
                                    ProfileBase& profile) const {
    if (index == boost::none || index >= MAX_USERS) {
        return false;
    }
    const auto& prof_info = profiles[index.get()];
    profile.user_uuid = prof_info.user_uuid;
    profile.username = prof_info.username;
    profile.timestamp = prof_info.creation_time;
    return true;
}

/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
    auto idx = GetUserIndex(uuid);
    return GetProfileBase(idx, profile);
}

/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const {
    return GetProfileBase(user.user_uuid, profile);
}

/// Returns the current user count on the system. We keep a variable which tracks the count so we
/// don't have to loop the internal profile array every call.

std::size_t ProfileManager::GetUserCount() const {
    return user_count;
}

/// Lists the current "opened" users on the system. Users are typically not open until they sign
/// into something or pick a profile. As of right now users should all be open until qlaunch is
/// booting

std::size_t ProfileManager::GetOpenUserCount() const {
    return std::count_if(profiles.begin(), profiles.end(),
                         [](const ProfileInfo& p) { return p.is_open; });
}

/// Checks if a user id exists in our profile manager
bool ProfileManager::UserExists(UUID uuid) const {
    return (GetUserIndex(uuid) != boost::none);
}

/// Opens a specific user
void ProfileManager::OpenUser(UUID uuid) {
    auto idx = GetUserIndex(uuid);
    if (idx == boost::none) {
        return;
    }
    profiles[idx.get()].is_open = true;
    last_opened_user = uuid;
}

/// Closes a specific user
void ProfileManager::CloseUser(UUID uuid) {
    auto idx = GetUserIndex(uuid);
    if (idx == boost::none) {
        return;
    }
    profiles[idx.get()].is_open = false;
}

/// Gets all valid user ids on the system
UserIDArray ProfileManager::GetAllUsers() const {
    UserIDArray output;
    std::transform(profiles.begin(), profiles.end(), output.begin(),
                   [](const ProfileInfo& p) { return p.user_uuid; });
    return output;
}

/// Get all the open users on the system and zero out the rest of the data. This is specifically
/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out
UserIDArray ProfileManager::GetOpenUsers() const {
    UserIDArray output;
    std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) {
        if (p.is_open)
            return p.user_uuid;
        return UUID{};
    });
    std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; });
    return output;
}

/// Returns the last user which was opened
UUID ProfileManager::GetLastOpenedUser() const {
    return last_opened_user;
}

/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
                                           ProfileData& data) const {
    if (GetProfileBase(index, profile)) {
        data = profiles[index.get()].data;
        return true;
    }
    return false;
}

/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
                                           ProfileData& data) const {
    auto idx = GetUserIndex(uuid);
    return GetProfileBaseAndData(idx, profile, data);
}

/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
                                           ProfileData& data) const {
    return GetProfileBaseAndData(user.user_uuid, profile, data);
}

/// Returns if the system is allowing user registrations or not
bool ProfileManager::CanSystemRegisterUser() const {
    return false; // TODO(ogniK): Games shouldn't have
                  // access to user registration, when we
    // emulate qlaunch. Update this to dynamically change.
}

}; // namespace Service::Account