diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
index 05bddc76f..cf99b0e7a 100644
--- a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
@@ -26,10 +26,20 @@ namespace Ryujinx.HLE.HOS.Applets
{
_normalSession = normalSession;
_interactiveSession = interactiveSession;
-
- // TODO(jduncanator): Parse PlayerSelectConfig from input data
- _normalSession.Push(BuildResponse());
-
+
+ UserProfile selected = _system.Device.UIHandler.ShowPlayerSelectDialog();
+ if (selected == null)
+ {
+ _normalSession.Push(BuildResponse());
+ }
+ else if (selected.UserId == new UserId("00000000000000000000000000000080"))
+ {
+ _normalSession.Push(BuildGuestResponse());
+ }
+ else
+ {
+ _normalSession.Push(BuildResponse(selected));
+ }
AppletStateChanged?.Invoke(this, null);
_system.ReturnFocus();
@@ -37,16 +47,34 @@ namespace Ryujinx.HLE.HOS.Applets
return ResultCode.Success;
}
- private byte[] BuildResponse()
+ private byte[] BuildResponse(UserProfile selectedUser)
{
- UserProfile currentUser = _system.AccountManager.LastOpenedUser;
-
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
using BinaryWriter writer = new(stream);
writer.Write((ulong)PlayerSelectResult.Success);
- currentUser.UserId.Write(writer);
+ selectedUser.UserId.Write(writer);
+
+ return stream.ToArray();
+ }
+
+ private byte[] BuildGuestResponse()
+ {
+ using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
+ using BinaryWriter writer = new(stream);
+
+ writer.Write(new byte());
+
+ return stream.ToArray();
+ }
+
+ private byte[] BuildResponse()
+ {
+ using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
+ using BinaryWriter writer = new(stream);
+
+ writer.Write((ulong)PlayerSelectResult.Failure);
return stream.ToArray();
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg b/src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg
new file mode 100644
index 000000000..8310994de
Binary files /dev/null and b/src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg differ
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index d42ecf8b8..f551f1a18 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -48,6 +48,7 @@
+
diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
index 88af83735..8ccb5cf89 100644
--- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs
+++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
namespace Ryujinx.HLE.UI
@@ -59,5 +60,11 @@ namespace Ryujinx.HLE.UI
/// Gets fonts and colors used by the host.
///
IHostUITheme HostUITheme { get; }
+
+
+ ///
+ /// Displays the player select dialog and returns the selected profile.
+ ///
+ UserProfile ShowPlayerSelectDialog();
}
}
diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs
index 7017d1f59..068c32062 100644
--- a/src/Ryujinx/Headless/Windows/WindowBase.cs
+++ b/src/Ryujinx/Headless/Windows/WindowBase.cs
@@ -9,6 +9,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.UI;
using Ryujinx.Input;
@@ -26,6 +27,7 @@ using static SDL2.SDL;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
+using UserProfile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Headless
{
@@ -555,5 +557,10 @@ namespace Ryujinx.Headless
SDL2Driver.Instance.Dispose();
}
}
+
+ public UserProfile ShowPlayerSelectDialog()
+ {
+ return AccountSaveDataManager.GetLastUsedUser();
+ }
}
}
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index 8929ba3eb..8c72d5a3c 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -173,4 +173,10 @@
+
+
+ UserSelectorDialog.axaml
+ Code
+
+
diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
index 65f4c7795..2c63f6af0 100644
--- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
+++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
@@ -1,17 +1,24 @@
using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
+using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration;
+using Ryujinx.Common;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.UI;
using System;
+using System.Collections.ObjectModel;
+using System.Linq;
using System.Threading;
namespace Ryujinx.Ava.UI.Applet
@@ -253,5 +260,59 @@ namespace Ryujinx.Ava.UI.Applet
}
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
+
+ public UserProfile ShowPlayerSelectDialog()
+ {
+ UserId selected = UserId.Null;
+ byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
+ UserProfile guest = new UserProfile(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
+
+ ManualResetEvent dialogCloseEvent = new(false);
+
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ {
+ ObservableCollection profiles = [];
+ NavigationDialogHost nav = new();
+
+ _parent.AccountManager.GetAllUsers()
+ .OrderBy(x => x.Name)
+ .ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
+
+ profiles.Add(new Models.UserProfile(guest, nav));
+ UserSelectorDialogViewModel viewModel = new();
+ viewModel.Profiles = profiles;
+ viewModel.SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId;
+ UserSelectorDialog content = new(viewModel);
+ (UserId id, _) = await UserSelectorDialog.ShowInputDialog(content);
+
+ selected = id;
+
+ dialogCloseEvent.Set();
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ UserProfile profile = _parent.AccountManager.LastOpenedUser;
+ if (selected == guest.UserId)
+ {
+ profile = guest;
+ }
+ else if (selected == UserId.Null)
+ {
+ profile = null;
+ }
+ else
+ {
+ foreach (UserProfile p in _parent.AccountManager.GetAllUsers())
+ {
+ if (p.UserId == selected)
+ {
+ profile = p;
+ break;
+ }
+ }
+ }
+ return profile;
+ }
}
}
diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml
new file mode 100644
index 000000000..ed22fb088
--- /dev/null
+++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs
new file mode 100644
index 000000000..6e25588ec
--- /dev/null
+++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs
@@ -0,0 +1,123 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.UI.ViewModels.Input;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
+using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
+
+namespace Ryujinx.Ava.UI.Applet
+{
+ public partial class UserSelectorDialog : UserControl, INotifyPropertyChanged
+ {
+ public UserSelectorDialogViewModel ViewModel { get; set; }
+
+ public UserSelectorDialog(UserSelectorDialogViewModel viewModel)
+ {
+ InitializeComponent();
+ ViewModel = viewModel;
+ DataContext = ViewModel;
+ }
+
+ private void Grid_PointerEntered(object sender, PointerEventArgs e)
+ {
+ if (sender is Grid { DataContext: UserProfile profile })
+ {
+ profile.IsPointerOver = true;
+ }
+ }
+
+ private void Grid_OnPointerExited(object sender, PointerEventArgs e)
+ {
+ if (sender is Grid { DataContext: UserProfile profile })
+ {
+ profile.IsPointerOver = false;
+ }
+ }
+
+ private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (sender is ListBox listBox)
+ {
+ int selectedIndex = listBox.SelectedIndex;
+
+ if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
+ {
+ if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
+ {
+ ViewModel.SelectedUserId = userProfile.UserId;
+ Logger.Info?.Print(LogClass.UI, $"Selected user: {userProfile.UserId}");
+
+ ObservableCollection newProfiles = [];
+
+ foreach (var item in ViewModel.Profiles)
+ {
+ if (item is UserProfile originalItem)
+ {
+ var profile = new UserProfileSft(originalItem.UserId, originalItem.Name, originalItem.Image);
+
+ if (profile.UserId == ViewModel.SelectedUserId)
+ {
+ profile.AccountState = AccountState.Open;
+ }
+
+ newProfiles.Add(new UserProfile(profile, new NavigationDialogHost()));
+ }
+ }
+
+ ViewModel.Profiles = newProfiles;
+ }
+ }
+ }
+ }
+
+ public static async Task<(UserId Id, bool Result)> ShowInputDialog(UserSelectorDialog content)
+ {
+ ContentDialog contentDialog = new()
+ {
+ Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
+ PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
+ SecondaryButtonText = string.Empty,
+ CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
+ Content = content,
+ Padding = new Thickness(0)
+ };
+
+ UserId result = UserId.Null;
+ bool input = false;
+
+ void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
+ {
+ if (eventArgs.Result == ContentDialogResult.Primary)
+ {
+ if (contentDialog.Content is UserSelectorDialog view)
+ {
+ result = view.ViewModel.SelectedUserId;
+ input = true;
+ }
+ }
+ else
+ {
+ result = UserId.Null;
+ input = false;
+ }
+ }
+
+ contentDialog.Closed += Handler;
+
+ await ContentDialogHelper.ShowAsync(contentDialog);
+
+ return (result, input);
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs
new file mode 100644
index 000000000..094aed5cf
--- /dev/null
+++ b/src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs
@@ -0,0 +1,14 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System.Collections.ObjectModel;
+
+namespace Ryujinx.Ava.UI.ViewModels
+{
+ public partial class UserSelectorDialogViewModel : BaseModel
+ {
+
+ [ObservableProperty] private UserId _selectedUserId;
+
+ [ObservableProperty] private ObservableCollection _profiles = [];
+ }
+}