diff options
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Account')
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; } |
