mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-09-27 18:10:04 +02:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c62976f290 | ||
|
|
25e88be7f1 | ||
|
|
abc03d7325 | ||
|
|
096e054bb3 | ||
|
|
0f5aea82a0 | ||
|
|
1385c60875 | ||
|
|
a0ad6f2246 | ||
|
|
a412999eeb | ||
|
|
87605ebce2 | ||
|
|
b67e8df788 |
@@ -38,7 +38,7 @@
|
|||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.3-ryujinx.1" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.3-ryujinx.1" />
|
||||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
|
||||||
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.707.0" />
|
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.918.0" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
|
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
|
||||||
|
|||||||
@@ -166,13 +166,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
|||||||
|
|
||||||
string[] buttons = GetButtonsText(module, description, "DlgBtn");
|
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)
|
if (showDetails)
|
||||||
{
|
{
|
||||||
message = GetMessageText(module, description, "FlvMsg");
|
message = GetMessageText(module, description, "FlvMsg");
|
||||||
buttons = GetButtonsText(module, description, "FlvBtn");
|
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;
|
_normalSession = normalSession;
|
||||||
_interactiveSession = interactiveSession;
|
_interactiveSession = interactiveSession;
|
||||||
|
|
||||||
// TODO(jduncanator): Parse PlayerSelectConfig from input data
|
UserProfile selected = _system.Device.UIHandler.ShowPlayerSelectDialog();
|
||||||
|
if (selected == null)
|
||||||
|
{
|
||||||
_normalSession.Push(BuildResponse());
|
_normalSession.Push(BuildResponse());
|
||||||
|
}
|
||||||
|
else if (selected.UserId == new UserId("00000000000000000000000000000080"))
|
||||||
|
{
|
||||||
|
_normalSession.Push(BuildGuestResponse());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_normalSession.Push(BuildResponse(selected));
|
||||||
|
}
|
||||||
AppletStateChanged?.Invoke(this, null);
|
AppletStateChanged?.Invoke(this, null);
|
||||||
|
|
||||||
_system.ReturnFocus();
|
_system.ReturnFocus();
|
||||||
@@ -37,16 +47,34 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] BuildResponse()
|
private byte[] BuildResponse(UserProfile selectedUser)
|
||||||
{
|
{
|
||||||
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
|
||||||
|
|
||||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||||
using BinaryWriter writer = new(stream);
|
using BinaryWriter writer = new(stream);
|
||||||
|
|
||||||
writer.Write((ulong)PlayerSelectResult.Success);
|
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();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
{
|
{
|
||||||
MemoryArrange.MemoryArrange4GiB or
|
MemoryArrange.MemoryArrange4GiB or
|
||||||
MemoryArrange.MemoryArrange4GiBSystemDev or
|
MemoryArrange.MemoryArrange4GiBSystemDev or
|
||||||
MemoryArrange.MemoryArrange6GiBAppletDev => 3173 * MiB,
|
MemoryArrange.MemoryArrange6GiBAppletDev => 3236 * MiB,
|
||||||
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
|
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
|
||||||
MemoryArrange.MemoryArrange6GiB => 4804 * MiB,
|
MemoryArrange.MemoryArrange6GiB => 4867 * MiB,
|
||||||
MemoryArrange.MemoryArrange8GiB => 6852 * MiB,
|
MemoryArrange.MemoryArrange8GiB => 6915 * MiB,
|
||||||
MemoryArrange.MemoryArrange10GiB => 8900 * MiB,
|
MemoryArrange.MemoryArrange10GiB => 8963 * MiB,
|
||||||
MemoryArrange.MemoryArrange12GiB => 10948 * MiB,
|
MemoryArrange.MemoryArrange12GiB => 11011 * MiB,
|
||||||
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
|
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
heapRegion.Size = memConfig switch {
|
heapRegion.Size = memConfig switch {
|
||||||
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
||||||
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
||||||
|
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
|
||||||
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
||||||
|
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
|
||||||
_ => 0x180000000u
|
_ => 0x180000000u
|
||||||
};
|
};
|
||||||
stackRegion.Size = 0;
|
stackRegion.Size = 0;
|
||||||
@@ -238,7 +240,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
heapRegion.Size = memConfig switch {
|
heapRegion.Size = memConfig switch {
|
||||||
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
||||||
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
||||||
|
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
|
||||||
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
||||||
|
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
|
||||||
_ => 0x180000000u
|
_ => 0x180000000u
|
||||||
};
|
};
|
||||||
stackRegion.Size = 1UL << (addressSpaceWidth - 8);
|
stackRegion.Size = 1UL << (addressSpaceWidth - 8);
|
||||||
@@ -257,7 +261,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
heapRegion.Size = memConfig switch {
|
heapRegion.Size = memConfig switch {
|
||||||
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
MemoryConfiguration.MemoryConfiguration8GiB => 0x200000000u,
|
||||||
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
MemoryConfiguration.MemoryConfiguration10GiB when !isHeapLimited => 0x280000000u,
|
||||||
|
MemoryConfiguration.MemoryConfiguration10GiB => 0x200000000u,
|
||||||
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
MemoryConfiguration.MemoryConfiguration12GiB when !isHeapLimited => 0x300000000u,
|
||||||
|
MemoryConfiguration.MemoryConfiguration12GiB => 0x200000000u,
|
||||||
_ => 0x180000000u
|
_ => 0x180000000u
|
||||||
};
|
};
|
||||||
stackRegion.Size = 0x80000000;
|
stackRegion.Size = 0x80000000;
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
+15
@@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
return ResultCode.Success;
|
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)]
|
[CommandCmif(10)]
|
||||||
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
||||||
public ResultCode CreateStorage(ServiceCtx context)
|
public ResultCode CreateStorage(ServiceCtx context)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using LibHac.Tools.Fs;
|
|||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -23,7 +24,62 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
private ulong _latestPid;
|
private ulong _latestPid;
|
||||||
|
|
||||||
public ProcessResult ActiveApplication => _processesByPid[_latestPid];
|
private readonly object _pidLock = new();
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
public ProcessResult? ActiveApplication
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_pidLock)
|
||||||
|
{
|
||||||
|
// Check if _latestPid is still valid
|
||||||
|
if (_latestPid == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify process still exists in kernel (authoritative source)
|
||||||
|
if (!_device.System.KernelContext.Processes.TryGetValue(_latestPid, out HOS.Kernel.Process.KProcess? kernelProcess))
|
||||||
|
{
|
||||||
|
// Process no longer exists in kernel, clear stale state
|
||||||
|
Logger.Warning?.Print(LogClass.Loader,
|
||||||
|
$"ActiveApplication PID {_latestPid} no longer exists in kernel, clearing stale state");
|
||||||
|
|
||||||
|
_processesByPid.TryRemove(_latestPid, out _);
|
||||||
|
_latestPid = 0;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify process still exists in ProcessLoader's dictionary
|
||||||
|
if (_processesByPid.TryGetValue(_latestPid, out ProcessResult? processResult))
|
||||||
|
{
|
||||||
|
// Additional check: verify process state
|
||||||
|
if (kernelProcess.State == HOS.Kernel.Process.ProcessState.Exited ||
|
||||||
|
kernelProcess.State == HOS.Kernel.Process.ProcessState.Exiting)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Loader,
|
||||||
|
$"ActiveApplication PID {_latestPid} is in state {kernelProcess.State}, clearing");
|
||||||
|
|
||||||
|
_processesByPid.TryRemove(_latestPid, out _);
|
||||||
|
_latestPid = 0;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return processResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: clear stale PID if not in our dictionary
|
||||||
|
Logger.Warning?.Print(LogClass.Loader,
|
||||||
|
$"ActiveApplication PID {_latestPid} not in ProcessLoader dictionary, clearing");
|
||||||
|
_latestPid = 0;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
public ProcessLoader(Switch device)
|
public ProcessLoader(Switch device)
|
||||||
{
|
{
|
||||||
@@ -226,7 +282,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly null TitleId to disable the shader cache.
|
// Explicitly null TitleId to disable the shader cache.
|
||||||
Graphics.Gpu.GraphicsConfig.TitleId = null;
|
GraphicsConfig.TitleId = null;
|
||||||
_device.Gpu.HostInitalized.Set();
|
_device.Gpu.HostInitalized.Set();
|
||||||
|
|
||||||
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
|
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
|
||||||
@@ -265,5 +321,37 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears a specific process from the ProcessLoader's tracking.
|
||||||
|
/// This should be called when a process exits or is terminated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pid">The process ID to clear</param>
|
||||||
|
public void ClearProcess(ulong pid)
|
||||||
|
{
|
||||||
|
lock (_pidLock)
|
||||||
|
{
|
||||||
|
if (_processesByPid.TryRemove(pid, out _))
|
||||||
|
{
|
||||||
|
if (_latestPid == pid)
|
||||||
|
{
|
||||||
|
_latestPid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all processes from the ProcessLoader's tracking.
|
||||||
|
/// This should be called during system shutdown.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAllProcesses()
|
||||||
|
{
|
||||||
|
lock (_pidLock)
|
||||||
|
{
|
||||||
|
_processesByPid.Clear();
|
||||||
|
_latestPid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
||||||
<EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
|
<EmbeddedResource Include="HOS\Services\Account\Acc\DefaultUserImage.jpg" />
|
||||||
|
<EmbeddedResource Include="HOS\Services\Account\Acc\GuestUserImage.jpg" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ namespace Ryujinx.HLE
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
|
Processes.ClearAllProcesses();
|
||||||
System.Dispose();
|
System.Dispose();
|
||||||
AudioDeviceDriver.Dispose();
|
AudioDeviceDriver.Dispose();
|
||||||
FileSystem.Dispose();
|
FileSystem.Dispose();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.HLE.HOS.Applets;
|
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.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.UI
|
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.
|
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Creates a handler to process keyboard inputs into text strings.
|
/// 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.
|
/// Takes a screenshot from the current renderer and saves it in the screenshots folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void TakeScreenshot();
|
void TakeScreenshot();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays the player select dialog and returns the selected profile.
|
||||||
|
/// </summary>
|
||||||
|
UserProfile ShowPlayerSelectDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Ryujinx.Graphics.GAL.Multithreading;
|
|||||||
using Ryujinx.Graphics.Gpu;
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.Graphics.OpenGL;
|
using Ryujinx.Graphics.OpenGL;
|
||||||
using Ryujinx.HLE.HOS.Applets;
|
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.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||||
using Ryujinx.HLE.Loaders.Processes;
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
@@ -28,6 +29,7 @@ using static SDL.SDL3;
|
|||||||
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
|
||||||
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
|
||||||
using Switch = Ryujinx.HLE.Switch;
|
using Switch = Ryujinx.HLE.Switch;
|
||||||
|
using UserProfile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||||
|
|
||||||
namespace Ryujinx.Headless
|
namespace Ryujinx.Headless
|
||||||
{
|
{
|
||||||
@@ -531,7 +533,7 @@ namespace Ryujinx.Headless
|
|||||||
Exit();
|
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];
|
SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
|
||||||
|
|
||||||
@@ -590,5 +592,10 @@ namespace Ryujinx.Headless
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserProfile ShowPlayerSelectDialog()
|
||||||
|
{
|
||||||
|
return AccountSaveDataManager.GetLastUsedUser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,4 +169,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
|
<TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="UI\Applet\UserSelectorDialog.axaml.cs">
|
||||||
|
<DependentUpon>UserSelectorDialog.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Gommon;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE;
|
using Ryujinx.HLE;
|
||||||
using Ryujinx.HLE.HOS.Applets;
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
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.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Applet
|
namespace Ryujinx.Ava.UI.Applet
|
||||||
@@ -215,7 +221,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
_parent.ViewModel.AppHost?.Stop();
|
_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);
|
ManualResetEvent dialogCloseEvent = new(false);
|
||||||
|
|
||||||
@@ -256,9 +262,59 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
return showDetails;
|
return showDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDynamicTextInputHandler CreateDynamicTextInputHandler()
|
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
|
||||||
|
|
||||||
|
public UserProfile ShowPlayerSelectDialog()
|
||||||
{
|
{
|
||||||
return new AvaloniaDynamicTextInputHandler(_parent);
|
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()
|
public void TakeScreenshot()
|
||||||
|
|||||||
@@ -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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user