Better support for user accounts (#349)

* Better support for user accounts

* Nits

* Check for invalid ids
This commit is contained in:
gdkchan 2018-08-14 19:02:42 -03:00 committed by GitHub
parent 17f54b5d78
commit 9ac5583513
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 314 additions and 136 deletions

View file

@ -2,6 +2,7 @@ using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.SystemState;
using System;
using System.Collections.Concurrent;
using System.IO;

View file

@ -242,6 +242,7 @@ namespace Ryujinx.HLE.OsHle.Kernel
Process.Scheduler.Suspend(CurrThread);
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
Thread.Yield();

View file

@ -11,6 +11,7 @@ using Ryujinx.HLE.OsHle.Exceptions;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Kernel;
using Ryujinx.HLE.OsHle.Services.Nv;
using Ryujinx.HLE.OsHle.SystemState;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.OsHle
{
public struct Profile
{
public string Username;
public string UserId;
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.OsHle.Services.Acc
{
static class AccErr
{
public const int UserNotFound = 100;
}
}

View file

@ -1,7 +1,11 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.SystemState;
using System.Collections.Generic;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Services.Acc
{
class IAccountServiceForApplication : IpcService
@ -27,49 +31,80 @@ namespace Ryujinx.HLE.OsHle.Services.Acc
public long GetUserCount(ServiceCtx Context)
{
Context.ResponseData.Write(0);
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
Context.ResponseData.Write(Context.Ns.Os.SystemState.GetUserCount());
return 0;
}
public long GetUserExistence(ServiceCtx Context)
{
Context.ResponseData.Write(1);
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
Context.ResponseData.Write(Context.Ns.Os.SystemState.TryGetUser(Uuid, out _) ? 1 : 0);
return 0;
}
public long ListAllUsers(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return 0;
return WriteUserList(Context, Context.Ns.Os.SystemState.GetAllUsers());
}
public long ListOpenUsers(ServiceCtx Context)
{
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
return WriteUserList(Context, Context.Ns.Os.SystemState.GetOpenUsers());
}
private long WriteUserList(ServiceCtx Context, IEnumerable<UserProfile> Profiles)
{
long OutputPosition = Context.Request.RecvListBuff[0].Position;
long OutputSize = Context.Request.RecvListBuff[0].Size;
long Offset = 0;
foreach (UserProfile Profile in Profiles)
{
if ((ulong)Offset + 16 > (ulong)OutputSize)
{
break;
}
byte[] Uuid = Profile.Uuid.Bytes;
for (int Index = Uuid.Length - 1; Index >= 0; Index--)
{
Context.Memory.WriteByte(OutputPosition + Offset++, Uuid[Index]);
}
}
return 0;
}
public long GetLastOpenedUser(ServiceCtx Context)
{
Context.ResponseData.Write(1L);
Context.ResponseData.Write(0L);
UserProfile LastOpened = Context.Ns.Os.SystemState.LastOpenUser;
Context.Ns.Log.PrintStub(LogClass.ServiceAcc, "Stubbed.");
LastOpened.Uuid.Write(Context.ResponseData);
return 0;
}
public long GetProfile(ServiceCtx Context)
{
MakeObject(Context, new IProfile());
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
if (!Context.Ns.Os.SystemState.TryGetUser(Uuid, out UserProfile Profile))
{
Context.Ns.Log.PrintWarning(LogClass.ServiceAcc, $"User 0x{Uuid} not found!");
return MakeError(ErrorModule.Account, AccErr.UserNotFound);
}
MakeObject(Context, new IProfile(Profile));
return 0;
}

View file

@ -1,6 +1,7 @@
using ChocolArm64.Memory;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.SystemState;
using Ryujinx.HLE.OsHle.Utilities;
using System.Collections.Generic;
using System.Text;
@ -13,13 +14,17 @@ namespace Ryujinx.HLE.OsHle.Services.Acc
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
public IProfile()
private UserProfile Profile;
public IProfile(UserProfile Profile)
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
{ 0, Get },
{ 1, GetBase }
};
this.Profile = Profile;
}
public long Get(ServiceCtx Context)
@ -32,20 +37,18 @@ namespace Ryujinx.HLE.OsHle.Services.Acc
Context.Memory.WriteInt32(Position, 0);
Context.Memory.WriteInt32(Position + 4, 1);
Context.Memory.WriteByte(Position + 8, 1);
Context.Memory.WriteInt64(Position + 8, 1);
return GetBase(Context);
}
public long GetBase(ServiceCtx Context)
{
ProfileBase ProfileBase = new ProfileBase(Context.Ns.Settings.User);
Profile.Uuid.Write(Context.ResponseData);
Context.ResponseData.Write(ProfileBase.UserId.ToBytes());
Context.ResponseData.Write(ProfileBase.Timestamp);
Context.ResponseData.Write(Profile.LastModifiedTimestamp);
int ByteCount = Encoding.UTF8.GetByteCount(ProfileBase.Username);
byte[] Username = StringUtils.GetFixedLengthBytes(ProfileBase.Username, 0x20, Encoding.UTF8);
byte[] Username = StringUtils.GetFixedLengthBytes(Profile.Name, 0x20, Encoding.UTF8);
Context.ResponseData.Write(Username);

View file

@ -1,52 +0,0 @@
using Ryujinx.HLE.OsHle.Utilities;
using System;
using System.Linq;
namespace Ryujinx.HLE.OsHle.Services.Acc
{
struct ProfileBase
{
public UserId UserId;
public long Timestamp;
public string Username;
public ProfileBase(Profile User)
{
UserId = new UserId(User.UserId);
Username = User.Username;
Timestamp = ((DateTimeOffset)DateTime.Today).ToUnixTimeSeconds();
}
}
struct UserId
{
private readonly ulong LowBytes;
private readonly ulong HighBytes;
public UserId(string UserIdHex)
{
if (UserIdHex == null || UserIdHex.Length != 32 || !UserIdHex.All("0123456789abcdefABCDEF".Contains))
{
throw new ArgumentException("UserId is not a valid Hex string", "UserIdHex");
}
byte[] HexBytes = StringUtils.HexToBytes(UserIdHex);
LowBytes = BitConverter.ToUInt64(HexBytes, 8);
Array.Resize(ref HexBytes, 8);
HighBytes = BitConverter.ToUInt64(HexBytes, 0);
}
public byte[] ToBytes()
{
return BitConverter.GetBytes(HighBytes).Concat(BitConverter.GetBytes(LowBytes)).ToArray();
}
public override string ToString()
{
return BitConverter.ToString(ToBytes()).ToLower().Replace("-", string.Empty);
}
}
}

View file

@ -3,7 +3,6 @@ using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic;
using static Ryujinx.HLE.OsHle.SystemStateMgr;
using static Ryujinx.HLE.OsHle.ErrorCode;
namespace Ryujinx.HLE.OsHle.Services.Am
@ -58,7 +57,9 @@ namespace Ryujinx.HLE.OsHle.Services.Am
public long GetOperationMode(ServiceCtx Context)
{
OperationMode Mode = DockedMode ? OperationMode.Docked : OperationMode.Handheld;
OperationMode Mode = Context.Ns.Os.SystemState.DockedMode
? OperationMode.Docked
: OperationMode.Handheld;
Context.ResponseData.Write((byte)Mode);
@ -67,7 +68,9 @@ namespace Ryujinx.HLE.OsHle.Services.Am
public long GetPerformanceMode(ServiceCtx Context)
{
Apm.PerformanceMode Mode = DockedMode ? Apm.PerformanceMode.Docked : Apm.PerformanceMode.Handheld;
Apm.PerformanceMode Mode = Context.Ns.Os.SystemState.DockedMode
? Apm.PerformanceMode.Docked
: Apm.PerformanceMode.Handheld;
Context.ResponseData.Write((int)Mode);

View file

@ -1,6 +1,7 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.SystemState;
using System.Collections.Generic;
using System.Text;

View file

@ -1,4 +1,6 @@
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.SystemState;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Friend
@ -13,8 +15,35 @@ namespace Ryujinx.HLE.OsHle.Services.Friend
{
m_Commands = new Dictionary<int, ServiceProcessRequest>()
{
//...
{ 10601, DeclareCloseOnlinePlaySession },
{ 10610, UpdateUserPresence }
};
}
public long DeclareCloseOnlinePlaySession(ServiceCtx Context)
{
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
if (Context.Ns.Os.SystemState.TryGetUser(Uuid, out UserProfile Profile))
{
Profile.OnlinePlayState = OpenCloseState.Closed;
}
return 0;
}
public long UpdateUserPresence(ServiceCtx Context)
{
UserId Uuid = new UserId(
Context.RequestData.ReadInt64(),
Context.RequestData.ReadInt64());
//TODO.
Context.Ns.Log.PrintStub(LogClass.ServiceFriend, "Stubbed.");
return 0;
}
}
}

View file

@ -1,4 +1,5 @@
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.OsHle.SystemState;
using System.Collections.Generic;
namespace Ryujinx.HLE.OsHle.Services.Set

View file

@ -1,5 +1,5 @@
using Ryujinx.HLE.OsHle.Ipc;
using Ryujinx.HLE.Settings;
using Ryujinx.HLE.OsHle.SystemState;
using System;
using System.Collections.Generic;
using System.IO;
@ -75,7 +75,7 @@ namespace Ryujinx.HLE.OsHle.Services.Set
public static long GetColorSetId(ServiceCtx Context)
{
Context.ResponseData.Write((int)Context.Ns.Settings.ThemeColor);
Context.ResponseData.Write((int)Context.Ns.Os.SystemState.ThemeColor);
return 0;
}
@ -84,7 +84,8 @@ namespace Ryujinx.HLE.OsHle.Services.Set
{
int ColorSetId = Context.RequestData.ReadInt32();
Context.Ns.Settings.ThemeColor = (ColorSet)ColorSetId;
Context.Ns.Os.SystemState.ThemeColor = (ColorSet)ColorSetId;
return 0;
}
@ -121,6 +122,7 @@ namespace Ryujinx.HLE.OsHle.Services.Set
SettingBuffer = Encoding.ASCII.GetBytes(StringValue + "\0");
}
}
if (NxSetting is int IntValue)
{
SettingBuffer = BitConverter.GetBytes(IntValue);

View file

@ -3,7 +3,7 @@ using Ryujinx.HLE.OsHle.Services.Am;
using System;
using System.Collections.Concurrent;
namespace Ryujinx.HLE.OsHle
namespace Ryujinx.HLE.OsHle.SystemState
{
class AppletStateMgr : IDisposable
{

View file

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.Settings
namespace Ryujinx.HLE.OsHle.SystemState
{
public enum ColorSet
{

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.OsHle.SystemState
{
public enum OpenCloseState
{
Closed,
Open
}
}

View file

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.OsHle
namespace Ryujinx.HLE.OsHle.SystemState
{
public enum SystemLanguage
{

View file

@ -1,7 +1,9 @@
using Ryujinx.HLE.Loaders.Npdm;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.HLE.OsHle
namespace Ryujinx.HLE.OsHle.SystemState
{
public class SystemStateMgr
{
@ -36,14 +38,27 @@ namespace Ryujinx.HLE.OsHle
internal long DesiredLanguageCode { get; private set; }
internal string ActiveAudioOutput { get; private set; }
public static bool DockedMode { get; set; }
public bool DockedMode { get; set; }
public ColorSet ThemeColor { get; set; }
private ConcurrentDictionary<string, UserProfile> Profiles;
internal UserProfile LastOpenUser { get; private set; }
public SystemStateMgr()
{
SetLanguage(SystemLanguage.AmericanEnglish);
SetAudioOutputAsBuiltInSpeaker();
Profiles = new ConcurrentDictionary<string, UserProfile>();
UserId DefaultUuid = new UserId("00000000000000000000000000000001");
AddUser(DefaultUuid, "Player");
OpenUser(DefaultUuid);
}
public void SetLanguage(SystemLanguage Language)
@ -66,6 +81,49 @@ namespace Ryujinx.HLE.OsHle
ActiveAudioOutput = AudioOutputs[2];
}
public void AddUser(UserId Uuid, string Name)
{
UserProfile Profile = new UserProfile(Uuid, Name);
Profiles.AddOrUpdate(Uuid.UserIdHex, Profile, (Key, Old) => Profile);
}
public void OpenUser(UserId Uuid)
{
if (Profiles.TryGetValue(Uuid.UserIdHex, out UserProfile Profile))
{
(LastOpenUser = Profile).AccountState = OpenCloseState.Open;
}
}
public void CloseUser(UserId Uuid)
{
if (Profiles.TryGetValue(Uuid.UserIdHex, out UserProfile Profile))
{
Profile.AccountState = OpenCloseState.Closed;
}
}
public int GetUserCount()
{
return Profiles.Count;
}
internal bool TryGetUser(UserId Uuid, out UserProfile Profile)
{
return Profiles.TryGetValue(Uuid.UserIdHex, out Profile);
}
internal IEnumerable<UserProfile> GetAllUsers()
{
return Profiles.Values;
}
internal IEnumerable<UserProfile> GetOpenUsers()
{
return Profiles.Values.Where(x => x.AccountState == OpenCloseState.Open);
}
internal static long GetLanguageCode(int Index)
{
if ((uint)Index >= LanguageCodes.Length)

View file

@ -0,0 +1,76 @@
using Ryujinx.HLE.OsHle.Utilities;
using System;
using System.IO;
using System.Linq;
namespace Ryujinx.HLE.OsHle.SystemState
{
public struct UserId
{
public string UserIdHex { get; private set; }
public byte[] Bytes { get; private set; }
public UserId(long Low, long High)
{
if ((Low | High) == 0)
{
throw new ArgumentException("Zero is not a valid user id!");
}
byte[] Bytes = new byte[16];
int Index = Bytes.Length;
void WriteBytes(long Value)
{
for (int Byte = 0; Byte < 8; Byte++)
{
Bytes[--Index] = (byte)(Value >> Byte * 8);
}
}
WriteBytes(Low);
WriteBytes(High);
UserIdHex = string.Empty;
foreach (byte Byte in Bytes)
{
UserIdHex += Byte.ToString("X2");
}
this.Bytes = Bytes;
}
public UserId(string UserIdHex)
{
if (UserIdHex == null || UserIdHex.Length != 32 || !UserIdHex.All("0123456789abcdefABCDEF".Contains))
{
throw new ArgumentException("Invalid user id!", nameof(UserIdHex));
}
if (UserIdHex == "00000000000000000000000000000000")
{
throw new ArgumentException("Zero is not a valid user id!", nameof(UserIdHex));
}
this.UserIdHex = UserIdHex.ToUpper();
Bytes = StringUtils.HexToBytes(UserIdHex);
}
internal void Write(BinaryWriter Writer)
{
for (int Index = Bytes.Length - 1; Index >= 0; Index--)
{
Writer.Write(Bytes[Index]);
}
}
public override string ToString()
{
return UserIdHex;
}
}
}

View file

@ -0,0 +1,36 @@
using System;
namespace Ryujinx.HLE.OsHle.SystemState
{
class UserProfile
{
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public UserId Uuid { get; private set; }
public string Name { get; private set; }
public long LastModifiedTimestamp { get; private set; }
public OpenCloseState AccountState { get; set; }
public OpenCloseState OnlinePlayState { get; set; }
public UserProfile(UserId Uuid, string Name)
{
this.Uuid = Uuid;
this.Name = Name;
LastModifiedTimestamp = 0;
AccountState = OpenCloseState.Closed;
OnlinePlayState = OpenCloseState.Closed;
UpdateTimestamp();
}
private void UpdateTimestamp()
{
LastModifiedTimestamp = (long)(DateTime.Now - Epoch).TotalSeconds;
}
}
}

View file

@ -11,13 +11,13 @@ namespace Ryujinx.HLE.OsHle.Utilities
{
InputString = InputString + "\0";
int ByteCount = Encoding.GetByteCount(InputString);
int BytesCount = Encoding.GetByteCount(InputString);
byte[] Output = new byte[Size];
if (ByteCount < Size)
if (BytesCount < Size)
{
Encoding.GetBytes(InputString, 0, InputString.Length, Output, Size - ByteCount);
Encoding.GetBytes(InputString, 0, InputString.Length, Output, 0);
}
else
{
@ -35,15 +35,14 @@ namespace Ryujinx.HLE.OsHle.Utilities
public static byte[] HexToBytes(string HexString)
{
//Ignore last charactor if HexLength % 2 != 0
//Ignore last charactor if HexLength % 2 != 0.
int BytesInHex = HexString.Length / 2;
byte[] Output = new byte[BytesInHex];
for (int Index = 0; Index < BytesInHex; Index++)
{
Output[Index] = byte.Parse(HexString.Substring(Index * 2, 2),
NumberStyles.HexNumber);
Output[Index] = byte.Parse(HexString.Substring(Index * 2, 2), NumberStyles.HexNumber);
}
return Output;

View file

@ -1,11 +0,0 @@
using System.Collections.Generic;
using Ryujinx.HLE.OsHle;
namespace Ryujinx.HLE.Settings
{
public class SystemSettings
{
public Profile User { get; set; }
public ColorSet ThemeColor { get; set; }
}
}

View file

@ -5,7 +5,6 @@ using Ryujinx.HLE.Gpu;
using Ryujinx.HLE.Input;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle;
using Ryujinx.HLE.Settings;
using System;
namespace Ryujinx.HLE
@ -22,8 +21,6 @@ namespace Ryujinx.HLE
public Horizon Os { get; private set; }
public SystemSettings Settings { get; private set; }
public PerformanceStatistics Statistics { get; private set; }
public Hid Hid { get; private set; }
@ -54,8 +51,6 @@ namespace Ryujinx.HLE
Os = new Horizon(this);
Settings = new SystemSettings();
Statistics = new PerformanceStatistics();
Hid = new Hid(Log);
@ -67,12 +62,6 @@ namespace Ryujinx.HLE
Os.FontSharedMem.MemoryMapped += Font.ShMemMap;
Os.FontSharedMem.MemoryUnmapped += Font.ShMemUnmap;
Settings.User = new Profile()
{
Username = "Ryujinx",
UserId = "000123456789abcdef09876543210000"
};
}
public void LoadCart(string ExeFsDir, string RomFsFile = null)

View file

@ -1,5 +1,6 @@
using Ryujinx.UI.Input;
using Ryujinx.HLE;
using Ryujinx.HLE.Logging;
using Ryujinx.UI.Input;
using System;
using System.Globalization;
using System.Collections.Generic;
@ -7,8 +8,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using static Ryujinx.HLE.OsHle.SystemStateMgr;
namespace Ryujinx
{
public static class Config
@ -16,7 +15,7 @@ namespace Ryujinx
public static JoyConKeyboard JoyConKeyboard { get; private set; }
public static JoyConController JoyConController { get; private set; }
public static void Read(Logger Log)
public static void Read(Switch Device)
{
string IniFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
@ -28,13 +27,13 @@ namespace Ryujinx
GraphicsConfig.ShadersDumpPath = Parser.Value("Graphics_Shaders_Dump_Path");
Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")));
Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub")));
Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info")));
Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")));
Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error")));
DockedMode = Convert.ToBoolean(Parser.Value("Docked_Mode"));
Device.Log.SetEnable(LogLevel.Debug, Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")));
Device.Log.SetEnable(LogLevel.Stub, Convert.ToBoolean(Parser.Value("Logging_Enable_Stub")));
Device.Log.SetEnable(LogLevel.Info, Convert.ToBoolean(Parser.Value("Logging_Enable_Info")));
Device.Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")));
Device.Log.SetEnable(LogLevel.Error, Convert.ToBoolean(Parser.Value("Logging_Enable_Error")));
Device.Os.SystemState.DockedMode = Convert.ToBoolean(Parser.Value("Docked_Mode"));
string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries);
@ -46,7 +45,7 @@ namespace Ryujinx
{
foreach (LogClass Class in Enum.GetValues(typeof(LogClass)))
{
Log.SetEnable(Class, false);
Device.Log.SetEnable(Class, false);
}
}
@ -58,7 +57,7 @@ namespace Ryujinx
{
if (Class.ToString().ToLower().Contains(LogClass.Trim().ToLower()))
{
Log.SetEnable(Class, true);
Device.Log.SetEnable(Class, true);
}
}
}

View file

@ -20,7 +20,7 @@ namespace Ryujinx
Switch Ns = new Switch(Renderer, AudioOut);
Config.Read(Ns.Log);
Config.Read(Ns);
Ns.Log.Updated += ConsoleLog.PrintLog;