Author SHA1 Message Date
Coxxs 096e054bb3 Implement CreateLibraryAppletEx in ILibraryAppletCreator 2026-09-18 00:11:05 -05:00
KeatonTheBot 0f5aea82a0 Raise application pool sizes for DRAM selections (again)
- Fixes Ultracam 20+ (beta) support
2026-09-18 00:11:05 -05:00
Neo 1385c60875 HLE: Update ProcessResult in ProcessLoader.cs
Some games/applications may crash with a specific error:

00:00:00.690 |E| Application : Unhandled exception caught: System.Collections.Generic.KeyNotFoundException: The given key '0' was not present in the dictionary.

(or a different key)

However, this message is misleading and not indicative of the actual error (it masks it).

As an example, the error above was seen when attempting to load a mod onto Yo-kai Watch 1 (base game + update worked). However, after patching the ProcessResult, the resulting error was more revealing:

00:00:00.716 |E| Application : Unhandled exception caught: LibHac.Common.HorizonResultException: ResultFsInvalidCharacter (2002-6004): Error creating LocalFileSystem. at LibHac.FsSystem.LocalFileSystem..ctor(String rootPath)

(and so forth)

The actual error was due to an incorrect folder name in the Mods folder.
2026-09-17 15:18:48 -05:00
GreemDev a0ad6f2246 misc: chore: add direct error code tuple to DisplayErrorAppletDialog
for use when i find the list of error codes -> causes
2026-09-13 21:24:52 -05:00
KeatonTheBot a412999eeb Fix Skip User Profiles
This and `Add the player select applet` fixes commit c9c78841b9.
2026-09-13 20:49:31 -05:00
JacobandGreemDev 87605ebce2 Add the player select applet
This introduces the somewhat completed version of the Player Select
Applet, allowing users to select either a user or a guest from the UI.
Note: Selecting the guest more then once currently does not work.

closes https://github.com/Ryubing/Ryujinx/issues/532

- misc: chore: optimize UserSelectorDialog closed handler

- misc: chore: Rename UserSelectorDialog to ProfileSelectorDialog

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2026-09-13 20:48:20 -05:00
GreemDev b67e8df788 RenderDoc API support 2026-09-13 15:08:11 -05:00
14 changed files with 413 additions and 22 deletions
@@ -166,13 +166,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string[] buttons = GetButtonsText(module, description, "DlgBtn");
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
(uint Module, uint Description) errorCodeTuple = (module, uint.Parse(description.ToString("0000")));
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
if (showDetails)
{
message = GetMessageText(module, description, "FlvMsg");
buttons = GetButtonsText(module, description, "FlvBtn");
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
}
}
@@ -27,9 +27,19 @@ 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();
}
@@ -26,12 +26,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
{
MemoryArrange.MemoryArrange4GiB or
MemoryArrange.MemoryArrange4GiBSystemDev or
MemoryArrange.MemoryArrange6GiBAppletDev => 3173 * MiB,
MemoryArrange.MemoryArrange6GiBAppletDev => 3236 * MiB,
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
MemoryArrange.MemoryArrange6GiB => 4804 * MiB,
MemoryArrange.MemoryArrange8GiB => 6852 * MiB,
MemoryArrange.MemoryArrange10GiB => 8900 * MiB,
MemoryArrange.MemoryArrange12GiB => 10948 * MiB,
MemoryArrange.MemoryArrange6GiB => 4867 * MiB,
MemoryArrange.MemoryArrange8GiB => 6915 * MiB,
MemoryArrange.MemoryArrange10GiB => 8963 * MiB,
MemoryArrange.MemoryArrange12GiB => 11011 * MiB,
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
};
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

@@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
[CommandCmif(3)] // 20.0.0+
// CreateLibraryAppletEx(u32, u32, u64) -> object<nn::am::service::ILibraryAppletAccessor>
public ResultCode CreateLibraryAppletEx(ServiceCtx context)
{
AppletId appletId = (AppletId)context.RequestData.ReadInt32();
_ = context.RequestData.ReadInt32(); // libraryAppletMode
_ = context.RequestData.ReadUInt64(); // threadId
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
return ResultCode.Success;
}
[CommandCmif(10)]
// CreateStorage(u64) -> object<nn::am::service::IStorage>
public ResultCode CreateStorage(ServiceCtx context)
@@ -23,7 +23,16 @@ namespace Ryujinx.HLE.Loaders.Processes
private ulong _latestPid;
public ProcessResult ActiveApplication => _processesByPid[_latestPid];
public ProcessResult ActiveApplication
{
get
{
if (_latestPid != 0 && _processesByPid.TryGetValue(_latestPid, out ProcessResult process))
return process;
return null;
}
}
public ProcessLoader(Switch device)
{
+1
View File
@@ -59,6 +59,7 @@
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
<EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
<EmbeddedResource Include="HOS\Services\Account\Acc\GuestUserImage.jpg" />
</ItemGroup>
</Project>
+8 -1
View File
@@ -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
@@ -48,7 +49,8 @@ namespace Ryujinx.HLE.UI
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
/// </summary>
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
// ReSharper disable once UnusedParameter.Global
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null);
/// <summary>
/// Creates a handler to process keyboard inputs into text strings.
@@ -65,5 +67,10 @@ namespace Ryujinx.HLE.UI
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
/// </summary>
void TakeScreenshot();
/// <summary>
/// Displays the player select dialog and returns the selected profile.
/// </summary>
UserProfile ShowPlayerSelectDialog();
}
}
+8 -1
View File
@@ -10,6 +10,7 @@ using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
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.Loaders.Processes;
using Ryujinx.HLE.UI;
@@ -28,6 +29,7 @@ using static SDL.SDL3;
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
{
@@ -531,7 +533,7 @@ namespace Ryujinx.Headless
Exit();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null)
{
SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
@@ -590,5 +592,10 @@ namespace Ryujinx.Headless
{
throw new NotImplementedException();
}
public UserProfile ShowPlayerSelectDialog()
{
return AccountSaveDataManager.GetLastUsedUser();
}
}
}
+6
View File
@@ -169,4 +169,10 @@
<ItemGroup>
<TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Applet\UserSelectorDialog.axaml.cs">
<DependentUpon>UserSelectorDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project>
+61 -5
View File
@@ -1,17 +1,23 @@
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.Windows;
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 Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
namespace Ryujinx.Ava.UI.Applet
@@ -215,7 +221,7 @@ namespace Ryujinx.Ava.UI.Applet
_parent.ViewModel.AppHost?.Stop();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons, (uint Module, uint Description)? errorCode = null)
{
ManualResetEvent dialogCloseEvent = new(false);
@@ -256,11 +262,61 @@ namespace Ryujinx.Ava.UI.Applet
return showDetails;
}
public IDynamicTextInputHandler CreateDynamicTextInputHandler()
{
return new AvaloniaDynamicTextInputHandler(_parent);
}
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<BaseModel> 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));
ProfileSelectorDialogViewModel viewModel = new()
{
Profiles = profiles,
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
};
(selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel);
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;
}
public void TakeScreenshot()
{
_parent.ViewModel.AppHost.ScreenshotRequested = true;
@@ -0,0 +1,121 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Applet.ProfileSelectorDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
d:DesignHeight="450"
MinWidth="500"
d:DesignWidth="800"
mc:Ignorable="d"
Focusable="True"
x:DataType="viewModels:ProfileSelectorDialogViewModel">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Design.DataContext>
<viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
<ListBox
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="Transparent"
ItemsSource="{Binding Profiles}"
SelectionChanged="ProfilesList_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
HorizontalAlignment="Left"
VerticalAlignment="Center"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Rectangle#SelectionIndicator">
<Setter Property="Opacity" Value="0" />
</Style>
</ListBox.Styles>
<ListBox.DataTemplates>
<DataTemplate
DataType="models:UserProfile">
<Grid
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5"
Background="{Binding BackgroundColor}">
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image
Width="96"
Height="96"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<TextBlock
HorizontalAlignment="Stretch"
MaxWidth="90"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="5" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
<DataTemplate
DataType="viewModels:BaseModel">
<Panel
Height="118"
Width="96">
<Panel.Styles>
<Style Selector="Panel">
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
</Style>
</Panel.Styles>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</Border>
<StackPanel
Grid.Row="1"
Margin="0 24 0 0"
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="10">
</StackPanel>
</Grid>
</UserControl>
@@ -0,0 +1,125 @@
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.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.UI.Common.Configuration;
using System.Collections.ObjectModel;
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 ProfileSelectorDialog : UserControl
{
public ProfileSelectorDialogViewModel ViewModel { get; set; }
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{
DataContext = ViewModel = viewModel;
InitializeComponent();
}
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: {userProfile.UserId}", "ProfileSelector");
ObservableCollection<BaseModel> newProfiles = [];
foreach (BaseModel item in ViewModel.Profiles)
{
if (item is UserProfile originalItem)
{
UserProfileSft profile = new(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(ProfileSelectorDialogViewModel viewModel)
{
if (ConfigurationState.Instance.System.SkipUserProfilesManager)
{
UserId defaultId = viewModel.SelectedUserId;
return (defaultId, true);
}
FAContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
Content = new ProfileSelectorDialog(viewModel),
Padding = new Thickness(0)
};
UserId result = UserId.Null;
bool input = false;
contentDialog.Closed += Handler;
await ContentDialogHelper.ShowAsync(contentDialog);
return (result, input);
void Handler(FAContentDialog sender, FAContentDialogClosedEventArgs eventArgs)
{
if (eventArgs.Result == FAContentDialogResult.Primary)
{
result = viewModel.SelectedUserId;
input = true;
}
else
{
result = UserId.Null;
input = false;
}
}
}
}
}
@@ -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 ProfileSelectorDialogViewModel : BaseModel
{
[ObservableProperty] private UserId _selectedUserId;
[ObservableProperty] private ObservableCollection<BaseModel> _profiles = [];
}
}