aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Services/Account
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Account')
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs172
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs87
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs4
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs36
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs4
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs73
7 files changed, 339 insertions, 51 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
index d36ea931..2cea57e9 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
@@ -1,41 +1,85 @@
-using Ryujinx.Common;
+using LibHac;
+using LibHac.Fs;
+using LibHac.Fs.Shim;
+using Ryujinx.Common;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.FileSystem.Content;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
public class AccountManager
{
+ public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
+
+ private readonly VirtualFileSystem _virtualFileSystem;
+ private readonly AccountSaveDataManager _accountSaveDataManager;
+
private ConcurrentDictionary<string, UserProfile> _profiles;
public UserProfile LastOpenedUser { get; private set; }
- public AccountManager()
+ public AccountManager(VirtualFileSystem virtualFileSystem)
{
+ _virtualFileSystem = virtualFileSystem;
+
_profiles = new ConcurrentDictionary<string, UserProfile>();
- UserId defaultUserId = new UserId("00000000000000010000000000000000");
- byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
+ _accountSaveDataManager = new AccountSaveDataManager(_profiles);
+
+ if (!_profiles.TryGetValue(DefaultUserId.ToString(), out _))
+ {
+ byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
- AddUser(defaultUserId, "Player", defaultUserImage);
-
- OpenUser(defaultUserId);
+ AddUser("RyuPlayer", defaultUserImage, DefaultUserId);
+
+ OpenUser(DefaultUserId);
+ }
+ else
+ {
+ OpenUser(_accountSaveDataManager.LastOpened);
+ }
}
- public void AddUser(UserId userId, string name, byte[] image)
+ public void AddUser(string name, byte[] image, UserId userId = new UserId())
{
+ if (userId.IsNull)
+ {
+ userId = new UserId(Guid.NewGuid().ToString().Replace("-", ""));
+ }
+
UserProfile profile = new UserProfile(userId, name, image);
_profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile);
+
+ _accountSaveDataManager.Save(_profiles);
}
public void OpenUser(UserId userId)
{
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
{
+ // TODO: Support multiple open users ?
+ foreach (UserProfile userProfile in GetAllUsers())
+ {
+ if (userProfile == LastOpenedUser)
+ {
+ userProfile.AccountState = AccountState.Closed;
+
+ break;
+ }
+ }
+
(LastOpenedUser = profile).AccountState = AccountState.Open;
+
+ _accountSaveDataManager.LastOpened = userId;
}
+
+ _accountSaveDataManager.Save(_profiles);
}
public void CloseUser(UserId userId)
@@ -44,9 +88,117 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
profile.AccountState = AccountState.Closed;
}
+
+ _accountSaveDataManager.Save(_profiles);
+ }
+
+ public void OpenUserOnlinePlay(UserId userId)
+ {
+ if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
+ {
+ // TODO: Support multiple open online users ?
+ foreach (UserProfile userProfile in GetAllUsers())
+ {
+ if (userProfile == LastOpenedUser)
+ {
+ userProfile.OnlinePlayState = AccountState.Closed;
+
+ break;
+ }
+ }
+
+ profile.OnlinePlayState = AccountState.Open;
+ }
+
+ _accountSaveDataManager.Save(_profiles);
+ }
+
+ public void CloseUserOnlinePlay(UserId userId)
+ {
+ if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
+ {
+ profile.OnlinePlayState = AccountState.Closed;
+ }
+
+ _accountSaveDataManager.Save(_profiles);
+ }
+
+ public void SetUserImage(UserId userId, byte[] image)
+ {
+ foreach (UserProfile userProfile in GetAllUsers())
+ {
+ if (userProfile.UserId == userId)
+ {
+ userProfile.Image = image;
+
+ break;
+ }
+ }
+
+ _accountSaveDataManager.Save(_profiles);
+ }
+
+ public void SetUserName(UserId userId, string name)
+ {
+ foreach (UserProfile userProfile in GetAllUsers())
+ {
+ if (userProfile.UserId == userId)
+ {
+ userProfile.Name = name;
+
+ break;
+ }
+ }
+
+ _accountSaveDataManager.Save(_profiles);
+ }
+
+ public void DeleteUser(UserId userId)
+ {
+ DeleteSaveData(userId);
+
+ _profiles.Remove(userId.ToString(), out _);
+
+ OpenUser(DefaultUserId);
+
+ _accountSaveDataManager.Save(_profiles);
+ }
+
+ private void DeleteSaveData(UserId userId)
+ {
+ SaveDataFilter saveDataFilter = new SaveDataFilter();
+ saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
+
+ Result result = _virtualFileSystem.FsClient.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, ref saveDataFilter);
+ if (result.IsSuccess())
+ {
+ Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
+
+ while (true)
+ {
+ saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo);
+
+ if (readCount == 0)
+ {
+ break;
+ }
+
+ for (int i = 0; i < readCount; i++)
+ {
+ // TODO: We use Directory.Delete workaround because DeleteSaveData softlock without, due to a bug in LibHac 0.12.0.
+ string savePath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataInfo[i].SaveDataId:x16}");
+ string saveMetaPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/saveMeta/{saveDataInfo[i].SaveDataId:x16}");
+
+ Directory.Delete(savePath, true);
+ Directory.Delete(saveMetaPath, true);
+
+ _virtualFileSystem.FsClient.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId);
+ }
+ }
+ }
}
- public int GetUserCount()
+ internal int GetUserCount()
{
return _profiles.Count;
}
@@ -56,7 +208,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return _profiles.TryGetValue(userId.ToString(), out profile);
}
- internal IEnumerable<UserProfile> GetAllUsers()
+ public IEnumerable<UserProfile> GetAllUsers()
{
return _profiles.Values;
}
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs
new file mode 100644
index 00000000..44ef3f33
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs
@@ -0,0 +1,87 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Utilities;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.HLE.HOS.Services.Account.Acc
+{
+ class AccountSaveDataManager
+ {
+ private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
+
+ private struct ProfilesJson
+ {
+ [JsonPropertyName("profiles")]
+ public List<UserProfileJson> Profiles { get; set; }
+ [JsonPropertyName("last_opened")]
+ public string LastOpened { get; set; }
+ }
+
+ private struct UserProfileJson
+ {
+ [JsonPropertyName("user_id")]
+ public string UserId { get; set; }
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+ [JsonPropertyName("account_state")]
+ public AccountState AccountState { get; set; }
+ [JsonPropertyName("online_play_state")]
+ public AccountState OnlinePlayState { get; set; }
+ [JsonPropertyName("last_modified_timestamp")]
+ public long LastModifiedTimestamp { get; set; }
+ [JsonPropertyName("image")]
+ public byte[] Image { get; set; }
+ }
+
+ public UserId LastOpened { get; set; }
+
+ public AccountSaveDataManager(ConcurrentDictionary<string, UserProfile> profiles)
+ {
+ // TODO: Use 0x8000000000000010 system savedata instead of a JSON file if needed.
+
+ if (File.Exists(_profilesJsonPath))
+ {
+ ProfilesJson profilesJson = JsonHelper.DeserializeFromFile<ProfilesJson>(_profilesJsonPath);
+
+ foreach (var profile in profilesJson.Profiles)
+ {
+ UserProfile addedProfile = new UserProfile(new UserId(profile.UserId), profile.Name, profile.Image, profile.LastModifiedTimestamp);
+
+ profiles.AddOrUpdate(profile.UserId, addedProfile, (key, old) => addedProfile);
+ }
+
+ LastOpened = new UserId(profilesJson.LastOpened);
+ }
+ else
+ {
+ LastOpened = AccountManager.DefaultUserId;
+ }
+ }
+
+ public void Save(ConcurrentDictionary<string, UserProfile> profiles)
+ {
+ ProfilesJson profilesJson = new ProfilesJson()
+ {
+ Profiles = new List<UserProfileJson>(),
+ LastOpened = LastOpened.ToString()
+ };
+
+ foreach (var profile in profiles)
+ {
+ profilesJson.Profiles.Add(new UserProfileJson()
+ {
+ UserId = profile.Value.UserId.ToString(),
+ Name = profile.Value.Name,
+ AccountState = profile.Value.AccountState,
+ OnlinePlayState = profile.Value.OnlinePlayState,
+ LastModifiedTimestamp = profile.Value.LastModifiedTimestamp,
+ Image = profile.Value.Image,
+ });
+ }
+
+ File.WriteAllText(_profilesJsonPath, JsonHelper.Serialize(profilesJson, true));
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs
index c7efe778..471942f1 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs
@@ -73,8 +73,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
public ResultCode LoadIdTokenCache(ServiceCtx context)
{
- long bufferPosition = context.Request.ReceiveBuff[0].Position;
- long bufferSize = context.Request.ReceiveBuff[0].Size;
+ ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
+ ulong bufferSize = context.Request.ReceiveBuff[0].Size;
// NOTE: This opens the file at "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x")
// in the "account:/" savedata and writes some data in the buffer.
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs
index 18534393..8e29f94b 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs
@@ -16,16 +16,16 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
public ResultCode Get(ServiceCtx context)
{
- context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x80L);
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x80UL);
- long bufferPosition = context.Request.RecvListBuff[0].Position;
+ ulong bufferPosition = context.Request.RecvListBuff[0].Position;
MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x80);
// TODO: Determine the struct.
- context.Memory.Write((ulong)bufferPosition, 0); // Unknown
- context.Memory.Write((ulong)bufferPosition + 4, 1); // Icon ID. 0 = Mii, the rest are character icon IDs.
- context.Memory.Write((ulong)bufferPosition + 8, (byte)1); // Profile icon background color ID
+ context.Memory.Write(bufferPosition, 0); // Unknown
+ context.Memory.Write(bufferPosition + 4, 1); // Icon ID. 0 = Mii, the rest are character icon IDs.
+ context.Memory.Write(bufferPosition + 8, (byte)1); // Profile icon background color ID
// 0x07 bytes - Unknown
// 0x10 bytes - Some ID related to the Mii? All zeros when a character icon is used.
// 0x60 bytes - Usually zeros?
@@ -57,15 +57,15 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
public ResultCode LoadImage(ServiceCtx context)
{
- long bufferPosition = context.Request.ReceiveBuff[0].Position;
- long bufferLen = context.Request.ReceiveBuff[0].Size;
+ ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
+ ulong bufferLen = context.Request.ReceiveBuff[0].Size;
- if (_profile.Image.Length > bufferLen)
+ if ((ulong)_profile.Image.Length > bufferLen)
{
return ResultCode.InvalidBufferSize;
}
- context.Memory.Write((ulong)bufferPosition, _profile.Image);
+ context.Memory.Write(bufferPosition, _profile.Image);
context.ResponseData.Write(_profile.Image.Length);
@@ -74,12 +74,12 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
public ResultCode Store(ServiceCtx context)
{
- long userDataPosition = context.Request.PtrBuff[0].Position;
- long userDataSize = context.Request.PtrBuff[0].Size;
+ ulong userDataPosition = context.Request.PtrBuff[0].Position;
+ ulong userDataSize = context.Request.PtrBuff[0].Size;
byte[] userData = new byte[userDataSize];
- context.Memory.Read((ulong)userDataPosition, userData);
+ context.Memory.Read(userDataPosition, userData);
// TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata.
@@ -90,19 +90,19 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
public ResultCode StoreWithImage(ServiceCtx context)
{
- long userDataPosition = context.Request.PtrBuff[0].Position;
- long userDataSize = context.Request.PtrBuff[0].Size;
+ ulong userDataPosition = context.Request.PtrBuff[0].Position;
+ ulong userDataSize = context.Request.PtrBuff[0].Size;
byte[] userData = new byte[userDataSize];
- context.Memory.Read((ulong)userDataPosition, userData);
+ context.Memory.Read(userDataPosition, userData);
- long profileImagePosition = context.Request.SendBuff[0].Position;
- long profileImageSize = context.Request.SendBuff[0].Size;
+ ulong profileImagePosition = context.Request.SendBuff[0].Position;
+ ulong profileImageSize = context.Request.SendBuff[0].Size;
byte[] profileImageData = new byte[profileImageSize];
- context.Memory.Read((ulong)profileImagePosition, profileImageData);
+ context.Memory.Read(profileImagePosition, profileImageData);
// TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata.
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs
index 29cce5d7..794c72ce 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs
@@ -53,8 +53,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return ResultCode.InvalidBuffer;
}
- long outputPosition = context.Request.RecvListBuff[0].Position;
- long outputSize = context.Request.RecvListBuff[0].Size;
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+ ulong outputSize = context.Request.RecvListBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
@@ -67,8 +67,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
break;
}
- context.Memory.Write((ulong)outputPosition + offset, userProfile.UserId.High);
- context.Memory.Write((ulong)outputPosition + offset + 8, userProfile.UserId.Low);
+ context.Memory.Write(outputPosition + offset, userProfile.UserId.High);
+ context.Memory.Write(outputPosition + offset + 8, userProfile.UserId.Low);
offset += 0x10;
}
@@ -156,8 +156,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return ResultCode.InvalidBuffer;
}
- long inputPosition = context.Request.SendBuff[0].Position;
- long inputSize = context.Request.SendBuff[0].Size;
+ ulong inputPosition = context.Request.SendBuff[0].Position;
+ ulong inputSize = context.Request.SendBuff[0].Size;
if (inputSize != 0x24000)
{
@@ -166,7 +166,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
byte[] thumbnailBuffer = new byte[inputSize];
- context.Memory.Read((ulong)inputPosition, thumbnailBuffer);
+ context.Memory.Read(inputPosition, thumbnailBuffer);
// NOTE: Account service call nn::fs::WriteSaveDataThumbnailFile().
// TODO: Store thumbnailBuffer somewhere, in save data 0x8000000000000010 ?
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
index 6067dc44..2fbf950c 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs
@@ -142,8 +142,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
// ListOpenContextStoredUsers() -> array<nn::account::Uid, 0xa>
public ResultCode ListOpenContextStoredUsers(ServiceCtx context)
{
- long outputPosition = context.Request.RecvListBuff[0].Position;
- long outputSize = context.Request.RecvListBuff[0].Size;
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+ ulong outputSize = context.Request.RecvListBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs
index a57796c9..ef0a1a64 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs
@@ -8,31 +8,80 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
public UserId UserId { get; }
- public string Name { get; }
+ public long LastModifiedTimestamp { get; set; }
- public byte[] Image { get; }
+ private string _name;
- public long LastModifiedTimestamp { get; private set; }
+ public string Name
+ {
+ get => _name;
+ set
+ {
+ _name = value;
+
+ UpdateLastModifiedTimestamp();
+ }
+ }
- public AccountState AccountState { get; set; }
- public AccountState OnlinePlayState { get; set; }
+ private byte[] _image;
- public UserProfile(UserId userId, string name, byte[] image)
+ public byte[] Image
{
- UserId = userId;
- Name = name;
+ get => _image;
+ set
+ {
+ _image = value;
- Image = image;
+ UpdateLastModifiedTimestamp();
+ }
+ }
+
+ private AccountState _accountState;
- LastModifiedTimestamp = 0;
+ public AccountState AccountState
+ {
+ get => _accountState;
+ set
+ {
+ _accountState = value;
+
+ UpdateLastModifiedTimestamp();
+ }
+ }
+
+ public AccountState _onlinePlayState;
+
+ public AccountState OnlinePlayState
+ {
+ get => _onlinePlayState;
+ set
+ {
+ _onlinePlayState = value;
+
+ UpdateLastModifiedTimestamp();
+ }
+ }
+
+ public UserProfile(UserId userId, string name, byte[] image, long lastModifiedTimestamp = 0)
+ {
+ UserId = userId;
+ Name = name;
+ Image = image;
AccountState = AccountState.Closed;
OnlinePlayState = AccountState.Closed;
- UpdateTimestamp();
+ if (lastModifiedTimestamp != 0)
+ {
+ LastModifiedTimestamp = lastModifiedTimestamp;
+ }
+ else
+ {
+ UpdateLastModifiedTimestamp();
+ }
}
- private void UpdateTimestamp()
+ private void UpdateLastModifiedTimestamp()
{
LastModifiedTimestamp = (long)(DateTime.Now - Epoch).TotalSeconds;
}