Преглед изворни кода

account: Adds AccountManager (#2184)

* account: Adds Account Manager

In a way to have Custom User Profiles merged in master faster, this PR adds a `AccountManager` class (based on `AccountUtils` class) and the following changes have been made:
- Adds a "default profile values" which were the old hardcoded ones.
- The image profile is moved to the Account service folder.
- The hardcoded UserId for the savedata is now using the `AccountManager` last opened one.
- The DeviceId in Mii service is changed to the right value (checked by REd sys:set call).

* Fix csproj

* Addresses gdkchan's comments

* Fix UserProfile fields

* Fix mii GetDeviceId()

* Update Ryujinx.HLE.csproj
Ac_K пре 5 година
родитељ
комит
7344dee475

+ 1 - 1
Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs

@@ -40,7 +40,7 @@ namespace Ryujinx.HLE.HOS.Applets
 
         private byte[] BuildResponse()
         {
-            UserProfile currentUser = _system.State.Account.LastOpenedUser;
+            UserProfile currentUser = _system.AccountManager.LastOpenedUser;
 
             using (MemoryStream stream = new MemoryStream())
             using (BinaryWriter writer = new BinaryWriter(stream))

+ 1 - 1
Ryujinx.HLE/HOS/ApplicationLoader.cs

@@ -648,7 +648,7 @@ namespace Ryujinx.HLE.HOS
         {
             Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
 
-            Uid user = _device.System.State.Account.LastOpenedUser.UserId.ToLibHacUid();
+            Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
 
             ref ApplicationControlProperty control = ref ControlData.Value;
 

+ 4 - 1
Ryujinx.HLE/HOS/Horizon.cs

@@ -18,6 +18,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.HOS.Kernel.Threading;
 using Ryujinx.HLE.HOS.Services;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
 using Ryujinx.HLE.HOS.Services.Apm;
 using Ryujinx.HLE.HOS.Services.Arp;
@@ -86,6 +87,7 @@ namespace Ryujinx.HLE.HOS
         internal KSharedMemory IirsSharedMem { get; private set; }
         internal SharedFontManager Font { get; private set; }
 
+        internal AccountManager AccountManager { get; private set; }
         internal ContentManager ContentManager { get; private set; }
         internal CaptureManager CaptureManager { get; private set; }
 
@@ -110,7 +112,7 @@ namespace Ryujinx.HLE.HOS
         internal LibHac.Horizon LibHacHorizonServer { get; private set; }
         internal HorizonClient LibHacHorizonClient { get; private set; }
 
-        public Horizon(Switch device, ContentManager contentManager, MemoryConfiguration memoryConfiguration)
+        public Horizon(Switch device, ContentManager contentManager, AccountManager accountManager, MemoryConfiguration memoryConfiguration)
         {
             KernelContext = new KernelContext(
                 device,
@@ -165,6 +167,7 @@ namespace Ryujinx.HLE.HOS
 
             DisplayResolutionChangeEvent = new KEvent(KernelContext);
 
+            AccountManager = accountManager;
             ContentManager = contentManager;
             CaptureManager = new CaptureManager(device);
 

+ 14 - 6
Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs → Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs

@@ -1,23 +1,31 @@
-using System.Collections.Concurrent;
+using Ryujinx.Common;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 
 namespace Ryujinx.HLE.HOS.Services.Account.Acc
 {
-    public class AccountUtils
+    public class AccountManager
     {
         private ConcurrentDictionary<string, UserProfile> _profiles;
 
-        internal UserProfile LastOpenedUser { get; private set; }
+        public UserProfile LastOpenedUser { get; private set; }
 
-        public AccountUtils()
+        public AccountManager()
         {
             _profiles = new ConcurrentDictionary<string, UserProfile>();
+
+            UserId defaultUserId    = new UserId("00000000000000010000000000000000");
+            byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg");
+
+            AddUser(defaultUserId, "Player", defaultUserImage);
+            
+            OpenUser(defaultUserId);
         }
 
-        public void AddUser(UserId userId, string name)
+        public void AddUser(UserId userId, string name, byte[] image)
         {
-            UserProfile profile = new UserProfile(userId, name);
+            UserProfile profile = new UserProfile(userId, name, image);
 
             _profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile);
         }

+ 13 - 16
Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs

@@ -1,8 +1,6 @@
 using Ryujinx.Common.Logging;
 using Ryujinx.Cpu;
 using Ryujinx.HLE.Utilities;
-using System.IO;
-using System.Reflection;
 using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
@@ -10,12 +8,10 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
     class ProfileServer
     {
         private UserProfile _profile;
-        private Stream      _profilePictureStream;
 
         public ProfileServer(UserProfile profile)
         {
-            _profile              = profile;
-            _profilePictureStream = Assembly.GetCallingAssembly().GetManifestResourceStream("Ryujinx.HLE.RyujinxProfileImage.jpg");
+            _profile = profile;
         }
 
         public ResultCode Get(ServiceCtx context)
@@ -54,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
 
         public ResultCode GetImageSize(ServiceCtx context)
         {
-            context.ResponseData.Write(_profilePictureStream.Length);
+            context.ResponseData.Write(_profile.Image.Length);
 
             return ResultCode.Success;
         }
@@ -64,13 +60,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
             long bufferPosition = context.Request.ReceiveBuff[0].Position;
             long bufferLen      = context.Request.ReceiveBuff[0].Size;
 
-            byte[] profilePictureData = new byte[bufferLen];
+            if (_profile.Image.Length > bufferLen)
+            {
+                return ResultCode.InvalidBufferSize;
+            }
 
-            _profilePictureStream.Read(profilePictureData, 0, profilePictureData.Length);
+            context.Memory.Write((ulong)bufferPosition, _profile.Image);
 
-            context.Memory.Write((ulong)bufferPosition, profilePictureData);
-
-            context.ResponseData.Write(_profilePictureStream.Length);
+            context.ResponseData.Write(_profile.Image.Length);
 
             return ResultCode.Success;
         }
@@ -100,16 +97,16 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
 
             context.Memory.Read((ulong)userDataPosition, userData);
 
-            long profilePicturePosition = context.Request.SendBuff[0].Position;
-            long profilePictureSize     = context.Request.SendBuff[0].Size;
+            long profileImagePosition = context.Request.SendBuff[0].Position;
+            long profileImageSize     = context.Request.SendBuff[0].Size;
 
-            byte[] profilePictureData = new byte[profilePictureSize];
+            byte[] profileImageData = new byte[profileImageSize];
 
-            context.Memory.Read((ulong)profilePicturePosition, profilePictureData);
+            context.Memory.Read((ulong)profileImagePosition, profileImageData);
 
             // TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata.
 
-            Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { userDataSize, profilePictureSize });
+            Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { userDataSize, profileImageSize });
 
             return ResultCode.Success;
         }

+ 12 - 12
Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs

@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
 
         public ResultCode GetUserCountImpl(ServiceCtx context)
         {
-            context.ResponseData.Write(context.Device.System.State.Account.GetUserCount());
+            context.ResponseData.Write(context.Device.System.AccountManager.GetUserCount());
 
             return ResultCode.Success;
         }
@@ -31,26 +31,26 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
                 return resultCode;
             }
 
-            context.ResponseData.Write(context.Device.System.State.Account.TryGetUser(userId, out _));
+            context.ResponseData.Write(context.Device.System.AccountManager.TryGetUser(userId, out _));
 
             return ResultCode.Success;
         }
 
         public ResultCode ListAllUsers(ServiceCtx context)
         {
-            return WriteUserList(context, context.Device.System.State.Account.GetAllUsers());
+            return WriteUserList(context, context.Device.System.AccountManager.GetAllUsers());
         }
 
         public ResultCode ListOpenUsers(ServiceCtx context)
         {
-            return WriteUserList(context, context.Device.System.State.Account.GetOpenedUsers());
+            return WriteUserList(context, context.Device.System.AccountManager.GetOpenedUsers());
         }
 
         private ResultCode WriteUserList(ServiceCtx context, IEnumerable<UserProfile> profiles)
         {
             if (context.Request.RecvListBuff.Count == 0)
             {
-                return ResultCode.InvalidInputBuffer;
+                return ResultCode.InvalidBuffer;
             }
 
             long outputPosition = context.Request.RecvListBuff[0].Position;
@@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
 
         public ResultCode GetLastOpenedUser(ServiceCtx context)
         {
-            context.Device.System.State.Account.LastOpenedUser.UserId.Write(context.ResponseData);
+            context.Device.System.AccountManager.LastOpenedUser.UserId.Write(context.ResponseData);
 
             return ResultCode.Success;
         }
@@ -94,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
                 return resultCode;
             }
 
-            if (!context.Device.System.State.Account.TryGetUser(userId, out UserProfile userProfile))
+            if (!context.Device.System.AccountManager.TryGetUser(userId, out UserProfile userProfile))
             {
                 Logger.Warning?.Print(LogClass.ServiceAcc, $"User 0x{userId} not found!");
 
@@ -118,7 +118,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
 
         public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
         {
-            if (context.Device.System.State.Account.GetUserCount() != 1)
+            if (context.Device.System.AccountManager.GetUserCount() != 1)
             {
                 // Invalid UserId.
                 UserId.Null.Write(context.ResponseData);
@@ -137,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             }
 
             // NOTE: As we returned an invalid UserId if there is more than one user earlier, now we can return only the first one.
-            context.Device.System.State.Account.GetFirst().UserId.Write(context.ResponseData);
+            context.Device.System.AccountManager.GetFirst().UserId.Write(context.ResponseData);
 
             return ResultCode.Success;
         }
@@ -153,7 +153,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
 
             if (context.Request.SendBuff.Count == 0)
             {
-                return ResultCode.InvalidInputBuffer;
+                return ResultCode.InvalidBuffer;
             }
 
             long inputPosition = context.Request.SendBuff[0].Position;
@@ -161,7 +161,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
 
             if (inputSize != 0x24000)
             {
-                return ResultCode.InvalidInputBufferSize;
+                return ResultCode.InvalidBufferSize;
             }
 
             byte[] thumbnailBuffer = new byte[inputSize];
@@ -205,7 +205,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
         {
             // TODO: Determine how users are "qualified". We assume all users are "qualified" for now.
 
-            return WriteUserList(context, context.Device.System.State.Account.GetAllUsers());
+            return WriteUserList(context, context.Device.System.AccountManager.GetAllUsers());
         }
 
         public ResultCode CheckUserId(ServiceCtx context, out UserId userId)

+ 0 - 0
Ryujinx.HLE/RyujinxProfileImage.jpg → Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg


+ 1 - 1
Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs

@@ -111,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
         {
             UserId userId = context.RequestData.ReadStruct<UserId>();
 
-            if (!context.Device.System.State.Account.TryGetUser(userId, out UserProfile userProfile))
+            if (!context.Device.System.AccountManager.TryGetUser(userId, out UserProfile userProfile))
             {
                 Logger.Warning?.Print(LogClass.ServiceAcc, $"User 0x{userId} not found!");
 

+ 10 - 7
Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs

@@ -1,26 +1,29 @@
-using Ryujinx.HLE.Utilities;
-using System;
+using System;
 
 namespace Ryujinx.HLE.HOS.Services.Account.Acc
 {
-    class UserProfile
+    public class UserProfile
     {
         private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
 
-        public UserId UserId { get; private set; }
+        public UserId UserId { get; }
 
-        public string Name { get; private set; }
+        public string Name { get; }
+
+        public byte[] Image { get; }
 
         public long LastModifiedTimestamp { get; private set; }
 
-        public AccountState AccountState    { get; set; }
+        public AccountState AccountState { get; set; }
         public AccountState OnlinePlayState { get; set; }
 
-        public UserProfile(UserId userId, string name)
+        public UserProfile(UserId userId, string name, byte[] image)
         {
             UserId = userId;
             Name   = name;
 
+            Image = image;
+
             LastModifiedTimestamp = 0;
 
             AccountState    = AccountState.Closed;

+ 12 - 12
Ryujinx.HLE/HOS/Services/Account/ResultCode.cs

@@ -7,17 +7,17 @@ namespace Ryujinx.HLE.HOS.Services.Account
 
         Success = 0,
 
-        NullArgument                         = (20  << ErrorCodeShift) | ModuleId,
-        InvalidArgument                      = (22  << ErrorCodeShift) | ModuleId,
-        NullInputBuffer                      = (30  << ErrorCodeShift) | ModuleId,
-        InvalidInputBufferSize               = (31  << ErrorCodeShift) | ModuleId,
-        InvalidInputBuffer                   = (32  << ErrorCodeShift) | ModuleId,
-        AsyncExecutionNotInitialized         = (40  << ErrorCodeShift) | ModuleId,
-        Unknown41                            = (41  << ErrorCodeShift) | ModuleId,
-        InternetRequestDenied                = (59  << ErrorCodeShift) | ModuleId,
-        UserNotFound                         = (100 << ErrorCodeShift) | ModuleId,
-        NullObject                           = (302 << ErrorCodeShift) | ModuleId,
-        Unknown341                           = (341 << ErrorCodeShift) | ModuleId,
-        InvalidIdTokenCacheBufferSize        = (451 << ErrorCodeShift) | ModuleId
+        NullArgument                  = (20  << ErrorCodeShift) | ModuleId,
+        InvalidArgument               = (22  << ErrorCodeShift) | ModuleId,
+        NullInputBuffer               = (30  << ErrorCodeShift) | ModuleId,
+        InvalidBufferSize             = (31  << ErrorCodeShift) | ModuleId,
+        InvalidBuffer                 = (32  << ErrorCodeShift) | ModuleId,
+        AsyncExecutionNotInitialized  = (40  << ErrorCodeShift) | ModuleId,
+        Unknown41                     = (41  << ErrorCodeShift) | ModuleId,
+        InternetRequestDenied         = (59  << ErrorCodeShift) | ModuleId,
+        UserNotFound                  = (100 << ErrorCodeShift) | ModuleId,
+        NullObject                    = (302 << ErrorCodeShift) | ModuleId,
+        Unknown341                    = (341 << ErrorCodeShift) | ModuleId,
+        InvalidIdTokenCacheBufferSize = (451 << ErrorCodeShift) | ModuleId
     }
 }

+ 1 - 1
Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs

@@ -58,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
                     break;
                 case LaunchParameterKind.PreselectedUser:
                     // Only the first 0x18 bytes of the Data seems to be actually used.
-                    storageData = StorageHelper.MakeLaunchParams(context.Device.System.State.Account.LastOpenedUser);
+                    storageData = StorageHelper.MakeLaunchParams(context.Device.System.AccountManager.LastOpenedUser);
                     break;
                 case LaunchParameterKind.Unknown:
                     throw new NotImplementedException("Unknown LaunchParameterKind.");

+ 2 - 2
Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs

@@ -150,7 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
                 return ResultCode.InvalidArgument;
             }
 
-            if (context.Device.System.State.Account.TryGetUser(userId, out UserProfile profile))
+            if (context.Device.System.AccountManager.TryGetUser(userId, out UserProfile profile))
             {
                 profile.OnlinePlayState = AccountState.Open;
             }
@@ -171,7 +171,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
                 return ResultCode.InvalidArgument;
             }
 
-            if (context.Device.System.State.Account.TryGetUser(userId, out UserProfile profile))
+            if (context.Device.System.AccountManager.TryGetUser(userId, out UserProfile profile))
             {
                 profile.OnlinePlayState = AccountState.Closed;
             }

+ 2 - 3
Ryujinx.HLE/HOS/Services/Mii/Helper.cs

@@ -1,5 +1,4 @@
-using Ryujinx.HLE.HOS.SystemState;
-using Ryujinx.HLE.Utilities;
+using Ryujinx.HLE.Utilities;
 using System;
 using System.Buffers.Binary;
 
@@ -32,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
         public static UInt128 GetDeviceId()
         {
             // FIXME: call set:sys GetMiiAuthorId
-            return SystemStateMgr.DefaultUserId.ToUInt128();
+            return new UInt128(0, 1);
         }
 
         public static ReadOnlySpan<byte> Ver3FacelineColorTable => new byte[] { 0, 1, 2, 3, 4, 5 };

+ 1 - 1
Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs

@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
 
             if (byUserId)
             {
-                if (!context.Device.System.State.Account.TryGetUser(userId, out _))
+                if (!context.Device.System.AccountManager.TryGetUser(userId, out _))
                 {
                     return ResultCode.UserNotFound;
                 }

+ 0 - 10
Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs

@@ -1,12 +1,9 @@
-using Ryujinx.HLE.HOS.Services.Account.Acc;
 using System;
 
 namespace Ryujinx.HLE.HOS.SystemState
 {
     public class SystemStateMgr
     {
-        public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
-
         internal static string[] LanguageCodes = new string[]
         {
             "ja",
@@ -46,15 +43,8 @@ namespace Ryujinx.HLE.HOS.SystemState
 
         public bool InstallContents { get; set; }
 
-        public AccountUtils Account { get; private set; }
-
         public SystemStateMgr()
         {
-            Account = new AccountUtils();
-
-            Account.AddUser(DefaultUserId, "Player");
-            Account.OpenUser(DefaultUserId);
-
             // TODO: Let user specify.
             DesiredKeyboardLayout = (long)KeyboardLayout.Default;
         }

+ 2 - 2
Ryujinx.HLE/Ryujinx.HLE.csproj

@@ -32,12 +32,12 @@
 
   <ItemGroup>
     <None Remove="Homebrew.npdm" />
-    <None Remove="RyujinxProfileImage.jpg" />
+    <None Remove="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
   </ItemGroup>
 
   <ItemGroup>
     <EmbeddedResource Include="Homebrew.npdm" />
-    <EmbeddedResource Include="RyujinxProfileImage.jpg" />
+    <EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
   </ItemGroup>
 
 </Project>

+ 3 - 1
Ryujinx.HLE/Switch.cs

@@ -13,6 +13,7 @@ using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.HLE.HOS.Services.Apm;
 using Ryujinx.HLE.HOS.Services.Hid;
 using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices;
@@ -57,6 +58,7 @@ namespace Ryujinx.HLE
         public Switch(
             VirtualFileSystem fileSystem,
             ContentManager contentManager,
+            AccountManager accountManager,
             UserChannelPersistence userChannelPersistence,
             IRenderer renderer,
             IHardwareDeviceDriver audioDeviceDriver,
@@ -112,7 +114,7 @@ namespace Ryujinx.HLE
 
             FileSystem = fileSystem;
 
-            System = new Horizon(this, contentManager, memoryConfiguration);
+            System = new Horizon(this, contentManager, accountManager, memoryConfiguration);
             System.InitializeServices();
 
             Statistics = new PerformanceStatistics();

+ 5 - 2
Ryujinx/Ui/MainWindow.cs

@@ -3,7 +3,6 @@ using ARMeilleure.Translation.PTC;
 using Gtk;
 using LibHac.Common;
 using LibHac.Ns;
-using Ryujinx.Audio;
 using Ryujinx.Audio.Backends.Dummy;
 using Ryujinx.Audio.Backends.OpenAL;
 using Ryujinx.Audio.Backends.SoundIo;
@@ -17,6 +16,7 @@ using Ryujinx.Graphics.OpenGL;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem.Content;
 using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.Modules;
 using Ryujinx.Ui.App;
 using Ryujinx.Ui.Applet;
@@ -42,6 +42,7 @@ namespace Ryujinx.Ui
     {
         private readonly VirtualFileSystem _virtualFileSystem;
         private readonly ContentManager    _contentManager;
+        private readonly AccountManager    _accountManager;
 
         private UserChannelPersistence _userChannelPersistence;
 
@@ -135,6 +136,7 @@ namespace Ryujinx.Ui
             // Instanciate HLE objects.
             _virtualFileSystem      = VirtualFileSystem.CreateInstance();
             _contentManager         = new ContentManager(_virtualFileSystem);
+            _accountManager         = new AccountManager();
             _userChannelPersistence = new UserChannelPersistence();
 
             // Instanciate GUI objects.
@@ -344,6 +346,7 @@ namespace Ryujinx.Ui
             _emulationContext = new HLE.Switch(
                 _virtualFileSystem,
                 _contentManager,
+                _accountManager,
                 _userChannelPersistence,
                 renderer,
                 deviceDriver,
@@ -942,7 +945,7 @@ namespace Ryujinx.Ui
 
             BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
 
-            _ = new GameTableContextMenu(this, _virtualFileSystem, titleFilePath, titleName, titleId, controlData);
+            _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, titleFilePath, titleName, titleId, controlData);
         }
 
         private void Load_Application_File(object sender, EventArgs args)

+ 5 - 2
Ryujinx/Ui/Widgets/GameTableContextMenu.cs

@@ -13,6 +13,7 @@ using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
 using Ryujinx.Ui.Helper;
 using Ryujinx.Ui.Windows;
 using System;
@@ -31,6 +32,7 @@ namespace Ryujinx.Ui.Widgets
     {
         private readonly MainWindow                             _parent;
         private readonly VirtualFileSystem                      _virtualFileSystem;
+        private readonly AccountManager                         _accountManager;
         private readonly BlitStruct<ApplicationControlProperty> _controlData;
 
         private readonly string _titleFilePath;
@@ -41,13 +43,14 @@ namespace Ryujinx.Ui.Widgets
         private MessageDialog _dialog;
         private bool          _cancel;
 
-        public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
+        public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
         {
             _parent = parent;
 
             InitializeComponent();
 
             _virtualFileSystem = virtualFileSystem;
+            _accountManager    = accountManager;
             _titleFilePath     = titleFilePath;
             _titleName         = titleName;
             _titleIdText       = titleId;
@@ -429,7 +432,7 @@ namespace Ryujinx.Ui.Widgets
         private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
         {
             SaveDataFilter saveDataFilter = new SaveDataFilter();
-            saveDataFilter.SetUserId(new UserId(1, 0)); // TODO: Remove Hardcoded value.
+            saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low));
 
             OpenSaveDir(saveDataFilter);
         }